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$"; 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 */ 1642311Sjkh pipe(stdin_pipe); /* child's stdin */ 1652311Sjkh pipe(stdout_pipe); /* child's stdout */ 1668857Srgrimes 1672311Sjkh /* since we are a forked process, we can diddle the command string 1682311Sjkh * we were passed -- nobody else is going to use it again, right? 1692311Sjkh * 1702311Sjkh * if a % is present in the command, previous characters are the 1712311Sjkh * command, and subsequent characters are the additional input to 1722311Sjkh * the command. Subsequent %'s will be transformed into newlines, 1732311Sjkh * but that happens later. 17410660Sjoerg * 17510660Sjoerg * If there are escaped %'s, remove the escape character. 1762311Sjkh */ 1772311Sjkh /*local*/{ 1782311Sjkh register int escaped = FALSE; 1792311Sjkh register int ch; 18010660Sjoerg register char *p; 1812311Sjkh 18229452Scharnier for (input_data = p = e->cmd; (ch = *input_data); 18310660Sjoerg input_data++, p++) { 18410660Sjoerg if (p != input_data) 18510660Sjoerg *p = ch; 1862311Sjkh if (escaped) { 18710660Sjoerg if (ch == '%' || ch == '\\') 18810660Sjoerg *--p = ch; 1892311Sjkh escaped = FALSE; 1902311Sjkh continue; 1912311Sjkh } 1922311Sjkh if (ch == '\\') { 1932311Sjkh escaped = TRUE; 1942311Sjkh continue; 1952311Sjkh } 1962311Sjkh if (ch == '%') { 1972311Sjkh *input_data++ = '\0'; 1982311Sjkh break; 1992311Sjkh } 2002311Sjkh } 20110660Sjoerg *p = '\0'; 2022311Sjkh } 2032311Sjkh 2042311Sjkh /* fork again, this time so we can exec the user's command. 2052311Sjkh */ 2062311Sjkh switch (vfork()) { 2072311Sjkh case -1: 2082311Sjkh log_it("CRON",getpid(),"error","can't vfork"); 2092311Sjkh exit(ERROR_EXIT); 2102311Sjkh /*NOTREACHED*/ 2112311Sjkh case 0: 2122311Sjkh Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n", 2132311Sjkh getpid())) 2142311Sjkh 215129280Syar if (e->uid == ROOT_UID) 216129280Syar Jitter = RootJitter; 217129280Syar if (Jitter != 0) { 218129280Syar srandom(getpid()); 219129280Syar sleep(random() % Jitter); 220129280Syar } 221129280Syar 2222311Sjkh /* write a log message. we've waited this long to do it 2232311Sjkh * because it was not until now that we knew the PID that 2242311Sjkh * the actual user command shell was going to get and the 2252311Sjkh * PID is part of the log message. 2262311Sjkh */ 2272311Sjkh /*local*/{ 2282311Sjkh char *x = mkprints((u_char *)e->cmd, strlen(e->cmd)); 2292311Sjkh 2302311Sjkh log_it(usernm, getpid(), "CMD", x); 2312311Sjkh free(x); 2322311Sjkh } 2332311Sjkh 2342311Sjkh /* that's the last thing we'll log. close the log files. 2352311Sjkh */ 2362311Sjkh#ifdef SYSLOG 2372311Sjkh closelog(); 2382311Sjkh#endif 2392311Sjkh 2402311Sjkh /* get new pgrp, void tty, etc. 2412311Sjkh */ 2422311Sjkh (void) setsid(); 2432311Sjkh 2442311Sjkh /* close the pipe ends that we won't use. this doesn't affect 2452311Sjkh * the parent, who has to read and write them; it keeps the 2462311Sjkh * kernel from recording us as a potential client TWICE -- 2472311Sjkh * which would keep it from sending SIGPIPE in otherwise 2482311Sjkh * appropriate circumstances. 2492311Sjkh */ 2502311Sjkh close(stdin_pipe[WRITE_PIPE]); 2512311Sjkh close(stdout_pipe[READ_PIPE]); 2522311Sjkh 2532311Sjkh /* grandchild process. make std{in,out} be the ends of 2542311Sjkh * pipes opened by our daddy; make stderr go to stdout. 2552311Sjkh */ 2562311Sjkh close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN); 2572311Sjkh close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT); 2582311Sjkh close(STDERR); dup2(STDOUT, STDERR); 2592311Sjkh 2602311Sjkh /* close the pipes we just dup'ed. The resources will remain. 2612311Sjkh */ 2622311Sjkh close(stdin_pipe[READ_PIPE]); 2632311Sjkh close(stdout_pipe[WRITE_PIPE]); 2642311Sjkh 2652311Sjkh /* set our login universe. Do this in the grandchild 2662311Sjkh * so that the child can invoke /usr/lib/sendmail 2672311Sjkh * without surprises. 2682311Sjkh */ 2692311Sjkh do_univ(u); 2702311Sjkh 27121895Sdavidn# if defined(LOGIN_CAP) 27221895Sdavidn /* Set user's entire context, but skip the environment 27321895Sdavidn * as cron provides a separate interface for this 27421895Sdavidn */ 27530895Sache if ((pwd = getpwnam(usernm)) == NULL) 27630895Sache pwd = getpwuid(e->uid); 27730895Sache lc = NULL; 27830895Sache if (pwd != NULL) { 27930895Sache pwd->pw_gid = e->gid; 28030895Sache if (e->class != NULL) 28130895Sache lc = login_getclass(e->class); 28230895Sache } 28323886Speter if (pwd && 28430895Sache setusercontext(lc, pwd, e->uid, 28523886Speter LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0) 28623886Speter (void) endpwent(); 28723886Speter else { 28823884Speter /* fall back to the old method */ 28923886Speter (void) endpwent(); 29023884Speter# endif 29123884Speter /* set our directory, uid and gid. Set gid first, 292159527Smaxim * since once we set uid, we've lost root privileges. 29323884Speter */ 294159142Smaxim if (setgid(e->gid) != 0) { 295159142Smaxim log_it(usernm, getpid(), 296159142Smaxim "error", "setgid failed"); 297159142Smaxim exit(ERROR_EXIT); 298159142Smaxim } 2992311Sjkh# if defined(BSD) 300159142Smaxim if (initgroups(usernm, e->gid) != 0) { 301159142Smaxim log_it(usernm, getpid(), 302159142Smaxim "error", "initgroups failed"); 303159142Smaxim exit(ERROR_EXIT); 304159142Smaxim } 3052311Sjkh# endif 306159142Smaxim if (setlogin(usernm) != 0) { 307159142Smaxim log_it(usernm, getpid(), 308159142Smaxim "error", "setlogin failed"); 309159142Smaxim exit(ERROR_EXIT); 310159142Smaxim } 311159142Smaxim if (setuid(e->uid) != 0) { 312159142Smaxim log_it(usernm, getpid(), 313159142Smaxim "error", "setuid failed"); 314159142Smaxim exit(ERROR_EXIT); 315159142Smaxim } 316159142Smaxim /* we aren't root after this..*/ 31723884Speter#if defined(LOGIN_CAP) 31823884Speter } 31962376Sache if (lc != NULL) 32062376Sache login_close(lc); 32121895Sdavidn#endif 32220573Spst chdir(env_get("HOME", e->envp)); 3232311Sjkh 3242311Sjkh /* exec the command. 3252311Sjkh */ 3262311Sjkh { 3272311Sjkh char *shell = env_get("SHELL", e->envp); 3282311Sjkh 3292311Sjkh# if DEBUGGING 3302311Sjkh if (DebugFlags & DTEST) { 3312311Sjkh fprintf(stderr, 3322311Sjkh "debug DTEST is on, not exec'ing command.\n"); 3332311Sjkh fprintf(stderr, 3342311Sjkh "\tcmd='%s' shell='%s'\n", e->cmd, shell); 3352311Sjkh _exit(OK_EXIT); 3362311Sjkh } 3372311Sjkh# endif /*DEBUGGING*/ 3382311Sjkh execle(shell, shell, "-c", e->cmd, (char *)0, e->envp); 33929452Scharnier warn("execl: couldn't exec `%s'", shell); 3402311Sjkh _exit(ERROR_EXIT); 3412311Sjkh } 3422311Sjkh break; 3432311Sjkh default: 3442311Sjkh /* parent process */ 3452311Sjkh break; 3462311Sjkh } 3472311Sjkh 3482311Sjkh children++; 3492311Sjkh 3502311Sjkh /* middle process, child of original cron, parent of process running 3512311Sjkh * the user's command. 3522311Sjkh */ 3532311Sjkh 3542311Sjkh Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid())) 3552311Sjkh 3562311Sjkh /* close the ends of the pipe that will only be referenced in the 3572311Sjkh * grandchild process... 3582311Sjkh */ 3592311Sjkh close(stdin_pipe[READ_PIPE]); 3602311Sjkh close(stdout_pipe[WRITE_PIPE]); 3612311Sjkh 3622311Sjkh /* 3632311Sjkh * write, to the pipe connected to child's stdin, any input specified 3642311Sjkh * after a % in the crontab entry. while we copy, convert any 3652311Sjkh * additional %'s to newlines. when done, if some characters were 3662311Sjkh * written and the last one wasn't a newline, write a newline. 3672311Sjkh * 3682311Sjkh * Note that if the input data won't fit into one pipe buffer (2K 3692311Sjkh * or 4K on most BSD systems), and the child doesn't read its stdin, 3702311Sjkh * we would block here. thus we must fork again. 3712311Sjkh */ 3722311Sjkh 3732311Sjkh if (*input_data && fork() == 0) { 3742311Sjkh register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); 3752311Sjkh register int need_newline = FALSE; 3762311Sjkh register int escaped = FALSE; 3772311Sjkh register int ch; 3782311Sjkh 37960826Sghelmer if (out == NULL) { 38060826Sghelmer warn("fdopen failed in child2"); 38160826Sghelmer _exit(ERROR_EXIT); 38260826Sghelmer } 38360826Sghelmer 3842311Sjkh Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) 3852311Sjkh 3862311Sjkh /* close the pipe we don't use, since we inherited it and 3872311Sjkh * are part of its reference count now. 3882311Sjkh */ 3892311Sjkh close(stdout_pipe[READ_PIPE]); 3902311Sjkh 3912311Sjkh /* translation: 3922311Sjkh * \% -> % 3932311Sjkh * % -> \n 3942311Sjkh * \x -> \x for all x != % 3952311Sjkh */ 39629452Scharnier while ((ch = *input_data++)) { 3972311Sjkh if (escaped) { 3982311Sjkh if (ch != '%') 3992311Sjkh putc('\\', out); 4002311Sjkh } else { 4012311Sjkh if (ch == '%') 4022311Sjkh ch = '\n'; 4032311Sjkh } 4042311Sjkh 4052311Sjkh if (!(escaped = (ch == '\\'))) { 4062311Sjkh putc(ch, out); 4072311Sjkh need_newline = (ch != '\n'); 4082311Sjkh } 4092311Sjkh } 4102311Sjkh if (escaped) 4112311Sjkh putc('\\', out); 4122311Sjkh if (need_newline) 4132311Sjkh putc('\n', out); 4142311Sjkh 4152311Sjkh /* close the pipe, causing an EOF condition. fclose causes 4162311Sjkh * stdin_pipe[WRITE_PIPE] to be closed, too. 4172311Sjkh */ 4182311Sjkh fclose(out); 4192311Sjkh 4202311Sjkh Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid())) 4212311Sjkh exit(0); 4222311Sjkh } 4232311Sjkh 4242311Sjkh /* close the pipe to the grandkiddie's stdin, since its wicked uncle 4252311Sjkh * ernie back there has it open and will close it when he's done. 4262311Sjkh */ 4272311Sjkh close(stdin_pipe[WRITE_PIPE]); 4282311Sjkh 4292311Sjkh children++; 4302311Sjkh 4312311Sjkh /* 4322311Sjkh * read output from the grandchild. it's stderr has been redirected to 4332311Sjkh * it's stdout, which has been redirected to our pipe. if there is any 4342311Sjkh * output, we'll be mailing it to the user whose crontab this is... 4352311Sjkh * when the grandchild exits, we'll get EOF. 4362311Sjkh */ 4372311Sjkh 4382311Sjkh Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) 4392311Sjkh 4402311Sjkh /*local*/{ 4412311Sjkh register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); 442116590Ssilby register int ch; 4432311Sjkh 44460826Sghelmer if (in == NULL) { 44560826Sghelmer warn("fdopen failed in child"); 44660826Sghelmer _exit(ERROR_EXIT); 44760826Sghelmer } 44860826Sghelmer 449116590Ssilby ch = getc(in); 4502311Sjkh if (ch != EOF) { 4512311Sjkh register FILE *mail; 4522311Sjkh register int bytes = 1; 4532311Sjkh int status = 0; 4542311Sjkh 4552311Sjkh Debug(DPROC|DEXT, 4562311Sjkh ("[%d] got data (%x:%c) from grandchild\n", 4572311Sjkh getpid(), ch, ch)) 4582311Sjkh 4592311Sjkh /* get name of recipient. this is MAILTO if set to a 4602311Sjkh * valid local username; USER otherwise. 4612311Sjkh */ 462180096Smarck if (mailto == NULL) { 463180096Smarck /* MAILTO not present, set to USER, 464180096Smarck * unless globally overriden. 4652311Sjkh */ 466180096Smarck if (defmailto) 467180096Smarck mailto = defmailto; 468180096Smarck else 469180096Smarck mailto = usernm; 4702311Sjkh } 471181115Smarck if (mailto && *mailto == '\0') 472181115Smarck mailto = NULL; 4738857Srgrimes 4742311Sjkh /* if we are supposed to be mailing, MAILTO will 4752311Sjkh * be non-NULL. only in this case should we set 4762311Sjkh * up the mail command and subjects and stuff... 4772311Sjkh */ 4782311Sjkh 479181115Smarck if (mailto) { 4802311Sjkh register char **env; 4812311Sjkh auto char mailcmd[MAX_COMMAND]; 4822311Sjkh auto char hostname[MAXHOSTNAMELEN]; 4832311Sjkh 4842311Sjkh (void) gethostname(hostname, MAXHOSTNAMELEN); 48520573Spst (void) snprintf(mailcmd, sizeof(mailcmd), 48620573Spst MAILARGS, MAILCMD); 48762367Sache if (!(mail = cron_popen(mailcmd, "w", e))) { 48829452Scharnier warn("%s", MAILCMD); 4892311Sjkh (void) _exit(ERROR_EXIT); 4902311Sjkh } 49162367Sache fprintf(mail, "From: %s (Cron Daemon)\n", usernm); 4922311Sjkh fprintf(mail, "To: %s\n", mailto); 4932311Sjkh fprintf(mail, "Subject: Cron <%s@%s> %s\n", 4942311Sjkh usernm, first_word(hostname, "."), 4952311Sjkh e->cmd); 4962311Sjkh# if defined(MAIL_DATE) 4972311Sjkh fprintf(mail, "Date: %s\n", 4982311Sjkh arpadate(&TargetTime)); 4992311Sjkh# endif /* MAIL_DATE */ 5002311Sjkh for (env = e->envp; *env; env++) 5012311Sjkh fprintf(mail, "X-Cron-Env: <%s>\n", 5022311Sjkh *env); 5032311Sjkh fprintf(mail, "\n"); 5042311Sjkh 5052311Sjkh /* this was the first char from the pipe 5062311Sjkh */ 5072311Sjkh putc(ch, mail); 5082311Sjkh } 5092311Sjkh 5102311Sjkh /* we have to read the input pipe no matter whether 5112311Sjkh * we mail or not, but obviously we only write to 5122311Sjkh * mail pipe if we ARE mailing. 5132311Sjkh */ 5142311Sjkh 5152311Sjkh while (EOF != (ch = getc(in))) { 5162311Sjkh bytes++; 5172311Sjkh if (mailto) 5182311Sjkh putc(ch, mail); 5192311Sjkh } 5202311Sjkh 5212311Sjkh /* only close pipe if we opened it -- i.e., we're 5222311Sjkh * mailing... 5232311Sjkh */ 5242311Sjkh 5252311Sjkh if (mailto) { 5262311Sjkh Debug(DPROC, ("[%d] closing pipe to mail\n", 5272311Sjkh getpid())) 5282311Sjkh /* Note: the pclose will probably see 5292311Sjkh * the termination of the grandchild 5302311Sjkh * in addition to the mail process, since 5312311Sjkh * it (the grandchild) is likely to exit 5322311Sjkh * after closing its stdout. 5332311Sjkh */ 5342311Sjkh status = cron_pclose(mail); 5352311Sjkh } 5362311Sjkh 5372311Sjkh /* if there was output and we could not mail it, 5382311Sjkh * log the facts so the poor user can figure out 5392311Sjkh * what's going on. 5402311Sjkh */ 5412311Sjkh if (mailto && status) { 5422311Sjkh char buf[MAX_TEMPSTR]; 5432311Sjkh 54420573Spst snprintf(buf, sizeof(buf), 5452311Sjkh "mailed %d byte%s of output but got status 0x%04x\n", 5462311Sjkh bytes, (bytes==1)?"":"s", 5472311Sjkh status); 5482311Sjkh log_it(usernm, getpid(), "MAIL", buf); 5492311Sjkh } 5502311Sjkh 5512311Sjkh } /*if data from grandchild*/ 5522311Sjkh 5532311Sjkh Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid())) 5542311Sjkh 5552311Sjkh fclose(in); /* also closes stdout_pipe[READ_PIPE] */ 5562311Sjkh } 5572311Sjkh 5582311Sjkh /* wait for children to die. 5592311Sjkh */ 5602311Sjkh for (; children > 0; children--) 5612311Sjkh { 5622311Sjkh WAIT_T waiter; 5632311Sjkh PID_T pid; 5642311Sjkh 5652311Sjkh Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n", 5662311Sjkh getpid(), children)) 5672311Sjkh pid = wait(&waiter); 5682311Sjkh if (pid < OK) { 5692311Sjkh Debug(DPROC, ("[%d] no more grandchildren--mail written?\n", 5702311Sjkh getpid())) 5712311Sjkh break; 5722311Sjkh } 5732311Sjkh Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x", 5742311Sjkh getpid(), pid, WEXITSTATUS(waiter))) 5752311Sjkh if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) 5762311Sjkh Debug(DPROC, (", dumped core")) 5772311Sjkh Debug(DPROC, ("\n")) 5782311Sjkh } 5792311Sjkh} 5802311Sjkh 5812311Sjkh 5822311Sjkhstatic void 5832311Sjkhdo_univ(u) 5842311Sjkh user *u; 5852311Sjkh{ 5862311Sjkh#if defined(sequent) 5872311Sjkh/* Dynix (Sequent) hack to put the user associated with 5882311Sjkh * the passed user structure into the ATT universe if 5892311Sjkh * necessary. We have to dig the gecos info out of 5902311Sjkh * the user's password entry to see if the magic 5912311Sjkh * "universe(att)" string is present. 5922311Sjkh */ 5932311Sjkh 5942311Sjkh struct passwd *p; 5952311Sjkh char *s; 5962311Sjkh int i; 5972311Sjkh 5982311Sjkh p = getpwuid(u->uid); 5992311Sjkh (void) endpwent(); 6002311Sjkh 6012311Sjkh if (p == NULL) 6022311Sjkh return; 6032311Sjkh 6042311Sjkh s = p->pw_gecos; 6052311Sjkh 6062311Sjkh for (i = 0; i < 4; i++) 6072311Sjkh { 6082311Sjkh if ((s = strchr(s, ',')) == NULL) 6092311Sjkh return; 6102311Sjkh s++; 6112311Sjkh } 6122311Sjkh if (strcmp(s, "universe(att)")) 6132311Sjkh return; 6142311Sjkh 6152311Sjkh (void) universe(U_ATT); 6162311Sjkh#endif 6172311Sjkh} 618