1 /* Copyright 1988,1990,1993 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
18 #if !defined(lint) && !defined(LINT)
19 static char rcsid
[] = "$Id: crontab.c,v 1.1 1994/01/05 20:40:13 jtc Exp $";
22 /* crontab - install and manage per-user crontab files
23 * vix 02may87 [RCS has the rest of the log]
24 * vix 26jan87 [original]
39 # include <sys/time.h>
50 static char User
[MAX_UNAME
], RealUser
[MAX_UNAME
];
51 static char Filename
[MAX_FNAME
];
52 static FILE *NewCrontab
;
53 static int CheckErrorCount
;
54 static enum { opt_unknown
, opt_list
, opt_delete
, opt_edit
, opt_replace
}
56 static void list_cmd
__P((void)),
57 delete_cmd
__P((void)),
59 replace_cmd
__P((void)),
60 poke_daemon
__P((void)),
61 check_error
__P((char *)),
62 parse_args
__P((int c
, char *v
[]));
65 #define NHEADER_LINES 3
69 static char *Options
[] = { "???", "list", "delete", "edit", "replace" };
77 fprintf(stderr
, "%s: usage error: %s\n", ProgramName
, msg
);
78 fprintf(stderr
, "usage:\t%s [-u user] file\n", ProgramName
);
79 fprintf(stderr
, "\t%s [-u user] { -e | -l | -r }\n", ProgramName
);
80 fprintf(stderr
, "\t\t(default operation is replace, per 1003.2)\n");
81 fprintf(stderr
, "\t-e\t(edit user's crontab)\n");
82 fprintf(stderr
, "\t-l\t(list user's crontab)\n");
83 fprintf(stderr
, "\t-r\t(delete user's crontab)\n");
94 ProgramName
= argv
[0];
97 setlocale(LC_ALL
, "");
103 parse_args(argc
, argv
); /* sets many globals, opens a file */
106 if (!allowed(User
)) {
108 "You (%s) are not allowed to use this program (%s)\n",
110 fprintf(stderr
, "See crontab(1) for more information\n");
111 log_it(RealUser
, Pid
, "AUTH", "crontab command not allowed");
116 case opt_list
: list_cmd();
118 case opt_delete
: delete_cmd();
120 case opt_edit
: edit_cmd();
122 case opt_replace
: replace_cmd();
131 parse_args(argc
, argv
)
138 if (!(pw
= getpwuid(getuid()))) {
139 fprintf(stderr
, "%s: your UID isn't in the passwd file.\n",
141 fprintf(stderr
, "bailing out.\n");
144 strcpy(User
, pw
->pw_name
);
145 strcpy(RealUser
, User
);
147 Option
= opt_unknown
;
148 while (EOF
!= (argch
= getopt(argc
, argv
, "u:lerx:"))) {
151 if (!set_debug_flags(optarg
))
152 usage("bad debug option");
155 if (getuid() != ROOT_UID
)
158 "must be privileged to use -u\n");
161 if ((struct passwd
*)NULL
== getpwnam(optarg
))
163 fprintf(stderr
, "%s: user `%s' unknown\n",
164 ProgramName
, optarg
);
167 (void) strcpy(User
, optarg
);
170 if (Option
!= opt_unknown
)
171 usage("only one operation permitted");
175 if (Option
!= opt_unknown
)
176 usage("only one operation permitted");
180 if (Option
!= opt_unknown
)
181 usage("only one operation permitted");
185 usage("unrecognized option");
191 if (Option
!= opt_unknown
) {
192 if (argv
[optind
] != NULL
) {
193 usage("no arguments permitted after this option");
196 if (argv
[optind
] != NULL
) {
197 Option
= opt_replace
;
198 (void) strcpy (Filename
, argv
[optind
]);
200 usage("file name must be specified for replace");
204 if (Option
== opt_replace
) {
205 /* we have to open the file here because we're going to
206 * chdir(2) into /var/cron before we get around to
209 if (!strcmp(Filename
, "-")) {
212 /* relinquish the setuid status of the binary during
213 * the open, lest nonroot users read files they should
214 * not be able to read. we can't use access() here
215 * since there's a race condition. thanks go out to
216 * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
220 if (swap_uids() < OK
) {
221 perror("swapping uids");
224 if (!(NewCrontab
= fopen(Filename
, "r"))) {
228 if (swap_uids() < OK
) {
229 perror("swapping uids back");
235 Debug(DMISC
, ("user=%s, file=%s, option=%s\n",
236 User
, Filename
, Options
[(int)Option
]))
246 log_it(RealUser
, Pid
, "LIST", User
);
247 (void) sprintf(n
, CRON_TAB(User
));
248 if (!(f
= fopen(n
, "r"))) {
250 fprintf(stderr
, "no crontab for %s\n", User
);
256 /* file is open. copy to stdout, close.
259 while (EOF
!= (ch
= get_char(f
)))
269 log_it(RealUser
, Pid
, "DELETE", User
);
270 (void) sprintf(n
, CRON_TAB(User
));
273 fprintf(stderr
, "no crontab for %s\n", User
);
286 CheckErrorCount
+= 1;
287 fprintf(stderr
, "\"%s\", line %d: %s\n", Filename
, LineNumber
, msg
);
293 char n
[MAX_FNAME
], *editor
;
300 log_it(RealUser
, Pid
, "BEGIN EDIT", User
);
302 (void) sprintf(n
, CRON_TAB(User
));
303 if (!(f
= fopen(n
, "r"))) {
304 if (errno
!= ENOENT
) {
308 fprintf(stderr
, "no crontab for %s - using an empty one\n",
310 if (!(f
= fopen("/dev/null", "r"))) {
316 (void) sprintf(Filename
, "/tmp/crontab.%d", Pid
);
317 if (-1 == (t
= open(Filename
, O_CREAT
|O_EXCL
|O_RDWR
, 0700))) {
322 if (fchown(t
, getuid(), getgid()) < 0) {
324 if (chown(Filename
, getuid(), getgid()) < 0) {
329 if (!(NewCrontab
= fdopen(t
, "r+"))) {
336 /* ignore the top few comments since we probably put them there.
338 for (x
= 0; x
< NHEADER_LINES
; x
++) {
343 putc(ch
, NewCrontab
);
346 while (EOF
!= (ch
= get_char(f
)))
353 /* copy the rest of the crontab (if any) to the temp file.
356 while (EOF
!= (ch
= get_char(f
)))
357 putc(ch
, NewCrontab
);
359 fflush(NewCrontab
); rewind(NewCrontab
);
360 if (ferror(NewCrontab
)) {
361 fprintf(stderr
, "%s: error while writing new crontab to %s\n",
362 ProgramName
, Filename
);
363 fatal
: if (NewCrontab
) fclose(NewCrontab
);
367 if (fstat(t
, &statbuf
) < 0) {
371 mtime
= statbuf
.st_mtime
;
373 if ((!(editor
= getenv("VISUAL")))
374 && (!(editor
= getenv("EDITOR")))
379 /* we still have the file open. editors will generally rewrite the
380 * original file rather than renaming/unlinking it and starting a
381 * new one; even backup files are supposed to be made by copying
382 * rather than by renaming. if some editor does not support this,
383 * then don't use it. the security problems are more severe if we
384 * close and reopen the file around the edit.
387 switch (pid
= fork()) {
393 if (setuid(getuid()) < 0) {
394 perror("setuid(getuid())");
397 if (chdir("/tmp") < 0) {
398 perror("chdir(/tmp)");
401 execlp(editor
, editor
, Filename
, NULL
);
413 fprintf(stderr
, "%s: wrong PID (%d != %d) from \"%s\"\n",
414 ProgramName
, x
, pid
, editor
);
417 if (WIFEXITED(waiter
) && WEXITSTATUS(waiter
)) {
418 fprintf(stderr
, "%s: \"%s\" exited with status %d\n",
419 ProgramName
, editor
, WEXITSTATUS(waiter
));
422 if (WIFSIGNALED(waiter
)) {
424 "%s: \"%s\" killed; signal %d (%score dumped)\n",
425 ProgramName
, editor
, WTERMSIG(waiter
),
426 WCOREDUMP(waiter
) ?"" :"no ");
429 if (fstat(t
, &statbuf
) < 0) {
433 if (mtime
== statbuf
.st_mtime
) {
434 fprintf(stderr
, "%s: no changes made to crontab\n",
437 fprintf(stderr
, "%s: installing new crontab\n",
441 fclose(NewCrontab
); unlink(Filename
);
442 log_it(RealUser
, Pid
, "END EDIT", User
);
448 char n
[MAX_FNAME
], envstr
[MAX_ENVSTR
], tn
[MAX_FNAME
];
452 time_t now
= time(NULL
);
454 (void) sprintf(n
, "tmp.%d", Pid
);
455 (void) sprintf(tn
, CRON_TAB(n
));
456 if (!(tmp
= fopen(tn
, "w+"))) {
461 /* write a signature at the top of the file.
463 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
465 fprintf(tmp
, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
466 fprintf(tmp
, "# (%s installed on %-24.24s)\n", Filename
, ctime(&now
));
467 fprintf(tmp
, "# (Cron version -- %s)\n", rcsid
);
469 /* copy the crontab to the tmp
472 while (EOF
!= (ch
= get_char(NewCrontab
)))
475 ftruncate(fileno(tmp
), ftell(tmp
));
476 fflush(tmp
); rewind(tmp
);
479 fprintf(stderr
, "%s: error while writing new crontab to %s\n",
481 fclose(tmp
); unlink(tn
);
485 /* check the syntax of the file being installed.
488 /* BUG: was reporting errors after the EOF if there were any errors
489 * in the file proper -- kludged it by stopping after first error.
493 CheckErrorCount
= 0; eof
= FALSE
;
494 while (!CheckErrorCount
&& !eof
) {
495 switch (load_env(envstr
, tmp
)) {
500 e
= load_entry(tmp
, check_error
, 0);
501 if (e
) free((void*)e
);
508 if (CheckErrorCount
!= 0) {
509 fprintf(stderr
, "errors in crontab file, can't install.\n");
510 fclose(tmp
); unlink(tn
);
515 if (fchown(fileno(tmp
), ROOT_UID
, -1) < OK
) {
517 if (chown(tn
, ROOT_UID
, -1) < OK
) {
520 fclose(tmp
); unlink(tn
);
525 if (fchmod(fileno(tmp
), 0600) < OK
) {
527 if (chmod(tn
, 0600) < OK
) {
530 fclose(tmp
); unlink(tn
);
534 if (fclose(tmp
) == EOF
) {
540 (void) sprintf(n
, CRON_TAB(User
));
542 fprintf(stderr
, "%s: error renaming %s to %s\n",
548 log_it(RealUser
, Pid
, "REPLACE", User
);
557 struct timeval tvs
[2];
560 (void) gettimeofday(&tvs
[0], &tz
);
562 if (utimes(SPOOL_DIR
, tvs
) < OK
) {
563 fprintf(stderr
, "crontab: can't update mtime on spooldir\n");
568 if (utime(SPOOL_DIR
, NULL
) < OK
) {
569 fprintf(stderr
, "crontab: can't update mtime on spooldir\n");
573 #endif /*USE_UTIMES*/