12311Sjkh/* Copyright 1988,1990,1993,1994 by Paul Vixie 22311Sjkh * All rights reserved 32311Sjkh * 42311Sjkh * Distribute freely, except: don't remove my name from the source or 52311Sjkh * documentation (don't take credit for my work), mark your changes (don't 62311Sjkh * get me blamed for your possible bugs), don't alter or remove this 72311Sjkh * notice. May be sold if buildable source is provided to buyer. No 82311Sjkh * warrantee of any kind, express or implied, is included with this 92311Sjkh * software; use at your own risk, responsibility for damages (if any) to 102311Sjkh * anyone resulting from the use of this software rests entirely with the 112311Sjkh * user. 122311Sjkh * 132311Sjkh * Send bug reports, bug fixes, enhancements, requests, flames, etc., and 142311Sjkh * I'll try to keep a version up to date. I can be reached as follows: 152311Sjkh * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul 162311Sjkh */ 172311Sjkh 182311Sjkh#if !defined(lint) && !defined(LINT) 1929452Scharnierstatic const char rcsid[] = 2050479Speter "$FreeBSD: stable/10/usr.sbin/cron/cron/do_command.c 321244 2017-07-19 20:29:07Z ngie $"; 212311Sjkh#endif 222311Sjkh 232311Sjkh 242311Sjkh#include "cron.h" 252311Sjkh#include <sys/signal.h> 262311Sjkh#if defined(sequent) 272311Sjkh# include <sys/universe.h> 282311Sjkh#endif 292311Sjkh#if defined(SYSLOG) 302311Sjkh# include <syslog.h> 312311Sjkh#endif 3221895Sdavidn#if defined(LOGIN_CAP) 3321895Sdavidn# include <login_cap.h> 3421895Sdavidn#endif 35170890Syar#ifdef PAM 36170890Syar# include <security/pam_appl.h> 37170890Syar# include <security/openpam.h> 38170890Syar#endif 392311Sjkh 402311Sjkh 41173412Skevlostatic void child_process(entry *, user *), 42173412Skevlo do_univ(user *); 432311Sjkh 442311Sjkh 452311Sjkhvoid 462311Sjkhdo_command(e, u) 472311Sjkh entry *e; 482311Sjkh user *u; 492311Sjkh{ 502311Sjkh Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", 512311Sjkh getpid(), e->cmd, u->name, e->uid, e->gid)) 522311Sjkh 532311Sjkh /* fork to become asynchronous -- parent process is done immediately, 542311Sjkh * and continues to run the normal cron code, which means return to 552311Sjkh * tick(). the child and grandchild don't leave this function, alive. 562311Sjkh * 572311Sjkh * vfork() is unsuitable, since we have much to do, and the parent 582311Sjkh * needs to be able to run off and fork other processes. 592311Sjkh */ 602311Sjkh switch (fork()) { 612311Sjkh case -1: 622311Sjkh log_it("CRON",getpid(),"error","can't fork"); 632311Sjkh break; 642311Sjkh case 0: 652311Sjkh /* child process */ 66149430Spjd pidfile_close(pfh); 672311Sjkh child_process(e, u); 682311Sjkh Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) 692311Sjkh _exit(OK_EXIT); 702311Sjkh break; 712311Sjkh default: 722311Sjkh /* parent process */ 732311Sjkh break; 742311Sjkh } 752311Sjkh Debug(DPROC, ("[%d] main process returning to work\n", getpid())) 762311Sjkh} 772311Sjkh 782311Sjkh 792311Sjkhstatic void 802311Sjkhchild_process(e, u) 812311Sjkh entry *e; 822311Sjkh user *u; 832311Sjkh{ 842311Sjkh int stdin_pipe[2], stdout_pipe[2]; 852311Sjkh register char *input_data; 862311Sjkh char *usernm, *mailto; 872311Sjkh int children = 0; 8821895Sdavidn# if defined(LOGIN_CAP) 8923884Speter struct passwd *pwd; 9030895Sache login_cap_t *lc; 9121895Sdavidn# endif 922311Sjkh 932311Sjkh Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd)) 942311Sjkh 952311Sjkh /* mark ourselves as different to PS command watchers by upshifting 962311Sjkh * our program name. This has no effect on some kernels. 972311Sjkh */ 9874375Speter setproctitle("running job"); 992311Sjkh 1002311Sjkh /* discover some useful and important environment settings 1012311Sjkh */ 1022311Sjkh usernm = env_get("LOGNAME", e->envp); 1032311Sjkh mailto = env_get("MAILTO", e->envp); 1047809Sache 105170890Syar#ifdef PAM 106170890Syar /* use PAM to see if the user's account is available, 107170890Syar * i.e., not locked or expired or whatever. skip this 108170890Syar * for system tasks from /etc/crontab -- they can run 109170890Syar * as any user. 110170890Syar */ 111170890Syar if (strcmp(u->name, SYS_NAME)) { /* not equal */ 112170890Syar pam_handle_t *pamh = NULL; 113170890Syar int pam_err; 114170890Syar struct pam_conv pamc = { 115170890Syar .conv = openpam_nullconv, 116170890Syar .appdata_ptr = NULL 117170890Syar }; 118170890Syar 119170890Syar Debug(DPROC, ("[%d] checking account with PAM\n", getpid())) 120170890Syar 121170890Syar /* u->name keeps crontab owner name while LOGNAME is the name 122170890Syar * of user to run command on behalf of. they should be the 123170890Syar * same for a task from a per-user crontab. 124170890Syar */ 125170890Syar if (strcmp(u->name, usernm)) { 126170890Syar log_it(usernm, getpid(), "username ambiguity", u->name); 127170890Syar exit(ERROR_EXIT); 128170890Syar } 129170890Syar 130170890Syar pam_err = pam_start("cron", usernm, &pamc, &pamh); 131170890Syar if (pam_err != PAM_SUCCESS) { 132170890Syar log_it("CRON", getpid(), "error", "can't start PAM"); 133170890Syar exit(ERROR_EXIT); 134170890Syar } 135170890Syar 136170890Syar pam_err = pam_acct_mgmt(pamh, PAM_SILENT); 137170890Syar /* Expired password shouldn't prevent the job from running. */ 138170890Syar if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD) { 139170890Syar log_it(usernm, getpid(), "USER", "account unavailable"); 140170890Syar exit(ERROR_EXIT); 141170890Syar } 142170890Syar 143170890Syar pam_end(pamh, pam_err); 144170890Syar } 145170890Syar#endif 146170890Syar 1472311Sjkh#ifdef USE_SIGCHLD 1482311Sjkh /* our parent is watching for our death by catching SIGCHLD. we 1492311Sjkh * do not care to watch for our children's deaths this way -- we 150228990Suqs * use wait() explicitly. so we have to disable the signal (which 1512311Sjkh * was inherited from the parent). 1522311Sjkh */ 15362359Sache (void) signal(SIGCHLD, SIG_DFL); 1542311Sjkh#else 1552311Sjkh /* on system-V systems, we are ignoring SIGCLD. we have to stop 1562311Sjkh * ignoring it now or the wait() in cron_pclose() won't work. 1572311Sjkh * because of this, we have to wait() for our children here, as well. 1582311Sjkh */ 1592311Sjkh (void) signal(SIGCLD, SIG_DFL); 1602311Sjkh#endif /*BSD*/ 1612311Sjkh 1622311Sjkh /* create some pipes to talk to our future child 1632311Sjkh */ 164293132Spfg if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) { 165293132Spfg log_it("CRON", getpid(), "error", "can't pipe"); 166293132Spfg exit(ERROR_EXIT); 167293132Spfg } 1688857Srgrimes 1692311Sjkh /* since we are a forked process, we can diddle the command string 1702311Sjkh * we were passed -- nobody else is going to use it again, right? 1712311Sjkh * 1722311Sjkh * if a % is present in the command, previous characters are the 1732311Sjkh * command, and subsequent characters are the additional input to 1742311Sjkh * the command. Subsequent %'s will be transformed into newlines, 1752311Sjkh * but that happens later. 17610660Sjoerg * 17710660Sjoerg * If there are escaped %'s, remove the escape character. 1782311Sjkh */ 1792311Sjkh /*local*/{ 1802311Sjkh register int escaped = FALSE; 1812311Sjkh register int ch; 18210660Sjoerg register char *p; 1832311Sjkh 18429452Scharnier for (input_data = p = e->cmd; (ch = *input_data); 18510660Sjoerg input_data++, p++) { 18610660Sjoerg if (p != input_data) 18710660Sjoerg *p = ch; 1882311Sjkh if (escaped) { 18910660Sjoerg if (ch == '%' || ch == '\\') 19010660Sjoerg *--p = ch; 1912311Sjkh escaped = FALSE; 1922311Sjkh continue; 1932311Sjkh } 1942311Sjkh if (ch == '\\') { 1952311Sjkh escaped = TRUE; 1962311Sjkh continue; 1972311Sjkh } 1982311Sjkh if (ch == '%') { 1992311Sjkh *input_data++ = '\0'; 2002311Sjkh break; 2012311Sjkh } 2022311Sjkh } 20310660Sjoerg *p = '\0'; 2042311Sjkh } 2052311Sjkh 2062311Sjkh /* fork again, this time so we can exec the user's command. 2072311Sjkh */ 2082311Sjkh switch (vfork()) { 2092311Sjkh case -1: 2102311Sjkh log_it("CRON",getpid(),"error","can't vfork"); 2112311Sjkh exit(ERROR_EXIT); 2122311Sjkh /*NOTREACHED*/ 2132311Sjkh case 0: 2142311Sjkh Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n", 2152311Sjkh getpid())) 2162311Sjkh 217129280Syar if (e->uid == ROOT_UID) 218129280Syar Jitter = RootJitter; 219129280Syar if (Jitter != 0) { 220129280Syar srandom(getpid()); 221129280Syar sleep(random() % Jitter); 222129280Syar } 223129280Syar 2242311Sjkh /* write a log message. we've waited this long to do it 2252311Sjkh * because it was not until now that we knew the PID that 2262311Sjkh * the actual user command shell was going to get and the 2272311Sjkh * PID is part of the log message. 2282311Sjkh */ 2292311Sjkh /*local*/{ 2302311Sjkh char *x = mkprints((u_char *)e->cmd, strlen(e->cmd)); 2312311Sjkh 2322311Sjkh log_it(usernm, getpid(), "CMD", x); 2332311Sjkh free(x); 2342311Sjkh } 2352311Sjkh 2362311Sjkh /* that's the last thing we'll log. close the log files. 2372311Sjkh */ 2382311Sjkh#ifdef SYSLOG 2392311Sjkh closelog(); 2402311Sjkh#endif 2412311Sjkh 2422311Sjkh /* get new pgrp, void tty, etc. 2432311Sjkh */ 2442311Sjkh (void) setsid(); 2452311Sjkh 2462311Sjkh /* close the pipe ends that we won't use. this doesn't affect 2472311Sjkh * the parent, who has to read and write them; it keeps the 2482311Sjkh * kernel from recording us as a potential client TWICE -- 2492311Sjkh * which would keep it from sending SIGPIPE in otherwise 2502311Sjkh * appropriate circumstances. 2512311Sjkh */ 2522311Sjkh close(stdin_pipe[WRITE_PIPE]); 2532311Sjkh close(stdout_pipe[READ_PIPE]); 2542311Sjkh 2552311Sjkh /* grandchild process. make std{in,out} be the ends of 2562311Sjkh * pipes opened by our daddy; make stderr go to stdout. 2572311Sjkh */ 2582311Sjkh close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN); 2592311Sjkh close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT); 2602311Sjkh close(STDERR); dup2(STDOUT, STDERR); 2612311Sjkh 2622311Sjkh /* close the pipes we just dup'ed. The resources will remain. 2632311Sjkh */ 2642311Sjkh close(stdin_pipe[READ_PIPE]); 2652311Sjkh close(stdout_pipe[WRITE_PIPE]); 2662311Sjkh 2672311Sjkh /* set our login universe. Do this in the grandchild 2682311Sjkh * so that the child can invoke /usr/lib/sendmail 2692311Sjkh * without surprises. 2702311Sjkh */ 2712311Sjkh do_univ(u); 2722311Sjkh 27321895Sdavidn# if defined(LOGIN_CAP) 27421895Sdavidn /* Set user's entire context, but skip the environment 27521895Sdavidn * as cron provides a separate interface for this 27621895Sdavidn */ 27730895Sache if ((pwd = getpwnam(usernm)) == NULL) 27830895Sache pwd = getpwuid(e->uid); 27930895Sache lc = NULL; 28030895Sache if (pwd != NULL) { 28130895Sache pwd->pw_gid = e->gid; 28230895Sache if (e->class != NULL) 28330895Sache lc = login_getclass(e->class); 28430895Sache } 28523886Speter if (pwd && 28630895Sache setusercontext(lc, pwd, e->uid, 28723886Speter LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0) 28823886Speter (void) endpwent(); 28923886Speter else { 29023884Speter /* fall back to the old method */ 29123886Speter (void) endpwent(); 29223884Speter# endif 29323884Speter /* set our directory, uid and gid. Set gid first, 294159527Smaxim * since once we set uid, we've lost root privileges. 29523884Speter */ 296159142Smaxim if (setgid(e->gid) != 0) { 297159142Smaxim log_it(usernm, getpid(), 298159142Smaxim "error", "setgid failed"); 299159142Smaxim exit(ERROR_EXIT); 300159142Smaxim } 3012311Sjkh# if defined(BSD) 302159142Smaxim if (initgroups(usernm, e->gid) != 0) { 303159142Smaxim log_it(usernm, getpid(), 304159142Smaxim "error", "initgroups failed"); 305159142Smaxim exit(ERROR_EXIT); 306159142Smaxim } 3072311Sjkh# endif 308159142Smaxim if (setlogin(usernm) != 0) { 309159142Smaxim log_it(usernm, getpid(), 310159142Smaxim "error", "setlogin failed"); 311159142Smaxim exit(ERROR_EXIT); 312159142Smaxim } 313159142Smaxim if (setuid(e->uid) != 0) { 314159142Smaxim log_it(usernm, getpid(), 315159142Smaxim "error", "setuid failed"); 316159142Smaxim exit(ERROR_EXIT); 317159142Smaxim } 318159142Smaxim /* we aren't root after this..*/ 31923884Speter#if defined(LOGIN_CAP) 32023884Speter } 32162376Sache if (lc != NULL) 32262376Sache login_close(lc); 32321895Sdavidn#endif 32420573Spst chdir(env_get("HOME", e->envp)); 3252311Sjkh 3262311Sjkh /* exec the command. 3272311Sjkh */ 3282311Sjkh { 3292311Sjkh char *shell = env_get("SHELL", e->envp); 3302311Sjkh 3312311Sjkh# if DEBUGGING 3322311Sjkh if (DebugFlags & DTEST) { 3332311Sjkh fprintf(stderr, 3342311Sjkh "debug DTEST is on, not exec'ing command.\n"); 3352311Sjkh fprintf(stderr, 3362311Sjkh "\tcmd='%s' shell='%s'\n", e->cmd, shell); 3372311Sjkh _exit(OK_EXIT); 3382311Sjkh } 3392311Sjkh# endif /*DEBUGGING*/ 340296460Spfg execle(shell, shell, "-c", e->cmd, (char *)NULL, 341296460Spfg e->envp); 342296460Spfg warn("execle: couldn't exec `%s'", shell); 3432311Sjkh _exit(ERROR_EXIT); 3442311Sjkh } 3452311Sjkh break; 3462311Sjkh default: 3472311Sjkh /* parent process */ 3482311Sjkh break; 3492311Sjkh } 3502311Sjkh 3512311Sjkh children++; 3522311Sjkh 3532311Sjkh /* middle process, child of original cron, parent of process running 3542311Sjkh * the user's command. 3552311Sjkh */ 3562311Sjkh 3572311Sjkh Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid())) 3582311Sjkh 3592311Sjkh /* close the ends of the pipe that will only be referenced in the 3602311Sjkh * grandchild process... 3612311Sjkh */ 3622311Sjkh close(stdin_pipe[READ_PIPE]); 3632311Sjkh close(stdout_pipe[WRITE_PIPE]); 3642311Sjkh 3652311Sjkh /* 3662311Sjkh * write, to the pipe connected to child's stdin, any input specified 3672311Sjkh * after a % in the crontab entry. while we copy, convert any 3682311Sjkh * additional %'s to newlines. when done, if some characters were 3692311Sjkh * written and the last one wasn't a newline, write a newline. 3702311Sjkh * 3712311Sjkh * Note that if the input data won't fit into one pipe buffer (2K 3722311Sjkh * or 4K on most BSD systems), and the child doesn't read its stdin, 3732311Sjkh * we would block here. thus we must fork again. 3742311Sjkh */ 3752311Sjkh 3762311Sjkh if (*input_data && fork() == 0) { 3772311Sjkh register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); 3782311Sjkh register int need_newline = FALSE; 3792311Sjkh register int escaped = FALSE; 3802311Sjkh register int ch; 3812311Sjkh 38260826Sghelmer if (out == NULL) { 38360826Sghelmer warn("fdopen failed in child2"); 38460826Sghelmer _exit(ERROR_EXIT); 38560826Sghelmer } 38660826Sghelmer 3872311Sjkh Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) 3882311Sjkh 3892311Sjkh /* close the pipe we don't use, since we inherited it and 3902311Sjkh * are part of its reference count now. 3912311Sjkh */ 3922311Sjkh close(stdout_pipe[READ_PIPE]); 3932311Sjkh 3942311Sjkh /* translation: 3952311Sjkh * \% -> % 3962311Sjkh * % -> \n 3972311Sjkh * \x -> \x for all x != % 3982311Sjkh */ 39929452Scharnier while ((ch = *input_data++)) { 4002311Sjkh if (escaped) { 4012311Sjkh if (ch != '%') 4022311Sjkh putc('\\', out); 4032311Sjkh } else { 4042311Sjkh if (ch == '%') 4052311Sjkh ch = '\n'; 4062311Sjkh } 4072311Sjkh 4082311Sjkh if (!(escaped = (ch == '\\'))) { 4092311Sjkh putc(ch, out); 4102311Sjkh need_newline = (ch != '\n'); 4112311Sjkh } 4122311Sjkh } 4132311Sjkh if (escaped) 4142311Sjkh putc('\\', out); 4152311Sjkh if (need_newline) 4162311Sjkh putc('\n', out); 4172311Sjkh 4182311Sjkh /* close the pipe, causing an EOF condition. fclose causes 4192311Sjkh * stdin_pipe[WRITE_PIPE] to be closed, too. 4202311Sjkh */ 4212311Sjkh fclose(out); 4222311Sjkh 4232311Sjkh Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid())) 4242311Sjkh exit(0); 4252311Sjkh } 4262311Sjkh 4272311Sjkh /* close the pipe to the grandkiddie's stdin, since its wicked uncle 4282311Sjkh * ernie back there has it open and will close it when he's done. 4292311Sjkh */ 4302311Sjkh close(stdin_pipe[WRITE_PIPE]); 4312311Sjkh 4322311Sjkh children++; 4332311Sjkh 4342311Sjkh /* 4352311Sjkh * read output from the grandchild. it's stderr has been redirected to 4362311Sjkh * it's stdout, which has been redirected to our pipe. if there is any 4372311Sjkh * output, we'll be mailing it to the user whose crontab this is... 4382311Sjkh * when the grandchild exits, we'll get EOF. 4392311Sjkh */ 4402311Sjkh 4412311Sjkh Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) 4422311Sjkh 4432311Sjkh /*local*/{ 4442311Sjkh register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); 445116590Ssilby register int ch; 4462311Sjkh 44760826Sghelmer if (in == NULL) { 44860826Sghelmer warn("fdopen failed in child"); 44960826Sghelmer _exit(ERROR_EXIT); 45060826Sghelmer } 45160826Sghelmer 452116590Ssilby ch = getc(in); 4532311Sjkh if (ch != EOF) { 4542311Sjkh register FILE *mail; 4552311Sjkh register int bytes = 1; 4562311Sjkh int status = 0; 4572311Sjkh 4582311Sjkh Debug(DPROC|DEXT, 4592311Sjkh ("[%d] got data (%x:%c) from grandchild\n", 4602311Sjkh getpid(), ch, ch)) 4612311Sjkh 4622311Sjkh /* get name of recipient. this is MAILTO if set to a 4632311Sjkh * valid local username; USER otherwise. 4642311Sjkh */ 465180096Smarck if (mailto == NULL) { 466180096Smarck /* MAILTO not present, set to USER, 467180096Smarck * unless globally overriden. 4682311Sjkh */ 469180096Smarck if (defmailto) 470180096Smarck mailto = defmailto; 471180096Smarck else 472180096Smarck mailto = usernm; 4732311Sjkh } 474181115Smarck if (mailto && *mailto == '\0') 475181115Smarck mailto = NULL; 4768857Srgrimes 4772311Sjkh /* if we are supposed to be mailing, MAILTO will 4782311Sjkh * be non-NULL. only in this case should we set 4792311Sjkh * up the mail command and subjects and stuff... 4802311Sjkh */ 4812311Sjkh 482181115Smarck if (mailto) { 4832311Sjkh register char **env; 4842311Sjkh auto char mailcmd[MAX_COMMAND]; 4852311Sjkh auto char hostname[MAXHOSTNAMELEN]; 4862311Sjkh 487321244Sngie if (gethostname(hostname, MAXHOSTNAMELEN) == -1) 488321244Sngie hostname[0] = '\0'; 489321244Sngie hostname[sizeof(hostname) - 1] = '\0'; 49020573Spst (void) snprintf(mailcmd, sizeof(mailcmd), 49120573Spst MAILARGS, MAILCMD); 49262367Sache if (!(mail = cron_popen(mailcmd, "w", e))) { 49329452Scharnier warn("%s", MAILCMD); 4942311Sjkh (void) _exit(ERROR_EXIT); 4952311Sjkh } 496321244Sngie fprintf(mail, "From: Cron Daemon <%s@%s>\n", 497321244Sngie usernm, hostname); 4982311Sjkh fprintf(mail, "To: %s\n", mailto); 4992311Sjkh fprintf(mail, "Subject: Cron <%s@%s> %s\n", 5002311Sjkh usernm, first_word(hostname, "."), 5012311Sjkh e->cmd); 5022311Sjkh# if defined(MAIL_DATE) 5032311Sjkh fprintf(mail, "Date: %s\n", 5042311Sjkh arpadate(&TargetTime)); 5052311Sjkh# endif /* MAIL_DATE */ 5062311Sjkh for (env = e->envp; *env; env++) 5072311Sjkh fprintf(mail, "X-Cron-Env: <%s>\n", 5082311Sjkh *env); 5092311Sjkh fprintf(mail, "\n"); 5102311Sjkh 5112311Sjkh /* this was the first char from the pipe 5122311Sjkh */ 5132311Sjkh putc(ch, mail); 5142311Sjkh } 5152311Sjkh 5162311Sjkh /* we have to read the input pipe no matter whether 5172311Sjkh * we mail or not, but obviously we only write to 5182311Sjkh * mail pipe if we ARE mailing. 5192311Sjkh */ 5202311Sjkh 5212311Sjkh while (EOF != (ch = getc(in))) { 5222311Sjkh bytes++; 5232311Sjkh if (mailto) 5242311Sjkh putc(ch, mail); 5252311Sjkh } 5262311Sjkh 5272311Sjkh /* only close pipe if we opened it -- i.e., we're 5282311Sjkh * mailing... 5292311Sjkh */ 5302311Sjkh 5312311Sjkh if (mailto) { 5322311Sjkh Debug(DPROC, ("[%d] closing pipe to mail\n", 5332311Sjkh getpid())) 5342311Sjkh /* Note: the pclose will probably see 5352311Sjkh * the termination of the grandchild 5362311Sjkh * in addition to the mail process, since 5372311Sjkh * it (the grandchild) is likely to exit 5382311Sjkh * after closing its stdout. 5392311Sjkh */ 5402311Sjkh status = cron_pclose(mail); 5412311Sjkh } 5422311Sjkh 5432311Sjkh /* if there was output and we could not mail it, 5442311Sjkh * log the facts so the poor user can figure out 5452311Sjkh * what's going on. 5462311Sjkh */ 5472311Sjkh if (mailto && status) { 5482311Sjkh char buf[MAX_TEMPSTR]; 5492311Sjkh 55020573Spst snprintf(buf, sizeof(buf), 5512311Sjkh "mailed %d byte%s of output but got status 0x%04x\n", 5522311Sjkh bytes, (bytes==1)?"":"s", 5532311Sjkh status); 5542311Sjkh log_it(usernm, getpid(), "MAIL", buf); 5552311Sjkh } 5562311Sjkh 5572311Sjkh } /*if data from grandchild*/ 5582311Sjkh 5592311Sjkh Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid())) 5602311Sjkh 5612311Sjkh fclose(in); /* also closes stdout_pipe[READ_PIPE] */ 5622311Sjkh } 5632311Sjkh 5642311Sjkh /* wait for children to die. 5652311Sjkh */ 5662311Sjkh for (; children > 0; children--) 5672311Sjkh { 5682311Sjkh WAIT_T waiter; 5692311Sjkh PID_T pid; 5702311Sjkh 5712311Sjkh Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n", 5722311Sjkh getpid(), children)) 5732311Sjkh pid = wait(&waiter); 5742311Sjkh if (pid < OK) { 5752311Sjkh Debug(DPROC, ("[%d] no more grandchildren--mail written?\n", 5762311Sjkh getpid())) 5772311Sjkh break; 5782311Sjkh } 5792311Sjkh Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x", 5802311Sjkh getpid(), pid, WEXITSTATUS(waiter))) 5812311Sjkh if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) 5822311Sjkh Debug(DPROC, (", dumped core")) 5832311Sjkh Debug(DPROC, ("\n")) 5842311Sjkh } 5852311Sjkh} 5862311Sjkh 5872311Sjkh 5882311Sjkhstatic void 5892311Sjkhdo_univ(u) 5902311Sjkh user *u; 5912311Sjkh{ 5922311Sjkh#if defined(sequent) 5932311Sjkh/* Dynix (Sequent) hack to put the user associated with 5942311Sjkh * the passed user structure into the ATT universe if 5952311Sjkh * necessary. We have to dig the gecos info out of 5962311Sjkh * the user's password entry to see if the magic 5972311Sjkh * "universe(att)" string is present. 5982311Sjkh */ 5992311Sjkh 6002311Sjkh struct passwd *p; 6012311Sjkh char *s; 6022311Sjkh int i; 6032311Sjkh 6042311Sjkh p = getpwuid(u->uid); 6052311Sjkh (void) endpwent(); 6062311Sjkh 6072311Sjkh if (p == NULL) 6082311Sjkh return; 6092311Sjkh 6102311Sjkh s = p->pw_gecos; 6112311Sjkh 6122311Sjkh for (i = 0; i < 4; i++) 6132311Sjkh { 6142311Sjkh if ((s = strchr(s, ',')) == NULL) 6152311Sjkh return; 6162311Sjkh s++; 6172311Sjkh } 6182311Sjkh if (strcmp(s, "universe(att)")) 6192311Sjkh return; 6202311Sjkh 6212311Sjkh (void) universe(U_ATT); 6222311Sjkh#endif 6232311Sjkh} 624