a8e650c60ac94a42b4ebbd37f17f0f88253cfe8a
[oweals/busybox.git] / miscutils / crontab.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * CRONTAB
4  *
5  * usually setuid root, -c option only works if getuid() == geteuid()
6  *
7  * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
8  * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
9  *
10  * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
11  */
12
13 #include "busybox.h"
14
15 #ifndef CRONTABS
16 #define CRONTABS        "/var/spool/cron/crontabs"
17 #endif
18 #ifndef TMPDIR
19 #define TMPDIR          "/var/spool/cron"
20 #endif
21 #ifndef CRONUPDATE
22 #define CRONUPDATE      "cron.update"
23 #endif
24 #ifndef PATH_VI
25 #define PATH_VI         "/bin/vi"   /* location of vi       */
26 #endif
27
28 static const char  *CDir = CRONTABS;
29
30 static void EditFile(const char *user, const char *file);
31 static int GetReplaceStream(const char *user, const char *file);
32 static int  ChangeUser(const char *user, short dochdir);
33
34 int crontab_main(int ac, char **av)
35 {
36     enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE;
37     const struct passwd *pas;
38     const char *repFile = NULL;
39     int repFd = 0;
40     int i;
41     char caller[256];           /* user that ran program */
42     int   UserId;
43
44     UserId = getuid();
45     if ((pas = getpwuid(UserId)) == NULL)
46         bb_perror_msg_and_die("getpwuid");
47
48     safe_strncpy(caller, pas->pw_name, sizeof(caller));
49
50     i = 1;
51     if (ac > 1) {
52         if (av[1][0] == '-' && av[1][1] == 0) {
53             option = REPLACE;
54             ++i;
55         } else if (av[1][0] != '-') {
56             option = REPLACE;
57             ++i;
58             repFile = av[1];
59         }
60     }
61
62     for (; i < ac; ++i) {
63         char *ptr = av[i];
64
65         if (*ptr != '-')
66             break;
67         ptr += 2;
68
69         switch(ptr[-1]) {
70         case 'l':
71             if (ptr[-1] == 'l')
72                 option = LIST;
73             /* fall through */
74         case 'e':
75             if (ptr[-1] == 'e')
76                 option = EDIT;
77             /* fall through */
78         case 'd':
79             if (ptr[-1] == 'd')
80                 option = DELETE;
81             /* fall through */
82         case 'u':
83             if (i + 1 < ac && av[i+1][0] != '-') {
84                 ++i;
85                 if (getuid() == geteuid()) {
86                     pas = getpwnam(av[i]);
87                     if (pas) {
88                         UserId = pas->pw_uid;
89                     } else {
90                         bb_error_msg_and_die("user %s unknown", av[i]);
91                     }
92                 } else {
93                     bb_error_msg_and_die("only the superuser may specify a user");
94                 }
95             }
96             break;
97         case 'c':
98             if (getuid() == geteuid()) {
99                 CDir = (*ptr) ? ptr : av[++i];
100             } else {
101                 bb_error_msg_and_die("-c option: superuser only");
102             }
103             break;
104         default:
105             i = ac;
106             break;
107         }
108     }
109     if (i != ac || option == NONE)
110         bb_show_usage();
111
112     /*
113      * Get password entry
114      */
115
116     if ((pas = getpwuid(UserId)) == NULL)
117         bb_perror_msg_and_die("getpwuid");
118
119     /*
120      * If there is a replacement file, obtain a secure descriptor to it.
121      */
122
123     if (repFile) {
124         repFd = GetReplaceStream(caller, repFile);
125         if (repFd < 0)
126             bb_error_msg_and_die("unable to read replacement file");
127     }
128
129     /*
130      * Change directory to our crontab directory
131      */
132
133     xchdir(CDir);
134
135     /*
136      * Handle options as appropriate
137      */
138
139     switch(option) {
140     case LIST:
141         {
142             FILE *fi;
143             char buf[1024];
144
145             if ((fi = fopen(pas->pw_name, "r"))) {
146                 while (fgets(buf, sizeof(buf), fi) != NULL)
147                     fputs(buf, stdout);
148                 fclose(fi);
149             } else {
150                 bb_error_msg("no crontab for %s", pas->pw_name);
151             }
152         }
153         break;
154     case EDIT:
155         {
156             FILE *fi;
157             int fd;
158             int n;
159             char tmp[128];
160             char buf[1024];
161
162             snprintf(tmp, sizeof(tmp), TMPDIR "/crontab.%d", getpid());
163             fd = xopen3(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600);
164             chown(tmp, getuid(), getgid());
165             if ((fi = fopen(pas->pw_name, "r"))) {
166                 while ((n = fread(buf, 1, sizeof(buf), fi)) > 0)
167                     write(fd, buf, n);
168             }
169             EditFile(caller, tmp);
170             remove(tmp);
171             lseek(fd, 0L, 0);
172             repFd = fd;
173         }
174         option = REPLACE;
175         /* fall through */
176     case REPLACE:
177         {
178             char buf[1024];
179             char path[1024];
180             int fd;
181             int n;
182
183             snprintf(path, sizeof(path), "%s.new", pas->pw_name);
184             if ((fd = open(path, O_CREAT|O_TRUNC|O_APPEND|O_WRONLY, 0600)) >= 0) {
185                 while ((n = read(repFd, buf, sizeof(buf))) > 0) {
186                     write(fd, buf, n);
187                 }
188                 close(fd);
189                 rename(path, pas->pw_name);
190             } else {
191                 bb_error_msg("unable to create %s/%s", CDir, path);
192             }
193             close(repFd);
194         }
195         break;
196     case DELETE:
197         remove(pas->pw_name);
198         break;
199     case NONE:
200     default:
201         break;
202     }
203
204     /*
205      *  Bump notification file.  Handle window where crond picks file up
206      *  before we can write our entry out.
207      */
208
209     if (option == REPLACE || option == DELETE) {
210         FILE *fo;
211         struct stat st;
212
213         while ((fo = fopen(CRONUPDATE, "a"))) {
214             fprintf(fo, "%s\n", pas->pw_name);
215             fflush(fo);
216             if (fstat(fileno(fo), &st) != 0 || st.st_nlink != 0) {
217                 fclose(fo);
218                 break;
219             }
220             fclose(fo);
221             /* loop */
222         }
223         if (fo == NULL) {
224             bb_error_msg("unable to append to %s/%s", CDir, CRONUPDATE);
225         }
226     }
227     return 0;
228 }
229
230 static int GetReplaceStream(const char *user, const char *file)
231 {
232     int filedes[2];
233     int pid;
234     int fd;
235     int n;
236     char buf[1024];
237
238     if (pipe(filedes) < 0) {
239         perror("pipe");
240         return(-1);
241     }
242     if ((pid = fork()) < 0) {
243         perror("fork");
244         return(-1);
245     }
246     if (pid > 0) {
247         /*
248          * PARENT
249          */
250
251         close(filedes[1]);
252         if (read(filedes[0], buf, 1) != 1) {
253             close(filedes[0]);
254             filedes[0] = -1;
255         }
256         return(filedes[0]);
257     }
258
259     /*
260      * CHILD
261      */
262
263     close(filedes[0]);
264
265     if (ChangeUser(user, 0) < 0)
266         exit(0);
267
268     bb_default_error_retval = 0;
269     fd = xopen3(file, O_RDONLY, 0);
270     buf[0] = 0;
271     write(filedes[1], buf, 1);
272     while ((n = read(fd, buf, sizeof(buf))) > 0) {
273         write(filedes[1], buf, n);
274     }
275     exit(0);
276 }
277
278 static void EditFile(const char *user, const char *file)
279 {
280     int pid;
281
282     if ((pid = fork()) == 0) {
283         /*
284          * CHILD - change user and run editor
285          */
286         char *ptr;
287         char visual[1024];
288
289         if (ChangeUser(user, 1) < 0)
290             exit(0);
291         if ((ptr = getenv("VISUAL")) == NULL || strlen(ptr) > 256)
292             ptr = PATH_VI;
293
294         snprintf(visual, sizeof(visual), "%s %s", ptr, file);
295         execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", visual, NULL);
296         perror("exec");
297         exit(0);
298     }
299     if (pid < 0) {
300         /*
301          * PARENT - failure
302          */
303         bb_perror_msg_and_die("fork");
304     }
305     wait4(pid, NULL, 0, NULL);
306 }
307
308 static int ChangeUser(const char *user, short dochdir)
309 {
310     struct passwd *pas;
311
312     /*
313      * Obtain password entry and change privileges
314      */
315
316     if ((pas = getpwnam(user)) == NULL) {
317         bb_perror_msg_and_die("failed to get uid for %s", user);
318         return(-1);
319     }
320     setenv("USER", pas->pw_name, 1);
321     setenv("HOME", pas->pw_dir, 1);
322     setenv("SHELL", DEFAULT_SHELL, 1);
323
324     /*
325      * Change running state to the user in question
326      */
327     change_identity(pas);
328
329     if (dochdir) {
330         if (chdir(pas->pw_dir) < 0) {
331             bb_perror_msg("chdir failed: %s %s", user, pas->pw_dir);
332             xchdir(TMPDIR);
333         }
334     }
335     return(pas->pw_uid);
336 }