do_command.c revision 129280
1251881Speter/* Copyright 1988,1990,1993,1994 by Paul Vixie 2251881Speter * All rights reserved 3251881Speter * 4251881Speter * Distribute freely, except: don't remove my name from the source or 5251881Speter * documentation (don't take credit for my work), mark your changes (don't 6251881Speter * get me blamed for your possible bugs), don't alter or remove this 7251881Speter * notice. May be sold if buildable source is provided to buyer. No 8251881Speter * warrantee of any kind, express or implied, is included with this 9251881Speter * software; use at your own risk, responsibility for damages (if any) to 10251881Speter * anyone resulting from the use of this software rests entirely with the 11251881Speter * user. 12251881Speter * 13251881Speter * Send bug reports, bug fixes, enhancements, requests, flames, etc., and 14251881Speter * I'll try to keep a version up to date. I can be reached as follows: 15251881Speter * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul 16251881Speter */ 17251881Speter 18251881Speter#if !defined(lint) && !defined(LINT) 19251881Speterstatic const char rcsid[] = 20251881Speter "$FreeBSD: head/usr.sbin/cron/cron/do_command.c 129280 2004-05-16 19:29:33Z yar $"; 21251881Speter#endif 22251881Speter 23251881Speter 24251881Speter#include "cron.h" 25251881Speter#include <sys/signal.h> 26251881Speter#if defined(sequent) 27251881Speter# include <sys/universe.h> 28251881Speter#endif 29251881Speter#if defined(SYSLOG) 30251881Speter# include <syslog.h> 31251881Speter#endif 32251881Speter#if defined(LOGIN_CAP) 33251881Speter# include <login_cap.h> 34251881Speter#endif 35251881Speter 36251881Speter 37251881Speterstatic void child_process __P((entry *, user *)), 38251881Speter do_univ __P((user *)); 39251881Speter 40251881Speter 41251881Spetervoid 42289180Speterdo_command(e, u) 43251881Speter entry *e; 44251881Speter user *u; 45251881Speter{ 46251881Speter Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", 47289180Speter getpid(), e->cmd, u->name, e->uid, e->gid)) 48251881Speter 49289180Speter /* fork to become asynchronous -- parent process is done immediately, 50251881Speter * and continues to run the normal cron code, which means return to 51251881Speter * tick(). the child and grandchild don't leave this function, alive. 52251881Speter * 53251881Speter * vfork() is unsuitable, since we have much to do, and the parent 54251881Speter * needs to be able to run off and fork other processes. 55251881Speter */ 56251881Speter switch (fork()) { 57289180Speter case -1: 58289180Speter log_it("CRON",getpid(),"error","can't fork"); 59289180Speter break; 60289180Speter case 0: 61289180Speter /* child process */ 62251881Speter acquire_daemonlock(1); 63251881Speter child_process(e, u); 64251881Speter Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) 65251881Speter _exit(OK_EXIT); 66251881Speter break; 67251881Speter default: 68251881Speter /* parent process */ 69251881Speter break; 70251881Speter } 71251881Speter Debug(DPROC, ("[%d] main process returning to work\n", getpid())) 72251881Speter} 73251881Speter 74251881Speter 75251881Speterstatic void 76251881Speterchild_process(e, u) 77251881Speter entry *e; 78251881Speter user *u; 79251881Speter{ 80251881Speter int stdin_pipe[2], stdout_pipe[2]; 81251881Speter register char *input_data; 82251881Speter char *usernm, *mailto; 83251881Speter int children = 0; 84251881Speter# if defined(LOGIN_CAP) 85251881Speter struct passwd *pwd; 86251881Speter login_cap_t *lc; 87251881Speter# endif 88251881Speter 89251881Speter Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd)) 90251881Speter 91251881Speter /* mark ourselves as different to PS command watchers by upshifting 92251881Speter * our program name. This has no effect on some kernels. 93251881Speter */ 94251881Speter setproctitle("running job"); 95251881Speter 96251881Speter /* discover some useful and important environment settings 97251881Speter */ 98251881Speter usernm = env_get("LOGNAME", e->envp); 99251881Speter mailto = env_get("MAILTO", e->envp); 100251881Speter 101251881Speter#ifdef USE_SIGCHLD 102251881Speter /* our parent is watching for our death by catching SIGCHLD. we 103251881Speter * do not care to watch for our children's deaths this way -- we 104251881Speter * use wait() explictly. so we have to disable the signal (which 105251881Speter * was inherited from the parent). 106251881Speter */ 107251881Speter (void) signal(SIGCHLD, SIG_DFL); 108251881Speter#else 109251881Speter /* on system-V systems, we are ignoring SIGCLD. we have to stop 110289180Speter * ignoring it now or the wait() in cron_pclose() won't work. 111251881Speter * because of this, we have to wait() for our children here, as well. 112251881Speter */ 113251881Speter (void) signal(SIGCLD, SIG_DFL); 114251881Speter#endif /*BSD*/ 115251881Speter 116251881Speter /* create some pipes to talk to our future child 117251881Speter */ 118251881Speter pipe(stdin_pipe); /* child's stdin */ 119251881Speter pipe(stdout_pipe); /* child's stdout */ 120251881Speter 121289180Speter /* since we are a forked process, we can diddle the command string 122289180Speter * we were passed -- nobody else is going to use it again, right? 123289180Speter * 124289180Speter * if a % is present in the command, previous characters are the 125251881Speter * command, and subsequent characters are the additional input to 126251881Speter * the command. Subsequent %'s will be transformed into newlines, 127251881Speter * but that happens later. 128251881Speter * 129251881Speter * If there are escaped %'s, remove the escape character. 130251881Speter */ 131251881Speter /*local*/{ 132289180Speter register int escaped = FALSE; 133289180Speter register int ch; 134251881Speter register char *p; 135251881Speter 136289180Speter for (input_data = p = e->cmd; (ch = *input_data); 137251881Speter input_data++, p++) { 138251881Speter if (p != input_data) 139251881Speter *p = ch; 140251881Speter if (escaped) { 141251881Speter if (ch == '%' || ch == '\\') 142251881Speter *--p = ch; 143251881Speter escaped = FALSE; 144251881Speter continue; 145251881Speter } 146251881Speter if (ch == '\\') { 147251881Speter escaped = TRUE; 148251881Speter continue; 149251881Speter } 150251881Speter if (ch == '%') { 151251881Speter *input_data++ = '\0'; 152251881Speter break; 153251881Speter } 154251881Speter } 155251881Speter *p = '\0'; 156257936Speter } 157251881Speter 158251881Speter /* fork again, this time so we can exec the user's command. 159251881Speter */ 160251881Speter switch (vfork()) { 161251881Speter case -1: 162251881Speter log_it("CRON",getpid(),"error","can't vfork"); 163251881Speter exit(ERROR_EXIT); 164251881Speter /*NOTREACHED*/ 165251881Speter case 0: 166289180Speter Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n", 167251881Speter getpid())) 168251881Speter 169251881Speter if (e->uid == ROOT_UID) 170251881Speter Jitter = RootJitter; 171251881Speter if (Jitter != 0) { 172289180Speter srandom(getpid()); 173251881Speter sleep(random() % Jitter); 174251881Speter } 175251881Speter 176251881Speter /* write a log message. we've waited this long to do it 177251881Speter * because it was not until now that we knew the PID that 178251881Speter * the actual user command shell was going to get and the 179251881Speter * PID is part of the log message. 180251881Speter */ 181251881Speter /*local*/{ 182251881Speter char *x = mkprints((u_char *)e->cmd, strlen(e->cmd)); 183251881Speter 184251881Speter log_it(usernm, getpid(), "CMD", x); 185251881Speter free(x); 186251881Speter } 187251881Speter 188251881Speter /* that's the last thing we'll log. close the log files. 189251881Speter */ 190251881Speter#ifdef SYSLOG 191251881Speter closelog(); 192251881Speter#endif 193251881Speter 194289180Speter /* get new pgrp, void tty, etc. 195251881Speter */ 196251881Speter (void) setsid(); 197251881Speter 198251881Speter /* close the pipe ends that we won't use. this doesn't affect 199251881Speter * the parent, who has to read and write them; it keeps the 200251881Speter * kernel from recording us as a potential client TWICE -- 201251881Speter * which would keep it from sending SIGPIPE in otherwise 202251881Speter * appropriate circumstances. 203251881Speter */ 204251881Speter close(stdin_pipe[WRITE_PIPE]); 205289180Speter close(stdout_pipe[READ_PIPE]); 206251881Speter 207251881Speter /* grandchild process. make std{in,out} be the ends of 208251881Speter * pipes opened by our daddy; make stderr go to stdout. 209251881Speter */ 210251881Speter close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN); 211251881Speter close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT); 212251881Speter close(STDERR); dup2(STDOUT, STDERR); 213251881Speter 214251881Speter /* close the pipes we just dup'ed. The resources will remain. 215289180Speter */ 216289180Speter close(stdin_pipe[READ_PIPE]); 217289180Speter close(stdout_pipe[WRITE_PIPE]); 218251881Speter 219251881Speter /* set our login universe. Do this in the grandchild 220251881Speter * so that the child can invoke /usr/lib/sendmail 221251881Speter * without surprises. 222251881Speter */ 223251881Speter do_univ(u); 224251881Speter 225251881Speter# if defined(LOGIN_CAP) 226251881Speter /* Set user's entire context, but skip the environment 227251881Speter * as cron provides a separate interface for this 228251881Speter */ 229251881Speter if ((pwd = getpwnam(usernm)) == NULL) 230251881Speter pwd = getpwuid(e->uid); 231251881Speter lc = NULL; 232251881Speter if (pwd != NULL) { 233251881Speter pwd->pw_gid = e->gid; 234251881Speter if (e->class != NULL) 235251881Speter lc = login_getclass(e->class); 236251881Speter } 237251881Speter if (pwd && 238251881Speter setusercontext(lc, pwd, e->uid, 239251881Speter LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0) 240251881Speter (void) endpwent(); 241251881Speter else { 242251881Speter /* fall back to the old method */ 243251881Speter (void) endpwent(); 244251881Speter# endif 245251881Speter /* set our directory, uid and gid. Set gid first, 246251881Speter * since once we set uid, we've lost root privledges. 247251881Speter */ 248251881Speter setgid(e->gid); 249251881Speter# if defined(BSD) 250251881Speter initgroups(usernm, e->gid); 251251881Speter# endif 252251881Speter setlogin(usernm); 253289180Speter setuid(e->uid); /* we aren't root after this..*/ 254289180Speter#if defined(LOGIN_CAP) 255289180Speter } 256251881Speter if (lc != NULL) 257289180Speter login_close(lc); 258251881Speter#endif 259251881Speter chdir(env_get("HOME", e->envp)); 260251881Speter 261251881Speter /* exec the command. 262251881Speter */ 263251881Speter { 264251881Speter char *shell = env_get("SHELL", e->envp); 265251881Speter 266289180Speter# if DEBUGGING 267289180Speter if (DebugFlags & DTEST) { 268289180Speter fprintf(stderr, 269251881Speter "debug DTEST is on, not exec'ing command.\n"); 270251881Speter fprintf(stderr, 271251881Speter "\tcmd='%s' shell='%s'\n", e->cmd, shell); 272251881Speter _exit(OK_EXIT); 273251881Speter } 274251881Speter# endif /*DEBUGGING*/ 275251881Speter execle(shell, shell, "-c", e->cmd, (char *)0, e->envp); 276251881Speter warn("execl: couldn't exec `%s'", shell); 277251881Speter _exit(ERROR_EXIT); 278251881Speter } 279251881Speter break; 280251881Speter default: 281251881Speter /* parent process */ 282251881Speter break; 283251881Speter } 284251881Speter 285251881Speter children++; 286251881Speter 287251881Speter /* middle process, child of original cron, parent of process running 288251881Speter * the user's command. 289251881Speter */ 290251881Speter 291251881Speter Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid())) 292251881Speter 293251881Speter /* close the ends of the pipe that will only be referenced in the 294251881Speter * grandchild process... 295251881Speter */ 296251881Speter close(stdin_pipe[READ_PIPE]); 297251881Speter close(stdout_pipe[WRITE_PIPE]); 298251881Speter 299251881Speter /* 300251881Speter * write, to the pipe connected to child's stdin, any input specified 301251881Speter * after a % in the crontab entry. while we copy, convert any 302251881Speter * additional %'s to newlines. when done, if some characters were 303251881Speter * written and the last one wasn't a newline, write a newline. 304251881Speter * 305251881Speter * Note that if the input data won't fit into one pipe buffer (2K 306251881Speter * or 4K on most BSD systems), and the child doesn't read its stdin, 307251881Speter * we would block here. thus we must fork again. 308251881Speter */ 309251881Speter 310251881Speter if (*input_data && fork() == 0) { 311289180Speter register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); 312289180Speter register int need_newline = FALSE; 313289180Speter register int escaped = FALSE; 314251881Speter register int ch; 315251881Speter 316251881Speter if (out == NULL) { 317251881Speter warn("fdopen failed in child2"); 318251881Speter _exit(ERROR_EXIT); 319251881Speter } 320251881Speter 321251881Speter Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) 322251881Speter 323251881Speter /* close the pipe we don't use, since we inherited it and 324251881Speter * are part of its reference count now. 325289180Speter */ 326289180Speter close(stdout_pipe[READ_PIPE]); 327289180Speter 328289180Speter /* translation: 329289180Speter * \% -> % 330289180Speter * % -> \n 331289180Speter * \x -> \x for all x != % 332289180Speter */ 333289180Speter while ((ch = *input_data++)) { 334289180Speter if (escaped) { 335289180Speter if (ch != '%') 336251881Speter putc('\\', out); 337251881Speter } else { 338251881Speter if (ch == '%') 339251881Speter ch = '\n'; 340251881Speter } 341251881Speter 342251881Speter if (!(escaped = (ch == '\\'))) { 343251881Speter putc(ch, out); 344251881Speter need_newline = (ch != '\n'); 345251881Speter } 346251881Speter } 347251881Speter if (escaped) 348251881Speter putc('\\', out); 349251881Speter if (need_newline) 350251881Speter putc('\n', out); 351251881Speter 352251881Speter /* close the pipe, causing an EOF condition. fclose causes 353251881Speter * stdin_pipe[WRITE_PIPE] to be closed, too. 354251881Speter */ 355251881Speter fclose(out); 356251881Speter 357251881Speter Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid())) 358251881Speter exit(0); 359251881Speter } 360289180Speter 361289180Speter /* close the pipe to the grandkiddie's stdin, since its wicked uncle 362289180Speter * ernie back there has it open and will close it when he's done. 363289180Speter */ 364289180Speter close(stdin_pipe[WRITE_PIPE]); 365289180Speter 366289180Speter children++; 367289180Speter 368289180Speter /* 369289180Speter * read output from the grandchild. it's stderr has been redirected to 370289180Speter * it's stdout, which has been redirected to our pipe. if there is any 371289180Speter * output, we'll be mailing it to the user whose crontab this is... 372289180Speter * when the grandchild exits, we'll get EOF. 373251881Speter */ 374251881Speter 375251881Speter Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) 376251881Speter 377251881Speter /*local*/{ 378251881Speter register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); 379251881Speter register int ch; 380251881Speter 381251881Speter if (in == NULL) { 382251881Speter warn("fdopen failed in child"); 383251881Speter _exit(ERROR_EXIT); 384251881Speter } 385251881Speter 386251881Speter ch = getc(in); 387251881Speter if (ch != EOF) { 388251881Speter register FILE *mail; 389251881Speter register int bytes = 1; 390251881Speter int status = 0; 391251881Speter 392251881Speter Debug(DPROC|DEXT, 393251881Speter ("[%d] got data (%x:%c) from grandchild\n", 394251881Speter getpid(), ch, ch)) 395251881Speter 396251881Speter /* get name of recipient. this is MAILTO if set to a 397251881Speter * valid local username; USER otherwise. 398251881Speter */ 399251881Speter if (mailto) { 400251881Speter /* MAILTO was present in the environment 401251881Speter */ 402251881Speter if (!*mailto) { 403251881Speter /* ... but it's empty. set to NULL 404251881Speter */ 405251881Speter mailto = NULL; 406251881Speter } 407251881Speter } else { 408251881Speter /* MAILTO not present, set to USER. 409251881Speter */ 410251881Speter mailto = usernm; 411251881Speter } 412251881Speter 413251881Speter /* if we are supposed to be mailing, MAILTO will 414251881Speter * be non-NULL. only in this case should we set 415289180Speter * up the mail command and subjects and stuff... 416251881Speter */ 417289180Speter 418289180Speter if (mailto) { 419289180Speter register char **env; 420289180Speter auto char mailcmd[MAX_COMMAND]; 421289180Speter auto char hostname[MAXHOSTNAMELEN]; 422251881Speter 423251881Speter (void) gethostname(hostname, MAXHOSTNAMELEN); 424251881Speter (void) snprintf(mailcmd, sizeof(mailcmd), 425251881Speter MAILARGS, MAILCMD); 426251881Speter if (!(mail = cron_popen(mailcmd, "w", e))) { 427251881Speter warn("%s", MAILCMD); 428251881Speter (void) _exit(ERROR_EXIT); 429251881Speter } 430251881Speter fprintf(mail, "From: %s (Cron Daemon)\n", usernm); 431251881Speter fprintf(mail, "To: %s\n", mailto); 432251881Speter fprintf(mail, "Subject: Cron <%s@%s> %s\n", 433251881Speter usernm, first_word(hostname, "."), 434251881Speter e->cmd); 435251881Speter# if defined(MAIL_DATE) 436251881Speter fprintf(mail, "Date: %s\n", 437251881Speter arpadate(&TargetTime)); 438251881Speter# endif /* MAIL_DATE */ 439251881Speter for (env = e->envp; *env; env++) 440251881Speter fprintf(mail, "X-Cron-Env: <%s>\n", 441251881Speter *env); 442251881Speter fprintf(mail, "\n"); 443289180Speter 444251881Speter /* this was the first char from the pipe 445251881Speter */ 446251881Speter putc(ch, mail); 447251881Speter } 448251881Speter 449251881Speter /* we have to read the input pipe no matter whether 450251881Speter * we mail or not, but obviously we only write to 451251881Speter * mail pipe if we ARE mailing. 452251881Speter */ 453251881Speter 454251881Speter while (EOF != (ch = getc(in))) { 455251881Speter bytes++; 456251881Speter if (mailto) 457251881Speter putc(ch, mail); 458251881Speter } 459251881Speter 460251881Speter /* only close pipe if we opened it -- i.e., we're 461251881Speter * mailing... 462251881Speter */ 463251881Speter 464251881Speter if (mailto) { 465251881Speter Debug(DPROC, ("[%d] closing pipe to mail\n", 466251881Speter getpid())) 467251881Speter /* Note: the pclose will probably see 468251881Speter * the termination of the grandchild 469289180Speter * in addition to the mail process, since 470251881Speter * it (the grandchild) is likely to exit 471251881Speter * after closing its stdout. 472251881Speter */ 473251881Speter status = cron_pclose(mail); 474251881Speter } 475251881Speter 476251881Speter /* if there was output and we could not mail it, 477251881Speter * log the facts so the poor user can figure out 478251881Speter * what's going on. 479251881Speter */ 480251881Speter if (mailto && status) { 481251881Speter char buf[MAX_TEMPSTR]; 482251881Speter 483251881Speter snprintf(buf, sizeof(buf), 484251881Speter "mailed %d byte%s of output but got status 0x%04x\n", 485251881Speter bytes, (bytes==1)?"":"s", 486251881Speter status); 487251881Speter log_it(usernm, getpid(), "MAIL", buf); 488251881Speter } 489251881Speter 490251881Speter } /*if data from grandchild*/ 491251881Speter 492251881Speter Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid())) 493251881Speter 494251881Speter fclose(in); /* also closes stdout_pipe[READ_PIPE] */ 495251881Speter } 496251881Speter 497251881Speter /* wait for children to die. 498251881Speter */ 499251881Speter for (; children > 0; children--) 500251881Speter { 501251881Speter WAIT_T waiter; 502289180Speter PID_T pid; 503289180Speter 504289180Speter Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n", 505289180Speter getpid(), children)) 506289180Speter pid = wait(&waiter); 507289180Speter if (pid < OK) { 508251881Speter Debug(DPROC, ("[%d] no more grandchildren--mail written?\n", 509289180Speter getpid())) 510289180Speter break; 511289180Speter } 512251881Speter Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x", 513251881Speter getpid(), pid, WEXITSTATUS(waiter))) 514251881Speter if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) 515251881Speter Debug(DPROC, (", dumped core")) 516251881Speter Debug(DPROC, ("\n")) 517251881Speter } 518251881Speter} 519251881Speter 520251881Speter 521251881Speterstatic void 522251881Speterdo_univ(u) 523251881Speter user *u; 524251881Speter{ 525251881Speter#if defined(sequent) 526251881Speter/* Dynix (Sequent) hack to put the user associated with 527251881Speter * the passed user structure into the ATT universe if 528251881Speter * necessary. We have to dig the gecos info out of 529251881Speter * the user's password entry to see if the magic 530251881Speter * "universe(att)" string is present. 531251881Speter */ 532251881Speter 533251881Speter struct passwd *p; 534251881Speter char *s; 535251881Speter int i; 536251881Speter 537251881Speter p = getpwuid(u->uid); 538251881Speter (void) endpwent(); 539251881Speter 540251881Speter if (p == NULL) 541251881Speter return; 542251881Speter 543251881Speter s = p->pw_gecos; 544289180Speter 545289180Speter for (i = 0; i < 4; i++) 546251881Speter { 547251881Speter if ((s = strchr(s, ',')) == NULL) 548251881Speter return; 549251881Speter s++; 550251881Speter } 551251881Speter if (strcmp(s, "universe(att)")) 552251881Speter return; 553251881Speter 554251881Speter (void) universe(U_ATT); 555251881Speter#endif 556251881Speter} 557251881Speter