/*
FILE
	$Id: lckpwdf.c 6 2005-11-24 00:02:59Z ggw $
PURPOSE
	In general we want to insure that any operations on /etc/passwd and/or
	/etc/shadow done by mysqlISP applications do not interfere with other
	OS applications like adduser etc.
	Some non linux BSD OSs don't come with lckpwdf and ulckpwdf functions that
	do provide this. From linux man pages...
		"The lckpwdf and ulckpwdf routines should be used to insure exclusive access 
		to the /etc/shadow file. lckpwdf attempts to acquire a lock using pw_lock for 
		up to 15 seconds. It continues by attempting to acquire a second lock using 
		spw_lock for the remainder of the initial 15 seconds. Should either attempt 
		fail after a total of 15 seconds, lckpwdf returns -1. When both locks are 
		acquired 0 is returned."
	Solution: We include this file with openisp.net mysqlISP family of applications
	for FreeBSD and other BSD unix variant support. Only used if makefile has
	been modified accordingly usually just by defining -DFreeBSD or
	similar compile time make define.
OTHER
	Other solutions have been reported and may be better or more compatible
	with general purpose as stated above:
	FreeBSD: 
	shell> install /usr/ports/security/cryptlib
WARNING
	Only tested on a few FreeBSD servers and kernels. More testing is needed.
	Please test and report to us: support @ openisp . net
*/


/*
 * lckpwdf.c -- prevent simultaneous updates of password files
 *
 * Before modifying any of the password files, call lckpwdf().  It may block
 * for up to 15 seconds trying to get the lock.  Return value is 0 on success
 * or -1 on failure.  When you are done, call ulckpwdf() to release the lock.
 * The lock is also released automatically when the process exits.  Only one
 * process at a time may hold the lock.
 *
 * These functions are supposed to be conformant with AT&T SVID Issue 3.
 *
 * Written by Marek Michalkiewicz <marekm@i17linuxb.ists.pwr.wroc.pl>,
 * public domain.
 */

#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

#define LOCKFILE "/etc/.pwd.lock"
#define TIMEOUT 15

static int lockfd = -1;

int lckpwdf(void);
int ulckpwdf(void);

static int
set_close_on_exec(int fd)
{
	int flags = fcntl(fd, F_GETFD, 0);
	if (flags == -1)
		return -1;
	flags |= FD_CLOEXEC;
	return fcntl(fd, F_SETFD, flags);
}

static int
do_lock(int fd)
{
	struct flock fl;

	memset(&fl, 0, sizeof fl);
	fl.l_type = F_WRLCK;
	fl.l_whence = SEEK_SET;
	return fcntl(fd, F_SETLKW, &fl);
}

static void
alarm_catch(int sig)
{
/* does nothing, but fcntl F_SETLKW will fail with EINTR */
}

int
lckpwdf(void)
{
	struct sigaction act, oldact;
	sigset_t set, oldset;

	if (lockfd != -1)
		return -1;

	lockfd = open(LOCKFILE, O_CREAT | O_WRONLY, 0600);
	if (lockfd == -1)
		return -1;
	if (set_close_on_exec(lockfd) == -1)
		goto cleanup_fd;

	memset(&act, 0, sizeof act);
	act.sa_handler = alarm_catch;
	act.sa_flags = 0;
	sigfillset(&act.sa_mask);
	if (sigaction(SIGALRM, &act, &oldact) == -1)
		goto cleanup_fd;

	sigemptyset(&set);
	sigaddset(&set, SIGALRM);
	if (sigprocmask(SIG_UNBLOCK, &set, &oldset) == -1)
		goto cleanup_sig;

	alarm(TIMEOUT);
	if (do_lock(lockfd) == -1)
		goto cleanup_alarm;
	alarm(0);
	sigprocmask(SIG_SETMASK, &oldset, NULL);
	sigaction(SIGALRM, &oldact, NULL);
	return 0;

cleanup_alarm:
	alarm(0);
	sigprocmask(SIG_SETMASK, &oldset, NULL);
cleanup_sig:
	sigaction(SIGALRM, &oldact, NULL);
cleanup_fd:
	close(lockfd);
	lockfd = -1;
	return -1;
}

int
ulckpwdf(void)
{
	if (lockfd == -1)
		return -1;

	if (close(lockfd) == -1) {
		lockfd = -1;
		return -1;
	}
	lockfd = -1;
	return 0;
}

