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 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 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 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