/* $Header: lock.c,v 1.2 88/08/30 16:13:14 network Exp $
 *
 * Mailbox locking.
 * Local hacks for mailbox access should be grafted here.
 *
 * $Log:	lock.c,v $
 *
 * Linux version 5.3b  30th June 1993
 * Added code to make setgid secure - hopefully!
 *
 * COPYRIGHT
 * Unfortunately the copyright notice for to this file appears to
 * have become separated from the file itself.
 * Unless there are any prior claims to the contrary it is hereby
 * placed under the protection of the GNU General Public License
 * version 2 (or a later version, at your option).
 *
 *  - Ian Jackson <ijackson@nyx.cs.du.edu>
 * 
 * Revision 1.2  88/08/30  16:13:14  network
 * Portability fixes from Ronald Karr <tron@uts.amdahl.com>.
 * 
 * Revision 1.1  88/06/06  09:38:48  chip
 * Initial revision
 *
 *
 * This is the lock.c file which is distributed with the deliver program.
 * It has been modified to work with ucb mail
 *	2/1/89		J. Bayer
 */


 
#include	"rcv.h"
#include	<varargs.h>
#include	<fcntl.h>
#include	<errno.h>


/*----------------------------------------------------------------------
 * Maximum filename length.
 * Note that this is for _filenames_, not _pathnames_.
 * For AT&T file systems, the usual value is 14.
 * For Berzerkley file systems, use something big like 255.
 */

#ifdef BSD
#define MAX_NAMESIZE    255
#else
#define MAX_NAMESIZE    14
#endif


static void mesg(fmt, va_alist)
char    *fmt;
va_dcl
{
	va_list args;
	va_start(args);

	(void) vfprintf(stderr, fmt, args);
}

/*
 * External data.
 */

extern  int     errno;
extern  int     sys_nerr;
extern  char    *sys_errlist[];



/*----------------------------------------------------------------------
 * Report an error returned from a system call.
 */

/* VARARGS1 */
syserr(fmt, va_alist)
char    *fmt;
va_dcl
{
char	*progname = "mail";

	int     e = errno;
	va_list args;
	va_start(args);

	(void) fprintf(stderr, "%s: ", progname);
	(void) vfprintf(stderr, fmt, args);
	if (e <= sys_nerr)
		(void) fprintf(stderr, ": %s\n", sys_errlist[e]);
	else
		(void) fprintf(stderr, ": unknown system error %d\n", e);
}

/*----------------------------------------------------------------------
 * Set the gid if the path is in the normal mail spool
 */

static int perhaps_setgid(name, gid)
     char *name;
     gid_t gid;
{
     char safepath[]= "/usr/spool/mail/";

     if (strncmp(name,safepath,sizeof(safepath)-1) ||
         strchr(name+sizeof(safepath)-1,'/')) return 0;

     if (setgid(gid) == 0) return 0;
     
     syserr("can't change gid to %d",gid);
     return -1;
}

/*----------------------------------------------------------------------
 * Sleep for the given number of seconds.
 */

snooze(n)
int     n;
{
#ifdef M_XENIX
	(void) nap(n * 1000L);
#else
	(void) sleep(n);
#endif
}

/*----------------------------------------------------------------------
 * Return the last component of the given pathname.
 */

char *
basename(name)
char    *name;
{
	char    *b;

	if ((b = strrchr(name, '/')) != NULL)
		++b;
	else
		b = name;

	return (b);
}


/*
 * Validate the locking configuration.
 */

#if (defined(ML_FCNTL) + defined(ML_LOCKF) + defined(ML_LOCKING)) > 1
  lose! "Only one of ML_FCNTL, ML_LOCKF and ML_LOCKING may be defined.";
#endif

/*
 * Support for the lockf() system call.
 */

#ifdef ML_LOCKF
#include <unistd.h>
#define SIMPLE_LOCK "lockf"
#define LOCKFD(fd, size)    lockf(fd, F_LOCK, size)
#define UNLOCKFD(fd, size)  lockf(fd, F_ULOCK, size)
#endif /* ML_LOCKF */

