|
|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T s
Length: 38164 (0x9514)
Types: TextFile
Names: »sysdep.c«
└─⟦9ae75bfbd⟧ Bits:30007242 EUUGD3: Starter Kit
└─⟦2fafebccf⟧ »EurOpenD3/mail/smail3.1.19.tar.Z«
└─⟦bcd2bc73f⟧
└─⟦this⟧ »src/sysdep.c«
/* @(#)sysdep.c 3.49 3/25/89 15:31:55 */
/*
* Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
*
* See the file COPYING, distributed with smail, for restriction
* and warranty information.
*/
/*
* sysdep.c:
* functions which may be operating system dependent.
*
* external functions: time_stamp, get_arpa_date, get_local_year,
* unix_date, compute_sender, getfullname,
* fopen_as_user, lock_file, unlock_file,
* compute_hostname, open_child, close_child,
* close_all, scan_dir, fcntl_rd_lock
*/
#include <stdio.h>
#include <sys/types.h>
#include <pwd.h>
#include <ctype.h>
#include <sys/stat.h>
#include <errno.h>
#include "defs.h"
#include "smail.h"
#include "child.h"
#include "dys.h"
#include "log.h"
#include "exitcodes.h"
#ifndef DEPEND
# include "extern.h"
# include "debug.h"
#endif
#ifdef UNIX_SYS5
# include <sys/param.h>
# include <fcntl.h>
#endif
#ifdef HAVE_UNAME
# include <sys/utsname.h>
#endif
#if defined(UNIX_BSD)
# include <sys/dir.h>
#else
# if defined(UNIX_XENIX)
# include <sys/ndir.h>
# else
# if defined(HAVE_READDIR)
# include <dirent.h>
# define direct dirent
# else
# include <sys/dir.h>
# endif /* defined(HAVE_READDIR) */
# endif /* not defined(UNIX_XENIX) */
#endif /* not defined(UNIX_BSD) */
#ifdef UNIX_BSD
# include <sys/time.h>
# include <sys/file.h>
#else
# include <time.h>
#endif
#if !defined(UNIX_BSD) && !defined(UNIX_SYS5)
/* use this for ancient ftime() system call, as a last resort */
# include <sys/timeb.h>
#endif
/* functions local to this file */
static char *get_time_zone();
static void fullname_from_gcos();
static void check_stale();
static int lock_file_by_name();
static void unlock_file_by_name();
/* imported library functions */
extern long lseek();
extern long time();
extern char *getenv();
extern char *getlogin();
extern struct tm *gmtime();
extern struct tm *localtime();
extern char *ctime();
#ifndef UNIX_SYS5
extern char *timezone();
#endif /* UNIX_SYS5 */
/* define these, if they aren't already */
#ifndef O_RDONLY
# define O_RDONLY 0
#endif
#ifndef O_WRONLY
# define O_WRONLY 1
#endif
#ifndef O_RDWR
# define O_RDWR 2
#endif
\f
/*
* time_stamp - return a time stamp string in the form:
*
* mm/dd/yy hh:mm:ss
*/
char *
time_stamp()
{
#ifdef GLOTZNET
long clock = time((long *)0) - tzoffset * 3600;
#else /* GLOTZNET */
long clock = time((long *)0);
#endif /* GLOTZNET */
struct tm *ltm = localtime(&clock);
static char timebuf[sizeof("mm/dd/yy hh:mm:ss")];
(void) sprintf(timebuf, "%02d/%02d/%02d %02d:%02d:%02d",
ltm->tm_mon+1, ltm->tm_mday, ltm->tm_year,
ltm->tm_hour, ltm->tm_min, ltm->tm_sec);
return timebuf;
}
/*
* strings used by the date and time routines below
*/
static char *week_day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static char *months[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
};
/*
* get_local_year - get the year according to local time
*/
int
get_local_year()
{
long stamp; /* local time stamp in seconds form Epoch */
/* return year */
#ifdef GLOTZNET
stamp = time((long *)0) - tzoffset * 3600;
#else /* GLOTZNET */
stamp = time((long *)0);
#endif /* GLOTZNET */
return (localtime(&stamp))->tm_year+1900;
}
/*
* get_arpa_date - return date in RFC822 format
*/
char *
get_arpa_date(clock)
long clock;
{
#ifdef GLOTZNET
struct tm *ltm;
char *tz_name;
static char timebuf[sizeof("ddd, dd mmm yy hh:mm tzn+hh:mm:ss")];
clock -= tzoffset * 3600;
ltm = localtime(&clock);
tz_name = get_time_zone(ltm);
#else /* GLOTZNET */
struct tm *ltm = localtime(&clock);
char *tz_name = get_time_zone(ltm);
static char timebuf[sizeof("ddd, dd mmm yy hh:mm tzn+hh:mm:ss")];
#endif /* GLOTZNET */
if (tz_name == NULL) {
tz_name = "GMT";
#ifdef GLOTZNET
clock += tzoffset * 3600;
#endif /* GLOTZNET */
ltm = gmtime(&clock);
}
(void) sprintf(timebuf, "%s, %d %s %02d %02d:%02d %s",
week_day[ltm->tm_wday], ltm->tm_mday,
months[ltm->tm_mon], ltm->tm_year,
ltm->tm_hour, ltm->tm_min, tz_name);
return timebuf;
}
/*
* get_time_zone - get the current timezone
*/
/* ARGSUSED */
static char *
get_time_zone(ltm)
struct tm *ltm;
{
#ifdef UNIX_BSD
struct timeval tv;
struct timezone tz;
(void) gettimeofday(&tv, &tz);
#ifdef GLOTZNET
tz.tz_minuteswest += tzoffset * 60;
if (tznodst) ltm->tm_isdst = 0;
#endif /* GLOTZNET */
return timezone(tz.tz_minuteswest, ltm->tm_isdst);
#else /* not UNIX_BSD */
# ifdef UNIX_SYS5
extern char *tzname[2];
return tzname[ltm->tm_isdst];
# else /* not UNIX_SYS5 */
struct timeb timeb;
/* try the V6/V7 system call for getting the time zone */
(void) ftime(&timeb);
return timezone(timeb.timezone, ltm->tm_isdst);
# endif /* not UNIX_SYS5 */
#endif /* not UNIX_BSD */
}
/*
* unix_date - get the current date as suitable for a From_ line
*
* Use ctime format.
*/
char *
unix_date()
{
#ifdef GLOTZNET
long clock = time((long *)0) - tzoffset * 3600;
#else /* GLOTZNET */
long clock = time((long *)0);
#endif /* GLOTZNET */
char *date = ctime(&clock);
/*
* XXX - ctime format is quite standard, so just but the nul in
* the proper spot.
*/
date[24] = '\0'; /* get rid of that \n */
return date;
}
/*
* compute_sender - compute the user running this program.
*
* We don't go through the passwd file cache, because the cache
* doesn't keep the full name information.
*/
void
compute_sender()
{
struct passwd *pw;
struct passwd *getpwuid(); /* get a password file entry given uid */
islocal = TRUE; /* we can only compute local senders */
if (sender = getlogin()) {
register char *new_sender = xmalloc(strlen(sender) + 1);
(void) strcpy(new_sender, sender);
sender = new_sender;
return;
}
if (pw = getpwuid(real_uid)) {
sender = xmalloc((unsigned)(strlen(pw->pw_name)));
(void) strcpy(sender, pw->pw_name);
if (!sender_name == NULL) {
/*
* might as well compute the full name while we have the
* password entry on hand
*/
fullname_from_gcos(pw->pw_gecos);
}
return;
}
sender = "postmaster";
return;
}
/*
* getfullname - get the full name of the sender
*
* The full name comes from the GCOS field in the password entry.
*/
void
getfullname()
{
struct passwd *pw;
struct passwd *getpwnam(); /* get a password entry given a username */
if (islocal) {
/* only possible for local senders */
pw = getpwnam(sender);
if (pw) {
fullname_from_gcos(pw->pw_gecos);
}
}
return;
}
/*
* fullname_from_gcos - fill in sender_name from the given gcos field
*
* (Modified to strip 0000-name(0000) USG junk - hargen@pdn 8/20/88
*/
static void
fullname_from_gcos(gecos)
register char *gecos;
{
struct str str;
register struct str *sp = &str; /* put full name here */
char *cp;
STR_INIT(sp);
if (isdigit(*gecos) && (cp = index(gecos, '-')) != NULL)
gecos = cp + 1; /* skip USG-style 0000-Name junk */
while (*gecos && *gecos != ',' && *gecos != '(') {
/* name may end with a comma or USG-style (0000) junk */
if (*gecos == '&') { /* & means copy sender, capitalized */
STR_NEXT(sp, uppercase(sender[0]));
STR_CAT(sp, sender+1);
} else {
STR_NEXT(sp, *gecos);
}
gecos++;
}
STR_NEXT(sp, '\0');
STR_DONE(sp);
sender_name = sp->p;
}
/*
* fopen_as_user - call fopen(3s) within the context of a specific uid/gid
*
* given a uid and gid, fopen a file only if the given uid/gid would be
* able to do so. Also, for "a" and "w" types: if the file did not
* previously exist, the file will be owned by the uid/gid after having
* being opened (if the mailer is running as root).
*
* return value and errno are set as a call to fopen(3s) would set them.
*
* NOTE: the mode argument is only required if type is "a" or "w".
*/
/*VARARGS5*/
FILE *
fopen_as_user(fn, type, check_path, uid, gid, mode)
char *fn; /* name of file to open */
char *type; /* mode for fopen ("r", "w" or "a") */
int uid; /* user id */
int gid; /* group id */
int mode; /* mode for creat() */
{
struct stat dir_stat; /* one for directories */
struct stat file_stat; /* one for the file */
char *fn_build; /* area for building test filenames */
register char *fnp; /* pointer for scanning fn */
char *new_fnp; /* pointer for scanning fn */
register char *bp; /* pointer to building fn_build */
FILE *f; /* opened file */
#ifdef HAVE_SETEUID
int fd; /* file descriptor for opened file */
#endif
/* reject non-root-based paths out of hand, though we shouldn't get any */
if (fn[0] != '/') {
write_log(LOG_SYS|LOG_PANIC,
"fopen_as_user: given non-root based path: %s",
fn);
errno = EACCES;
return NULL;
}
#ifdef HAVE_SETEUID
/*
* Under BSD, it is easy if we are running as root. Just set the
* effective ids to the passed uid and gid and call fopen. Then
* set the effective ids back to root. Having independent seteuid
* and setruid calls is great.
*
* NOTE: we assume that setgroups(0, (int *)NULL) has been called
* to clear out any groups that may erroneously allow access
* to the file.
*/
if (geteuid() == 0) {
int save_gid = getegid();
/* order here is, oddly enough, mildly important */
(void) setegid(gid);
(void) seteuid(uid);
if (type[0] == 'a') {
fd = open(fn, O_WRONLY|O_APPEND|O_CREAT, mode);
if (fd < 0) {
f = NULL;
} else {
f = fdopen(fd, type);
}
} else if (type[0] == 'w') {
fd = open(fn, O_WRONLY|O_CREAT|O_TRUNC, mode);
if (fd < 0) {
f = NULL;
} else {
f = fdopen(fd, type);
}
} else {
f = fopen(fn, type);
}
/* and it is important, again, here */
(void) seteuid(0);
(void) setegid(save_gid);
return f;
}
#endif /* HAVE_SETEUID */
/*
* now wasn't that nice and simple? Well, now we get to do
* it the long way. We stat all of the directories to ensure
* that search permission is allowed all the way down to the
* file itself. Then, if type is "w" or "a", we chmod the
* file, if it did not already exist (creating only if the
* directory allowed write access, of course).
*
* NOTE: namei calls are not known to be cheap, though on a system
* with a good namei cache, this may not be too bad.
*/
if (check_path) {
fnp = fn;
bp = fn_build = xmalloc(strlen(fn));
while (new_fnp = index(fnp, '/')) {
while (fnp <= new_fnp) {
*bp++ = *fnp++;
}
bp[0] = '\0';
/* since the name ends in /, it can only match directories */
if (stat(fn_build, &dir_stat) < 0) {
/* directory doesn't exist, or we can't access it */
return NULL;
}
/*
* should probably use some #define somewhere. However,
* the octal numbers below are permission mode bits.
*/
if (! ((dir_stat.st_uid == uid && (dir_stat.st_mode & 0100)) ||
(dir_stat.st_gid == gid && (dir_stat.st_mode & 0010)) ||
(dir_stat.st_mode & 0001)) )
{
errno = EACCES;
return NULL;
}
}
}
/* made it that far, stat the file, itself */
if (stat(fn, &file_stat) >= 0) {
/* file exists, make sure access is allowed, then open it */
if (type[0] == 'r') {
/* check for read access */
if (! ((file_stat.st_uid == uid && (file_stat.st_mode & 0400)) ||
(file_stat.st_gid == gid && (file_stat.st_mode & 0040)) ||
(file_stat.st_mode & 0004)) )
{
errno = EACCES;
return NULL;
}
} else {
/* check for write access */
if (! ((file_stat.st_uid == uid && (file_stat.st_mode & 0200)) ||
(file_stat.st_gid == gid && (file_stat.st_mode & 0020)) ||
(file_stat.st_mode & 0002)) )
{
errno = EACCES;
return NULL;
}
}
return fopen(fn, type);
}
/*
* file does not exist, if type is "r", this is an error, otherwise
* the directory must be writable to creat the file.
*/
if (type[0] == 'r') {
errno = EACCES;
return NULL;
}
if (check_path) {
if (! ((dir_stat.st_uid == uid && (dir_stat.st_mode & 0200)) ||
(dir_stat.st_gid == gid && (dir_stat.st_mode & 0020)) ||
(dir_stat.st_mode & 0002)) )
{
errno = EACCES;
return NULL;
}
}
/* now creat the file and chown and chmod it */
f = fopen(fn, type);
if (f == NULL) {
return NULL;
}
/*
* YAPTN - Yet Another Pass Through Namei()
* The order here is significant for non-superusers under System V.
*/
(void) chmod(fn, mode);
(void) chown(fn, uid, gid);
return f;
}
/*
* lock_file - lock a user's mailbox or other mail file
*
* return SUCCEED or FAIL.
*/
#ifdef lock_fd_wait
/*
* under 4.3BSD and some 4.2+ operating systems, flock(2) is used
* to lock mailboxes or other mail files. FLOCK_MAILBOX should
* be defined in os.h in this case. We assume that the file
* descriptor points to the beginning of the file.
*/
int
lock_file(fn, f)
char *fn; /* name of file */
FILE *f; /* open file */
{
long offset;
int success;
if (! flock_mailbox) {
/* use filename-based locking for mailbox files */
return lock_file_by_name(fn);
}
offset = lseek(fileno(f), 0L, 1);
(void) lseek(fileno(f), 0L, 0);
success = lock_fd_wait(fileno(f));
(void) lseek(fileno(f), offset, 0);
if (success < 0) {
/*
* This should never fail, but if it does, we will retry delivery
* at a later time.
*/
return FAIL;
}
return SUCCEED;
}
#else /* not lock_fd_wait */
/*
* if the lock_fd_wait macro is not available then we must always
* use the V6-style file-locking.
*/
/*ARGSUSED*/
int
lock_file(fn, f)
char *fn; /* name of file */
FILE *f; /* open file */
{
return lock_file_by_name(fn);
}
#endif /* not lock_fd_wait */
/*
* V7-style or Xenix-style locking.
* As far as I know, all flavors of UN*X that do not use 4.3BSD style
* locking use this method.
*
* To lock a file named fn, create a file named fn.lock (or for Xenix,
* /tmp/basename.mlk) and stuff the pid into it. If the file already
* exisits, see if it is stale and remove it if so. If it was stale,
* sleep for FNLOCK_INTERVAL and try to lock it again. Retry at most
* FNLOCK_RETRIES times. On non-BSD systems, locking will not be done
* if the basename for the file is more than 12 chars. The lenth
* restriction does not apply to Xenix, because the basename is always
* truncated to 10 chars, allowing sufficient space for the .mlk
* suffix.
*
* on systems without O_EXCL for the open(2) call, creat is used
* with a mode that does not allow writing.
*/
static int
lock_file_by_name(fn)
char *fn; /* name of file */
{
int l_fd; /* open lock file */
char *l_fn; /* lock file name */
int retries; /* remaining retries */
char apid[BITS_PER_INT/3 + 1]; /* ASCII representation of pid */
if (fn[0] != '/') {
/*
* there should not be a way for the software to generate
* a filename that does not begin with /
*/
panic(EX_SOFTWARE, "lock_by_name: non-rooted filename: %s", fn);
/*NOTREACHED*/
}
/*
* will it be possible to create the lock file? On BSD systems,
* the open call will tell us if the filename is too long. On
* other systems, we need to check this ourselves before hand.
*/
#if !defined(UNIX_BSD) && !defined(UNIX_XENIX)
{
char *p = rindex(fn, '/') + 1;
/* allow at least enough room for a trailing .l */
if (strlen(p) > DIRSIZ - 2) {
/*
* always succeed
*/
return SUCCEED;
}
}
#endif /* not UNIX_BSD and not UNIX_XENIX */
/*
* generate the lock filename
*/
#ifdef UNIX_XENIX
{
char *p = rindex(fn, '/') + 1;
l_fn = xmalloc(sizeof("/tmp/.mlk") + 10);
(void) sprintf(l_fn, "/tmp/%.10s.mlk", p);
}
#else /* not UNIX_XENIX */
l_fn = xmalloc(strlen(fn) + sizeof(".lock"));
(void) sprintf(l_fn, "%s.lock", fn);
#endif /* not UNIX_XENIX */
/*
* try to create the lock file at most FNLOCK_RETRIES+1 times.
* each time a lock fails, read a pid from the lock file and try
* to remove the lock if that pid does not exist then sleep for
* FNLOCK_INTERVAL and try again.
*/
for (retries = fnlock_retries; retries-- >= 0; sleep(fnlock_interval)) {
#ifdef O_EXCL
l_fd = open(l_fn, O_WRONLY|O_CREAT|O_EXCL, fnlock_mode);
#else /* O_EXCL */
/*
* if we can't open for exclusive creat, we will have to
* use the creat call.
*/
l_fd = creat(l_fn, fnlock_mode & (~0222));
#endif /* O_EXCL */
if (l_fd < 0) {
/*
* examine why this failed
*/
#ifdef O_EXCL
/*
* open with O_EXCL returns this error if file exists
*/
if (errno == EEXIST) {
check_stale(l_fn); /* remove stale lock files */
continue;
}
#else /* O_EXCL */
/*
* creat returns the ambiguous EACCES if the file exists
* and is not writable (the correct condition for a lock)
*/
if (errno == EACCES) {
check_stale(l_fn);
continue;
}
#endif /* O_EXCL */
/*
* some other reason is preventing us from writing
* the lock file, thus we won't bother retrying.
*/
xfree(l_fn);
#ifdef UNIX_BSD
if (errno == ENOENT) {
/*
* this is what BSD seems to return on basename too
* long, so return SUCCEED in this case because
* locking is not possible. Unfortunately this
* error does not uniquely identify the problem.
*/
return SUCCEED;
}
#endif /* UNIX_BSD */
/*
* for anything else, something strange is preventing us
* creating the lock file. FAIL for now, we will try
* again later.
*/
return FAIL;
}
break;
}
xfree(l_fn); /* don't need this any more */
/*
* none of the attempts to create the lock file succeeded
*/
if (l_fd < 0) {
return FAIL;
}
(void) sprintf(apid, "%d", getpid());
(void) write(l_fd, apid, strlen(apid));
(void) close(l_fd);
return SUCCEED;
}
/*
* check_stale - see if a lock file is stale. If so, remove it.
*/
static void
check_stale(l_fn)
char *l_fn; /* name of lock file */
{
char buf[50];
int ct;
int fd;
int pid = 0;
struct stat statbuf;
#ifdef O_RDONLY
fd = open(l_fn, O_RDONLY);
#else
fd = open(l_fn, 0);
#endif
if (fd < 0) {
DEBUG2(DBG_DRIVER_MID, "%s: cannot open lock file: %s",
l_fn, strerrno());
return; /* doesn't exist? */
}
for (;;) {
ct = read(fd, buf, sizeof(buf) - 1);
if (ct == 0) {
DEBUG1(DBG_DRIVER_MID, "%s: zero-length lock file ... ", l_fn);
/* sleep 30 seconds and see if new data comes in */
(void) sleep(30);
/* see if it has been removed */
(void) fstat(fd, &statbuf);
/* fine if it went away */
if (statbuf.st_nlink == 0) {
DEBUG(DBG_DRIVER_MID, "lock file went away\n");
(void) close(fd);
return;
}
/* stale if still empty */
if (statbuf.st_size == 0) {
if (statbuf.st_nlink > 0) {
/* unlink if it still exists */
(void) unlink(l_fn);
}
DEBUG(DBG_DRIVER_MID, "still empty, removed\n");
(void) close(fd);
return;
}
}
else {
#ifdef UNIX_XENIX
/* For Xenix, simple existence is quite enough. */
DEBUG1(DBG_DRIVER_MID, "%s: valid lock file\n", l_fn);
#else /* !Xenix */
buf[ct] = '\0';
(void) sscanf(buf, "%d", &pid);
/* see if the pid exists */
if (kill(pid, 0) < 0 && errno == ESRCH) {
/* process does not exist */
(void) fstat(fd, &statbuf);
if (statbuf.st_nlink > 0) {
/* remove the file if it still exists */
(void) unlink(l_fn);
}
DEBUG1(DBG_DRIVER_MID, "%s: stale lock file, removed\n",
l_fn);
} else {
DEBUG1(DBG_DRIVER_MID, "%s: valid lock file\n", l_fn);
}
#endif /* !Xenix */
(void) close(fd);
return;
}
}
}
/*
* unlock_file - unlock a user's mailbox or other mail file
*
* we do not check to make sure we unlocked the file. What
* could we do if an unlock failed?
*/
#ifdef lock_fd_wait
/*
* for 4.3BSD-style locking, just call flock to unlock the file
*/
void
unlock_file(fn, f)
char *fn; /* name of file */
FILE *f; /* open file */
{
long offset;
if (! flock_mailbox) {
/* use the V6 locking protocol */
unlock_file_by_name(fn);
return;
}
offset = lseek(fileno(f), 0L, 1);
(void) lseek(fileno(f), 0L, 0); /* unlock from beginning to end */
unlock_fd_wait(fileno(f));
(void) lseek(fileno(f), offset, 0);
}
#else /* not lock_fd_wait */
/*
* flock not available, so always call the V6-style unlock function.
*/
/*ARGSUSED*/
void
unlock_file(fn, f)
char *fn; /* name of file */
FILE *f; /* open file */
{
unlock_file_by_name(fn);
}
#endif /* not lock_fd_wait */
/*
* for V6-style locking remove the lock file. Be careful to
* make sure that the name passed to unlink(2) won't unlink
* the main file (i.e., use the same checks for basename size
* used in creating the lock file)
*/
static void
unlock_file_by_name(fn)
char *fn; /* name of file */
{
char *l_fn; /* lock file name */
if (fn[0] != '/') {
/*
* there should not be a way for the software to generate
* a filename that does not begin with /, however, the panic
* should have occured when lock_file was called. We will
* check anyway, just to make us feel better.
*/
panic(EX_SOFTWARE, "unlock_file: non-rooted filename: %s", fn);
/*NOTREACHED*/
}
/*
* was it possible to create the lock file? On BSD systems,
* the unlink will fail if the lock file name is too long. On
* other systems, we need to check this ourselves before hand.
*/
#if !defined(UNIX_BSD) && !defined(UNIX_XENIX)
{
char *p = rindex(fn, '/') + 1;
/* allow at least enough room for a trailing .l */
if (strlen(p) > DIRSIZ - 2) {
/*
* no lock file could have been created so don't try
* to remove one.
*/
return;
}
}
#endif /* not UNIX_BSD and not UNIX_XENIX */
/*
* generate the lock filename
*/
#ifdef UNIX_XENIX
{
char *p = rindex(fn, '/') + 1;
l_fn = xmalloc(sizeof("/tmp/.mlk") + 10);
(void) sprintf(l_fn, "/tmp/%.10s.mlk", p);
}
#else /* not UNIX_XENIX */
l_fn = xmalloc(strlen(fn) + sizeof(".lock"));
(void) sprintf(l_fn, "%s.lock", fn);
#endif /* not UNIX_XENIX */
/*
* remove the lock file and clean up
*/
(void) unlink(l_fn);
xfree(l_fn);
}
/*
* compute_hostname - compute the name of the local host
*
* return NULL if we are on an operating system that does not know about
* hostnames.
*/
#ifdef HAVE_GETHOSTNAME
char *
compute_hostname()
{
register char *p;
static char *hostname;
# ifdef GETHOSTNAME_USE_PTR
int len;
# endif
/*
* My man page says that 255 chars (plus nul byte) is the limit
* on length of the local host name. There appears to be no
* #define for it in 4.2BSD.
*/
hostname = xmalloc(256);
# ifdef GETHOSTNAME_USE_PTR
len = 256;
(void) gethostname(hostname, &len);
# else
(void) gethostname(hostname, 256);
# endif
/*
* Some systems return the entire hostname, including the domain.
* In this case, the primary hostname should be set to this name,
* though for now, just truncate off the domain.
*/
if ((p = index(hostname, '.')) != NULL) {
*p = '\0';
}
/* though we don't need to continue wasting all that space */
hostname = xrealloc(hostname, strlen(hostname) + 1);
return hostname;
}
#else /* not HAVE_GETHOSTNAME */
# ifdef HAVE_UNAME
char *
compute_hostname()
{
static struct utsname utsname;
(void) uname(&utsname);
/* Is the sysname tag used for something interesting? */
return utsname.nodename;
}
# else /* not HAVE_UNAME */
# ifdef SITENAME_FILE
/* sitename is stored in a file */
char *
compute_hostname()
{
static struct str hostname;
static int inited = FALSE;
register int c;
FILE *f;
if (inited) {
return hostname.p;
}
inited = TRUE;
STR_INIT(&hostname);
f = fopen(SITENAME_FILE, "r");
while ((c = getc(f)) != EOF && c != '\n') {
STR_NEXT(&hostname, c);
}
STR_NEXT(&hostname, '\0');
STR_DONE(&hostname);
return hostname.p;
}
# else /* not SITENAME_FILE */
char *
compute_hostname()
{
/*
* perhaps we should call uuname -l rather than punting.
*/
panic(EX_SOFTWARE,
"the local host name is not computable and is not configured");
/*NOTREACHED*/
}
# endif /* not SITENAME_FILE */
# endif /* not HAVE_UNAME */
#endif /* not HAVE_GETHOSTNAME */
\f
/*
* open_child - create a child process and open pipes to it and exec
*
* open_child creates a child process, with possible read and write
* pipes to the child process's stdin and stdout/stderr. Setuid and
* setgid are called in the child process to change the uid/gid to
* whichever uid/gid are given to open_child.
*
* open_child does nothing with signals in the parent process.
* Only signal behaviors modified by exec will be changed in the
* child process. If more complex signal behavior desired, call
* with argv == NULL and handle signals and the exec in the caller
* routine within the child process.
*
* inputs:
* argv - a vector suitable for passing to execve. argv[0] is
* given as the program to exec. If argv is NULL, then
* do a return instead of an exec. In this case as well,
* never to a vfork(), always use fork().
* envp - a vector of environment variables suitable for passing
* to execve. If envp is null and CHILD_MINENV is not
* set in flags, then the parents environment will be
* passed to the child.
* in - a pointer to a FILE* variable. If non-NULL, a pipe
* is created, with the write-end returned in this
* variable and with the read-end tied to the stdin of
* the child process.
* out - a pointer to a FILE* variable. If non-NULL, a pipe
* is created, with thre read-end returned in this
* variable and with the write-end tied to the stdout
* of the child process.
* errfd - if errfd >= 0 then it sepecifies a file descriptor
* to duplicate to the stdout and/or stderr of the
* child. If out != NULL, errfd is only dup'd to the
* stderr.
* flags - a bitmask of flags affecting open_child's operation.
* Flags are:
*
* CHILD_DUPERR - duplicate stdout to stderr in the
* child process.
* CHILD_DEVNULL - open "/dev/null" and associate it
* with stdin and/or stdout in the
* child process, if no in and/or out
* variable is specified.
* CHILD_RETRY - retry fork() operation up to FORK_RETRIES
* times at FORK_INTERVAL second intervals,
* if fork returns EAGAIN.
* CHILD_MINENV - give the child process a very minimal
* environment. This is overridden by giving
* an explicit environment in envp.
* CHILD_NOCLOSE - don't close file descriptors. If this is
* not set, close all file descriptors
* other than stdin/stdout/stderr.
*
* uid - do a setuid(uid) in the child
* gid - do a setgid(gid) in the child
*
* output:
* pid of child process, or 0 if in child process (only if argv==NULL).
* Return EOF if pipe() or fork() failed.
*/
int
open_child(argv, envp, in, out, errfd, flags, uid, gid)
char **argv; /* arg vector for execve */
char **envp; /* environment vector for execve */
FILE **in; /* make a stdin file pointer */
FILE **out; /* make a stdout file pointer */
int errfd; /* fd to use for stdout/stderr */
int flags; /* flags affecting operation */
int uid; /* user ID for child process */
int gid; /* group ID for child process */
{
int stdin_fd[2]; /* fds from pipe(2) for child stdin */
int stdout_fd[2]; /* fds from pipe(2) for child stdout */
/* remember, fd[0] is the read descriptor, fd[1] is the write descriptor */
int retries = FORK_RETRIES; /* countdown of retries */
int pid; /* pid from fork() */
static char *min_env[] = {
#ifdef CHILD_ENVIRON
CHILD_ENVIRON,
#else /* CHILD_ENVIRON */
"PATH=/bin:/usr/bin",
"SHELL=/bin/sh",
"HOME=/",
#endif /* CHILD_ENVIRON */
#ifndef UNIX_BSD
NULL, /* leave space for timezone */
#endif /* UNIX_BSD */
NULL, /* end of environment */
};
if (in && pipe(stdin_fd) < 0) {
return EOF;
}
if (out && pipe(stdout_fd) < 0) {
/* free resources */
(void) close(stdin_fd[0]);
(void) close(stdin_fd[1]);
return EOF;
}
/* keep trying to create a fork until we succeed or retries == 0 */
while (
#ifdef UNIX_BSD
(pid = (argv? vfork(): fork())) < 0 &&
#else /* UNIX_BSD */
(pid = fork()) < 0 &&
#endif /* UNIX_BSD */
errno == EAGAIN &&
--retries >= 0)
{
(void) sleep(FORK_INTERVAL); /* sleep between retries */
}
if (pid < 0) {
/* free resources */
(void) close(stdin_fd[0]);
(void) close(stdin_fd[1]);
(void) close(stdout_fd[0]);
(void) close(stdout_fd[1]);
return EOF;
}
if (pid == 0) {
/* in child process */
/* close unnecessary file descriptors */
if (in) {
(void) close(stdin_fd[1]);
}
if (out) {
(void) close(stdout_fd[0]);
}
/* if errfd == 0, watch out for the code below */
if (errfd == 0 || errfd == 1) {
int new_errfd;
/* search for a descriptor we don't care about */
for (new_errfd = 3; new_errfd < 10; new_errfd++) {
if (in && new_errfd == stdin_fd[0] ||
out && new_errfd == stdout_fd[1])
{
continue;
}
}
(void) dup2(errfd, new_errfd);
(void) close(errfd);
errfd = new_errfd;
}
if (in || (flags & CHILD_DEVNULL)) {
/*
* setup the child's stdin
*/
if (in) {
/* pipe from parent process */
if (stdin_fd[0] != 0) {
(void) dup2(stdin_fd[0], 0);
(void) close(stdin_fd[0]);
}
} else if (flags & CHILD_DEVNULL) {
/*
* XXX - we rely on pipe() never returning 0 as the
* write file descriptor, or this could close
* stdout_fd[1], which would not be nice.
*/
/* open /dev/null as the stdin */
(void) close(0);
if (open("/dev/null", O_RDONLY) < 0) {
/* uggh! failed to open /dev/null, quit */
_exit(EX_OSFILE);
}
}
}
if (out || errfd >= 0 || (flags & CHILD_DEVNULL)) {
/*
* setup the child's stdout
*/
if (out) {
/* pipe to parent process */
if (stdout_fd[1] != 1) {
(void) dup2(stdout_fd[1], 1);
(void) close(stdout_fd[1]);
}
} else if (errfd >= 0) {
/* use the given fd for stdout */
(void) dup2(errfd, 1);
} else if (flags & CHILD_DEVNULL) {
/* open /dev/null as stdout */
(void) close(1);
if (open("/dev/null", O_WRONLY) < 0) {
/* uggh! failed to open /dev/null, quit */
_exit(EX_OSFILE);
}
}
}
if (errfd >= 0) {
/* use this fd as the stderr */
if (errfd != 2) {
(void) dup2(errfd, 2);
(void) close(errfd);
}
} else if (flags & CHILD_DUPERR) {
/* duplicate stdout to get the stderr */
(void) dup2(1, 2);
} else {
(void) close(2);
if (open("/dev/null", O_WRONLY) < 0) {
/* uggh! failed to open /dev/null, quit */
_exit(EX_OSFILE);
}
}
/* close all unnecessary file descriptors */
if ( !(flags & CHILD_NOCLOSE) ) {
close_all();
}
/* change uid and gid, if we can, don't bother to check */
(void) setgid(gid);
(void) setuid(uid);
if (argv) {
/* execute the program */
if (envp) {
(void) execve(argv[0], argv, envp);
} else if (flags & CHILD_MINENV) {
#if !defined(UNIX_BSD)
/* pass the TZ environment variable on non-BSD systems */
char *tz = getenv("TZ");
if (tz) {
char *tzenv = xmalloc(strlen(tz) + 4);
strcpy(tzenv, "TZ=");
strcat(tzenv, tz);
min_env[3] = tzenv;
}
#endif /* not UNIX_BSD */
(void) execve(argv[0], argv, min_env);
} else {
(void) execv(argv[0], argv);
}
/* Oh well, all this work and we can't even exec the file */
_exit(EX_OSFILE);
}
} else {
/* in parent process */
if (in) {
/* close the child stdin, return the write-channel */
(void) close(stdin_fd[0]);
*in = fdopen(stdin_fd[1], "w");
}
if (out) {
/* close the child stdout, return the read-channel */
(void) close(stdout_fd[1]);
*out = fdopen(stdout_fd[0], "r");
}
}
return pid;
}
/*
* close_child - close down the child process started with open_child.
*
* if non-zero file pointers are passed, the given files are closed.
* Then, wait for the specified pid to exit and return its status.
*
* NOTE: we do not keep the exit status of processes other than the
* one we are waiting. Thus, only one process should be open
* at a time by open_child, unless the caller wishes to handle
* the performing the waits itself.
*
* return EOF, on error, or exit status.
*/
int
close_child(in, out, pid)
FILE *in; /* pipe to child's stdin */
FILE *out; /* pipe to child's stdout */
int pid; /* pid of child process */
{
int i; /* return value from wait */
int status; /* status from wait */
if (in) {
(void) fclose(in); /* close the pipe to stdin */
}
if (out) {
(void) fclose(out); /* close the pipe to stdout */
}
/* wait for the child process to die */
while ((i = wait(&status)) != pid && i >= 0) ;
if (i < 0) {
/* failed to find the child process */
return FAIL;
}
return status; /* everything went okay */
}
/*
* close_all - close all file descriptors other than 0, 1 and 2
*
* At several key points smail needs to know that no extra file descriptors
* being used, such as when execing other processes (to ensure that
* child processes cannot read or write protected information left open in
* smail) and at startup (to prevent smail running out of file descriptors).
*/
void
close_all()
{
register int i;
for (i = 3;
/* so just how many file descriptors do we have? */
#ifdef NOFILE
i < NOFILE;
#else
# ifdef OPEN_MAX
i < OPEN_MAX;
# else
/* if neither of the above are defined, 20 shoule be right */
i < 20;
# endif
#endif
i++)
{
(void) close(i);
}
}
#ifndef HAVE_RENAME
/*
* rename - emulate the BSD/System V.3 rename(2) system call
*
* This subroutine is not atomic, so functions which are worried about
* consistency across system failures or in the face of multiple processes
* should consider the situation when HAVE_RENAME is not defined.
*
* The errno returned will not always correspond to what would have
* been produced by a true rename(2) system call.
*/
int
rename(from, to)
char *from; /* old file name */
char *to; /* new file name */
{
if (unlink(to) && errno != ENOENT) {
/* could not unlink `to', though it may exist */
return -1;
}
if (link(from, to) < 0) {
/* failed to link, however, an existing `to' file was removed */
return -1;
}
if (unlink(from) < 0) {
/* could not unlink, the file exists under two names */
return -1;
}
return 0;
}
#endif /* HAVE_RENAME */
#if !defined(HAVE_MKDIR)
/*
* for OS's that lack a mkdir system call, call the mkdir program
*/
int
mkdir(dir, mode)
char *dir;
int mode;
{
static char *mkdir_argv[] = {
"/bin/mkdir",
NULL, /* space for directory name */
NULL,
};
/* set the umask so that the directory will have the correct perms */
int oumask = umask((~mode)&0777);
int pid;
mkdir_argv[1] = dir;
/* start up the mkdir program */
pid = open_child(mkdir_argv, (char **)NULL,
(FILE **)NULL, (FILE **)NULL, -1, CHILD_DEVNULL, 0, 0);
(void) umask(oumask);
return close_child((FILE *)NULL, (FILE *)NULL, pid);
}
#endif /* !defined(HAVE_MKDIR) */
#ifndef HAVE_DUP2
/*
* dup2 - this provides the dup2 function as described by the Posix Draft
* for those sites which do not have it.
*
* Mark Colburn (mark@jhereg.mn.org)
*/
int
dup2(fildes, fildes2)
int fildes, fildes2;
{
int fid;
if (fcntl(fildes, F_GETFL, 0) == -1)
return(EBADF);
if (fildes != fildes2)
close(fildes2);
fid = fcntl(fildes, F_DUPFD, fildes2);
return(fid);
}
#endif /* HAVE_DUP2 */
#ifndef HAVE_READDIR
/*
* for OS's that lack the directory facilities, emulate
*/
struct direct_with_null {
struct direct d;
int trailing_null; /* make sure name ends in a nul byte */
};
typedef FILE DIR;
#define opendir(dn) (fopen(dn, "r"))
#define closedir(dn) (fclose(dn))
static struct direct *
readdir(dirp)
DIR *dirp;
{
static struct direct_with_null ret;
ret.trailing_null = 0;
do {
if (fread(&ret.d, sizeof(ret.d), 1, (FILE *)dirp) < 1) {
return NULL;
}
} while (ret.d.d_ino == 0);
return &ret.d;
}
#endif /* HAVE_READDIR */
/*
* scan_dir - scan through a directory, looking for filenames
*
* if given a non-NULL directory argument, opens the directory and
* returns the first file, or NULL if it contains no files.
* successive calls, with a NULL argument, return successive files
* in the directory. On the call after the last directory, a NULL
* is returned and the directory is closed.
*
* scan_dir returns static data which is reused on subsequent calls.
*/
char *
scan_dir(dn)
char *dn; /* directory name, or NULL */
{
static DIR *dirp = NULL; /* opened directory */
struct direct *dp; /* current directory entry */
if (dn) {
dirp = opendir(dn);
}
if (dirp == NULL) {
return NULL; /* no directory, so no entries */
}
dp = readdir(dirp);
if (dp == NULL) {
(void) closedir(dirp);
return NULL;
}
return dp->d_name;
}
#ifdef USE_FCNTL_RD_LOCK
/*
* fcntl_rd_lock - get a shared lock using a System V-style fcntl.
*/
int
fcntl_rd_lock(fd)
int fd;
{
struct flock l;
l.l_type = F_RDLCK;
l.l_whence = 0;
l.l_start = 0L;
l.l_len = 0L;
if (fcntl(fd, F_SETLKW, &l) < 0) {
return FAIL;
}
return SUCCEED;
}
#endif