fe590ffe40
Specifically, import the diff from commit e745bd4c10ab to commit 83563783cc2 in https://github.com/vixie/cron.git My sole motivation is changing to the common MIT license. The old license, especially the "buildable source" clause, is unfriendly for commercial users of this code. Simply changing the license without importing [most of] the code accompanying that license seemed legally dubious. The most regrettable change is losing Paul's uucp path. I partially atone for this loss by restoring the upstream $Id$ tags, since $FreeBSD$ is no longer useful. This is [intended to be] a complete list of the functional changes in this commit. Some changes were made so that we could consider vixie cron to be our upstream and reduce our diffs against it, while others were simply a good idea. - main() - use putenv instead of setenv for PATH - open_pidfile no longer needs snprintf to build pidfile - crontab main() - abort() on impossible errors - check for truncation when building strings with snprintf - getdtablesize() -> sysconf(_SC_OPEN_MAX) These changes were not taken from upstream's 4.0 diff because they [could] actually change behavior. Some of them might be beneficial, but should be taken separately. - config.h - sendmail args: remove -oi and add -or0s - call setlocale(LC_ALL, "") at the top of main() - acquire_daemonlock - we already use pidfile - cast getpid(), uid_t, and gid_t to long for printf - remove unnecessary braces - I consider them beneficial - BSDi support - glue_strings() - use snprintf(), as we often already did MFC after: on demand Sponsored by: Dell EMC Isilon Differential Revision: https://reviews.freebsd.org/D40260
588 lines
12 KiB
C
588 lines
12 KiB
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.
|
|
*/
|
|
|
|
#if !defined(lint) && !defined(LINT)
|
|
static const char rcsid[] = "$Id: misc.c,v 1.5 1998/08/14 00:32:40 vixie Exp $";
|
|
#endif
|
|
|
|
/* 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*/
|