/* Distributed Checksum Clearinghouse server database functions
 *
 * Copyright (c) 2005 by Rhyolite Software
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE
 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 * Rhyolite Software DCC 1.2.74-1.37 $Revision$
 */

#include "dcc_defs.h"
#include "dcc_paths.h"


#ifndef DCC_WIN32
static uid_t dcc_real_uid, dcc_effective_uid;
#endif

/* We must be SUID to read and write the system's common
 *	connection parameter memory mapped file.
 *	If the real UID is 0, then forget any SUID stuff and run as root.
 *	Otherwise remember the powerful UID and the real UID, and
 *	release the privilege of using the powerful UID */
void
dcc_init_priv(void)
{
#ifndef DCC_WIN32
	dcc_real_uid = getuid();
	if (dcc_real_uid == 0) {
		dcc_effective_uid = 0;
	} else {
		dcc_effective_uid = geteuid();
	}

	if (0 > seteuid(dcc_real_uid))
		dcc_error_msg("seteuid(%d): %s",
			      (int)dcc_real_uid, ERROR_STR());
#endif
}



#ifndef DCC_WIN32
static void
dcc_get_priv(void)
{
	if (dcc_real_uid != dcc_effective_uid
	    && 0 > seteuid(dcc_effective_uid))
		dcc_error_msg("seteuid(%d): %s",
			      (int)dcc_effective_uid, ERROR_STR());
}



/* get set-UID privilege if the file is in the DCC home directory */
u_char					/* 0=bad idea, 1=have privilege */
dcc_get_priv_home(const char *nm)
{
	DCC_PATH abs_nm;

	if (dcc_real_uid == dcc_effective_uid)
		return 0;

	dcc_fnm2path(abs_nm, nm);
	if (strncmp(abs_nm, dcc_homedir, strlen(dcc_homedir))
	    || strstr(abs_nm+strlen(dcc_homedir)-1, "/../"))
		return 0;

	dcc_get_priv();
	return 1;
}
#endif



void
dcc_rel_priv(void)
{
#ifndef DCC_WIN32
	int serrno;

	if (dcc_real_uid != dcc_effective_uid) {
		serrno = errno;
		if (0 > seteuid(dcc_real_uid))
			dcc_error_msg("seteuid(%d): %s",
				      (int)dcc_real_uid, ERROR_STR());
		errno = serrno;
	}
#endif
}



/* see if a file is private */
u_char
dcc_ck_private(DCC_EMSG emsg, struct stat *sb, const char *nm, int fd)
{
	struct stat sb0;
	int i;

	if (!sb)
		sb = &sb0;

	if (fd == -1) {
		i = stat(nm, sb);
	} else {
		i = fstat(fd, sb);
	}
	if (i < 0) {
		dcc_pemsg(EX_IOERR, emsg, "stat(%s): %s",
			  DCC_NM2PATH(nm), ERROR_STR());
		return 0;
	}
#ifdef HAVE_PRIVATE_FILES
	/* even on systems like Windows without private files,
	 * some callers want the results of the stat() */
	if ((sb->st_mode & (S_IRGRP|S_IWGRP|S_IWOTH|S_IXOTH)) != 0) {
		dcc_pemsg(EX_NOPERM, emsg,
			  "%s is not private", DCC_NM2PATH(nm));
		return 0;
	}
#endif
	return 1;
}



#ifndef DCC_WIN32
/* #define DEBUG_FLOCK */
#ifdef DEBUG_FLOCK
#define FSTAT_NM(nm) {char cmd[200];					\
	snprintf(cmd, sizeof(cmd), "fstat %s > /dev/console", nm);	\
	system(cmd);							\
}
#else
#define FSTAT_NM(nm)
#endif


static void
set_fl(struct flock *fl, int type, int byte_num)
{
	fl->l_type = type;
	fl->l_whence = SEEK_SET;
	if (byte_num == DCC_LOCK_ALL_FILE) {
		fl->l_start = 0;
		fl->l_len = 0;
	} else {
		fl->l_start = byte_num;
		fl->l_len = 1;
	}
}
#endif /* DCC_WIN32 */



/* open a file with a (possibly pretend) lock
 *	If flags does not include O_RDWR, then lock_mode must be 2 */
