root/usr.sbin/cron/lib/misc.c
/* Copyright 1988,1990,1993,1994 by Paul Vixie
 * All rights reserved
 *
 * Distribute freely, except: don't remove my name from the source or
 * documentation (don't take credit for my work), mark your changes (don't
 * get me blamed for your possible bugs), don't alter or remove this
 * notice.  May be sold if buildable source is provided to buyer.  No
 * warrantee of any kind, express or implied, is included with this
 * software; use at your own risk, responsibility for damages (if any) to
 * anyone resulting from the use of this software rests entirely with the
 * user.
 *
 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
 * I'll try to keep a version up to date.  I can be reached as follows:
 * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
 *
 * $FreeBSD: src/usr.sbin/cron/lib/misc.c,v 1.8.2.2 2002/04/28 22:45:53 dwmalone Exp $
 */

/* vix 26jan87 [RCS has the rest of the log]
 * vix 30dec86 [written]
 */


#include "cron.h"
#if SYS_TIME_H
# include <sys/time.h>
#else
# include <time.h>
#endif
#include <sys/file.h>
#include <sys/stat.h>
#include <err.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#if defined(SYSLOG)
# include <syslog.h>
#endif


#if defined(LOG_DAEMON) && !defined(LOG_CRON)
#define LOG_CRON LOG_DAEMON
#endif


static int              LogFD = ERR;


int
strcmp_until(char *left, char *right, int until)
{
        int diff;

        while (*left && *left != until && *left == *right) {
                left++;
                right++;
        }

        if ((*left=='\0' || *left == until) &&
            (*right=='\0' || *right == until)) {
                diff = 0;
        } else {
                diff = *left - *right;
        }

        return diff;
}


/* strdtb(s) - delete trailing blanks in string 's' and return new length
 */
int
strdtb(char *s)
{
        char    *x = s;

        /* scan forward to the null
         */
        while (*x)
                x++;

        /* scan backward to either the first character before the string,
         * or the last non-blank in the string, whichever comes first.
         */
        do      {x--;}
        while (x >= s && isspace(*x));

        /* one character beyond where we stopped above is where the null
         * goes.
         */
        *++x = '\0';

        /* the difference between the position of the null character and
         * the position of the first character of the string is the length.
         */
        return x - s;
}


int
set_debug_flags(char *flags)
{
        /* debug flags are of the form    flag[,flag ...]
         *
         * if an error occurs, print a message to stdout and return FALSE.
         * otherwise return TRUE after setting ERROR_FLAGS.
         */

#if !DEBUGGING

        printf("this program was compiled without debugging enabled\n");
        return FALSE;

#else /* DEBUGGING */

        char    *pc = flags;

        DebugFlags = 0;

        while (*pc) {
                char    **test;
                int     mask;

                /* try to find debug flag name in our list.
                 */
                for (   test = DebugFlagNames, mask = 1;
                        *test && strcmp_until(*test, pc, ',');
                        test++, mask <<= 1
                    )
                        ;

                if (!*test) {
                        fprintf(stderr,
                                "unrecognized debug flag <%s> <%s>\n",
                                flags, pc);
                        return FALSE;
                }

                DebugFlags |= mask;

                /* skip to the next flag
                 */
                while (*pc && *pc != ',')
                        pc++;
                if (*pc == ',')
                        pc++;
        }

        if (DebugFlags) {
                int     flag;

                fprintf(stderr, "debug flags enabled:");

                for (flag = 0;  DebugFlagNames[flag];  flag++)
                        if (DebugFlags & (1 << flag))
                                fprintf(stderr, " %s", DebugFlagNames[flag]);
                fprintf(stderr, "\n");
        }

        return TRUE;

#endif /* DEBUGGING */
}


void
set_cron_uid(void)
{
#if defined(BSD) || defined(POSIX)
        if (seteuid(ROOT_UID) < OK)
                err(ERROR_EXIT, "seteuid");
#else
        if (setuid(ROOT_UID) < OK)
                err(ERROR_EXIT, "setuid");
#endif
}


