blob: 6b9446464eb5707c5f3896415ce51c8877052e3c [file] [log] [blame]
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001/*
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>
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000021#include <unistd.h>
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000022#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
Eric Andersen9edcabd2003-07-14 19:14:26 +000040#define PATH_VI "/bin/vi" /* location of vi */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000041#endif
42
43#include "busybox.h"
44
45static const char *CDir = CRONTABS;
46
47static void EditFile(const char *user, const char *file);
48static int GetReplaceStream(const char *user, const char *file);
49static int ChangeUser(const char *user, short dochdir);
50
51int
52crontab_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)
Manuel Novoa III cad53642003-03-19 09:13:01 +000064 bb_perror_msg_and_die("getpwuid");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000065
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 {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000108 bb_error_msg_and_die("user %s unknown", av[i]);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000109 }
110 } else {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000111 bb_error_msg_and_die("only the superuser may specify a user");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000112 }
113 }
114 break;
115 case 'c':
116 if (getuid() == geteuid()) {
117 CDir = (*ptr) ? ptr : av[++i];
118 } else {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000119 bb_error_msg_and_die("-c option: superuser only");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000120 }
121 break;
122 default:
123 i = ac;
124 break;
125 }
126 }
127 if (i != ac || option == NONE)
Manuel Novoa III cad53642003-03-19 09:13:01 +0000128 bb_show_usage();
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000129
130 /*
131 * Get password entry
132 */
133
134 if ((pas = getpwuid(UserId)) == NULL)
Manuel Novoa III cad53642003-03-19 09:13:01 +0000135 bb_perror_msg_and_die("getpwuid");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000136
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)
Manuel Novoa III cad53642003-03-19 09:13:01 +0000144 bb_error_msg_and_die("unable to read replacement file");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000145 }
146
147 /*
148 * Change directory to our crontab directory
149 */
150
151 if (chdir(CDir) < 0)
Manuel Novoa III cad53642003-03-19 09:13:01 +0000152 bb_perror_msg_and_die("cannot change dir to %s", CDir);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000153
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 {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000169 bb_error_msg("no crontab for %s", pas->pw_name);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000170 }
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 {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000193 bb_error_msg_and_die("unable to create %s", tmp);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000194 }
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);
Glenn L McGrath5a7ec222002-11-10 21:28:13 +0000207 if ((fd = open(path, O_CREAT|O_TRUNC|O_APPEND|O_WRONLY, 0600)) >= 0) {
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000208 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 {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000214 bb_error_msg("unable to create %s/%s", CDir, path);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000215 }
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) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000247 bb_error_msg("unable to append to %s/%s", CDir, CRONUPDATE);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000248 }
249 }
250 return 0;
251}
252
253static int
254GetReplaceStream(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) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000294 bb_error_msg("unable to open %s", file);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000295 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
305static void
306EditFile(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("/bin/sh", "/bin/sh", "-c", visual, NULL);
324 perror("exec");
325 exit(0);
326 }
327 if (pid < 0) {
328 /*
329 * PARENT - failure
330 */
Manuel Novoa III cad53642003-03-19 09:13:01 +0000331 bb_perror_msg_and_die("fork");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000332 }
333 wait4(pid, NULL, 0, NULL);
334}
335
336static void
337log(const char *ctl, ...)
338{
339 va_list va;
340 char buf[1024];
341
342 va_start(va, ctl);
343 vsnprintf(buf, sizeof(buf), ctl, va);
344 syslog(LOG_NOTICE, "%s",buf );
345 va_end(va);
346}
347
348static int
349ChangeUser(const char *user, short dochdir)
350{
351 struct passwd *pas;
352
353 /*
354 * Obtain password entry and change privilages
355 */
356
357 if ((pas = getpwnam(user)) == 0) {
358 log("failed to get uid for %s", user);
359 return(-1);
360 }
361 setenv("USER", pas->pw_name, 1);
362 setenv("HOME", pas->pw_dir, 1);
363 setenv("SHELL", "/bin/sh", 1);
364
365 /*
366 * Change running state to the user in question
367 */
368
369 if (initgroups(user, pas->pw_gid) < 0) {
370 log("initgroups failed: %s %m", user);
371 return(-1);
372 }
373 if (setregid(pas->pw_gid, pas->pw_gid) < 0) {
374 log("setregid failed: %s %d", user, pas->pw_gid);
375 return(-1);
376 }
377 if (setreuid(pas->pw_uid, pas->pw_uid) < 0) {
378 log("setreuid failed: %s %d", user, pas->pw_uid);
379 return(-1);
380 }
381 if (dochdir) {
382 if (chdir(pas->pw_dir) < 0) {
383 if (chdir(TMPDIR) < 0) {
384 log("chdir failed: %s %s", user, pas->pw_dir);
385 log("chdir failed: %s " TMPDIR, user);
386 return(-1);
387 }
388 }
389 }
390 return(pas->pw_uid);
391}