crontab.c (e93f27e3aeb7c0778012b7732bc6376e20f80427) crontab.c (fe590ffe40f49fe09d8275fbf29f0d46c5b99dc7)
1/* Copyright 1988,1990,1993,1994 by Paul Vixie
2 * All rights reserved
1/* Copyright 1988,1990,1993,1994 by Paul Vixie
2 * All rights reserved
3 */
4
5/*
6 * Copyright (c) 1997 by Internet Software Consortium
3 *
7 *
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.
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
12 *
11 *
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
16 * From Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp
12 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
13 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
14 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
15 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
16 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
17 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
18 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
19 * SOFTWARE.
17 */
18
19#if !defined(lint) && !defined(LINT)
20static const char rcsid[] =
20 */
21
22#if !defined(lint) && !defined(LINT)
23static const char rcsid[] =
21 "$FreeBSD$";
24 "$Id: crontab.c,v 1.3 1998/08/14 00:32:38 vixie Exp $";
22#endif
23
24/* crontab - install and manage per-user crontab files
25 * vix 02may87 [RCS has the rest of the log]
26 * vix 26jan87 [original]
27 */
28
29#define MAIN_PROGRAM
30
25#endif
26
27/* crontab - install and manage per-user crontab files
28 * vix 02may87 [RCS has the rest of the log]
29 * vix 26jan87 [original]
30 */
31
32#define MAIN_PROGRAM
33
31#include <sys/param.h>
32#include "cron.h"
34#include "cron.h"
33#include <errno.h>
34#include <fcntl.h>
35#include <md5.h>
35#include <md5.h>
36#include <paths.h>
37#include <sys/file.h>
38#include <sys/stat.h>
39#ifdef USE_UTIMES
40# include <sys/time.h>
41#else
42# include <time.h>
43# include <utime.h>
44#endif
45#if defined(POSIX)
46# include <locale.h>
47#endif
48
49#define MD5_SIZE 33
50#define NHEADER_LINES 3
51
36
37#define MD5_SIZE 33
38#define NHEADER_LINES 3
39
52
53enum opt_t { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace };
54
55#if DEBUGGING
56static char *Options[] = { "???", "list", "delete", "edit", "replace" };
57#endif
58
40enum opt_t { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace };
41
42#if DEBUGGING
43static char *Options[] = { "???", "list", "delete", "edit", "replace" };
44#endif
45
59
60static PID_T Pid;
61static char User[MAXLOGNAME], RealUser[MAXLOGNAME];
62static char Filename[MAX_FNAME];
63static FILE *NewCrontab;
64static int CheckErrorCount;
65static enum opt_t Option;
66static int fflag;
67static struct passwd *pw;
68static void list_cmd(void),
69 delete_cmd(void),
70 edit_cmd(void),
71 poke_daemon(void),
46static PID_T Pid;
47static char User[MAXLOGNAME], RealUser[MAXLOGNAME];
48static char Filename[MAX_FNAME];
49static FILE *NewCrontab;
50static int CheckErrorCount;
51static enum opt_t Option;
52static int fflag;
53static struct passwd *pw;
54static void list_cmd(void),
55 delete_cmd(void),
56 edit_cmd(void),
57 poke_daemon(void),
72 check_error(char *),
58 check_error(const char *),
73 parse_args(int c, char *v[]);
74static int replace_cmd(void);
75
59 parse_args(int c, char *v[]);
60static int replace_cmd(void);
61
76
77static void
62static void
78usage(char *msg)
63usage(const char *msg)
79{
80 fprintf(stderr, "crontab: usage error: %s\n", msg);
81 fprintf(stderr, "%s\n%s\n",
82 "usage: crontab [-u user] file",
83 " crontab [-u user] { -l | -r [-f] | -e }");
84 exit(ERROR_EXIT);
85}
86
64{
65 fprintf(stderr, "crontab: usage error: %s\n", msg);
66 fprintf(stderr, "%s\n%s\n",
67 "usage: crontab [-u user] file",
68 " crontab [-u user] { -l | -r [-f] | -e }");
69 exit(ERROR_EXIT);
70}
71
87
88int
89main(int argc, char *argv[])
90{
91 int exitstatus;
92
93 Pid = getpid();
94 ProgramName = argv[0];
95
72int
73main(int argc, char *argv[])
74{
75 int exitstatus;
76
77 Pid = getpid();
78 ProgramName = argv[0];
79
96#if defined(POSIX)
97 setlocale(LC_ALL, "");
80 setlocale(LC_ALL, "");
98#endif
99
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 warnx("you (%s) are not allowed to use this program", User);
108 log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
109 exit(ERROR_EXIT);
110 }
111 exitstatus = OK_EXIT;
112 switch (Option) {
81
82#if defined(BSD)
83 setlinebuf(stderr);
84#endif
85 parse_args(argc, argv); /* sets many globals, opens a file */
86 set_cron_uid();
87 set_cron_cwd();
88 if (!allowed(User)) {
89 warnx("you (%s) are not allowed to use this program", User);
90 log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
91 exit(ERROR_EXIT);
92 }
93 exitstatus = OK_EXIT;
94 switch (Option) {
113 case opt_list: list_cmd();
114 break;
115 case opt_delete: delete_cmd();
116 break;
117 case opt_edit: edit_cmd();
118 break;
119 case opt_replace: if (replace_cmd() < 0)
120 exitstatus = ERROR_EXIT;
121 break;
95 case opt_list:
96 list_cmd();
97 break;
98 case opt_delete:
99 delete_cmd();
100 break;
101 case opt_edit:
102 edit_cmd();
103 break;
104 case opt_replace:
105 if (replace_cmd() < 0)
106 exitstatus = ERROR_EXIT;
107 break;
122 case opt_unknown:
108 case opt_unknown:
123 break;
109 default:
110 abort();
124 }
125 exit(exitstatus);
126 /*NOTREACHED*/
127}
128
111 }
112 exit(exitstatus);
113 /*NOTREACHED*/
114}
115
129
130static void
131parse_args(int argc, char *argv[])
132{
116static void
117parse_args(int argc, char *argv[])
118{
133 int argch;
134 char resolved_path[PATH_MAX];
119 int argch;
120 char resolved_path[PATH_MAX];
135
136 if (!(pw = getpwuid(getuid())))
137 errx(ERROR_EXIT, "your UID isn't in the passwd file, bailing out");
138 bzero(pw->pw_passwd, strlen(pw->pw_passwd));
139 (void) strncpy(User, pw->pw_name, (sizeof User)-1);
140 User[(sizeof User)-1] = '\0';
141 strcpy(RealUser, User);
142 Filename[0] = '\0';

--- 82 unchanged lines hidden (view full) ---

225 err(ERROR_EXIT, "swapping uids back");
226 }
227
228 Debug(DMISC, ("user=%s, file=%s, option=%s\n",
229 User, Filename, Options[(int)Option]))
230}
231
232static void
121
122 if (!(pw = getpwuid(getuid())))
123 errx(ERROR_EXIT, "your UID isn't in the passwd file, bailing out");
124 bzero(pw->pw_passwd, strlen(pw->pw_passwd));
125 (void) strncpy(User, pw->pw_name, (sizeof User)-1);
126 User[(sizeof User)-1] = '\0';
127 strcpy(RealUser, User);
128 Filename[0] = '\0';

