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