1 /* Copyright 1988,1990,1993 by Paul Vixie
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
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 $";
27 # include <sys/signal.h>
30 # include <sys/universe.h>
37 static void child_process
__P((entry
*, user
*)),
38 do_univ
__P((user
*));
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.
58 log_it("CRON",getpid(),"error","can't fork");
62 acquire_daemonlock(1);
64 Debug(DPROC
, ("[%d] child process done, exiting\n", getpid()))
71 Debug(DPROC
, ("[%d] main process returning to work\n", getpid()))
80 int stdin_pipe
[2], stdout_pipe
[2];
81 register char *input_data
, *usernm
, *mailto
;
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.
94 for (pch
= ProgramName
; *pch
; pch
++)
98 /* discover some useful and important environment settings
100 usernm
= env_get("LOGNAME", u
->envp
);
101 mailto
= env_get("MAILTO", u
->envp
);
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
);
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.
130 register int escaped
= FALSE
;
133 for (input_data
= e
->cmd
; ch
= *input_data
; input_data
++) {
143 *input_data
++ = '\0';
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.
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.
161 char *x
= mkprints((u_char
*)e
->cmd
, strlen(e
->cmd
));
163 log_it(usernm
, getpid(), "CMD", x
);
167 /* that's the last thing we'll log. close the log files.
173 /* get new pgrp, void tty, etc.
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
204 /* set our directory, uid and gid. Set gid first, since once
205 * we set uid, we've lost root privledges.
209 initgroups(env_get("LOGNAME", u
->envp
), u
->gid
);
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
218 setuid(e
->exec_uid
? e
->exec_uid
: u
->uid
);
219 /* you aren't root after this... */
220 chdir(env_get("HOME", u
->envp
));
225 char *shell
= env_get("SHELL", u
->envp
);
228 if (DebugFlags
& DTEST
) {
230 "debug DTEST is on, not exec'ing command.\n");
232 "\tcmd='%s' shell='%s'\n", e
->cmd
, shell
);
235 # endif /*DEBUGGING*/
236 execle(shell
, shell
, "-c", e
->cmd
, (char *)0, u
->envp
);
237 fprintf(stderr
, "execl: couldn't exec `%s'\n", shell
);
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
;
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
]);
286 * \x -> \x for all x != %
288 while (ch
= *input_data
++) {
297 if (!(escaped
= (ch
== '\\'))) {
299 need_newline
= (ch
!= '\n');
307 /* close the pipe, causing an EOF condition. fclose causes
308 * stdin_pipe[WRITE_PIPE] to be closed, too.
312 Debug(DPROC
, ("[%d] child2 done sending to grandchild\n", getpid()))
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
]);
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()))
335 register FILE *in
= fdopen(stdout_pipe
[READ_PIPE
], "r");
336 register int ch
= getc(in
);
340 register int bytes
= 1;
344 ("[%d] got data (%x:%c) from grandchild\n",
347 /* get name of recipient. this is MAILTO if set to a
348 * valid local username; USER otherwise.
351 /* MAILTO was present in the environment
354 /* ... but it's empty. set to NULL
359 /* MAILTO not present, set to USER.
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...
371 auto char mailcmd
[MAX_COMMAND
];
372 auto char hostname
[MAXHOSTNAMELEN
];
374 (void) gethostname(hostname
, MAXHOSTNAMELEN
);
375 (void) sprintf(mailcmd
, MAILARGS
,
377 if (!(mail
= cron_popen(mailcmd
, "w"))) {
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
, "."),
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",
395 /* this was the first char from the pipe
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
))) {
411 /* only close pipe if we opened it -- i.e., we're
416 Debug(DPROC
, ("[%d] closing pipe to mail\n",
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
431 if (mailto
&& status
) {
432 char buf
[MAX_TEMPSTR
];
435 "mailed %d byte%s of output but got status 0x%04x\n",
436 bytes
, (bytes
==1)?"":"s",
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] */
449 /* wait for children to die.
451 for (; children
> 0; children
--)
456 Debug(DPROC
, ("[%d] waiting for grandchild #%d to finish\n",
460 Debug(DPROC
, ("[%d] no more grandchildren--mail written?\n",
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"))
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".
490 char envstr
[MAX_ENVSTR
], **env_set();
492 p
= getpwuid(u
->uid
);
500 for (i
= 0; i
< 4; i
++)
502 if ((s
= strchr(s
, ',')) == NULL
)
506 if (strcmp(s
, "universe(att)"))
509 (void) sprintf(envstr
, "LOGNAME=%s", p
->pw_name
);
510 u
->envp
= env_set(u
->envp
, envstr
);
512 (void) universe(U_ATT
);