/* Copyright 1988,1990,1993,1994 by Paul Vixie
* 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
* 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
* From Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp
#if !defined(lint) && !defined(LINT)
static char rcsid
[] = "$Header: /a/cvs/386BSD/src/usr.bin/crontab/crontab.c,v 1.2 1994/01/22 20:41:10 guido Exp $";
/* crontab - install and manage per-user crontab files
* vix 02may87 [RCS has the rest of the log]
enum opt_t
{ opt_unknown
, opt_list
, opt_delete
, opt_edit
, opt_replace
};
static char *Options
[] = { "???", "list", "delete", "edit", "replace" };
static char User
[MAX_UNAME
], RealUser
[MAX_UNAME
];
static char Filename
[MAX_FNAME
];
static int CheckErrorCount
;
static enum opt_t Option
;
static struct passwd
*pw
;
static void list_cmd
__P((void)),
check_error
__P((char *)),
parse_args
__P((int c
, char *v
[]));
static int replace_cmd
__P((void));
fprintf(stderr
, "%s: usage error: %s\n", ProgramName
, msg
);
fprintf(stderr
, "usage:\t%s [-u user] file\n", ProgramName
);
fprintf(stderr
, "\t%s [-u user] { -e | -l | -r }\n", ProgramName
);
fprintf(stderr
, "\t\t(default operation is replace, per 1003.2)\n");
fprintf(stderr
, "\t-e\t(edit user's crontab)\n");
fprintf(stderr
, "\t-l\t(list user's crontab)\n");
fprintf(stderr
, "\t-r\t(delete user's crontab)\n");
parse_args(argc
, argv
); /* sets many globals, opens a file */
"You (%s) are not allowed to use this program (%s)\n",
fprintf(stderr
, "See crontab(1) for more information\n");
log_it(RealUser
, Pid
, "AUTH", "crontab command not allowed");
case opt_list
: list_cmd();
case opt_delete
: delete_cmd();
case opt_edit
: edit_cmd();
case opt_replace
: if (replace_cmd() < 0)
if (!(pw
= getpwuid(getuid()))) {
fprintf(stderr
, "%s: your UID isn't in the passwd file.\n",
fprintf(stderr
, "bailing out.\n");
strcpy(User
, pw
->pw_name
);
while (EOF
!= (argch
= getopt(argc
, argv
, "u:lerx:"))) {
if (!set_debug_flags(optarg
))
usage("bad debug option");
if (getuid() != ROOT_UID
)
"must be privileged to use -u\n");
if (!(pw
= getpwnam(optarg
)))
fprintf(stderr
, "%s: user `%s' unknown\n",
(void) strcpy(User
, optarg
);
if (Option
!= opt_unknown
)
usage("only one operation permitted");
if (Option
!= opt_unknown
)
usage("only one operation permitted");
if (Option
!= opt_unknown
)
usage("only one operation permitted");
usage("unrecognized option");
if (Option
!= opt_unknown
) {
if (argv
[optind
] != NULL
) {
usage("no arguments permitted after this option");
if (argv
[optind
] != NULL
) {
(void) strcpy (Filename
, argv
[optind
]);
usage("file name must be specified for replace");
if (Option
== opt_replace
) {
/* we have to open the file here because we're going to
* chdir(2) into /var/cron before we get around to
if (!strcmp(Filename
, "-")) {
/* relinquish the setuid status of the binary during
* the open, lest nonroot users read files they should
* not be able to read. we can't use access() here
* since there's a race condition. thanks go out to
* Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
if (!(NewCrontab
= fopen(Filename
, "r"))) {
perror("swapping uids back");
Debug(DMISC
, ("user=%s, file=%s, option=%s\n",
User
, Filename
, Options
[(int)Option
]))
log_it(RealUser
, Pid
, "LIST", User
);
(void) sprintf(n
, CRON_TAB(User
));
if (!(f
= fopen(n
, "r"))) {
fprintf(stderr
, "no crontab for %s\n", User
);
/* file is open. copy to stdout, close.
while (EOF
!= (ch
= get_char(f
)))
log_it(RealUser
, Pid
, "DELETE", User
);
(void) sprintf(n
, CRON_TAB(User
));
fprintf(stderr
, "no crontab for %s\n", User
);
fprintf(stderr
, "\"%s\":%d: %s\n", Filename
, LineNumber
-1, msg
);
char n
[MAX_FNAME
], q
[MAX_TEMPSTR
], *editor
;
log_it(RealUser
, Pid
, "BEGIN EDIT", User
);
(void) sprintf(n
, CRON_TAB(User
));
if (!(f
= fopen(n
, "r"))) {
fprintf(stderr
, "no crontab for %s - using an empty one\n",
if (!(f
= fopen("/dev/null", "r"))) {
(void) sprintf(Filename
, "/tmp/crontab.%d", Pid
);
if (-1 == (t
= open(Filename
, O_CREAT
|O_EXCL
|O_RDWR
, 0600))) {
if (fchown(t
, getuid(), getgid()) < 0) {
if (chown(Filename
, getuid(), getgid()) < 0) {
if (!(NewCrontab
= fdopen(t
, "r+"))) {
/* ignore the top few comments since we probably put them there.
for (x
= 0; x
< NHEADER_LINES
; x
++) {
while (EOF
!= (ch
= get_char(f
)))
/* copy the rest of the crontab (if any) to the temp file.
while (EOF
!= (ch
= get_char(f
)))
if (fflush(NewCrontab
) < OK
) {
if (ferror(NewCrontab
)) {
fprintf(stderr
, "%s: error while writing new crontab to %s\n",
if (fstat(t
, &statbuf
) < 0) {
mtime
= statbuf
.st_mtime
;
if ((!(editor
= getenv("VISUAL")))
&& (!(editor
= getenv("EDITOR")))
/* we still have the file open. editors will generally rewrite the
* original file rather than renaming/unlinking it and starting a
* new one; even backup files are supposed to be made by copying
* rather than by renaming. if some editor does not support this,
* then don't use it. the security problems are more severe if we
* close and reopen the file around the edit.
if (setuid(getuid()) < 0) {
perror("setuid(getuid())");
if (strlen(editor
) + strlen(Filename
) + 2 >= MAX_TEMPSTR
) {
fprintf(stderr
, "%s: editor or filename too long\n",
sprintf(q
, "%s %s", editor
, Filename
);
execlp(_PATH_BSHELL
, _PATH_BSHELL
, "-c", q
, NULL
);
fprintf(stderr
, "%s: wrong PID (%d != %d) from \"%s\"\n",
ProgramName
, xpid
, pid
, editor
);
if (WIFEXITED(waiter
) && WEXITSTATUS(waiter
)) {
fprintf(stderr
, "%s: \"%s\" exited with status %d\n",
ProgramName
, editor
, WEXITSTATUS(waiter
));
if (WIFSIGNALED(waiter
)) {
"%s: \"%s\" killed; signal %d (%score dumped)\n",
ProgramName
, editor
, WTERMSIG(waiter
),
WCOREDUMP(waiter
) ?"" :"no ");
if (fstat(t
, &statbuf
) < 0) {
if (mtime
== statbuf
.st_mtime
) {
fprintf(stderr
, "%s: no changes made to crontab\n",
fprintf(stderr
, "%s: installing new crontab\n", ProgramName
);
printf("Do you want to retry the same edit? ");
(void) fgets(q
, sizeof q
, stdin
);
switch (islower(q
[0]) ? q
[0] : tolower(q
[0])) {
fprintf(stderr
, "Enter Y or N\n");
fprintf(stderr
, "%s: edits left in %s\n",
fprintf(stderr
, "%s: panic: bad switch() in replace_cmd()\n");
log_it(RealUser
, Pid
, "END EDIT", User
);
char n
[MAX_FNAME
], envstr
[MAX_ENVSTR
], tn
[MAX_FNAME
];
char **envp
= env_init();
(void) sprintf(n
, "tmp.%d", Pid
);
(void) sprintf(tn
, CRON_TAB(n
));
if (!(tmp
= fopen(tn
, "w+"))) {
/* write a signature at the top of the file.
* VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
fprintf(tmp
, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
fprintf(tmp
, "# (%s installed on %-24.24s)\n", Filename
, ctime(&now
));
fprintf(tmp
, "# (Cron version -- %s)\n", rcsid
);
/* copy the crontab to the tmp
while (EOF
!= (ch
= get_char(NewCrontab
)))
ftruncate(fileno(tmp
), ftell(tmp
));
fflush(tmp
); rewind(tmp
);
fprintf(stderr
, "%s: error while writing new crontab to %s\n",
/* check the syntax of the file being installed.
/* BUG: was reporting errors after the EOF if there were any errors
* in the file proper -- kludged it by stopping after first error.
Set_LineNum(1 - NHEADER_LINES
)
CheckErrorCount
= 0; eof
= FALSE
;
while (!CheckErrorCount
&& !eof
) {
switch (load_env(envstr
, tmp
)) {
e
= load_entry(tmp
, check_error
, pw
, envp
);
if (CheckErrorCount
!= 0) {
fprintf(stderr
, "errors in crontab file, can't install.\n");
if (fchown(fileno(tmp
), ROOT_UID
, -1) < OK
)
if (chown(tn
, ROOT_UID
, -1) < OK
)
if (fchmod(fileno(tmp
), 0600) < OK
)
if (chmod(tn
, 0600) < OK
)
if (fclose(tmp
) == EOF
) {
(void) sprintf(n
, CRON_TAB(User
));
fprintf(stderr
, "%s: error renaming %s to %s\n",
log_it(RealUser
, Pid
, "REPLACE", User
);
(void) gettimeofday(&tvs
[0], &tz
);
if (utimes(SPOOL_DIR
, tvs
) < OK
) {
fprintf(stderr
, "crontab: can't update mtime on spooldir\n");
if (utime(SPOOL_DIR
, NULL
) < OK
) {
fprintf(stderr
, "crontab: can't update mtime on spooldir\n");