1 /* Copyright 1988,1990,1993,1994 by Paul Vixie
2 * All rights reserved
3 */
4
5 /*
6 * Copyright (c) 1997 by Internet Software Consortium
7 *
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
13 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
14 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
15 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
16 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
17 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
18 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
19 * SOFTWARE.
20 */
21
22 #if !defined(lint) && !defined(LINT)
23 static const char rcsid[] =
24 "$Id: do_command.c,v 1.3 1998/08/14 00:32:39 vixie Exp $";
25 #endif
26
27 #include "cron.h"
28 #if defined(LOGIN_CAP)
29 # include <login_cap.h>
30 #endif
31 #ifdef PAM
32 # include <security/pam_appl.h>
33 # include <security/openpam.h>
34 #endif
35
36 static void child_process(entry *, user *);
37 static WAIT_T wait_on_child(PID_T, const char *);
38
39 extern char *environ;
40
41 void
do_command(entry * e,user * u)42 do_command(entry *e, user *u)
43 {
44 pid_t pid;
45
46 Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
47 getpid(), e->cmd, u->name, e->uid, e->gid))
48
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.
52 */
53 switch ((pid = fork())) {
54 case -1:
55 log_it("CRON", getpid(), "error", "can't fork");
56 if (e->flags & INTERVAL)
57 e->lastexit = time(NULL);
58 break;
59 case 0:
60 /* child process */
61 pidfile_close(pfh);
62 child_process(e, u);
63 Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
64 _exit(OK_EXIT);
65 break;
66 default:
67 /* parent process */
68 Debug(DPROC, ("[%d] main process forked child #%d, "
69 "returning to work\n", getpid(), pid))
70 if (e->flags & INTERVAL) {
71 e->lastexit = 0;
72 e->child = pid;
73 }
74 break;
75 }
76 Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
77 }
78
79
80 static void
child_process(entry * e,user * u)81 child_process(entry *e, user *u)
82 {
83 int stdin_pipe[2], stdout_pipe[2];
84 char *input_data;
85 const char *usernm, *mailto, *mailfrom;
86 PID_T jobpid, stdinjob, mailpid;
87 FILE *mail;
88 int bytes = 1;
89 int status = 0;
90 const char *homedir = NULL;
91 # if defined(LOGIN_CAP)
92 struct passwd *pwd;
93 login_cap_t *lc;
94 # endif
95
96 Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
97
98 /* mark ourselves as different to PS command watchers by upshifting
99 * our program name. This has no effect on some kernels.
100 */
101 setproctitle("running job");
102
103 /* discover some useful and important environment settings
104 */
105 usernm = env_get("LOGNAME", e->envp);
106 mailto = env_get("MAILTO", e->envp);
107 mailfrom = env_get("MAILFROM", e->envp);
108
109 #ifdef PAM
110 /* use PAM to see if the user's account is available,
111 * i.e., not locked or expired or whatever. skip this
112 * for system tasks from /etc/crontab -- they can run
113 * as any user.
114 */
115 if (strcmp(u->name, SYS_NAME)) { /* not equal */
116 pam_handle_t *pamh = NULL;
117 int pam_err;
118 struct pam_conv pamc = {
119 .conv = openpam_nullconv,
120 .appdata_ptr = NULL
121 };
122
123 Debug(DPROC, ("[%d] checking account with PAM\n", getpid()))
124
125 /* u->name keeps crontab owner name while LOGNAME is the name
126 * of user to run command on behalf of. they should be the
127 * same for a task from a per-user crontab.
128 */
129 if (strcmp(u->name, usernm)) {
130 log_it(usernm, getpid(), "username ambiguity", u->name);
131 exit(ERROR_EXIT);
132 }
133
134 pam_err = pam_start("cron", usernm, &pamc, &pamh);
135 if (pam_err != PAM_SUCCESS) {
136 log_it("CRON", getpid(), "error", "can't start PAM");
137 exit(ERROR_EXIT);
138 }
139
140 pam_err = pam_acct_mgmt(pamh, PAM_SILENT);
141 /* Expired password shouldn't prevent the job from running. */
142 if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD) {
143 log_it(usernm, getpid(), "USER", "account unavailable");
144 exit(ERROR_EXIT);
145 }
146
147 pam_end(pamh, pam_err);
148 }
149 #endif
150
151 /* our parent is watching for our death by catching SIGCHLD. we
152 * do not care to watch for our children's deaths this way -- we
153 * use wait() explicitly. so we have to disable the signal (which
154 * was inherited from the parent).
155 */
156 (void) signal(SIGCHLD, SIG_DFL);
157
158 /* create some pipes to talk to our future child
159 */
160 if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) {
161 log_it("CRON", getpid(), "error", "can't pipe");
162 exit(ERROR_EXIT);
163 }
164
165 /* since we are a forked process, we can diddle the command string
166 * we were passed -- nobody else is going to use it again, right?
167 *
168 * if a % is present in the command, previous characters are the
169 * command, and subsequent characters are the additional input to
170 * the command. Subsequent %'s will be transformed into newlines,
171 * but that happens later.
172 *
173 * If there are escaped %'s, remove the escape character.
174 */
175 /*local*/{
176 int escaped = FALSE;
177 int ch;
178 char *p;
179
180 for (input_data = p = e->cmd;
181 (ch = *input_data) != '\0';
182 input_data++, p++) {
183 if (p != input_data)
184 *p = ch;
185 if (escaped) {
186 if (ch == '%' || ch == '\\')
187 *--p = ch;
188 escaped = FALSE;
189 continue;
190 }
191 if (ch == '\\') {
192 escaped = TRUE;
193 continue;
194 }
195 if (ch == '%') {
196 *input_data++ = '\0';
197 break;
198 }
199 }
200 *p = '\0';
201 }
202
203 /* fork again, this time so we can exec the user's command.
204 */
205 switch (jobpid = fork()) {
206 case -1:
207 log_it("CRON", getpid(), "error", "can't fork");
208 exit(ERROR_EXIT);
209 /*NOTREACHED*/
210 case 0:
211 Debug(DPROC, ("[%d] grandchild process fork()'ed\n",
212 getpid()))
213
214 if (e->uid == ROOT_UID)
215 Jitter = RootJitter;
216 if (Jitter != 0) {
217 srandom(getpid());
218 sleep(random() % Jitter);
219 }
220
221 /* write a log message. we've waited this long to do it
222 * because it was not until now that we knew the PID that
223 * the actual user command shell was going to get and the
224 * PID is part of the log message.
225 */
226 if ((e->flags & DONT_LOG) == 0) {
227 char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
228
229 log_it(usernm, getpid(), "CMD", x);
230 free(x);
231 }
232
233 /* that's the last thing we'll log. close the log files.
234 */
235 #ifdef SYSLOG
236 closelog();
237 #endif
238
239 /* get new pgrp, void tty, etc.
240 */
241 (void) setsid();
242
243 /* close the pipe ends that we won't use. this doesn't affect
244 * the parent, who has to read and write them; it keeps the
245 * kernel from recording us as a potential client TWICE --
246 * which would keep it from sending SIGPIPE in otherwise
247 * appropriate circumstances.
248 */
249 close(stdin_pipe[WRITE_PIPE]);
250 close(stdout_pipe[READ_PIPE]);
251
252 /* grandchild process. make std{in,out} be the ends of
253 * pipes opened by our daddy; make stderr go to stdout.
254 */
255 close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN);
256 close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT);
257 close(STDERR); dup2(STDOUT, STDERR);
258
259 /* close the pipes we just dup'ed. The resources will remain.
260 */
261 close(stdin_pipe[READ_PIPE]);
262 close(stdout_pipe[WRITE_PIPE]);
263
264 environ = NULL;
265
266 # if defined(LOGIN_CAP)
267 /* Set user's entire context, but note that PATH will
268 * be overridden later
269 */
270 if ((pwd = getpwnam(usernm)) == NULL)
271 pwd = getpwuid(e->uid);
272 lc = NULL;
273 if (pwd != NULL) {
274 if (pwd->pw_dir != NULL
275 && pwd->pw_dir[0] != '\0') {
276 homedir = strdup(pwd->pw_dir);
277 if (homedir == NULL) {
278 warn("strdup");
279 _exit(ERROR_EXIT);
280 }
281 }
282 pwd->pw_gid = e->gid;
283 if (e->class != NULL)
284 lc = login_getclass(e->class);
285 }
286 if (pwd &&
287 setusercontext(lc, pwd, e->uid,
288 LOGIN_SETALL) == 0)
289 (void) endpwent();
290 else {
291 /* fall back to the old method */
292 (void) endpwent();
293 # endif
294 /* set our directory, uid and gid. Set gid first,
295 * since once we set uid, we've lost root privileges.
296 */
297 if (setgid(e->gid) != 0) {
298 log_it(usernm, getpid(),
299 "error", "setgid failed");
300 _exit(ERROR_EXIT);
301 }
302 if (initgroups(usernm, e->gid) != 0) {
303 log_it(usernm, getpid(),
304 "error", "initgroups failed");
305 _exit(ERROR_EXIT);
306 }
307 if (setlogin(usernm) != 0) {
308 log_it(usernm, getpid(),
309 "error", "setlogin failed");
310 _exit(ERROR_EXIT);
311 }
312 if (setuid(e->uid) != 0) {
313 log_it(usernm, getpid(),
314 "error", "setuid failed");
315 _exit(ERROR_EXIT);
316 }
317 /* we aren't root after this..*/
318 #if defined(LOGIN_CAP)
319 }
320 if (lc != NULL)
321 login_close(lc);
322 #endif
323
324 /* For compatibility, we chdir to the value of HOME if it was
325 * specified explicitly in the crontab file, but not if it was
326 * set in the environment by some other mechanism. We chdir to
327 * the homedir given by the pw entry otherwise.
328 *
329 * If !LOGIN_CAP, then HOME is always set in e->envp.
330 *
331 * XXX: probably should also consult PAM.
332 */
333 {
334 char *new_home = env_get("HOME", e->envp);
335 if (new_home != NULL && new_home[0] != '\0')
336 chdir(new_home);
337 else if (homedir != NULL)
338 chdir(homedir);
339 else
340 chdir("/");
341 }
342
343 /* exec the command. Note that SHELL is not respected from
344 * either login.conf or pw_shell, only an explicit setting
345 * in the crontab. (default of _PATH_BSHELL is supplied when
346 * setting up the entry)
347 */
348 {
349 char *shell = env_get("SHELL", e->envp);
350 char **p;
351
352 /* Apply the environment from the entry, overriding
353 * existing values (this will always set LOGNAME and
354 * SHELL). putenv should not fail unless malloc does.
355 */
356 for (p = e->envp; *p; ++p) {
357 if (putenv(*p) != 0) {
358 warn("putenv");
359 _exit(ERROR_EXIT);
360 }
361 }
362
363 /* HOME in login.conf overrides pw, and HOME in the
364 * crontab overrides both. So set pw's value only if
365 * nothing was already set (overwrite==0).
366 */
367 if (homedir != NULL
368 && setenv("HOME", homedir, 0) < 0) {
369 warn("setenv(HOME)");
370 _exit(ERROR_EXIT);
371 }
372
373 /* PATH in login.conf is respected, but the crontab
374 * overrides; set a default value only if nothing
375 * already set.
376 */
377 if (setenv("PATH", _PATH_DEFPATH, 0) < 0) {
378 warn("setenv(PATH)");
379 _exit(ERROR_EXIT);
380 }
381
382 # if DEBUGGING
383 if (DebugFlags & DTEST) {
384 fprintf(stderr,
385 "debug DTEST is on, not exec'ing command.\n");
386 fprintf(stderr,
387 "\tcmd='%s' shell='%s'\n", e->cmd, shell);
388 _exit(OK_EXIT);
389 }
390 # endif /*DEBUGGING*/
391 execl(shell, shell, "-c", e->cmd, (char *)NULL);
392 warn("execl: couldn't exec `%s'", shell);
393 _exit(ERROR_EXIT);
394 }
395 break;
396 default:
397 /* parent process */
398 break;
399 }
400
401 /* middle process, child of original cron, parent of process running
402 * the user's command.
403 */
404
405 Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
406
407 /* close the ends of the pipe that will only be referenced in the
408 * grandchild process...
409 */
410 close(stdin_pipe[READ_PIPE]);
411 close(stdout_pipe[WRITE_PIPE]);
412
413 /*
414 * write, to the pipe connected to child's stdin, any input specified
415 * after a % in the crontab entry. while we copy, convert any
416 * additional %'s to newlines. when done, if some characters were
417 * written and the last one wasn't a newline, write a newline.
418 *
419 * Note that if the input data won't fit into one pipe buffer (2K
420 * or 4K on most BSD systems), and the child doesn't read its stdin,
421 * we would block here. thus we must fork again.
422 */
423
424 if (*input_data && (stdinjob = fork()) == 0) {
425 FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
426 int need_newline = FALSE;
427 int escaped = FALSE;
428 int ch;
429
430 if (out == NULL) {
431 warn("fdopen failed in child2");
432 _exit(ERROR_EXIT);
433 }
434
435 Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
436
437 /* close the pipe we don't use, since we inherited it and
438 * are part of its reference count now.
439 */
440 close(stdout_pipe[READ_PIPE]);
441
442 /* translation:
443 * \% -> %
444 * % -> \n
445 * \x -> \x for all x != %
446 */
447 while ((ch = *input_data++) != '\0') {
448 if (escaped) {
449 if (ch != '%')
450 putc('\\', out);
451 } else {
452 if (ch == '%')
453 ch = '\n';
454 }
455
456 if (!(escaped = (ch == '\\'))) {
457 putc(ch, out);
458 need_newline = (ch != '\n');
459 }
460 }
461 if (escaped)
462 putc('\\', out);
463 if (need_newline)
464 putc('\n', out);
465
466 /* close the pipe, causing an EOF condition. fclose causes
467 * stdin_pipe[WRITE_PIPE] to be closed, too.
468 */
469 fclose(out);
470
471 Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
472 exit(0);
473 }
474
475 /* close the pipe to the grandkiddie's stdin, since its wicked uncle
476 * ernie back there has it open and will close it when he's done.
477 */
478 close(stdin_pipe[WRITE_PIPE]);
479
480 /*
481 * read output from the grandchild. it's stderr has been redirected to
482 * it's stdout, which has been redirected to our pipe. if there is any
483 * output, we'll be mailing it to the user whose crontab this is...
484 * when the grandchild exits, we'll get EOF.
485 */
486
487 Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
488
489 /*local*/{
490 FILE *in = fdopen(stdout_pipe[READ_PIPE], "r");
491 int ch;
492
493 if (in == NULL) {
494 warn("fdopen failed in child");
495 _exit(ERROR_EXIT);
496 }
497
498 mail = NULL;
499
500 ch = getc(in);
501 if (ch != EOF) {
502 Debug(DPROC|DEXT,
503 ("[%d] got data (%x:%c) from grandchild\n",
504 getpid(), ch, ch))
505
506 /* get name of recipient. this is MAILTO if set to a
507 * valid local username; USER otherwise.
508 */
509 if (mailto == NULL) {
510 /* MAILTO not present, set to USER,
511 * unless globally overridden.
512 */
513 if (defmailto)
514 mailto = defmailto;
515 else
516 mailto = usernm;
517 }
518 if (mailto && *mailto == '\0')
519 mailto = NULL;
520
521 /* if we are supposed to be mailing, MAILTO will
522 * be non-NULL. only in this case should we set
523 * up the mail command and subjects and stuff...
524 */
525
526 if (mailto) {
527 char **env;
528 char mailcmd[MAX_COMMAND];
529 char hostname[MAXHOSTNAMELEN];
530
531 if (gethostname(hostname, MAXHOSTNAMELEN) == -1)
532 hostname[0] = '\0';
533 hostname[sizeof(hostname) - 1] = '\0';
534 if (snprintf(mailcmd, sizeof(mailcmd), MAILFMT,
535 MAILARG) >= sizeof(mailcmd)) {
536 warnx("mail command too long");
537 (void) _exit(ERROR_EXIT);
538 }
539 if (!(mail = cron_popen(mailcmd, "w", e, &mailpid))) {
540 warn("%s", mailcmd);
541 (void) _exit(ERROR_EXIT);
542 }
543 if (mailfrom == NULL || *mailfrom == '\0')
544 fprintf(mail, "From: Cron Daemon <%s@%s>\n",
545 usernm, hostname);
546 else
547 fprintf(mail, "From: Cron Daemon <%s>\n",
548 mailfrom);
549 fprintf(mail, "To: %s\n", mailto);
550 fprintf(mail, "Subject: Cron <%s@%s> %s\n",
551 usernm, first_word(hostname, "."),
552 e->cmd);
553 #ifdef MAIL_DATE
554 fprintf(mail, "Date: %s\n",
555 arpadate(&TargetTime));
556 #endif /*MAIL_DATE*/
557 for (env = e->envp; *env; env++)
558 fprintf(mail, "X-Cron-Env: <%s>\n",
559 *env);
560 fprintf(mail, "\n");
561
562 /* this was the first char from the pipe
563 */
564 putc(ch, mail);
565 }
566
567 /* we have to read the input pipe no matter whether
568 * we mail or not, but obviously we only write to
569 * mail pipe if we ARE mailing.
570 */
571
572 while (EOF != (ch = getc(in))) {
573 bytes++;
574 if (mail)
575 putc(ch, mail);
576 }
577 }
578 /*if data from grandchild*/
579
580 Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
581
582 /* also closes stdout_pipe[READ_PIPE] */
583 fclose(in);
584 }
585
586 /* wait for children to die.
587 */
588 if (jobpid > 0) {
589 WAIT_T waiter;
590
591 waiter = wait_on_child(jobpid, "grandchild command job");
592
593 /* If everything went well, and -n was set, _and_ we have mail,
594 * we won't be mailing... so shoot the messenger!
595 */
596 if (WIFEXITED(waiter) && WEXITSTATUS(waiter) == 0
597 && (e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR
598 && mail) {
599 Debug(DPROC, ("[%d] %s executed successfully, mail suppressed\n",
600 getpid(), "grandchild command job"))
601 kill(mailpid, SIGKILL);
602 (void)fclose(mail);
603 mail = NULL;
604 }
605
606 /* only close pipe if we opened it -- i.e., we're
607 * mailing...
608 */
609
610 if (mail) {
611 Debug(DPROC, ("[%d] closing pipe to mail\n",
612 getpid()))
613 /* Note: the pclose will probably see
614 * the termination of the grandchild
615 * in addition to the mail process, since
616 * it (the grandchild) is likely to exit
617 * after closing its stdout.
618 */
619 status = cron_pclose(mail);
620
621 /* if there was output and we could not mail it,
622 * log the facts so the poor user can figure out
623 * what's going on.
624 */
625 if (status) {
626 char buf[MAX_TEMPSTR];
627
628 snprintf(buf, sizeof(buf),
629 "mailed %d byte%s of output but got status 0x%04x\n",
630 bytes, (bytes==1)?"":"s",
631 status);
632 log_it(usernm, getpid(), "MAIL", buf);
633 }
634 }
635 }
636
637 if (*input_data && stdinjob > 0)
638 wait_on_child(stdinjob, "grandchild stdinjob");
639 }
640
641 static WAIT_T
wait_on_child(PID_T childpid,const char * name)642 wait_on_child(PID_T childpid, const char *name)
643 {
644 WAIT_T waiter;
645 PID_T pid;
646
647 Debug(DPROC, ("[%d] waiting for %s (%d) to finish\n",
648 getpid(), name, childpid))
649
650 #ifdef POSIX
651 while ((pid = waitpid(childpid, &waiter, 0)) < 0 && errno == EINTR)
652 #else
653 while ((pid = wait4(childpid, &waiter, 0, NULL)) < 0 && errno == EINTR)
654 #endif
655 ;
656
657 if (pid < OK)
658 return waiter;
659
660 Debug(DPROC, ("[%d] %s (%d) finished, status=%04x",
661 getpid(), name, pid, WEXITSTATUS(waiter)))
662 if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
663 Debug(DPROC, (", dumped core"))
664 Debug(DPROC, ("\n"))
665
666 return waiter;
667 }
668