Commit | Line | Data |
---|---|---|
15637ed4 | 1 | #if !defined(lint) && !defined(LINT) |
5ba07240 | 2 | static 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 | ||
77 | void | |
78 | do_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 | ||
111 | void | |
112 | child_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 | ||
552 | void | |
553 | do_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 |