root/usr.sbin/cron/lib/misc.c
/* Copyright 1988,1990,1993,1994 by Paul Vixie
 * All rights reserved
 */

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


/* 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 <errno.h>
#include <string.h>
#include <fcntl.h>
#if defined(SYSLOG)
# include <syslog.h>
#endif


#if defined(LOG_CRON) && defined(LOG_FILE)
# undef LOG_FILE
#endif

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


static int              LogFD = ERR;


int
strcmp_until(const char *left, const char *right, int until)
{
        while (*left && *left != until && *left == *right) {
                left++;
                right++;
        }

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


/* 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) {
                const char      **test;
                int             mask;

                /* try to find debug flag name in our list.
                 */
                for (   test = DebugFlagNames, mask = 1;
                        *test != NULL && 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);
}


/* 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, 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)
{
        FILE    *allow, *deny;
        int     isallowed;

        isallowed = FALSE;

        deny = NULL;
#if defined(ALLOW_FILE) && defined(DENY_FILE)
        if ((allow = fopen(ALLOW_FILE, "r")) == NULL && errno != ENOENT)
                goto out;
        if ((deny = fopen(DENY_FILE, "r")) == NULL && errno != ENOENT)
                goto out;
        Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny))
#else
        allow = NULL;
#endif

        if (allow)
                isallowed = in_file(username, allow);
        else if (deny)
                isallowed = !in_file(username, deny);
        else {
#if defined(ALLOW_ONLY_ROOT)
                isallowed = (strcmp(username, ROOT_USER) == 0);
#else
                isallowed = TRUE; 
#endif
        }
out:    if (allow)
                fclose(allow);
        if (deny)
                fclose(deny);
        return (isallowed);
}


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

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

#if defined(LOG_FILE)
        /* 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 {
                                (void) 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
 * parameters:
 *      s: string we want the first word of
 *      t: terminators, implicitly including \0
 */
char *
first_word(char *s, 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)
{
        /*
         * XXX
         * We know this routine can't overflow the dst buffer because mkprints()
         * allocated enough space for the worst case.
         */
        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 = 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[60];    /* zone name might be >3 chars */

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

        (void) 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()); }
int swap_uids_back(void) { return seteuid(save_euid); }
#else /*HAVE_SAVED_UIDS*/
int swap_uids(void) { return setreuid(geteuid(), getuid()); }
int swap_uids_back(void) { return swap_uids(); }
#endif /*HAVE_SAVED_UIDS*/