/* Distributed Checksum Clearinghouse * * open and lock database and whitelist files * * Copyright (c) 2006 by Rhyolite Software, LLC * * This agreement is not applicable to any entity which sells anti-spam * solutions to others or provides an anti-spam solution as part of a * security solution sold to other entities, or to a private network * which employs the DCC or uses data provided by operation of the DCC * but does not provide corresponding data to other users. * * 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. * * Parties not eligible to receive a license under this agreement can * obtain a commercial license to use DCC and permission to use * U.S. Patent 6,330,590 by contacting Commtouch at http://www.commtouch.com/ * or by email to nospam@commtouch.com. * * A commercial license would be for Distributed Checksum and Reputation * Clearinghouse software. That software includes additional features. This * free license for Distributed ChecksumClearinghouse Software does not in any * way grant permision to use Distributed Checksum and Reputation Clearinghouse * software * * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC * 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.3.50-1.53 $Revision$ */ #include "dcc_defs.h" #include "dcc_paths.h" #ifndef DCC_WIN32 uid_t dcc_real_uid, dcc_effective_uid; gid_t dcc_real_gid, dcc_effective_gid; #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()); dcc_real_gid = getgid(); if (dcc_real_gid == 0) { dcc_effective_gid = 0; } else { dcc_effective_gid = getegid(); } if (0 > setegid(dcc_real_gid)) dcc_error_msg("setegid(%d): %s", (int)dcc_real_gid, ERROR_STR()); #endif } #ifndef DCC_WIN32 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()); if (dcc_real_gid != dcc_effective_gid && 0 > setegid(dcc_effective_gid)) dcc_error_msg("setegid(%d): %s", (int)dcc_effective_gid, 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 && dcc_real_gid == dcc_effective_gid) return 0; if (!fnm2path(abs_nm, nm, 0) || !strncmp(abs_nm, "../", 3) || strncmp(abs_nm, dcc_homedir, strlen(dcc_homedir))) 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 || dcc_real_gid != dcc_effective_gid) { serrno = errno; if (0 > seteuid(dcc_real_uid)) dcc_error_msg("seteuid(%d): %s", (int)dcc_real_uid, ERROR_STR()); if (0 > setegid(dcc_real_gid)) dcc_error_msg("setegid(%d): %s", (int)dcc_real_gid, 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; DCC_PATH path; 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", fnm2path_err(path, 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", fnm2path_err(path, nm)); return 0; } #endif return 1; } #ifndef DCC_WIN32 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 lock, * The lock can optionally be a pretense. */ 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 *busyp) /* 1=file is busy */ { #ifdef DCC_WIN32 /* Win95 and Win98 do 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; DCC_PATH path; h = CreateFile(nm, (open_flags == O_RDONLY ? 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", fnm2path_err(path, nm), ERROR_STR()); if (busyp) *busyp = 0; return -1; } fd = _open_osfhandle((long)h, 0); if (fd < 0) { dcc_pemsg(EX_IOERR, emsg, "_open_osfhandle(%s): %s", nm, ERROR_STR()); CloseHandle(h); if (busyp) *busyp = 0; return -1; } if (!(lock_mode & DCC_LOCK_OPEN_NO_LOCK)) { if (!win32_lock(h, ((lock_mode & DCC_LOCK_OPEN_SHARE) ? 0 : LOCKFILE_EXCLUSIVE_LOCK) | (lock_mode & DCC_LOCK_OPEN_NOWAIT ? 0 : LOCKFILE_FAIL_IMMEDIATELY))) { dcc_pemsg(EX_IOERR, emsg, "open LockFileEx(%s): %s", fnm2path_err(path, nm), ERROR_STR()); if (busyp) *busyp = (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; DCC_PATH path; /* 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) { clean_stdio(); 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) { if (errno == EACCES) dcc_pemsg(EX_NOINPUT, emsg, "lock_open(%s): %s;" " file not writeable for locking", fnm2path_err(path, nm), ERROR_STR()); else dcc_pemsg(EX_NOINPUT, emsg, "lock_open(%s): %s", fnm2path_err(path, nm), ERROR_STR()); if (busyp) *busyp = DCC_BLOCK_ERROR(); return -1; } if (0 > fcntl(fd, F_SETFD, FD_CLOEXEC)) { if (busyp) *busyp = 0; dcc_pemsg(EX_IOERR, emsg, "fcntl(F_SETFD FD_CLOEXEC %s %d): %s", fnm2path_err(path, nm), lock_num, 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_NOLOCK) { set_fl(&fl, (lock_mode & DCC_LOCK_OPEN_SHARE) ? F_RDLCK : F_WRLCK, lock_num); if (0 > fcntl(fd, (lock_mode & DCC_LOCK_OPEN_NOWAIT ? F_SETLK : F_SETLKW), &fl)) { dcc_pemsg(EX_NOINPUT, emsg, "open fcntl(%s F_WRLCK %s %d): %s", (lock_mode & DCC_LOCK_OPEN_NOWAIT ? "F_SETLK" : "F_SETLKW"), fnm2path_err(path, nm), lock_num, ERROR_STR()); if (busyp) *busyp = (DCC_BLOCK_ERROR() || errno == EACCES); /* for SunOS */ close(fd); return -1; } } #endif /* !DCC_WIN32 or UNIX */ if (busyp) *busyp = 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 DCC_PATH path; if (!win32_unlock((HANDLE)_get_osfhandle(fd))) { dcc_pemsg(EX_NOINPUT, emsg, "UnlockFileEx(%s%s): %s", str, fnm2path_err(path, nm), ERROR_STR()); return 0; } return 1; #else struct flock fl; DCC_PATH path; 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, fnm2path_err(path, nm), lock_num, 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 DCC_PATH path; if (!win32_lock((HANDLE)_get_osfhandle(fd), 0)) { dcc_pemsg(EX_IOERR, emsg, "LockFileEx(%s %s): %s", str, fnm2path_err(path, nm), ERROR_STR()); return 0; } return 1; #else struct flock fl; DCC_PATH path; 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, fnm2path_err(path, nm), lock_num, 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 DCC_PATH path; if (!win32_lock((HANDLE)_get_osfhandle(fd), LOCKFILE_EXCLUSIVE_LOCK)) { dcc_pemsg(EX_IOERR, emsg, "LockFileEx(%s %s): %s", str, fnm2path_err(path, nm), ERROR_STR()); return 0; } return 1; #else struct flock fl; DCC_PATH path; set_fl(&fl, F_WRLCK, lock_num); if (0 > fcntl(fd, F_SETLKW, &fl)) { /* treat EINTR as an error * for the alarm via db_lock in dbclean */ dcc_pemsg(EX_NOINPUT, emsg, "fcntl(F_SETLKW F_WRLCK %s%s %d): %s", str, fnm2path_err(path, nm), lock_num, 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. */ u_char dcc_set_mtime(DCC_EMSG emsg, const char *nm, int fd UATTRIB, const struct timeval *mtime) { #undef DIDIT #if defined(HAVE_FUTIMES) && !defined(linux) && !defined(DIDIT) /* some Linux systems have a broken futimes implementation that does * not work for programs started as root but that release privileges */ struct timeval tbuf[2], *tbufp; DCC_PATH path; int result; if (mtime) { tbuf[0] = *mtime; tbuf[1] = *mtime; tbufp = tbuf; } else { tbufp = 0; } result = futimes(fd, tbufp); if (result < 0 && (errno == EACCES || errno == EPERM) && dcc_real_uid != dcc_effective_uid) { dcc_get_priv(); result = futimes(fd, tbufp); dcc_rel_priv(); } if (result < 0) dcc_pemsg(EX_IOERR, emsg,"futimes(%s): %s", fnm2path_err(path, nm), ERROR_STR()); #define DIDIT #endif /* HAVE_FUTIMES */ #if defined(HAVE_UTIME_H) && !defined(DIDIT) struct utimbuf tbuf, *tbufp; DCC_PATH path; int result; if (mtime) { tbuf.actime = tbuf.modtime = mtime->tv_sec; tbufp = &tbuf; } else { tbufp = 0; } result = utime(nm, tbufp); if (result < 0 && dcc_real_uid != dcc_effective_uid) { dcc_get_priv(); result = utime(nm, tbufp); dcc_rel_priv(); } if (result < 0) dcc_pemsg(EX_IOERR, emsg,"utime(%s): %s", fnm2path_err(path, nm), ERROR_STR()); #define DIDIT #endif /* HAVE_UTIME_H */ #ifdef DIDIT return result >= 0; #undef DIDIT #else return 1; /* WIN32 */ #endif }