1*e4d25771Stodd /* $OpenBSD: do_command.c,v 1.14 2002/06/14 21:35:01 todd Exp $ */ 2df930be7Sderaadt /* Copyright 1988,1990,1993,1994 by Paul Vixie 3df930be7Sderaadt * All rights reserved 4f454ebdeSmillert */ 5f454ebdeSmillert 6f454ebdeSmillert /* 7f454ebdeSmillert * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. 8df930be7Sderaadt * 9f454ebdeSmillert * Permission to use, copy, modify, and distribute this software for any 10f454ebdeSmillert * purpose with or without fee is hereby granted, provided that the above 11f454ebdeSmillert * copyright notice and this permission notice appear in all copies. 12df930be7Sderaadt * 13f454ebdeSmillert * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS 14f454ebdeSmillert * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 15f454ebdeSmillert * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE 16f454ebdeSmillert * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 17f454ebdeSmillert * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 18f454ebdeSmillert * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 19f454ebdeSmillert * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 20f454ebdeSmillert * SOFTWARE. 21df930be7Sderaadt */ 22df930be7Sderaadt 23df930be7Sderaadt #if !defined(lint) && !defined(LINT) 24*e4d25771Stodd static char rcsid[] = "$OpenBSD: do_command.c,v 1.14 2002/06/14 21:35:01 todd Exp $"; 25df930be7Sderaadt #endif 26df930be7Sderaadt 27df930be7Sderaadt #include "cron.h" 28df930be7Sderaadt 29f454ebdeSmillert static void child_process(entry *, user *); 30f454ebdeSmillert static int safe_p(const char *, const char *); 31df930be7Sderaadt 32df930be7Sderaadt void 33f454ebdeSmillert do_command(entry *e, user *u) { 34f454ebdeSmillert Debug(DPROC, ("[%ld] do_command(%s, (%s,%ld,%ld))\n", 35f454ebdeSmillert (long)getpid(), e->cmd, u->name, 36f454ebdeSmillert (long)e->uid, (long)e->gid)) 37df930be7Sderaadt 38df930be7Sderaadt /* fork to become asynchronous -- parent process is done immediately, 39df930be7Sderaadt * and continues to run the normal cron code, which means return to 40df930be7Sderaadt * tick(). the child and grandchild don't leave this function, alive. 41df930be7Sderaadt * 42df930be7Sderaadt * vfork() is unsuitable, since we have much to do, and the parent 43df930be7Sderaadt * needs to be able to run off and fork other processes. 44df930be7Sderaadt */ 45df930be7Sderaadt switch (fork()) { 46df930be7Sderaadt case -1: 47df930be7Sderaadt log_it("CRON", getpid(), "error", "can't fork"); 48df930be7Sderaadt break; 49df930be7Sderaadt case 0: 50df930be7Sderaadt /* child process */ 51df930be7Sderaadt acquire_daemonlock(1); 52df930be7Sderaadt child_process(e, u); 53f454ebdeSmillert Debug(DPROC, ("[%ld] child process done, exiting\n", 54f454ebdeSmillert (long)getpid())) 55df930be7Sderaadt _exit(OK_EXIT); 56df930be7Sderaadt break; 57df930be7Sderaadt default: 58df930be7Sderaadt /* parent process */ 59df930be7Sderaadt break; 60df930be7Sderaadt } 61f454ebdeSmillert Debug(DPROC, ("[%ld] main process returning to work\n",(long)getpid())) 62df930be7Sderaadt } 63df930be7Sderaadt 64df930be7Sderaadt static void 65f454ebdeSmillert child_process(entry *e, user *u) { 66df930be7Sderaadt int stdin_pipe[2], stdout_pipe[2]; 67f454ebdeSmillert char *usernm; 68d5919ef3Smillert char *input_data, *mailto; 69d5919ef3Smillert int children = 0; 70df930be7Sderaadt 71f454ebdeSmillert Debug(DPROC, ("[%ld] child_process('%s')\n", (long)getpid(), e->cmd)) 729f52c269Sderaadt 73f454ebdeSmillert #ifdef CAPITALIZE_FOR_PS 74df930be7Sderaadt /* mark ourselves as different to PS command watchers by upshifting 75df930be7Sderaadt * our program name. This has no effect on some kernels. 76df930be7Sderaadt */ 77df930be7Sderaadt /*local*/{ 78f454ebdeSmillert char *pch; 79df930be7Sderaadt 80df930be7Sderaadt for (pch = ProgramName; *pch; pch++) 81df930be7Sderaadt *pch = MkUpper(*pch); 82df930be7Sderaadt } 83f454ebdeSmillert #endif /* CAPITALIZE_FOR_PS */ 84df930be7Sderaadt 85df930be7Sderaadt /* discover some useful and important environment settings 86df930be7Sderaadt */ 87df930be7Sderaadt usernm = env_get("LOGNAME", e->envp); 88df930be7Sderaadt mailto = env_get("MAILTO", e->envp); 89df930be7Sderaadt 90df930be7Sderaadt /* our parent is watching for our death by catching SIGCHLD. we 91df930be7Sderaadt * do not care to watch for our children's deaths this way -- we 92*e4d25771Stodd * use wait() explicitly. so we have to reset the signal (which 93df930be7Sderaadt * was inherited from the parent). 94df930be7Sderaadt */ 955815f23fSmillert (void) signal(SIGCHLD, SIG_DFL); 96df930be7Sderaadt 97df930be7Sderaadt /* create some pipes to talk to our future child 98df930be7Sderaadt */ 99df930be7Sderaadt pipe(stdin_pipe); /* child's stdin */ 100df930be7Sderaadt pipe(stdout_pipe); /* child's stdout */ 101df930be7Sderaadt 102df930be7Sderaadt /* since we are a forked process, we can diddle the command string 103df930be7Sderaadt * we were passed -- nobody else is going to use it again, right? 104df930be7Sderaadt * 105df930be7Sderaadt * if a % is present in the command, previous characters are the 106df930be7Sderaadt * command, and subsequent characters are the additional input to 107df930be7Sderaadt * the command. Subsequent %'s will be transformed into newlines, 108df930be7Sderaadt * but that happens later. 109d94ebf94Sderaadt * 110d94ebf94Sderaadt * If there are escaped %'s, remove the escape character. 111df930be7Sderaadt */ 112df930be7Sderaadt /*local*/{ 113f454ebdeSmillert int escaped = FALSE; 114f454ebdeSmillert int ch; 115f454ebdeSmillert char *p; 116df930be7Sderaadt 117f454ebdeSmillert for (input_data = p = e->cmd; 118f454ebdeSmillert (ch = *input_data) != '\0'; 119d94ebf94Sderaadt input_data++, p++) { 120d94ebf94Sderaadt if (p != input_data) 121d94ebf94Sderaadt *p = ch; 122df930be7Sderaadt if (escaped) { 123d94ebf94Sderaadt if (ch == '%' || ch == '\\') 124d94ebf94Sderaadt *--p = ch; 125df930be7Sderaadt escaped = FALSE; 126df930be7Sderaadt continue; 127df930be7Sderaadt } 128df930be7Sderaadt if (ch == '\\') { 129df930be7Sderaadt escaped = TRUE; 130df930be7Sderaadt continue; 131df930be7Sderaadt } 132df930be7Sderaadt if (ch == '%') { 133df930be7Sderaadt *input_data++ = '\0'; 134df930be7Sderaadt break; 135df930be7Sderaadt } 136df930be7Sderaadt } 137d94ebf94Sderaadt *p = '\0'; 138df930be7Sderaadt } 139df930be7Sderaadt 140df930be7Sderaadt /* fork again, this time so we can exec the user's command. 141df930be7Sderaadt */ 14214852038Smillert switch (fork()) { 143df930be7Sderaadt case -1: 144df930be7Sderaadt log_it("CRON", getpid(), "error", "can't vfork"); 145df930be7Sderaadt exit(ERROR_EXIT); 146df930be7Sderaadt /*NOTREACHED*/ 147df930be7Sderaadt case 0: 148f454ebdeSmillert Debug(DPROC, ("[%ld] grandchild process Vfork()'ed\n", 149f454ebdeSmillert (long)getpid())) 150df930be7Sderaadt 151df930be7Sderaadt /* write a log message. we've waited this long to do it 152df930be7Sderaadt * because it was not until now that we knew the PID that 153df930be7Sderaadt * the actual user command shell was going to get and the 154df930be7Sderaadt * PID is part of the log message. 155df930be7Sderaadt */ 156f454ebdeSmillert if ((e->flags & DONT_LOG) == 0) { 157df930be7Sderaadt char *x = mkprints((u_char *)e->cmd, strlen(e->cmd)); 158df930be7Sderaadt 159df930be7Sderaadt log_it(usernm, getpid(), "CMD", x); 160df930be7Sderaadt free(x); 161df930be7Sderaadt } 162df930be7Sderaadt 163df930be7Sderaadt /* that's the last thing we'll log. close the log files. 164df930be7Sderaadt */ 165df930be7Sderaadt #ifdef SYSLOG 166df930be7Sderaadt closelog(); 167df930be7Sderaadt #endif 168df930be7Sderaadt 169df930be7Sderaadt /* get new pgrp, void tty, etc. 170df930be7Sderaadt */ 171df930be7Sderaadt (void) setsid(); 172df930be7Sderaadt 173df930be7Sderaadt /* close the pipe ends that we won't use. this doesn't affect 174df930be7Sderaadt * the parent, who has to read and write them; it keeps the 175df930be7Sderaadt * kernel from recording us as a potential client TWICE -- 176df930be7Sderaadt * which would keep it from sending SIGPIPE in otherwise 177df930be7Sderaadt * appropriate circumstances. 178df930be7Sderaadt */ 179df930be7Sderaadt close(stdin_pipe[WRITE_PIPE]); 180df930be7Sderaadt close(stdout_pipe[READ_PIPE]); 181df930be7Sderaadt 182df930be7Sderaadt /* grandchild process. make std{in,out} be the ends of 183df930be7Sderaadt * pipes opened by our daddy; make stderr go to stdout. 184df930be7Sderaadt */ 18514852038Smillert if (stdin_pipe[READ_PIPE] != STDIN) { 18614852038Smillert dup2(stdin_pipe[READ_PIPE], STDIN); 187df930be7Sderaadt close(stdin_pipe[READ_PIPE]); 18814852038Smillert } 18914852038Smillert if (stdout_pipe[WRITE_PIPE] != STDOUT) { 19014852038Smillert close(STDOUT); 19114852038Smillert dup2(stdout_pipe[WRITE_PIPE], STDOUT); 19214852038Smillert } 19314852038Smillert dup2(STDOUT, STDERR); 194df930be7Sderaadt 195df930be7Sderaadt /* set our directory, uid and gid. Set gid first, since once 196df930be7Sderaadt * we set uid, we've lost root privledges. 197df930be7Sderaadt */ 198fed231abSmillert #ifdef LOGIN_CAP 199fed231abSmillert { 200fed231abSmillert struct passwd *pwd; 201fed231abSmillert char *ep, *np; 202fed231abSmillert 203fed231abSmillert /* XXX - should just pass in a login_cap_t * */ 204fed231abSmillert pwd = getpwuid(e->uid); 205fed231abSmillert if (pwd == NULL) { 206fed231abSmillert fprintf(stderr, "getpwuid: couldn't get entry for %d\n", e->uid); 207fed231abSmillert _exit(ERROR_EXIT); 208fed231abSmillert } 209fed231abSmillert if (setusercontext(0, pwd, e->uid, LOGIN_SETALL) < 0) { 210fed231abSmillert fprintf(stderr, "setusercontext failed for %d\n", e->uid); 211fed231abSmillert _exit(ERROR_EXIT); 212fed231abSmillert } 213f454ebdeSmillert #ifdef BSD_AUTH 214f454ebdeSmillert if (auth_approval(0, 0, pwd->pw_name, "cron") <= 0) { 215f454ebdeSmillert fprintf(stderr, "approval failed for %d\n", e->uid); 216f454ebdeSmillert _exit(ERROR_EXIT); 217f454ebdeSmillert } 218f454ebdeSmillert #endif /* BSD_AUTH */ 219fed231abSmillert /* If no PATH specified in crontab file but 220f454ebdeSmillert * we just added one via login.conf, add it to 221fed231abSmillert * the crontab environment. 222fed231abSmillert */ 223fed231abSmillert if (env_get("PATH", e->envp) == NULL && 224fed231abSmillert (ep = getenv("PATH"))) { 225fed231abSmillert np = malloc(strlen(ep) + 6); 226fed231abSmillert if (np) { 227fed231abSmillert strcpy(np, "PATH="); 228fed231abSmillert strcat(np, ep); 229fed231abSmillert e->envp = env_set(e->envp, np); 230fed231abSmillert } 231fed231abSmillert } 232fed231abSmillert 233f454ebdeSmillert } 234fed231abSmillert #else 235df930be7Sderaadt setgid(e->gid); 236df930be7Sderaadt initgroups(env_get("LOGNAME", e->envp), e->gid); 237bd5a2f8eSderaadt setlogin(usernm); 238df930be7Sderaadt setuid(e->uid); /* we aren't root after this... */ 239f454ebdeSmillert 240f454ebdeSmillert #endif /* LOGIN_CAP */ 241df930be7Sderaadt chdir(env_get("HOME", e->envp)); 242df930be7Sderaadt 243f454ebdeSmillert /* 244f454ebdeSmillert * Exec the command. 245df930be7Sderaadt */ 246df930be7Sderaadt { 247df930be7Sderaadt char *shell = env_get("SHELL", e->envp); 248df930be7Sderaadt 249df930be7Sderaadt # if DEBUGGING 250df930be7Sderaadt if (DebugFlags & DTEST) { 251df930be7Sderaadt fprintf(stderr, 252df930be7Sderaadt "debug DTEST is on, not exec'ing command.\n"); 253df930be7Sderaadt fprintf(stderr, 254df930be7Sderaadt "\tcmd='%s' shell='%s'\n", e->cmd, shell); 255df930be7Sderaadt _exit(OK_EXIT); 256df930be7Sderaadt } 257df930be7Sderaadt # endif /*DEBUGGING*/ 258df930be7Sderaadt execle(shell, shell, "-c", e->cmd, (char *)0, e->envp); 259df930be7Sderaadt fprintf(stderr, "execl: couldn't exec `%s'\n", shell); 260df930be7Sderaadt perror("execl"); 261df930be7Sderaadt _exit(ERROR_EXIT); 262df930be7Sderaadt } 263df930be7Sderaadt break; 264df930be7Sderaadt default: 265df930be7Sderaadt /* parent process */ 266df930be7Sderaadt break; 267df930be7Sderaadt } 268df930be7Sderaadt 269df930be7Sderaadt children++; 270df930be7Sderaadt 271df930be7Sderaadt /* middle process, child of original cron, parent of process running 272df930be7Sderaadt * the user's command. 273df930be7Sderaadt */ 274df930be7Sderaadt 275f454ebdeSmillert Debug(DPROC, ("[%ld] child continues, closing pipes\n",(long)getpid())) 276df930be7Sderaadt 277df930be7Sderaadt /* close the ends of the pipe that will only be referenced in the 278df930be7Sderaadt * grandchild process... 279df930be7Sderaadt */ 280df930be7Sderaadt close(stdin_pipe[READ_PIPE]); 281df930be7Sderaadt close(stdout_pipe[WRITE_PIPE]); 282df930be7Sderaadt 283df930be7Sderaadt /* 284df930be7Sderaadt * write, to the pipe connected to child's stdin, any input specified 285df930be7Sderaadt * after a % in the crontab entry. while we copy, convert any 286df930be7Sderaadt * additional %'s to newlines. when done, if some characters were 287df930be7Sderaadt * written and the last one wasn't a newline, write a newline. 288df930be7Sderaadt * 289df930be7Sderaadt * Note that if the input data won't fit into one pipe buffer (2K 290df930be7Sderaadt * or 4K on most BSD systems), and the child doesn't read its stdin, 291df930be7Sderaadt * we would block here. thus we must fork again. 292df930be7Sderaadt */ 293df930be7Sderaadt 294df930be7Sderaadt if (*input_data && fork() == 0) { 295f454ebdeSmillert FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); 296f454ebdeSmillert int need_newline = FALSE; 297f454ebdeSmillert int escaped = FALSE; 298f454ebdeSmillert int ch; 299df930be7Sderaadt 300f454ebdeSmillert Debug(DPROC, ("[%ld] child2 sending data to grandchild\n", 301f454ebdeSmillert (long)getpid())) 302df930be7Sderaadt 303df930be7Sderaadt /* close the pipe we don't use, since we inherited it and 304df930be7Sderaadt * are part of its reference count now. 305df930be7Sderaadt */ 306df930be7Sderaadt close(stdout_pipe[READ_PIPE]); 307df930be7Sderaadt 308df930be7Sderaadt /* translation: 309df930be7Sderaadt * \% -> % 310df930be7Sderaadt * % -> \n 311df930be7Sderaadt * \x -> \x for all x != % 312df930be7Sderaadt */ 3139f52c269Sderaadt while ((ch = *input_data++) != '\0') { 314df930be7Sderaadt if (escaped) { 315df930be7Sderaadt if (ch != '%') 316df930be7Sderaadt putc('\\', out); 317df930be7Sderaadt } else { 318df930be7Sderaadt if (ch == '%') 319df930be7Sderaadt ch = '\n'; 320df930be7Sderaadt } 321df930be7Sderaadt 322df930be7Sderaadt if (!(escaped = (ch == '\\'))) { 323df930be7Sderaadt putc(ch, out); 324df930be7Sderaadt need_newline = (ch != '\n'); 325df930be7Sderaadt } 326df930be7Sderaadt } 327df930be7Sderaadt if (escaped) 328df930be7Sderaadt putc('\\', out); 329df930be7Sderaadt if (need_newline) 330df930be7Sderaadt putc('\n', out); 331df930be7Sderaadt 332df930be7Sderaadt /* close the pipe, causing an EOF condition. fclose causes 333df930be7Sderaadt * stdin_pipe[WRITE_PIPE] to be closed, too. 334df930be7Sderaadt */ 335df930be7Sderaadt fclose(out); 336df930be7Sderaadt 337f454ebdeSmillert Debug(DPROC, ("[%ld] child2 done sending to grandchild\n", 338f454ebdeSmillert (long)getpid())) 339df930be7Sderaadt exit(0); 340df930be7Sderaadt } 341df930be7Sderaadt 342df930be7Sderaadt /* close the pipe to the grandkiddie's stdin, since its wicked uncle 343df930be7Sderaadt * ernie back there has it open and will close it when he's done. 344df930be7Sderaadt */ 345df930be7Sderaadt close(stdin_pipe[WRITE_PIPE]); 346df930be7Sderaadt 347df930be7Sderaadt children++; 348df930be7Sderaadt 349df930be7Sderaadt /* 350df930be7Sderaadt * read output from the grandchild. it's stderr has been redirected to 351df930be7Sderaadt * it's stdout, which has been redirected to our pipe. if there is any 352df930be7Sderaadt * output, we'll be mailing it to the user whose crontab this is... 353df930be7Sderaadt * when the grandchild exits, we'll get EOF. 354df930be7Sderaadt */ 355df930be7Sderaadt 356f454ebdeSmillert Debug(DPROC, ("[%ld] child reading output from grandchild\n", 357f454ebdeSmillert (long)getpid())) 358df930be7Sderaadt 359df930be7Sderaadt /*local*/{ 360f454ebdeSmillert FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); 361f454ebdeSmillert int ch = getc(in); 362df930be7Sderaadt 363df930be7Sderaadt if (ch != EOF) { 364d5919ef3Smillert FILE *mail; 365f454ebdeSmillert int bytes = 1; 366df930be7Sderaadt int status = 0; 367df930be7Sderaadt 368df930be7Sderaadt Debug(DPROC|DEXT, 369f454ebdeSmillert ("[%ld] got data (%x:%c) from grandchild\n", 370f454ebdeSmillert (long)getpid(), ch, ch)) 371df930be7Sderaadt 372df930be7Sderaadt /* get name of recipient. this is MAILTO if set to a 373df930be7Sderaadt * valid local username; USER otherwise. 374df930be7Sderaadt */ 375f454ebdeSmillert if (mailto && safe_p(usernm, mailto)) { 376df930be7Sderaadt /* MAILTO was present in the environment 377df930be7Sderaadt */ 378df930be7Sderaadt if (!*mailto) { 379df930be7Sderaadt /* ... but it's empty. set to NULL 380df930be7Sderaadt */ 381df930be7Sderaadt mailto = NULL; 382df930be7Sderaadt } 383df930be7Sderaadt } else { 384df930be7Sderaadt /* MAILTO not present, set to USER. 385df930be7Sderaadt */ 386df930be7Sderaadt mailto = usernm; 387df930be7Sderaadt } 388df930be7Sderaadt 389df930be7Sderaadt /* if we are supposed to be mailing, MAILTO will 390df930be7Sderaadt * be non-NULL. only in this case should we set 391df930be7Sderaadt * up the mail command and subjects and stuff... 392df930be7Sderaadt */ 393df930be7Sderaadt 394df930be7Sderaadt if (mailto) { 395f454ebdeSmillert char **env; 396f454ebdeSmillert char mailcmd[MAX_COMMAND]; 397f454ebdeSmillert char hostname[MAXHOSTNAMELEN]; 398df930be7Sderaadt 3994af80cabSmpech gethostname(hostname, sizeof(hostname)); 400f454ebdeSmillert if (snprintf(mailcmd, sizeof mailcmd, MAILFMT, 401f454ebdeSmillert MAILARG) >= sizeof mailcmd) { 402f454ebdeSmillert fprintf(stderr, "mailcmd too long\n"); 403f454ebdeSmillert (void) _exit(ERROR_EXIT); 404f454ebdeSmillert } 4058eda01ecSmillert if (!(mail = cron_popen(mailcmd, "w", e))) { 406f454ebdeSmillert perror(mailcmd); 407df930be7Sderaadt (void) _exit(ERROR_EXIT); 408df930be7Sderaadt } 409df930be7Sderaadt fprintf(mail, "From: root (Cron Daemon)\n"); 410df930be7Sderaadt fprintf(mail, "To: %s\n", mailto); 411df930be7Sderaadt fprintf(mail, "Subject: Cron <%s@%s> %s\n", 412df930be7Sderaadt usernm, first_word(hostname, "."), 413df930be7Sderaadt e->cmd); 414f454ebdeSmillert #ifdef MAIL_DATE 415df930be7Sderaadt fprintf(mail, "Date: %s\n", 416f326c411Sderaadt arpadate(&StartTime)); 417df930be7Sderaadt #endif /*MAIL_DATE*/ 418df930be7Sderaadt for (env = e->envp; *env; env++) 419df930be7Sderaadt fprintf(mail, "X-Cron-Env: <%s>\n", 420df930be7Sderaadt *env); 421df930be7Sderaadt fprintf(mail, "\n"); 422df930be7Sderaadt 423df930be7Sderaadt /* this was the first char from the pipe 424df930be7Sderaadt */ 425f454ebdeSmillert fputc(ch, mail); 426df930be7Sderaadt } 427df930be7Sderaadt 428df930be7Sderaadt /* we have to read the input pipe no matter whether 429df930be7Sderaadt * we mail or not, but obviously we only write to 430df930be7Sderaadt * mail pipe if we ARE mailing. 431df930be7Sderaadt */ 432df930be7Sderaadt 433df930be7Sderaadt while (EOF != (ch = getc(in))) { 434df930be7Sderaadt bytes++; 435df930be7Sderaadt if (mailto) 436f454ebdeSmillert fputc(ch, mail); 437df930be7Sderaadt } 438df930be7Sderaadt 439df930be7Sderaadt /* only close pipe if we opened it -- i.e., we're 440df930be7Sderaadt * mailing... 441df930be7Sderaadt */ 442df930be7Sderaadt 443df930be7Sderaadt if (mailto) { 444f454ebdeSmillert Debug(DPROC, ("[%ld] closing pipe to mail\n", 445f454ebdeSmillert (long)getpid())) 446df930be7Sderaadt /* Note: the pclose will probably see 447df930be7Sderaadt * the termination of the grandchild 448df930be7Sderaadt * in addition to the mail process, since 449df930be7Sderaadt * it (the grandchild) is likely to exit 450df930be7Sderaadt * after closing its stdout. 451df930be7Sderaadt */ 452df930be7Sderaadt status = cron_pclose(mail); 453df930be7Sderaadt } 454df930be7Sderaadt 455df930be7Sderaadt /* if there was output and we could not mail it, 456df930be7Sderaadt * log the facts so the poor user can figure out 457df930be7Sderaadt * what's going on. 458df930be7Sderaadt */ 459df930be7Sderaadt if (mailto && status) { 460df930be7Sderaadt char buf[MAX_TEMPSTR]; 461df930be7Sderaadt 462f5e9cd7eSderaadt snprintf(buf, sizeof buf, 463df930be7Sderaadt "mailed %d byte%s of output but got status 0x%04x\n", 464df930be7Sderaadt bytes, (bytes==1)?"":"s", 465df930be7Sderaadt status); 466df930be7Sderaadt log_it(usernm, getpid(), "MAIL", buf); 467df930be7Sderaadt } 468df930be7Sderaadt 469df930be7Sderaadt } /*if data from grandchild*/ 470df930be7Sderaadt 471f454ebdeSmillert Debug(DPROC, ("[%ld] got EOF from grandchild\n", 472f454ebdeSmillert (long)getpid())) 473df930be7Sderaadt 474df930be7Sderaadt fclose(in); /* also closes stdout_pipe[READ_PIPE] */ 475df930be7Sderaadt } 476df930be7Sderaadt 477df930be7Sderaadt /* wait for children to die. 478df930be7Sderaadt */ 479f454ebdeSmillert for (; children > 0; children--) { 480df930be7Sderaadt WAIT_T waiter; 481df930be7Sderaadt PID_T pid; 482df930be7Sderaadt 483f454ebdeSmillert Debug(DPROC, ("[%ld] waiting for grandchild #%d to finish\n", 484f454ebdeSmillert (long)getpid(), children)) 485df930be7Sderaadt pid = wait(&waiter); 486df930be7Sderaadt if (pid < OK) { 48714852038Smillert if (errno == EINTR) 48814852038Smillert continue; 489f454ebdeSmillert Debug(DPROC, 490f454ebdeSmillert ("[%ld] no more grandchildren--mail written?\n", 491f454ebdeSmillert (long)getpid())) 492df930be7Sderaadt break; 493df930be7Sderaadt } 494f454ebdeSmillert Debug(DPROC, ("[%ld] grandchild #%ld finished, status=%04x", 495f454ebdeSmillert (long)getpid(), (long)pid, WEXITSTATUS(waiter))) 496df930be7Sderaadt if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) 497df930be7Sderaadt Debug(DPROC, (", dumped core")) 498df930be7Sderaadt Debug(DPROC, ("\n")) 499df930be7Sderaadt } 500df930be7Sderaadt } 501df930be7Sderaadt 502f454ebdeSmillert static int 503f454ebdeSmillert safe_p(const char *usernm, const char *s) { 504f454ebdeSmillert static const char safe_delim[] = "@!:%-.,"; /* conservative! */ 505f454ebdeSmillert const char *t; 506f454ebdeSmillert int ch, first; 507df930be7Sderaadt 508f454ebdeSmillert for (t = s, first = 1; (ch = *t++) != '\0'; first = 0) { 509f454ebdeSmillert if (isascii(ch) && isprint(ch) && 510f454ebdeSmillert (isalnum(ch) || (!first && strchr(safe_delim, ch)))) 511f454ebdeSmillert continue; 512f454ebdeSmillert log_it(usernm, getpid(), "UNSAFE", s); 513f454ebdeSmillert return (FALSE); 514df930be7Sderaadt } 515f454ebdeSmillert return (TRUE); 516df930be7Sderaadt } 517