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