/*
#ident "@(#)smail/src:RELEASE-3_2_0_121:sysdep.c,v 1.128 2005/08/26 19:59:39 woods Exp"
*/
/*
* Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
* Copyright (C) 1992 Ronald S. Karr
*
* 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_local_sender, getfullname,
* fopen_as_user, lock_file, unlock_file,
* compute_hostname, compute_domain,
* open_child, close_child,
* close_all, scan_dir, fcntl_rd_lock, touch, fsetsize,
* vfprintf, strerror, strysexit, strsignal, sig2str,
* set_sigchld_handler
*/
#include "defs.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <errno.h>
#include <limits.h>
#include <pwd.h>
#include <grp.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif
#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
# include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif
#if defined(POSIX_OS) || defined(UNIX_BSD) || defined(WAIT_USE_UNION)
# include <sys/wait.h>
#endif
#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#if defined(UNIX_SYS5) || defined(POSIX_OS) || defined(USE_FCNTL)
# include <fcntl.h>
#else
# if defined(UNIX_BSD)
# include <sys/file.h>
# endif
#endif
#ifdef __GLIBC__
# include <sys/file.h>
#endif
#ifdef HAVE_SYS_LOCKING_H
# include <sys/locking.h>
#endif
#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif
#if defined(HAVE_SETUSERCONTEXT) && defined(HAVE_LOGIN_CAP_H)
# include <login_cap.h>
#endif
#ifdef HAVE_UNAME
# include <sys/utsname.h>
#endif
#if defined(UNIX_BSD) && !defined(POSIX_OS)
# include <sys/dir.h>
#else
# if defined(UNIX_XENIX) && !defined(POSIX_OS)
# include <sys/ndir.h>
# else
# if defined(HAVE_READDIR)
# include <dirent.h>
# define direct dirent
# else
# include <sys/dir.h>
# endif /* HAVE_READDIR */
# endif /* UNIX_XENIX && !POSIX_OS */
#endif /* UNIX_BSD && !POSIX_OS */
#if defined(POSIX_OS) || defined(HAVE_UTIME_H)
# include <utime.h>
#else
# if defined(DECLARE_UTIMBUF) && !defined(NO_DECLARE_UTIMBUF)
struct utimbuf {
time_t actime;
time_t modtime;
};
# endif /* DECLARE_UTIMBUF */
#endif /* POSIX_OS || HAVE_UTIME_H */
#if !defined(UNIX_BSD) && !defined(UNIX_SYS5) && !defined(POSIX_OS)
/* use this for ancient ftime() system call, as a last resort */
# include <sys/timeb.h>
#endif
#include "smail.h"
#include "smailarch.h"
#include "smailsock.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "child.h"
#include "alloc.h"
#include "list.h"
#include "smailstring.h"
#include "smailwait.h"
#include "dys.h"
#include "log.h"
#include "route.h"
#include "bindlib.h"
#include "exitcodes.h"
#include "extern.h"
#include "debug.h"
#include "smailport.h"
/*
* double-check some assumptions....
*
* XXX can CPP deal with "the usual conversions"?
*/
#if (MAXINT_B10_DIGITS == MAXLONG_B10_DIGITS && UINT_MAX != ULONG_MAX)
# include "ERROR: ARCH_TYPE assumptions about MAX*_B10_DIGITS are wrong!"
#endif
#if (__STDC__ - 0) > 0
# if (MAXINT_B10_DIGITS <= 5 && UINT_MAX > 0xffffU)
# include "ERROR: ARCH_TYPE (16bit?) assumptions about MAXINT_B10_DIGITS are wrong!"
# endif
# if (MAXINT_B10_DIGITS <= 10 && UINT_MAX > 0xffffffffU)
# include "ERROR: ARCH_TYPE (32bit?) assumptions about MAXINT_B10_DIGITS are wrong!"
# endif
# if (MAXLONG_B10_DIGITS <= 10 && ULONG_MAX > 0xffffffffU)
# include "ERROR: ARCH_TYPE assumptions about MAXLONG_B10_DIGITS are wrong!"
# endif
# ifdef HAVE_LONG_LONG
# if (MAXLONG_B10_DIGITS <= 20 && 0xffffffffffffffffULL)
# include "ERROR: ARCH_TYPE assumptions about MAXLONG_B10_DIGITS are wrong!"
# endif
# endif
#endif
/*
* XXX This is a most horrible work-around to a bug in the setuid(3V)
* XXX call in the SunOS-4.1.x /usr/5lib/libc.a. The manual page lies,
* XXX and in fact returns EINVAL for uid values greater than 65533.
* XXX The implementation in /usr/lib/libc.a works as documented and
* XXX allows uid values up to and including 65535. Same for setgid(3V).
*
* XXX WARNING: these should really be implemented as it is in
* XXX /usr/5lib/libc.a{setuid.o} to ensure maximum bug compatability.
* XXX So far as I can tell from disassembling /usr/lib/libc.a{setuid.o},
* XXX the setuid() macro is a faithful re-implementation of that routine:
*
* XXX We do need to be sure we pretend to fail if we're trying to setuid to
* XXX -1 (the common value for BOGUS_USER), since setre?id() won't fail in
* XXX this case and we'd still be running as root when we thought we'd
* XXX changed to nobody_uid or some such less privileged user or group.
* XXX This will prevent use of uid 65535, but will allow -2 (65534).
*/
#if defined(sun) && defined(UNIX_SUN_OS_4_1) && defined(UNIX_SYS5)
# define setuid(n) ((n) == (uid_t) -1 ? (errno = EINVAL, -1) : setreuid((-1), (n)))
# define setgid(n) ((n) == (gid_t) -1 ? (errno = EINVAL, -1) : setregid((-1), (n)))
#endif
#ifndef UID_MAX
# ifdef MAXUID
# define UID_MAX MAXUID
# else
# ifdef SHRT_MAX
# define UID_MAX SHRT_MAX /* XXX SHORT_MAX, LONG_MAX on 4.4bsd???? */
# else
# define UID_MAX 32767
# endif
# endif
#endif
#ifndef GID_MAX
# define GID_MAX UID_MAX
#endif
#ifndef UID_NOBODY
# define UID_NOBODY UID_MAX
#endif
#ifndef GID_NOBODY
# define GID_NOBODY GID_MAX
#endif
#if defined(FORCE_DIRSIZ_VALUE)
# undef DIRSIZ
# define DIRSIZ FORCE_DIRSIZ_VALUE
#endif
/* functions local to this file */
static char *get_time_zone __P((struct tm *, struct tm *));
static char *get_time_zone_name __P((struct tm *));
static void fullname_from_gcos __P((char *));
static void check_stale __P((char *));
static int lock_file_by_name __P((char *));
static void unlock_file_by_name __P((char *));
static int lockfile_name __P((char *, char **));
#ifndef __STDC__
/* imported library functions */
extern off_t lseek();
extern time_t time();
extern char *getenv();
extern char *getlogin();
extern char *ctime();
# if !defined(USE_TM_TZNAME) && \
!defined(UNIX_SYS5) && !defined(POSIX_OS) && !defined(USE_TZNAME)
extern char *timezone(); /* only bsd or v6/v7 */
# endif /* UNIX_SYS5 */
extern struct tm *gmtime();
extern struct tm *localtime();
#endif
#ifndef __STDC__
/* variables imported from libc */
extern int errno;
#endif
/*
* time_stamp - return a time stamp string in the form:
*
* yyyy/dd/dd hh:mm:ss
*
* XXX this isn't really a system dependent function, and was not even before
* the days of POSIX....
*/
char *
time_stamp()
{
time_t curtime = time((time_t *) NULL);
struct tm *ltm = localtime(&curtime);
#ifndef USE_OLD_TIMESTAMP
/*
* not exactly ISO-8601 but close, the T becomes a space so as to preserve
* field counts & offsets in logs, etc.
*/
static char timebuf[sizeof("yyyy/mm/dd hh:mm:ss")];
(void) sprintf(timebuf, "%04d/%02d/%02d %02d:%02d:%02d",
ltm->tm_year + 1900, ltm->tm_mon + 1, ltm->tm_mday,
ltm->tm_hour, ltm->tm_min, ltm->tm_sec);
#else
/* old stupid format */
static char timebuf[sizeof("mm/dd/yyyy hh:mm:ss")];
(void) sprintf(timebuf, "%02d/%02d/%04d %02d:%02d:%02d",
ltm->tm_mon + 1, ltm->tm_mday, ltm->tm_year + 1900,
ltm->tm_hour, ltm->tm_min, ltm->tm_sec);
#endif
return timebuf;
}
/*
* get_local_year - get the year according to local time
*
* XXX this isn't really a system dependent function, and was not even before
* the days of POSIX....
*/
int
get_local_year()
{
time_t stamp; /* local time stamp in seconds form Epoch */
/* return year */
stamp = time((time_t *) NULL);
return ((localtime(&stamp))->tm_year + 1900);
}
/*
* strings used by get_arpa_date()
*/
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_arpa_date - return date in RFC822 format
*
* XXX this isn't really a system dependent function, and was not even before
* the days of POSIX....
*/
char *
get_arpa_date(sometime)
time_t sometime;
{
struct tm gtm, *ltm;
char *tzinfo;
static char timebuf[sizeof("ddd, dd mmm yyyy hh:mm:ss +tzof (tzn dst) [extra]")];
/*
* We need to do this since most implementations will use the same static
* buffer for both localtime and gmtime
*/
ltm = gmtime(&sometime);
(void) memcpy(>m, ltm, sizeof(gtm));
ltm = localtime(&sometime);
tzinfo = get_time_zone(>m, ltm);
(void) sprintf(timebuf, "%s, %d %s %04d %02d:%02d:%02d %s",
week_day[ltm->tm_wday], ltm->tm_mday,
months[ltm->tm_mon], ltm->tm_year + 1900,
ltm->tm_hour, ltm->tm_min, ltm->tm_sec,
tzinfo);
return timebuf;
}
/*
* get_time_zone - get the current timezone
*
* XXX this isn't really a system dependent function, and was not even before
* the days of POSIX....
*/
/* ARGSUSED */
static char *
get_time_zone(gtm, ltm)
struct tm *gtm, *ltm;
{
int tzoff, tzoff_hour, tzoff_min;
char sign;
char * ltzname = NULL;
static char tzbuf[20];
/* This code works on all systems to get timezone */
tzoff = ((ltm->tm_min - gtm->tm_min) +
(60 * (ltm->tm_hour - gtm->tm_hour)));
if (ltm->tm_year != gtm->tm_year) {
tzoff += (ltm->tm_year > gtm->tm_year) ? 1440 : -1440;
} else {
if (ltm->tm_yday != gtm->tm_yday) {
tzoff += (ltm->tm_yday > gtm->tm_yday) ? 1440 : -1440;
}
}
if (tzoff >= 0) {
sign = '+';
} else {
sign = '-';
tzoff = -tzoff;
}
tzoff_min = tzoff % 60;
tzoff_hour = tzoff / 60;
ltzname = get_time_zone_name(ltm);
if (ltzname) {
sprintf(tzbuf, "%c%02d%02d (%s)", sign, tzoff_hour, tzoff_min, ltzname);
} else {
sprintf(tzbuf, "%c%02d%02d", sign, tzoff_hour, tzoff_min);
}
return (tzbuf);
}
static char *
get_time_zone_name(ltm)
struct tm *ltm;
{
#if defined(USE_TM_ZONE)
return ltm->tm_zone; /* SunOS-4, 4.3BSD */
#else
# if defined(UNIX_SYS5) || defined(POSIX_OS) || defined(USE_TZNAME)
extern char *tzname[2];
return tzname[ltm->tm_isdst];
# else
# if (defined(UNIX_BSD) || defined(USE_GETTIMEOFDAY))
struct timeval tv;
struct timezone tz;
(void) gettimeofday(&tv, &tz);
return timezone(tz.tz_minuteswest, ltm->tm_isdst);
# else
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
# endif
#endif
}
/*
* unix_date - get the current date as suitable for a From_ line
*
* Use ctime format.
*
* XXX this isn't really a system dependent function, and was not even before
* the days of POSIX....
*/
char *
unix_date()
{
time_t curtime = time((time_t *) NULL);
char *date = ctime(&curtime);
/*
* XXX - ctime format is quite standard, so just put the nul in
* the proper spot.
*/
date[24] = '\0'; /* get rid of that \n */
return date;
}
/*
* compute_nobody - figure out the nobody uid/gid
*
* if `nobody_uid' and `nobody_gid' are defined, use them, otherwise
* use the login name in `nobody' to determine nobody_uid/gid.
*
* XXX assumes nobody's gid == group "nobody"
*/
void
compute_nobody()
{
if (nobody_uid != BOGUS_USER && nobody_gid != BOGUS_GROUP) {
return;
}
if (nobody == NULL || nobody[0] == '\0') {
/*
* nobody uid/gid not defined. use something likely to not be
* in use
*/
nobody_uid = UID_NOBODY;
nobody_gid = GID_NOBODY;
} else {
struct passwd *pw; /* passwd entry for `nobody' */
pw = getpwbyname(FALSE, nobody);
if (pw == NULL) {
nobody_uid = UID_NOBODY; /* XXX may still be == BOGUS_USER */
nobody_gid = GID_NOBODY;
} else {
nobody_uid = pw->pw_uid;
nobody_gid = pw->pw_gid;
}
}
}
/*
* compute_local_sender - compute the user running this program.
*
* XXX this isn't really a system dependent function, and was not even before
* the days of POSIX....
*/
void
compute_local_sender()
{
struct passwd *pw;
if (local_sender) {
return;
}
islocal = TRUE; /* only local sender determined here */
if (sender_env_variable) {
char *p = getenv(sender_env_variable);
if (p && *p) {
pw = getpwbyname(FALSE, p);
if (pw != NULL && pw->pw_uid == real_uid) {
local_sender = COPY_STRING(p);
return;
}
}
}
if ((pw = getpwbyuid(real_uid)) != NULL) {
local_sender = COPY_STRING(pw->pw_name);
return;
}
write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC, "no username found for UID=%u", (unsigned int) real_uid);
local_sender = xprintf("%u", (unsigned int) real_uid);
return;
}
/*
* getfullname - get the full name of the sender
*
* The full name comes from the GCOS field in the password entry.
*
* XXX this isn't really a system dependent function, and was not even before
* the days of POSIX....
*/
void
getfullname()
{
struct passwd *pw;
if (islocal) {
/* only possible for local senders */
pw = getpwbyname(FALSE, 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
*
* XXX this isn't really a system dependent function, and was not even before
* the days of POSIX....
*/
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((int) *gecos) && (cp = strchr(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, toupper((int) sender[0]));
STR_CAT(sp, sender+1);
} else {
STR_NEXT(sp, *gecos);
}
gecos++;
}
STR_NEXT(sp, '\0');
STR_DONE(sp);
sender_name = STR(sp);
}
#ifdef HAVE_BSD_NETWORKING
/*
* get_inet_addr - call inet_addr()
*
* believe it or not, but inet_addr() is not portable!!!! :-(
*/
unsigned long
get_inet_addr(ipaddr)
char *ipaddr;
{
unsigned long inet; /* internet address */
# if defined(INET_ADDR_USE_STRUCT)
struct in_addr s_inet; /* Ultrix(?) internet address */
# endif
# ifdef INET_ADDR_USE_STRUCT
s_inet = inet_addr(ipaddr);
inet = s_inet.s_addr;
# else
inet = inet_addr(ipaddr);
# endif
return inet;
}
#endif /* HAVE_BSD_NETWORKING */
/*
* 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 or as uid/gid).
*
* 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".
*/
/* XXX FIXME: this doesn't belong in sysdep.c, does it? [yes, if we add the socketpair() file-descriptor passing version!] */
FILE *
fopen_as_user(fn, type, uid, gid, mode)
char *fn; /* name of file to open */
char *type; /* mode for fopen ("r", "w" or "a") */
uid_t uid; /* user id */
gid_t gid; /* group id */
unsigned int mode; /* mode for creat() (XXX mode_t) */
{
FILE *fp; /* opened file */
int fd = EOF; /* file descriptor for opened file */
int cfd = EOF; /* file descriptor for file in child */
pid_t pid; /* PID of forked process */
int i; /* Used to iternate through processes */
STATUS_TYPE status; /* status of exited forked process */
struct stat stb_root, stb_user; /* stat of file in root/user contexts */
DEBUG5(DBG_DRIVER_HI, "fopen_as_user(%s, %s, %u, %u, 0%o) called\n",
fn, type, (unsigned int) uid, (unsigned int) gid, mode);
/* reject non-root-based paths out of hand, though we shouldn't get any */
if (!fn || fn[0] != '/') {
write_log(WRITE_LOG_SYS|WRITE_LOG_PANIC,
"fopen_as_user: given non-root anchored path: %s",
fn);
errno = EACCES;
return NULL;
}
/*
* This is the classic way for a setuid program to use a file safely as a
* user without worrying about race conditions. If the file doesn't exist
* we first must create it as the user. In all cases we then open the file
* first as root (no create, no truncation unless the file is to be owned
* by root), then as the user in a child process, and we compare st_dev and
* st_ino for both files, failing if they are not identical.
*/
if (geteuid() == 0 && uid != 0) {
/*
* If writing (incl. appending), check whether the file already exists
* as we need it to do the access check properly.
*/
if (((type[0] == 'w') || (type[0] == 'a')) && (access(fn, F_OK) == EOF)) {
/*
* OK, we need to create the file as the user, so we fork().... If
* access() failed we don't care why, as open() will discover the
* reason soon enough.
*/
switch ((pid = fork())) {
case EOF:
DEBUG1(DBG_DRIVER_LO, "fopen_as_user(): create child fork(): %s.\n", strerror(errno));
return NULL;
/* NOTREACHED */
case 0:
if (getegid() != gid) { /* this is probably silly */
if (setgid(gid) != 0) {
DEBUG3(DBG_DRIVER_LO, "fopen_as_user(): create child setgid(%d): %s (uid was %u).\n",
gid, strerror(errno), (unsigned int) uid);
exit(errno + 1);
/* NOTREACHED */
}
}
if (setuid(uid) != 0) {
DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): create child setuid(%u): %s.\n",
(unsigned int) uid, strerror(errno));
exit(errno + 1);
/* NOTREACHED */
}
if ((cfd = open(fn, O_WRONLY | O_CREAT, mode)) < 0) {
DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): create open(%s): %s.\n", fn, strerror(errno));
exit(errno + 1);
/* NOTREACHED */
}
close(cfd);
exit(0);
/* NOTREACHED */
default:
/* wait for the child process to die */
while ((i = wait((STATUS_TYPE *) &status)) != pid) {
if (i < 0) {
DEBUG2(DBG_DRIVER_MID, "fopen_as_user(): create child wait() for PID %ld: %s.\n",
(long int) pid, strerror(errno));
return NULL;
}
}
if (WIFEXITED(status)) {
if (WEXITSTATUS(status)) {
DEBUG1(WEXITSTATUS(status) - 1 ? DBG_DRIVER_LO : DBG_DRIVER_HI,
"fopen_as_user(): create child exited with %d.\n",
WEXITSTATUS(status) - 1);
errno = WEXITSTATUS(status) - 1;
return NULL;
}
} else if (WIFSIGNALED(status)) {
DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): create child[%d] signalled PID %ld.\n",
(long int) pid, WTERMSIG(status));
errno = EINTR;
return NULL;
} else if (WIFSTOPPED(status)) {
DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): create child[%d] stopped PID %ld.\n",
(long int) pid, WSTOPSIG(status));
errno = EINTR;
return NULL;
}
}
}
}
/*
* If we're not running as euid==0, then we're assuming the file does exist
* and has group write permission aka SysV policy. There is a race
* condition here where the user might remove the file just after we've
* created it above, but if so, too bad for the user -- mailer readers
* should truncate the empty mailbox, not remove it.
*/
#define CREAT_TRUNC_ARE_OK ((geteuid() == 0 && uid == 0) || \
(geteuid() != 0 && access(fn, F_OK) != EOF))
if (type[0] == 'w') {
fd = open(fn, O_WRONLY | (CREAT_TRUNC_ARE_OK ? (O_CREAT | O_TRUNC) : 0), mode);
} else if (type[0] == 'a') {
fd = open(fn, O_WRONLY | O_APPEND | (CREAT_TRUNC_ARE_OK ? (O_CREAT) : 0), mode);
} else {
fd = open(fn, O_RDONLY, mode);
}
if (fd < 0) {
DEBUG2(DBG_DRIVER_MID, "fopen_as_user(): open(%s): %s.\n", fn, strerror(errno));
return(NULL);
}
if (geteuid() == 0 && uid != 0) {
if (fstat(fd, &stb_root) != 0) {
int oerrno = errno;
DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): root fstat(%s): %s.\n", fn, strerror(errno));
close(fd);
errno = oerrno;
return NULL;
}
switch ((pid = fork())) {
case EOF:
{
int oerrno = errno;
DEBUG1(DBG_DRIVER_LO, "fopen_as_user(): test child fork(): %s.\n", strerror(errno));
close(fd);
errno = oerrno;
}
return NULL;
/* NOTREACHED */
case 0:
if (getegid() != gid) { /* this is probably silly */
if (setgid(gid) != 0) {
DEBUG4(DBG_DRIVER_LO, "fopen_as_user(): test child setgid(%u): %s (uid=%u now, to become %u).\n",
(unsigned int) gid, strerror(errno), (unsigned int) getuid(), (unsigned int) uid);
exit(errno + 1);
/* NOTREACHED */
}
}
if (setuid(uid) != 0) {
DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): test child setuid(%u): %s.\n",
(unsigned int) uid, strerror(errno));
exit(errno + 1);
/* NOTREACHED */
}
if (type[0] == 'w') {
/*
* NB: Truncate only here to prevent people being able to
* wipe files under the open() done as root.
*/
cfd = open(fn, O_WRONLY | O_TRUNC, mode);
} else if (type[0] == 'a') {
cfd = open(fn, O_WRONLY | O_APPEND, mode);
} else {
cfd = open(fn, O_RDONLY, mode);
}
if (cfd < 0) {
DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): test child open(%s): %s.\n",
fn, strerror(errno));
exit(errno + 1);
/* NOTREACHED */
}
if (fstat(cfd, &stb_user) < 0) {
int oerrno = errno;
DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): test child fstat(%s): %s.\n",
fn, strerror(errno));
close(cfd);
exit(oerrno + 1);
}
close(cfd);
if ((stb_root.st_dev != stb_user.st_dev) ||
(stb_root.st_ino != stb_user.st_ino)) {
write_log(WRITE_LOG_SYS|WRITE_LOG_PANIC, /* XXX child writing to log here!!! */
"fopen_as_user: parent/child file mismatch!: %s.\n", fn);
exit(EACCES + 1); /* got a better idea? */
}
exit(0);
/* NOTREACHED */
default:
/* wait for the child process to die */
while ((i = wait((STATUS_TYPE *) &status)) != pid) {
if (i < 0) {
int oerrno = errno;
DEBUG2(DBG_DRIVER_MID, "fopen_as_user(): test child wait() for PID %ld: %s.\n",
(long int) pid, strerror(errno));
close(fd);
errno = oerrno;
return NULL;
}
}
if (WIFEXITED(status)) {
if (WEXITSTATUS(status)) {
DEBUG1(WEXITSTATUS(status) - 1 ? DBG_DRIVER_LO : DBG_DRIVER_HI,
"fopen_as_user(): test child exited with %d.\n",
WEXITSTATUS(status) - 1);
close(fd);
errno = WEXITSTATUS(status) - 1;
return NULL;
}
} else if (WIFSIGNALED(status)) {
DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): test child[%d] signalled PID %ld.\n",
(long int) pid, WTERMSIG(status));
close(fd);
errno = EINTR;
return NULL;
} else if (WIFSTOPPED(status)) {
DEBUG2(DBG_DRIVER_LO, "fopen_as_user(): test child[%d] stopped PID %d.\n",
(long int) pid, WSTOPSIG(status));
close(fd);
errno = EINTR;
return NULL;
}
}
}
if (!(fp = fdopen(fd, type))) {
int oerrno = errno;
DEBUG3(DBG_DRIVER_LO, "fopen_as_user(): fdopen(%s, %s): %s.\n",
fn, type, strerror(errno));
close(fd);
errno = oerrno;
return NULL;
}
DEBUG5(DBG_DRIVER_HI, "fopen_as_user(%s, %s, %u, %u, 0%o) returning\n",
fn, type, (unsigned int) uid, (unsigned int) gid, mode);
return fp;
}
/*
* 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 */
{
off_t offset;
int success;
if (! flock_mailbox) {
/* use filename-based locking for mailbox files */
return lock_file_by_name(fn);
}
offset = lseek(fileno(f), (off_t) 0L, SEEK_CUR);
(void) lseek(fileno(f), (off_t) 0L, SEEK_SET);
success = lock_fd_wait(fileno(f));
(void) lseek(fileno(f), offset, SEEK_SET);
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 = EOF; /* open lock file */
char *l_fn; /* lock file name */
int retries; /* remaining retries */
char apid[BITS_PER_INT/3 + 1]; /* ASCII representation of pid */
/* generate lockfile name */
if (lockfile_name(fn, &l_fn) == FAIL)
return FAIL;
/* if no lockfile required, that's it */
if (l_fn == NULL)
return SUCCEED;
/*
* 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, "%ld", (long int) 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[MAXLONG_B10_DIGITS + 1]; /* for contents of lock file (a PID) */
int ct;
int fd;
long int pid = 0; /* not pid_t -- we're reading it from a file */
struct stat statbuf;
fd = open(l_fn, O_RDONLY);
if (fd < 0) {
DEBUG2(DBG_DRIVER_MID, "%s: cannot open lock file: %s\n",
l_fn, strerror(errno));
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);
/* fine if it went away */
if ((fstat(fd, &statbuf) < 0) || (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, "%ld", &pid);
/* see if the pid exists */
if ((kill((pid_t) pid, 0) < 0) && (errno == ESRCH)) {
/* process does not exist, wait 5 seconds before removing */
(void) sleep(5);
if ((fstat(fd, &statbuf) == 0) && (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: stale lock file vanished\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 */
{
off_t offset;
if (! flock_mailbox) {
/* use the V6 locking protocol */
unlock_file_by_name(fn);
return;
}
offset = lseek(fileno(f), (off_t) 0L, SEEK_CUR);
(void) lseek(fileno(f), (off_t) 0L, SEEK_SET); /* unlock from beginning to end */
unlock_fd_wait(fileno(f));
(void) lseek(fileno(f), offset, SEEK_SET);
}
#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 */
/* generate lockfile name */
if (lockfile_name(fn, &l_fn) == FAIL)
return;
/* if no lockfile required, that's it */
if (l_fn == NULL)
return;
/*
* remove the lock file and clean up
*/
(void) unlink(l_fn);
xfree(l_fn);
}
/*
* generate a mailbox lockfile name into the given char **plfn.
* if returned pointer is NULL, no lockfile is required.
* return SUCCEED or FAIL.
*/
static int
lockfile_name(fn, plfn)
char *fn;
char **plfn;
{
char *base;
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*/
}
/* find basename */
base = strrchr(fn, '/') + 1;
#ifdef lint
base = base + 1; /* lint says not used (SunOS) */
#endif
/* assume that no lockfile is required */
*plfn = NULL;
#ifdef UNIX_XENIX
*plfn = xmalloc(sizeof("/tmp/.mlk") + 10);
(void) sprintf(*plfn, "/tmp/%.10s.mlk", base);
return SUCCEED;
#else /* not UNIX_XENIX */
/*
* 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.
*
* On POSIX systems, the only way to know whether truncation
* would occur is to call pathconf().
*/
# if defined(POSIX_OS)
{
int no_trunc_flag;
int name_max = 0;
char *p;
if ((int)strlen(base) > _POSIX_NAME_MAX - 2) {
char *dir;
p = base;
while (p > fn && *(p - 1) == '/') {
--p;
}
dir = xmalloc((size_t) ((p - fn) + 1));
(void) memcpy(dir, fn, (size_t) (p - fn));
dir[p - fn] = '\0';
no_trunc_flag = pathconf(dir, _PC_NO_TRUNC);
if (! no_trunc_flag) {
name_max = pathconf(dir, _PC_NAME_MAX);
}
xfree(dir);
if (no_trunc_flag < 0 || name_max < 0)
return FAIL;
if (! no_trunc_flag && (int)strlen(base) > name_max - 2) {
/*
* If there is not enough room to append a .l, then
* always succeed the lock operation. This is dangerous
* but there is probably nothing better to do.
*/
return SUCCEED;
}
}
}
# else /* not POSIX_OS */
# if !defined(UNIX_BSD) && !defined(UNIX_XENIX)
/* allow at least enough room for a trailing .l */
if (strlen(base) > DIRSIZ - 2) {
/*
* always succeed
*/
return SUCCEED;
}
# endif /* not UNIX_BSD and not UNIX_XENIX */
# endif /* POSIX_OS */
/* generate the lock filename */
*plfn = xmalloc(strlen(fn) + sizeof(".lock"));
(void) sprintf(*plfn, "%s.lock", fn);
return SUCCEED;
#endif /* not UNIX_XENIX */
}
/*
* 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()
{
static char *hostname;
# ifdef GETHOSTNAME_USE_PTR
size_t len;
# endif
hostname = xmalloc((size_t) (MAXHOSTNAMELEN + 1));
*hostname = '\0';
# ifdef GETHOSTNAME_USE_PTR
len = MAXHOSTNAMELEN;
(void) gethostname(hostname, &len);
# else
(void) gethostname(hostname, (size_t) MAXHOSTNAMELEN);
# endif
hostname[MAXHOSTNAMELEN] = '\0';
hostname = xrealloc(hostname, (size_t) strlen(hostname) + 1);
return hostname;
}
#else /* not HAVE_GETHOSTNAME */
# ifdef HAVE_UNAME
char *
compute_hostname()
{
static struct utsname utsnm;
(void) uname(&utsnm);
/* Is the sysname tag used for something interesting? */
return utsnm.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 STR(&hostname);
}
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 STR(&hostname);
}
# 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 */
char *
compute_domain(hostname)
char *hostname
#ifndef HAVE_BSD_NETWORKING
__attribute__((unused))
#endif
;
{
char *s = NULL;
char *p;
#if defined(HAVE_BSD_NETWORKING) && !defined(NO_USEGETHOSTBYNAME)
struct hostent *host_info;
#endif /* HAVE_BSD_NETWORKING && !NO_USEGETHOSTBYNAME */
#ifdef HAVE_BIND
s = bind_compute_domain();
#endif /* HAVE_BIND */
#if defined(HAVE_BSD_NETWORKING) && !defined(NO_USEGETHOSTBYNAME)
if (!s || !*s) {
/*
* gethostbyname() will usually expand an unqualified name, though on
* BIND systems it should be the same as what bind_compute_domain()
* will find, i.e. the value of "domain" from /etc/resolv.conf
*/
if ((host_info = gethostbyname(hostname))) {
if (strchr(host_info->h_name, '.')) {
char *hn = COPY_STRING(host_info->h_name);
p = strchr(hn, '.');
*p++ = '\0';
if (EQIC(hn, hostname)) { /* XXX is this check necessary? */
s = COPY_STRING(p);
}
xfree(hn);
}
}
}
#endif /* HAVE_BSD_NETWORKING && !NO_USEGETHOSTBYNAME */
#if defined(HAVE_GETDOMAINNAME)
if (!s || !*s) {
char *dn = xmalloc((size_t) MAXHOSTNAMELEN + 1);
*dn = '\0';
(void) getdomainname(dn, (size_t) MAXHOSTNAMELEN);
dn[MAXHOSTNAMELEN] = '\0';
s = xrealloc(dn, (size_t) strlen(dn) + 1);
}
#endif /* HAVE_GETDOMAINNAME */
/* Next bit of code removes trailing dots */
if (s && *s) {
p = s + strlen(s) - 1;
while (*p == '.') {
*p-- = '\0';
}
return(s);
}
return (NULL);
}
/*
* 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.
* 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 the 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 specifies 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_CLSERR - close 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 on critical errors, such as if pipe() or fork() failed.
*/
pid_t
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 */
volatile int errfd; /* fd to use for stdout/stderr */
unsigned int flags; /* flags affecting operation */
uid_t uid; /* user ID for child process */
gid_t 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 */
volatile int retries = FORK_RETRIES; /* countdown of retries */
pid_t pid; /* pid from fork() (XXX pid_t) */
int oerrno; /* errno from execv*(), etc. */
static char *min_env[] = {
#ifdef CHILD_ENVIRON
CHILD_ENVIRON,
#else /* CHILD_ENVIRON */
"HOME=/",
NULL, /* leave space for path */
NULL, /* leave space for shell */
#endif /* CHILD_ENVIRON */
NULL, /* leave space for timezone */
NULL, /* end of environment */
};
static char **minp = NULL;
#if defined(HAVE_SETUSERCONTEXT) || defined(HAVE_SETLOGIN)
struct passwd *upwd;
#endif
if (!argv) {
write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
"open_child() called with NULL argv!");
return EOF;
}
DEBUG5(DBG_DRIVER_HI, "open_child('%s' ... , env, in, out, %s, 0%o, %u, %u) called\n",
argv[0], errfd >= 0 ? "errlog" : "noerrlog", flags, (unsigned int) uid, (unsigned int) gid);
if (in && pipe(stdin_fd) < 0) {
write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
"open_child(%s): pipe(stdin_fd) failed: %s", argv[0], strerror(errno));
return EOF;
}
if (out && pipe(stdout_fd) < 0) {
write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
"open_child(%s): pipe(stdout_fd) failed: %s", argv[0], strerror(errno));
/* free resources */
if (in) {
(void) close(stdin_fd[0]);
(void) close(stdin_fd[1]);
}
return EOF;
}
/*
* keep trying to create a new child process until either we succeed, or we
* run out of retries
*
* Do not use vfork() -- we muck with far too much in here!
*/
while ((pid = fork()) < 0 && errno == EAGAIN && --retries >= 0) {
(void) sleep(FORK_INTERVAL); /* sleep a bit between retries */
}
if (pid < 0) {
write_log(WRITE_LOG_TTY | WRITE_LOG_SYS | WRITE_LOG_PANIC,
"open_child(%s): fork() failed after %d: %s", argv[0], FORK_RETRIES, strerror(errno));
/* free resources */
if (in) {
(void) close(stdin_fd[0]);
(void) close(stdin_fd[1]);
}
if (out) {
(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 == STDIN_FILENO || errfd == STDOUT_FILENO) {
int new_errfd;
/* search for a descriptor we don't care about */
for (new_errfd = (STDERR_FILENO + 1); new_errfd < 10; new_errfd++) {
if ((in && new_errfd == stdin_fd[0]) ||
(out && new_errfd == stdout_fd[1]))
{
continue;
}
break;
}
if (dup2(errfd, new_errfd) == -1) {
/* XXX this debug may not even print!!! */
write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
"open_child(%s): dup2(errfd, new_errfd) failed: %s", argv[0], strerror(errno));
_exit(EX_OSERR);
}
(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] != STDIN_FILENO) {
if (dup2(stdin_fd[0], STDIN_FILENO) == -1) {
/* XXX this debug may not even print!!! */
write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
"open_child(%s): dup2(stdin_fd[0], 0) failed: %s", argv[0], strerror(errno));
_exit(EX_OSERR);
}
(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(STDIN_FILENO);
if (open("/dev/null", O_RDONLY) < 0) { /* XXX assumes will re-open on just closed fd!!!! */
/* uggh! failed to open /dev/null, quit without flush */
write_log(WRITE_LOG_TTY | WRITE_LOG_SYS | WRITE_LOG_PANIC,
"open_child(%s): open(/dev/null, O_RDONLY) failed: %s", argv[0], strerror(errno));
_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] != STDOUT_FILENO) {
if (dup2(stdout_fd[1], STDOUT_FILENO) == -1) {
/* XXX this debug may not even print!!! */
write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
"open_child(%s): dup2(stdin_fd[1], 1) failed: %s", argv[0], strerror(errno));
_exit(EX_OSERR);
}
(void) close(stdout_fd[1]);
}
} else if (errfd >= 0) {
/* use the given fd for stdout */
if (dup2(errfd, STDOUT_FILENO) == -1) {
/* XXX this debug may not even print!!! */
write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
"open_child(%s): dup2(errfd, 1) failed: %s", argv[0], strerror(errno));
_exit(EX_OSERR);
}
} else if (flags & CHILD_DEVNULL) {
/* open /dev/null as stdout */
(void) close(STDOUT_FILENO);
if (open("/dev/null", O_WRONLY) < 0) { /* XXX assumes will re-open on just closed fd!!!! */
/* uggh! failed to open /dev/null, quit without flush */
write_log(WRITE_LOG_TTY | WRITE_LOG_SYS | WRITE_LOG_PANIC,
"open_child(%s): open(/dev/null, O_WRONLY) failed: %s", argv[0], strerror(errno));
_exit(EX_OSFILE);
}
}
}
if (errfd >= 0) {
/* use this fd as the stderr */
if (errfd != STDERR_FILENO) {
if (dup2(errfd, STDERR_FILENO) == -1) {
/* XXX this debug may not even print!!! */
write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
"open_child(%s): dup2(errfd, 2) failed: %s", argv[0], strerror(errno));
_exit(EX_OSERR);
}
(void) close(errfd);
}
} else if (flags & CHILD_DUPERR) {
/* duplicate stdout to get the stderr */
if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) {
/* XXX this debug may not even print!!! */
write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
"open_child(%s): dup2(1, 2) failed: %s", argv[0], strerror(errno));
_exit(EX_OSERR);
}
} else if (flags & CHILD_CLSERR) {
(void) close(STDERR_FILENO);
if (open("/dev/null", O_WRONLY) < 0) { /* XXX assumes will re-open on just closed fd!!!! */
/* uggh! failed to open /dev/null, quit without flush */
write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
"open_child(%s): open(/dev/null) failed: %s", argv[0], strerror(errno));
_exit(EX_OSFILE);
}
}
#ifdef POSIX_OS
/* NOTE: we must do this before using setlogin(2) [via setusercontext()] */
(void) setsid();
#else
(void) setpgrp(0, getpid());
#endif
#ifdef HAVE_SETUSERCONTEXT
if (!(upwd = getpwbyuid(uid))) {
write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_SYS | WRITE_LOG_PANIC : 0),
"open_child(%s): getpwbyuid(%u) for setusercontext() failed (possible local configuration error): %s",
argv[0], (unsigned int) uid, strerror(errno));
_exit(EX_OSERR);
}
/* setusercontext() doesn't set GID the way we want it set */
if (setgid(gid) == -1) {
write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_SYS | WRITE_LOG_PANIC : 0),
"open_child(%s): setgid(%u) failed (possible local configuration error): %s",
argv[0], (unsigned int) gid, strerror(errno));
_exit(EX_NOPERM);
}
/*
* every system with setusercontext(3) pretty much must have
* setgroups(2) and it's almost certain that it'll have a working
* version too -- i.e. one that knows what to do with a count of 0.
*/
if (setgroups(0, (gid_t *) NULL) != 0) {
write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_PANIC : 0),
"setgroups(0, NULL) failed: %s", gid, strerror(errno));
}
/*
* i.e. everything but LOGIN_SETGROUP and LOGIN_SETENV (the latter
* would be overridden by our own environment setup and use of
* execve().
*
* Note that without the pwd parameter setusercontext() turns off
* LOGIN_SETGROUP and LOGIN_SETLOGIN regardless, but it's easier,
* safer, and more portable for us to use getpwuid() than it is to do
* both that and then use login_getclass() and then use only the flags
* we know we want.
*/
if (setusercontext((login_cap_t *) NULL, upwd, uid, (LOGIN_SETRESOURCES |
LOGIN_SETPRIORITY |
LOGIN_SETUMASK |
LOGIN_SETLOGIN |
LOGIN_SETUSER |
LOGIN_SETPATH)) == -1) {
oerrno = errno;
write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_SYS | WRITE_LOG_PANIC : 0),
"open_child(%s): setusercontext() failed (possible local configuration error): %s",
argv[0], strerror(errno));
_exit((oerrno == EPERM) ? EX_NOPERM : EX_OSERR);
}
#else /* !HAVE_SETUSERCONTEXT */
# ifdef HAVE_SETLOGIN
if (!(upwd = getpwbyuid(uid))) {
write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_SYS | WRITE_LOG_PANIC : 0),
"open_child(%s): getpwbyuid(%u) for setlogin() failed (possible local configuration error): %s",
argv[0], (unsigned int) uid, strerror(errno));
_exit(EX_OSERR);
}
if (setlogin(upwd->pw_name) == -1) {
write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_SYS | WRITE_LOG_PANIC : 0),
"open_child(%s): setlogin(%s) failed (possible local configuration error): %s",
argv[0], upwd->pw_name, strerror(errno));
_exit(EX_NOPERM);
}
# endif
/* change uid and gid */
if (setgid(gid) == -1) {
write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_SYS | WRITE_LOG_PANIC : 0),
"open_child(%s): setgid(%u) failed (possible local configuration error): %s",
argv[0], (unsigned int) gid, strerror(errno));
_exit(EX_NOPERM);
}
# ifdef HAVE_SETGROUPS
/* clear out all extra groups. We don't want to have to deal with them */
{
gid_t dummy_grouplist[1];
dummy_grouplist[0] = gid;
if (setgroups(0, (gid_t *) NULL) != 0) {
if (setgroups(1, dummy_grouplist) != 0) {
write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_PANIC : 0),
"setgroups(0, NULL) and setgroups(1, [%d]) failed: %s", gid, strerror(errno));
}
}
}
# endif
if (setuid(uid) == -1) {
write_log(WRITE_LOG_TTY | ((geteuid() == 0) ? WRITE_LOG_SYS | WRITE_LOG_PANIC : 0),
"open_child(%s): setuid(%u) failed (possible local configuration error): %s",
argv[0], (unsigned int) uid, strerror(errno));
_exit(EX_NOPERM);
}
#endif /* !HAVE_SETUSERCONTEXT */
/* close all unnecessary file descriptors */
if ( !(flags & CHILD_NOCLOSE) ) {
close_all();
}
/* run the program in the current process' context */
if (envp) {
(void) execve(argv[0], argv, envp);
oerrno = errno;
DEBUG2(DBG_DRIVER_LO, "open_child(): execve(%s, envp): %s.\n", argv[0], strerror(errno));
} else if (flags & CHILD_MINENV) {
if (minp == NULL) {
/* pass through the TZ environment variable */
char *tz;
for (minp = min_env; *minp; minp++) {
;
}
#ifndef CHILD_ENVIRON
# ifdef SHELL_EXEC_PATH
*minp++ = xprintf("SHELL=%s", SHELL_EXEC_PATH);
# else
*minp++ = "SHELL=/bin/sh";
# endif
*minp++ = xprintf("PATH=%s", SECURE_PATH);
#endif /* CHILD_ENVIRON */
if ((tz = getenv("TZ"))) {
*minp++ = xprintf("TZ=%s", tz);
}
}
(void) execve(argv[0], argv, min_env);
oerrno = errno;
DEBUG2(DBG_DRIVER_LO, "open_child(): execve(%s, minenv): %s.\n", argv[0], strerror(errno));
} else {
(void) execv(argv[0], argv);
oerrno = errno;
DEBUG2(DBG_DRIVER_LO, "open_child(): execv(%s): %s.\n", argv[0], strerror(errno));
}
/* Oh well... all this work and we can't even exec the file! */
write_log(WRITE_LOG_TTY | WRITE_LOG_SYS | WRITE_LOG_PANIC,
"open_child(): exec(%s) failed (possible local configuration error): %s",
argv[0], strerror(oerrno));
_exit(EX_OSFILE);
} else {
/* in parent process */
if (in) {
/* close the child stdin, return the write-channel */
(void) close(stdin_fd[0]);
if (!(*in = fdopen(stdin_fd[1], "w"))) {
write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
"open_child(%s): fdopen(stdin_fd[1], w) failed: %s", argv[0], strerror(errno));
/* free resources */
(void) close(stdin_fd[1]);
if (out) {
(void) close(stdout_fd[0]);
(void) close(stdout_fd[1]);
}
return EOF;
}
}
if (out) {
/* close the child stdout, return the read-channel */
(void) close(stdout_fd[1]);
if (!(*out = fdopen(stdout_fd[0], "r"))) {
write_log(WRITE_LOG_TTY | WRITE_LOG_PANIC,
"open_child(%s): fdopen(stdout_fd[1], r) failed: %s", argv[0], strerror(errno));
/* free remaining resources */
(void) close(stdout_fd[0]);
return EOF;
}
}
}
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 do
* the wait() calls 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 */
pid_t 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_TYPE *) &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;
int max_nfd;
/*
* so, just how many file descriptors do we have?
*/
#ifdef OPEN_MAX
max_nfd = OPEN_MAX;
#else /* not POSIX_OS && not OPEN_MAX */
# ifdef NOFILE
max_nfd = NOFILE;
# else /* not POSIX_OS && not OPEN_MAX && not NOFILE */
max_nfd = 20;
# endif
#endif
for (i = 3; i < max_nfd; i++) {
if (errfile && i == fileno(errfile)) {
continue;
}
if (logfile && i == fileno(logfile)) {
continue;
}
if (panicfile && i == fileno(panicfile)) {
continue;
}
(void) close(i);
}
return;
}
#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)
const char *from; /* old file name */
const char *to; /* new file name */
{
struct stat statbuf;
if (stat(from, &statbuf) < 0) {
/* `from' is inaccessible, so forget it */
return -1;
}
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 */
unsigned int oumask = umask((~mode) & 0777);
pid_t 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) {
if (dirp) {
(void) closedir(dirp);
dirp = NULL;
}
dirp = opendir(dn);
}
if (dirp == NULL) {
return NULL; /* no directory, so no entries */
}
dp = readdir(dirp);
if (dp == NULL) {
(void) closedir(dirp);
dirp = NULL;
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_SETLK, &l) < 0) {
return FAIL;
}
return SUCCEED;
}
/*
* fcntl_rd_lock_wait - get a shared lock with blocking using a System V-style fcntl.
*/
int
fcntl_rd_lock_wait(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
/* touch - update the access and modified times of the given path;
* if both values are zero, the current time is used for both
*/
int
touch(fname, atime, mtime)
char *fname;
time_t atime, mtime;
{
struct utimbuf times;
time_t cur_time;
(void) memset((char *) ×, '\0', sizeof(times));
cur_time = time((time_t *) NULL);
times.actime = atime ? atime : cur_time;
times.modtime = mtime ? mtime : cur_time;
return utime(fname, ×);
}
/* fsetsize - set file size
*
* If truncation is possible, set file to wantsize.
* Otherwise, write NULs after oversize.
*/
/* ARGSUSED */
int
fsetsize(fd, wantsize, oversize)
int fd; /* fd to adjust */
off_t wantsize; /* desired size */
off_t oversize /* bytes not to overwrite */
#ifndef F_FREESP
__attribute__((unused))
#endif
;
{
off_t cursize;
/* determine current file size */
if ((cursize = lseek(fd, (off_t) 0L, SEEK_END)) == -1) {
return -1;
}
/* maybe lengthen... */
if (cursize < wantsize) {
if (lseek(fd, wantsize - 1, 0) == -1 || write(fd, "", (size_t) 1) == -1) {
return -1;
}
return 0;
}
/* maybe shorten... */
if (wantsize < cursize) {
#ifdef HAVE_FTRUNCATE
return ftruncate(fd, wantsize);
#else
# ifdef HAVE_CHSIZE
return chsize(fd, wantsize);
# else
# ifdef F_FREESP
/*
* Thanks to Larry Wall for this hack.
* It relies on the UNDOCUMENTED F_FREESP argument to
* fcntl(2), which truncates the file so that it ends
* at the position indicated by fl.l_start.
*/
struct flock fl;
fl.l_whence = 0;
fl.l_len = 0;
fl.l_start = wantsize;
fl.l_type = F_WRLCK; /* write lock on file space */
return fcntl(fd, F_FREESP, &fl);
# else /* !F_FREESP */
/*
* No truncation features are available.
* Zero out unwanted bytes (i.e. those past oversize).
*/
if (oversize < cursize) {
off_t left;
if (lseek(fd, oversize, SEEK_SET) == -1) {
return -1;
}
left = cursize - oversize;
while (left > 0) {
static char x[64];
size_t n;
if ((n = sizeof(x)) > left) {
n = left;
}
if (write(fd, x, n) == -1) {
return -1;
}
left -= n;
}
}
return 0;
# endif /* F_FREESP */
# endif /* HAVE_CHSIZE */
#endif /* HAVE_FTRUNCATE */
}
/* no adjustment required */
return 0;
}
#ifndef HAVE_VFPRINTF
/*
* vfprintf - a hacked version of vfprintf() for sites that don't have it
*
* XXX - will _doprnt() work here?
*/
int
vfprintf(file, fmt, ap)
FILE *file;
char *fmt;
va_list ap;
{
int a,b,c,d,e,f,g,h,i,j,k,l,m,n,o;
a = va_arg(ap, int);
b = va_arg(ap, int);
c = va_arg(ap, int);
d = va_arg(ap, int);
e = va_arg(ap, int);
f = va_arg(ap, int);
g = va_arg(ap, int);
h = va_arg(ap, int);
i = va_arg(ap, int);
j = va_arg(ap, int);
k = va_arg(ap, int);
l = va_arg(ap, int);
m = va_arg(ap, int);
n = va_arg(ap, int);
o = va_arg(ap, int);
return fprintf(file, fmt, a,b,c,d,e,f,g,h,i,j,k,l,m,n,o);
}
#endif /* HAVE_VFPRINTF */
#ifndef HAVE_STRERROR
/*
* strerror - return a string representing the error given in errnum.
*/
char *
strerror(errnum)
int errnum;
{
static char misc_err[sizeof("Unknown error: ") + MAXINT_B10_DIGITS + 1]; /* used when sprintf must be used */
#if 0
extern const char *const sys_errlist[]; /* 4.4BSD */
#else
extern char *sys_errlist[]; /* list of error strings */
#endif
extern int sys_nerr; /* number of entries in sys_errlist */
if (errnum > sys_nerr || errnum < 0) {
/* there is no entry for it in sys_errlist, build one */
(void) sprintf(misc_err, "Unknown error: %d", errnum);
return misc_err;
} else {
/* there is an entry in sys_errlist, use it */
return sys_errlist[errnum];
}
}
#endif /* HAVE_STRERROR */
/*
* strsysexit - return a string corresponding to an exit code.
*
* XXX as-is this should be sysexit2name() or something
*/
char *
strsysexit(exitcode)
int exitcode;
{
static char buf[sizeof("EX_") + MAXINT_B10_DIGITS + 1]; /* buffer for generating message */
switch (exitcode) {
case EX_USAGE:
return "EX_USAGE";
case EX_DATAERR:
return "EX_DATAERR";
case EX_NOINPUT:
return "EX_NOINPUT";
case EX_NOUSER:
return "EX_NOUSER";
case EX_NOHOST:
return "EX_NOHOST";
case EX_UNAVAILABLE:
return "EX_UNAVAILABLE";
case EX_SOFTWARE:
return "EX_SOFTWARE";
case EX_OSERR:
return "EX_OSERR";
case EX_OSFILE:
return "EX_OSFILE";
case EX_CANTCREAT:
return "EX_CANTCREAT";
case EX_IOERR:
return "EX_IOERR";
case EX_TEMPFAIL:
return "EX_TEMPFAIL";
case EX_PROTOCOL:
return "EX_PROTOCOL";
case EX_NOPERM:
return "EX_NOPERM";
default:
(void) sprintf(buf, "EX_%d", exitcode);
return buf;
}
}
/* NetBSD gained sys_nsig sometime just prior to 1.4 */
#if defined(__NetBSD_Version__) && (__NetBSD_Version__ >= 104000000)
# define HAVE_SYS_NSIG 1
# define SYS_NSIG_DECLARED 1
#endif
/* FreeBSD gained sys_nsig sometime just prior to 4.0 */
#if defined(__FreeBSD_version) && (__FreeBSD_version >= 400017)
# define HAVE_SYS_NSIG 1
# define SYS_NSIG_DECLARED 1
#endif
#if !defined(HAVE_STRSIGNAL) || (!defined(HAVE_SIG2STR) && defined(HAVE_SYS_SIGNAME))
# if !defined(SYS_NSIG_DECLARED) && !defined(__SYS_NSIG_DECLARED)
# if defined(HAVE_SYS_NSIG)
extern int sys_nsig;
# else
# define sys_nsig NSIG
# endif
# endif
#endif
#ifndef HAVE_STRSIGNAL
/*
* strsignal - return a pointer to a string describing the signal given in signum.
*/
char *
strsignal(signum)
int signum;
{
# if !defined(SYS_SIGLIST_DECLARED) && !defined(__SYS_SIGLIST_DECLARED)
# if 0 /* 4.4BSD, GNU/Linux, SCO5, SunOS-5, if they needed it */
extern const char *const sys_siglist[];
# else
extern char *sys_siglist[];
# endif
# endif
#if defined(SIGRTMIN) && defined(SIGRTMAX)
if (signum >= sys_nsig && signum >= SIGRTMIN && signum <= SIGRTMAX) {
static char rt_descr[sizeof("Real time signal: ") + MAXINT_B10_DIGITS + 1];
/*
* Signum is out of range but is within SIGRTMIN..SIGRTMAX so we build
* our own description for it.
*/
(void) sprintf(rt_descr, "Real time signal: %d", signum);
return rt_descr;
} else
#endif
if (signum >= sys_nsig || signum < 0) {
char signame[SIG2STR_MAX];
static char misc_err[sizeof("Unknown signal: SIG []") + MAXINT_B10_DIGITS + SIG2STR_MAX + 1];
/*
* Signum is out of range so there's no entry for it in sys_siglist, so
* we build our own description.
*
* Even though the SunOS-5 manual says "The strsignal() function
* returns NULL if sig is not a valid signal number.", it's easy enough
* to assume signum is valid and instead that sys_siglist is
* incomplete. The behaviour of this implementation matches that of
* 4.4BSD.
*/
if (sig2str(signum, signame) == -1) {
(void) sprintf(signame, "#%d", signum);
}
(void) sprintf(misc_err, "Unknown signal: SIG%s [%d]", signame, signum);
return misc_err;
}
/* there is an entry in sys_siglist, use it */
return sys_siglist[signum];
}
#endif /* HAVE_STRSIGNAL */
#ifndef HAVE_SIG2STR
# if defined(HAVE_SYS_SIGNAME)
# define SYS_NSIGNAME sys_nsig
# else
# define SYS_NSIGNAME sys_nsigname /* can't be sure gen'ed one has sys_nsig entries */
extern const unsigned int sys_nsigname; /* from ../compat/signame.c */
# endif
/*
* sig2str - pass back via signame a string naming the signal given by signum.
*
* Returns -1 if no name is known for the signal.
*
* NOTE: Unfortunately this most horrible API is "standard" in SunOS-5.x
*/
int
sig2str(signum, signame)
int signum;
char *signame; /* ptr to at least SIG2STR_MAX bytes */
{
# if !defined(SYS_SIGNAME_DECLARED) && !defined(__SYS_SIGNAME_DECLARED)
# if 0 /* 4.4BSD, Linux, etc. */
extern const char *const sys_signame[];
# else
extern char *sys_signame[];
# endif
# endif
# if defined(SIGRTMIN) && defined(SIGRTMAX)
if (signum >= (int) SYS_NSIGNAME && signum >= SIGRTMIN && signum <= SIGRTMAX) {
/*
* Signum is out of range but is within SIGRTMIN..SIGRTMAX so we build
* our own description for it.
*
* XXX any str2sig() implementation should try to account for this
* hack, just as the SunOS-5 implementation does, though perhaps not
* with the "RTMAX-1" weirdness.
*/
# if (SIG2STR_MAX < (MAXINT_B10_DIGITS + 7))
# include "ERROR: SIG2STR_MAX is too small!"
# endif
(void) sprintf(signame, "RTMIN+%d", (signum - SIGRTMIN));
return 0;
} else
# endif
if (signum >= (int) SYS_NSIGNAME || signum < 0) {
/* XXX Maybe we dould format "#%d" in here anyway? */
return (-1);
}
strncpy(signame, sys_signame[signum], (size_t) SIG2STR_MAX);
signame[SIG2STR_MAX - 1] = '\0';
return 0;
}
#endif /* HAVE_SIG2STR */
/*
* Unless the target system has POSIX.4 queued signals we really need reliable
* signals in order to safely handle SIGCHLD. This is because only with
* reliable signals are we free to ignore problems with lost signals (which
* would adversely affect our ability to reliably count remaining active child
* processes) and free to avoid worrying about creating a loop should we
* re-instate the signal handler before all exited child processes have been
* reaped.
*
* (except under old SysVr2.x with signal(SIGCLD) where the right thing
* always happens -- the signal hander is always re-entered if any SIGCLD
* signals arrive while it is executing, and with only wait(2) the signal
* handler must reap dead child processes).
*
* XXX it is basically impossible to deal with SIGCLD on SysVr3.x because it's
* not queued, and it is reset, and there's no non-blocking alternative to
* wait(2).
*
* XXX on BSD4.3 or BSD4.2 we will need to use yucky old sigvec(2).
*
* For all others (i.e. for POSIX_OS) we use sigaction(2).
*
* Rumour (in the form of code in Postfix) has it that on HP/UX (and perhaps
* others) the handler will have to muck with the signal context in order to
* get select() or accept() to return EINTR instead of restarting.
*/
#ifdef SA_NOCLDSTOP /* aka HAVE_SIGACTION aka POSIX_OS */
int
set_sigchld_handler(handler)
void (*handler) __P((int));
{
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
# ifdef SA_RESTART
/* NOTE: we do not intend accept() or select() to restart! */
action.sa_flags |= SA_RESTART;
# endif
action.sa_flags |= SA_NOCLDSTOP;
action.sa_handler = handler;
return sigaction(SIGCHLD, &action, (struct sigaction *) NULL);
}
#else /* SA_NOCLDSTOP */
# ifdef UNIX_SYS5_2
int
set_sigchld_handler(handler)
void (*handler)(int);
{
return (signal(SIGCLD, handler) == SIG_ERR) ? -1 : 0;
}
# else
# include "ERROR: smail not currently supported on this platform without sigaction(2)"
# endif /* UNIX_SYS5_2 */
#endif /* SA_NOCLDSTOP */
/*
* Local Variables:
* c-file-style: "smail"
* End:
*/
syntax highlighted by Code2HTML, v. 0.9.1