This commit was generated by cvs2svn to track changes on a CVS vendor
[unix-history] / usr.sbin / cron / do_command.c
CommitLineData
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)
20static 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
34static void child_process __P((entry *, user *)),
35 do_univ __P((user *));
36
37
38void
39do_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
72static void
73child_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
467static void
468do_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}