No empty .Rs/.Re
[netbsd-mini2440.git] / usr.sbin / cron / do_command.c
blobf3e62570a4e257547455ac6373a2d3a1b011cc52
1 /* $NetBSD: do_command.c,v 1.24 2006/10/22 21:00:21 christos Exp $ */
3 /* Copyright 1988,1990,1993,1994 by Paul Vixie
4 * All rights reserved
6 * Distribute freely, except: don't remove my name from the source or
7 * documentation (don't take credit for my work), mark your changes (don't
8 * get me blamed for your possible bugs), don't alter or remove this
9 * notice. May be sold if buildable source is provided to buyer. No
10 * warrantee of any kind, express or implied, is included with this
11 * software; use at your own risk, responsibility for damages (if any) to
12 * anyone resulting from the use of this software rests entirely with the
13 * user.
15 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
16 * I'll try to keep a version up to date. I can be reached as follows:
17 * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
20 #include <sys/cdefs.h>
21 #if !defined(lint) && !defined(LINT)
22 #if 0
23 static char rcsid[] = "Id: do_command.c,v 2.12 1994/01/15 20:43:43 vixie Exp ";
24 #else
25 __RCSID("$NetBSD: do_command.c,v 1.24 2006/10/22 21:00:21 christos Exp $");
26 #endif
27 #endif
30 #include "cron.h"
31 #include <sys/signal.h>
32 #include <err.h>
33 #if defined(sequent)
34 # include <sys/universe.h>
35 #endif
36 #if defined(SYSLOG)
37 # include <syslog.h>
38 #endif
40 #ifdef LOGIN_CAP
41 # include <pwd.h>
42 # include <login_cap.h>
43 #endif
45 static void child_process(entry *, user *),
46 do_univ(user *);
49 void
50 do_command(entry *e, user *u)
52 Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
53 getpid(), e->cmd, u->name, e->uid, e->gid))
55 /* fork to become asynchronous -- parent process is done immediately,
56 * and continues to run the normal cron code, which means return to
57 * tick(). the child and grandchild don't leave this function, alive.
59 * vfork() is unsuitable, since we have much to do, and the parent
60 * needs to be able to run off and fork other processes.
62 switch (fork()) {
63 case -1:
64 log_it("CRON",getpid(),"error","can't fork");
65 break;
66 case 0:
67 /* child process */
68 acquire_daemonlock(1);
69 child_process(e, u);
70 Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
71 _exit(OK_EXIT);
72 break;
73 default:
74 /* parent process */
75 break;
77 Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
81 static void
82 child_process(entry *e, user *u)
84 int stdin_pipe[2], stdout_pipe[2];
85 char * volatile input_data;
86 char *usernm, * volatile mailto;
87 int children = 0;
89 Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
91 /* note we handle a job */
92 setproctitle("running job");
94 /* discover some useful and important environment settings
96 usernm = env_get("LOGNAME", e->envp);
97 mailto = env_get("MAILTO", e->envp);
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() explicitly. so we have to disable the signal (which
103 * was inherited from the parent).
105 (void) signal(SIGCHLD, SIG_DFL);
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.
111 (void) signal(SIGCLD, SIG_DFL);
112 #endif /*BSD*/
114 /* create some pipes to talk to our future child
116 pipe(stdin_pipe); /* child's stdin */
117 pipe(stdout_pipe); /* child's stdout */
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?
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.
127 /*local*/{
128 int escaped = FALSE;
129 int ch;
130 char *p;
132 /* translation:
133 * \% -> %
134 * % -> end of command, following is command input.
135 * \x -> \x for all x != %
137 input_data = p = e->cmd;
138 while ((ch = *input_data++) != '\0') {
139 if (escaped) {
140 if (ch != '%')
141 *p++ = '\\';
142 } else {
143 if (ch == '%') {
144 break;
148 if (!(escaped = (ch == '\\'))) {
149 *p++ = ch;
152 if (ch == '\0') {
153 /* move pointer back, so that code below
154 * won't think we encountered % sequence */
155 input_data--;
157 if (escaped)
158 *p++ = '\\';
160 *p = '\0';
163 /* fork again, this time so we can exec the user's command.
165 switch (vfork()) {
166 case -1:
167 log_it("CRON",getpid(),"error","can't vfork");
168 exit(ERROR_EXIT);
169 /*NOTREACHED*/
170 case 0:
171 Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n",
172 getpid()))
174 /* write a log message. we've waited this long to do it
175 * because it was not until now that we knew the PID that
176 * the actual user command shell was going to get and the
177 * PID is part of the log message.
179 /*local*/{
180 char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
182 log_it(usernm, getpid(), "CMD START", x);
183 free(x);
186 /* that's the last thing we'll log. close the log files.
188 #ifdef SYSLOG
189 closelog();
190 #endif
191 /* get new pgrp, void tty, etc.
193 if (setsid() == -1)
194 syslog(LOG_ERR, "setsid() failure: %m");
196 if (setlogin(usernm) < 0)
197 syslog(LOG_ERR, "setlogin() failure: %m");
199 /* close the pipe ends that we won't use. this doesn't affect
200 * the parent, who has to read and write them; it keeps the
201 * kernel from recording us as a potential client TWICE --
202 * which would keep it from sending SIGPIPE in otherwise
203 * appropriate circumstances.
205 close(stdin_pipe[WRITE_PIPE]);
206 close(stdout_pipe[READ_PIPE]);
208 /* grandchild process. make std{in,out} be the ends of
209 * pipes opened by our daddy; make stderr go to stdout.
211 close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN);
212 close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT);
213 close(STDERR); dup2(STDOUT, STDERR);
215 /* close the pipes we just dup'ed. The resources will remain.
217 close(stdin_pipe[READ_PIPE]);
218 close(stdout_pipe[WRITE_PIPE]);
220 /* set our login universe. Do this in the grandchild
221 * so that the child can invoke /usr/lib/sendmail
222 * without surprises.
224 do_univ(u);
226 #ifdef LOGIN_CAP
227 if (setusercontext(NULL, getpwuid(e->uid), e->uid,
228 LOGIN_SETRESOURCES|LOGIN_SETPRIORITY|
229 LOGIN_SETUMASK) != 0) {
230 syslog(LOG_ERR, "setusercontext failed");
231 _exit(ERROR_EXIT);
233 #endif /* LOGIN_CAP */
234 /* set our directory, uid and gid. Set gid first, since once
235 * we set uid, we've lost root privledges.
237 if (setgid(e->gid) != 0) {
238 syslog(LOG_ERR, "setgid failed");
239 _exit(ERROR_EXIT);
241 # if defined(BSD)
242 if (initgroups(usernm, e->gid) != 0) {
243 syslog(LOG_ERR, "initgroups failed");
244 _exit(ERROR_EXIT);
246 # endif
247 if (setuid(e->uid) != 0) {
248 syslog(LOG_ERR, "setuid failed");
249 _exit(ERROR_EXIT);
251 /* we aren't root after this... */
252 chdir(env_get("HOME", e->envp));
254 #ifdef USE_SIGCHLD
255 /* our grandparent is watching for our death by catching
256 * SIGCHLD. the parent is ignoring SIGCHLD's; we want
257 * to restore default behaviour.
259 (void) signal(SIGCHLD, SIG_DFL);
260 #endif
261 (void) signal(SIGHUP, SIG_DFL);
263 /* exec the command.
266 char *shell = env_get("SHELL", e->envp);
268 # if DEBUGGING
269 if (DebugFlags & DTEST) {
270 fprintf(stderr,
271 "debug DTEST is on, not exec'ing command.\n");
272 fprintf(stderr,
273 "\tcmd='%s' shell='%s'\n", e->cmd, shell);
274 _exit(OK_EXIT);
276 # endif /*DEBUGGING*/
277 execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);
278 warn("execl: couldn't exec `%s'", shell);
279 _exit(ERROR_EXIT);
281 break;
282 default:
283 /* parent process */
284 break;
287 children++;
289 /* middle process, child of original cron, parent of process running
290 * the user's command.
293 Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
295 /* close the ends of the pipe that will only be referenced in the
296 * grandchild process...
298 close(stdin_pipe[READ_PIPE]);
299 close(stdout_pipe[WRITE_PIPE]);
302 * write, to the pipe connected to child's stdin, any input specified
303 * after a % in the crontab entry. while we copy, convert any
304 * additional %'s to newlines. when done, if some characters were
305 * written and the last one wasn't a newline, write a newline.
307 * Note that if the input data won't fit into one pipe buffer (2K
308 * or 4K on most BSD systems), and the child doesn't read its stdin,
309 * we would block here. thus we must fork again.
312 if (*input_data && fork() == 0) {
313 FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
314 int need_newline = FALSE;
315 int escaped = FALSE;
316 int ch;
318 Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
320 /* close the pipe we don't use, since we inherited it and
321 * are part of its reference count now.
323 close(stdout_pipe[READ_PIPE]);
325 /* translation:
326 * \% -> %
327 * % -> \n
328 * \x -> \x for all x != %
330 while ((ch = *input_data++) != '\0') {
331 if (escaped) {
332 if (ch != '%')
333 putc('\\', out);
334 } else {
335 if (ch == '%')
336 ch = '\n';
339 if (!(escaped = (ch == '\\'))) {
340 putc(ch, out);
341 need_newline = (ch != '\n');
344 if (escaped)
345 putc('\\', out);
346 if (need_newline)
347 putc('\n', out);
349 /* close the pipe, causing an EOF condition. fclose causes
350 * stdin_pipe[WRITE_PIPE] to be closed, too.
352 fclose(out);
354 Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
355 exit(0);
358 /* close the pipe to the grandkiddie's stdin, since its wicked uncle
359 * ernie back there has it open and will close it when he's done.
361 close(stdin_pipe[WRITE_PIPE]);
363 children++;
366 * read output from the grandchild. it's stderr has been redirected to
367 * it's stdout, which has been redirected to our pipe. if there is any
368 * output, we'll be mailing it to the user whose crontab this is...
369 * when the grandchild exits, we'll get EOF.
372 Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
374 /*local*/{
375 FILE *in = fdopen(stdout_pipe[READ_PIPE], "r");
376 int ch = getc(in);
378 if (ch != EOF) {
379 FILE *mail;
380 int bytes = 1;
381 int status = 0;
383 #ifdef __GNUC__
384 mail = NULL; /* XXX gcc */
385 #endif
386 Debug(DPROC|DEXT,
387 ("[%d] got data (%x:%c) from grandchild\n",
388 getpid(), ch, ch))
390 /* get name of recipient. this is MAILTO if set to a
391 * valid local username; USER otherwise.
393 if (mailto) {
394 /* MAILTO was present in the environment
396 if (!*mailto) {
397 /* ... but it's empty. set to NULL
399 mailto = NULL;
401 } else {
402 /* MAILTO not present, set to USER.
404 mailto = usernm;
407 /* if we are supposed to be mailing, MAILTO will
408 * be non-NULL. only in this case should we set
409 * up the mail command and subjects and stuff...
412 if (mailto) {
413 char **env;
414 char mailcmd[MAX_COMMAND];
415 char hostname[MAXHOSTNAMELEN + 1];
417 (void)gethostname(hostname, sizeof hostname);
418 hostname[sizeof(hostname) - 1] = '\0';
419 (void)snprintf(mailcmd, sizeof(mailcmd),
420 MAILARGS, MAILCMD);
421 if (!(mail = cron_popen(mailcmd, "w"))) {
422 warn("cannot run %s", MAILCMD);
423 (void) _exit(ERROR_EXIT);
425 fprintf(mail, "From: root (Cron Daemon)\n");
426 fprintf(mail, "To: %s\n", mailto);
427 fprintf(mail, "Subject: Cron <%s@%s> %s\n",
428 usernm, first_word(hostname, "."),
429 e->cmd);
430 fprintf(mail, "Auto-Submitted: auto-generated\n");
431 # if defined(MAIL_DATE)
432 fprintf(mail, "Date: %s\n",
433 arpadate(&TargetTime));
434 # endif /* MAIL_DATE */
435 for (env = e->envp; *env; env++)
436 fprintf(mail, "X-Cron-Env: <%s>\n",
437 *env);
438 fprintf(mail, "\n");
440 /* this was the first char from the pipe
442 putc(ch, mail);
445 /* we have to read the input pipe no matter whether
446 * we mail or not, but obviously we only write to
447 * mail pipe if we ARE mailing.
450 while (EOF != (ch = getc(in))) {
451 bytes++;
452 if (mailto)
453 putc(ch, mail);
456 /* only close pipe if we opened it -- i.e., we're
457 * mailing...
460 if (mailto) {
461 Debug(DPROC, ("[%d] closing pipe to mail\n",
462 getpid()))
463 /* Note: the pclose will probably see
464 * the termination of the grandchild
465 * in addition to the mail process, since
466 * it (the grandchild) is likely to exit
467 * after closing its stdout.
469 status = cron_pclose(mail);
472 /* if there was output and we could not mail it,
473 * log the facts so the poor user can figure out
474 * what's going on.
476 if (mailto && status) {
477 char buf[MAX_TEMPSTR];
479 snprintf(buf, sizeof(buf),
480 "mailed %d byte%s of output but got status 0x%04x\n",
481 bytes, (bytes==1)?"":"s",
482 status);
483 log_it(usernm, getpid(), "MAIL", buf);
486 } /*if data from grandchild*/
488 Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
490 fclose(in); /* also closes stdout_pipe[READ_PIPE] */
493 /* wait for children to die.
495 for (; children > 0; children--)
497 WAIT_T waiter;
498 PID_T pid;
500 Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
501 getpid(), children))
502 pid = wait(&waiter);
503 if (pid < OK) {
504 Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
505 getpid()))
506 break;
508 Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
509 getpid(), pid, WEXITSTATUS(waiter)))
510 if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
511 Debug(DPROC, (", dumped core"))
512 Debug(DPROC, ("\n"))
515 /* Log the time when we finished deadling with the job */
516 /*local*/{
517 char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
519 log_it(usernm, getpid(), "CMD FINISH", x);
520 free(x);
525 static void
526 do_univ(user *u
527 #ifndef sequent
528 __unused
529 #endif
532 #if defined(sequent)
533 /* Dynix (Sequent) hack to put the user associated with
534 * the passed user structure into the ATT universe if
535 * necessary. We have to dig the gecos info out of
536 * the user's password entry to see if the magic
537 * "universe(att)" string is present.
540 struct passwd *p;
541 char *s;
542 int i;
544 p = getpwuid(u->uid);
545 (void) endpwent();
547 if (p == NULL)
548 return;
550 s = p->pw_gecos;
552 for (i = 0; i < 4; i++)
554 if ((s = strchr(s, ',')) == NULL)
555 return;
556 s++;
558 if (strcmp(s, "universe(att)"))
559 return;
561 (void) universe(U_ATT);
562 #endif