1 /* $NetBSD: crontab.c,v 1.32 2009/04/11 12:55:29 lukem Exp $ */
3 /* Copyright 1988,1990,1993,1994 by Paul Vixie
6 * Distribute freely, except: don't remove my name from the source or
7 * documentation (don't take credit for my work), mark your changes (don't
8 * get me blamed for your possible bugs), don't alter or remove this
9 * notice. May be sold if buildable source is provided to buyer. No
10 * warrantee of any kind, express or implied, is included with this
11 * software; use at your own risk, responsibility for damages (if any) to
12 * anyone resulting from the use of this software rests entirely with the
15 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
16 * I'll try to keep a version up to date. I can be reached as follows:
17 * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
20 #include <sys/cdefs.h>
21 #if !defined(lint) && !defined(LINT)
23 static char rcsid
[] = "Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp";
25 __RCSID("$NetBSD: crontab.c,v 1.32 2009/04/11 12:55:29 lukem Exp $");
29 /* crontab - install and manage per-user crontab files
30 * vix 02may87 [RCS has the rest of the log]
31 * vix 26jan87 [original]
35 #define MAXCRONTABSIZE (1024*256) /* max crontab size == 256 KB */
48 # include <sys/time.h>
59 #define NHEADER_LINES 3
62 enum opt_t
{ opt_unknown
, opt_list
, opt_delete
, opt_edit
, opt_replace
};
65 static const char *Options
[] = { "???", "list", "delete", "edit", "replace" };
70 static char User
[MAX_UNAME
], RealUser
[MAX_UNAME
];
71 static char Filename
[MAX_FNAME
];
72 static FILE *NewCrontab
;
73 static int CheckErrorCount
;
74 static enum opt_t Option
;
75 static struct passwd
*pw
;
76 static void list_cmd(void),
80 check_error(const char *),
81 parse_args(int c
, char *v
[]),
82 skip_header(int *, FILE *),
84 static int replace_cmd(void);
88 usage(const char *msg
)
90 fprintf(stderr
, "%s: usage error: %s\n", getprogname(), msg
);
91 fprintf(stderr
, "usage:\t%s [-u user] file\n", getprogname());
92 fprintf(stderr
, "\t%s [-u user] [ -e | -l | -r ]\n", getprogname());
93 fprintf(stderr
, "\t\t(default operation is replace, per 1003.2)\n");
94 fprintf(stderr
, "\t-e\t(edit user's crontab)\n");
95 fprintf(stderr
, "\t-l\t(list user's crontab)\n");
96 fprintf(stderr
, "\t-r\t(delete user's crontab)\n");
102 main(int argc
, char **argv
)
106 setprogname(argv
[0]);
110 setlocale(LC_ALL
, "");
116 parse_args(argc
, argv
); /* sets many globals, opens a file */
119 if (!allowed(User
)) {
121 "You (%s) are not allowed to use this program (%s)\n",
122 User
, getprogname());
123 fprintf(stderr
, "See crontab(1) for more information\n");
124 log_it(RealUser
, Pid
, "AUTH", "crontab command not allowed");
127 exitstatus
= OK_EXIT
;
129 case opt_list
: list_cmd();
131 case opt_delete
: delete_cmd();
133 case opt_edit
: edit_cmd();
135 case opt_replace
: if (replace_cmd() < 0)
136 exitstatus
= ERROR_EXIT
;
139 usage("unrecognized option");
148 parse_args(int argc
, char **argv
)
152 if (!(pw
= getpwuid(getuid()))) {
153 warnx("your UID isn't in the passwd file. bailing out.");
156 strlcpy(User
, pw
->pw_name
, sizeof(User
));
157 strlcpy(RealUser
, User
, sizeof(RealUser
));
159 Option
= opt_unknown
;
160 while (-1 != (argch
= getopt(argc
, argv
, "u:lerx:"))) {
163 if (!set_debug_flags(optarg
))
164 usage("bad debug option");
167 if (getuid() != ROOT_UID
)
169 warnx("must be privileged to use -u");
172 if (!(pw
= getpwnam(optarg
)))
174 warnx("user `%s' unknown", optarg
);
177 (void) strlcpy(User
, optarg
, sizeof(User
));
180 if (Option
!= opt_unknown
)
181 usage("only one operation permitted");
185 if (Option
!= opt_unknown
)
186 usage("only one operation permitted");
190 if (Option
!= opt_unknown
)
191 usage("only one operation permitted");
195 usage("unrecognized option");
201 if (Option
!= opt_unknown
) {
202 if (argv
[optind
] != NULL
) {
203 usage("no arguments permitted after this option");
206 if (argv
[optind
] != NULL
) {
207 Option
= opt_replace
;
208 (void) strlcpy(Filename
, argv
[optind
],
211 usage("file name must be specified for replace");
215 if (Option
== opt_replace
) {
216 /* we have to open the file here because we're going to
217 * chdir(2) into /var/cron before we get around to
220 if (!strcmp(Filename
, "-")) {
223 /* relinquish the setuid status of the binary during
224 * the open, lest nonroot users read files they should
225 * not be able to read. we can't use access() here
226 * since there's a race condition. thanks go out to
227 * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
231 if (swap_uids() < OK
) {
232 warn("cannot swap uids");
235 if (!(NewCrontab
= fopen(Filename
, "r"))) {
236 warn("cannot open %s", Filename
);
239 if (swap_uids() < OK
) {
240 warn("cannot swap uids back");
246 Debug(DMISC
, ("user=%s, file=%s, option=%s\n",
247 User
, Filename
, Options
[(int)Option
]))
251 skip_header(int *pch
, FILE *f
)
256 /* ignore the top few comments since we probably put them there.
258 for (x
= 0; x
< NHEADER_LINES
; x
++) {
264 while (EOF
!= (ch
= get_char(f
)))
283 log_it(RealUser
, Pid
, "LIST", User
);
284 (void) snprintf(n
, sizeof(n
), CRON_TAB(User
));
285 if (!(f
= fopen(n
, "r"))) {
287 warnx("no crontab for %s", User
);
289 warn("cannot open %s", n
);
293 /* file is open. copy to stdout, close.
297 for (; EOF
!= ch
; ch
= get_char(f
))
307 log_it(RealUser
, Pid
, "DELETE", User
);
308 (void) snprintf(n
, sizeof(n
), CRON_TAB(User
));
311 warnx("no crontab for %s", User
);
313 warn("cannot unlink %s", n
);
321 check_error(const char *msg
)
324 fprintf(stderr
, "\"%s\":%d: %s\n", Filename
, LineNumber
-1, msg
);
329 char n
[MAX_FNAME
], q
[MAX_TEMPSTR
];
341 log_it(RealUser
, Pid
, "BEGIN EDIT", User
);
342 (void) snprintf(n
, sizeof(n
), CRON_TAB(User
));
343 if (!(f
= fopen(n
, "r"))) {
344 if (errno
!= ENOENT
) {
345 warn("cannot open %s", n
);
348 warnx("no crontab for %s - using an empty one", User
);
349 if (!(f
= fopen("/dev/null", "r"))) {
350 warn("cannot open /dev/null");
355 (void) snprintf(Filename
, sizeof(Filename
), "/tmp/crontab.%d", Pid
);
356 if (-1 == (t
= open(Filename
, O_CREAT
|O_EXCL
|O_RDWR
, 0600))) {
357 warn("cannot open %s", Filename
);
361 if (fchown(t
, getuid(), getgid()) < 0) {
363 if (chown(Filename
, getuid(), getgid()) < 0) {
365 warn("cannot chown %s", Filename
);
368 if (fcntl(t
, F_SETFD
, FD_CLOEXEC
) == -1) {
369 warn("cannot set close on exec");
372 if (!(NewCrontab
= fdopen(t
, "r+"))) {
373 warn("cannot open fd");
380 /* copy the rest of the crontab (if any) to the temp file.
382 for (; EOF
!= ch
; ch
= get_char(f
))
383 putc(ch
, NewCrontab
);
385 if (fflush(NewCrontab
) < OK
) {
386 warn("cannot flush output for %s", Filename
);
391 if (ferror(NewCrontab
)) {
392 warn("error while writing new crontab to %s", Filename
);
393 fatal
: unlink(Filename
);
396 if (fstat(t
, &statbuf
) < 0) {
397 warn("cannot stat %s", Filename
);
400 mtime
= statbuf
.st_mtime
;
401 mtimensec
= statbuf
.st_mtimensec
;
403 if ((!(editor
= getenv("VISUAL")))
404 && (!(editor
= getenv("EDITOR")))
409 /* we still have the file open. editors will generally rewrite the
410 * original file rather than renaming/unlinking it and starting a
411 * new one; even backup files are supposed to be made by copying
412 * rather than by renaming. if some editor does not support this,
413 * then don't use it. the security problems are more severe if we
414 * close and reopen the file around the edit.
416 oint
= signal(SIGINT
, SIG_IGN
);
417 oabrt
= signal(SIGABRT
, SIG_IGN
);
419 switch (pid
= fork()) {
425 if (setuid(getuid()) < 0) {
426 warn("cannot setuid(getuid())");
429 if (chdir("/tmp") < 0) {
430 warn("cannot chdir(/tmp)");
433 asprintf(&edit
, "%s %s", editor
, Filename
);
434 if (system(edit
) == -1) {
435 warn("Cannot run editor %s", editor
);
445 xpid
= wait(&waiter
);
447 warnx("wrong PID (%d != %d) from \"%s\"", xpid
, pid
, editor
);
450 (void)signal(SIGINT
, oint
);
451 (void)signal(SIGABRT
, oabrt
);
453 if (WIFEXITED(waiter
) && WEXITSTATUS(waiter
)) {
454 warnx("\"%s\" exited with status %d",
455 editor
, WEXITSTATUS(waiter
));
458 if (WIFSIGNALED(waiter
)) {
459 warnx("\"%s\" killed; signal %d (%score dumped)",
460 editor
, WTERMSIG(waiter
), WCOREDUMP(waiter
) ?"" :"no ");
463 if (fstat(t
, &statbuf
) < 0) {
464 warn("cannot stat %s", Filename
);
467 if (mtime
== statbuf
.st_mtime
&& mtimensec
== statbuf
.st_mtimensec
) {
468 warnx("no changes made to crontab");
471 warnx("installing new crontab");
472 switch (replace_cmd()) {
478 printf("Do you want to retry the same edit? ");
481 (void) fgets(q
, sizeof q
, stdin
);
482 switch (tolower((unsigned char)q
[0])) {
488 fprintf(stderr
, "Enter Y or N\n");
494 warnx("edits left in %s", Filename
);
497 warnx("panic: bad switch() in replace_cmd()");
503 log_it(RealUser
, Pid
, "END EDIT", User
);
507 /* returns 0 on success
509 * -2 on install error
513 char n
[MAX_FNAME
], n2
[MAX_FNAME
], envstr
[MAX_ENVSTR
], tn
[MAX_FNAME
];
514 FILE *tmp
, *fmaxtabsize
;
517 time_t now
= time(NULL
);
518 char **envp
= env_init(), *tnp
= NULL
;
523 (void) snprintf(n
, sizeof(n
), "tmp.%d", Pid
);
524 (void) snprintf(tn
, sizeof(tn
), CRON_TAB(n
));
525 if (!(tmp
= fopen(tn
, "w+"))) {
526 warn("Cannot open %s", tn
);
531 /* Make sure that the crontab is not an unreasonable size.
533 * XXX This is subject to a race condition--the user could
534 * add stuff to the file after we've checked the size but
535 * before we slurp it in and write it out. We can't just move
536 * the test to test the temp file we later create, because by
537 * that time we've already filled up the crontab disk. Probably
538 * the right thing to do is to do a bytecount in the copy loop
539 * rather than stating the file we're about to read.
541 (void) snprintf(n2
, sizeof(n
), "%s/%s", CRONDIR
, MAXTABSIZE_FILE
);
542 if ((fmaxtabsize
= fopen(n2
, "r"))) {
543 if (fgets(n2
, sizeof(n2
), fmaxtabsize
) == NULL
) {
546 maxtabsize
= atoi(n2
);
550 maxtabsize
= MAXTABSIZE_DEFAULT
;
553 if (fstat(fileno(NewCrontab
), &statbuf
)) {
554 warn("error stat'ing crontab input");
557 if ((uintmax_t)statbuf
.st_size
> (uintmax_t)maxtabsize
) {
558 warnx("%ld bytes is larger than the maximum size of %ld bytes",
559 (long) statbuf
.st_size
, (long) maxtabsize
);
564 /* write a signature at the top of the file.
566 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
568 fprintf(tmp
, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
569 fprintf(tmp
, "# (%s installed on %-24.24s)\n", Filename
, ctime(&now
));
570 fprintf(tmp
, "# (Cron version -- %s)\n",
571 "$NetBSD: crontab.c,v 1.32 2009/04/11 12:55:29 lukem Exp $");
573 /* copy the crontab to the tmp
578 while (EOF
!= (ch
= get_char(NewCrontab
))) {
582 if (lastch
!= EOF
&& lastch
!= '\n') {
583 warnx("missing trailing newline in %s", Filename
);
588 if (ferror(NewCrontab
)) {
589 warn("error while reading %s", Filename
);
593 ftruncate(fileno(tmp
), ftell(tmp
)); /* XXX this should be a NOOP - is */
597 warn("error while writing new crontab to %s", tn
);
602 /* check the syntax of the file being installed.
605 /* BUG: was reporting errors after the EOF if there were any errors
606 * in the file proper -- kludged it by stopping after first error.
609 Set_LineNum(1 - NHEADER_LINES
)
610 CheckErrorCount
= 0; eof
= FALSE
;
611 while (!CheckErrorCount
&& !eof
) {
612 switch (load_env(envstr
, tmp
)) {
617 e
= load_entry(tmp
, check_error
, pw
, envp
);
626 if (CheckErrorCount
!= 0) {
627 warnx("errors in crontab file, can't install");
633 if (fchown(fileno(tmp
), ROOT_UID
, -1) < OK
)
635 if (chown(tn
, ROOT_UID
, -1) < OK
)
638 warn("cannot chown %s", tn
);
643 if (fchmod(fileno(tmp
), 0600) < OK
)
645 if (chmod(tn
, 0600) < OK
)
648 warn("cannot chmod %s", tn
);
652 if (fclose(tmp
) == EOF
) {
654 warn("error closing file");
659 (void) snprintf(n
, sizeof(n
), CRON_TAB(User
));
661 warn("error renaming %s to %s", tn
, n
);
664 log_it(RealUser
, Pid
, "REPLACE", User
);
682 struct timeval tvs
[2];
685 (void) gettimeofday(&tvs
[0], &tz
);
687 if (utimes(SPOOL_DIR
, tvs
) < OK
) {
688 warn("can't update mtime on spooldir %s", SPOOL_DIR
);
692 if (utime(SPOOL_DIR
, NULL
) < OK
) {
693 warn("can't update mtime on spooldir %s", SPOOL_DIR
);
696 #endif /*USE_UTIMES*/