--- 82 unchanged lines hidden (view full) ---

211 err(ERROR_EXIT, "swapping uids back");
212 }
213
214 Debug(DMISC, ("user=%s, file=%s, option=%s\n",
215 User, Filename, Options[(int)Option]))
216}
217
218static void
233copy_file(FILE *in, FILE *out) {
234 int x, ch;
219copy_file(FILE *in, FILE *out)
220{
221 int x, ch;
235
236 Set_LineNum(1)
237 /* ignore the top few comments since we probably put them there.
238 */
222
223 Set_LineNum(1)
224 /* ignore the top few comments since we probably put them there.
225 */
239 for (x = 0; x < NHEADER_LINES; x++) {
226 for (x = 0; x < NHEADER_LINES; x++) {
240 ch = get_char(in);
241 if (EOF == ch)
242 break;
243 if ('#' != ch) {
244 putc(ch, out);
245 break;
246 }
247 while (EOF != (ch = get_char(in)))

--- 8 unchanged lines hidden (view full) ---

256 if (EOF != ch)
257 while (EOF != (ch = get_char(in)))
258 putc(ch, out);
259}
260
261static void
262list_cmd(void)
263{
227 ch = get_char(in);
228 if (EOF == ch)
229 break;
230 if ('#' != ch) {
231 putc(ch, out);
232 break;
233 }
234 while (EOF != (ch = get_char(in)))

--- 8 unchanged lines hidden (view full) ---

243 if (EOF != ch)
244 while (EOF != (ch = get_char(in)))
245 putc(ch, out);
246}
247
248static void
249list_cmd(void)
250{
264 char n[MAX_FNAME];
265 FILE *f;
251 char n[MAX_FNAME];
252 FILE *f;
266
267 log_it(RealUser, Pid, "LIST", User);
268 (void) snprintf(n, sizeof(n), CRON_TAB(User));
269 if (!(f = fopen(n, "r"))) {
270 if (errno == ENOENT)
271 errx(ERROR_EXIT, "no crontab for %s", User);
272 else
273 err(ERROR_EXIT, "%s", n);
274 }
275
276 /* file is open. copy to stdout, close.
277 */
278 copy_file(f, stdout);
279 fclose(f);
280}
281
253
254 log_it(RealUser, Pid, "LIST", User);
255 (void) snprintf(n, sizeof(n), CRON_TAB(User));
256 if (!(f = fopen(n, "r"))) {
257 if (errno == ENOENT)
258 errx(ERROR_EXIT, "no crontab for %s", User);
259 else
260 err(ERROR_EXIT, "%s", n);
261 }
262
263 /* file is open. copy to stdout, close.
264 */
265 copy_file(f, stdout);
266 fclose(f);
267}
268
282
283static void
284delete_cmd(void)
285{
269static void
270delete_cmd(void)
271{
286 char n[MAX_FNAME];
272 char n[MAX_FNAME];
287 int ch, first;
288
289 if (!fflag && isatty(STDIN_FILENO)) {
290 (void)fprintf(stderr, "remove crontab for %s? ", User);
291 first = ch = getchar();
292 while (ch != '\n' && ch != EOF)
293 ch = getchar();
294 if (first != 'y' && first != 'Y')
295 return;
296 }
297
298 log_it(RealUser, Pid, "DELETE", User);
273 int ch, first;
274
275 if (!fflag && isatty(STDIN_FILENO)) {
276 (void)fprintf(stderr, "remove crontab for %s? ", User);
277 first = ch = getchar();
278 while (ch != '\n' && ch != EOF)
279 ch = getchar();
280 if (first != 'y' && first != 'Y')
281 return;
282 }
283
284 log_it(RealUser, Pid, "DELETE", User);
299 (void) snprintf(n, sizeof(n), CRON_TAB(User));
300 if (unlink(n)) {
285 if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n))
286 errx(ERROR_EXIT, "path too long");
287 if (unlink(n) != 0) {
301 if (errno == ENOENT)
302 errx(ERROR_EXIT, "no crontab for %s", User);
303 else
304 err(ERROR_EXIT, "%s", n);
305 }
306 poke_daemon();
307}
308
288 if (errno == ENOENT)
289 errx(ERROR_EXIT, "no crontab for %s", User);
290 else
291 err(ERROR_EXIT, "%s", n);
292 }
293 poke_daemon();
294}
295
309
310static void
296static void
311check_error(char *msg)
297check_error(const char *msg)
312{
313 CheckErrorCount++;
314 fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg);
315}
316
298{
299 CheckErrorCount++;
300 fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg);
301}
302
317
318static void
319edit_cmd(void)
320{
303static void
304edit_cmd(void)
305{
321 char n[MAX_FNAME], q[MAX_TEMPSTR], *editor;
322 FILE *f;
323 int t;
324 struct stat statbuf, fsbuf;
325 WAIT_T waiter;
326 PID_T pid, xpid;
327 mode_t um;
328 int syntax_error = 0;
329 char orig_md5[MD5_SIZE];
330 char new_md5[MD5_SIZE];
306 char n[MAX_FNAME], q[MAX_TEMPSTR], *editor;
307 FILE *f;
308 int t;
309 struct stat statbuf, fsbuf;
310 WAIT_T waiter;
311 PID_T pid, xpid;
312 mode_t um;
313 int syntax_error = 0;
314 char orig_md5[MD5_SIZE];
315 char new_md5[MD5_SIZE];
331
332 log_it(RealUser, Pid, "BEGIN EDIT", User);
316
317 log_it(RealUser, Pid, "BEGIN EDIT", User);
333 (void) snprintf(n, sizeof(n), CRON_TAB(User));
318 if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n))
319 errx(ERROR_EXIT, "path too long");
334 if (!(f = fopen(n, "r"))) {
335 if (errno != ENOENT)
336 err(ERROR_EXIT, "%s", n);
337 warnx("no crontab for %s - using an empty one", User);
338 if (!(f = fopen(_PATH_DEVNULL, "r")))
339 err(ERROR_EXIT, _PATH_DEVNULL);
340 }
341

