do_command.c revision 180096
1214501Srpaulo/* Copyright 1988,1990,1993,1994 by Paul Vixie 2214501Srpaulo * All rights reserved 3214501Srpaulo * 4214501Srpaulo * Distribute freely, except: don't remove my name from the source or 5252726Srpaulo * documentation (don't take credit for my work), mark your changes (don't 6252726Srpaulo * get me blamed for your possible bugs), don't alter or remove this 7214501Srpaulo * notice. May be sold if buildable source is provided to buyer. No 8214501Srpaulo * warrantee of any kind, express or implied, is included with this 9214501Srpaulo * software; use at your own risk, responsibility for damages (if any) to 10214501Srpaulo * anyone resulting from the use of this software rests entirely with the 11214501Srpaulo * user. 12214501Srpaulo * 13214501Srpaulo * Send bug reports, bug fixes, enhancements, requests, flames, etc., and 14214501Srpaulo * I'll try to keep a version up to date. I can be reached as follows: 15214501Srpaulo * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul 16214501Srpaulo */ 17214501Srpaulo 18214501Srpaulo#if !defined(lint) && !defined(LINT) 19214501Srpaulostatic const char rcsid[] = 20214501Srpaulo "$FreeBSD: head/usr.sbin/cron/cron/do_command.c 180096 2008-06-29 16:56:18Z marck $"; 21214501Srpaulo#endif 22214501Srpaulo 23214501Srpaulo 24214501Srpaulo#include "cron.h" 25214501Srpaulo#include <sys/signal.h> 26214501Srpaulo#if defined(sequent) 27214501Srpaulo# include <sys/universe.h> 28214501Srpaulo#endif 29214501Srpaulo#if defined(SYSLOG) 30214501Srpaulo# include <syslog.h> 31214501Srpaulo#endif 32214501Srpaulo#if defined(LOGIN_CAP) 33214501Srpaulo# include <login_cap.h> 34214501Srpaulo#endif 35214501Srpaulo#ifdef PAM 36214501Srpaulo# include <security/pam_appl.h> 37214501Srpaulo# include <security/openpam.h> 38214501Srpaulo#endif 39214501Srpaulo 40214501Srpaulo 41214501Srpaulostatic void child_process(entry *, user *), 42214501Srpaulo do_univ(user *); 43214501Srpaulo 44214501Srpaulo 45214501Srpaulovoid 46214501Srpaulodo_command(e, u) 47214501Srpaulo entry *e; 48214501Srpaulo user *u; 49214501Srpaulo{ 50214501Srpaulo Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", 51214501Srpaulo getpid(), e->cmd, u->name, e->uid, e->gid)) 52214501Srpaulo 53214501Srpaulo /* fork to become asynchronous -- parent process is done immediately, 54214501Srpaulo * and continues to run the normal cron code, which means return to 55214501Srpaulo * tick(). the child and grandchild don't leave this function, alive. 56214501Srpaulo * 57214501Srpaulo * vfork() is unsuitable, since we have much to do, and the parent 58214501Srpaulo * needs to be able to run off and fork other processes. 59214501Srpaulo */ 60214501Srpaulo switch (fork()) { 61214501Srpaulo case -1: 62214501Srpaulo log_it("CRON",getpid(),"error","can't fork"); 63214501Srpaulo break; 64214501Srpaulo case 0: 65214501Srpaulo /* child process */ 66214501Srpaulo pidfile_close(pfh); 67214501Srpaulo child_process(e, u); 68214501Srpaulo Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) 69214501Srpaulo _exit(OK_EXIT); 70214501Srpaulo break; 71214501Srpaulo default: 72214501Srpaulo /* parent process */ 73214501Srpaulo break; 74214501Srpaulo } 75214501Srpaulo Debug(DPROC, ("[%d] main process returning to work\n", getpid())) 76214501Srpaulo} 77214501Srpaulo 78214501Srpaulo 79214501Srpaulostatic void 80214501Srpaulochild_process(e, u) 81214501Srpaulo entry *e; 82214501Srpaulo user *u; 83214501Srpaulo{ 84214501Srpaulo int stdin_pipe[2], stdout_pipe[2]; 85214501Srpaulo register char *input_data; 86214501Srpaulo char *usernm, *mailto; 87214501Srpaulo int children = 0; 88214501Srpaulo# if defined(LOGIN_CAP) 89214501Srpaulo struct passwd *pwd; 90214501Srpaulo login_cap_t *lc; 91214501Srpaulo# endif 92214501Srpaulo 93214501Srpaulo Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd)) 94214501Srpaulo 95214501Srpaulo /* mark ourselves as different to PS command watchers by upshifting 96214501Srpaulo * our program name. This has no effect on some kernels. 97214501Srpaulo */ 98214501Srpaulo setproctitle("running job"); 99214501Srpaulo 100214501Srpaulo /* discover some useful and important environment settings 101214501Srpaulo */ 102214501Srpaulo usernm = env_get("LOGNAME", e->envp); 103214501Srpaulo mailto = env_get("MAILTO", e->envp); 104214501Srpaulo 105214501Srpaulo#ifdef PAM 106214501Srpaulo /* use PAM to see if the user's account is available, 107214501Srpaulo * i.e., not locked or expired or whatever. skip this 108214501Srpaulo * for system tasks from /etc/crontab -- they can run 109214501Srpaulo * as any user. 110214501Srpaulo */ 111214501Srpaulo if (strcmp(u->name, SYS_NAME)) { /* not equal */ 112214501Srpaulo pam_handle_t *pamh = NULL; 113214501Srpaulo int pam_err; 114214501Srpaulo struct pam_conv pamc = { 115214501Srpaulo .conv = openpam_nullconv, 116214501Srpaulo .appdata_ptr = NULL 117214501Srpaulo }; 118214501Srpaulo 119214501Srpaulo Debug(DPROC, ("[%d] checking account with PAM\n", getpid())) 120214501Srpaulo 121214501Srpaulo /* u->name keeps crontab owner name while LOGNAME is the name 122214501Srpaulo * of user to run command on behalf of. they should be the 123214501Srpaulo * same for a task from a per-user crontab. 124214501Srpaulo */ 125214501Srpaulo if (strcmp(u->name, usernm)) { 126214501Srpaulo log_it(usernm, getpid(), "username ambiguity", u->name); 127214501Srpaulo exit(ERROR_EXIT); 128214501Srpaulo } 129214501Srpaulo 130214501Srpaulo pam_err = pam_start("cron", usernm, &pamc, &pamh); 131214501Srpaulo if (pam_err != PAM_SUCCESS) { 132214501Srpaulo log_it("CRON", getpid(), "error", "can't start PAM"); 133214501Srpaulo exit(ERROR_EXIT); 134214501Srpaulo } 135214501Srpaulo 136214501Srpaulo pam_err = pam_acct_mgmt(pamh, PAM_SILENT); 137214501Srpaulo /* Expired password shouldn't prevent the job from running. */ 138214501Srpaulo if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD) { 139214501Srpaulo log_it(usernm, getpid(), "USER", "account unavailable"); 140214501Srpaulo exit(ERROR_EXIT); 141214501Srpaulo } 142214501Srpaulo 143214501Srpaulo pam_end(pamh, pam_err); 144214501Srpaulo } 145214501Srpaulo#endif 146214501Srpaulo 147214501Srpaulo#ifdef USE_SIGCHLD 148214501Srpaulo /* our parent is watching for our death by catching SIGCHLD. we 149214501Srpaulo * do not care to watch for our children's deaths this way -- we 150214501Srpaulo * use wait() explictly. so we have to disable the signal (which 151214501Srpaulo * was inherited from the parent). 152214501Srpaulo */ 153214501Srpaulo (void) signal(SIGCHLD, SIG_DFL); 154214501Srpaulo#else 155214501Srpaulo /* on system-V systems, we are ignoring SIGCLD. we have to stop 156214501Srpaulo * ignoring it now or the wait() in cron_pclose() won't work. 157214501Srpaulo * because of this, we have to wait() for our children here, as well. 158214501Srpaulo */ 159214501Srpaulo (void) signal(SIGCLD, SIG_DFL); 160214501Srpaulo#endif /*BSD*/ 161214501Srpaulo 162214501Srpaulo /* create some pipes to talk to our future child 163214501Srpaulo */ 164214501Srpaulo pipe(stdin_pipe); /* child's stdin */ 165214501Srpaulo pipe(stdout_pipe); /* child's stdout */ 166214501Srpaulo 167214501Srpaulo /* since we are a forked process, we can diddle the command string 168214501Srpaulo * we were passed -- nobody else is going to use it again, right? 169214501Srpaulo * 170214501Srpaulo * if a % is present in the command, previous characters are the 171214501Srpaulo * command, and subsequent characters are the additional input to 172214501Srpaulo * the command. Subsequent %'s will be transformed into newlines, 173214501Srpaulo * but that happens later. 174214501Srpaulo * 175214501Srpaulo * If there are escaped %'s, remove the escape character. 176214501Srpaulo */ 177214501Srpaulo /*local*/{ 178214501Srpaulo register int escaped = FALSE; 179252726Srpaulo register int ch; 180252726Srpaulo register char *p; 181214501Srpaulo 182214501Srpaulo for (input_data = p = e->cmd; (ch = *input_data); 183214501Srpaulo input_data++, p++) { 184214501Srpaulo if (p != input_data) 185246875Sdim *p = ch; 186214501Srpaulo if (escaped) { 187214501Srpaulo if (ch == '%' || ch == '\\') 188214501Srpaulo *--p = ch; 189214501Srpaulo escaped = FALSE; 190214501Srpaulo continue; 191214501Srpaulo } 192214501Srpaulo if (ch == '\\') { 193214501Srpaulo escaped = TRUE; 194214501Srpaulo continue; 195214501Srpaulo } 196214501Srpaulo if (ch == '%') { 197214501Srpaulo *input_data++ = '\0'; 198214501Srpaulo break; 199214501Srpaulo } 200214501Srpaulo } 201214501Srpaulo *p = '\0'; 202214501Srpaulo } 203214501Srpaulo 204214501Srpaulo /* fork again, this time so we can exec the user's command. 205214501Srpaulo */ 206214501Srpaulo switch (vfork()) { 207214501Srpaulo case -1: 208214501Srpaulo log_it("CRON",getpid(),"error","can't vfork"); 209214501Srpaulo exit(ERROR_EXIT); 210214501Srpaulo /*NOTREACHED*/ 211214501Srpaulo case 0: 212214501Srpaulo Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n", 213214501Srpaulo getpid())) 214214501Srpaulo 215214501Srpaulo if (e->uid == ROOT_UID) 216214501Srpaulo Jitter = RootJitter; 217214501Srpaulo if (Jitter != 0) { 218214501Srpaulo srandom(getpid()); 219214501Srpaulo sleep(random() % Jitter); 220214501Srpaulo } 221214501Srpaulo 222214501Srpaulo /* write a log message. we've waited this long to do it 223214501Srpaulo * because it was not until now that we knew the PID that 224214501Srpaulo * the actual user command shell was going to get and the 225214501Srpaulo * PID is part of the log message. 226214501Srpaulo */ 227214501Srpaulo /*local*/{ 228214501Srpaulo char *x = mkprints((u_char *)e->cmd, strlen(e->cmd)); 229214501Srpaulo 230214501Srpaulo log_it(usernm, getpid(), "CMD", x); 231214501Srpaulo free(x); 232214501Srpaulo } 233214501Srpaulo 234214501Srpaulo /* that's the last thing we'll log. close the log files. 235214501Srpaulo */ 236214501Srpaulo#ifdef SYSLOG 237214501Srpaulo closelog(); 238214501Srpaulo#endif 239214501Srpaulo 240214501Srpaulo /* get new pgrp, void tty, etc. 241214501Srpaulo */ 242214501Srpaulo (void) setsid(); 243214501Srpaulo 244214501Srpaulo /* close the pipe ends that we won't use. this doesn't affect 245214501Srpaulo * the parent, who has to read and write them; it keeps the 246214501Srpaulo * kernel from recording us as a potential client TWICE -- 247214501Srpaulo * which would keep it from sending SIGPIPE in otherwise 248214501Srpaulo * appropriate circumstances. 249214501Srpaulo */ 250214501Srpaulo close(stdin_pipe[WRITE_PIPE]); 251214501Srpaulo close(stdout_pipe[READ_PIPE]); 252214501Srpaulo 253214501Srpaulo /* grandchild process. make std{in,out} be the ends of 254214501Srpaulo * pipes opened by our daddy; make stderr go to stdout. 255214501Srpaulo */ 256214501Srpaulo close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN); 257214501Srpaulo close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT); 258214501Srpaulo close(STDERR); dup2(STDOUT, STDERR); 259214501Srpaulo 260214501Srpaulo /* close the pipes we just dup'ed. The resources will remain. 261214501Srpaulo */ 262214501Srpaulo close(stdin_pipe[READ_PIPE]); 263214501Srpaulo close(stdout_pipe[WRITE_PIPE]); 264214501Srpaulo 265214501Srpaulo /* set our login universe. Do this in the grandchild 266214501Srpaulo * so that the child can invoke /usr/lib/sendmail 267214501Srpaulo * without surprises. 268214501Srpaulo */ 269214501Srpaulo do_univ(u); 270214501Srpaulo 271214501Srpaulo# if defined(LOGIN_CAP) 272214501Srpaulo /* Set user's entire context, but skip the environment 273214501Srpaulo * as cron provides a separate interface for this 274214501Srpaulo */ 275214501Srpaulo if ((pwd = getpwnam(usernm)) == NULL) 276214501Srpaulo pwd = getpwuid(e->uid); 277214501Srpaulo lc = NULL; 278214501Srpaulo if (pwd != NULL) { 279214501Srpaulo pwd->pw_gid = e->gid; 280214501Srpaulo if (e->class != NULL) 281214501Srpaulo lc = login_getclass(e->class); 282214501Srpaulo } 283214501Srpaulo if (pwd && 284214501Srpaulo setusercontext(lc, pwd, e->uid, 285214501Srpaulo LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0) 286214501Srpaulo (void) endpwent(); 287214501Srpaulo else { 288 /* fall back to the old method */ 289 (void) endpwent(); 290# endif 291 /* set our directory, uid and gid. Set gid first, 292 * since once we set uid, we've lost root privileges. 293 */ 294 if (setgid(e->gid) != 0) { 295 log_it(usernm, getpid(), 296 "error", "setgid failed"); 297 exit(ERROR_EXIT); 298 } 299# if defined(BSD) 300 if (initgroups(usernm, e->gid) != 0) { 301 log_it(usernm, getpid(), 302 "error", "initgroups failed"); 303 exit(ERROR_EXIT); 304 } 305# endif 306 if (setlogin(usernm) != 0) { 307 log_it(usernm, getpid(), 308 "error", "setlogin failed"); 309 exit(ERROR_EXIT); 310 } 311 if (setuid(e->uid) != 0) { 312 log_it(usernm, getpid(), 313 "error", "setuid failed"); 314 exit(ERROR_EXIT); 315 } 316 /* we aren't root after this..*/ 317#if defined(LOGIN_CAP) 318 } 319 if (lc != NULL) 320 login_close(lc); 321#endif 322 chdir(env_get("HOME", e->envp)); 323 324 /* exec the command. 325 */ 326 { 327 char *shell = env_get("SHELL", e->envp); 328 329# if DEBUGGING 330 if (DebugFlags & DTEST) { 331 fprintf(stderr, 332 "debug DTEST is on, not exec'ing command.\n"); 333 fprintf(stderr, 334 "\tcmd='%s' shell='%s'\n", e->cmd, shell); 335 _exit(OK_EXIT); 336 } 337# endif /*DEBUGGING*/ 338 execle(shell, shell, "-c", e->cmd, (char *)0, e->envp); 339 warn("execl: couldn't exec `%s'", shell); 340 _exit(ERROR_EXIT); 341 } 342 break; 343 default: 344 /* parent process */ 345 break; 346 } 347 348 children++; 349 350 /* middle process, child of original cron, parent of process running 351 * the user's command. 352 */ 353 354 Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid())) 355 356 /* close the ends of the pipe that will only be referenced in the 357 * grandchild process... 358 */ 359 close(stdin_pipe[READ_PIPE]); 360 close(stdout_pipe[WRITE_PIPE]); 361 362 /* 363 * write, to the pipe connected to child's stdin, any input specified 364 * after a % in the crontab entry. while we copy, convert any 365 * additional %'s to newlines. when done, if some characters were 366 * written and the last one wasn't a newline, write a newline. 367 * 368 * Note that if the input data won't fit into one pipe buffer (2K 369 * or 4K on most BSD systems), and the child doesn't read its stdin, 370 * we would block here. thus we must fork again. 371 */ 372 373 if (*input_data && fork() == 0) { 374 register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); 375 register int need_newline = FALSE; 376 register int escaped = FALSE; 377 register int ch; 378 379 if (out == NULL) { 380 warn("fdopen failed in child2"); 381 _exit(ERROR_EXIT); 382 } 383 384 Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) 385 386 /* close the pipe we don't use, since we inherited it and 387 * are part of its reference count now. 388 */ 389 close(stdout_pipe[READ_PIPE]); 390 391 /* translation: 392 * \% -> % 393 * % -> \n 394 * \x -> \x for all x != % 395 */ 396 while ((ch = *input_data++)) { 397 if (escaped) { 398 if (ch != '%') 399 putc('\\', out); 400 } else { 401 if (ch == '%') 402 ch = '\n'; 403 } 404 405 if (!(escaped = (ch == '\\'))) { 406 putc(ch, out); 407 need_newline = (ch != '\n'); 408 } 409 } 410 if (escaped) 411 putc('\\', out); 412 if (need_newline) 413 putc('\n', out); 414 415 /* close the pipe, causing an EOF condition. fclose causes 416 * stdin_pipe[WRITE_PIPE] to be closed, too. 417 */ 418 fclose(out); 419 420 Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid())) 421 exit(0); 422 } 423 424 /* close the pipe to the grandkiddie's stdin, since its wicked uncle 425 * ernie back there has it open and will close it when he's done. 426 */ 427 close(stdin_pipe[WRITE_PIPE]); 428 429 children++; 430 431 /* 432 * read output from the grandchild. it's stderr has been redirected to 433 * it's stdout, which has been redirected to our pipe. if there is any 434 * output, we'll be mailing it to the user whose crontab this is... 435 * when the grandchild exits, we'll get EOF. 436 */ 437 438 Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) 439 440 /*local*/{ 441 register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); 442 register int ch; 443 444 if (in == NULL) { 445 warn("fdopen failed in child"); 446 _exit(ERROR_EXIT); 447 } 448 449 ch = getc(in); 450 if (ch != EOF) { 451 register FILE *mail; 452 register int bytes = 1; 453 int status = 0; 454 455 Debug(DPROC|DEXT, 456 ("[%d] got data (%x:%c) from grandchild\n", 457 getpid(), ch, ch)) 458 459 /* get name of recipient. this is MAILTO if set to a 460 * valid local username; USER otherwise. 461 */ 462 if (mailto == NULL) { 463 /* MAILTO not present, set to USER, 464 * unless globally overriden. 465 */ 466 if (defmailto) 467 mailto = defmailto; 468 else 469 mailto = usernm; 470 } 471 472 /* if we are supposed to be mailing, MAILTO will 473 * be non-NULL. only in this case should we set 474 * up the mail command and subjects and stuff... 475 */ 476 477 if (mailto && *mailto != '\0') { 478 register char **env; 479 auto char mailcmd[MAX_COMMAND]; 480 auto char hostname[MAXHOSTNAMELEN]; 481 482 (void) gethostname(hostname, MAXHOSTNAMELEN); 483 (void) snprintf(mailcmd, sizeof(mailcmd), 484 MAILARGS, MAILCMD); 485 if (!(mail = cron_popen(mailcmd, "w", e))) { 486 warn("%s", MAILCMD); 487 (void) _exit(ERROR_EXIT); 488 } 489 fprintf(mail, "From: %s (Cron Daemon)\n", usernm); 490 fprintf(mail, "To: %s\n", mailto); 491 fprintf(mail, "Subject: Cron <%s@%s> %s\n", 492 usernm, first_word(hostname, "."), 493 e->cmd); 494# if defined(MAIL_DATE) 495 fprintf(mail, "Date: %s\n", 496 arpadate(&TargetTime)); 497# endif /* MAIL_DATE */ 498 for (env = e->envp; *env; env++) 499 fprintf(mail, "X-Cron-Env: <%s>\n", 500 *env); 501 fprintf(mail, "\n"); 502 503 /* this was the first char from the pipe 504 */ 505 putc(ch, mail); 506 } 507 508 /* we have to read the input pipe no matter whether 509 * we mail or not, but obviously we only write to 510 * mail pipe if we ARE mailing. 511 */ 512 513 while (EOF != (ch = getc(in))) { 514 bytes++; 515 if (mailto) 516 putc(ch, mail); 517 } 518 519 /* only close pipe if we opened it -- i.e., we're 520 * mailing... 521 */ 522 523 if (mailto) { 524 Debug(DPROC, ("[%d] closing pipe to mail\n", 525 getpid())) 526 /* Note: the pclose will probably see 527 * the termination of the grandchild 528 * in addition to the mail process, since 529 * it (the grandchild) is likely to exit 530 * after closing its stdout. 531 */ 532 status = cron_pclose(mail); 533 } 534 535 /* if there was output and we could not mail it, 536 * log the facts so the poor user can figure out 537 * what's going on. 538 */ 539 if (mailto && status) { 540 char buf[MAX_TEMPSTR]; 541 542 snprintf(buf, sizeof(buf), 543 "mailed %d byte%s of output but got status 0x%04x\n", 544 bytes, (bytes==1)?"":"s", 545 status); 546 log_it(usernm, getpid(), "MAIL", buf); 547 } 548 549 } /*if data from grandchild*/ 550 551 Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid())) 552 553 fclose(in); /* also closes stdout_pipe[READ_PIPE] */ 554 } 555 556 /* wait for children to die. 557 */ 558 for (; children > 0; children--) 559 { 560 WAIT_T waiter; 561 PID_T pid; 562 563 Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n", 564 getpid(), children)) 565 pid = wait(&waiter); 566 if (pid < OK) { 567 Debug(DPROC, ("[%d] no more grandchildren--mail written?\n", 568 getpid())) 569 break; 570 } 571 Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x", 572 getpid(), pid, WEXITSTATUS(waiter))) 573 if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) 574 Debug(DPROC, (", dumped core")) 575 Debug(DPROC, ("\n")) 576 } 577} 578 579 580static void 581do_univ(u) 582 user *u; 583{ 584#if defined(sequent) 585/* Dynix (Sequent) hack to put the user associated with 586 * the passed user structure into the ATT universe if 587 * necessary. We have to dig the gecos info out of 588 * the user's password entry to see if the magic 589 * "universe(att)" string is present. 590 */ 591 592 struct passwd *p; 593 char *s; 594 int i; 595 596 p = getpwuid(u->uid); 597 (void) endpwent(); 598 599 if (p == NULL) 600 return; 601 602 s = p->pw_gecos; 603 604 for (i = 0; i < 4; i++) 605 { 606 if ((s = strchr(s, ',')) == NULL) 607 return; 608 s++; 609 } 610 if (strcmp(s, "universe(att)")) 611 return; 612 613 (void) universe(U_ATT); 614#endif 615} 616