void
set_cron_cwd(void)
{
        struct stat     sb;

        /* first check for CRONDIR ("/var/cron" or some such)
         */
        if (stat(CRONDIR, &sb) < OK && errno == ENOENT) {
                warn("%s", CRONDIR);
                if (OK == mkdir(CRONDIR, 0700)) {
                        warnx("%s: created", CRONDIR);
                        stat(CRONDIR, &sb);
                } else {
                        err(ERROR_EXIT, "%s: mkdir", CRONDIR);
                }
        }
        if (!(sb.st_mode & S_IFDIR))
                err(ERROR_EXIT, "'%s' is not a directory, bailing out", CRONDIR);
        if (chdir(CRONDIR) < OK)
                err(ERROR_EXIT, "cannot chdir(%s), bailing out", CRONDIR);

        /* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such)
         */
        if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) {
                warn("%s", SPOOL_DIR);
                if (OK == mkdir(SPOOL_DIR, 0700)) {
                        warnx("%s: created", SPOOL_DIR);
                        stat(SPOOL_DIR, &sb);
                } else {
                        err(ERROR_EXIT, "%s: mkdir", SPOOL_DIR);
                }
        }
        if (!(sb.st_mode & S_IFDIR))
                err(ERROR_EXIT, "'%s' is not a directory, bailing out", SPOOL_DIR);
}


/* acquire_daemonlock() - write our PID into /etc/cron.pid, unless
 *      another daemon is already running, which we detect here.
 *
 * note: main() calls us twice; once before forking, once after.
 *      we maintain static storage of the file pointer so that we
 *      can rewrite our PID into the PIDFILE after the fork.
 *
 * it would be great if fflush() disassociated the file buffer.
 */
void
acquire_daemonlock(int closeflag)
{
        static  FILE    *fp = NULL;

        if (closeflag && fp) {
                fclose(fp);
                fp = NULL;
                return;
        }

        if (!fp) {
                char    pidfile[MAX_FNAME];
                char    buf[MAX_TEMPSTR];
                int     fd, otherpid;

                sprintf(pidfile, PIDFILE, PIDDIR);
                if ((-1 == (fd = open(pidfile, O_RDWR|O_CREAT, 0644)))
                    || (NULL == (fp = fdopen(fd, "r+")))
                    ) {
                        sprintf(buf, "can't open or create %.64s: %s",
                                pidfile, strerror(errno));
                        log_it("CRON", getpid(), "DEATH", buf);
                        errx(ERROR_EXIT, "%s", buf);
                }

                if (flock(fd, LOCK_EX|LOCK_NB) < OK) {
                        int save_errno = errno;

                        fscanf(fp, "%d", &otherpid);
                        sprintf(buf, "can't lock %.64s, otherpid may be %d: %s",
                                pidfile, otherpid, strerror(save_errno));
                        log_it("CRON", getpid(), "DEATH", buf);
                        errx(ERROR_EXIT, "%s", buf);
                }

                fcntl(fd, F_SETFD, 1);
        }

        rewind(fp);
        fprintf(fp, "%d\n", getpid());
        fflush(fp);
        ftruncate(fileno(fp), ftell(fp));

        /* abandon fd and fp even though the file is open. we need to
         * keep it open and locked, but we don't need the handles elsewhere.
         */
}

/* get_char(file) : like getc() but increment LineNumber on newlines
 */
int
get_char(FILE *file)
{
        int     ch;

        ch = getc(file);
        if (ch == '\n')
                Set_LineNum(LineNumber + 1)
        return ch;
}


/* unget_char(ch, file) : like ungetc but do LineNumber processing
 */
void
unget_char(int ch, FILE *file)
{
        ungetc(ch, file);
        if (ch == '\n')
                Set_LineNum(LineNumber - 1)
}


/* get_string(str, max, file, termstr) : like fgets() but
 *              (1) has terminator string which should include \n
 *              (2) will always leave room for the null
 *              (3) uses get_char() so LineNumber will be accurate
 *              (4) returns EOF or terminating character, whichever
 */