--- 26 unchanged lines hidden (view full) ---

368 warn("unable to fstat temp file");
369 goto fatal;
370 }
371 again:
372 if (swap_uids() < OK)
373 err(ERROR_EXIT, "swapping uids");
374 if (stat(Filename, &statbuf) < 0) {
375 warn("stat");
320 if (!(f = fopen(n, "r"))) {
321 if (errno != ENOENT)
322 err(ERROR_EXIT, "%s", n);
323 warnx("no crontab for %s - using an empty one", User);
324 if (!(f = fopen(_PATH_DEVNULL, "r")))
325 err(ERROR_EXIT, _PATH_DEVNULL);
326 }
327

--- 26 unchanged lines hidden (view full) ---

354 warn("unable to fstat temp file");
355 goto fatal;
356 }
357 again:
358 if (swap_uids() < OK)
359 err(ERROR_EXIT, "swapping uids");
360 if (stat(Filename, &statbuf) < 0) {
361 warn("stat");
376 fatal: unlink(Filename);
362 fatal:
363 unlink(Filename);
377 exit(ERROR_EXIT);
378 }
379 if (swap_uids_back() < OK)
380 err(ERROR_EXIT, "swapping uids back");
381 if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino)
382 errx(ERROR_EXIT, "temp file must be edited in place");
383 if (MD5File(Filename, orig_md5) == NULL) {
384 warn("MD5");
385 goto fatal;
386 }
387
364 exit(ERROR_EXIT);
365 }
366 if (swap_uids_back() < OK)
367 err(ERROR_EXIT, "swapping uids back");
368 if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino)
369 errx(ERROR_EXIT, "temp file must be edited in place");
370 if (MD5File(Filename, orig_md5) == NULL) {
371 warn("MD5");
372 goto fatal;
373 }
374
388 if ((!(editor = getenv("VISUAL")))
389 && (!(editor = getenv("EDITOR")))
390 ) {
375 if ((editor = getenv("VISUAL")) == NULL &&
376 (editor = getenv("EDITOR")) == NULL) {
391 editor = EDITOR;
392 }
393
394 /* we still have the file open. editors will generally rewrite the
395 * original file rather than renaming/unlinking it and starting a
396 * new one; even backup files are supposed to be made by copying
397 * rather than by renaming. if some editor does not support this,
398 * then don't use it. the security problems are more severe if we

--- 100 unchanged lines hidden (view full) ---

499
500/* returns 0 on success
501 * -1 on syntax error
502 * -2 on install error
503 */
504static int
505replace_cmd(void)
506{
377 editor = EDITOR;
378 }
379
380 /* we still have the file open. editors will generally rewrite the
381 * original file rather than renaming/unlinking it and starting a
382 * new one; even backup files are supposed to be made by copying
383 * rather than by renaming. if some editor does not support this,
384 * then don't use it. the security problems are more severe if we

--- 100 unchanged lines hidden (view full) ---

485
486/* returns 0 on success
487 * -1 on syntax error
488 * -2 on install error
489 */
490static int
491replace_cmd(void)
492{
507 char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
508 FILE *tmp;
509 int ch, eof;
510 entry *e;
511 time_t now = time(NULL);
512 char **envp = env_init();
493 char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
494 FILE *tmp;
495 int ch, eof;
496 entry *e;
497 time_t now = time(NULL);
498 char **envp = env_init();
513
514 if (envp == NULL) {
515 warnx("cannot allocate memory");
516 return (-2);
517 }
518
519 (void) snprintf(n, sizeof(n), "tmp.%d", Pid);
499
500 if (envp == NULL) {
501 warnx("cannot allocate memory");
502 return (-2);
503 }
504
505 (void) snprintf(n, sizeof(n), "tmp.%d", Pid);
520 (void) snprintf(tn, sizeof(tn), CRON_TAB(n));
506 if (snprintf(tn, sizeof(tn), CRON_TAB(n)) >= (int)sizeof(tn)) {
507 warnx("path too long");
508 return (-2);
509 }
521
522 if (!(tmp = fopen(tn, "w+"))) {
523 warn("%s", tn);
524 return (-2);
525 }
526
527 /* write a signature at the top of the file.
528 *

--- 71 unchanged lines hidden (view full) ---

600 }
601
602 if (fclose(tmp) == EOF) {
603 warn("fclose");
604 unlink(tn);
605 return (-2);
606 }
607
510
511 if (!(tmp = fopen(tn, "w+"))) {
512 warn("%s", tn);
513 return (-2);
514 }
515
516 /* write a signature at the top of the file.
517 *

--- 71 unchanged lines hidden (view full) ---

589 }
590
591 if (fclose(tmp) == EOF) {
592 warn("fclose");
593 unlink(tn);
594 return (-2);
595 }
596
608 (void) snprintf(n, sizeof(n), CRON_TAB(User));
597 if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n)) {
598 warnx("path too long");
599 unlink(tn);
600 return (-2);
601 }
602
609 if (rename(tn, n)) {
610 warn("error renaming %s to %s", tn, n);
611 unlink(tn);
612 return (-2);
613 }
614
615 log_it(RealUser, Pid, "REPLACE", User);
616

--- 6 unchanged lines hidden (view full) ---

623 */
624 sleep(1);
625
626 poke_daemon();
627
628 return (0);
629}
630
603 if (rename(tn, n)) {
604 warn("error renaming %s to %s", tn, n);
605 unlink(tn);
606 return (-2);
607 }
608
609 log_it(RealUser, Pid, "REPLACE", User);
610

--- 6 unchanged lines hidden (view full) ---

617 */
618 sleep(1);
619
620 poke_daemon();
621
622 return (0);
623}
624
631
632static void
633poke_daemon(void)
634{
625static void
626poke_daemon(void)
627{
635#ifdef USE_UTIMES
636 struct timeval tvs[2];
637
638 (void)gettimeofday(&tvs[0], NULL);
639 tvs[1] = tvs[0];
640 if (utimes(SPOOL_DIR, tvs) < OK) {
641 warn("can't update mtime on spooldir %s", SPOOL_DIR);
642 return;
643 }
644#else
645 if (utime(SPOOL_DIR, NULL) < OK) {
646 warn("can't update mtime on spooldir %s", SPOOL_DIR);
647 return;
648 }
628 if (utime(SPOOL_DIR, NULL) < OK) {
629 warn("can't update mtime on spooldir %s", SPOOL_DIR);
630 return;
631 }
649#endif /*USE_UTIMES*/
650}
632}