/*
 * Setup for the locking() system call.
 */

#ifdef ML_LOCKING
#include <sys/locking.h>
#define SIMPLE_LOCK "locking"
#define LOCKFD(fd, size)    locking(fd, LK_LOCK, size)
#define UNLOCKFD(fd, size)  locking(fd, LK_UNLOCK, size)
#endif

/*
 * Local functions.
 */

#ifdef ML_DOTLOCK
static  char    *dotlock_name();
#endif
#ifdef ML_DOTMLK
static  char    *dotmlk_name();
#endif

/*----------------------------------------------------------------------
 * Lock a mailbox by name.
 *
 * This code looks quite hairy with all the ifdefs.  In fact, the only
 * somewhat strange thing here is that neither, either, or both of
 * ML_DOTLOCK and ML_DOTMLK may be defined, and we have to allow for it.
 */

int
lock_name(name)
char    *name;
{
#ifdef ML_DOTLOCK
	char    *dotlock;
#endif
#ifdef ML_DOTMLK
	char    *dotmlk;
#endif

#ifdef ML_DOTLOCK
	if ((dotlock = dotlock_name(name)) == NULL
	 || create_lockfile(dotlock) < 0)
		return -1;
#endif /* ML_DOTLOCK */

#ifdef ML_DOTMLK
	if ((dotmlk = dotmlk_name(name)) == NULL
	 || create_lockfile(dotmlk) < 0)
	{
#ifdef ML_DOTLOCK
		(void) remove_lockfile(dotlock); /* don't leave me hanging */
#endif
		return -1;
	}
#endif /* ML_DOTMLK */

	return 0;
}

/*----------------------------------------------------------------------
 * Unlock a mailbox by name.
 */

int
unlock_name(name)
char    *name;
{
	int     ret = 0;

#ifdef ML_DOTLOCK
	char    *dotlock;
#endif
#ifdef ML_DOTMLK
	char    *dotmlk;
#endif

#ifdef ML_DOTLOCK
	if ((dotlock = dotlock_name(name)) == NULL
	 || remove_lockfile(dotlock) < 0)
		ret = -1;
#endif /* ML_DOTLOCK */

#ifdef ML_DOTMLK
	if ((dotmlk = dotmlk_name(name)) == NULL
	 || remove_lockfile(dotmlk) < 0)
		ret = -1;
#endif /* ML_DOTMLK */

	return ret;
}

/*----------------------------------------------------------------------
 * Lock a file descriptor.
 */

int
lock_fd(fd)
int     fd;
{
#ifdef ML_FCNTL
	struct flock fl;

	fl.l_type = F_WRLCK;
	fl.l_whence = 0;
	fl.l_start = 0L;
	fl.l_len = 0L;

	if (fcntl(fd, F_SETLKW, &fl) == -1)
	{
		syserr("can't lock with fcntl()");
		return -1;
	}

	if (debug)
		mesg("locked mailbox with fcntl()\n");
#endif /* ML_FCNTL */

#ifdef SIMPLE_LOCK
	long    pos;

	if ((pos = lseek(fd, 0L, 0)) == -1)
	{
		syserr("can't seek in mailbox");
		return -1;
	}
	if (LOCKFD(fd, 0L) == -1)
	{
		perror("foo");
		syserr("can't lock with %s()", SIMPLE_LOCK);
		return -1;
	}
	if (lseek(fd, pos, 0) == -1)
	{
		syserr("can't seek in mailbox");
		return -1;
	}

	if (debug)
		mesg("locked mailbox with %s()\n", SIMPLE_LOCK);
#endif /* SIMPLE_LOCK */

	/* Default: success */
	return 0;
}

/*----------------------------------------------------------------------
 * Unlock a file descriptor.
 */

