diff options
Diffstat (limited to 'busybox/miscutils/crontab.c')
-rw-r--r-- | busybox/miscutils/crontab.c | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/busybox/miscutils/crontab.c b/busybox/miscutils/crontab.c new file mode 100644 index 000000000..89e13775f --- /dev/null +++ b/busybox/miscutils/crontab.c | |||
@@ -0,0 +1,368 @@ | |||
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 privileges | ||
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 | } | ||