int					/* -1=failed & emsg set, >=0 if done */
dcc_lock_open(DCC_EMSG emsg,
	      const char *nm,
	      int open_flags,		/* eg. O_CREAT */
	      u_char lock_mode,		/* DCC_LOCK_OPEN_* */
	      int lock_num,		/* which byte of the file to lock */
	      u_char *busy)		/* 1=file is busy */
{
#ifdef DCC_WIN32
/* Win95 and Win98 does not include UNIX style file locking,
 * including non-blocking locks and upgrading locks from shared to exclusive.
 * In principle they could be constructed from the WIN32 synchronization
 * primitives and a few bytes of shared memory.  Win98 does not even
 * have a blocking file lock function.
 */
	HANDLE h;
	int fd;

	h = CreateFile(nm,
		       ((open_flags & O_RDWR)
			? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE)),
		       FILE_SHARE_READ | FILE_SHARE_WRITE, 0,
		       ((open_flags & O_EXCL)
			? CREATE_NEW
			: (open_flags & O_CREAT)
			? CREATE_ALWAYS
			: OPEN_EXISTING),
		       FILE_ATTRIBUTE_NORMAL, 0);
	if (h == INVALID_HANDLE_VALUE) {
		dcc_pemsg(EX_IOERR, emsg, "CreateFile(%s): %s",
			  DCC_NM2PATH(nm), ERROR_STR());
		if (busy)
			*busy = 0;
		return -1;
	}
	fd = _open_osfhandle((long)h, 0);
	if (fd < 0) {
		dcc_pemsg(EX_SOFTWARE, emsg, "_open_osfhandle(%s): %s",
			  nm, ERROR_STR());
		CloseHandle(h);
		if (busy)
			*busy = 0;
		return -1;
	}
	if (lock_mode != DCC_LOCK_OPEN_EXT) {
		if (!win32_lock(h, (lock_mode == DCC_LOCK_OPEN_WAIT
				    ? LOCKFILE_EXCLUSIVE_LOCK
				    : (LOCKFILE_FAIL_IMMEDIATELY
				       | LOCKFILE_EXCLUSIVE_LOCK)))) {
			if (!busy
			    || lock_mode != DCC_LOCK_OPEN_WAIT
			    || (GetLastError() != ERROR_LOCK_VIOLATION
				&& GetLastError() != ERROR_LOCK_FAILED))
				dcc_pemsg(EX_IOERR, emsg,
					  "open LockFileEx(%s): %s",
					  DCC_NM2PATH(nm), ERROR_STR());
			if (busy)
				*busy = (GetLastError() == ERROR_LOCK_VIOLATION
					 || GetLastError() ==ERROR_LOCK_FAILED);
			close(fd);
			return -1;
		}
	}

#else /* !DCC_WIN32 */
	static u_char checked_stdio = 0;
	int fd;
	struct flock fl;

	/* ensure 0, 1, and 2 are open so none of our real files get
	 * those file descriptors and are used as stdin, stdout, or stderr */
	if (!checked_stdio) {
		for (;;) {
			fd = open(_PATH_DEVNULL, O_RDWR, 0666);
			if (fd < 0)
				break;
			if (fd > 2) {
				close(fd);
				break;
			}
		}
		checked_stdio = 1;
	}

	fd = open(nm, open_flags, 0666);
	if (fd < 0 && dcc_get_priv_home(nm)) {
		fd = open(nm, open_flags, 0666);
		dcc_rel_priv();
	}
	if (fd < 0) {
		/* generate message only if necessary */
		if (!busy
		    || lock_mode != DCC_LOCK_OPEN_NOWAIT
		    || !DCC_BLOCK_ERROR()) {
			if (errno == EACCES)
				dcc_pemsg(EX_NOINPUT, emsg,
					  "lock open(%s): %s;"
					  " file not writeable for locking",
					  DCC_NM2PATH(nm), ERROR_STR());
			else
				dcc_pemsg(EX_NOINPUT, emsg,
					  "lock open(%s): %s",
					  DCC_NM2PATH(nm), ERROR_STR());
		}
		if (busy)
			*busy = DCC_BLOCK_ERROR();
		FSTAT_NM(nm);
		return -1;
	}

	if (0 > fcntl(fd, F_SETFD, FD_CLOEXEC)) {
		if (busy)
			*busy = 0;
		dcc_pemsg(EX_IOERR, emsg,
			  "fcntl(F_SETFD FD_CLOEXEC %s %d): %s",
			  DCC_NM2PATH(nm), fd, ERROR_STR());
		close(fd);
		return -1;
	}

	/* We may already have a lock on the file under a different file
	 * descriptor.  If not, try to get a lock */
	if (lock_mode != DCC_LOCK_OPEN_EXT) {
		set_fl(&fl, F_WRLCK, lock_num);
		if (0 > fcntl(fd, (lock_mode == DCC_LOCK_OPEN_WAIT
				   ? F_SETLKW : F_SETLK),
			      &fl)) {
			/* generate message only if necessary */
			if (!busy
			    || lock_mode != DCC_LOCK_OPEN_WAIT
			    || (!DCC_BLOCK_ERROR()
				&& errno != EACCES))	/* for SunOS */
				dcc_pemsg(EX_NOINPUT, emsg,
					  "open fcntl(%s F_WRLCK %s %d): %s",
					  (lock_mode == DCC_LOCK_OPEN_WAIT
					   ? "F_SETLKW" : "F_SETLK"),
					  DCC_NM2PATH(nm), fd, ERROR_STR());
			if (busy)
				*busy = (DCC_BLOCK_ERROR()
					 || errno == EACCES);	/* for SunOS */
			FSTAT_NM(nm);
			close(fd);
			return -1;
		}
	}
