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
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
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)
23 static char rcsid
[] = "Id: do_command.c,v 2.12 1994/01/15 20:43:43 vixie Exp ";
25 __RCSID("$NetBSD: do_command.c,v 1.24 2006/10/22 21:00:21 christos Exp $");
31 #include <sys/signal.h>
34 # include <sys/universe.h>
42 # include <login_cap.h>
45 static void child_process(entry
*, user
*),
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.
64 log_it("CRON",getpid(),"error","can't fork");
68 acquire_daemonlock(1);
70 Debug(DPROC
, ("[%d] child process done, exiting\n", getpid()))
77 Debug(DPROC
, ("[%d] main process returning to work\n", getpid()))
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
;
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
);
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
);
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
);
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.
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') {
148 if (!(escaped
= (ch
== '\\'))) {
153 /* move pointer back, so that code below
154 * won't think we encountered % sequence */
163 /* fork again, this time so we can exec the user's command.
167 log_it("CRON",getpid(),"error","can't vfork");
171 Debug(DPROC
, ("[%d] grandchild process Vfork()'ed\n",
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.
180 char *x
= mkprints((u_char
*)e
->cmd
, strlen(e
->cmd
));
182 log_it(usernm
, getpid(), "CMD START", x
);
186 /* that's the last thing we'll log. close the log files.
191 /* get new pgrp, void tty, etc.
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
227 if (setusercontext(NULL
, getpwuid(e
->uid
), e
->uid
,
228 LOGIN_SETRESOURCES
|LOGIN_SETPRIORITY
|
229 LOGIN_SETUMASK
) != 0) {
230 syslog(LOG_ERR
, "setusercontext failed");
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");
242 if (initgroups(usernm
, e
->gid
) != 0) {
243 syslog(LOG_ERR
, "initgroups failed");
247 if (setuid(e
->uid
) != 0) {
248 syslog(LOG_ERR
, "setuid failed");
251 /* we aren't root after this... */
252 chdir(env_get("HOME", e
->envp
));
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
);
261 (void) signal(SIGHUP
, SIG_DFL
);
266 char *shell
= env_get("SHELL", e
->envp
);
269 if (DebugFlags
& DTEST
) {
271 "debug DTEST is on, not exec'ing command.\n");
273 "\tcmd='%s' shell='%s'\n", e
->cmd
, shell
);
276 # endif /*DEBUGGING*/
277 execle(shell
, shell
, "-c", e
->cmd
, (char *)0, e
->envp
);
278 warn("execl: couldn't exec `%s'", shell
);
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
;
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
]);
328 * \x -> \x for all x != %
330 while ((ch
= *input_data
++) != '\0') {
339 if (!(escaped
= (ch
== '\\'))) {
341 need_newline
= (ch
!= '\n');
349 /* close the pipe, causing an EOF condition. fclose causes
350 * stdin_pipe[WRITE_PIPE] to be closed, too.
354 Debug(DPROC
, ("[%d] child2 done sending to grandchild\n", getpid()))
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
]);
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()))
375 FILE *in
= fdopen(stdout_pipe
[READ_PIPE
], "r");
384 mail
= NULL
; /* XXX gcc */
387 ("[%d] got data (%x:%c) from grandchild\n",
390 /* get name of recipient. this is MAILTO if set to a
391 * valid local username; USER otherwise.
394 /* MAILTO was present in the environment
397 /* ... but it's empty. set to NULL
402 /* MAILTO not present, set to USER.
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...
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
),
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
, "."),
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",
440 /* this was the first char from the pipe
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
))) {
456 /* only close pipe if we opened it -- i.e., we're
461 Debug(DPROC
, ("[%d] closing pipe to mail\n",
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
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",
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
--)
500 Debug(DPROC
, ("[%d] waiting for grandchild #%d to finish\n",
504 Debug(DPROC
, ("[%d] no more grandchildren--mail written?\n",
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"))
515 /* Log the time when we finished deadling with the job */
517 char *x
= mkprints((u_char
*)e
->cmd
, strlen(e
->cmd
));
519 log_it(usernm
, getpid(), "CMD FINISH", x
);
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.
544 p
= getpwuid(u
->uid
);
552 for (i
= 0; i
< 4; i
++)
554 if ((s
= strchr(s
, ',')) == NULL
)
558 if (strcmp(s
, "universe(att)"))
561 (void) universe(U_ATT
);