int
unlock_fd(fd)
int     fd;
{
#ifdef ML_FCNTL
	struct flock fl;

	fl.l_type = F_UNLCK;
	fl.l_whence = 0;
	fl.l_start = 0L;
	fl.l_len = 0L;

	if (fcntl(fd, F_SETLKW, &fl) == -1)
	{
		syserr("can't unlock with fcntl()");
		return -1;
	}

	if (debug)
		mesg("unlocked mailbox with fcntl()\n");
#endif /* ML_FCNTL */

#ifdef SIMPLE_LOCK
	long    pos;

	if ((pos = lseek(fd, 0L, 0)) == -1)
	{
		syserr("can't seek in mailbox");
		return -1;
	}
	if (LOCKFD(fd, 0L) == -1)
	{
		syserr("can't unlock with %s()", SIMPLE_LOCK);
		return -1;
	}
	if (lseek(fd, pos, 0) == -1)
	{
		syserr("can't seek in mailbox");
		return -1;
	}

	if (debug)
		mesg("unlocked mailbox with %s()\n", SIMPLE_LOCK);
#endif /* SIMPLE_LOCK */

	/* Default: success */
	return 0;
}

/*----------------------------------------------------------------------
 * Return the name of the appropriate ".lock" file for a mailbox.
 */

#ifdef ML_DOTLOCK

static char *
dotlock_name(name)
char    *name;
{
	static char *lname = NULL;
	static int lsize = 0;
	char    *p;
	int     n, i;

	n = strlen(name);
	if (lsize < n + 8)
	{
		if (lname)
			free(lname);
		lsize = n + 32;
		/* lname = (char *)zalloc(lsize); */
		lname = (char *)malloc(lsize);
	}

	(void) strcpy(lname, name);

	/*
	 * We want as much of `basename.lock' as will fit in a string
	 * MAX_NAMESIZE long.
	 */
	for (i = 0, p = basename(lname); (i < MAX_NAMESIZE - 5) && (*p); ++i)
		++p;
	(void) strcpy(p, ".lock");

	return lname;
}

#endif /* ML_DOTLOCK */

/*----------------------------------------------------------------------
 * Return the name of the appropriate ".mlk" file for a mailbox.
 */

#ifdef ML_DOTMLK

static char *
dotmlk_name(name)
char    *name;
{
	static char lname[MAX_NAMESIZE + 16];
	char    *p, *d;
	int     i;

	/*
	 * To explain the below:  If we ass_u_me that MAX_NAMESIZE is 14,
	 * then this code is like `printf(lname, "/tmp/%.10s.mlk", ...)'.
	 * In other words, we want as much of `basename.mlk' as will fit
	 * in a string MAX_NAMESIZE long.
	 */
	d = lname;
	for (p = "/tmp/"; *p; )
		*d++ = *p++;
	for (i = 0, p = basename(name); (i < MAX_NAMESIZE - 4) && (*p); ++i)
		*d++ = *p++;
	(void) strcpy(d, ".mlk");

	return lname;
}

#endif /* ML_DOTMLK */

/*----------------------------------------------------------------------
 * Create a lockfile.
 */

int
create_lockfile(name)
char    *name;
{
	int     tries, fd;

	for (tries = 0; tries < 10; ++tries)
	{
		if (tries)
			snooze(3);

                if (perhaps_setgid(name, effectivegid)) return -1;

                fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0);

                if (perhaps_setgid(name, realgid)) return -1;
                
		if (fd >= 0) {
			(void) close(fd);
			if (debug)
				mesg("created lockfile %s\n", name);
			return 0;
		}

		if (debug && (tries == 0))
		{
			mesg("Waiting to create %s (try #%d)\n",
				name, tries + 1);
		}
	}

	syserr("can't create lockfile %s", name);
	return -1;
}

/*----------------------------------------------------------------------
 * Remove a lockfile.
 */

int
remove_lockfile(name)
char    *name;
{
        int rv, e;

        if (perhaps_setgid(name,effectivegid)) return -1;
        
        rv= unlink(name);

        if (perhaps_setgid(name,realgid)) return -1;

        if (rv == -1)
	{
		syserr("can't remove lockfile %s", name);
		return -1;
	}

	if (debug)
		mesg("removed lockfile %s\n", name);

	return 0;
}