#endif /* !DCC_WIN32 or UNIX */

	if (busy)
		*busy = 0;
	return fd;
}



u_char					/* 1=done 0=failed */
dcc_unlock_fd(DCC_EMSG emsg, int fd,
	      int lock_num,		/* which byte of the file to unlock */
	      const char *str, const char *nm)
{
#ifdef DCC_WIN32
	if (!win32_unlock((HANDLE)_get_osfhandle(fd))) {
		dcc_pemsg(EX_NOINPUT, emsg,
			  "UnlockFileEx(%s%s): %s",
			  str, DCC_NM2PATH(nm), ERROR_STR());
		return 0;
	}
	return 1;
#else
	struct flock fl;

	set_fl(&fl, F_UNLCK, lock_num);
	if (0 > fcntl(fd, F_SETLK, &fl)) {
		dcc_pemsg(EX_NOINPUT, emsg,
			  "fcntl(F_SETLK F_UNLCK %s%s %d): %s",
			  str, DCC_NM2PATH(nm), fd, ERROR_STR());
		return 0;
	}
	return 1;
#endif /* DCC_WIN32 */
}



u_char					/* 1=done 0=failed */
dcc_shlock_fd(DCC_EMSG emsg, int fd,
	      int lock_num,		/* which byte of the file to lock */
	      const char *str, const char *nm)
{
#ifdef DCC_WIN32
	if (!win32_lock((HANDLE)_get_osfhandle(fd), 0)) {
		dcc_pemsg(EX_IOERR, emsg, "LockFileEx(%s %s): %s",
			  str, DCC_NM2PATH(nm), ERROR_STR());
		return 0;
	}
	return 1;
#else
	struct flock fl;

	set_fl(&fl, F_RDLCK, lock_num);
	if (0 > fcntl(fd, F_SETLKW, &fl)) {
		dcc_pemsg(EX_NOINPUT, emsg,
			  "fcntl(F_SETLKW F_RDLCK %s%s %d): %s",
			  str, DCC_NM2PATH(nm), fd, ERROR_STR());
		return 0;
	}
	return 1;
#endif /* DCC_WIN32 */
}



u_char					/* 1=done 0=failed */
dcc_exlock_fd(DCC_EMSG emsg, int fd,
	      int lock_num,		/* which byte of the file to lock */
	      const char *str, const char *nm)
{
#ifdef DCC_WIN32
	if (!win32_lock((HANDLE)_get_osfhandle(fd), LOCKFILE_EXCLUSIVE_LOCK)) {
		dcc_pemsg(EX_IOERR, emsg, "LockFileEx(%s %s): %s",
			  str, DCC_NM2PATH(nm), ERROR_STR());
		return 0;
	}
	return 1;
#else
	struct flock fl;

	set_fl(&fl, F_WRLCK, lock_num);
	if (0 > fcntl(fd, F_SETLKW, &fl)) {
		dcc_pemsg(EX_NOINPUT, emsg,
			  "fcntl(F_SETLKW F_WRLCK %s%s %d): %s",
			  str, DCC_NM2PATH(nm), fd, ERROR_STR());
		return 0;
	}
	return 1;
#endif /* DCC_WIN32 */
}



/* Several systems do not update the mtimes of files modified with mmap().
 * Some like BSD/OS delay changing the mtime until the file accessed with
 * read().  Others including filesystems on some versions of Linux
 * apparently never change the mtime. */
void
dcc_mmap_utime(const char *nm, u_char now)
{
#ifdef HAVE_UTIME_H
	static const struct utimbuf never = {0, 0};
	int result;

	result = utime(nm, now ? 0 : &never);
	if (result < 0
	    && dcc_real_uid != dcc_effective_uid) {
		dcc_get_priv();
		result = utime(nm, now ? 0 : &never);
		dcc_rel_priv();
	}
	if (result < 0)
		dcc_error_msg("utime(%s): %s",
			      DCC_NM2PATH(nm), ERROR_STR());

#endif
}
