blob: 703d01ecc88e5c978cd23afb94f8551c6d7a59f5 [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)
8 * May be distributed under the GNU General Public License
9 *
10 * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 to be used in busybox
11 *
12 */
13
14#include <stdio.h>
15#include <stdlib.h>
16#include <stdarg.h>
17#include <string.h>
18#include <errno.h>
19#include <time.h>
20#include <dirent.h>
21#include <fcntl.h>
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000022#include <unistd.h>
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000023#include <syslog.h>
24#include <signal.h>
25#include <getopt.h>
26#include <sys/ioctl.h>
27#include <sys/wait.h>
28#include <sys/stat.h>
29#include <sys/resource.h>
30
31#ifndef CRONTABS
32#define CRONTABS "/var/spool/cron/crontabs"
33#endif
34#ifndef TMPDIR
35#define TMPDIR "/var/spool/cron"
36#endif
37#ifndef CRONUPDATE
38#define CRONUPDATE "cron.update"
39#endif
40#ifndef PATH_VI
Eric Andersen9edcabd2003-07-14 19:14:26 +000041#define PATH_VI "/bin/vi" /* location of vi */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000042#endif
43
44#include "busybox.h"
45
46static const char *CDir = CRONTABS;
47
48static void EditFile(const char *user, const char *file);
49static int GetReplaceStream(const char *user, const char *file);
50static int ChangeUser(const char *user, short dochdir);
51
52int
53crontab_main(int ac, char **av)
54{
55 enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE;
56 const struct passwd *pas;
57 const char *repFile = NULL;
58 int repFd = 0;
59 int i;
60 char caller[256]; /* user that ran program */
61 int UserId;
62
63 UserId = getuid();
64 if ((pas = getpwuid(UserId)) == NULL)
Manuel Novoa III cad53642003-03-19 09:13:01 +000065 bb_perror_msg_and_die("getpwuid");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000066
67 strncpy(caller, pas->pw_name, sizeof(caller));
68
69 i = 1;
70 if (ac > 1) {
71 if (av[1][0] == '-' && av[1][1] == 0) {
72 option = REPLACE;
73 ++i;
74 } else if (av[1][0] != '-') {
75 option = REPLACE;
76 ++i;
77 repFile = av[1];
78 }
79 }
80
81 for (; i < ac; ++i) {
82 char *ptr = av[i];
83
84 if (*ptr != '-')
85 break;
86 ptr += 2;
87
88 switch(ptr[-1]) {
89 case 'l':
90 if (ptr[-1] == 'l')
91 option = LIST;
92 /* fall through */
93 case 'e':
94 if (ptr[-1] == 'e')
95 option = EDIT;
96 /* fall through */
97 case 'd':
98 if (ptr[-1] == 'd')
99 option = DELETE;
100 /* fall through */
101 case 'u':
102 if (i + 1 < ac && av[i+1][0] != '-') {
103 ++i;
104 if (getuid() == geteuid()) {
105 pas = getpwnam(av[i]);
106 if (pas) {
107 UserId = pas->pw_uid;
108 } else {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000109 bb_error_msg_and_die("user %s unknown", av[i]);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000110 }
111 } else {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000112 bb_error_msg_and_die("only the superuser may specify a user");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000113 }
114 }
115 break;
116 case 'c':
117 if (getuid() == geteuid()) {
118 CDir = (*ptr) ? ptr : av[++i];
119 } else {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000120 bb_error_msg_and_die("-c option: superuser only");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000121 }
122 break;
123 default:
124 i = ac;
125 break;
126 }
127 }
128 if (i != ac || option == NONE)
Manuel Novoa III cad53642003-03-19 09:13:01 +0000129 bb_show_usage();
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000130
131 /*
132 * Get password entry
133 */
134
135 if ((pas = getpwuid(UserId)) == NULL)
Manuel Novoa III cad53642003-03-19 09:13:01 +0000136 bb_perror_msg_and_die("getpwuid");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000137
138 /*
139 * If there is a replacement file, obtain a secure descriptor to it.
140 */
141
142 if (repFile) {
143 repFd = GetReplaceStream(caller, repFile);
144 if (repFd < 0)
Manuel Novoa III cad53642003-03-19 09:13:01 +0000145 bb_error_msg_and_die("unable to read replacement file");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000146 }
147
148 /*
149 * Change directory to our crontab directory
150 */
151
Bernhard Reutner-Fischerd9cf7ac2006-04-12 18:39:58 +0000152 bb_xchdir(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);
Glenn L McGrathdc4e75e2003-09-02 02:36:18 +0000323 execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", visual, NULL);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000324 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
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000336static int
337ChangeUser(const char *user, short dochdir)
338{
339 struct passwd *pas;
340
341 /*
Eric Andersenaff114c2004-04-14 17:51:38 +0000342 * Obtain password entry and change privileges
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000343 */
344
"Vladimir N. Oleynik"24f819f2006-01-10 12:35:43 +0000345 if ((pas = getpwnam(user)) == NULL) {
Glenn L McGrath99bd5ad2003-09-03 12:18:42 +0000346 bb_perror_msg_and_die("failed to get uid for %s", user);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000347 return(-1);
348 }
349 setenv("USER", pas->pw_name, 1);
350 setenv("HOME", pas->pw_dir, 1);
Glenn L McGrathdc4e75e2003-09-02 02:36:18 +0000351 setenv("SHELL", DEFAULT_SHELL, 1);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000352
353 /*
354 * Change running state to the user in question
355 */
Glenn L McGrath99bd5ad2003-09-03 12:18:42 +0000356 change_identity(pas);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000357
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000358 if (dochdir) {
359 if (chdir(pas->pw_dir) < 0) {
"Vladimir N. Oleynik"24f819f2006-01-10 12:35:43 +0000360 bb_perror_msg("chdir failed: %s %s", user, pas->pw_dir);
Bernhard Reutner-Fischerd9cf7ac2006-04-12 18:39:58 +0000361 bb_xchdir(TMPDIR);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000362 }
363 }
364 return(pas->pw_uid);
365}