Commit | Line | Data |
---|---|---|
693d8207 GR |
1 | /* Copyright 1988,1990,1993,1994 by Paul Vixie |
2 | * All rights reserved | |
3 | * | |
4 | * Distribute freely, except: don't remove my name from the source or | |
5 | * documentation (don't take credit for my work), mark your changes (don't | |
6 | * get me blamed for your possible bugs), don't alter or remove this | |
7 | * notice. May be sold if buildable source is provided to buyer. No | |
8 | * warrantee of any kind, express or implied, is included with this | |
9 | * software; use at your own risk, responsibility for damages (if any) to | |
10 | * anyone resulting from the use of this software rests entirely with the | |
11 | * user. | |
12 | * | |
13 | * Send bug reports, bug fixes, enhancements, requests, flames, etc., and | |
14 | * I'll try to keep a version up to date. I can be reached as follows: | |
15 | * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul | |
16 | * from Id: do_command.c,v 2.12 1994/01/15 20:43:43 vixie Exp | |
17 | */ | |
18 | ||
19 | #if !defined(lint) && !defined(LINT) | |
20 | static char rcsid[] = "$Header: $"; | |
21 | #endif | |
22 | ||
23 | ||
24 | #include "cron.h" | |
25 | #include <sys/signal.h> | |
26 | #if defined(sequent) | |
27 | # include <sys/universe.h> | |
28 | #endif | |
29 | #if defined(SYSLOG) | |
30 | # include <syslog.h> | |
31 | #endif | |
32 | ||
33 | ||
34 | static void child_process __P((entry *, user *)), | |
35 | do_univ __P((user *)); | |
36 | ||
37 | ||
38 | void | |
39 | do_command(e, u) | |
40 | entry *e; | |
41 | user *u; | |
42 | { | |
43 | Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", | |
44 | getpid(), e->cmd, u->name, e->uid, e->gid)) | |
45 | ||
46 | /* fork to become asynchronous -- parent process is done immediately, | |
47 | * and continues to run the normal cron code, which means return to | |
48 | * tick(). the child and grandchild don't leave this function, alive. | |
49 | * | |
50 | * vfork() is unsuitable, since we have much to do, and the parent | |
51 | * needs to be able to run off and fork other processes. | |
52 | */ | |
53 | switch (fork()) { | |
54 | case -1: | |
55 | log_it("CRON",getpid(),"error","can't fork"); | |
56 | break; | |
57 | case 0: | |
58 | /* child process */ | |
59 | acquire_daemonlock(1); | |
60 | child_process(e, u); | |
61 | Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) | |
62 | _exit(OK_EXIT); | |
63 | break; | |
64 | default: | |
65 | /* parent process */ | |
66 | break; | |
67 | } | |
68 | Debug(DPROC, ("[%d] main process returning to work\n", getpid())) | |
69 | } | |
70 | ||
71 | ||
72 | static void | |
73 | child_process(e, u) | |
74 | entry *e; | |
75 | user *u; | |
76 | { | |
77 | int stdin_pipe[2], stdout_pipe[2]; | |
78 | register char *input_data; | |
79 | char *usernm, *mailto; | |
80 | int children = 0; | |
81 | ||
82 | Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd)) | |
83 | ||
84 | /* mark ourselves as different to PS command watchers by upshifting | |
85 | * our program name. This has no effect on some kernels. | |
86 | */ | |
87 | /*local*/{ | |
88 | register char *pch; | |
89 | ||
90 | for (pch = ProgramName; *pch; pch++) | |
91 | *pch = MkUpper(*pch); | |
92 | } | |
93 | ||
94 | /* discover some useful and important environment settings | |
95 | */ | |
96 | usernm = env_get("LOGNAME", e->envp); | |
97 | mailto = env_get("MAILTO", e->envp); | |
98 | ||
99 | #ifdef USE_SIGCHLD | |
100 | /* our parent is watching for our death by catching SIGCHLD. we | |
101 | * do not care to watch for our children's deaths this way -- we | |
102 | * use wait() explictly. so we have to disable the signal (which | |
103 | * was inherited from the parent). | |
104 | */ | |
105 | (void) signal(SIGCHLD, SIG_IGN); | |
106 | #else | |
107 | /* on system-V systems, we are ignoring SIGCLD. we have to stop | |
108 | * ignoring it now or the wait() in cron_pclose() won't work. | |
109 | * because of this, we have to wait() for our children here, as well. | |
110 | */ | |
111 | (void) signal(SIGCLD, SIG_DFL); | |
112 | #endif /*BSD*/ | |
113 | ||
114 | /* create some pipes to talk to our future child | |
115 | */ | |
116 | pipe(stdin_pipe); /* child's stdin */ | |
117 | pipe(stdout_pipe); /* child's stdout */ | |
118 | ||
119 | /* since we are a forked process, we can diddle the command string | |
120 | * we were passed -- nobody else is going to use it again, right? | |
121 | * | |
122 | * if a % is present in the command, previous characters are the | |
123 | * command, and subsequent characters are the additional input to | |
124 | * the command. Subsequent %'s will be transformed into newlines, | |
125 | * but that happens later. | |
126 | */ | |
127 | /*local*/{ | |
128 | register int escaped = FALSE; | |
129 | register int ch; | |
130 | ||
131 | for (input_data = e->cmd; ch = *input_data; input_data++) { | |
132 | if (escaped) { | |
133 | escaped = FALSE; | |
134 | continue; | |
135 | } | |
136 | if (ch == '\\') { | |
137 | escaped = TRUE; | |
138 | continue; | |
139 | } | |
140 | if (ch == '%') { | |
141 | *input_data++ = '\0'; | |
142 | break; | |
143 | } | |
144 | } | |
145 | } | |
146 | ||
147 | /* fork again, this time so we can exec the user's command. | |
148 | */ | |
149 | switch (vfork()) { | |
150 | case -1: | |
151 | log_it("CRON",getpid(),"error","can't vfork"); | |
152 | exit(ERROR_EXIT); | |
153 | /*NOTREACHED*/ | |
154 | case 0: | |
155 | Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n", | |
156 | getpid())) | |
157 | ||
158 | /* write a log message. we've waited this long to do it | |
159 | * because it was not until now that we knew the PID that | |
160 | * the actual user command shell was going to get and the | |
161 | * PID is part of the log message. | |
162 | */ | |
163 | /*local*/{ | |
164 | char *x = mkprints((u_char *)e->cmd, strlen(e->cmd)); | |
165 | ||
166 | log_it(usernm, getpid(), "CMD", x); | |
167 | free(x); | |
168 | } | |
169 | ||
170 | /* that's the last thing we'll log. close the log files. | |
171 | */ | |
172 | #ifdef SYSLOG | |
173 | closelog(); | |
174 | #endif | |
175 | ||
176 | /* get new pgrp, void tty, etc. | |
177 | */ | |
178 | (void) setsid(); | |
179 | ||
180 | /* close the pipe ends that we won't use. this doesn't affect | |
181 | * the parent, who has to read and write them; it keeps the | |
182 | * kernel from recording us as a potential client TWICE -- | |
183 | * which would keep it from sending SIGPIPE in otherwise | |
184 | * appropriate circumstances. | |
185 | */ | |
186 | close(stdin_pipe[WRITE_PIPE]); | |
187 | close(stdout_pipe[READ_PIPE]); | |
188 | ||
189 | /* grandchild process. make std{in,out} be the ends of | |
190 | * pipes opened by our daddy; make stderr go to stdout. | |
191 | */ | |
192 | close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN); | |
193 | close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT); | |
194 | close(STDERR); dup2(STDOUT, STDERR); | |
195 | ||
196 | /* close the pipes we just dup'ed. The resources will remain. | |
197 | */ | |
198 | close(stdin_pipe[READ_PIPE]); | |
199 | close(stdout_pipe[WRITE_PIPE]); | |
200 | ||
201 | /* set our login universe. Do this in the grandchild | |
202 | * so that the child can invoke /usr/lib/sendmail | |
203 | * without surprises. | |
204 | */ | |
205 | do_univ(u); | |
206 | ||
207 | /* set our directory, uid and gid. Set gid first, since once | |
208 | * we set uid, we've lost root privledges. | |
209 | */ | |
210 | setgid(e->gid); | |
211 | # if defined(BSD) | |
212 | initgroups(env_get("LOGNAME", e->envp), e->gid); | |
213 | # endif | |
214 | setuid(e->uid); /* we aren't root after this... */ | |
215 | chdir(env_get("HOME", e->envp)); | |
216 | ||
217 | /* exec the command. | |
218 | */ | |
219 | { | |
220 | char *shell = env_get("SHELL", e->envp); | |
221 | ||
222 | # if DEBUGGING | |
223 | if (DebugFlags & DTEST) { | |
224 | fprintf(stderr, | |
225 | "debug DTEST is on, not exec'ing command.\n"); | |
226 | fprintf(stderr, | |
227 | "\tcmd='%s' shell='%s'\n", e->cmd, shell); | |
228 | _exit(OK_EXIT); | |
229 | } | |
230 | # endif /*DEBUGGING*/ | |
231 | execle(shell, shell, "-c", e->cmd, (char *)0, e->envp); | |
232 | fprintf(stderr, "execl: couldn't exec `%s'\n", shell); | |
233 | perror("execl"); | |
234 | _exit(ERROR_EXIT); | |
235 | } | |
236 | break; | |
237 | default: | |
238 | /* parent process */ | |
239 | break; | |
240 | } | |
241 | ||
242 | children++; | |
243 | ||
244 | /* middle process, child of original cron, parent of process running | |
245 | * the user's command. | |
246 | */ | |
247 | ||
248 | Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid())) | |
249 | ||
250 | /* close the ends of the pipe that will only be referenced in the | |
251 | * grandchild process... | |
252 | */ | |
253 | close(stdin_pipe[READ_PIPE]); | |
254 | close(stdout_pipe[WRITE_PIPE]); | |
255 | ||
256 | /* | |
257 | * write, to the pipe connected to child's stdin, any input specified | |
258 | * after a % in the crontab entry. while we copy, convert any | |
259 | * additional %'s to newlines. when done, if some characters were | |
260 | * written and the last one wasn't a newline, write a newline. | |
261 | * | |
262 | * Note that if the input data won't fit into one pipe buffer (2K | |
263 | * or 4K on most BSD systems), and the child doesn't read its stdin, | |
264 | * we would block here. thus we must fork again. | |
265 | */ | |
266 | ||
267 | if (*input_data && fork() == 0) { | |
268 | register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); | |
269 | register int need_newline = FALSE; | |
270 | register int escaped = FALSE; | |
271 | register int ch; | |
272 | ||
273 | Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) | |
274 | ||
275 | /* close the pipe we don't use, since we inherited it and | |
276 | * are part of its reference count now. | |
277 | */ | |
278 | close(stdout_pipe[READ_PIPE]); | |
279 | ||
280 | /* translation: | |
281 | * \% -> % | |
282 | * % -> \n | |
283 | * \x -> \x for all x != % | |
284 | */ | |
285 | while (ch = *input_data++) { | |
286 | if (escaped) { | |
287 | if (ch != '%') | |
288 | putc('\\', out); | |
289 | } else { | |
290 | if (ch == '%') | |
291 | ch = '\n'; | |
292 | } | |
293 | ||
294 | if (!(escaped = (ch == '\\'))) { | |
295 | putc(ch, out); | |
296 | need_newline = (ch != '\n'); | |
297 | } | |
298 | } | |
299 | if (escaped) | |
300 | putc('\\', out); | |
301 | if (need_newline) | |
302 | putc('\n', out); | |
303 | ||
304 | /* close the pipe, causing an EOF condition. fclose causes | |
305 | * stdin_pipe[WRITE_PIPE] to be closed, too. | |
306 | */ | |
307 | fclose(out); | |
308 | ||
309 | Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid())) | |
310 | exit(0); | |
311 | } | |
312 | ||
313 | /* close the pipe to the grandkiddie's stdin, since its wicked uncle | |
314 | * ernie back there has it open and will close it when he's done. | |
315 | */ | |
316 | close(stdin_pipe[WRITE_PIPE]); | |
317 | ||
318 | children++; | |
319 | ||
320 | /* | |
321 | * read output from the grandchild. it's stderr has been redirected to | |
322 | * it's stdout, which has been redirected to our pipe. if there is any | |
323 | * output, we'll be mailing it to the user whose crontab this is... | |
324 | * when the grandchild exits, we'll get EOF. | |
325 | */ | |
326 | ||
327 | Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) | |
328 | ||
329 | /*local*/{ | |
330 | register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); | |
331 | register int ch = getc(in); | |
332 | ||
333 | if (ch != EOF) { | |
334 | register FILE *mail; | |
335 | register int bytes = 1; | |
336 | int status = 0; | |
337 | ||
338 | Debug(DPROC|DEXT, | |
339 | ("[%d] got data (%x:%c) from grandchild\n", | |
340 | getpid(), ch, ch)) | |
341 | ||
342 | /* get name of recipient. this is MAILTO if set to a | |
343 | * valid local username; USER otherwise. | |
344 | */ | |
345 | if (mailto) { | |
346 | /* MAILTO was present in the environment | |
347 | */ | |
348 | if (!*mailto) { | |
349 | /* ... but it's empty. set to NULL | |
350 | */ | |
351 | mailto = NULL; | |
352 | } | |
353 | } else { | |
354 | /* MAILTO not present, set to USER. | |
355 | */ | |
356 | mailto = usernm; | |
357 | } | |
358 | ||
359 | /* if we are supposed to be mailing, MAILTO will | |
360 | * be non-NULL. only in this case should we set | |
361 | * up the mail command and subjects and stuff... | |
362 | */ | |
363 | ||
364 | if (mailto) { | |
365 | register char **env; | |
366 | auto char mailcmd[MAX_COMMAND]; | |
367 | auto char hostname[MAXHOSTNAMELEN]; | |
368 | ||
369 | (void) gethostname(hostname, MAXHOSTNAMELEN); | |
370 | (void) sprintf(mailcmd, MAILARGS, | |
371 | MAILCMD, mailto); | |
372 | if (!(mail = cron_popen(mailcmd, "w"))) { | |
373 | perror(MAILCMD); | |
374 | (void) _exit(ERROR_EXIT); | |
375 | } | |
376 | fprintf(mail, "From: root (Cron Daemon)\n"); | |
377 | fprintf(mail, "To: %s\n", mailto); | |
378 | fprintf(mail, "Subject: Cron <%s@%s> %s\n", | |
379 | usernm, first_word(hostname, "."), | |
380 | e->cmd); | |
381 | # if defined(MAIL_DATE) | |
382 | fprintf(mail, "Date: %s\n", | |
383 | arpadate(&TargetTime)); | |
384 | # endif /* MAIL_DATE */ | |
385 | for (env = e->envp; *env; env++) | |
386 | fprintf(mail, "X-Cron-Env: <%s>\n", | |
387 | *env); | |
388 | fprintf(mail, "\n"); | |
389 | ||
390 | /* this was the first char from the pipe | |
391 | */ | |
392 | putc(ch, mail); | |
393 | } | |
394 | ||
395 | /* we have to read the input pipe no matter whether | |
396 | * we mail or not, but obviously we only write to | |
397 | * mail pipe if we ARE mailing. | |
398 | */ | |
399 | ||
400 | while (EOF != (ch = getc(in))) { | |
401 | bytes++; | |
402 | if (mailto) | |
403 | putc(ch, mail); | |
404 | } | |
405 | ||
406 | /* only close pipe if we opened it -- i.e., we're | |
407 | * mailing... | |
408 | */ | |
409 | ||
410 | if (mailto) { | |
411 | Debug(DPROC, ("[%d] closing pipe to mail\n", | |
412 | getpid())) | |
413 | /* Note: the pclose will probably see | |
414 | * the termination of the grandchild | |
415 | * in addition to the mail process, since | |
416 | * it (the grandchild) is likely to exit | |
417 | * after closing its stdout. | |
418 | */ | |
419 | status = cron_pclose(mail); | |
420 | } | |
421 | ||
422 | /* if there was output and we could not mail it, | |
423 | * log the facts so the poor user can figure out | |
424 | * what's going on. | |
425 | */ | |
426 | if (mailto && status) { | |
427 | char buf[MAX_TEMPSTR]; | |
428 | ||
429 | sprintf(buf, | |
430 | "mailed %d byte%s of output but got status 0x%04x\n", | |
431 | bytes, (bytes==1)?"":"s", | |
432 | status); | |
433 | log_it(usernm, getpid(), "MAIL", buf); | |
434 | } | |
435 | ||
436 | } /*if data from grandchild*/ | |
437 | ||
438 | Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid())) | |
439 | ||
440 | fclose(in); /* also closes stdout_pipe[READ_PIPE] */ | |
441 | } | |
442 | ||
443 | /* wait for children to die. | |
444 | */ | |
445 | for (; children > 0; children--) | |
446 | { | |
447 | WAIT_T waiter; | |
448 | PID_T pid; | |
449 | ||
450 | Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n", | |
451 | getpid(), children)) | |
452 | pid = wait(&waiter); | |
453 | if (pid < OK) { | |
454 | Debug(DPROC, ("[%d] no more grandchildren--mail written?\n", | |
455 | getpid())) | |
456 | break; | |
457 | } | |
458 | Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x", | |
459 | getpid(), pid, WEXITSTATUS(waiter))) | |
460 | if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) | |
461 | Debug(DPROC, (", dumped core")) | |
462 | Debug(DPROC, ("\n")) | |
463 | } | |
464 | } | |
465 | ||
466 | ||
467 | static void | |
468 | do_univ(u) | |
469 | user *u; | |
470 | { | |
471 | #if defined(sequent) | |
472 | /* Dynix (Sequent) hack to put the user associated with | |
473 | * the passed user structure into the ATT universe if | |
474 | * necessary. We have to dig the gecos info out of | |
475 | * the user's password entry to see if the magic | |
476 | * "universe(att)" string is present. | |
477 | */ | |
478 | ||
479 | struct passwd *p; | |
480 | char *s; | |
481 | int i; | |
482 | ||
483 | p = getpwuid(u->uid); | |
484 | (void) endpwent(); | |
485 | ||
486 | if (p == NULL) | |
487 | return; | |
488 | ||
489 | s = p->pw_gecos; | |
490 | ||
491 | for (i = 0; i < 4; i++) | |
492 | { | |
493 | if ((s = strchr(s, ',')) == NULL) | |
494 | return; | |
495 | s++; | |
496 | } | |
497 | if (strcmp(s, "universe(att)")) | |
498 | return; | |
499 | ||
500 | (void) universe(U_ATT); | |
501 | #endif | |
502 | } |