184f33deaSJordan K. Hubbard /* Copyright 1988,1990,1993,1994 by Paul Vixie
284f33deaSJordan K. Hubbard * All rights reserved
3*fe590ffeSEric van Gyzen */
4*fe590ffeSEric van Gyzen
5*fe590ffeSEric van Gyzen /*
6*fe590ffeSEric van Gyzen * Copyright (c) 1997 by Internet Software Consortium
784f33deaSJordan K. Hubbard *
8*fe590ffeSEric van Gyzen * Permission to use, copy, modify, and distribute this software for any
9*fe590ffeSEric van Gyzen * purpose with or without fee is hereby granted, provided that the above
10*fe590ffeSEric van Gyzen * copyright notice and this permission notice appear in all copies.
1184f33deaSJordan K. Hubbard *
12*fe590ffeSEric van Gyzen * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
13*fe590ffeSEric van Gyzen * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
14*fe590ffeSEric van Gyzen * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
15*fe590ffeSEric van Gyzen * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
16*fe590ffeSEric van Gyzen * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
17*fe590ffeSEric van Gyzen * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
18*fe590ffeSEric van Gyzen * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
19*fe590ffeSEric van Gyzen * SOFTWARE.
2084f33deaSJordan K. Hubbard */
2184f33deaSJordan K. Hubbard
2284f33deaSJordan K. Hubbard #if !defined(lint) && !defined(LINT)
23401e6468SPhilippe Charnier static const char rcsid[] =
24*fe590ffeSEric van Gyzen "$Id: crontab.c,v 1.3 1998/08/14 00:32:38 vixie Exp $";
2584f33deaSJordan K. Hubbard #endif
2684f33deaSJordan K. Hubbard
2784f33deaSJordan K. Hubbard /* crontab - install and manage per-user crontab files
2884f33deaSJordan K. Hubbard * vix 02may87 [RCS has the rest of the log]
2984f33deaSJordan K. Hubbard * vix 26jan87 [original]
3084f33deaSJordan K. Hubbard */
3184f33deaSJordan K. Hubbard
3284f33deaSJordan K. Hubbard #define MAIN_PROGRAM
3384f33deaSJordan K. Hubbard
3484f33deaSJordan K. Hubbard #include "cron.h"
350cd2e3abSDiomidis Spinellis #include <md5.h>
3684f33deaSJordan K. Hubbard
370cd2e3abSDiomidis Spinellis #define MD5_SIZE 33
3884f33deaSJordan K. Hubbard #define NHEADER_LINES 3
3984f33deaSJordan K. Hubbard
4084f33deaSJordan K. Hubbard enum opt_t { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace };
4184f33deaSJordan K. Hubbard
4284f33deaSJordan K. Hubbard #if DEBUGGING
4384f33deaSJordan K. Hubbard static char *Options[] = { "???", "list", "delete", "edit", "replace" };
4484f33deaSJordan K. Hubbard #endif
4584f33deaSJordan K. Hubbard
4684f33deaSJordan K. Hubbard static PID_T Pid;
475e5d4b23SEd Maste static char User[MAXLOGNAME], RealUser[MAXLOGNAME];
4884f33deaSJordan K. Hubbard static char Filename[MAX_FNAME];
4984f33deaSJordan K. Hubbard static FILE *NewCrontab;
5084f33deaSJordan K. Hubbard static int CheckErrorCount;
5184f33deaSJordan K. Hubbard static enum opt_t Option;
52d21656dcSConrad Meyer static int fflag;
5384f33deaSJordan K. Hubbard static struct passwd *pw;
54784bddbcSKevin Lo static void list_cmd(void),
55784bddbcSKevin Lo delete_cmd(void),
56784bddbcSKevin Lo edit_cmd(void),
57784bddbcSKevin Lo poke_daemon(void),
58*fe590ffeSEric van Gyzen check_error(const char *),
59784bddbcSKevin Lo parse_args(int c, char *v[]);
60784bddbcSKevin Lo static int replace_cmd(void);
6184f33deaSJordan K. Hubbard
6284f33deaSJordan K. Hubbard static void
usage(const char * msg)63*fe590ffeSEric van Gyzen usage(const char *msg)
6484f33deaSJordan K. Hubbard {
65401e6468SPhilippe Charnier fprintf(stderr, "crontab: usage error: %s\n", msg);
66401e6468SPhilippe Charnier fprintf(stderr, "%s\n%s\n",
67401e6468SPhilippe Charnier "usage: crontab [-u user] file",
68d21656dcSConrad Meyer " crontab [-u user] { -l | -r [-f] | -e }");
6984f33deaSJordan K. Hubbard exit(ERROR_EXIT);
7084f33deaSJordan K. Hubbard }
7184f33deaSJordan K. Hubbard
7284f33deaSJordan K. Hubbard int
main(int argc,char * argv[])7366d48cdaSMatteo Riondato main(int argc, char *argv[])
7484f33deaSJordan K. Hubbard {
7584f33deaSJordan K. Hubbard int exitstatus;
7684f33deaSJordan K. Hubbard
7784f33deaSJordan K. Hubbard Pid = getpid();
7884f33deaSJordan K. Hubbard ProgramName = argv[0];
7984f33deaSJordan K. Hubbard
8084f33deaSJordan K. Hubbard setlocale(LC_ALL, "");
8184f33deaSJordan K. Hubbard
8284f33deaSJordan K. Hubbard #if defined(BSD)
8384f33deaSJordan K. Hubbard setlinebuf(stderr);
8484f33deaSJordan K. Hubbard #endif
8584f33deaSJordan K. Hubbard parse_args(argc, argv); /* sets many globals, opens a file */
8684f33deaSJordan K. Hubbard set_cron_uid();
8784f33deaSJordan K. Hubbard set_cron_cwd();
8884f33deaSJordan K. Hubbard if (!allowed(User)) {
89401e6468SPhilippe Charnier warnx("you (%s) are not allowed to use this program", User);
9084f33deaSJordan K. Hubbard log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
9184f33deaSJordan K. Hubbard exit(ERROR_EXIT);
9284f33deaSJordan K. Hubbard }
9384f33deaSJordan K. Hubbard exitstatus = OK_EXIT;
9484f33deaSJordan K. Hubbard switch (Option) {
95*fe590ffeSEric van Gyzen case opt_list:
96*fe590ffeSEric van Gyzen list_cmd();
9784f33deaSJordan K. Hubbard break;
98*fe590ffeSEric van Gyzen case opt_delete:
99*fe590ffeSEric van Gyzen delete_cmd();
10084f33deaSJordan K. Hubbard break;
101*fe590ffeSEric van Gyzen case opt_edit:
102*fe590ffeSEric van Gyzen edit_cmd();
10384f33deaSJordan K. Hubbard break;
104*fe590ffeSEric van Gyzen case opt_replace:
105*fe590ffeSEric van Gyzen if (replace_cmd() < 0)
10684f33deaSJordan K. Hubbard exitstatus = ERROR_EXIT;
10784f33deaSJordan K. Hubbard break;
108401e6468SPhilippe Charnier case opt_unknown:
109*fe590ffeSEric van Gyzen default:
110*fe590ffeSEric van Gyzen abort();
11184f33deaSJordan K. Hubbard }
112e9d295bfSDima Dorfman exit(exitstatus);
11384f33deaSJordan K. Hubbard /*NOTREACHED*/
11484f33deaSJordan K. Hubbard }
11584f33deaSJordan K. Hubbard
11684f33deaSJordan K. Hubbard static void
parse_args(int argc,char * argv[])117e93f27e3SJohn Baldwin parse_args(int argc, char *argv[])
11884f33deaSJordan K. Hubbard {
11984f33deaSJordan K. Hubbard int argch;
1209896de5aSBrooks Davis char resolved_path[PATH_MAX];
12184f33deaSJordan K. Hubbard
122401e6468SPhilippe Charnier if (!(pw = getpwuid(getuid())))
123401e6468SPhilippe Charnier errx(ERROR_EXIT, "your UID isn't in the passwd file, bailing out");
124c11807acSMatteo Riondato bzero(pw->pw_passwd, strlen(pw->pw_passwd));
125bdddbd2fSPaul Traina (void) strncpy(User, pw->pw_name, (sizeof User)-1);
126bdddbd2fSPaul Traina User[(sizeof User)-1] = '\0';
12784f33deaSJordan K. Hubbard strcpy(RealUser, User);
12884f33deaSJordan K. Hubbard Filename[0] = '\0';
12984f33deaSJordan K. Hubbard Option = opt_unknown;
130d21656dcSConrad Meyer while ((argch = getopt(argc, argv, "u:lerx:f")) != -1) {
13184f33deaSJordan K. Hubbard switch (argch) {
13284f33deaSJordan K. Hubbard case 'x':
13384f33deaSJordan K. Hubbard if (!set_debug_flags(optarg))
13484f33deaSJordan K. Hubbard usage("bad debug option");
13584f33deaSJordan K. Hubbard break;
13684f33deaSJordan K. Hubbard case 'u':
13784f33deaSJordan K. Hubbard if (getuid() != ROOT_UID)
138401e6468SPhilippe Charnier errx(ERROR_EXIT, "must be privileged to use -u");
13984f33deaSJordan K. Hubbard if (!(pw = getpwnam(optarg)))
140401e6468SPhilippe Charnier errx(ERROR_EXIT, "user `%s' unknown", optarg);
141c11807acSMatteo Riondato bzero(pw->pw_passwd, strlen(pw->pw_passwd));
142bdddbd2fSPaul Traina (void) strncpy(User, pw->pw_name, (sizeof User)-1);
143bdddbd2fSPaul Traina User[(sizeof User)-1] = '\0';
14484f33deaSJordan K. Hubbard break;
14584f33deaSJordan K. Hubbard case 'l':
14684f33deaSJordan K. Hubbard if (Option != opt_unknown)
14784f33deaSJordan K. Hubbard usage("only one operation permitted");
14884f33deaSJordan K. Hubbard Option = opt_list;
14984f33deaSJordan K. Hubbard break;
15084f33deaSJordan K. Hubbard case 'r':
15184f33deaSJordan K. Hubbard if (Option != opt_unknown)
15284f33deaSJordan K. Hubbard usage("only one operation permitted");
15384f33deaSJordan K. Hubbard Option = opt_delete;
15484f33deaSJordan K. Hubbard break;
15584f33deaSJordan K. Hubbard case 'e':
15684f33deaSJordan K. Hubbard if (Option != opt_unknown)
15784f33deaSJordan K. Hubbard usage("only one operation permitted");
15884f33deaSJordan K. Hubbard Option = opt_edit;
15984f33deaSJordan K. Hubbard break;
160d21656dcSConrad Meyer case 'f':
161d21656dcSConrad Meyer fflag = 1;
162d21656dcSConrad Meyer break;
16384f33deaSJordan K. Hubbard default:
16484f33deaSJordan K. Hubbard usage("unrecognized option");
16584f33deaSJordan K. Hubbard }
16684f33deaSJordan K. Hubbard }
16784f33deaSJordan K. Hubbard
16884f33deaSJordan K. Hubbard endpwent();
16984f33deaSJordan K. Hubbard
17084f33deaSJordan K. Hubbard if (Option != opt_unknown) {
17184f33deaSJordan K. Hubbard if (argv[optind] != NULL) {
17284f33deaSJordan K. Hubbard usage("no arguments permitted after this option");
17384f33deaSJordan K. Hubbard }
17484f33deaSJordan K. Hubbard } else {
17584f33deaSJordan K. Hubbard if (argv[optind] != NULL) {
17684f33deaSJordan K. Hubbard Option = opt_replace;
177bdddbd2fSPaul Traina (void) strncpy (Filename, argv[optind], (sizeof Filename)-1);
178bdddbd2fSPaul Traina Filename[(sizeof Filename)-1] = '\0';
179bdddbd2fSPaul Traina
18084f33deaSJordan K. Hubbard } else {
18184f33deaSJordan K. Hubbard usage("file name must be specified for replace");
18284f33deaSJordan K. Hubbard }
18384f33deaSJordan K. Hubbard }
18484f33deaSJordan K. Hubbard
18584f33deaSJordan K. Hubbard if (Option == opt_replace) {
18684f33deaSJordan K. Hubbard /* relinquish the setuid status of the binary during
18784f33deaSJordan K. Hubbard * the open, lest nonroot users read files they should
18884f33deaSJordan K. Hubbard * not be able to read. we can't use access() here
18984f33deaSJordan K. Hubbard * since there's a race condition. thanks go out to
19084f33deaSJordan K. Hubbard * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
19184f33deaSJordan K. Hubbard * the race.
19284f33deaSJordan K. Hubbard */
19384f33deaSJordan K. Hubbard
194401e6468SPhilippe Charnier if (swap_uids() < OK)
195401e6468SPhilippe Charnier err(ERROR_EXIT, "swapping uids");
196cc427081SXin LI
197cc427081SXin LI /* we have to open the file here because we're going to
198cc427081SXin LI * chdir(2) into /var/cron before we get around to
199cc427081SXin LI * reading the file.
200cc427081SXin LI */
201cc427081SXin LI if (!strcmp(Filename, "-")) {
202cc427081SXin LI NewCrontab = stdin;
203cc427081SXin LI } else if (realpath(Filename, resolved_path) != NULL &&
204cc427081SXin LI !strcmp(resolved_path, SYSCRONTAB)) {
205cc427081SXin LI err(ERROR_EXIT, SYSCRONTAB " must be edited manually");
206cc427081SXin LI } else {
207401e6468SPhilippe Charnier if (!(NewCrontab = fopen(Filename, "r")))
208401e6468SPhilippe Charnier err(ERROR_EXIT, "%s", Filename);
209cc427081SXin LI }
21066d48cdaSMatteo Riondato if (swap_uids_back() < OK)
211401e6468SPhilippe Charnier err(ERROR_EXIT, "swapping uids back");
21284f33deaSJordan K. Hubbard }
21384f33deaSJordan K. Hubbard
21484f33deaSJordan K. Hubbard Debug(DMISC, ("user=%s, file=%s, option=%s\n",
21584f33deaSJordan K. Hubbard User, Filename, Options[(int)Option]))
21684f33deaSJordan K. Hubbard }
21784f33deaSJordan K. Hubbard
2189a2ef7d1SDiomidis Spinellis static void
copy_file(FILE * in,FILE * out)219*fe590ffeSEric van Gyzen copy_file(FILE *in, FILE *out)
220*fe590ffeSEric van Gyzen {
2219a2ef7d1SDiomidis Spinellis int x, ch;
2229a2ef7d1SDiomidis Spinellis
2239a2ef7d1SDiomidis Spinellis Set_LineNum(1)
2249a2ef7d1SDiomidis Spinellis /* ignore the top few comments since we probably put them there.
2259a2ef7d1SDiomidis Spinellis */
2269a2ef7d1SDiomidis Spinellis for (x = 0; x < NHEADER_LINES; x++) {
2279a2ef7d1SDiomidis Spinellis ch = get_char(in);
2289a2ef7d1SDiomidis Spinellis if (EOF == ch)
2299a2ef7d1SDiomidis Spinellis break;
2309a2ef7d1SDiomidis Spinellis if ('#' != ch) {
2319a2ef7d1SDiomidis Spinellis putc(ch, out);
2329a2ef7d1SDiomidis Spinellis break;
2339a2ef7d1SDiomidis Spinellis }
2349a2ef7d1SDiomidis Spinellis while (EOF != (ch = get_char(in)))
2359a2ef7d1SDiomidis Spinellis if (ch == '\n')
2369a2ef7d1SDiomidis Spinellis break;
2379a2ef7d1SDiomidis Spinellis if (EOF == ch)
2389a2ef7d1SDiomidis Spinellis break;
2399a2ef7d1SDiomidis Spinellis }
2409a2ef7d1SDiomidis Spinellis
2419a2ef7d1SDiomidis Spinellis /* copy the rest of the crontab (if any) to the output file.
2429a2ef7d1SDiomidis Spinellis */
2439a2ef7d1SDiomidis Spinellis if (EOF != ch)
2449a2ef7d1SDiomidis Spinellis while (EOF != (ch = get_char(in)))
2459a2ef7d1SDiomidis Spinellis putc(ch, out);
2469a2ef7d1SDiomidis Spinellis }
24784f33deaSJordan K. Hubbard
24884f33deaSJordan K. Hubbard static void
list_cmd(void)249e93f27e3SJohn Baldwin list_cmd(void)
250e93f27e3SJohn Baldwin {
25184f33deaSJordan K. Hubbard char n[MAX_FNAME];
25284f33deaSJordan K. Hubbard FILE *f;
25384f33deaSJordan K. Hubbard
25484f33deaSJordan K. Hubbard log_it(RealUser, Pid, "LIST", User);
2552b9f079cSMatteo Riondato (void) snprintf(n, sizeof(n), CRON_TAB(User));
25684f33deaSJordan K. Hubbard if (!(f = fopen(n, "r"))) {
25784f33deaSJordan K. Hubbard if (errno == ENOENT)
258401e6468SPhilippe Charnier errx(ERROR_EXIT, "no crontab for %s", User);
25984f33deaSJordan K. Hubbard else
260401e6468SPhilippe Charnier err(ERROR_EXIT, "%s", n);
26184f33deaSJordan K. Hubbard }
26284f33deaSJordan K. Hubbard
26384f33deaSJordan K. Hubbard /* file is open. copy to stdout, close.
26484f33deaSJordan K. Hubbard */
2659a2ef7d1SDiomidis Spinellis copy_file(f, stdout);
26684f33deaSJordan K. Hubbard fclose(f);
26784f33deaSJordan K. Hubbard }
26884f33deaSJordan K. Hubbard
26984f33deaSJordan K. Hubbard static void
delete_cmd(void)270e93f27e3SJohn Baldwin delete_cmd(void)
271e93f27e3SJohn Baldwin {
27284f33deaSJordan K. Hubbard char n[MAX_FNAME];
27330cfb241SPaul Richards int ch, first;
27430cfb241SPaul Richards
275d21656dcSConrad Meyer if (!fflag && isatty(STDIN_FILENO)) {
27630cfb241SPaul Richards (void)fprintf(stderr, "remove crontab for %s? ", User);
27730cfb241SPaul Richards first = ch = getchar();
27830cfb241SPaul Richards while (ch != '\n' && ch != EOF)
27930cfb241SPaul Richards ch = getchar();
28030cfb241SPaul Richards if (first != 'y' && first != 'Y')
28130cfb241SPaul Richards return;
28230cfb241SPaul Richards }
28384f33deaSJordan K. Hubbard
28484f33deaSJordan K. Hubbard log_it(RealUser, Pid, "DELETE", User);
285*fe590ffeSEric van Gyzen if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n))
286*fe590ffeSEric van Gyzen errx(ERROR_EXIT, "path too long");
287*fe590ffeSEric van Gyzen if (unlink(n) != 0) {
28884f33deaSJordan K. Hubbard if (errno == ENOENT)
289401e6468SPhilippe Charnier errx(ERROR_EXIT, "no crontab for %s", User);
29084f33deaSJordan K. Hubbard else
291401e6468SPhilippe Charnier err(ERROR_EXIT, "%s", n);
29284f33deaSJordan K. Hubbard }
29384f33deaSJordan K. Hubbard poke_daemon();
29484f33deaSJordan K. Hubbard }
29584f33deaSJordan K. Hubbard
29684f33deaSJordan K. Hubbard static void
check_error(const char * msg)297*fe590ffeSEric van Gyzen check_error(const char *msg)
29884f33deaSJordan K. Hubbard {
29984f33deaSJordan K. Hubbard CheckErrorCount++;
30084f33deaSJordan K. Hubbard fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg);
30184f33deaSJordan K. Hubbard }
30284f33deaSJordan K. Hubbard
30384f33deaSJordan K. Hubbard static void
edit_cmd(void)304e93f27e3SJohn Baldwin edit_cmd(void)
305e93f27e3SJohn Baldwin {
30684f33deaSJordan K. Hubbard char n[MAX_FNAME], q[MAX_TEMPSTR], *editor;
30784f33deaSJordan K. Hubbard FILE *f;
3089a2ef7d1SDiomidis Spinellis int t;
30925e9ca2bSDavid Malone struct stat statbuf, fsbuf;
31084f33deaSJordan K. Hubbard WAIT_T waiter;
31184f33deaSJordan K. Hubbard PID_T pid, xpid;
312bdddbd2fSPaul Traina mode_t um;
31308e019a8SDiomidis Spinellis int syntax_error = 0;
3140cd2e3abSDiomidis Spinellis char orig_md5[MD5_SIZE];
3150cd2e3abSDiomidis Spinellis char new_md5[MD5_SIZE];
31684f33deaSJordan K. Hubbard
31784f33deaSJordan K. Hubbard log_it(RealUser, Pid, "BEGIN EDIT", User);
318*fe590ffeSEric van Gyzen if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n))
319*fe590ffeSEric van Gyzen errx(ERROR_EXIT, "path too long");
32084f33deaSJordan K. Hubbard if (!(f = fopen(n, "r"))) {
321401e6468SPhilippe Charnier if (errno != ENOENT)
322401e6468SPhilippe Charnier err(ERROR_EXIT, "%s", n);
323401e6468SPhilippe Charnier warnx("no crontab for %s - using an empty one", User);
3241a37aa56SDavid E. O'Brien if (!(f = fopen(_PATH_DEVNULL, "r")))
3251a37aa56SDavid E. O'Brien err(ERROR_EXIT, _PATH_DEVNULL);
32684f33deaSJordan K. Hubbard }
32784f33deaSJordan K. Hubbard
328bdddbd2fSPaul Traina um = umask(077);
3292b9f079cSMatteo Riondato (void) snprintf(Filename, sizeof(Filename), "/tmp/crontab.XXXXXXXXXX");
330bdddbd2fSPaul Traina if ((t = mkstemp(Filename)) == -1) {
331401e6468SPhilippe Charnier warn("%s", Filename);
332bdddbd2fSPaul Traina (void) umask(um);
33384f33deaSJordan K. Hubbard goto fatal;
33484f33deaSJordan K. Hubbard }
335bdddbd2fSPaul Traina (void) umask(um);
33684f33deaSJordan K. Hubbard #ifdef HAS_FCHOWN
33784f33deaSJordan K. Hubbard if (fchown(t, getuid(), getgid()) < 0) {
33884f33deaSJordan K. Hubbard #else
33984f33deaSJordan K. Hubbard if (chown(Filename, getuid(), getgid()) < 0) {
34084f33deaSJordan K. Hubbard #endif
341401e6468SPhilippe Charnier warn("fchown");
34284f33deaSJordan K. Hubbard goto fatal;
34384f33deaSJordan K. Hubbard }
34425e9ca2bSDavid Malone if (!(NewCrontab = fdopen(t, "r+"))) {
345401e6468SPhilippe Charnier warn("fdopen");
34684f33deaSJordan K. Hubbard goto fatal;
34784f33deaSJordan K. Hubbard }
34884f33deaSJordan K. Hubbard
3499a2ef7d1SDiomidis Spinellis copy_file(f, NewCrontab);
35084f33deaSJordan K. Hubbard fclose(f);
35125e9ca2bSDavid Malone if (fflush(NewCrontab))
352401e6468SPhilippe Charnier err(ERROR_EXIT, "%s", Filename);
35325e9ca2bSDavid Malone if (fstat(t, &fsbuf) < 0) {
35425e9ca2bSDavid Malone warn("unable to fstat temp file");
35525e9ca2bSDavid Malone goto fatal;
35625e9ca2bSDavid Malone }
35784f33deaSJordan K. Hubbard again:
358cc427081SXin LI if (swap_uids() < OK)
359cc427081SXin LI err(ERROR_EXIT, "swapping uids");
3608dac9202SAndrey A. Chernov if (stat(Filename, &statbuf) < 0) {
361401e6468SPhilippe Charnier warn("stat");
362*fe590ffeSEric van Gyzen fatal:
363*fe590ffeSEric van Gyzen unlink(Filename);
36484f33deaSJordan K. Hubbard exit(ERROR_EXIT);
36584f33deaSJordan K. Hubbard }
366cc427081SXin LI if (swap_uids_back() < OK)
367cc427081SXin LI err(ERROR_EXIT, "swapping uids back");
36825e9ca2bSDavid Malone if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino)
36925e9ca2bSDavid Malone errx(ERROR_EXIT, "temp file must be edited in place");
3700cd2e3abSDiomidis Spinellis if (MD5File(Filename, orig_md5) == NULL) {
3710cd2e3abSDiomidis Spinellis warn("MD5");
3720cd2e3abSDiomidis Spinellis goto fatal;
3730cd2e3abSDiomidis Spinellis }
37484f33deaSJordan K. Hubbard
375*fe590ffeSEric van Gyzen if ((editor = getenv("VISUAL")) == NULL &&
376*fe590ffeSEric van Gyzen (editor = getenv("EDITOR")) == NULL) {
37784f33deaSJordan K. Hubbard editor = EDITOR;
37884f33deaSJordan K. Hubbard }
37984f33deaSJordan K. Hubbard
38084f33deaSJordan K. Hubbard /* we still have the file open. editors will generally rewrite the
38184f33deaSJordan K. Hubbard * original file rather than renaming/unlinking it and starting a
38284f33deaSJordan K. Hubbard * new one; even backup files are supposed to be made by copying
38384f33deaSJordan K. Hubbard * rather than by renaming. if some editor does not support this,
38484f33deaSJordan K. Hubbard * then don't use it. the security problems are more severe if we
38584f33deaSJordan K. Hubbard * close and reopen the file around the edit.
38684f33deaSJordan K. Hubbard */
38784f33deaSJordan K. Hubbard
38884f33deaSJordan K. Hubbard switch (pid = fork()) {
38984f33deaSJordan K. Hubbard case -1:
390401e6468SPhilippe Charnier warn("fork");
39184f33deaSJordan K. Hubbard goto fatal;
39284f33deaSJordan K. Hubbard case 0:
39384f33deaSJordan K. Hubbard /* child */
394401e6468SPhilippe Charnier if (setuid(getuid()) < 0)
395401e6468SPhilippe Charnier err(ERROR_EXIT, "setuid(getuid())");
396401e6468SPhilippe Charnier if (chdir("/tmp") < 0)
397401e6468SPhilippe Charnier err(ERROR_EXIT, "chdir(/tmp)");
398401e6468SPhilippe Charnier if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR)
399401e6468SPhilippe Charnier errx(ERROR_EXIT, "editor or filename too long");
4007bc6d015SBrian Somers execlp(editor, editor, Filename, (char *)NULL);
401401e6468SPhilippe Charnier err(ERROR_EXIT, "%s", editor);
40284f33deaSJordan K. Hubbard /*NOTREACHED*/
40384f33deaSJordan K. Hubbard default:
40484f33deaSJordan K. Hubbard /* parent */
40584f33deaSJordan K. Hubbard break;
40684f33deaSJordan K. Hubbard }
40784f33deaSJordan K. Hubbard
40884f33deaSJordan K. Hubbard /* parent */
409fd2c4394SMarc G. Fournier {
41066d48cdaSMatteo Riondato void (*sig[3])(int signal);
41166d48cdaSMatteo Riondato sig[0] = signal(SIGHUP, SIG_IGN);
41266d48cdaSMatteo Riondato sig[1] = signal(SIGINT, SIG_IGN);
41366d48cdaSMatteo Riondato sig[2] = signal(SIGTERM, SIG_IGN);
41484f33deaSJordan K. Hubbard xpid = wait(&waiter);
41566d48cdaSMatteo Riondato signal(SIGHUP, sig[0]);
41666d48cdaSMatteo Riondato signal(SIGINT, sig[1]);
41766d48cdaSMatteo Riondato signal(SIGTERM, sig[2]);
418fd2c4394SMarc G. Fournier }
41984f33deaSJordan K. Hubbard if (xpid != pid) {
420401e6468SPhilippe Charnier warnx("wrong PID (%d != %d) from \"%s\"", xpid, pid, editor);
42184f33deaSJordan K. Hubbard goto fatal;
42284f33deaSJordan K. Hubbard }
42384f33deaSJordan K. Hubbard if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
424401e6468SPhilippe Charnier warnx("\"%s\" exited with status %d", editor, WEXITSTATUS(waiter));
42584f33deaSJordan K. Hubbard goto fatal;
42684f33deaSJordan K. Hubbard }
42784f33deaSJordan K. Hubbard if (WIFSIGNALED(waiter)) {
428401e6468SPhilippe Charnier warnx("\"%s\" killed; signal %d (%score dumped)",
429401e6468SPhilippe Charnier editor, WTERMSIG(waiter), WCOREDUMP(waiter) ?"" :"no ");
43084f33deaSJordan K. Hubbard goto fatal;
43184f33deaSJordan K. Hubbard }
432cc427081SXin LI if (swap_uids() < OK)
433cc427081SXin LI err(ERROR_EXIT, "swapping uids");
4348dac9202SAndrey A. Chernov if (stat(Filename, &statbuf) < 0) {
435401e6468SPhilippe Charnier warn("stat");
43684f33deaSJordan K. Hubbard goto fatal;
43784f33deaSJordan K. Hubbard }
43825e9ca2bSDavid Malone if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino)
43925e9ca2bSDavid Malone errx(ERROR_EXIT, "temp file must be edited in place");
4400cd2e3abSDiomidis Spinellis if (MD5File(Filename, new_md5) == NULL) {
4410cd2e3abSDiomidis Spinellis warn("MD5");
4420cd2e3abSDiomidis Spinellis goto fatal;
4430cd2e3abSDiomidis Spinellis }
444cc427081SXin LI if (swap_uids_back() < OK)
445cc427081SXin LI err(ERROR_EXIT, "swapping uids back");
4460cd2e3abSDiomidis Spinellis if (strcmp(orig_md5, new_md5) == 0 && !syntax_error) {
447401e6468SPhilippe Charnier warnx("no changes made to crontab");
44884f33deaSJordan K. Hubbard goto remove;
44984f33deaSJordan K. Hubbard }
450401e6468SPhilippe Charnier warnx("installing new crontab");
45184f33deaSJordan K. Hubbard switch (replace_cmd()) {
45208e019a8SDiomidis Spinellis case 0: /* Success */
45384f33deaSJordan K. Hubbard break;
45408e019a8SDiomidis Spinellis case -1: /* Syntax error */
45584f33deaSJordan K. Hubbard for (;;) {
45684f33deaSJordan K. Hubbard printf("Do you want to retry the same edit? ");
45784f33deaSJordan K. Hubbard fflush(stdout);
45884f33deaSJordan K. Hubbard q[0] = '\0';
45984f33deaSJordan K. Hubbard (void) fgets(q, sizeof q, stdin);
46084f33deaSJordan K. Hubbard switch (islower(q[0]) ? q[0] : tolower(q[0])) {
46184f33deaSJordan K. Hubbard case 'y':
46208e019a8SDiomidis Spinellis syntax_error = 1;
46384f33deaSJordan K. Hubbard goto again;
46484f33deaSJordan K. Hubbard case 'n':
46584f33deaSJordan K. Hubbard goto abandon;
46684f33deaSJordan K. Hubbard default:
46784f33deaSJordan K. Hubbard fprintf(stderr, "Enter Y or N\n");
46884f33deaSJordan K. Hubbard }
46984f33deaSJordan K. Hubbard }
47084f33deaSJordan K. Hubbard /*NOTREACHED*/
47108e019a8SDiomidis Spinellis case -2: /* Install error */
47284f33deaSJordan K. Hubbard abandon:
473401e6468SPhilippe Charnier warnx("edits left in %s", Filename);
47484f33deaSJordan K. Hubbard goto done;
47584f33deaSJordan K. Hubbard default:
476401e6468SPhilippe Charnier warnx("panic: bad switch() in replace_cmd()");
47784f33deaSJordan K. Hubbard goto fatal;
47884f33deaSJordan K. Hubbard }
47984f33deaSJordan K. Hubbard remove:
48084f33deaSJordan K. Hubbard unlink(Filename);
48184f33deaSJordan K. Hubbard done:
48284f33deaSJordan K. Hubbard log_it(RealUser, Pid, "END EDIT", User);
48384f33deaSJordan K. Hubbard }
48484f33deaSJordan K. Hubbard
48584f33deaSJordan K. Hubbard
48684f33deaSJordan K. Hubbard /* returns 0 on success
48784f33deaSJordan K. Hubbard * -1 on syntax error
48884f33deaSJordan K. Hubbard * -2 on install error
48984f33deaSJordan K. Hubbard */
49084f33deaSJordan K. Hubbard static int
491e93f27e3SJohn Baldwin replace_cmd(void)
492e93f27e3SJohn Baldwin {
49384f33deaSJordan K. Hubbard char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
49484f33deaSJordan K. Hubbard FILE *tmp;
49584f33deaSJordan K. Hubbard int ch, eof;
49684f33deaSJordan K. Hubbard entry *e;
49784f33deaSJordan K. Hubbard time_t now = time(NULL);
49884f33deaSJordan K. Hubbard char **envp = env_init();
49984f33deaSJordan K. Hubbard
500bdddbd2fSPaul Traina if (envp == NULL) {
501401e6468SPhilippe Charnier warnx("cannot allocate memory");
502bdddbd2fSPaul Traina return (-2);
503bdddbd2fSPaul Traina }
504bdddbd2fSPaul Traina
5052b9f079cSMatteo Riondato (void) snprintf(n, sizeof(n), "tmp.%d", Pid);
506*fe590ffeSEric van Gyzen if (snprintf(tn, sizeof(tn), CRON_TAB(n)) >= (int)sizeof(tn)) {
507*fe590ffeSEric van Gyzen warnx("path too long");
508*fe590ffeSEric van Gyzen return (-2);
509*fe590ffeSEric van Gyzen }
5108037791bSMatteo Riondato
51184f33deaSJordan K. Hubbard if (!(tmp = fopen(tn, "w+"))) {
512401e6468SPhilippe Charnier warn("%s", tn);
51384f33deaSJordan K. Hubbard return (-2);
51484f33deaSJordan K. Hubbard }
51584f33deaSJordan K. Hubbard
51684f33deaSJordan K. Hubbard /* write a signature at the top of the file.
51784f33deaSJordan K. Hubbard *
51884f33deaSJordan K. Hubbard * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
51984f33deaSJordan K. Hubbard */
52084f33deaSJordan K. Hubbard fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
52184f33deaSJordan K. Hubbard fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now));
52284f33deaSJordan K. Hubbard fprintf(tmp, "# (Cron version -- %s)\n", rcsid);
52384f33deaSJordan K. Hubbard
52484f33deaSJordan K. Hubbard /* copy the crontab to the tmp
52584f33deaSJordan K. Hubbard */
52625e9ca2bSDavid Malone rewind(NewCrontab);
52784f33deaSJordan K. Hubbard Set_LineNum(1)
52884f33deaSJordan K. Hubbard while (EOF != (ch = get_char(NewCrontab)))
52984f33deaSJordan K. Hubbard putc(ch, tmp);
530e78e0c43SPedro F. Giffuni ftruncate(fileno(tmp), ftello(tmp));
53184f33deaSJordan K. Hubbard fflush(tmp); rewind(tmp);
53284f33deaSJordan K. Hubbard
53384f33deaSJordan K. Hubbard if (ferror(tmp)) {
534401e6468SPhilippe Charnier warnx("error while writing new crontab to %s", tn);
53584f33deaSJordan K. Hubbard fclose(tmp); unlink(tn);
53684f33deaSJordan K. Hubbard return (-2);
53784f33deaSJordan K. Hubbard }
53884f33deaSJordan K. Hubbard
53984f33deaSJordan K. Hubbard /* check the syntax of the file being installed.
54084f33deaSJordan K. Hubbard */
54184f33deaSJordan K. Hubbard
54284f33deaSJordan K. Hubbard /* BUG: was reporting errors after the EOF if there were any errors
54384f33deaSJordan K. Hubbard * in the file proper -- kludged it by stopping after first error.
54484f33deaSJordan K. Hubbard * vix 31mar87
54584f33deaSJordan K. Hubbard */
54684f33deaSJordan K. Hubbard Set_LineNum(1 - NHEADER_LINES)
54784f33deaSJordan K. Hubbard CheckErrorCount = 0; eof = FALSE;
54884f33deaSJordan K. Hubbard while (!CheckErrorCount && !eof) {
54984f33deaSJordan K. Hubbard switch (load_env(envstr, tmp)) {
55084f33deaSJordan K. Hubbard case ERR:
55184f33deaSJordan K. Hubbard eof = TRUE;
55284f33deaSJordan K. Hubbard break;
55384f33deaSJordan K. Hubbard case FALSE:
55484f33deaSJordan K. Hubbard e = load_entry(tmp, check_error, pw, envp);
55584f33deaSJordan K. Hubbard if (e)
5567044922bSPedro F. Giffuni free_entry(e);
55784f33deaSJordan K. Hubbard break;
55884f33deaSJordan K. Hubbard case TRUE:
55984f33deaSJordan K. Hubbard break;
56084f33deaSJordan K. Hubbard }
56184f33deaSJordan K. Hubbard }
56284f33deaSJordan K. Hubbard
56384f33deaSJordan K. Hubbard if (CheckErrorCount != 0) {
564401e6468SPhilippe Charnier warnx("errors in crontab file, can't install");
56584f33deaSJordan K. Hubbard fclose(tmp); unlink(tn);
56684f33deaSJordan K. Hubbard return (-1);
56784f33deaSJordan K. Hubbard }
56884f33deaSJordan K. Hubbard
56984f33deaSJordan K. Hubbard #ifdef HAS_FCHOWN
57084f33deaSJordan K. Hubbard if (fchown(fileno(tmp), ROOT_UID, -1) < OK)
57184f33deaSJordan K. Hubbard #else
57284f33deaSJordan K. Hubbard if (chown(tn, ROOT_UID, -1) < OK)
57384f33deaSJordan K. Hubbard #endif
57484f33deaSJordan K. Hubbard {
575401e6468SPhilippe Charnier warn("chown");
57684f33deaSJordan K. Hubbard fclose(tmp); unlink(tn);
57784f33deaSJordan K. Hubbard return (-2);
57884f33deaSJordan K. Hubbard }
57984f33deaSJordan K. Hubbard
58084f33deaSJordan K. Hubbard #ifdef HAS_FCHMOD
58184f33deaSJordan K. Hubbard if (fchmod(fileno(tmp), 0600) < OK)
58284f33deaSJordan K. Hubbard #else
58384f33deaSJordan K. Hubbard if (chmod(tn, 0600) < OK)
58484f33deaSJordan K. Hubbard #endif
58584f33deaSJordan K. Hubbard {
586401e6468SPhilippe Charnier warn("chown");
58784f33deaSJordan K. Hubbard fclose(tmp); unlink(tn);
58884f33deaSJordan K. Hubbard return (-2);
58984f33deaSJordan K. Hubbard }
59084f33deaSJordan K. Hubbard
59184f33deaSJordan K. Hubbard if (fclose(tmp) == EOF) {
592401e6468SPhilippe Charnier warn("fclose");
59384f33deaSJordan K. Hubbard unlink(tn);
59484f33deaSJordan K. Hubbard return (-2);
59584f33deaSJordan K. Hubbard }
59684f33deaSJordan K. Hubbard
597*fe590ffeSEric van Gyzen if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n)) {
598*fe590ffeSEric van Gyzen warnx("path too long");
599*fe590ffeSEric van Gyzen unlink(tn);
600*fe590ffeSEric van Gyzen return (-2);
601*fe590ffeSEric van Gyzen }
602*fe590ffeSEric van Gyzen
60384f33deaSJordan K. Hubbard if (rename(tn, n)) {
604401e6468SPhilippe Charnier warn("error renaming %s to %s", tn, n);
60584f33deaSJordan K. Hubbard unlink(tn);
60684f33deaSJordan K. Hubbard return (-2);
60784f33deaSJordan K. Hubbard }
6088037791bSMatteo Riondato
60984f33deaSJordan K. Hubbard log_it(RealUser, Pid, "REPLACE", User);
61084f33deaSJordan K. Hubbard
6115cdfc55cSJohn Baldwin /*
6125cdfc55cSJohn Baldwin * Creating the 'tn' temp file has already updated the
6135cdfc55cSJohn Baldwin * modification time of the spool directory. Sleep for a
6145cdfc55cSJohn Baldwin * second to ensure that poke_daemon() sets a later
6155cdfc55cSJohn Baldwin * modification time. Otherwise, this can race with the cron
6165cdfc55cSJohn Baldwin * daemon scanning for updated crontabs.
6175cdfc55cSJohn Baldwin */
6185cdfc55cSJohn Baldwin sleep(1);
6195cdfc55cSJohn Baldwin
62084f33deaSJordan K. Hubbard poke_daemon();
62184f33deaSJordan K. Hubbard
62284f33deaSJordan K. Hubbard return (0);
62384f33deaSJordan K. Hubbard }
62484f33deaSJordan K. Hubbard
62584f33deaSJordan K. Hubbard static void
626e93f27e3SJohn Baldwin poke_daemon(void)
627e93f27e3SJohn Baldwin {
62884f33deaSJordan K. Hubbard if (utime(SPOOL_DIR, NULL) < OK) {
629401e6468SPhilippe Charnier warn("can't update mtime on spooldir %s", SPOOL_DIR);
63084f33deaSJordan K. Hubbard return;
63184f33deaSJordan K. Hubbard }
63284f33deaSJordan K. Hubbard }
633