blob: bad7f0018b22254343bc7450a8bb7f5a4d32cc6c [file] [log] [blame]
Bernhard Reutner-Fischerd9cf7ac2006-04-12 18:39:58 +00001/* vi: set sw=4 ts=4: */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00002/*
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)
Mike Frysingerf284c762006-04-16 20:38:26 +00008 * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00009 *
Mike Frysingerf284c762006-04-16 20:38:26 +000010 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000011 */
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
Bernhard Reutner-Fischerd9cf7ac2006-04-12 18:39:58 +0000151 bb_xchdir(CDir);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000152
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 {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000168 bb_error_msg("no crontab for %s", pas->pw_name);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000169 }
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());
Bernhard Reutner-Fischerc2cb0f32006-04-13 12:45:04 +0000181 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);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000186 }
Bernhard Reutner-Fischerc2cb0f32006-04-13 12:45:04 +0000187 EditFile(caller, tmp);
188 remove(tmp);
189 lseek(fd, 0L, 0);
190 repFd = fd;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000191 }
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);
Glenn L McGrath5a7ec222002-11-10 21:28:13 +0000202 if ((fd = open(path, O_CREAT|O_TRUNC|O_APPEND|O_WRONLY, 0600)) >= 0) {
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000203 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 {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000209 bb_error_msg("unable to create %s/%s", CDir, path);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000210 }
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) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000242 bb_error_msg("unable to append to %s/%s", CDir, CRONUPDATE);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000243 }
244 }
245 return 0;
246}
247
248static int
249GetReplaceStream(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
Bernhard Reutner-Fischerc2cb0f32006-04-13 12:45:04 +0000287 bb_default_error_retval = 0;
288 fd = bb_xopen3(file, O_RDONLY, 0);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000289 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
297static void
298EditFile(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);
Glenn L McGrathdc4e75e2003-09-02 02:36:18 +0000315 execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", visual, NULL);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000316 perror("exec");
317 exit(0);
318 }
319 if (pid < 0) {
320 /*
321 * PARENT - failure
322 */
Manuel Novoa III cad53642003-03-19 09:13:01 +0000323 bb_perror_msg_and_die("fork");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000324 }
325 wait4(pid, NULL, 0, NULL);
326}
327
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000328static int
329ChangeUser(const char *user, short dochdir)
330{
331 struct passwd *pas;
332
333 /*
Eric Andersenaff114c2004-04-14 17:51:38 +0000334 * Obtain password entry and change privileges
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000335 */
336
"Vladimir N. Oleynik"24f819f2006-01-10 12:35:43 +0000337 if ((pas = getpwnam(user)) == NULL) {
Glenn L McGrath99bd5ad2003-09-03 12:18:42 +0000338 bb_perror_msg_and_die("failed to get uid for %s", user);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000339 return(-1);
340 }
341 setenv("USER", pas->pw_name, 1);
342 setenv("HOME", pas->pw_dir, 1);
Glenn L McGrathdc4e75e2003-09-02 02:36:18 +0000343 setenv("SHELL", DEFAULT_SHELL, 1);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000344
345 /*
346 * Change running state to the user in question
347 */
Glenn L McGrath99bd5ad2003-09-03 12:18:42 +0000348 change_identity(pas);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000349
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000350 if (dochdir) {
351 if (chdir(pas->pw_dir) < 0) {
"Vladimir N. Oleynik"24f819f2006-01-10 12:35:43 +0000352 bb_perror_msg("chdir failed: %s %s", user, pas->pw_dir);
Bernhard Reutner-Fischerd9cf7ac2006-04-12 18:39:58 +0000353 bb_xchdir(TMPDIR);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000354 }
355 }
356 return(pas->pw_uid);
357}