Do not use .Xo/.Xc to work around ancient roff limits.
[netbsd-mini2440.git] / libexec / cron / crontab.c
blobbc9cc3f252459a1e3a3ea87f99c758a8ecc6f99e
1 /* Copyright 1988,1990,1993 by Paul Vixie
2 * All rights reserved
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
11 * user.
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 $";
20 #endif
22 /* crontab - install and manage per-user crontab files
23 * vix 02may87 [RCS has the rest of the log]
24 * vix 26jan87 [original]
28 #define MAIN_PROGRAM
31 #include "cron.h"
32 #include "externs.h"
33 #include <pwd.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <sys/file.h>
37 #include <sys/stat.h>
38 #ifdef USE_UTIMES
39 # include <sys/time.h>
40 #else
41 # include <time.h>
42 # include <utime.h>
43 #endif
44 #if defined(POSIX)
45 # include <locale.h>
46 #endif
49 static int Pid;
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 }
55 Option;
56 static void list_cmd __P((void)),
57 delete_cmd __P((void)),
58 edit_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
68 #if DEBUGGING
69 static char *Options[] = { "???", "list", "delete", "edit", "replace" };
70 #endif
73 static void
74 usage(msg)
75 char *msg;
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");
84 exit(ERROR_EXIT);
88 int
89 main(argc, argv)
90 int argc;
91 char *argv[];
93 Pid = getpid();
94 ProgramName = argv[0];
96 #if defined(POSIX)
97 setlocale(LC_ALL, "");
98 #endif
100 #if defined(BSD)
101 setlinebuf(stderr);
102 #endif
103 parse_args(argc, argv); /* sets many globals, opens a file */
104 set_cron_uid();
105 set_cron_cwd();
106 if (!allowed(User)) {
107 fprintf(stderr,
108 "You (%s) are not allowed to use this program (%s)\n",
109 User, ProgramName);
110 fprintf(stderr, "See crontab(1) for more information\n");
111 log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
112 exit(ERROR_EXIT);
114 switch (Option)
116 case opt_list: list_cmd();
117 break;
118 case opt_delete: delete_cmd();
119 break;
120 case opt_edit: edit_cmd();
121 break;
122 case opt_replace: replace_cmd();
123 break;
125 exit(0);
126 /*NOTREACHED*/
130 static void
131 parse_args(argc, argv)
132 int argc;
133 char *argv[];
135 struct passwd *pw;
136 int argch;
138 if (!(pw = getpwuid(getuid()))) {
139 fprintf(stderr, "%s: your UID isn't in the passwd file.\n",
140 ProgramName);
141 fprintf(stderr, "bailing out.\n");
142 exit(ERROR_EXIT);
144 strcpy(User, pw->pw_name);
145 strcpy(RealUser, User);
146 Filename[0] = '\0';
147 Option = opt_unknown;
148 while (EOF != (argch = getopt(argc, argv, "u:lerx:"))) {
149 switch (argch) {
150 case 'x':
151 if (!set_debug_flags(optarg))
152 usage("bad debug option");
153 break;
154 case 'u':
155 if (getuid() != ROOT_UID)
157 fprintf(stderr,
158 "must be privileged to use -u\n");
159 exit(ERROR_EXIT);
161 if ((struct passwd *)NULL == getpwnam(optarg))
163 fprintf(stderr, "%s: user `%s' unknown\n",
164 ProgramName, optarg);
165 exit(ERROR_EXIT);
167 (void) strcpy(User, optarg);
168 break;
169 case 'l':
170 if (Option != opt_unknown)
171 usage("only one operation permitted");
172 Option = opt_list;
173 break;
174 case 'r':
175 if (Option != opt_unknown)
176 usage("only one operation permitted");
177 Option = opt_delete;
178 break;
179 case 'e':
180 if (Option != opt_unknown)
181 usage("only one operation permitted");
182 Option = opt_edit;
183 break;
184 default:
185 usage("unrecognized option");
189 endpwent();
191 if (Option != opt_unknown) {
192 if (argv[optind] != NULL) {
193 usage("no arguments permitted after this option");
195 } else {
196 if (argv[optind] != NULL) {
197 Option = opt_replace;
198 (void) strcpy (Filename, argv[optind]);
199 } else {
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
207 * reading the file.
209 if (!strcmp(Filename, "-")) {
210 NewCrontab = stdin;
211 } else {
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
217 * the race.
220 if (swap_uids() < OK) {
221 perror("swapping uids");
222 exit(ERROR_EXIT);
224 if (!(NewCrontab = fopen(Filename, "r"))) {
225 perror(Filename);
226 exit(ERROR_EXIT);
228 if (swap_uids() < OK) {
229 perror("swapping uids back");
230 exit(ERROR_EXIT);
235 Debug(DMISC, ("user=%s, file=%s, option=%s\n",
236 User, Filename, Options[(int)Option]))
240 static void
241 list_cmd() {
242 char n[MAX_FNAME];
243 FILE *f;
244 int ch;
246 log_it(RealUser, Pid, "LIST", User);
247 (void) sprintf(n, CRON_TAB(User));
248 if (!(f = fopen(n, "r"))) {
249 if (errno == ENOENT)
250 fprintf(stderr, "no crontab for %s\n", User);
251 else
252 perror(n);
253 exit(ERROR_EXIT);
256 /* file is open. copy to stdout, close.
258 Set_LineNum(1)
259 while (EOF != (ch = get_char(f)))
260 putchar(ch);
261 fclose(f);
265 static void
266 delete_cmd() {
267 char n[MAX_FNAME];
269 log_it(RealUser, Pid, "DELETE", User);
270 (void) sprintf(n, CRON_TAB(User));
271 if (unlink(n)) {
272 if (errno == ENOENT)
273 fprintf(stderr, "no crontab for %s\n", User);
274 else
275 perror(n);
276 exit(ERROR_EXIT);
278 poke_daemon();
282 static void
283 check_error(msg)
284 char *msg;
286 CheckErrorCount += 1;
287 fprintf(stderr, "\"%s\", line %d: %s\n", Filename, LineNumber, msg);
291 static void
292 edit_cmd() {
293 char n[MAX_FNAME], *editor;
294 FILE *f;
295 int ch, t, x, pid;
296 struct stat statbuf;
297 time_t mtime;
298 WAIT_T waiter;
300 log_it(RealUser, Pid, "BEGIN EDIT", User);
301 NewCrontab = NULL;
302 (void) sprintf(n, CRON_TAB(User));
303 if (!(f = fopen(n, "r"))) {
304 if (errno != ENOENT) {
305 perror(n);
306 exit(ERROR_EXIT);
308 fprintf(stderr, "no crontab for %s - using an empty one\n",
309 User);
310 if (!(f = fopen("/dev/null", "r"))) {
311 perror("/dev/null");
312 exit(ERROR_EXIT);
316 (void) sprintf(Filename, "/tmp/crontab.%d", Pid);
317 if (-1 == (t = open(Filename, O_CREAT|O_EXCL|O_RDWR, 0700))) {
318 perror(Filename);
319 goto fatal;
321 #ifdef HAS_FCHOWN
322 if (fchown(t, getuid(), getgid()) < 0) {
323 #else
324 if (chown(Filename, getuid(), getgid()) < 0) {
325 #endif
326 perror("fchown");
327 goto fatal;
329 if (!(NewCrontab = fdopen(t, "r+"))) {
330 perror("fdopen");
331 goto fatal;
334 Set_LineNum(1)
336 /* ignore the top few comments since we probably put them there.
338 for (x = 0; x < NHEADER_LINES; x++) {
339 ch = get_char(f);
340 if (EOF == ch)
341 break;
342 if ('#' != ch) {
343 putc(ch, NewCrontab);
344 break;
346 while (EOF != (ch = get_char(f)))
347 if (ch == '\n')
348 break;
349 if (EOF == ch)
350 break;
353 /* copy the rest of the crontab (if any) to the temp file.
355 if (EOF != ch)
356 while (EOF != (ch = get_char(f)))
357 putc(ch, NewCrontab);
358 fclose(f);
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);
364 unlink(Filename);
365 exit(ERROR_EXIT);
367 if (fstat(t, &statbuf) < 0) {
368 perror("fstat");
369 goto fatal;
371 mtime = statbuf.st_mtime;
373 if ((!(editor = getenv("VISUAL")))
374 && (!(editor = getenv("EDITOR")))
376 editor = 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()) {
388 case -1:
389 perror("fork");
390 goto fatal;
391 case 0:
392 /* child */
393 if (setuid(getuid()) < 0) {
394 perror("setuid(getuid())");
395 exit(ERROR_EXIT);
397 if (chdir("/tmp") < 0) {
398 perror("chdir(/tmp)");
399 exit(ERROR_EXIT);
401 execlp(editor, editor, Filename, NULL);
402 perror(editor);
403 exit(ERROR_EXIT);
404 /*NOTREACHED*/
405 default:
406 /* parent */
407 break;
410 /* parent */
411 x = wait(&waiter);
412 if (x != pid) {
413 fprintf(stderr, "%s: wrong PID (%d != %d) from \"%s\"\n",
414 ProgramName, x, pid, editor);
415 goto fatal;
417 if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
418 fprintf(stderr, "%s: \"%s\" exited with status %d\n",
419 ProgramName, editor, WEXITSTATUS(waiter));
420 goto fatal;
422 if (WIFSIGNALED(waiter)) {
423 fprintf(stderr,
424 "%s: \"%s\" killed; signal %d (%score dumped)\n",
425 ProgramName, editor, WTERMSIG(waiter),
426 WCOREDUMP(waiter) ?"" :"no ");
427 goto fatal;
429 if (fstat(t, &statbuf) < 0) {
430 perror("fstat");
431 goto fatal;
433 if (mtime == statbuf.st_mtime) {
434 fprintf(stderr, "%s: no changes made to crontab\n",
435 ProgramName);
436 } else {
437 fprintf(stderr, "%s: installing new crontab\n",
438 ProgramName);
439 replace_cmd();
441 fclose(NewCrontab); unlink(Filename);
442 log_it(RealUser, Pid, "END EDIT", User);
446 static void
447 replace_cmd() {
448 char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
449 FILE *tmp;
450 int ch, eof;
451 entry *e;
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+"))) {
457 perror(tn);
458 exit(ERROR_EXIT);
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
471 Set_LineNum(1)
472 while (EOF != (ch = get_char(NewCrontab)))
473 putc(ch, tmp);
474 fclose(NewCrontab);
475 ftruncate(fileno(tmp), ftell(tmp));
476 fflush(tmp); rewind(tmp);
478 if (ferror(tmp)) {
479 fprintf(stderr, "%s: error while writing new crontab to %s\n",
480 ProgramName, tn);
481 fclose(tmp); unlink(tn);
482 exit(ERROR_EXIT);
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.
490 * vix 31mar87
492 Set_LineNum(1)
493 CheckErrorCount = 0; eof = FALSE;
494 while (!CheckErrorCount && !eof) {
495 switch (load_env(envstr, tmp)) {
496 case ERR:
497 eof = TRUE;
498 break;
499 case FALSE:
500 e = load_entry(tmp, check_error, 0);
501 if (e) free((void*)e);
502 break;
503 case TRUE:
504 break;
508 if (CheckErrorCount != 0) {
509 fprintf(stderr, "errors in crontab file, can't install.\n");
510 fclose(tmp); unlink(tn);
511 exit(ERROR_EXIT);
514 #ifdef HAS_FCHOWN
515 if (fchown(fileno(tmp), ROOT_UID, -1) < OK) {
516 #else
517 if (chown(tn, ROOT_UID, -1) < OK) {
518 #endif
519 perror("chown");
520 fclose(tmp); unlink(tn);
521 exit(ERROR_EXIT);
524 #ifdef HAS_FCHMOD
525 if (fchmod(fileno(tmp), 0600) < OK) {
526 #else
527 if (chmod(tn, 0600) < OK) {
528 #endif
529 perror("chown");
530 fclose(tmp); unlink(tn);
531 exit(ERROR_EXIT);
534 if (fclose(tmp) == EOF) {
535 perror("fclose");
536 unlink(tn);
537 exit(ERROR_EXIT);
540 (void) sprintf(n, CRON_TAB(User));
541 if (rename(tn, n)) {
542 fprintf(stderr, "%s: error renaming %s to %s\n",
543 ProgramName, tn, n);
544 perror("rename");
545 unlink(tn);
546 exit(ERROR_EXIT);
548 log_it(RealUser, Pid, "REPLACE", User);
550 poke_daemon();
554 static void
555 poke_daemon() {
556 #ifdef USE_UTIMES
557 struct timeval tvs[2];
558 struct timezone tz;
560 (void) gettimeofday(&tvs[0], &tz);
561 tvs[1] = tvs[0];
562 if (utimes(SPOOL_DIR, tvs) < OK) {
563 fprintf(stderr, "crontab: can't update mtime on spooldir\n");
564 perror(SPOOL_DIR);
565 return;
567 #else
568 if (utime(SPOOL_DIR, NULL) < OK) {
569 fprintf(stderr, "crontab: can't update mtime on spooldir\n");
570 perror(SPOOL_DIR);
571 return;
573 #endif /*USE_UTIMES*/