blob: 76c382e8fa35eb90aac528c3ed896c8aafd41cb0 [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
Denis Vlasenkob6adbf12007-05-26 19:00:18 +000013#include "libbb.h"
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000014
15#ifndef CRONTABS
16#define CRONTABS "/var/spool/cron/crontabs"
17#endif
18#ifndef TMPDIR
19#define TMPDIR "/var/spool/cron"
20#endif
21#ifndef CRONUPDATE
22#define CRONUPDATE "cron.update"
23#endif
24#ifndef PATH_VI
Denis Vlasenko94d5d822006-09-27 19:48:56 +000025#define PATH_VI "/bin/vi" /* location of vi */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000026#endif
27
Denis Vlasenko94d5d822006-09-27 19:48:56 +000028static const char *CDir = CRONTABS;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000029
30static void EditFile(const char *user, const char *file);
31static int GetReplaceStream(const char *user, const char *file);
Denis Vlasenko94d5d822006-09-27 19:48:56 +000032static int ChangeUser(const char *user, short dochdir);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000033
Denis Vlasenko06af2162007-02-03 17:28:39 +000034int crontab_main(int ac, char **av);
Rob Landleyd921b2e2006-08-03 15:41:12 +000035int crontab_main(int ac, char **av)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000036{
Denis Vlasenko94d5d822006-09-27 19:48:56 +000037 enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE;
38 const struct passwd *pas;
39 const char *repFile = NULL;
40 int repFd = 0;
41 int i;
42 char caller[256]; /* user that ran program */
43 char buf[1024];
44 int UserId;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000045
Denis Vlasenko94d5d822006-09-27 19:48:56 +000046 UserId = getuid();
47 pas = getpwuid(UserId);
48 if (pas == NULL)
49 bb_perror_msg_and_die("getpwuid");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000050
Denis Vlasenko94d5d822006-09-27 19:48:56 +000051 safe_strncpy(caller, pas->pw_name, sizeof(caller));
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000052
Denis Vlasenko94d5d822006-09-27 19:48:56 +000053 i = 1;
54 if (ac > 1) {
Denis Vlasenko9f739442006-12-16 23:49:13 +000055 if (LONE_DASH(av[1])) {
Denis Vlasenko94d5d822006-09-27 19:48:56 +000056 option = REPLACE;
57 ++i;
58 } else if (av[1][0] != '-') {
59 option = REPLACE;
60 ++i;
61 repFile = av[1];
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000062 }
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000063 }
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000064
Denis Vlasenko94d5d822006-09-27 19:48:56 +000065 for (; i < ac; ++i) {
66 char *ptr = av[i];
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000067
Denis Vlasenko94d5d822006-09-27 19:48:56 +000068 if (*ptr != '-')
69 break;
70 ptr += 2;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000071
Denis Vlasenko94d5d822006-09-27 19:48:56 +000072 switch (ptr[-1]) {
73 case 'l':
74 if (ptr[-1] == 'l')
75 option = LIST;
76 /* fall through */
77 case 'e':
78 if (ptr[-1] == 'e')
79 option = EDIT;
80 /* fall through */
81 case 'd':
82 if (ptr[-1] == 'd')
83 option = DELETE;
84 /* fall through */
85 case 'u':
86 if (i + 1 < ac && av[i+1][0] != '-') {
87 ++i;
88 if (getuid() == geteuid()) {
89 pas = getpwnam(av[i]);
90 if (pas) {
91 UserId = pas->pw_uid;
92 } else {
93 bb_error_msg_and_die("user %s unknown", av[i]);
94 }
95 } else {
96 bb_error_msg_and_die("only the superuser may specify a user");
97 }
98 }
99 break;
100 case 'c':
101 if (getuid() == geteuid()) {
102 CDir = (*ptr) ? ptr : av[++i];
103 } else {
104 bb_error_msg_and_die("-c option: superuser only");
105 }
106 break;
107 default:
108 i = ac;
109 break;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000110 }
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000111 }
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000112 if (i != ac || option == NONE)
113 bb_show_usage();
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000114
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000115 /*
116 * Get password entry
117 */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000118
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000119 pas = getpwuid(UserId);
120 if (pas == NULL)
121 bb_perror_msg_and_die("getpwuid");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000122
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000123 /*
124 * If there is a replacement file, obtain a secure descriptor to it.
125 */
126
127 if (repFile) {
128 repFd = GetReplaceStream(caller, repFile);
129 if (repFd < 0)
Denis Vlasenko89f0b342006-11-18 22:04:09 +0000130 bb_error_msg_and_die("cannot read replacement file");
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000131 }
132
133 /*
134 * Change directory to our crontab directory
135 */
136
137 xchdir(CDir);
138
139 /*
140 * Handle options as appropriate
141 */
142
143 switch (option) {
144 case LIST:
145 {
146 FILE *fi;
147
148 fi = fopen(pas->pw_name, "r");
149 if (fi) {
150 while (fgets(buf, sizeof(buf), fi) != NULL)
151 fputs(buf, stdout);
152 fclose(fi);
153 } else {
154 bb_error_msg("no crontab for %s", pas->pw_name);
155 }
156 }
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000157 break;
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000158 case EDIT:
159 {
Denis Vlasenkocf749bc2006-11-26 15:45:17 +0000160/* FIXME: messy code here! we have file copying helpers for this! */
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000161 FILE *fi;
162 int fd;
163 int n;
164 char tmp[128];
165
166 snprintf(tmp, sizeof(tmp), TMPDIR "/crontab.%d", getpid());
167 fd = xopen3(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600);
Denis Vlasenkocf749bc2006-11-26 15:45:17 +0000168/* race, use fchown */
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000169 chown(tmp, getuid(), getgid());
170 fi = fopen(pas->pw_name, "r");
171 if (fi) {
172 while ((n = fread(buf, 1, sizeof(buf), fi)) > 0)
Denis Vlasenkocf749bc2006-11-26 15:45:17 +0000173 full_write(fd, buf, n);
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000174 }
175 EditFile(caller, tmp);
176 remove(tmp);
Denis Vlasenkoea620772006-10-14 02:23:43 +0000177 lseek(fd, 0L, SEEK_SET);
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000178 repFd = fd;
179 }
180 option = REPLACE;
181 /* fall through */
182 case REPLACE:
183 {
Denis Vlasenkocf749bc2006-11-26 15:45:17 +0000184/* same here */
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000185 char path[1024];
186 int fd;
187 int n;
188
189 snprintf(path, sizeof(path), "%s.new", pas->pw_name);
190 fd = open(path, O_CREAT|O_TRUNC|O_APPEND|O_WRONLY, 0600);
191 if (fd >= 0) {
192 while ((n = read(repFd, buf, sizeof(buf))) > 0) {
Denis Vlasenkocf749bc2006-11-26 15:45:17 +0000193 full_write(fd, buf, n);
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000194 }
195 close(fd);
196 rename(path, pas->pw_name);
197 } else {
Denis Vlasenko89f0b342006-11-18 22:04:09 +0000198 bb_error_msg("cannot create %s/%s", CDir, path);
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000199 }
200 close(repFd);
201 }
202 break;
203 case DELETE:
204 remove(pas->pw_name);
205 break;
206 case NONE:
207 default:
208 break;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000209 }
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000210
211 /*
212 * Bump notification file. Handle window where crond picks file up
213 * before we can write our entry out.
214 */
215
216 if (option == REPLACE || option == DELETE) {
217 FILE *fo;
218 struct stat st;
219
220 while ((fo = fopen(CRONUPDATE, "a"))) {
221 fprintf(fo, "%s\n", pas->pw_name);
222 fflush(fo);
223 if (fstat(fileno(fo), &st) != 0 || st.st_nlink != 0) {
224 fclose(fo);
225 break;
226 }
227 fclose(fo);
228 /* loop */
229 }
230 if (fo == NULL) {
Denis Vlasenko89f0b342006-11-18 22:04:09 +0000231 bb_error_msg("cannot append to %s/%s", CDir, CRONUPDATE);
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000232 }
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000233 }
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000234 return 0;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000235}
236
Rob Landleyd921b2e2006-08-03 15:41:12 +0000237static int GetReplaceStream(const char *user, const char *file)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000238{
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000239 int filedes[2];
240 int pid;
241 int fd;
242 int n;
243 char buf[1024];
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000244
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000245 if (pipe(filedes) < 0) {
246 perror("pipe");
247 return -1;
248 }
249 pid = fork();
250 if (pid < 0) {
251 perror("fork");
252 return -1;
253 }
254 if (pid > 0) {
255 /*
256 * PARENT
257 */
258
259 close(filedes[1]);
260 if (read(filedes[0], buf, 1) != 1) {
261 close(filedes[0]);
262 filedes[0] = -1;
263 }
264 return filedes[0];
265 }
266
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000267 /*
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000268 * CHILD
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000269 */
270
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000271 close(filedes[0]);
272
273 if (ChangeUser(user, 0) < 0)
274 exit(0);
275
Denis Vlasenko40920822006-10-03 20:28:06 +0000276 xfunc_error_retval = 0;
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000277 fd = xopen(file, O_RDONLY);
278 buf[0] = 0;
279 write(filedes[1], buf, 1);
280 while ((n = read(fd, buf, sizeof(buf))) > 0) {
281 write(filedes[1], buf, n);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000282 }
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000283 exit(0);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000284}
285
Rob Landleyd921b2e2006-08-03 15:41:12 +0000286static void EditFile(const char *user, const char *file)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000287{
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000288 int pid = fork();
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000289
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000290 if (pid == 0) {
291 /*
292 * CHILD - change user and run editor
293 */
Denis Vlasenko06c0a712007-01-29 22:51:44 +0000294 const char *ptr;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000295
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000296 if (ChangeUser(user, 1) < 0)
297 exit(0);
298 ptr = getenv("VISUAL");
299 if (ptr == NULL || strlen(ptr) > 256)
300 ptr = PATH_VI;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000301
Denis Vlasenko06c0a712007-01-29 22:51:44 +0000302 ptr = xasprintf("%s %s", ptr, file);
303 execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", ptr, NULL);
304 bb_perror_msg_and_die("exec");
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000305 }
306 if (pid < 0) {
307 /*
308 * PARENT - failure
309 */
310 bb_perror_msg_and_die("fork");
311 }
312 wait4(pid, NULL, 0, NULL);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000313}
314
Rob Landleyd921b2e2006-08-03 15:41:12 +0000315static int ChangeUser(const char *user, short dochdir)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000316{
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000317 struct passwd *pas;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000318
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000319 /*
320 * Obtain password entry and change privileges
321 */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000322
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000323 pas = getpwnam(user);
324 if (pas == NULL) {
325 bb_perror_msg_and_die("failed to get uid for %s", user);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000326 }
Denis Vlasenko94d5d822006-09-27 19:48:56 +0000327 setenv("USER", pas->pw_name, 1);
328 setenv("HOME", pas->pw_dir, 1);
329 setenv("SHELL", DEFAULT_SHELL, 1);
330
331 /*
332 * Change running state to the user in question
333 */
334 change_identity(pas);
335
336 if (dochdir) {
337 if (chdir(pas->pw_dir) < 0) {
338 bb_perror_msg("chdir(%s) by %s failed", pas->pw_dir, user);
339 xchdir(TMPDIR);
340 }
341 }
342 return pas->pw_uid;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000343}