it should work even if you don't assign a pidfile
[oweals/busybox.git] / miscutils / crontab.c
1 /*
2  * CRONTAB
3  *
4  * usually setuid root, -c option only works if getuid() == geteuid()
5  *
6  * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
7  * May be distributed under the GNU General Public License
8  *
9  * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 to be used in busybox
10  *
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     if (chdir(CDir) < 0)
152         bb_perror_msg_and_die("cannot change dir to %s", CDir);
153
154     /*
155      * Handle options as appropriate
156      */
157
158     switch(option) {
159     case LIST:
160         {
161             FILE *fi;
162             char buf[1024];
163
164             if ((fi = fopen(pas->pw_name, "r"))) {
165                 while (fgets(buf, sizeof(buf), fi) != NULL)
166                     fputs(buf, stdout);
167                 fclose(fi);
168             } else {
169                 bb_error_msg("no crontab for %s", pas->pw_name);
170             }
171         }
172         break;
173     case EDIT:
174         {
175             FILE *fi;
176             int fd;
177             int n;
178             char tmp[128];
179             char buf[1024];
180
181             snprintf(tmp, sizeof(tmp), TMPDIR "/crontab.%d", getpid());
182             if ((fd = open(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600)) >= 0) {
183                 chown(tmp, getuid(), getgid());
184                 if ((fi = fopen(pas->pw_name, "r"))) {
185                     while ((n = fread(buf, 1, sizeof(buf), fi)) > 0)
186                         write(fd, buf, n);
187                 }
188                 EditFile(caller, tmp);
189                 remove(tmp);
190                 lseek(fd, 0L, 0);
191                 repFd = fd;
192             } else {
193                 bb_error_msg_and_die("unable to create %s", tmp);
194             }
195
196         }
197         option = REPLACE;
198         /* fall through */
199     case REPLACE:
200         {
201             char buf[1024];
202             char path[1024];
203             int fd;
204             int n;
205
206             snprintf(path, sizeof(path), "%s.new", pas->pw_name);
207             if ((fd = open(path, O_CREAT|O_TRUNC|O_APPEND|O_WRONLY, 0600)) >= 0) {
208                 while ((n = read(repFd, buf, sizeof(buf))) > 0) {
209                     write(fd, buf, n);
210                 }
211                 close(fd);
212                 rename(path, pas->pw_name);
213             } else {
214                 bb_error_msg("unable to create %s/%s", CDir, path);
215             }
216             close(repFd);
217         }
218         break;
219     case DELETE:
220         remove(pas->pw_name);
221         break;
222     case NONE:
223     default:
224         break;
225     }
226
227     /*
228      *  Bump notification file.  Handle window where crond picks file up
229      *  before we can write our entry out.
230      */
231
232     if (option == REPLACE || option == DELETE) {
233         FILE *fo;
234         struct stat st;
235
236         while ((fo = fopen(CRONUPDATE, "a"))) {
237             fprintf(fo, "%s\n", pas->pw_name);
238             fflush(fo);
239             if (fstat(fileno(fo), &st) != 0 || st.st_nlink != 0) {
240                 fclose(fo);
241                 break;
242             }
243             fclose(fo);
244             /* loop */
245         }
246         if (fo == NULL) {
247             bb_error_msg("unable to append to %s/%s", CDir, CRONUPDATE);
248         }
249     }
250     return 0;
251 }
252
253 static int
254 GetReplaceStream(const char *user, const char *file)
255 {
256     int filedes[2];
257     int pid;
258     int fd;
259     int n;
260     char buf[1024];
261
262     if (pipe(filedes) < 0) {
263         perror("pipe");
264         return(-1);
265     }
266     if ((pid = fork()) < 0) {
267         perror("fork");
268         return(-1);
269     }
270     if (pid > 0) {
271         /*
272          * PARENT
273          */
274
275         close(filedes[1]);
276         if (read(filedes[0], buf, 1) != 1) {
277             close(filedes[0]);
278             filedes[0] = -1;
279         }
280         return(filedes[0]);
281     }
282
283     /*
284      * CHILD
285      */
286
287     close(filedes[0]);
288
289     if (ChangeUser(user, 0) < 0)
290         exit(0);
291
292     fd = open(file, O_RDONLY);
293     if (fd < 0) {
294         bb_error_msg("unable to open %s", file);
295         exit(0);
296     }
297     buf[0] = 0;
298     write(filedes[1], buf, 1);
299     while ((n = read(fd, buf, sizeof(buf))) > 0) {
300         write(filedes[1], buf, n);
301     }
302     exit(0);
303 }
304
305 static void
306 EditFile(const char *user, const char *file)
307 {
308     int pid;
309
310     if ((pid = fork()) == 0) {
311         /*
312          * CHILD - change user and run editor
313          */
314         char *ptr;
315         char visual[1024];
316
317         if (ChangeUser(user, 1) < 0)
318             exit(0);
319         if ((ptr = getenv("VISUAL")) == NULL || strlen(ptr) > 256)
320             ptr = PATH_VI;
321
322         snprintf(visual, sizeof(visual), "%s %s", ptr, file);
323         execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", visual, NULL);
324         perror("exec");
325         exit(0);
326     }
327     if (pid < 0) {
328         /*
329          * PARENT - failure
330          */
331         bb_perror_msg_and_die("fork");
332     }
333     wait4(pid, NULL, 0, NULL);
334 }
335
336 static int
337 ChangeUser(const char *user, short dochdir)
338 {
339     struct passwd *pas;
340
341     /*
342      * Obtain password entry and change privilages
343      */
344
345     if ((pas = getpwnam(user)) == 0) {
346         bb_perror_msg_and_die("failed to get uid for %s", user);
347         return(-1);
348     }
349     setenv("USER", pas->pw_name, 1);
350     setenv("HOME", pas->pw_dir, 1);
351     setenv("SHELL", DEFAULT_SHELL, 1);
352
353     /*
354      * Change running state to the user in question
355      */
356     change_identity(pas);
357
358     if (dochdir) {
359         if (chdir(pas->pw_dir) < 0) {
360             bb_perror_msg_and_die("chdir failed: %s %s", user, pas->pw_dir);
361             if (chdir(TMPDIR) < 0) {
362                 bb_perror_msg_and_die("chdir failed: %s %s", user, TMPDIR);
363                 return(-1);
364             }
365         }
366     }
367     return(pas->pw_uid);
368 }