int
get_string(char *string, int size, FILE *file, const char *terms)
{
        int     ch;

        while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) {
                if (size > 1) {
                        *string++ = (char) ch;
                        size--;
                }
        }

        if (size > 0)
                *string = '\0';

        return ch;
}


/* skip_comments(file) : read past comment (if any)
 */
void
skip_comments(FILE *file)
{
        int     ch;

        while (EOF != (ch = get_char(file))) {
                /* ch is now the first character of a line.
                 */

                while (ch == ' ' || ch == '\t')
                        ch = get_char(file);

                if (ch == EOF)
                        break;

                /* ch is now the first non-blank character of a line.
                 */

                if (ch != '\n' && ch != '#')
                        break;

                /* ch must be a newline or comment as first non-blank
                 * character on a line.
                 */

                while (ch != '\n' && ch != EOF)
                        ch = get_char(file);

                /* ch is now the newline of a line which we're going to
                 * ignore.
                 */
        }
        if (ch != EOF)
                unget_char(ch, file);
}


/* int in_file(char *string, FILE *file)
 *      return TRUE if one of the lines in file matches string exactly,
 *      FALSE otherwise.
 */
static int
in_file(char *string, FILE *file)
{
        char line[MAX_TEMPSTR];

        rewind(file);
        while (fgets(line, MAX_TEMPSTR, file)) {
                if (line[0] != '\0')
                        if (line[strlen(line)-1] == '\n')
                                line[strlen(line)-1] = '\0';
                if (0 == strcmp(line, string))
                        return TRUE;
        }
        return FALSE;
}


/* int allowed(char *username)
 *      returns TRUE if (ALLOW_FILE exists and user is listed)
 *      or (DENY_FILE exists and user is NOT listed)
 *      or (neither file exists but user=="root" so it's okay)
 */
int
allowed(char *username)
{
        static int      init = FALSE;
        static FILE     *allow, *deny;

        if (!init) {
                init = TRUE;
#if defined(ALLOW_FILE) && defined(DENY_FILE)
                allow = fopen(ALLOW_FILE, "r");
                deny = fopen(DENY_FILE, "r");
                Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny))
#else
                allow = NULL;
                deny = NULL;
#endif
        }

        if (allow)
                return (in_file(username, allow));
        if (deny)
                return (!in_file(username, deny));

#if defined(ALLOW_ONLY_ROOT)
        return (strcmp(username, ROOT_USER) == 0);
#else
        return TRUE;
#endif
}


void
log_it(const char *username, int xpid, const char *event, const char *detail)
{
        PID_T pid = xpid;
#if defined(LOG_FILE)
        char *msg;
        TIME_T now;
        struct tm *t;
#endif /*LOG_FILE*/

#if defined(SYSLOG)
        static int              syslog_open = 0;
#endif

#if defined(LOG_FILE)
        now = time((TIME_T)0);
        t = localtime(&now);
        /* we assume that MAX_TEMPSTR will hold the date, time, &punctuation.
         */
        msg = malloc(strlen(username)
                     + strlen(event)
                     + strlen(detail)
                     + MAX_TEMPSTR);

        if (msg == NULL)
                warnx("failed to allocate memory for log message");
        else {
                if (LogFD < OK) {
                        LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600);
                        if (LogFD < OK) {
                                warn("can't open log file %s", LOG_FILE);
                        } else {
                                fcntl(LogFD, F_SETFD, 1);
                        }
                }

                /* we have to sprintf() it because fprintf() doesn't always
                 * write everything out in one chunk and this has to be
                 * atomically appended to the log file.
                 */
                sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n",
                        username,
                        t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min,
                        t->tm_sec, pid, event, detail);

                /* we have to run strlen() because sprintf() returns (char*)
                 * on old BSD.
                 */
                if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) {
                        if (LogFD >= OK)
                                warn("%s", LOG_FILE);
                        warnx("can't write to log file");
                        write(STDERR, msg, strlen(msg));
                }

                free(msg);
        }
