Patrick Welche <prlw1@cam.ac.uk>
[netbsd-mini2440.git] / libexec / cron / do_command.c
bloba0ac1c3b0fa81df94fe2009394a2256ceaeb2fb3
1 /* Copyright 1988,1990,1993 by Paul Vixie
2 * All rights reserved
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.
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
18 #if !defined(lint) && !defined(LINT)
19 static char rcsid[] = "$Id: do_command.c,v 1.1 1994/01/05 20:40:14 jtc Exp $";
20 #endif
23 #include "cron.h"
24 #include "externs.h"
25 #include <pwd.h>
26 #ifdef USE_SIGCHLD
27 # include <sys/signal.h>
28 #endif
29 #if defined(sequent)
30 # include <sys/universe.h>
31 #endif
32 #if defined(SYSLOG)
33 # include <syslog.h>
34 #endif
37 static void child_process __P((entry *, user *)),
38 do_univ __P((user *));
41 void
42 do_command(e, u)
43 entry *e;
44 user *u;
46 Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
47 getpid(), e->cmd, env_get("LOGNAME", u->envp), u->uid, u->gid))
49 /* fork to become asynchronous -- parent process is done immediately,
50 * and continues to run the normal cron code, which means return to
51 * tick(). the child and grandchild don't leave this function, alive.
53 * vfork() is unsuitable, since we have much to do, and the parent
54 * needs to be able to run off and fork other processes.
56 switch (fork()) {
57 case -1:
58 log_it("CRON",getpid(),"error","can't fork");
59 break;
60 case 0:
61 /* child process */
62 acquire_daemonlock(1);
63 child_process(e, u);
64 Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
65 _exit(OK_EXIT);
66 break;
67 default:
68 /* parent process */
69 break;
71 Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
75 static void
76 child_process(e, u)
77 entry *e;
78 user *u;
80 int stdin_pipe[2], stdout_pipe[2];
81 register char *input_data, *usernm, *mailto;
82 #ifdef USE_SIGCHLD
83 int children = 0;
84 #endif
86 Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
88 /* mark ourselves as different to PS command watchers by upshifting
89 * our program name. This has no effect on some kernels.
91 /*local*/{
92 register char *pch;
94 for (pch = ProgramName; *pch; pch++)
95 *pch = MkUpper(*pch);
98 /* discover some useful and important environment settings
100 usernm = env_get("LOGNAME", u->envp);
101 mailto = env_get("MAILTO", u->envp);
103 #ifdef USE_SIGCHLD
104 /* our parent is watching for our death by catching SIGCHLD. we
105 * do not care to watch for our children's deaths this way -- we
106 * use wait() explictly. so we have to disable the signal (which
107 * was inherited from the parent).
109 * this isn't needed for system V, since our parent is already
110 * SIG_IGN on SIGCLD -- which, hopefully, will cause children to
111 * simply vanish when they die.
113 (void) signal(SIGCHLD, SIG_IGN);
114 #endif /*BSD*/
116 /* create some pipes to talk to our future child
118 pipe(stdin_pipe); /* child's stdin */
119 pipe(stdout_pipe); /* child's stdout */
121 /* since we are a forked process, we can diddle the command string
122 * we were passed -- nobody else is going to use it again, right?
124 * if a % is present in the command, previous characters are the
125 * command, and subsequent characters are the additional input to
126 * the command. Subsequent %'s will be transformed into newlines,
127 * but that happens later.
129 /*local*/{
130 register int escaped = FALSE;
131 register int ch;
133 for (input_data = e->cmd; ch = *input_data; input_data++) {
134 if (escaped) {
135 escaped = FALSE;
136 continue;
138 if (ch == '\\') {
139 escaped = TRUE;
140 continue;
142 if (ch == '%') {
143 *input_data++ = '\0';
144 break;
149 /* fork again, this time so we can exec the user's command. Vfork()
150 * is okay this time, since we are going to exec() pretty quickly.
152 if (vfork() == 0) {
153 Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n", getpid()))
155 /* write a log message. we've waited this long to do it
156 * because it was not until now that we knew the PID that
157 * the actual user command shell was going to get and the
158 * PID is part of the log message.
160 /*local*/{
161 char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
163 log_it(usernm, getpid(), "CMD", x);
164 free(x);
167 /* that's the last thing we'll log. close the log files.
169 #ifdef SYSLOG
170 closelog();
171 #endif
173 /* get new pgrp, void tty, etc.
175 (void) setsid();
177 /* close the pipe ends that we won't use. this doesn't affect
178 * the parent, who has to read and write them; it keeps the
179 * kernel from recording us as a potential client TWICE --
180 * which would keep it from sending SIGPIPE in otherwise
181 * appropriate circumstances.
183 close(stdin_pipe[WRITE_PIPE]);
184 close(stdout_pipe[READ_PIPE]);
186 /* grandchild process. make std{in,out} be the ends of
187 * pipes opened by our daddy; make stderr go to stdout.
189 close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN);
190 close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT);
191 close(STDERR); dup2(STDOUT, STDERR);
193 /* close the pipes we just dup'ed. The resources will remain.
195 close(stdin_pipe[READ_PIPE]);
196 close(stdout_pipe[WRITE_PIPE]);
198 /* set our login universe. Do this in the grandchild
199 * so that the child can invoke /usr/lib/sendmail
200 * without surprises.
202 do_univ(u);
204 /* set our directory, uid and gid. Set gid first, since once
205 * we set uid, we've lost root privledges.
207 setgid(u->gid);
208 # if defined(BSD)
209 initgroups(env_get("LOGNAME", u->envp), u->gid);
210 # endif
212 * switch to specified user id. exec_uid is only
213 * valid for /etc/crontab, in other environments it
214 * will be 0. Note that it is overloaded for root
215 * because if 0 it will still pickup u->uid which will
216 * still be 0.
218 setuid(e->exec_uid ? e->exec_uid : u->uid);
219 /* you aren't root after this... */
220 chdir(env_get("HOME", u->envp));
222 /* exec the command.
225 char *shell = env_get("SHELL", u->envp);
227 # if DEBUGGING
228 if (DebugFlags & DTEST) {
229 fprintf(stderr,
230 "debug DTEST is on, not exec'ing command.\n");
231 fprintf(stderr,
232 "\tcmd='%s' shell='%s'\n", e->cmd, shell);
233 _exit(OK_EXIT);
235 # endif /*DEBUGGING*/
236 execle(shell, shell, "-c", e->cmd, (char *)0, u->envp);
237 fprintf(stderr, "execl: couldn't exec `%s'\n", shell);
238 perror("execl");
239 _exit(ERROR_EXIT);
243 #ifdef USE_SIGCHLD
244 children++;
245 #endif
247 /* middle process, child of original cron, parent of process running
248 * the user's command.
251 Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
253 /* close the ends of the pipe that will only be referenced in the
254 * grandchild process...
256 close(stdin_pipe[READ_PIPE]);
257 close(stdout_pipe[WRITE_PIPE]);
260 * write, to the pipe connected to child's stdin, any input specified
261 * after a % in the crontab entry. while we copy, convert any
262 * additional %'s to newlines. when done, if some characters were
263 * written and the last one wasn't a newline, write a newline.
265 * Note that if the input data won't fit into one pipe buffer (2K
266 * or 4K on most BSD systems), and the child doesn't read its stdin,
267 * we would block here. thus we must fork again.
270 if (*input_data && fork() == 0) {
271 register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
272 register int need_newline = FALSE;
273 register int escaped = FALSE;
274 register int ch;
276 Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
278 /* close the pipe we don't use, since we inherited it and
279 * are part of its reference count now.
281 close(stdout_pipe[READ_PIPE]);
283 /* translation:
284 * \% -> %
285 * % -> \n
286 * \x -> \x for all x != %
288 while (ch = *input_data++) {
289 if (escaped) {
290 if (ch != '%')
291 putc('\\', out);
292 } else {
293 if (ch == '%')
294 ch = '\n';
297 if (!(escaped = (ch == '\\'))) {
298 putc(ch, out);
299 need_newline = (ch != '\n');
302 if (escaped)
303 putc('\\', out);
304 if (need_newline)
305 putc('\n', out);
307 /* close the pipe, causing an EOF condition. fclose causes
308 * stdin_pipe[WRITE_PIPE] to be closed, too.
310 fclose(out);
312 Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
313 exit(0);
316 /* close the pipe to the grandkiddie's stdin, since its wicked uncle
317 * ernie back there has it open and will close it when he's done.
319 close(stdin_pipe[WRITE_PIPE]);
321 #ifdef USE_SIGCHLD
322 children++;
323 #endif
326 * read output from the grandchild. it's stderr has been redirected to
327 * it's stdout, which has been redirected to our pipe. if there is any
328 * output, we'll be mailing it to the user whose crontab this is...
329 * when the grandchild exits, we'll get EOF.
332 Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
334 /*local*/{
335 register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r");
336 register int ch = getc(in);
338 if (ch != EOF) {
339 register FILE *mail;
340 register int bytes = 1;
341 int status = 0;
343 Debug(DPROC|DEXT,
344 ("[%d] got data (%x:%c) from grandchild\n",
345 getpid(), ch, ch))
347 /* get name of recipient. this is MAILTO if set to a
348 * valid local username; USER otherwise.
350 if (mailto) {
351 /* MAILTO was present in the environment
353 if (!*mailto) {
354 /* ... but it's empty. set to NULL
356 mailto = NULL;
358 } else {
359 /* MAILTO not present, set to USER.
361 mailto = usernm;
364 /* if we are supposed to be mailing, MAILTO will
365 * be non-NULL. only in this case should we set
366 * up the mail command and subjects and stuff...
369 if (mailto) {
370 register char **env;
371 auto char mailcmd[MAX_COMMAND];
372 auto char hostname[MAXHOSTNAMELEN];
374 (void) gethostname(hostname, MAXHOSTNAMELEN);
375 (void) sprintf(mailcmd, MAILARGS,
376 MAILCMD, mailto);
377 if (!(mail = cron_popen(mailcmd, "w"))) {
378 perror(MAILCMD);
379 (void) _exit(ERROR_EXIT);
381 fprintf(mail, "From: root (Cron Daemon)\n");
382 fprintf(mail, "To: %s\n", mailto);
383 fprintf(mail, "Subject: Cron <%s@%s> %s\n",
384 usernm, first_word(hostname, "."),
385 e->cmd);
386 # if defined(MAIL_DATE)
387 fprintf(mail, "Date: %s\n",
388 arpadate(&TargetTime));
389 # endif /* MAIL_DATE */
390 for (env = u->envp; *env; env++)
391 fprintf(mail, "X-Cron-Env: <%s>\n",
392 *env);
393 fprintf(mail, "\n");
395 /* this was the first char from the pipe
397 putc(ch, mail);
400 /* we have to read the input pipe no matter whether
401 * we mail or not, but obviously we only write to
402 * mail pipe if we ARE mailing.
405 while (EOF != (ch = getc(in))) {
406 bytes++;
407 if (mailto)
408 putc(ch, mail);
411 /* only close pipe if we opened it -- i.e., we're
412 * mailing...
415 if (mailto) {
416 Debug(DPROC, ("[%d] closing pipe to mail\n",
417 getpid()))
418 /* Note: the pclose will probably see
419 * the termination of the grandchild
420 * in addition to the mail process, since
421 * it (the grandchild) is likely to exit
422 * after closing its stdout.
424 status = cron_pclose(mail);
427 /* if there was output and we could not mail it,
428 * log the facts so the poor user can figure out
429 * what's going on.
431 if (mailto && status) {
432 char buf[MAX_TEMPSTR];
434 sprintf(buf,
435 "mailed %d byte%s of output but got status 0x%04x\n",
436 bytes, (bytes==1)?"":"s",
437 status);
438 log_it(usernm, getpid(), "MAIL", buf);
441 } /*if data from grandchild*/
443 Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
445 fclose(in); /* also closes stdout_pipe[READ_PIPE] */
448 #ifdef USE_SIGCHLD
449 /* wait for children to die.
451 for (; children > 0; children--)
453 int pid;
454 WAIT_T waiter;
456 Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
457 getpid(), children))
458 pid = wait(&waiter);
459 if (pid < OK) {
460 Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
461 getpid()))
462 break;
464 Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
465 getpid(), pid, WEXITSTATUS(waiter)))
466 if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
467 Debug(DPROC, (", dumped core"))
468 Debug(DPROC, ("\n"))
470 #endif /*BSD*/
474 static void
475 do_univ(u)
476 user *u;
478 #if defined(sequent)
479 /* Dynix (Sequent) hack to put the user associated with
480 * the passed user structure into the ATT universe if
481 * necessary. We have to dig the gecos info out of
482 * the user's password entry to see if the magic
483 * "universe(att)" string is present. If we do change
484 * the universe, also set "LOGNAME".
487 struct passwd *p;
488 char *s;
489 int i;
490 char envstr[MAX_ENVSTR], **env_set();
492 p = getpwuid(u->uid);
493 (void) endpwent();
495 if (p == NULL)
496 return;
498 s = p->pw_gecos;
500 for (i = 0; i < 4; i++)
502 if ((s = strchr(s, ',')) == NULL)
503 return;
504 s++;
506 if (strcmp(s, "universe(att)"))
507 return;
509 (void) sprintf(envstr, "LOGNAME=%s", p->pw_name);
510 u->envp = env_set(u->envp, envstr);
512 (void) universe(U_ATT);
513 #endif