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