1 /* Copyright 1988,1990,1993,1994 by Paul Vixie
4 * Distribute freely, except: don't remove my name from the source or
5 * documentation (don't take credit for my work), mark your changes (don't
6 * get me blamed for your possible bugs), don't alter or remove this
7 * notice. May be sold if buildable source is provided to buyer. No
8 * warrantee of any kind, express or implied, is included with this
9 * software; use at your own risk, responsibility for damages (if any) to
10 * anyone resulting from the use of this software rests entirely with the
13 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
14 * I'll try to keep a version up to date. I can be reached as follows:
15 * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
17 * From Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp
18 * $FreeBSD: src/usr.sbin/cron/crontab/crontab.c,v 1.12.2.4 2001/06/16 03:18:37 peter Exp $
19 * $DragonFly: src/usr.sbin/cron/crontab/crontab.c,v 1.5 2004/12/18 22:48:03 swildner Exp $
22 /* crontab - install and manage per-user crontab files
23 * vix 02may87 [RCS has the rest of the log]
24 * vix 26jan87 [original]
36 # include <sys/time.h>
46 #define NHEADER_LINES 3
49 enum opt_t
{ opt_unknown
, opt_list
, opt_delete
, opt_edit
, opt_replace
};
52 static char *Options
[] = { "???", "list", "delete", "edit", "replace" };
57 static char User
[MAX_UNAME
], RealUser
[MAX_UNAME
];
58 static char Filename
[MAX_FNAME
];
59 static FILE *NewCrontab
;
60 static int CheckErrorCount
;
61 static enum opt_t Option
;
62 static struct passwd
*pw
;
63 static void list_cmd(void),
68 parse_args(int c
, char *v
[]);
69 static int replace_cmd(void);
75 fprintf(stderr
, "crontab: usage error: %s\n", msg
);
76 fprintf(stderr
, "%s\n%s\n",
77 "usage: crontab [-u user] file",
78 " crontab [-u user] { -e | -l | -r }");
84 main(int argc
, char **argv
)
89 ProgramName
= argv
[0];
92 setlocale(LC_ALL
, "");
98 parse_args(argc
, argv
); /* sets many globals, opens a file */
101 if (!allowed(User
)) {
102 warnx("you (%s) are not allowed to use this program", User
);
103 log_it(RealUser
, Pid
, "AUTH", "crontab command not allowed");
106 exitstatus
= OK_EXIT
;
108 case opt_list
: list_cmd();
110 case opt_delete
: delete_cmd();
112 case opt_edit
: edit_cmd();
114 case opt_replace
: if (replace_cmd() < 0)
115 exitstatus
= ERROR_EXIT
;
126 parse_args(int argc
, char **argv
)
130 if (!(pw
= getpwuid(getuid())))
131 errx(ERROR_EXIT
, "your UID isn't in the passwd file, bailing out");
132 strncpy(User
, pw
->pw_name
, (sizeof User
)-1);
133 User
[(sizeof User
)-1] = '\0';
134 strcpy(RealUser
, User
);
136 Option
= opt_unknown
;
137 while ((argch
= getopt(argc
, argv
, "u:lerx:")) != -1) {
140 if (!set_debug_flags(optarg
))
141 usage("bad debug option");
144 if (getuid() != ROOT_UID
)
145 errx(ERROR_EXIT
, "must be privileged to use -u");
146 if (!(pw
= getpwnam(optarg
)))
147 errx(ERROR_EXIT
, "user `%s' unknown", optarg
);
148 strncpy(User
, pw
->pw_name
, (sizeof User
)-1);
149 User
[(sizeof User
)-1] = '\0';
152 if (Option
!= opt_unknown
)
153 usage("only one operation permitted");
157 if (Option
!= opt_unknown
)
158 usage("only one operation permitted");
162 if (Option
!= opt_unknown
)
163 usage("only one operation permitted");
167 usage("unrecognized option");
173 if (Option
!= opt_unknown
) {
174 if (argv
[optind
] != NULL
) {
175 usage("no arguments permitted after this option");
178 if (argv
[optind
] != NULL
) {
179 Option
= opt_replace
;
180 strncpy (Filename
, argv
[optind
], (sizeof Filename
)-1);
181 Filename
[(sizeof Filename
)-1] = '\0';
184 usage("file name must be specified for replace");
188 if (Option
== opt_replace
) {
189 /* we have to open the file here because we're going to
190 * chdir(2) into /var/cron before we get around to
193 if (!strcmp(Filename
, "-")) {
196 /* relinquish the setuid status of the binary during
197 * the open, lest nonroot users read files they should
198 * not be able to read. we can't use access() here
199 * since there's a race condition. thanks go out to
200 * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
204 if (swap_uids() < OK
)
205 err(ERROR_EXIT
, "swapping uids");
206 if (!(NewCrontab
= fopen(Filename
, "r")))
207 err(ERROR_EXIT
, "%s", Filename
);
208 if (swap_uids() < OK
)
209 err(ERROR_EXIT
, "swapping uids back");
213 Debug(DMISC
, ("user=%s, file=%s, option=%s\n",
214 User
, Filename
, Options
[(int)Option
]))
225 log_it(RealUser
, Pid
, "LIST", User
);
226 sprintf(n
, CRON_TAB(User
));
227 if (!(f
= fopen(n
, "r"))) {
229 errx(ERROR_EXIT
, "no crontab for %s", User
);
231 err(ERROR_EXIT
, "%s", n
);
234 /* file is open. copy to stdout, close.
238 /* ignore the top few comments since we probably put them there.
240 for (x
= 0; x
< NHEADER_LINES
; x
++) {
248 while (EOF
!= (ch
= get_char(f
)))
255 while (EOF
!= (ch
= get_char(f
)))
267 if (isatty(STDIN_FILENO
)) {
268 fprintf(stderr
, "remove crontab for %s? ", User
);
269 first
= ch
= getchar();
270 while (ch
!= '\n' && ch
!= EOF
)
272 if (first
!= 'y' && first
!= 'Y')
276 log_it(RealUser
, Pid
, "DELETE", User
);
277 sprintf(n
, CRON_TAB(User
));
280 errx(ERROR_EXIT
, "no crontab for %s", User
);
282 err(ERROR_EXIT
, "%s", n
);
289 check_error(char *msg
)
292 fprintf(stderr
, "\"%s\":%d: %s\n", Filename
, LineNumber
-1, msg
);
299 char n
[MAX_FNAME
], q
[MAX_TEMPSTR
], *editor
;
302 struct stat statbuf
, fsbuf
;
308 log_it(RealUser
, Pid
, "BEGIN EDIT", User
);
309 sprintf(n
, CRON_TAB(User
));
310 if (!(f
= fopen(n
, "r"))) {
312 err(ERROR_EXIT
, "%s", n
);
313 warnx("no crontab for %s - using an empty one", User
);
314 if (!(f
= fopen(_PATH_DEVNULL
, "r")))
315 err(ERROR_EXIT
, _PATH_DEVNULL
);
319 sprintf(Filename
, "/tmp/crontab.XXXXXXXXXX");
320 if ((t
= mkstemp(Filename
)) == -1) {
321 warn("%s", Filename
);
327 if (fchown(t
, getuid(), getgid()) < 0) {
329 if (chown(Filename
, getuid(), getgid()) < 0) {
334 if (!(NewCrontab
= fdopen(t
, "r+"))) {
341 /* ignore the top few comments since we probably put them there.
343 for (x
= 0; x
< NHEADER_LINES
; x
++) {
348 putc(ch
, NewCrontab
);
351 while (EOF
!= (ch
= get_char(f
)))
358 /* copy the rest of the crontab (if any) to the temp file.
361 while (EOF
!= (ch
= get_char(f
)))
362 putc(ch
, NewCrontab
);
364 if (fflush(NewCrontab
))
365 err(ERROR_EXIT
, "%s", Filename
);
366 if (fstat(t
, &fsbuf
) < 0) {
367 warn("unable to fstat temp file");
371 if (stat(Filename
, &statbuf
) < 0) {
373 fatal
: unlink(Filename
);
376 if (statbuf
.st_dev
!= fsbuf
.st_dev
|| statbuf
.st_ino
!= fsbuf
.st_ino
)
377 errx(ERROR_EXIT
, "temp file must be edited in place");
378 mtime
= statbuf
.st_mtime
;
380 if ((!(editor
= getenv("VISUAL")))
381 && (!(editor
= getenv("EDITOR")))
386 /* we still have the file open. editors will generally rewrite the
387 * original file rather than renaming/unlinking it and starting a
388 * new one; even backup files are supposed to be made by copying
389 * rather than by renaming. if some editor does not support this,
390 * then don't use it. the security problems are more severe if we
391 * close and reopen the file around the edit.
394 switch (pid
= fork()) {
400 if (setuid(getuid()) < 0)
401 err(ERROR_EXIT
, "setuid(getuid())");
402 if (chdir("/tmp") < 0)
403 err(ERROR_EXIT
, "chdir(/tmp)");
404 if (strlen(editor
) + strlen(Filename
) + 2 >= MAX_TEMPSTR
)
405 errx(ERROR_EXIT
, "editor or filename too long");
406 execlp(editor
, editor
, Filename
, NULL
);
407 err(ERROR_EXIT
, "%s", editor
);
417 f
[0] = signal(SIGHUP
, SIG_IGN
);
418 f
[1] = signal(SIGINT
, SIG_IGN
);
419 f
[2] = signal(SIGTERM
, SIG_IGN
);
420 xpid
= wait(&waiter
);
421 signal(SIGHUP
, f
[0]);
422 signal(SIGINT
, f
[1]);
423 signal(SIGTERM
, f
[2]);
426 warnx("wrong PID (%d != %d) from \"%s\"", xpid
, pid
, editor
);
429 if (WIFEXITED(waiter
) && WEXITSTATUS(waiter
)) {
430 warnx("\"%s\" exited with status %d", editor
, WEXITSTATUS(waiter
));
433 if (WIFSIGNALED(waiter
)) {
434 warnx("\"%s\" killed; signal %d (%score dumped)",
435 editor
, WTERMSIG(waiter
), WCOREDUMP(waiter
) ?"" :"no ");
438 if (stat(Filename
, &statbuf
) < 0) {
442 if (statbuf
.st_dev
!= fsbuf
.st_dev
|| statbuf
.st_ino
!= fsbuf
.st_ino
)
443 errx(ERROR_EXIT
, "temp file must be edited in place");
444 if (mtime
== statbuf
.st_mtime
) {
445 warnx("no changes made to crontab");
448 warnx("installing new crontab");
449 switch (replace_cmd()) {
454 printf("Do you want to retry the same edit? ");
457 fgets(q
, sizeof q
, stdin
);
458 switch (islower(q
[0]) ? q
[0] : tolower(q
[0])) {
464 fprintf(stderr
, "Enter Y or N\n");
470 warnx("edits left in %s", Filename
);
473 warnx("panic: bad switch() in replace_cmd()");
479 log_it(RealUser
, Pid
, "END EDIT", User
);
483 /* returns 0 on success
485 * -2 on install error
490 char n
[MAX_FNAME
], envstr
[MAX_ENVSTR
], tn
[MAX_FNAME
];
494 time_t now
= time(NULL
);
495 char **envp
= env_init();
498 warnx("cannot allocate memory");
502 sprintf(n
, "tmp.%d", Pid
);
503 sprintf(tn
, CRON_TAB(n
));
504 if (!(tmp
= fopen(tn
, "w+"))) {
509 /* write a signature at the top of the file.
511 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
513 fprintf(tmp
, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
514 fprintf(tmp
, "# (%s installed on %-24.24s)\n", Filename
, ctime(&now
));
516 /* copy the crontab to the tmp
520 while (EOF
!= (ch
= get_char(NewCrontab
)))
522 ftruncate(fileno(tmp
), ftell(tmp
));
523 fflush(tmp
); rewind(tmp
);
526 warnx("error while writing new crontab to %s", tn
);
527 fclose(tmp
); unlink(tn
);
531 /* check the syntax of the file being installed.
534 /* BUG: was reporting errors after the EOF if there were any errors
535 * in the file proper -- kludged it by stopping after first error.
538 Set_LineNum(1 - NHEADER_LINES
)
539 CheckErrorCount
= 0; eof
= FALSE
;
540 while (!CheckErrorCount
&& !eof
) {
541 switch (load_env(envstr
, tmp
)) {
546 e
= load_entry(tmp
, check_error
, pw
, envp
);
555 if (CheckErrorCount
!= 0) {
556 warnx("errors in crontab file, can't install");
557 fclose(tmp
); unlink(tn
);
562 if (fchown(fileno(tmp
), ROOT_UID
, -1) < OK
)
564 if (chown(tn
, ROOT_UID
, -1) < OK
)
568 fclose(tmp
); unlink(tn
);
573 if (fchmod(fileno(tmp
), 0600) < OK
)
575 if (chmod(tn
, 0600) < OK
)
579 fclose(tmp
); unlink(tn
);
583 if (fclose(tmp
) == EOF
) {
589 sprintf(n
, CRON_TAB(User
));
591 warn("error renaming %s to %s", tn
, n
);
595 log_it(RealUser
, Pid
, "REPLACE", User
);
607 struct timeval tvs
[2];
610 gettimeofday(&tvs
[0], &tz
);
612 if (utimes(SPOOL_DIR
, tvs
) < OK
) {
613 warn("can't update mtime on spooldir %s", SPOOL_DIR
);
617 if (utime(SPOOL_DIR
, NULL
) < OK
) {
618 warn("can't update mtime on spooldir %s", SPOOL_DIR
);
621 #endif /*USE_UTIMES*/