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]
27 static char version
[] = "$DragonFly: src/usr.sbin/cron/crontab/crontab.c,v 1.5 2004/12/18 22:48:03 swildner Exp $";
38 # include <sys/time.h>
48 #define NHEADER_LINES 3
51 enum opt_t
{ opt_unknown
, opt_list
, opt_delete
, opt_edit
, opt_replace
};
54 static char *Options
[] = { "???", "list", "delete", "edit", "replace" };
59 static char User
[MAX_UNAME
], RealUser
[MAX_UNAME
];
60 static char Filename
[MAX_FNAME
];
61 static FILE *NewCrontab
;
62 static int CheckErrorCount
;
63 static enum opt_t Option
;
64 static struct passwd
*pw
;
65 static void list_cmd(void),
70 parse_args(int c
, char *v
[]);
71 static int replace_cmd(void);
77 fprintf(stderr
, "crontab: usage error: %s\n", msg
);
78 fprintf(stderr
, "%s\n%s\n",
79 "usage: crontab [-u user] file",
80 " crontab [-u user] { -e | -l | -r }");
86 main(int argc
, char **argv
)
91 ProgramName
= argv
[0];
94 setlocale(LC_ALL
, "");
100 parse_args(argc
, argv
); /* sets many globals, opens a file */
103 if (!allowed(User
)) {
104 warnx("you (%s) are not allowed to use this program", User
);
105 log_it(RealUser
, Pid
, "AUTH", "crontab command not allowed");
108 exitstatus
= OK_EXIT
;
110 case opt_list
: list_cmd();
112 case opt_delete
: delete_cmd();
114 case opt_edit
: edit_cmd();
116 case opt_replace
: if (replace_cmd() < 0)
117 exitstatus
= ERROR_EXIT
;
128 parse_args(int argc
, char **argv
)
132 if (!(pw
= getpwuid(getuid())))
133 errx(ERROR_EXIT
, "your UID isn't in the passwd file, bailing out");
134 strncpy(User
, pw
->pw_name
, (sizeof User
)-1);
135 User
[(sizeof User
)-1] = '\0';
136 strcpy(RealUser
, User
);
138 Option
= opt_unknown
;
139 while ((argch
= getopt(argc
, argv
, "u:lerx:")) != -1) {
142 if (!set_debug_flags(optarg
))
143 usage("bad debug option");
146 if (getuid() != ROOT_UID
)
147 errx(ERROR_EXIT
, "must be privileged to use -u");
148 if (!(pw
= getpwnam(optarg
)))
149 errx(ERROR_EXIT
, "user `%s' unknown", optarg
);
150 strncpy(User
, pw
->pw_name
, (sizeof User
)-1);
151 User
[(sizeof User
)-1] = '\0';
154 if (Option
!= opt_unknown
)
155 usage("only one operation permitted");
159 if (Option
!= opt_unknown
)
160 usage("only one operation permitted");
164 if (Option
!= opt_unknown
)
165 usage("only one operation permitted");
169 usage("unrecognized option");
175 if (Option
!= opt_unknown
) {
176 if (argv
[optind
] != NULL
) {
177 usage("no arguments permitted after this option");
180 if (argv
[optind
] != NULL
) {
181 Option
= opt_replace
;
182 strncpy (Filename
, argv
[optind
], (sizeof Filename
)-1);
183 Filename
[(sizeof Filename
)-1] = '\0';
186 usage("file name must be specified for replace");
190 if (Option
== opt_replace
) {
191 /* we have to open the file here because we're going to
192 * chdir(2) into /var/cron before we get around to
195 if (!strcmp(Filename
, "-")) {
198 /* relinquish the setuid status of the binary during
199 * the open, lest nonroot users read files they should
200 * not be able to read. we can't use access() here
201 * since there's a race condition. thanks go out to
202 * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
206 if (swap_uids() < OK
)
207 err(ERROR_EXIT
, "swapping uids");
208 if (!(NewCrontab
= fopen(Filename
, "r")))
209 err(ERROR_EXIT
, "%s", Filename
);
210 if (swap_uids() < OK
)
211 err(ERROR_EXIT
, "swapping uids back");
215 Debug(DMISC
, ("user=%s, file=%s, option=%s\n",
216 User
, Filename
, Options
[(int)Option
]))
227 log_it(RealUser
, Pid
, "LIST", User
);
228 sprintf(n
, CRON_TAB(User
));
229 if (!(f
= fopen(n
, "r"))) {
231 errx(ERROR_EXIT
, "no crontab for %s", User
);
233 err(ERROR_EXIT
, "%s", n
);
236 /* file is open. copy to stdout, close.
240 /* ignore the top few comments since we probably put them there.
242 for (x
= 0; x
< NHEADER_LINES
; x
++) {
250 while (EOF
!= (ch
= get_char(f
)))
257 while (EOF
!= (ch
= get_char(f
)))
269 if (isatty(STDIN_FILENO
)) {
270 fprintf(stderr
, "remove crontab for %s? ", User
);
271 first
= ch
= getchar();
272 while (ch
!= '\n' && ch
!= EOF
)
274 if (first
!= 'y' && first
!= 'Y')
278 log_it(RealUser
, Pid
, "DELETE", User
);
279 sprintf(n
, CRON_TAB(User
));
282 errx(ERROR_EXIT
, "no crontab for %s", User
);
284 err(ERROR_EXIT
, "%s", n
);
291 check_error(char *msg
)
294 fprintf(stderr
, "\"%s\":%d: %s\n", Filename
, LineNumber
-1, msg
);
301 char n
[MAX_FNAME
], q
[MAX_TEMPSTR
], *editor
;
304 struct stat statbuf
, fsbuf
;
310 log_it(RealUser
, Pid
, "BEGIN EDIT", User
);
311 sprintf(n
, CRON_TAB(User
));
312 if (!(f
= fopen(n
, "r"))) {
314 err(ERROR_EXIT
, "%s", n
);
315 warnx("no crontab for %s - using an empty one", User
);
316 if (!(f
= fopen(_PATH_DEVNULL
, "r")))
317 err(ERROR_EXIT
, _PATH_DEVNULL
);
321 sprintf(Filename
, "/tmp/crontab.XXXXXXXXXX");
322 if ((t
= mkstemp(Filename
)) == -1) {
323 warn("%s", Filename
);
329 if (fchown(t
, getuid(), getgid()) < 0) {
331 if (chown(Filename
, getuid(), getgid()) < 0) {
336 if (!(NewCrontab
= fdopen(t
, "r+"))) {
343 /* ignore the top few comments since we probably put them there.
345 for (x
= 0; x
< NHEADER_LINES
; x
++) {
350 putc(ch
, NewCrontab
);
353 while (EOF
!= (ch
= get_char(f
)))
360 /* copy the rest of the crontab (if any) to the temp file.
363 while (EOF
!= (ch
= get_char(f
)))
364 putc(ch
, NewCrontab
);
366 if (fflush(NewCrontab
))
367 err(ERROR_EXIT
, "%s", Filename
);
368 if (fstat(t
, &fsbuf
) < 0) {
369 warn("unable to fstat temp file");
373 if (stat(Filename
, &statbuf
) < 0) {
375 fatal
: unlink(Filename
);
378 if (statbuf
.st_dev
!= fsbuf
.st_dev
|| statbuf
.st_ino
!= fsbuf
.st_ino
)
379 errx(ERROR_EXIT
, "temp file must be edited in place");
380 mtime
= statbuf
.st_mtime
;
382 if ((!(editor
= getenv("VISUAL")))
383 && (!(editor
= getenv("EDITOR")))
388 /* we still have the file open. editors will generally rewrite the
389 * original file rather than renaming/unlinking it and starting a
390 * new one; even backup files are supposed to be made by copying
391 * rather than by renaming. if some editor does not support this,
392 * then don't use it. the security problems are more severe if we
393 * close and reopen the file around the edit.
396 switch (pid
= fork()) {
402 if (setuid(getuid()) < 0)
403 err(ERROR_EXIT
, "setuid(getuid())");
404 if (chdir("/tmp") < 0)
405 err(ERROR_EXIT
, "chdir(/tmp)");
406 if (strlen(editor
) + strlen(Filename
) + 2 >= MAX_TEMPSTR
)
407 errx(ERROR_EXIT
, "editor or filename too long");
408 execlp(editor
, editor
, Filename
, NULL
);
409 err(ERROR_EXIT
, "%s", editor
);
419 f
[0] = signal(SIGHUP
, SIG_IGN
);
420 f
[1] = signal(SIGINT
, SIG_IGN
);
421 f
[2] = signal(SIGTERM
, SIG_IGN
);
422 xpid
= wait(&waiter
);
423 signal(SIGHUP
, f
[0]);
424 signal(SIGINT
, f
[1]);
425 signal(SIGTERM
, f
[2]);
428 warnx("wrong PID (%d != %d) from \"%s\"", xpid
, pid
, editor
);
431 if (WIFEXITED(waiter
) && WEXITSTATUS(waiter
)) {
432 warnx("\"%s\" exited with status %d", editor
, WEXITSTATUS(waiter
));
435 if (WIFSIGNALED(waiter
)) {
436 warnx("\"%s\" killed; signal %d (%score dumped)",
437 editor
, WTERMSIG(waiter
), WCOREDUMP(waiter
) ?"" :"no ");
440 if (stat(Filename
, &statbuf
) < 0) {
444 if (statbuf
.st_dev
!= fsbuf
.st_dev
|| statbuf
.st_ino
!= fsbuf
.st_ino
)
445 errx(ERROR_EXIT
, "temp file must be edited in place");
446 if (mtime
== statbuf
.st_mtime
) {
447 warnx("no changes made to crontab");
450 warnx("installing new crontab");
451 switch (replace_cmd()) {
456 printf("Do you want to retry the same edit? ");
459 fgets(q
, sizeof q
, stdin
);
460 switch (islower(q
[0]) ? q
[0] : tolower(q
[0])) {
466 fprintf(stderr
, "Enter Y or N\n");
472 warnx("edits left in %s", Filename
);
475 warnx("panic: bad switch() in replace_cmd()");
481 log_it(RealUser
, Pid
, "END EDIT", User
);
485 /* returns 0 on success
487 * -2 on install error
492 char n
[MAX_FNAME
], envstr
[MAX_ENVSTR
], tn
[MAX_FNAME
];
496 time_t now
= time(NULL
);
497 char **envp
= env_init();
500 warnx("cannot allocate memory");
504 sprintf(n
, "tmp.%d", Pid
);
505 sprintf(tn
, CRON_TAB(n
));
506 if (!(tmp
= fopen(tn
, "w+"))) {
511 /* write a signature at the top of the file.
513 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
515 fprintf(tmp
, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
516 fprintf(tmp
, "# (%s installed on %-24.24s)\n", Filename
, ctime(&now
));
517 fprintf(tmp
, "# (Cron version -- %s)\n", version
);
519 /* copy the crontab to the tmp
523 while (EOF
!= (ch
= get_char(NewCrontab
)))
525 ftruncate(fileno(tmp
), ftell(tmp
));
526 fflush(tmp
); rewind(tmp
);
529 warnx("error while writing new crontab to %s", tn
);
530 fclose(tmp
); unlink(tn
);
534 /* check the syntax of the file being installed.
537 /* BUG: was reporting errors after the EOF if there were any errors
538 * in the file proper -- kludged it by stopping after first error.
541 Set_LineNum(1 - NHEADER_LINES
)
542 CheckErrorCount
= 0; eof
= FALSE
;
543 while (!CheckErrorCount
&& !eof
) {
544 switch (load_env(envstr
, tmp
)) {
549 e
= load_entry(tmp
, check_error
, pw
, envp
);
558 if (CheckErrorCount
!= 0) {
559 warnx("errors in crontab file, can't install");
560 fclose(tmp
); unlink(tn
);
565 if (fchown(fileno(tmp
), ROOT_UID
, -1) < OK
)
567 if (chown(tn
, ROOT_UID
, -1) < OK
)
571 fclose(tmp
); unlink(tn
);
576 if (fchmod(fileno(tmp
), 0600) < OK
)
578 if (chmod(tn
, 0600) < OK
)
582 fclose(tmp
); unlink(tn
);
586 if (fclose(tmp
) == EOF
) {
592 sprintf(n
, CRON_TAB(User
));
594 warn("error renaming %s to %s", tn
, n
);
598 log_it(RealUser
, Pid
, "REPLACE", User
);
610 struct timeval tvs
[2];
613 gettimeofday(&tvs
[0], &tz
);
615 if (utimes(SPOOL_DIR
, tvs
) < OK
) {
616 warn("can't update mtime on spooldir %s", SPOOL_DIR
);
620 if (utime(SPOOL_DIR
, NULL
) < OK
) {
621 warn("can't update mtime on spooldir %s", SPOOL_DIR
);
624 #endif /*USE_UTIMES*/