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) 19static const char rcsid[] = 20 "$FreeBSD: src/usr.sbin/cron/cron/do_command.c,v 1.26 2006/06/11 21:13:49 maxim Exp $"; 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 36#ifdef __APPLE__ 37#include <stdlib.h> 38#include <IOKit/IOKitLib.h> 39#include <IOKit/pwr_mgt/IOPMLib.h> 40#include <IOKit/pwr_mgt/IOPM.h> 41#include <IOKit/IOReturn.h> 42#include <CoreFoundation/CFArray.h> 43#include <CoreFoundation/CFBase.h> 44#include <CoreFoundation/CFNumber.h> 45#include <CoreFoundation/CFData.h> 46#include <CoreFoundation/CoreFoundation.h> 47#include <mach/mach_init.h> /* for bootstrap_port */ 48#include <vproc.h> 49#include <vproc_priv.h> 50#include <bootstrap_priv.h> 51#endif /* __APPLE__ */ 52 53static void child_process __P((entry *, user *)), 54 do_univ __P((user *)); 55 56#ifdef __APPLE__ 57extern vproc_err_t _vproc_post_fork_ping(void); 58#endif 59 60void 61do_command(e, u) 62 entry *e; 63 user *u; 64{ 65#ifdef __APPLE__ 66 CFArrayRef cfarray; 67 static mach_port_t master = 0; 68 static io_connect_t pmcon = 0; 69 70 Debug(DPROC, ("[%d] do_command(%s, (%s,%s,%s))\n", 71 getpid(), e->cmd, u->name, e->uname, e->gname)) 72 73 if( e->flags & NOT_BATTERY ) { 74 if( master == 0 ) { 75 IOMasterPort(bootstrap_port, &master); 76 pmcon = IOPMFindPowerManagement(master); 77 } 78 79 if( IOPMCopyBatteryInfo(master, &cfarray) == kIOReturnSuccess) { 80 CFDictionaryRef dict; 81 CFNumberRef cfnum; 82 int flags; 83 84 dict = CFArrayGetValueAtIndex(cfarray, 0); 85 cfnum = CFDictionaryGetValue(dict, CFSTR(kIOBatteryFlagsKey)); 86 CFNumberGetValue(cfnum, kCFNumberLongType, &flags); 87 88 if( !(flags & kIOBatteryChargerConnect) ) { 89 return; 90 } 91 } 92 } 93#else 94 Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", 95 getpid(), e->cmd, u->name, e->uid, e->gid)) 96#endif 97 98 /* fork to become asynchronous -- parent process is done immediately, 99 * and continues to run the normal cron code, which means return to 100 * tick(). the child and grandchild don't leave this function, alive. 101 * 102 * vfork() is unsuitable, since we have much to do, and the parent 103 * needs to be able to run off and fork other processes. 104 */ 105 switch (fork()) { 106 case -1: 107 log_it("CRON",getpid(),"error","can't fork"); 108 break; 109 case 0: 110 /* child process */ 111 pidfile_close(pfh); 112 child_process(e, u); 113 Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) 114 _exit(OK_EXIT); 115 break; 116 default: 117 /* parent process */ 118 break; 119 } 120 Debug(DPROC, ("[%d] main process returning to work\n", getpid())) 121} 122 123 124static void 125child_process(e, u) 126 entry *e; 127 user *u; 128{ 129 int stdin_pipe[2], stdout_pipe[2]; 130 register char *input_data; 131 char *usernm, *mailto; 132#ifdef __APPLE__ 133 uid_t uid = -1; 134 gid_t gid = -1; 135 struct passwd *pwd; 136#endif 137 int children = 0; 138# if defined(LOGIN_CAP) 139 struct passwd *pwd; 140 login_cap_t *lc; 141# endif 142 143 Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd)) 144 145 /* mark ourselves as different to PS command watchers by upshifting 146 * our program name. This has no effect on some kernels. 147 */ 148#ifdef __APPLE__ 149 setprogname("running job"); 150#else 151 setproctitle("running job"); 152#endif 153 154 /* discover some useful and important environment settings 155 */ 156#ifdef __APPLE__ 157 usernm = e->uname; 158#else 159 usernm = env_get("LOGNAME", e->envp); 160#endif 161 mailto = env_get("MAILTO", e->envp); 162 163#ifdef USE_SIGCHLD 164 /* our parent is watching for our death by catching SIGCHLD. we 165 * do not care to watch for our children's deaths this way -- we 166 * use wait() explictly. so we have to disable the signal (which 167 * was inherited from the parent). 168 */ 169 (void) signal(SIGCHLD, SIG_DFL); 170#else 171 /* on system-V systems, we are ignoring SIGCLD. we have to stop 172 * ignoring it now or the wait() in cron_pclose() won't work. 173 * because of this, we have to wait() for our children here, as well. 174 */ 175 (void) signal(SIGCLD, SIG_DFL); 176#endif /*BSD*/ 177 178 /* create some pipes to talk to our future child 179 */ 180 pipe(stdin_pipe); /* child's stdin */ 181 pipe(stdout_pipe); /* child's stdout */ 182 183 /* since we are a forked process, we can diddle the command string 184 * we were passed -- nobody else is going to use it again, right? 185 * 186 * if a % is present in the command, previous characters are the 187 * command, and subsequent characters are the additional input to 188 * the command. Subsequent %'s will be transformed into newlines, 189 * but that happens later. 190 * 191 * If there are escaped %'s, remove the escape character. 192 */ 193 /*local*/{ 194 register int escaped = FALSE; 195 register int ch; 196 register char *p; 197 198 for (input_data = p = e->cmd; (ch = *input_data); 199 input_data++, p++) { 200 if (p != input_data) 201 *p = ch; 202 if (escaped) { 203 if (ch == '%' || ch == '\\') 204 *--p = ch; 205 escaped = FALSE; 206 continue; 207 } 208 if (ch == '\\') { 209 escaped = TRUE; 210 continue; 211 } 212 if (ch == '%') { 213 *input_data++ = '\0'; 214 break; 215 } 216 } 217 *p = '\0'; 218 } 219 220 /* fork again, this time so we can exec the user's command. 221 */ 222#ifdef __APPLE__ 223 switch (fork()) { 224#else 225 switch (vfork()) { 226#endif 227 case -1: 228 log_it("CRON",getpid(),"error","can't vfork"); 229 exit(ERROR_EXIT); 230 /*NOTREACHED*/ 231 case 0: 232 Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n", 233 getpid())) 234 235#ifndef __APPLE__ 236 if (e->uid == ROOT_UID) 237 Jitter = RootJitter; 238 if (Jitter != 0) { 239 srandom(getpid()); 240 sleep(random() % Jitter); 241 } 242#endif 243 244 /* write a log message. we've waited this long to do it 245 * because it was not until now that we knew the PID that 246 * the actual user command shell was going to get and the 247 * PID is part of the log message. 248 */ 249 /*local*/{ 250 char *x = mkprints((u_char *)e->cmd, strlen(e->cmd)); 251 252 log_it(usernm, getpid(), "CMD", x); 253 free(x); 254 } 255 256 /* that's the last thing we'll log. close the log files. 257 */ 258#ifdef SYSLOG 259 closelog(); 260#endif 261 262 /* get new pgrp, void tty, etc. 263 */ 264 (void) setsid(); 265 266 /* close the pipe ends that we won't use. this doesn't affect 267 * the parent, who has to read and write them; it keeps the 268 * kernel from recording us as a potential client TWICE -- 269 * which would keep it from sending SIGPIPE in otherwise 270 * appropriate circumstances. 271 */ 272 close(stdin_pipe[WRITE_PIPE]); 273 close(stdout_pipe[READ_PIPE]); 274 275 /* grandchild process. make std{in,out} be the ends of 276 * pipes opened by our daddy; make stderr go to stdout. 277 */ 278 close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN); 279 close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT); 280 close(STDERR); dup2(STDOUT, STDERR); 281 282 /* close the pipes we just dup'ed. The resources will remain. 283 */ 284 close(stdin_pipe[READ_PIPE]); 285 close(stdout_pipe[WRITE_PIPE]); 286 287 /* set our login universe. Do this in the grandchild 288 * so that the child can invoke /usr/lib/sendmail 289 * without surprises. 290 */ 291 do_univ(u); 292 293#ifdef __APPLE__ 294 /* Set user's entire context, but skip the environment 295 * as cron provides a separate interface for this 296 */ 297 if ((pwd = getpwnam(e->uname))) { 298 char envstr[MAXPATHLEN + sizeof "HOME="]; 299 300 uid = pwd->pw_uid; 301 gid = pwd->pw_gid; 302 303 if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) { 304 warn("user account expired: %s", e->uname); 305 _exit(ERROR_EXIT); 306 } 307 308 sprintf(envstr, "HOME=%s", pwd->pw_dir); 309 e->envp = env_set(e->envp, envstr); 310 if (e->envp == NULL) { 311 warn("env_set(%s)", envstr); 312 _exit(ERROR_EXIT); 313 } 314 } else { 315 warn("getpwnam(\"%s\")", e->uname); 316 _exit(ERROR_EXIT); 317 } 318 319 if (strlen(e->gname) > 0) { 320 struct group *gr = getgrnam(e->gname); 321 if (gr) { 322 gid = gr->gr_gid; 323 } else { 324 warn("getgrnam(\"%s\")", e->gname); 325 _exit(ERROR_EXIT); 326 } 327 } 328 329 /* move to the correct bootstrap */ 330 /* similar to but simpler than pam_launchd */ 331 mach_port_t puc = MACH_PORT_NULL; 332 kern_return_t kr = bootstrap_look_up_per_user(bootstrap_port, NULL, uid, &puc); 333 if (kr != BOOTSTRAP_SUCCESS) { 334 warnx("could not look up per-user bootstrap for uid %u", uid); 335 _exit(ERROR_EXIT); 336 } 337 mach_port_mod_refs(mach_task_self(), bootstrap_port, MACH_PORT_RIGHT_SEND, -1); 338 task_set_bootstrap_port(mach_task_self(), puc); 339 bootstrap_port = puc; 340 if (_vproc_post_fork_ping() != NULL) { 341 warnx("failed to setup exception ports"); 342 _exit(ERROR_EXIT); 343 } 344#endif /* __APPLE__ */ 345# if defined(LOGIN_CAP) 346 /* Set user's entire context, but skip the environment 347 * as cron provides a separate interface for this 348 */ 349 if ((pwd = getpwnam(usernm)) == NULL) 350 pwd = getpwuid(e->uid); 351 lc = NULL; 352 if (pwd != NULL) { 353 pwd->pw_gid = e->gid; 354 if (e->class != NULL) 355 lc = login_getclass(e->class); 356 } 357 if (pwd && 358 setusercontext(lc, pwd, e->uid, 359 LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0) 360 (void) endpwent(); 361 else { 362 /* fall back to the old method */ 363 (void) endpwent(); 364# endif 365 /* set our directory, uid and gid. Set gid first, 366 * since once we set uid, we've lost root privileges. 367 */ 368#ifdef __APPLE__ 369 if (setgid(gid) != 0) { 370#else 371 if (setgid(e->gid) != 0) { 372#endif 373 log_it(usernm, getpid(), 374 "error", "setgid failed"); 375 exit(ERROR_EXIT); 376 } 377# if defined(BSD) 378#ifdef __APPLE__ 379 if (initgroups(usernm, gid) != 0) { 380#else 381 if (initgroups(usernm, e->gid) != 0) { 382#endif 383 log_it(usernm, getpid(), 384 "error", "initgroups failed"); 385 exit(ERROR_EXIT); 386 } 387# endif 388 if (setlogin(usernm) != 0) { 389 log_it(usernm, getpid(), 390 "error", "setlogin failed"); 391 exit(ERROR_EXIT); 392 } 393#ifdef __APPLE__ 394 if (setuid(uid) != 0) { 395#else 396 if (setuid(e->uid) != 0) { 397#endif 398 log_it(usernm, getpid(), 399 "error", "setuid failed"); 400 exit(ERROR_EXIT); 401 } 402 /* we aren't root after this..*/ 403#if defined(LOGIN_CAP) 404 } 405 if (lc != NULL) 406 login_close(lc); 407#endif 408 chdir(env_get("HOME", e->envp)); 409 410 /* exec the command. 411 */ 412 { 413 char *shell = env_get("SHELL", e->envp); 414 415# if DEBUGGING 416 if (DebugFlags & DTEST) { 417 fprintf(stderr, 418 "debug DTEST is on, not exec'ing command.\n"); 419 fprintf(stderr, 420 "\tcmd='%s' shell='%s'\n", e->cmd, shell); 421 _exit(OK_EXIT); 422 } 423# endif /*DEBUGGING*/ 424 execle(shell, shell, "-c", e->cmd, (char *)0, e->envp); 425 warn("execl: couldn't exec `%s'", shell); 426 _exit(ERROR_EXIT); 427 } 428 break; 429 default: 430 /* parent process */ 431 break; 432 } 433 434 children++; 435 436 /* middle process, child of original cron, parent of process running 437 * the user's command. 438 */ 439 440 Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid())) 441 442 /* close the ends of the pipe that will only be referenced in the 443 * grandchild process... 444 */ 445 close(stdin_pipe[READ_PIPE]); 446 close(stdout_pipe[WRITE_PIPE]); 447 448 /* 449 * write, to the pipe connected to child's stdin, any input specified 450 * after a % in the crontab entry. while we copy, convert any 451 * additional %'s to newlines. when done, if some characters were 452 * written and the last one wasn't a newline, write a newline. 453 * 454 * Note that if the input data won't fit into one pipe buffer (2K 455 * or 4K on most BSD systems), and the child doesn't read its stdin, 456 * we would block here. thus we must fork again. 457 */ 458 459 if (*input_data && fork() == 0) { 460 register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); 461 register int need_newline = FALSE; 462 register int escaped = FALSE; 463 register int ch; 464 465 if (out == NULL) { 466 warn("fdopen failed in child2"); 467 _exit(ERROR_EXIT); 468 } 469 470 Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) 471 472 /* close the pipe we don't use, since we inherited it and 473 * are part of its reference count now. 474 */ 475 close(stdout_pipe[READ_PIPE]); 476 477 /* translation: 478 * \% -> % 479 * % -> \n 480 * \x -> \x for all x != % 481 */ 482 while ((ch = *input_data++)) { 483 if (escaped) { 484 if (ch != '%') 485 putc('\\', out); 486 } else { 487 if (ch == '%') 488 ch = '\n'; 489 } 490 491 if (!(escaped = (ch == '\\'))) { 492 putc(ch, out); 493 need_newline = (ch != '\n'); 494 } 495 } 496 if (escaped) 497 putc('\\', out); 498 if (need_newline) 499 putc('\n', out); 500 501 /* close the pipe, causing an EOF condition. fclose causes 502 * stdin_pipe[WRITE_PIPE] to be closed, too. 503 */ 504 fclose(out); 505 506 Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid())) 507 exit(0); 508 } 509 510 /* close the pipe to the grandkiddie's stdin, since its wicked uncle 511 * ernie back there has it open and will close it when he's done. 512 */ 513 close(stdin_pipe[WRITE_PIPE]); 514 515 children++; 516 517 /* 518 * read output from the grandchild. it's stderr has been redirected to 519 * it's stdout, which has been redirected to our pipe. if there is any 520 * output, we'll be mailing it to the user whose crontab this is... 521 * when the grandchild exits, we'll get EOF. 522 */ 523 524 Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) 525 526 /*local*/{ 527 register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); 528 register int ch; 529 530 if (in == NULL) { 531 warn("fdopen failed in child"); 532 _exit(ERROR_EXIT); 533 } 534 535 ch = getc(in); 536 if (ch != EOF) { 537 register FILE *mail = NULL; 538 register int bytes = 1; 539 int status = 0; 540 541 Debug(DPROC|DEXT, 542 ("[%d] got data (%x:%c) from grandchild\n", 543 getpid(), ch, ch)) 544 545 /* get name of recipient. this is MAILTO if set to a 546 * valid local username; USER otherwise. 547 */ 548 if (mailto) { 549 /* MAILTO was present in the environment 550 */ 551 if (!*mailto) { 552 /* ... but it's empty. set to NULL 553 */ 554 mailto = NULL; 555 } 556 } else { 557 /* MAILTO not present, set to USER. 558 */ 559 mailto = usernm; 560 } 561 562 /* if we are supposed to be mailing, MAILTO will 563 * be non-NULL. only in this case should we set 564 * up the mail command and subjects and stuff... 565 */ 566 567 if (mailto) { 568 register char **env; 569 auto char mailcmd[MAX_COMMAND]; 570 auto char hostname[MAXHOSTNAMELEN]; 571 572 (void) gethostname(hostname, MAXHOSTNAMELEN); 573 (void) snprintf(mailcmd, sizeof(mailcmd), 574 MAILARGS, MAILCMD); 575 if (!(mail = cron_popen(mailcmd, "w", e))) { 576 warn("%s", MAILCMD); 577 (void) _exit(ERROR_EXIT); 578 } 579 fprintf(mail, "From: %s (Cron Daemon)\n", usernm); 580 fprintf(mail, "To: %s\n", mailto); 581 fprintf(mail, "Subject: Cron <%s@%s> %s\n", 582 usernm, first_word(hostname, "."), 583 e->cmd); 584# if defined(MAIL_DATE) 585 fprintf(mail, "Date: %s\n", 586 arpadate(&TargetTime)); 587# endif /* MAIL_DATE */ 588 for (env = e->envp; *env; env++) 589 fprintf(mail, "X-Cron-Env: <%s>\n", 590 *env); 591 fprintf(mail, "\n"); 592 593 /* this was the first char from the pipe 594 */ 595 putc(ch, mail); 596 } 597 598 /* we have to read the input pipe no matter whether 599 * we mail or not, but obviously we only write to 600 * mail pipe if we ARE mailing. 601 */ 602 603 while (EOF != (ch = getc(in))) { 604 bytes++; 605 if (mailto) 606 putc(ch, mail); 607 } 608 609 /* only close pipe if we opened it -- i.e., we're 610 * mailing... 611 */ 612 613 if (mailto) { 614 Debug(DPROC, ("[%d] closing pipe to mail\n", 615 getpid())) 616 /* Note: the pclose will probably see 617 * the termination of the grandchild 618 * in addition to the mail process, since 619 * it (the grandchild) is likely to exit 620 * after closing its stdout. 621 */ 622 status = cron_pclose(mail); 623 } 624 625 /* if there was output and we could not mail it, 626 * log the facts so the poor user can figure out 627 * what's going on. 628 */ 629 if (mailto && status) { 630 char buf[MAX_TEMPSTR]; 631 632 snprintf(buf, sizeof(buf), 633 "mailed %d byte%s of output but got status 0x%04x\n", 634 bytes, (bytes==1)?"":"s", 635 status); 636 log_it(usernm, getpid(), "MAIL", buf); 637 } 638 639 } /*if data from grandchild*/ 640 641 Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid())) 642 643 fclose(in); /* also closes stdout_pipe[READ_PIPE] */ 644 } 645 646 /* wait for children to die. 647 */ 648 for (; children > 0; children--) 649 { 650 WAIT_T waiter; 651 PID_T pid; 652 653 Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n", 654 getpid(), children)) 655 pid = wait(&waiter); 656 if (pid < OK) { 657 Debug(DPROC, ("[%d] no more grandchildren--mail written?\n", 658 getpid())) 659 break; 660 } 661 Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x", 662 getpid(), pid, WEXITSTATUS(waiter))) 663 if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) 664 Debug(DPROC, (", dumped core")) 665 Debug(DPROC, ("\n")) 666 } 667} 668 669 670static void 671do_univ(u) 672 user *u; 673{ 674#ifdef __APPLE__ 675 u = u; // avoid unused argument warning 676#endif 677#if defined(sequent) 678/* Dynix (Sequent) hack to put the user associated with 679 * the passed user structure into the ATT universe if 680 * necessary. We have to dig the gecos info out of 681 * the user's password entry to see if the magic 682 * "universe(att)" string is present. 683 */ 684 685 struct passwd *p; 686 char *s; 687 int i; 688 689 p = getpwuid(u->uid); 690 (void) endpwent(); 691 692 if (p == NULL) 693 return; 694 695 s = p->pw_gecos; 696 697 for (i = 0; i < 4; i++) 698 { 699 if ((s = strchr(s, ',')) == NULL) 700 return; 701 s++; 702 } 703 if (strcmp(s, "universe(att)")) 704 return; 705 706 (void) universe(U_ATT); 707#endif 708} 709