Commit | Line | Data |
---|---|---|
6441d3de WJ |
1 | #if !defined(lint) && !defined(LINT) |
2 | static 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 | ||
65 | void | |
66 | do_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 | ||
100 | static void | |
101 | child_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 | ||
541 | void | |
542 | do_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 |