#endif /*LOG_FILE*/

#if defined(SYSLOG)
        if (!syslog_open) {
                /* we don't use LOG_PID since the pid passed to us by
                 * our client may not be our own.  therefore we want to
                 * print the pid ourselves.
                 */
# ifdef LOG_DAEMON
                openlog(ProgramName, LOG_PID, LOG_CRON);
# else
                openlog(ProgramName, LOG_PID);
# endif
                syslog_open = TRUE;             /* assume openlog success */
        }

        syslog(LOG_INFO, "(%s) %s (%s)\n", username, event, detail);

#endif /*SYSLOG*/

#if DEBUGGING
        if (DebugFlags) {
                fprintf(stderr, "log_it: (%s %d) %s (%s)\n",
                        username, pid, event, detail);
        }
#endif
}


void
log_close(void) {
        if (LogFD != ERR) {
                close(LogFD);
                LogFD = ERR;
        }
}


/* two warnings:
 *      (1) this routine is fairly slow
 *      (2) it returns a pointer to static storage
 *
 * s    string we want the first word of
 * t    terminators, implicitly including \0
 */
char *
first_word(char *s, const char *t)
{
        static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish C had GC */
        static int retsel = 0;
        char *rb, *rp;

        /* select a return buffer */
        retsel = 1-retsel;
        rb = &retbuf[retsel][0];
        rp = rb;

        /* skip any leading terminators */
        while (*s && (NULL != strchr(t, *s))) {
                s++;
        }

        /* copy until next terminator or full buffer */
        while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) {
                *rp++ = *s++;
        }

        /* finish the return-string and return it */
        *rp = '\0';
        return rb;
}


/* warning:
 *      heavily ascii-dependent.
 */
static void
mkprint(char *dst, unsigned char *src, int len)
{
        while (len-- > 0)
        {
                unsigned char ch = *src++;

                if (ch < ' ') {                 /* control character */
                        *dst++ = '^';
                        *dst++ = ch + '@';
                } else if (ch < 0177) {         /* printable */
                        *dst++ = ch;
                } else if (ch == 0177) {        /* delete/rubout */
                        *dst++ = '^';
                        *dst++ = '?';
                } else {                        /* parity character */
                        sprintf(dst, "\\%03o", ch);
                        dst += 4;
                }
        }
        *dst = '\0';
}


/* warning:
 *      returns a pointer to malloc'd storage, you must call free yourself.
 */
char *
mkprints(unsigned char *src, unsigned int len)
{
        char *dst;
        
        dst = malloc(len * 4 + 1);
        if (dst != NULL)
                mkprint(dst, src, len);

        return dst;
}


#ifdef MAIL_DATE
/* Sat, 27 Feb 93 11:44:51 CST
 * 123456789012345678901234567
 */
char *
arpadate(time_t *clock)
{
        time_t t = clock ?*clock :time(0L);
        struct tm *tm = localtime(&t);
        static char ret[32];    /* zone name might be >3 chars */

        if (tm->tm_year >= 100)
                tm->tm_year += 1900;

        snprintf(ret, sizeof(ret), "%s, %2d %s %d %02d:%02d:%02d %s",
                 DowNames[tm->tm_wday],
                 tm->tm_mday,
                 MonthNames[tm->tm_mon],
                 tm->tm_year,
                 tm->tm_hour,
                 tm->tm_min,
                 tm->tm_sec,
                 TZONE(*tm));
        return ret;
}
#endif /*MAIL_DATE*/


#ifdef HAVE_SAVED_UIDS
static int save_euid;
int
swap_uids(void)
{
        save_euid = geteuid();
        return seteuid(getuid());
}
#else /*HAVE_SAVED_UIDS*/
int
swap_uids(void)
{
        return setreuid(geteuid(), getuid());
}
#endif /*HAVE_SAVED_UIDS*/