aboutsummaryrefslogtreecommitdiff
path: root/miscutils
diff options
context:
space:
mode:
authorDenis Vlasenko <vda.linux@googlemail.com>2008-02-16 13:17:13 +0000
committerDenis Vlasenko <vda.linux@googlemail.com>2008-02-16 13:17:13 +0000
commit069e347863fa46f684ab6bd7e48cefd8fa74b629 (patch)
treeb2f2cb51963b95d56db7ff52ab3a65d5210f3cda /miscutils
parentcd5c61cd3b9bcca6afd30d6117d2d2e1791a7375 (diff)
downloadbusybox-w32-069e347863fa46f684ab6bd7e48cefd8fa74b629.tar.gz
busybox-w32-069e347863fa46f684ab6bd7e48cefd8fa74b629.tar.bz2
busybox-w32-069e347863fa46f684ab6bd7e48cefd8fa74b629.zip
crontab: almost complete rewrite
crontab: make options consistent with other implementations text data bss dec hex filename 2042 4 0 2046 7fe busybox.t1/miscutils/crontab.o 1331 0 0 1331 533 busybox.t2/miscutils/crontab.o function old new delta edit_file 733 956 +223 open_as_user - 171 +171 packed_usage 23652 23650 -2 CDir 8 4 -4 ChangeUser 139 - -139 crontab_main 1522 616 -906 ------------------------------------------------------------------------------ (add/remove: 1/1 grow/shrink: 1/3 up/down: 394/-1051) Total: -657 bytes
Diffstat (limited to 'miscutils')
-rw-r--r--miscutils/crontab.c470
1 files changed, 187 insertions, 283 deletions
diff --git a/miscutils/crontab.c b/miscutils/crontab.c
index 6d245da90..71037b7ee 100644
--- a/miscutils/crontab.c
+++ b/miscutils/crontab.c
@@ -25,321 +25,225 @@
25#define PATH_VI "/bin/vi" /* location of vi */ 25#define PATH_VI "/bin/vi" /* location of vi */
26#endif 26#endif
27 27
28static const char *CDir = CRONTABS; 28static void change_user(const struct passwd *pas)
29
30static void EditFile(const char *user, const char *file);
31static int GetReplaceStream(const char *user, const char *file);
32static int ChangeUser(const char *user, short dochdir);
33
34int crontab_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
35int crontab_main(int ac, char **av)
36{ 29{
37 enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE; 30 setenv("USER", pas->pw_name, 1);
38 const struct passwd *pas; 31 setenv("HOME", pas->pw_dir, 1);
39 const char *repFile = NULL; 32 setenv("SHELL", DEFAULT_SHELL, 1);
40 int repFd = 0;
41 int i;
42 char caller[256]; /* user that ran program */
43 char buf[1024];
44 int UserId;
45
46 UserId = getuid();
47 pas = getpwuid(UserId);
48 if (pas == NULL)
49 bb_perror_msg_and_die("getpwuid");
50
51 safe_strncpy(caller, pas->pw_name, sizeof(caller));
52
53 i = 1;
54 if (ac > 1) {
55 if (LONE_DASH(av[1])) {
56 option = REPLACE;
57 ++i;
58 } else if (av[1][0] != '-') {
59 option = REPLACE;
60 ++i;
61 repFile = av[1];
62 }
63 }
64 33
65 for (; i < ac; ++i) { 34 /* initgroups, setgid, setuid */
66 char *ptr = av[i]; 35 change_identity(pas);
67 36
68 if (*ptr != '-') 37 if (chdir(pas->pw_dir) < 0) {
69 break; 38 bb_perror_msg("chdir(%s) by %s failed",
70 ptr += 2; 39 pas->pw_dir, pas->pw_name);
71 40 xchdir(TMPDIR);
72 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;
110 }
111 } 41 }
112 if (i != ac || option == NONE) 42}
113 bb_show_usage();
114
115 /*
116 * Get password entry
117 */
118
119 pas = getpwuid(UserId);
120 if (pas == NULL)
121 bb_perror_msg_and_die("getpwuid");
122
123 /*
124 * If there is a replacement file, obtain a secure descriptor to it.
125 */
126 43
127 if (repFile) { 44static void edit_file(const struct passwd *pas, const char *file)
128 repFd = GetReplaceStream(caller, repFile); 45{
129 if (repFd < 0) 46 const char *ptr;
130 bb_error_msg_and_die("cannot read replacement file"); 47 int pid = vfork();
48
49 if (pid < 0) /* failure */
50 bb_perror_msg_and_die("vfork");
51 if (pid) { /* parent */
52 wait4pid(pid);
53 return;
131 } 54 }
132 55
133 /* 56 /* CHILD - change user and run editor */
134 * Change directory to our crontab directory 57 change_user(pas);
135 */ 58 ptr = getenv("VISUAL");
136 59 if (!ptr) {
137 xchdir(CDir); 60 ptr = getenv("EDITOR");
61 if (!ptr)
62 ptr = PATH_VI;
63 }
138 64
139 /* 65 /* TODO: clean up the environment!!! */
140 * Handle options as appropriate 66 /* not execlp - we won't use PATH */
141 */ 67 execl(ptr, ptr, file, NULL);
68 bb_perror_msg_and_die("exec %s", ptr);
69}
142 70
143 switch (option) { 71static int open_as_user(const struct passwd *pas, const char *file)
144 case LIST: 72{
145 { 73 int filedes[2];
146 FILE *fi; 74 pid_t pid;
147 75 char c;
148 fi = fopen(pas->pw_name, "r"); 76
149 if (fi) { 77 xpipe(filedes);
150 while (fgets(buf, sizeof(buf), fi) != NULL) 78 pid = vfork();
151 fputs(buf, stdout); 79 if (pid < 0) /* ERROR */
152 fclose(fi); 80 bb_perror_msg_and_die("vfork");
153 } else { 81 if (pid) { /* PARENT */
154 bb_error_msg("no crontab for %s", pas->pw_name); 82 int n = safe_read(filedes[0], &c, 1);
155 } 83 close(filedes[0]);
156 } 84 close(filedes[1]);
157 break; 85 if (n > 0) /* child says it can read */
158 case EDIT: 86 return open(file, O_RDONLY);
159 { 87 return -1;
160/* FIXME: messy code here! we have file copying helpers for this! */
161 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);
168/* race, use fchown */
169 chown(tmp, getuid(), getgid());
170 fi = fopen(pas->pw_name, "r");
171 if (fi) {
172 while ((n = fread(buf, 1, sizeof(buf), fi)) > 0)
173 full_write(fd, buf, n);
174 }
175 EditFile(caller, tmp);
176 remove(tmp);
177 lseek(fd, 0L, SEEK_SET);
178 repFd = fd;
179 }
180 option = REPLACE;
181 /* fall through */
182 case REPLACE:
183 {
184/* same here */
185 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) {
193 full_write(fd, buf, n);
194 }
195 close(fd);
196 rename(path, pas->pw_name);
197 } else {
198 bb_error_msg("cannot create %s/%s", CDir, path);
199 }
200 close(repFd);
201 }
202 break;
203 case DELETE:
204 remove(pas->pw_name);
205 break;
206 case NONE:
207 default:
208 break;
209 } 88 }
210 89
211 /* 90 /* CHILD */
212 * Bump notification file. Handle window where crond picks file up
213 * before we can write our entry out.
214 */
215 91
216 if (option == REPLACE || option == DELETE) { 92 /* initgroups, setgid, setuid */
217 FILE *fo; 93 change_identity(pas);
218 struct stat st;
219 94
220 while ((fo = fopen(CRONUPDATE, "a"))) { 95 /* We just try to read one byte. If that works, file is readable
221 fprintf(fo, "%s\n", pas->pw_name); 96 * under this user. We signal that by sending one byte to parent. */
222 fflush(fo); 97 if (safe_read(xopen(file, O_RDONLY), &c, 1) == 1)
223 if (fstat(fileno(fo), &st) != 0 || st.st_nlink != 0) { 98 safe_write(filedes[1], &c, 1); /* "papa, I can read!" */
224 fclose(fo); 99 _exit(0);
225 break;
226 }
227 fclose(fo);
228 /* loop */
229 }
230 if (fo == NULL) {
231 bb_error_msg("cannot append to %s/%s", CDir, CRONUPDATE);
232 }
233 }
234 return 0;
235} 100}
236 101
237static int GetReplaceStream(const char *user, const char *file) 102int crontab_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
103int crontab_main(int argc, char **argv)
238{ 104{
239 int filedes[2]; 105 const struct passwd *pas;
240 int pid; 106 const char *crontab_dir = CRONTABS;
107 char *tmp_fname;
108 char *new_fname;
109 char *user_name; /* -u USER */
241 int fd; 110 int fd;
242 int n; 111 int opt_ler;
243 char buf[1024]; 112 uid_t my_uid;
244 113
245 if (pipe(filedes) < 0) { 114 /* file [opts] Replace crontab from file
246 perror("pipe"); 115 * - [opts] Replace crontab from stdin
247 return -1; 116 * -u user User
248 } 117 * -c dir Crontab directory
249 pid = fork(); 118 * -l List crontab for user
250 if (pid < 0) { 119 * -e Edit crontab for user
251 perror("fork"); 120 * -r Delete crontab for user
252 return -1; 121 * bbox also supports -d == -r, but most other crontab
122 * implementations do not. Deprecated.
123 */
124 enum {
125 OPT_u = (1 << 0),
126 OPT_c = (1 << 1),
127 OPT_l = (1 << 2),
128 OPT_e = (1 << 3),
129 OPT_r = (1 << 4),
130 OPT_ler = OPT_l + OPT_e + OPT_r,
131 };
132
133 my_uid = getuid();
134
135 opt_complementary = "?1:dr"; /* max one argument; -d implies -r */
136 opt_ler = getopt32(argv, "u:c:lerd", &user_name, &crontab_dir);
137 argv += optind;
138
139 if (opt_ler & (OPT_u|OPT_c))
140 if (my_uid != geteuid())
141 bb_error_msg_and_die("only root can use -c or -u");
142
143 if (opt_ler & OPT_u) {
144 pas = getpwnam(user_name);
145 if (!pas)
146 bb_error_msg_and_die("user %s is not known", user_name);
147 my_uid = pas->pw_uid;
148 } else {
149 pas = getpwuid(my_uid);
150 if (!pas)
151 bb_perror_msg_and_die("no user record for UID %u",
152 (unsigned)my_uid);
253 } 153 }
254 if (pid > 0) {
255 /*
256 * PARENT
257 */
258 154
259 close(filedes[1]); 155#define user_name DONT_USE_ME_BEYOND_THIS_POINT
260 if (read(filedes[0], buf, 1) != 1) { 156#define my_uid DONT_USE_ME_BEYOND_THIS_POINT
261 close(filedes[0]); 157
262 filedes[0] = -1; 158 /* From now on, keep only -l, -e, -r bits */
159 opt_ler &= OPT_ler;
160 if ((opt_ler - 1) & opt_ler) /* more than one bit set? */
161 bb_show_usage();
162
163 /* Read replacement file under user's UID/GID/group vector */
164 if (!opt_ler) { /* Replace? */
165 if (!argv[0])
166 bb_show_usage();
167 if (NOT_LONE_DASH(argv[0])) {
168 fd = open_as_user(pas, argv[0]);
169 if (fd < 0)
170 bb_error_msg_and_die("user %s cannot read %s",
171 pas->pw_name, argv[0]);
172 xmove_fd(fd, STDIN_FILENO);
263 } 173 }
264 return filedes[0];
265 } 174 }
266 175
267 /* 176 /* cd to our crontab directory */
268 * CHILD 177 xchdir(crontab_dir);
269 */
270 178
271 close(filedes[0]); 179 tmp_fname = NULL;
272 180
273 if (ChangeUser(user, 0) < 0) 181 /* Handle requested operation */
274 exit(0); 182 switch (opt_ler) {
275 183
276 xfunc_error_retval = 0; 184 default: /* case OPT_r: Delete */
277 fd = xopen(file, O_RDONLY); 185 remove(pas->pw_name);
278 buf[0] = 0; 186 break;
279 write(filedes[1], buf, 1);
280 while ((n = read(fd, buf, sizeof(buf))) > 0) {
281 write(filedes[1], buf, n);
282 }
283 exit(0);
284}
285
286static void EditFile(const char *user, const char *file)
287{
288 int pid = fork();
289
290 if (pid == 0) {
291 /*
292 * CHILD - change user and run editor
293 */
294 const char *ptr;
295
296 if (ChangeUser(user, 1) < 0)
297 exit(0);
298 ptr = getenv("VISUAL");
299 if (ptr == NULL)
300 ptr = getenv("EDITOR");
301 if (ptr == NULL)
302 ptr = PATH_VI;
303 187
304 ptr = xasprintf("%s %s", ptr, file); 188 case OPT_l: /* List */
305 execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", ptr, NULL); 189 {
306 bb_perror_msg_and_die("exec"); 190 char *args[2] = { pas->pw_name, NULL };
307 } 191 return bb_cat(args);
308 if (pid < 0) { 192 /* list exits,
309 /* 193 * the rest go play with cron update file */
310 * PARENT - failure 194 }
311 */
312 bb_perror_msg_and_die("fork");
313 }
314 wait4(pid, NULL, 0, NULL);
315}
316 195
317static int ChangeUser(const char *user, short dochdir) 196 case OPT_e: /* Edit */
318{ 197 tmp_fname = xasprintf(TMPDIR "/crontab.%u", (unsigned)getpid());
319 struct passwd *pas; 198 fd = xopen3(tmp_fname, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600);
199 xmove_fd(fd, STDIN_FILENO);
200 fd = open(pas->pw_name, O_RDONLY);
201 if (fd >= 0) {
202 bb_copyfd_eof(fd, STDIN_FILENO);
203 close(fd);
204 }
205 fchown(STDIN_FILENO, pas->pw_uid, pas->pw_gid);
206 edit_file(pas, tmp_fname);
207 xlseek(STDIN_FILENO, 0, SEEK_SET);
208 /* fall through */
320 209
321 /* 210 case 0: /* Replace (no -l, -e, or -r were given) */
322 * Obtain password entry and change privileges 211 new_fname = xasprintf("%s.new", pas->pw_name);
323 */ 212 fd = open(new_fname, O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0600);
213 if (fd >= 0) {
214 bb_copyfd_eof(STDIN_FILENO, fd);
215 close(fd);
216 rename(new_fname, pas->pw_name);
217 } else {
218 bb_error_msg("cannot create %s/%s",
219 crontab_dir, new_fname);
220 }
221 if (tmp_fname)
222 remove(tmp_fname);
223 /*free(tmp_fname);*/
224 /*free(new_fname);*/
324 225
325 pas = getpwnam(user); 226 } /* switch */
326 if (pas == NULL) {
327 bb_perror_msg_and_die("cannot get uid for %s", user);
328 }
329 setenv("USER", pas->pw_name, 1);
330 setenv("HOME", pas->pw_dir, 1);
331 setenv("SHELL", DEFAULT_SHELL, 1);
332 227
333 /* 228 /* Bump notification file. Handle window where crond picks file up
334 * Change running state to the user in question 229 * before we can write our entry out.
335 */ 230 */
336 change_identity(pas); 231 while ((fd = open(CRONUPDATE, O_WRONLY|O_CREAT|O_APPEND)) >= 0) {
232 struct stat st;
337 233
338 if (dochdir) { 234 fdprintf(fd, "%s\n", pas->pw_name);
339 if (chdir(pas->pw_dir) < 0) { 235 if (fstat(fd, &st) != 0 || st.st_nlink != 0) {
340 bb_perror_msg("chdir(%s) by %s failed", pas->pw_dir, user); 236 /*close(fd);*/
341 xchdir(TMPDIR); 237 break;
342 } 238 }
239 /* st.st_nlink == 0:
240 * file was deleted, maybe crond missed our notification */
241 close(fd);
242 /* loop */
343 } 243 }
344 return pas->pw_uid; 244 if (fd < 0) {
245 bb_error_msg("cannot append to %s/%s",
246 crontab_dir, CRONUPDATE);
247 }
248 return 0;
345} 249}