Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/cron/crontab/crontab.c
/* Copyright 1988,1990,1993,1994 by Paul Vixie | /* Copyright 1988,1990,1993,1994 by Paul Vixie | ||||
* All rights reserved | * All rights reserved | ||||
*/ | |||||
/* | |||||
* Copyright (c) 1997 by Internet Software Consortium | |||||
* | * | ||||
* Distribute freely, except: don't remove my name from the source or | * Permission to use, copy, modify, and distribute this software for any | ||||
* documentation (don't take credit for my work), mark your changes (don't | * purpose with or without fee is hereby granted, provided that the above | ||||
* get me blamed for your possible bugs), don't alter or remove this | * copyright notice and this permission notice appear in all copies. | ||||
* 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 | * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS | ||||
* I'll try to keep a version up to date. I can be reached as follows: | * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES | ||||
* Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul | * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE | ||||
* From Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp | * 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) | #if !defined(lint) && !defined(LINT) | ||||
static const char rcsid[] = | static const char rcsid[] = | ||||
"$FreeBSD$"; | "$Id: crontab.c,v 1.3 1998/08/14 00:32:38 vixie Exp $"; | ||||
#endif | #endif | ||||
/* crontab - install and manage per-user crontab files | /* crontab - install and manage per-user crontab files | ||||
* vix 02may87 [RCS has the rest of the log] | * vix 02may87 [RCS has the rest of the log] | ||||
* vix 26jan87 [original] | * vix 26jan87 [original] | ||||
*/ | */ | ||||
#define MAIN_PROGRAM | #define MAIN_PROGRAM | ||||
#include <sys/param.h> | |||||
#include "cron.h" | #include "cron.h" | ||||
#include <errno.h> | |||||
#include <fcntl.h> | |||||
#include <md5.h> | #include <md5.h> | ||||
#include <paths.h> | |||||
#include <sys/file.h> | |||||
#include <sys/stat.h> | |||||
#ifdef USE_UTIMES | |||||
# include <sys/time.h> | |||||
#else | |||||
# include <time.h> | |||||
# include <utime.h> | |||||
#endif | |||||
#if defined(POSIX) | |||||
# include <locale.h> | |||||
#endif | |||||
#define MD5_SIZE 33 | #define MD5_SIZE 33 | ||||
#define NHEADER_LINES 3 | #define NHEADER_LINES 3 | ||||
enum opt_t { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace }; | enum opt_t { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace }; | ||||
#if DEBUGGING | #if DEBUGGING | ||||
static char *Options[] = { "???", "list", "delete", "edit", "replace" }; | static char *Options[] = { "???", "list", "delete", "edit", "replace" }; | ||||
#endif | #endif | ||||
static PID_T Pid; | static PID_T Pid; | ||||
static char User[MAXLOGNAME], RealUser[MAXLOGNAME]; | static char User[MAXLOGNAME], RealUser[MAXLOGNAME]; | ||||
static char Filename[MAX_FNAME]; | static char Filename[MAX_FNAME]; | ||||
static FILE *NewCrontab; | static FILE *NewCrontab; | ||||
static int CheckErrorCount; | static int CheckErrorCount; | ||||
static enum opt_t Option; | static enum opt_t Option; | ||||
static int fflag; | static int fflag; | ||||
static struct passwd *pw; | static struct passwd *pw; | ||||
static void list_cmd(void), | static void list_cmd(void), | ||||
delete_cmd(void), | delete_cmd(void), | ||||
edit_cmd(void), | edit_cmd(void), | ||||
poke_daemon(void), | poke_daemon(void), | ||||
check_error(char *), | check_error(const char *), | ||||
parse_args(int c, char *v[]); | parse_args(int c, char *v[]); | ||||
static int replace_cmd(void); | static int replace_cmd(void); | ||||
static void | static void | ||||
usage(char *msg) | usage(const char *msg) | ||||
{ | { | ||||
fprintf(stderr, "crontab: usage error: %s\n", msg); | fprintf(stderr, "crontab: usage error: %s\n", msg); | ||||
fprintf(stderr, "%s\n%s\n", | fprintf(stderr, "%s\n%s\n", | ||||
"usage: crontab [-u user] file", | "usage: crontab [-u user] file", | ||||
" crontab [-u user] { -l | -r [-f] | -e }"); | " crontab [-u user] { -l | -r [-f] | -e }"); | ||||
exit(ERROR_EXIT); | exit(ERROR_EXIT); | ||||
} | } | ||||
int | int | ||||
main(int argc, char *argv[]) | main(int argc, char *argv[]) | ||||
{ | { | ||||
int exitstatus; | int exitstatus; | ||||
Pid = getpid(); | Pid = getpid(); | ||||
ProgramName = argv[0]; | ProgramName = argv[0]; | ||||
#if defined(POSIX) | |||||
setlocale(LC_ALL, ""); | setlocale(LC_ALL, ""); | ||||
#endif | |||||
#if defined(BSD) | #if defined(BSD) | ||||
setlinebuf(stderr); | setlinebuf(stderr); | ||||
#endif | #endif | ||||
parse_args(argc, argv); /* sets many globals, opens a file */ | parse_args(argc, argv); /* sets many globals, opens a file */ | ||||
set_cron_uid(); | set_cron_uid(); | ||||
set_cron_cwd(); | set_cron_cwd(); | ||||
if (!allowed(User)) { | if (!allowed(User)) { | ||||
warnx("you (%s) are not allowed to use this program", User); | warnx("you (%s) are not allowed to use this program", User); | ||||
log_it(RealUser, Pid, "AUTH", "crontab command not allowed"); | log_it(RealUser, Pid, "AUTH", "crontab command not allowed"); | ||||
exit(ERROR_EXIT); | exit(ERROR_EXIT); | ||||
} | } | ||||
exitstatus = OK_EXIT; | exitstatus = OK_EXIT; | ||||
switch (Option) { | switch (Option) { | ||||
case opt_list: list_cmd(); | case opt_list: | ||||
list_cmd(); | |||||
break; | break; | ||||
case opt_delete: delete_cmd(); | case opt_delete: | ||||
delete_cmd(); | |||||
break; | break; | ||||
case opt_edit: edit_cmd(); | case opt_edit: | ||||
edit_cmd(); | |||||
break; | break; | ||||
case opt_replace: if (replace_cmd() < 0) | case opt_replace: | ||||
if (replace_cmd() < 0) | |||||
exitstatus = ERROR_EXIT; | exitstatus = ERROR_EXIT; | ||||
break; | break; | ||||
case opt_unknown: | case opt_unknown: | ||||
break; | default: | ||||
abort(); | |||||
} | } | ||||
exit(exitstatus); | exit(exitstatus); | ||||
/*NOTREACHED*/ | /*NOTREACHED*/ | ||||
} | } | ||||
static void | static void | ||||
parse_args(int argc, char *argv[]) | parse_args(int argc, char *argv[]) | ||||
{ | { | ||||
int argch; | int argch; | ||||
char resolved_path[PATH_MAX]; | char resolved_path[PATH_MAX]; | ||||
if (!(pw = getpwuid(getuid()))) | if (!(pw = getpwuid(getuid()))) | ||||
errx(ERROR_EXIT, "your UID isn't in the passwd file, bailing out"); | errx(ERROR_EXIT, "your UID isn't in the passwd file, bailing out"); | ||||
bzero(pw->pw_passwd, strlen(pw->pw_passwd)); | bzero(pw->pw_passwd, strlen(pw->pw_passwd)); | ||||
(void) strncpy(User, pw->pw_name, (sizeof User)-1); | (void) strncpy(User, pw->pw_name, (sizeof User)-1); | ||||
User[(sizeof User)-1] = '\0'; | User[(sizeof User)-1] = '\0'; | ||||
strcpy(RealUser, User); | strcpy(RealUser, User); | ||||
Filename[0] = '\0'; | Filename[0] = '\0'; | ||||
▲ Show 20 Lines • Show All 82 Lines • ▼ Show 20 Lines | if (swap_uids_back() < OK) | ||||
err(ERROR_EXIT, "swapping uids back"); | err(ERROR_EXIT, "swapping uids back"); | ||||
} | } | ||||
Debug(DMISC, ("user=%s, file=%s, option=%s\n", | Debug(DMISC, ("user=%s, file=%s, option=%s\n", | ||||
User, Filename, Options[(int)Option])) | User, Filename, Options[(int)Option])) | ||||
} | } | ||||
static void | static void | ||||
copy_file(FILE *in, FILE *out) { | copy_file(FILE *in, FILE *out) | ||||
{ | |||||
int x, ch; | int x, ch; | ||||
Set_LineNum(1) | Set_LineNum(1) | ||||
/* ignore the top few comments since we probably put them there. | /* ignore the top few comments since we probably put them there. | ||||
*/ | */ | ||||
for (x = 0; x < NHEADER_LINES; x++) { | for (x = 0; x < NHEADER_LINES; x++) { | ||||
ch = get_char(in); | ch = get_char(in); | ||||
if (EOF == ch) | if (EOF == ch) | ||||
break; | break; | ||||
if ('#' != ch) { | if ('#' != ch) { | ||||
putc(ch, out); | putc(ch, out); | ||||
break; | break; | ||||
} | } | ||||
while (EOF != (ch = get_char(in))) | while (EOF != (ch = get_char(in))) | ||||
if (ch == '\n') | if (ch == '\n') | ||||
break; | break; | ||||
if (EOF == ch) | if (EOF == ch) | ||||
break; | break; | ||||
} | } | ||||
/* copy the rest of the crontab (if any) to the output file. | /* copy the rest of the crontab (if any) to the output file. | ||||
*/ | */ | ||||
if (EOF != ch) | if (EOF != ch) | ||||
while (EOF != (ch = get_char(in))) | while (EOF != (ch = get_char(in))) | ||||
putc(ch, out); | putc(ch, out); | ||||
} | } | ||||
static void | static void | ||||
list_cmd(void) | list_cmd(void) | ||||
{ | { | ||||
char n[MAX_FNAME]; | char n[MAX_FNAME]; | ||||
FILE *f; | FILE *f; | ||||
log_it(RealUser, Pid, "LIST", User); | log_it(RealUser, Pid, "LIST", User); | ||||
(void) snprintf(n, sizeof(n), CRON_TAB(User)); | (void) snprintf(n, sizeof(n), CRON_TAB(User)); | ||||
if (!(f = fopen(n, "r"))) { | if (!(f = fopen(n, "r"))) { | ||||
if (errno == ENOENT) | if (errno == ENOENT) | ||||
errx(ERROR_EXIT, "no crontab for %s", User); | errx(ERROR_EXIT, "no crontab for %s", User); | ||||
else | else | ||||
err(ERROR_EXIT, "%s", n); | err(ERROR_EXIT, "%s", n); | ||||
} | } | ||||
/* file is open. copy to stdout, close. | /* file is open. copy to stdout, close. | ||||
*/ | */ | ||||
copy_file(f, stdout); | copy_file(f, stdout); | ||||
fclose(f); | fclose(f); | ||||
} | } | ||||
static void | static void | ||||
delete_cmd(void) | delete_cmd(void) | ||||
{ | { | ||||
char n[MAX_FNAME]; | char n[MAX_FNAME]; | ||||
int ch, first; | int ch, first; | ||||
if (!fflag && isatty(STDIN_FILENO)) { | if (!fflag && isatty(STDIN_FILENO)) { | ||||
(void)fprintf(stderr, "remove crontab for %s? ", User); | (void)fprintf(stderr, "remove crontab for %s? ", User); | ||||
first = ch = getchar(); | first = ch = getchar(); | ||||
while (ch != '\n' && ch != EOF) | while (ch != '\n' && ch != EOF) | ||||
ch = getchar(); | ch = getchar(); | ||||
if (first != 'y' && first != 'Y') | if (first != 'y' && first != 'Y') | ||||
return; | return; | ||||
} | } | ||||
log_it(RealUser, Pid, "DELETE", User); | log_it(RealUser, Pid, "DELETE", User); | ||||
(void) snprintf(n, sizeof(n), CRON_TAB(User)); | if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n)) | ||||
if (unlink(n)) { | errx(ERROR_EXIT, "path too long"); | ||||
if (unlink(n) != 0) { | |||||
if (errno == ENOENT) | if (errno == ENOENT) | ||||
errx(ERROR_EXIT, "no crontab for %s", User); | errx(ERROR_EXIT, "no crontab for %s", User); | ||||
else | else | ||||
err(ERROR_EXIT, "%s", n); | err(ERROR_EXIT, "%s", n); | ||||
} | } | ||||
poke_daemon(); | poke_daemon(); | ||||
} | } | ||||
static void | static void | ||||
check_error(char *msg) | check_error(const char *msg) | ||||
{ | { | ||||
CheckErrorCount++; | CheckErrorCount++; | ||||
fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg); | fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg); | ||||
} | } | ||||
static void | static void | ||||
edit_cmd(void) | edit_cmd(void) | ||||
{ | { | ||||
char n[MAX_FNAME], q[MAX_TEMPSTR], *editor; | char n[MAX_FNAME], q[MAX_TEMPSTR], *editor; | ||||
FILE *f; | FILE *f; | ||||
int t; | int t; | ||||
struct stat statbuf, fsbuf; | struct stat statbuf, fsbuf; | ||||
WAIT_T waiter; | WAIT_T waiter; | ||||
PID_T pid, xpid; | PID_T pid, xpid; | ||||
mode_t um; | mode_t um; | ||||
int syntax_error = 0; | int syntax_error = 0; | ||||
char orig_md5[MD5_SIZE]; | char orig_md5[MD5_SIZE]; | ||||
char new_md5[MD5_SIZE]; | char new_md5[MD5_SIZE]; | ||||
log_it(RealUser, Pid, "BEGIN EDIT", User); | log_it(RealUser, Pid, "BEGIN EDIT", User); | ||||
(void) snprintf(n, sizeof(n), CRON_TAB(User)); | if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n)) | ||||
errx(ERROR_EXIT, "path too long"); | |||||
if (!(f = fopen(n, "r"))) { | if (!(f = fopen(n, "r"))) { | ||||
if (errno != ENOENT) | if (errno != ENOENT) | ||||
err(ERROR_EXIT, "%s", n); | err(ERROR_EXIT, "%s", n); | ||||
warnx("no crontab for %s - using an empty one", User); | warnx("no crontab for %s - using an empty one", User); | ||||
if (!(f = fopen(_PATH_DEVNULL, "r"))) | if (!(f = fopen(_PATH_DEVNULL, "r"))) | ||||
err(ERROR_EXIT, _PATH_DEVNULL); | err(ERROR_EXIT, _PATH_DEVNULL); | ||||
} | } | ||||
Show All 26 Lines | if (fstat(t, &fsbuf) < 0) { | ||||
warn("unable to fstat temp file"); | warn("unable to fstat temp file"); | ||||
goto fatal; | goto fatal; | ||||
} | } | ||||
again: | again: | ||||
if (swap_uids() < OK) | if (swap_uids() < OK) | ||||
err(ERROR_EXIT, "swapping uids"); | err(ERROR_EXIT, "swapping uids"); | ||||
if (stat(Filename, &statbuf) < 0) { | if (stat(Filename, &statbuf) < 0) { | ||||
warn("stat"); | warn("stat"); | ||||
fatal: unlink(Filename); | fatal: | ||||
unlink(Filename); | |||||
exit(ERROR_EXIT); | exit(ERROR_EXIT); | ||||
} | } | ||||
if (swap_uids_back() < OK) | if (swap_uids_back() < OK) | ||||
err(ERROR_EXIT, "swapping uids back"); | err(ERROR_EXIT, "swapping uids back"); | ||||
if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino) | if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino) | ||||
errx(ERROR_EXIT, "temp file must be edited in place"); | errx(ERROR_EXIT, "temp file must be edited in place"); | ||||
if (MD5File(Filename, orig_md5) == NULL) { | if (MD5File(Filename, orig_md5) == NULL) { | ||||
warn("MD5"); | warn("MD5"); | ||||
goto fatal; | goto fatal; | ||||
} | } | ||||
if ((!(editor = getenv("VISUAL"))) | if ((editor = getenv("VISUAL")) == NULL && | ||||
&& (!(editor = getenv("EDITOR"))) | (editor = getenv("EDITOR")) == NULL) { | ||||
) { | |||||
editor = EDITOR; | editor = EDITOR; | ||||
} | } | ||||
/* we still have the file open. editors will generally rewrite the | /* we still have the file open. editors will generally rewrite the | ||||
* original file rather than renaming/unlinking it and starting a | * original file rather than renaming/unlinking it and starting a | ||||
* new one; even backup files are supposed to be made by copying | * new one; even backup files are supposed to be made by copying | ||||
* rather than by renaming. if some editor does not support this, | * rather than by renaming. if some editor does not support this, | ||||
* then don't use it. the security problems are more severe if we | * then don't use it. the security problems are more severe if we | ||||
▲ Show 20 Lines • Show All 100 Lines • ▼ Show 20 Lines | |||||
/* returns 0 on success | /* returns 0 on success | ||||
* -1 on syntax error | * -1 on syntax error | ||||
* -2 on install error | * -2 on install error | ||||
*/ | */ | ||||
static int | static int | ||||
replace_cmd(void) | replace_cmd(void) | ||||
{ | { | ||||
char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME]; | char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME]; | ||||
FILE *tmp; | FILE *tmp; | ||||
int ch, eof; | int ch, eof; | ||||
entry *e; | entry *e; | ||||
time_t now = time(NULL); | time_t now = time(NULL); | ||||
char **envp = env_init(); | char **envp = env_init(); | ||||
if (envp == NULL) { | if (envp == NULL) { | ||||
warnx("cannot allocate memory"); | warnx("cannot allocate memory"); | ||||
return (-2); | return (-2); | ||||
} | } | ||||
(void) snprintf(n, sizeof(n), "tmp.%d", Pid); | (void) snprintf(n, sizeof(n), "tmp.%d", Pid); | ||||
(void) snprintf(tn, sizeof(tn), CRON_TAB(n)); | if (snprintf(tn, sizeof(tn), CRON_TAB(n)) >= (int)sizeof(tn)) { | ||||
warnx("path too long"); | |||||
return (-2); | |||||
} | |||||
if (!(tmp = fopen(tn, "w+"))) { | if (!(tmp = fopen(tn, "w+"))) { | ||||
warn("%s", tn); | warn("%s", tn); | ||||
return (-2); | return (-2); | ||||
} | } | ||||
/* write a signature at the top of the file. | /* write a signature at the top of the file. | ||||
* | * | ||||
▲ Show 20 Lines • Show All 71 Lines • ▼ Show 20 Lines | #endif | ||||
} | } | ||||
if (fclose(tmp) == EOF) { | if (fclose(tmp) == EOF) { | ||||
warn("fclose"); | warn("fclose"); | ||||
unlink(tn); | unlink(tn); | ||||
return (-2); | return (-2); | ||||
} | } | ||||
(void) snprintf(n, sizeof(n), CRON_TAB(User)); | if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n)) { | ||||
warnx("path too long"); | |||||
unlink(tn); | |||||
return (-2); | |||||
} | |||||
if (rename(tn, n)) { | if (rename(tn, n)) { | ||||
warn("error renaming %s to %s", tn, n); | warn("error renaming %s to %s", tn, n); | ||||
unlink(tn); | unlink(tn); | ||||
return (-2); | return (-2); | ||||
} | } | ||||
log_it(RealUser, Pid, "REPLACE", User); | log_it(RealUser, Pid, "REPLACE", User); | ||||
/* | /* | ||||
* Creating the 'tn' temp file has already updated the | * Creating the 'tn' temp file has already updated the | ||||
* modification time of the spool directory. Sleep for a | * modification time of the spool directory. Sleep for a | ||||
* second to ensure that poke_daemon() sets a later | * second to ensure that poke_daemon() sets a later | ||||
* modification time. Otherwise, this can race with the cron | * modification time. Otherwise, this can race with the cron | ||||
* daemon scanning for updated crontabs. | * daemon scanning for updated crontabs. | ||||
*/ | */ | ||||
sleep(1); | sleep(1); | ||||
poke_daemon(); | poke_daemon(); | ||||
return (0); | return (0); | ||||
} | } | ||||
static void | static void | ||||
poke_daemon(void) | poke_daemon(void) | ||||
{ | { | ||||
#ifdef USE_UTIMES | |||||
struct timeval tvs[2]; | |||||
(void)gettimeofday(&tvs[0], NULL); | |||||
tvs[1] = tvs[0]; | |||||
if (utimes(SPOOL_DIR, tvs) < OK) { | |||||
warn("can't update mtime on spooldir %s", SPOOL_DIR); | |||||
return; | |||||
} | |||||
#else | |||||
if (utime(SPOOL_DIR, NULL) < OK) { | if (utime(SPOOL_DIR, NULL) < OK) { | ||||
warn("can't update mtime on spooldir %s", SPOOL_DIR); | warn("can't update mtime on spooldir %s", SPOOL_DIR); | ||||
return; | return; | ||||
} | } | ||||
#endif /*USE_UTIMES*/ | |||||
} | } |