1 /* Copyright 1988,1990,1993,1994 by Paul Vixie 2 * All rights reserved 3 * 4 * Distribute freely, except: don't remove my name from the source or 5 * documentation (don't take credit for my work), mark your changes (don't 6 * get me blamed for your possible bugs), don't alter or remove this 7 * notice. May be sold if buildable source is provided to buyer. No 8 * warrantee of any kind, express or implied, is included with this 9 * software; use at your own risk, responsibility for damages (if any) to 10 * anyone resulting from the use of this software rests entirely with the 11 * user. 12 * 13 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and 14 * I'll try to keep a version up to date. I can be reached as follows: 15 * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul 16 */ 17 18 #if !defined(lint) && !defined(LINT) 19 static const char rcsid[] = 20 "$FreeBSD$"; 21 #endif 22 23 24 #include "cron.h" 25 #include <sys/signal.h> 26 #if defined(sequent) 27 # include <sys/universe.h> 28 #endif 29 #if defined(SYSLOG) 30 # include <syslog.h> 31 #endif 32 #if defined(LOGIN_CAP) 33 # include <login_cap.h> 34 #endif 35 #ifdef PAM 36 # include <security/pam_appl.h> 37 # include <security/openpam.h> 38 #endif 39 40 41 static void child_process(entry *, user *), 42 do_univ(user *); 43 44 45 void 46 do_command(e, u) 47 entry *e; 48 user *u; 49 { 50 pid_t pid; 51 52 Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", 53 getpid(), e->cmd, u->name, e->uid, e->gid)) 54 55 /* fork to become asynchronous -- parent process is done immediately, 56 * and continues to run the normal cron code, which means return to 57 * tick(). the child and grandchild don't leave this function, alive. 58 * 59 * vfork() is unsuitable, since we have much to do, and the parent 60 * needs to be able to run off and fork other processes. 61 */ 62 switch ((pid = fork())) { 63 case -1: 64 log_it("CRON",getpid(),"error","can't fork"); 65 if (e->flags & INTERVAL) 66 e->lastexit = time(NULL); 67 break; 68 case 0: 69 /* child process */ 70 pidfile_close(pfh); 71 child_process(e, u); 72 Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) 73 _exit(OK_EXIT); 74 break; 75 default: 76 /* parent process */ 77 Debug(DPROC, ("[%d] main process forked child #%d, " 78 "returning to work\n", getpid(), pid)) 79 if (e->flags & INTERVAL) { 80 e->lastexit = 0; 81 e->child = pid; 82 } 83 break; 84 } 85 Debug(DPROC, ("[%d] main process returning to work\n", getpid())) 86 } 87 88 89 static void 90 child_process(e, u) 91 entry *e; 92 user *u; 93 { 94 int stdin_pipe[2], stdout_pipe[2]; 95 register char *input_data; 96 char *usernm, *mailto; 97 int children = 0; 98 # if defined(LOGIN_CAP) 99 struct passwd *pwd; 100 login_cap_t *lc; 101 # endif 102 103 Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd)) 104 105 /* mark ourselves as different to PS command watchers by upshifting 106 * our program name. This has no effect on some kernels. 107 */ 108 setproctitle("running job"); 109 110 /* discover some useful and important environment settings 111 */ 112 usernm = env_get("LOGNAME", e->envp); 113 mailto = env_get("MAILTO", e->envp); 114 115 #ifdef PAM 116 /* use PAM to see if the user's account is available, 117 * i.e., not locked or expired or whatever. skip this 118 * for system tasks from /etc/crontab -- they can run 119 * as any user. 120 */ 121 if (strcmp(u->name, SYS_NAME)) { /* not equal */ 122 pam_handle_t *pamh = NULL; 123 int pam_err; 124 struct pam_conv pamc = { 125 .conv = openpam_nullconv, 126 .appdata_ptr = NULL 127 }; 128 129 Debug(DPROC, ("[%d] checking account with PAM\n", getpid())) 130 131 /* u->name keeps crontab owner name while LOGNAME is the name 132 * of user to run command on behalf of. they should be the 133 * same for a task from a per-user crontab. 134 */ 135 if (strcmp(u->name, usernm)) { 136 log_it(usernm, getpid(), "username ambiguity", u->name); 137 exit(ERROR_EXIT); 138 } 139 140 pam_err = pam_start("cron", usernm, &pamc, &pamh); 141 if (pam_err != PAM_SUCCESS) { 142 log_it("CRON", getpid(), "error", "can't start PAM"); 143 exit(ERROR_EXIT); 144 } 145 146 pam_err = pam_acct_mgmt(pamh, PAM_SILENT); 147 /* Expired password shouldn't prevent the job from running. */ 148 if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD) { 149 log_it(usernm, getpid(), "USER", "account unavailable"); 150 exit(ERROR_EXIT); 151 } 152 153 pam_end(pamh, pam_err); 154 } 155 #endif 156 157 #ifdef USE_SIGCHLD 158 /* our parent is watching for our death by catching SIGCHLD. we 159 * do not care to watch for our children's deaths this way -- we 160 * use wait() explicitly. so we have to disable the signal (which 161 * was inherited from the parent). 162 */ 163 (void) signal(SIGCHLD, SIG_DFL); 164 #else 165 /* on system-V systems, we are ignoring SIGCLD. we have to stop 166 * ignoring it now or the wait() in cron_pclose() won't work. 167 * because of this, we have to wait() for our children here, as well. 168 */ 169 (void) signal(SIGCLD, SIG_DFL); 170 #endif /*BSD*/ 171 172 /* create some pipes to talk to our future child 173 */ 174 if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) { 175 log_it("CRON", getpid(), "error", "can't pipe"); 176 exit(ERROR_EXIT); 177 } 178 179 /* since we are a forked process, we can diddle the command string 180 * we were passed -- nobody else is going to use it again, right? 181 * 182 * if a % is present in the command, previous characters are the 183 * command, and subsequent characters are the additional input to 184 * the command. Subsequent %'s will be transformed into newlines, 185 * but that happens later. 186 * 187 * If there are escaped %'s, remove the escape character. 188 */ 189 /*local*/{ 190 register int escaped = FALSE; 191 register int ch; 192 register char *p; 193 194 for (input_data = p = e->cmd; (ch = *input_data); 195 input_data++, p++) { 196 if (p != input_data) 197 *p = ch; 198 if (escaped) { 199 if (ch == '%' || ch == '\\') 200 *--p = ch; 201 escaped = FALSE; 202 continue; 203 } 204 if (ch == '\\') { 205 escaped = TRUE; 206 continue; 207 } 208 if (ch == '%') { 209 *input_data++ = '\0'; 210 break; 211 } 212 } 213 *p = '\0'; 214 } 215 216 /* fork again, this time so we can exec the user's command. 217 */ 218 switch (vfork()) { 219 case -1: 220 log_it("CRON",getpid(),"error","can't vfork"); 221 exit(ERROR_EXIT); 222 /*NOTREACHED*/ 223 case 0: 224 Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n", 225 getpid())) 226 227 if (e->uid == ROOT_UID) 228 Jitter = RootJitter; 229 if (Jitter != 0) { 230 srandom(getpid()); 231 sleep(random() % Jitter); 232 } 233 234 /* write a log message. we've waited this long to do it 235 * because it was not until now that we knew the PID that 236 * the actual user command shell was going to get and the 237 * PID is part of the log message. 238 */ 239 /*local*/{ 240 char *x = mkprints((u_char *)e->cmd, strlen(e->cmd)); 241 242 log_it(usernm, getpid(), "CMD", x); 243 free(x); 244 } 245 246 /* that's the last thing we'll log. close the log files. 247 */ 248 #ifdef SYSLOG 249 closelog(); 250 #endif 251 252 /* get new pgrp, void tty, etc. 253 */ 254 (void) setsid(); 255 256 /* close the pipe ends that we won't use. this doesn't affect 257 * the parent, who has to read and write them; it keeps the 258 * kernel from recording us as a potential client TWICE -- 259 * which would keep it from sending SIGPIPE in otherwise 260 * appropriate circumstances. 261 */ 262 close(stdin_pipe[WRITE_PIPE]); 263 close(stdout_pipe[READ_PIPE]); 264 265 /* grandchild process. make std{in,out} be the ends of 266 * pipes opened by our daddy; make stderr go to stdout. 267 */ 268 close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN); 269 close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT); 270 close(STDERR); dup2(STDOUT, STDERR); 271 272 /* close the pipes we just dup'ed. The resources will remain. 273 */ 274 close(stdin_pipe[READ_PIPE]); 275 close(stdout_pipe[WRITE_PIPE]); 276 277 /* set our login universe. Do this in the grandchild 278 * so that the child can invoke /usr/lib/sendmail 279 * without surprises. 280 */ 281 do_univ(u); 282 283 # if defined(LOGIN_CAP) 284 /* Set user's entire context, but skip the environment 285 * as cron provides a separate interface for this 286 */ 287 if ((pwd = getpwnam(usernm)) == NULL) 288 pwd = getpwuid(e->uid); 289 lc = NULL; 290 if (pwd != NULL) { 291 pwd->pw_gid = e->gid; 292 if (e->class != NULL) 293 lc = login_getclass(e->class); 294 } 295 if (pwd && 296 setusercontext(lc, pwd, e->uid, 297 LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0) 298 (void) endpwent(); 299 else { 300 /* fall back to the old method */ 301 (void) endpwent(); 302 # endif 303 /* set our directory, uid and gid. Set gid first, 304 * since once we set uid, we've lost root privileges. 305 */ 306 if (setgid(e->gid) != 0) { 307 log_it(usernm, getpid(), 308 "error", "setgid failed"); 309 exit(ERROR_EXIT); 310 } 311 # if defined(BSD) 312 if (initgroups(usernm, e->gid) != 0) { 313 log_it(usernm, getpid(), 314 "error", "initgroups failed"); 315 exit(ERROR_EXIT); 316 } 317 # endif 318 if (setlogin(usernm) != 0) { 319 log_it(usernm, getpid(), 320 "error", "setlogin failed"); 321 exit(ERROR_EXIT); 322 } 323 if (setuid(e->uid) != 0) { 324 log_it(usernm, getpid(), 325 "error", "setuid failed"); 326 exit(ERROR_EXIT); 327 } 328 /* we aren't root after this..*/ 329 #if defined(LOGIN_CAP) 330 } 331 if (lc != NULL) 332 login_close(lc); 333 #endif 334 chdir(env_get("HOME", e->envp)); 335 336 /* exec the command. 337 */ 338 { 339 char *shell = env_get("SHELL", e->envp); 340 341 # if DEBUGGING 342 if (DebugFlags & DTEST) { 343 fprintf(stderr, 344 "debug DTEST is on, not exec'ing command.\n"); 345 fprintf(stderr, 346 "\tcmd='%s' shell='%s'\n", e->cmd, shell); 347 _exit(OK_EXIT); 348 } 349 # endif /*DEBUGGING*/ 350 execle(shell, shell, "-c", e->cmd, (char *)NULL, 351 e->envp); 352 warn("execle: couldn't exec `%s'", shell); 353 _exit(ERROR_EXIT); 354 } 355 break; 356 default: 357 /* parent process */ 358 break; 359 } 360 361 children++; 362 363 /* middle process, child of original cron, parent of process running 364 * the user's command. 365 */ 366 367 Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid())) 368 369 /* close the ends of the pipe that will only be referenced in the 370 * grandchild process... 371 */ 372 close(stdin_pipe[READ_PIPE]); 373 close(stdout_pipe[WRITE_PIPE]); 374 375 /* 376 * write, to the pipe connected to child's stdin, any input specified 377 * after a % in the crontab entry. while we copy, convert any 378 * additional %'s to newlines. when done, if some characters were 379 * written and the last one wasn't a newline, write a newline. 380 * 381 * Note that if the input data won't fit into one pipe buffer (2K 382 * or 4K on most BSD systems), and the child doesn't read its stdin, 383 * we would block here. thus we must fork again. 384 */ 385 386 if (*input_data && fork() == 0) { 387 register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); 388 register int need_newline = FALSE; 389 register int escaped = FALSE; 390 register int ch; 391 392 if (out == NULL) { 393 warn("fdopen failed in child2"); 394 _exit(ERROR_EXIT); 395 } 396 397 Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) 398 399 /* close the pipe we don't use, since we inherited it and 400 * are part of its reference count now. 401 */ 402 close(stdout_pipe[READ_PIPE]); 403 404 /* translation: 405 * \% -> % 406 * % -> \n 407 * \x -> \x for all x != % 408 */ 409 while ((ch = *input_data++)) { 410 if (escaped) { 411 if (ch != '%') 412 putc('\\', out); 413 } else { 414 if (ch == '%') 415 ch = '\n'; 416 } 417 418 if (!(escaped = (ch == '\\'))) { 419 putc(ch, out); 420 need_newline = (ch != '\n'); 421 } 422 } 423 if (escaped) 424 putc('\\', out); 425 if (need_newline) 426 putc('\n', out); 427 428 /* close the pipe, causing an EOF condition. fclose causes 429 * stdin_pipe[WRITE_PIPE] to be closed, too. 430 */ 431 fclose(out); 432 433 Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid())) 434 exit(0); 435 } 436 437 /* close the pipe to the grandkiddie's stdin, since its wicked uncle 438 * ernie back there has it open and will close it when he's done. 439 */ 440 close(stdin_pipe[WRITE_PIPE]); 441 442 children++; 443 444 /* 445 * read output from the grandchild. it's stderr has been redirected to 446 * it's stdout, which has been redirected to our pipe. if there is any 447 * output, we'll be mailing it to the user whose crontab this is... 448 * when the grandchild exits, we'll get EOF. 449 */ 450 451 Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) 452 453 /*local*/{ 454 register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); 455 register int ch; 456 457 if (in == NULL) { 458 warn("fdopen failed in child"); 459 _exit(ERROR_EXIT); 460 } 461 462 ch = getc(in); 463 if (ch != EOF) { 464 register FILE *mail; 465 register int bytes = 1; 466 int status = 0; 467 468 Debug(DPROC|DEXT, 469 ("[%d] got data (%x:%c) from grandchild\n", 470 getpid(), ch, ch)) 471 472 /* get name of recipient. this is MAILTO if set to a 473 * valid local username; USER otherwise. 474 */ 475 if (mailto == NULL) { 476 /* MAILTO not present, set to USER, 477 * unless globally overriden. 478 */ 479 if (defmailto) 480 mailto = defmailto; 481 else 482 mailto = usernm; 483 } 484 if (mailto && *mailto == '\0') 485 mailto = NULL; 486 487 /* if we are supposed to be mailing, MAILTO will 488 * be non-NULL. only in this case should we set 489 * up the mail command and subjects and stuff... 490 */ 491 492 if (mailto) { 493 register char **env; 494 auto char mailcmd[MAX_COMMAND]; 495 auto char hostname[MAXHOSTNAMELEN]; 496 497 if (gethostname(hostname, MAXHOSTNAMELEN) == -1) 498 hostname[0] = '\0'; 499 hostname[sizeof(hostname) - 1] = '\0'; 500 (void) snprintf(mailcmd, sizeof(mailcmd), 501 MAILARGS, MAILCMD); 502 if (!(mail = cron_popen(mailcmd, "w", e))) { 503 warn("%s", MAILCMD); 504 (void) _exit(ERROR_EXIT); 505 } 506 fprintf(mail, "From: Cron Daemon <%s@%s>\n", 507 usernm, hostname); 508 fprintf(mail, "To: %s\n", mailto); 509 fprintf(mail, "Subject: Cron <%s@%s> %s\n", 510 usernm, first_word(hostname, "."), 511 e->cmd); 512 # if defined(MAIL_DATE) 513 fprintf(mail, "Date: %s\n", 514 arpadate(&TargetTime)); 515 # endif /* MAIL_DATE */ 516 for (env = e->envp; *env; env++) 517 fprintf(mail, "X-Cron-Env: <%s>\n", 518 *env); 519 fprintf(mail, "\n"); 520 521 /* this was the first char from the pipe 522 */ 523 putc(ch, mail); 524 } 525 526 /* we have to read the input pipe no matter whether 527 * we mail or not, but obviously we only write to 528 * mail pipe if we ARE mailing. 529 */ 530 531 while (EOF != (ch = getc(in))) { 532 bytes++; 533 if (mailto) 534 putc(ch, mail); 535 } 536 537 /* only close pipe if we opened it -- i.e., we're 538 * mailing... 539 */ 540 541 if (mailto) { 542 Debug(DPROC, ("[%d] closing pipe to mail\n", 543 getpid())) 544 /* Note: the pclose will probably see 545 * the termination of the grandchild 546 * in addition to the mail process, since 547 * it (the grandchild) is likely to exit 548 * after closing its stdout. 549 */ 550 status = cron_pclose(mail); 551 } 552 553 /* if there was output and we could not mail it, 554 * log the facts so the poor user can figure out 555 * what's going on. 556 */ 557 if (mailto && status) { 558 char buf[MAX_TEMPSTR]; 559 560 snprintf(buf, sizeof(buf), 561 "mailed %d byte%s of output but got status 0x%04x\n", 562 bytes, (bytes==1)?"":"s", 563 status); 564 log_it(usernm, getpid(), "MAIL", buf); 565 } 566 567 } /*if data from grandchild*/ 568 569 Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid())) 570 571 fclose(in); /* also closes stdout_pipe[READ_PIPE] */ 572 } 573 574 /* wait for children to die. 575 */ 576 for (; children > 0; children--) 577 { 578 WAIT_T waiter; 579 PID_T pid; 580 581 Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n", 582 getpid(), children)) 583 pid = wait(&waiter); 584 if (pid < OK) { 585 Debug(DPROC, ("[%d] no more grandchildren--mail written?\n", 586 getpid())) 587 break; 588 } 589 Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x", 590 getpid(), pid, WEXITSTATUS(waiter))) 591 if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) 592 Debug(DPROC, (", dumped core")) 593 Debug(DPROC, ("\n")) 594 } 595 } 596 597 598 static void 599 do_univ(u) 600 user *u; 601 { 602 #if defined(sequent) 603 /* Dynix (Sequent) hack to put the user associated with 604 * the passed user structure into the ATT universe if 605 * necessary. We have to dig the gecos info out of 606 * the user's password entry to see if the magic 607 * "universe(att)" string is present. 608 */ 609 610 struct passwd *p; 611 char *s; 612 int i; 613 614 p = getpwuid(u->uid); 615 (void) endpwent(); 616 617 if (p == NULL) 618 return; 619 620 s = p->pw_gecos; 621 622 for (i = 0; i < 4; i++) 623 { 624 if ((s = strchr(s, ',')) == NULL) 625 return; 626 s++; 627 } 628 if (strcmp(s, "universe(att)")) 629 return; 630 631 (void) universe(U_ATT); 632 #endif 633 } 634