do_command.c revision 62376
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: head/usr.sbin/cron/cron/do_command.c 62376 2000-07-02 04:15:15Z ache $"; 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 352311Sjkh 362311Sjkh 372311Sjkhstatic void child_process __P((entry *, user *)), 382311Sjkh do_univ __P((user *)); 392311Sjkh 402311Sjkh 412311Sjkhvoid 422311Sjkhdo_command(e, u) 432311Sjkh entry *e; 442311Sjkh user *u; 452311Sjkh{ 462311Sjkh Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", 472311Sjkh getpid(), e->cmd, u->name, e->uid, e->gid)) 482311Sjkh 492311Sjkh /* fork to become asynchronous -- parent process is done immediately, 502311Sjkh * and continues to run the normal cron code, which means return to 512311Sjkh * tick(). the child and grandchild don't leave this function, alive. 522311Sjkh * 532311Sjkh * vfork() is unsuitable, since we have much to do, and the parent 542311Sjkh * needs to be able to run off and fork other processes. 552311Sjkh */ 562311Sjkh switch (fork()) { 572311Sjkh case -1: 582311Sjkh log_it("CRON",getpid(),"error","can't fork"); 592311Sjkh break; 602311Sjkh case 0: 612311Sjkh /* child process */ 622311Sjkh acquire_daemonlock(1); 632311Sjkh child_process(e, u); 642311Sjkh Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) 652311Sjkh _exit(OK_EXIT); 662311Sjkh break; 672311Sjkh default: 682311Sjkh /* parent process */ 692311Sjkh break; 702311Sjkh } 712311Sjkh Debug(DPROC, ("[%d] main process returning to work\n", getpid())) 722311Sjkh} 732311Sjkh 742311Sjkh 752311Sjkhstatic void 762311Sjkhchild_process(e, u) 772311Sjkh entry *e; 782311Sjkh user *u; 792311Sjkh{ 802311Sjkh int stdin_pipe[2], stdout_pipe[2]; 812311Sjkh register char *input_data; 822311Sjkh char *usernm, *mailto; 832311Sjkh int children = 0; 8421895Sdavidn# if defined(LOGIN_CAP) 8523884Speter struct passwd *pwd; 8630895Sache login_cap_t *lc; 8721895Sdavidn# endif 882311Sjkh 892311Sjkh Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd)) 902311Sjkh 912311Sjkh /* mark ourselves as different to PS command watchers by upshifting 922311Sjkh * our program name. This has no effect on some kernels. 932311Sjkh */ 942311Sjkh /*local*/{ 952311Sjkh register char *pch; 962311Sjkh 972311Sjkh for (pch = ProgramName; *pch; pch++) 982311Sjkh *pch = MkUpper(*pch); 992311Sjkh } 1002311Sjkh 1012311Sjkh /* discover some useful and important environment settings 1022311Sjkh */ 1032311Sjkh usernm = env_get("LOGNAME", e->envp); 1042311Sjkh mailto = env_get("MAILTO", e->envp); 1057809Sache 1062311Sjkh#ifdef USE_SIGCHLD 1072311Sjkh /* our parent is watching for our death by catching SIGCHLD. we 1082311Sjkh * do not care to watch for our children's deaths this way -- we 1092311Sjkh * use wait() explictly. so we have to disable the signal (which 1102311Sjkh * was inherited from the parent). 1112311Sjkh */ 11262359Sache (void) signal(SIGCHLD, SIG_DFL); 1132311Sjkh#else 1142311Sjkh /* on system-V systems, we are ignoring SIGCLD. we have to stop 1152311Sjkh * ignoring it now or the wait() in cron_pclose() won't work. 1162311Sjkh * because of this, we have to wait() for our children here, as well. 1172311Sjkh */ 1182311Sjkh (void) signal(SIGCLD, SIG_DFL); 1192311Sjkh#endif /*BSD*/ 1202311Sjkh 1212311Sjkh /* create some pipes to talk to our future child 1222311Sjkh */ 1232311Sjkh pipe(stdin_pipe); /* child's stdin */ 1242311Sjkh pipe(stdout_pipe); /* child's stdout */ 1258857Srgrimes 1262311Sjkh /* since we are a forked process, we can diddle the command string 1272311Sjkh * we were passed -- nobody else is going to use it again, right? 1282311Sjkh * 1292311Sjkh * if a % is present in the command, previous characters are the 1302311Sjkh * command, and subsequent characters are the additional input to 1312311Sjkh * the command. Subsequent %'s will be transformed into newlines, 1322311Sjkh * but that happens later. 13310660Sjoerg * 13410660Sjoerg * If there are escaped %'s, remove the escape character. 1352311Sjkh */ 1362311Sjkh /*local*/{ 1372311Sjkh register int escaped = FALSE; 1382311Sjkh register int ch; 13910660Sjoerg register char *p; 1402311Sjkh 14129452Scharnier for (input_data = p = e->cmd; (ch = *input_data); 14210660Sjoerg input_data++, p++) { 14310660Sjoerg if (p != input_data) 14410660Sjoerg *p = ch; 1452311Sjkh if (escaped) { 14610660Sjoerg if (ch == '%' || ch == '\\') 14710660Sjoerg *--p = ch; 1482311Sjkh escaped = FALSE; 1492311Sjkh continue; 1502311Sjkh } 1512311Sjkh if (ch == '\\') { 1522311Sjkh escaped = TRUE; 1532311Sjkh continue; 1542311Sjkh } 1552311Sjkh if (ch == '%') { 1562311Sjkh *input_data++ = '\0'; 1572311Sjkh break; 1582311Sjkh } 1592311Sjkh } 16010660Sjoerg *p = '\0'; 1612311Sjkh } 1622311Sjkh 1632311Sjkh /* fork again, this time so we can exec the user's command. 1642311Sjkh */ 1652311Sjkh switch (vfork()) { 1662311Sjkh case -1: 1672311Sjkh log_it("CRON",getpid(),"error","can't vfork"); 1682311Sjkh exit(ERROR_EXIT); 1692311Sjkh /*NOTREACHED*/ 1702311Sjkh case 0: 1712311Sjkh Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n", 1722311Sjkh getpid())) 1732311Sjkh 1742311Sjkh /* write a log message. we've waited this long to do it 1752311Sjkh * because it was not until now that we knew the PID that 1762311Sjkh * the actual user command shell was going to get and the 1772311Sjkh * PID is part of the log message. 1782311Sjkh */ 1792311Sjkh /*local*/{ 1802311Sjkh char *x = mkprints((u_char *)e->cmd, strlen(e->cmd)); 1812311Sjkh 1822311Sjkh log_it(usernm, getpid(), "CMD", x); 1832311Sjkh free(x); 1842311Sjkh } 1852311Sjkh 1862311Sjkh /* that's the last thing we'll log. close the log files. 1872311Sjkh */ 1882311Sjkh#ifdef SYSLOG 1892311Sjkh closelog(); 1902311Sjkh#endif 1912311Sjkh 1922311Sjkh /* get new pgrp, void tty, etc. 1932311Sjkh */ 1942311Sjkh (void) setsid(); 1952311Sjkh 1962311Sjkh /* close the pipe ends that we won't use. this doesn't affect 1972311Sjkh * the parent, who has to read and write them; it keeps the 1982311Sjkh * kernel from recording us as a potential client TWICE -- 1992311Sjkh * which would keep it from sending SIGPIPE in otherwise 2002311Sjkh * appropriate circumstances. 2012311Sjkh */ 2022311Sjkh close(stdin_pipe[WRITE_PIPE]); 2032311Sjkh close(stdout_pipe[READ_PIPE]); 2042311Sjkh 2052311Sjkh /* grandchild process. make std{in,out} be the ends of 2062311Sjkh * pipes opened by our daddy; make stderr go to stdout. 2072311Sjkh */ 2082311Sjkh close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN); 2092311Sjkh close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT); 2102311Sjkh close(STDERR); dup2(STDOUT, STDERR); 2112311Sjkh 2122311Sjkh /* close the pipes we just dup'ed. The resources will remain. 2132311Sjkh */ 2142311Sjkh close(stdin_pipe[READ_PIPE]); 2152311Sjkh close(stdout_pipe[WRITE_PIPE]); 2162311Sjkh 2172311Sjkh /* set our login universe. Do this in the grandchild 2182311Sjkh * so that the child can invoke /usr/lib/sendmail 2192311Sjkh * without surprises. 2202311Sjkh */ 2212311Sjkh do_univ(u); 2222311Sjkh 22321895Sdavidn# if defined(LOGIN_CAP) 22421895Sdavidn /* Set user's entire context, but skip the environment 22521895Sdavidn * as cron provides a separate interface for this 22621895Sdavidn */ 22730895Sache if ((pwd = getpwnam(usernm)) == NULL) 22830895Sache pwd = getpwuid(e->uid); 22930895Sache lc = NULL; 23030895Sache if (pwd != NULL) { 23130895Sache pwd->pw_gid = e->gid; 23230895Sache if (e->class != NULL) 23330895Sache lc = login_getclass(e->class); 23430895Sache } 23523886Speter if (pwd && 23630895Sache setusercontext(lc, pwd, e->uid, 23723886Speter LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0) 23823886Speter (void) endpwent(); 23923886Speter else { 24023884Speter /* fall back to the old method */ 24123886Speter (void) endpwent(); 24223884Speter# endif 24323884Speter /* set our directory, uid and gid. Set gid first, 24423884Speter * since once we set uid, we've lost root privledges. 24523884Speter */ 24623884Speter setgid(e->gid); 2472311Sjkh# if defined(BSD) 24830895Sache initgroups(usernm, e->gid); 2492311Sjkh# endif 25023884Speter setlogin(usernm); 25123884Speter setuid(e->uid); /* we aren't root after this..*/ 25223884Speter#if defined(LOGIN_CAP) 25323884Speter } 25462376Sache if (lc != NULL) 25562376Sache login_close(lc); 25621895Sdavidn#endif 25720573Spst chdir(env_get("HOME", e->envp)); 2582311Sjkh 2592311Sjkh /* exec the command. 2602311Sjkh */ 2612311Sjkh { 2622311Sjkh char *shell = env_get("SHELL", e->envp); 2632311Sjkh 2642311Sjkh# if DEBUGGING 2652311Sjkh if (DebugFlags & DTEST) { 2662311Sjkh fprintf(stderr, 2672311Sjkh "debug DTEST is on, not exec'ing command.\n"); 2682311Sjkh fprintf(stderr, 2692311Sjkh "\tcmd='%s' shell='%s'\n", e->cmd, shell); 2702311Sjkh _exit(OK_EXIT); 2712311Sjkh } 2722311Sjkh# endif /*DEBUGGING*/ 2732311Sjkh execle(shell, shell, "-c", e->cmd, (char *)0, e->envp); 27429452Scharnier warn("execl: couldn't exec `%s'", shell); 2752311Sjkh _exit(ERROR_EXIT); 2762311Sjkh } 2772311Sjkh break; 2782311Sjkh default: 2792311Sjkh /* parent process */ 2802311Sjkh break; 2812311Sjkh } 2822311Sjkh 2832311Sjkh children++; 2842311Sjkh 2852311Sjkh /* middle process, child of original cron, parent of process running 2862311Sjkh * the user's command. 2872311Sjkh */ 2882311Sjkh 2892311Sjkh Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid())) 2902311Sjkh 2912311Sjkh /* close the ends of the pipe that will only be referenced in the 2922311Sjkh * grandchild process... 2932311Sjkh */ 2942311Sjkh close(stdin_pipe[READ_PIPE]); 2952311Sjkh close(stdout_pipe[WRITE_PIPE]); 2962311Sjkh 2972311Sjkh /* 2982311Sjkh * write, to the pipe connected to child's stdin, any input specified 2992311Sjkh * after a % in the crontab entry. while we copy, convert any 3002311Sjkh * additional %'s to newlines. when done, if some characters were 3012311Sjkh * written and the last one wasn't a newline, write a newline. 3022311Sjkh * 3032311Sjkh * Note that if the input data won't fit into one pipe buffer (2K 3042311Sjkh * or 4K on most BSD systems), and the child doesn't read its stdin, 3052311Sjkh * we would block here. thus we must fork again. 3062311Sjkh */ 3072311Sjkh 3082311Sjkh if (*input_data && fork() == 0) { 3092311Sjkh register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); 3102311Sjkh register int need_newline = FALSE; 3112311Sjkh register int escaped = FALSE; 3122311Sjkh register int ch; 3132311Sjkh 31460826Sghelmer if (out == NULL) { 31560826Sghelmer warn("fdopen failed in child2"); 31660826Sghelmer _exit(ERROR_EXIT); 31760826Sghelmer } 31860826Sghelmer 3192311Sjkh Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) 3202311Sjkh 3212311Sjkh /* close the pipe we don't use, since we inherited it and 3222311Sjkh * are part of its reference count now. 3232311Sjkh */ 3242311Sjkh close(stdout_pipe[READ_PIPE]); 3252311Sjkh 3262311Sjkh /* translation: 3272311Sjkh * \% -> % 3282311Sjkh * % -> \n 3292311Sjkh * \x -> \x for all x != % 3302311Sjkh */ 33129452Scharnier while ((ch = *input_data++)) { 3322311Sjkh if (escaped) { 3332311Sjkh if (ch != '%') 3342311Sjkh putc('\\', out); 3352311Sjkh } else { 3362311Sjkh if (ch == '%') 3372311Sjkh ch = '\n'; 3382311Sjkh } 3392311Sjkh 3402311Sjkh if (!(escaped = (ch == '\\'))) { 3412311Sjkh putc(ch, out); 3422311Sjkh need_newline = (ch != '\n'); 3432311Sjkh } 3442311Sjkh } 3452311Sjkh if (escaped) 3462311Sjkh putc('\\', out); 3472311Sjkh if (need_newline) 3482311Sjkh putc('\n', out); 3492311Sjkh 3502311Sjkh /* close the pipe, causing an EOF condition. fclose causes 3512311Sjkh * stdin_pipe[WRITE_PIPE] to be closed, too. 3522311Sjkh */ 3532311Sjkh fclose(out); 3542311Sjkh 3552311Sjkh Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid())) 3562311Sjkh exit(0); 3572311Sjkh } 3582311Sjkh 3592311Sjkh /* close the pipe to the grandkiddie's stdin, since its wicked uncle 3602311Sjkh * ernie back there has it open and will close it when he's done. 3612311Sjkh */ 3622311Sjkh close(stdin_pipe[WRITE_PIPE]); 3632311Sjkh 3642311Sjkh children++; 3652311Sjkh 3662311Sjkh /* 3672311Sjkh * read output from the grandchild. it's stderr has been redirected to 3682311Sjkh * it's stdout, which has been redirected to our pipe. if there is any 3692311Sjkh * output, we'll be mailing it to the user whose crontab this is... 3702311Sjkh * when the grandchild exits, we'll get EOF. 3712311Sjkh */ 3722311Sjkh 3732311Sjkh Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) 3742311Sjkh 3752311Sjkh /*local*/{ 3762311Sjkh register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); 3772311Sjkh register int ch = getc(in); 3782311Sjkh 37960826Sghelmer if (in == NULL) { 38060826Sghelmer warn("fdopen failed in child"); 38160826Sghelmer _exit(ERROR_EXIT); 38260826Sghelmer } 38360826Sghelmer 3842311Sjkh if (ch != EOF) { 3852311Sjkh register FILE *mail; 3862311Sjkh register int bytes = 1; 3872311Sjkh int status = 0; 3882311Sjkh 3892311Sjkh Debug(DPROC|DEXT, 3902311Sjkh ("[%d] got data (%x:%c) from grandchild\n", 3912311Sjkh getpid(), ch, ch)) 3922311Sjkh 3932311Sjkh /* get name of recipient. this is MAILTO if set to a 3942311Sjkh * valid local username; USER otherwise. 3952311Sjkh */ 3962311Sjkh if (mailto) { 3972311Sjkh /* MAILTO was present in the environment 3982311Sjkh */ 3992311Sjkh if (!*mailto) { 4002311Sjkh /* ... but it's empty. set to NULL 4012311Sjkh */ 4022311Sjkh mailto = NULL; 4032311Sjkh } 4042311Sjkh } else { 4052311Sjkh /* MAILTO not present, set to USER. 4062311Sjkh */ 4072311Sjkh mailto = usernm; 4082311Sjkh } 4098857Srgrimes 4102311Sjkh /* if we are supposed to be mailing, MAILTO will 4112311Sjkh * be non-NULL. only in this case should we set 4122311Sjkh * up the mail command and subjects and stuff... 4132311Sjkh */ 4142311Sjkh 4152311Sjkh if (mailto) { 4162311Sjkh register char **env; 4172311Sjkh auto char mailcmd[MAX_COMMAND]; 4182311Sjkh auto char hostname[MAXHOSTNAMELEN]; 4192311Sjkh 4202311Sjkh (void) gethostname(hostname, MAXHOSTNAMELEN); 42120573Spst (void) snprintf(mailcmd, sizeof(mailcmd), 42220573Spst MAILARGS, MAILCMD); 42362367Sache if (!(mail = cron_popen(mailcmd, "w", e))) { 42429452Scharnier warn("%s", MAILCMD); 4252311Sjkh (void) _exit(ERROR_EXIT); 4262311Sjkh } 42762367Sache fprintf(mail, "From: %s (Cron Daemon)\n", usernm); 4282311Sjkh fprintf(mail, "To: %s\n", mailto); 4292311Sjkh fprintf(mail, "Subject: Cron <%s@%s> %s\n", 4302311Sjkh usernm, first_word(hostname, "."), 4312311Sjkh e->cmd); 4322311Sjkh# if defined(MAIL_DATE) 4332311Sjkh fprintf(mail, "Date: %s\n", 4342311Sjkh arpadate(&TargetTime)); 4352311Sjkh# endif /* MAIL_DATE */ 4362311Sjkh for (env = e->envp; *env; env++) 4372311Sjkh fprintf(mail, "X-Cron-Env: <%s>\n", 4382311Sjkh *env); 4392311Sjkh fprintf(mail, "\n"); 4402311Sjkh 4412311Sjkh /* this was the first char from the pipe 4422311Sjkh */ 4432311Sjkh putc(ch, mail); 4442311Sjkh } 4452311Sjkh 4462311Sjkh /* we have to read the input pipe no matter whether 4472311Sjkh * we mail or not, but obviously we only write to 4482311Sjkh * mail pipe if we ARE mailing. 4492311Sjkh */ 4502311Sjkh 4512311Sjkh while (EOF != (ch = getc(in))) { 4522311Sjkh bytes++; 4532311Sjkh if (mailto) 4542311Sjkh putc(ch, mail); 4552311Sjkh } 4562311Sjkh 4572311Sjkh /* only close pipe if we opened it -- i.e., we're 4582311Sjkh * mailing... 4592311Sjkh */ 4602311Sjkh 4612311Sjkh if (mailto) { 4622311Sjkh Debug(DPROC, ("[%d] closing pipe to mail\n", 4632311Sjkh getpid())) 4642311Sjkh /* Note: the pclose will probably see 4652311Sjkh * the termination of the grandchild 4662311Sjkh * in addition to the mail process, since 4672311Sjkh * it (the grandchild) is likely to exit 4682311Sjkh * after closing its stdout. 4692311Sjkh */ 4702311Sjkh status = cron_pclose(mail); 4712311Sjkh } 4722311Sjkh 4732311Sjkh /* if there was output and we could not mail it, 4742311Sjkh * log the facts so the poor user can figure out 4752311Sjkh * what's going on. 4762311Sjkh */ 4772311Sjkh if (mailto && status) { 4782311Sjkh char buf[MAX_TEMPSTR]; 4792311Sjkh 48020573Spst snprintf(buf, sizeof(buf), 4812311Sjkh "mailed %d byte%s of output but got status 0x%04x\n", 4822311Sjkh bytes, (bytes==1)?"":"s", 4832311Sjkh status); 4842311Sjkh log_it(usernm, getpid(), "MAIL", buf); 4852311Sjkh } 4862311Sjkh 4872311Sjkh } /*if data from grandchild*/ 4882311Sjkh 4892311Sjkh Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid())) 4902311Sjkh 4912311Sjkh fclose(in); /* also closes stdout_pipe[READ_PIPE] */ 4922311Sjkh } 4932311Sjkh 4942311Sjkh /* wait for children to die. 4952311Sjkh */ 4962311Sjkh for (; children > 0; children--) 4972311Sjkh { 4982311Sjkh WAIT_T waiter; 4992311Sjkh PID_T pid; 5002311Sjkh 5012311Sjkh Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n", 5022311Sjkh getpid(), children)) 5032311Sjkh pid = wait(&waiter); 5042311Sjkh if (pid < OK) { 5052311Sjkh Debug(DPROC, ("[%d] no more grandchildren--mail written?\n", 5062311Sjkh getpid())) 5072311Sjkh break; 5082311Sjkh } 5092311Sjkh Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x", 5102311Sjkh getpid(), pid, WEXITSTATUS(waiter))) 5112311Sjkh if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) 5122311Sjkh Debug(DPROC, (", dumped core")) 5132311Sjkh Debug(DPROC, ("\n")) 5142311Sjkh } 5152311Sjkh} 5162311Sjkh 5172311Sjkh 5182311Sjkhstatic void 5192311Sjkhdo_univ(u) 5202311Sjkh user *u; 5212311Sjkh{ 5222311Sjkh#if defined(sequent) 5232311Sjkh/* Dynix (Sequent) hack to put the user associated with 5242311Sjkh * the passed user structure into the ATT universe if 5252311Sjkh * necessary. We have to dig the gecos info out of 5262311Sjkh * the user's password entry to see if the magic 5272311Sjkh * "universe(att)" string is present. 5282311Sjkh */ 5292311Sjkh 5302311Sjkh struct passwd *p; 5312311Sjkh char *s; 5322311Sjkh int i; 5332311Sjkh 5342311Sjkh p = getpwuid(u->uid); 5352311Sjkh (void) endpwent(); 5362311Sjkh 5372311Sjkh if (p == NULL) 5382311Sjkh return; 5392311Sjkh 5402311Sjkh s = p->pw_gecos; 5412311Sjkh 5422311Sjkh for (i = 0; i < 4; i++) 5432311Sjkh { 5442311Sjkh if ((s = strchr(s, ',')) == NULL) 5452311Sjkh return; 5462311Sjkh s++; 5472311Sjkh } 5482311Sjkh if (strcmp(s, "universe(att)")) 5492311Sjkh return; 5502311Sjkh 5512311Sjkh (void) universe(U_ATT); 5522311Sjkh#endif 5532311Sjkh} 554