386BSD 0.1 development
[unix-history] / usr / src / libexec / crond / do_command.c
CommitLineData
6441d3de
WJ
1#if !defined(lint) && !defined(LINT)
2static char rcsid[] = "$Header: do_command.c,v 2.1 90/07/18 00:23:38 vixie Exp $";
3#endif
4
5/* $Source: /jove_u3/vixie/src/cron/RCS/do_command.c,v $
6 * $Revision: 2.1 $
7 * $Log: do_command.c,v $
8 * Revision 2.1 90/07/18 00:23:38 vixie
9 * Baseline for 4.4BSD release
10 *
11 * Revision 2.0 88/12/10 04:57:44 vixie
12 * V2 Beta
13 *
14 * Revision 1.5 88/11/29 13:06:06 vixie
15 * seems to work on Ultrix 3.0 FT1
16 *
17 * Revision 1.4 87/05/02 17:33:35 paul
18 * baseline for mod.sources release
19 *
20 * Revision 1.3 87/04/09 00:03:58 paul
21 * improved data hiding, locality of declaration/references
22 * fixed a rs@mirror bug by redesigning the mailto stuff completely
23 *
24 * Revision 1.2 87/03/19 12:46:24 paul
25 * implemented suggestions from rs@mirror (Rich $alz):
26 * MAILTO="" means no mail should be sent
27 * various fixes of bugs or lint complaints
28 * put a To: line in the mail message
29 *
30 * Revision 1.1 87/01/26 23:47:00 paul
31 * Initial revision
32 */
33
34/* Copyright 1988,1990 by Paul Vixie
35 * All rights reserved
36 *
37 * Distribute freely, except: don't remove my name from the source or
38 * documentation (don't take credit for my work), mark your changes (don't
39 * get me blamed for your possible bugs), don't alter or remove this
40 * notice. May be sold if buildable source is provided to buyer. No
41 * warrantee of any kind, express or implied, is included with this
42 * software; use at your own risk, responsibility for damages (if any) to
43 * anyone resulting from the use of this software rests entirely with the
44 * user.
45 *
46 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
47 * I'll try to keep a version up to date. I can be reached as follows:
48 * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
49 * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
50 */
51
52
53#include "cron.h"
54#include <signal.h>
55#include <pwd.h>
56#if defined(BSD)
57# include <sys/wait.h>
58#endif /*BSD*/
59#if defined(sequent)
60# include <strings.h>
61# include <sys/universe.h>
62#endif
63
64
65void
66do_command(cmd, u)
67 char *cmd;
68 user *u;
69{
70 extern int fork(), _exit();
71 extern void child_process(), log_it();
72 extern char *env_get();
73
74 Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
75 getpid(), cmd, env_get(USERENV, u->envp), u->uid, u->gid))
76
77 /* fork to become asynchronous -- parent process is done immediately,
78 * and continues to run the normal cron code, which means return to
79 * tick(). the child and grandchild don't leave this function, alive.
80 *
81 * vfork() is unsuitable, since we have much to do, and the parent
82 * needs to be able to run off and fork other processes.
83 */
84 switch (fork())
85 {
86 case -1:
87 log_it("CROND",getpid(),"error","can't fork");
88 break;
89 case 0:
90 /* child process */
91 child_process(cmd, u);
92 Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
93 _exit(OK_EXIT);
94 break;
95 }
96 Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
97}
98
99
100static void
101child_process(cmd, u)
102 char *cmd;
103 user *u;
104{
105 extern struct passwd *getpwnam();
106 extern void sigpipe_func(), be_different(), log_it();
107 extern int VFORK();
108 extern char *index(), *env_get();
109
110 auto int stdin_pipe[2], stdout_pipe[2];
111 register char *input_data, *usernm, *mailto;
112 auto int children = 0;
113#if defined(sequent)
114 extern void do_univ();
115#endif
116
117 Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), cmd))
118
119 /* mark ourselves as different to PS command watchers by upshifting
120 * our program name. This has no effect on some kernels.
121 */
122 {
123 register char *pch;
124
125 for (pch = ProgramName; *pch; pch++)
126 *pch = MkUpper(*pch);
127 }
128
129 /* discover some useful and important environment settings
130 */
131 usernm = env_get(USERENV, u->envp);
132 mailto = env_get("MAILTO", u->envp);
133
134#if defined(BSD)
135 /* our parent is watching for our death by catching SIGCHLD. we
136 * do not care to watch for our children's deaths this way -- we
137 * use wait() explictly. so we have to disable the signal (which
138 * was inherited from the parent).
139 *
140 * this isn't needed for system V, since our parent is already
141 * SIG_IGN on SIGCLD -- which, hopefully, will cause children to
142 * simply vanish when they die.
143 */
144 (void) signal(SIGCHLD, SIG_IGN);
145#endif /*BSD*/
146
147 /* create some pipes to talk to our future child
148 */
149 pipe(stdin_pipe); /* child's stdin */
150 pipe(stdout_pipe); /* child's stdout */
151
152 /* since we are a forked process, we can diddle the command string
153 * we were passed -- nobody else is going to use it again, right?
154 *
155 * if a % is present in the command, previous characters are the
156 * command, and subsequent characters are the additional input to
157 * the command. Subsequent %'s will be transformed into newlines,
158 * but that happens later.
159 */
160 if (NULL == (input_data = index(cmd, '%')))
161 {
162 /* no %. point input_data at a null string.
163 */
164 input_data = "";
165 }
166 else
167 {
168 /* % found. replace with a null (remember, we're a forked
169 * process and the string won't be reused), and increment
170 * input_data to point at the following character.
171 */
172 *input_data++ = '\0';
173 }
174
175 /* fork again, this time so we can exec the user's command. Vfork()
176 * is okay this time, since we are going to exec() pretty quickly.
177 * I'm assuming that closing pipe ends &whatnot will not affect our
178 * suspended pseudo-parent/alter-ego.
179 */
180 if (VFORK() == 0)
181 {
182 Debug(DPROC, ("[%d] grandchild process VFORK()'ed\n", getpid()))
183
184 /* write a log message. we've waited this long to do it
185 * because it was not until now that we knew the PID that
186 * the actual user command shell was going to get and the
187 * PID is part of the log message.
188 */
189#ifdef LOG_FILE
190 {
191 extern char *mkprints();
192 char *x = mkprints(cmd, strlen(cmd));
193
194 log_it(usernm, getpid(), "CMD", x);
195 free(x);
196 }
197#endif
198
199 /* get new pgrp, void tty, etc.
200 */
201 be_different();
202
203 /* close the pipe ends that we won't use. this doesn't affect
204 * the parent, who has to read and write them; it keeps the
205 * kernel from recording us as a potential client TWICE --
206 * which would keep it from sending SIGPIPE in otherwise
207 * appropriate circumstances.
208 */
209 close(stdin_pipe[WRITE_PIPE]);
210 close(stdout_pipe[READ_PIPE]);
211
212 /* grandchild process. make std{in,out} be the ends of
213 * pipes opened by our daddy; make stderr go to stdout.
214 */
215 close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN);
216 close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT);
217 close(STDERR); dup2(STDOUT, STDERR);
218
219 /* close the pipes we just dup'ed. The resources will remain,
220 * since they've been dup'ed... :-)...
221 */
222 close(stdin_pipe[READ_PIPE]);
223 close(stdout_pipe[WRITE_PIPE]);
224
225# if defined(sequent)
226 /* set our login universe. Do this in the grandchild
227 * so that the child can invoke /usr/lib/sendmail
228 * without surprises.
229 */
230 do_univ(u);
231# endif
232
233 /* set our directory, uid and gid. Set gid first, since once
234 * we set uid, we've lost root privledges. (oops!)
235 */
236 setgid(u->gid);
237# if defined(BSD)
238 initgroups(env_get(USERENV, u->envp), u->gid);
239# endif
240 setuid(u->uid); /* you aren't root after this... */
241 chdir(env_get("HOME", u->envp));
242
243 /* exec the command.
244 */
245 {
246 char *shell = env_get("SHELL", u->envp);
247
248# if DEBUGGING
249 if (DebugFlags & DTEST) {
250 fprintf(stderr,
251 "debug DTEST is on, not exec'ing command.\n");
252 fprintf(stderr,
253 "\tcmd='%s' shell='%s'\n", cmd, shell);
254 _exit(OK_EXIT);
255 }
256# endif /*DEBUGGING*/
257 /* normally you can't put debugging stuff here because
258 * it gets mailed with the command output.
259 */
260 /*
261 Debug(DPROC, ("[%d] execle('%s', '%s', -c, '%s')\n",
262 getpid(), shell, shell, cmd))
263 */
264
265# ifdef bad_idea
266 /* files writable by non-owner are a no-no
267 */
268 {
269 struct stat sb;
270
271 if (0 != stat(cmd, &sb)) {
272 fputs("crond: stat(2): ", stderr);
273 perror(cmd);
274 _exit(ERROR_EXIT);
275 } else if (sb.st_mode & 022) {
276 fprintf(stderr,
277 "crond: %s writable by nonowner\n",
278 cmd);
279 _exit(ERROR_EXIT);
280 } else if (sb.st_uid & 022) {
281 fprintf(stderr,
282 "crond: %s owned by uid %d\n",
283 cmd, sb.st_uid);
284 _exit(ERROR_EXIT);
285 }
286 }
287# endif /*bad_idea*/
288
289 execle(shell, shell, "-c", cmd, (char *)0, u->envp);
290 fprintf(stderr, "execl: couldn't exec `%s'\n", shell);
291 perror("execl");
292 _exit(ERROR_EXIT);
293 }
294 }
295
296 children++;
297
298 /* middle process, child of original cron, parent of process running
299 * the user's command.
300 */
301
302 Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
303
304 /* close the ends of the pipe that will only be referenced in the
305 * grandchild process...
306 */
307 close(stdin_pipe[READ_PIPE]);
308 close(stdout_pipe[WRITE_PIPE]);
309
310 /*
311 * write, to the pipe connected to child's stdin, any input specified
312 * after a % in the crontab entry. while we copy, convert any
313 * additional %'s to newlines. when done, if some characters were
314 * written and the last one wasn't a newline, write a newline.
315 *
316 * Note that if the input data won't fit into one pipe buffer (2K
317 * or 4K on most BSD systems), and the child doesn't read its stdin,
318 * we would block here. the solution, of course, is to fork again.
319 */
320
321 if (*input_data && fork() == 0) {
322 register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
323 register int need_newline = FALSE;
324 register int escaped = FALSE;
325 register int ch;
326
327 Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
328
329 /* close the pipe we don't use, since we inherited it and
330 * are part of its reference count now.
331 */
332 close(stdout_pipe[READ_PIPE]);
333
334 /* translation:
335 * \% -> %
336 * % -> \n
337 * \x -> \x for all x != %
338 */
339 while (ch = *input_data++)
340 {
341 if (escaped) {
342 if (ch != '%')
343 putc('\\', out);
344 } else {
345 if (ch == '%')
346 ch = '\n';
347 }
348
349 if (!(escaped = (ch == '\\'))) {
350 putc(ch, out);
351 need_newline = (ch != '\n');
352 }
353 }
354 if (escaped)
355 putc('\\', out);
356 if (need_newline)
357 putc('\n', out);
358
359 /* close the pipe, causing an EOF condition. fclose causes
360 * stdin_pipe[WRITE_PIPE] to be closed, too.
361 */
362 fclose(out);
363
364 Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
365 exit(0);
366 }
367
368 /* close the pipe to the grandkiddie's stdin, since its wicked uncle
369 * ernie back there has it open and will close it when he's done.
370 */
371 close(stdin_pipe[WRITE_PIPE]);
372
373 children++;
374
375 /*
376 * read output from the grandchild. it's stderr has been redirected to
377 * it's stdout, which has been redirected to our pipe. if there is any
378 * output, we'll be mailing it to the user whose crontab this is...
379 * when the grandchild exits, we'll get EOF.
380 */
381
382 Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
383
384 {
385 register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r");
386 register int ch = getc(in);
387
388 if (ch != EOF)
389 {
390 register FILE *mail;
391 register int bytes = 1;
392 union wait status;
393
394 Debug(DPROC|DEXT,
395 ("[%d] got data (%x:%c) from grandchild\n",
396 getpid(), ch, ch))
397
398 /* get name of recipient. this is MAILTO if set to a
399 * valid local username; USER otherwise.
400 */
401 if (mailto)
402 {
403 /* MAILTO was present in the environment
404 */
405 if (!*mailto)
406 {
407 /* ... but it's empty. set to NULL
408 */
409 mailto = NULL;
410 }
411 }
412 else
413 {
414 /* MAILTO not present, set to USER.
415 */
416 mailto = usernm;
417 }
418
419 /* if we are supposed to be mailing, MAILTO will
420 * be non-NULL. only in this case should we set
421 * up the mail command and subjects and stuff...
422 */
423
424 if (mailto)
425 {
426 extern FILE *popen();
427 extern char *sprintf(), *print_cmd();
428 register char **env;
429 auto char mailcmd[MAX_COMMAND];
430 auto char hostname[MAXHOSTNAMELEN];
431
432 (void) gethostname(hostname, MAXHOSTNAMELEN);
433 (void) sprintf(mailcmd, MAILCMD, mailto);
434 if (!(mail = popen(mailcmd, "w")))
435 {
436 perror(MAILCMD);
437 (void) _exit(ERROR_EXIT);
438 }
439 fprintf(mail, "From: root (Cron Daemon)\n");
440 fprintf(mail, "To: %s\n", mailto);
441 fprintf(mail,
442 "Subject: cron for %s@%s said this\n",
443 usernm, first_word(hostname, ".")
444 );
445 fprintf(mail, "Date: %s", ctime(&TargetTime));
446 fprintf(mail, "X-Cron-Cmd: <%s>\n", cmd);
447 for (env = u->envp; *env; env++)
448 fprintf(mail, "X-Cron-Env: <%s>\n",
449 *env);
450 fprintf(mail, "\n");
451
452 /* this was the first char from the pipe
453 */
454 putc(ch, mail);
455 }
456
457 /* we have to read the input pipe no matter whether
458 * we mail or not, but obviously we only write to
459 * mail pipe if we ARE mailing.
460 */
461
462 while (EOF != (ch = getc(in)))
463 {
464 bytes++;
465 if (mailto)
466 putc(ch, mail);
467 }
468
469 /* only close pipe if we opened it -- i.e., we're
470 * mailing...
471 */
472
473 if (mailto) {
474 Debug(DPROC, ("[%d] closing pipe to mail\n",
475 getpid()))
476 /* Note: the pclose will probably see
477 * the termination of the grandchild
478 * in addition to the mail process, since
479 * it (the grandchild) is likely to exit
480 * after closing its stdout.
481 */
482 status.w_status = pclose(mail);
483 }
484
485 /* if there was output and we could not mail it,
486 * log the facts so the poor user can figure out
487 * what's going on.
488 */
489 if (mailto && status.w_status) {
490 char buf[MAX_TEMPSTR];
491
492 sprintf(buf,
493 "mailed %d byte%s of output but got status 0x%04x\n",
494 bytes, (bytes==1)?"":"s",
495 status.w_status);
496 log_it(usernm, getpid(), "MAIL", buf);
497 }
498
499 } /*if data from grandchild*/
500
501 Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
502
503 fclose(in); /* also closes stdout_pipe[READ_PIPE] */
504 }
505
506#if defined(BSD)
507 /* wait for children to die.
508 */
509 for (; children > 0; children--)
510 {
511 int pid;
512 union wait waiter;
513
514 Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
515 getpid(), children))
516 pid = wait(&waiter);
517 if (pid < OK) {
518 Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
519 getpid()))
520 break;
521 }
522 Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
523 getpid(), pid, waiter.w_status))
524 if (waiter.w_coredump)
525 Debug(DPROC, (", dumped core"))
526 Debug(DPROC, ("\n"))
527 }
528#endif /*BSD*/
529}
530
531
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. If we do change
538 * the universe, also set "LOGNAME".
539 */
540
541void
542do_univ(u)
543 user *u;
544{
545 struct passwd *p;
546 char *s;
547 int i;
548 char envstr[MAX_ENVSTR], **env_set();
549
550 p = getpwuid(u->uid);
551 (void) endpwent();
552
553 if (p == NULL)
554 return;
555
556 s = p->pw_gecos;
557
558 for (i = 0; i < 4; i++)
559 {
560 if ((s = index(s, ',')) == NULL)
561 return;
562 s++;
563 }
564 if (strcmp(s, "universe(att)"))
565 return;
566
567 (void) sprintf(envstr, "LOGNAME=%s", p->pw_name);
568 u->envp = env_set(u->envp, envstr);
569
570 (void) universe(U_ATT);
571}
572#endif