1*2e6cac80Smillert /* $OpenBSD: do_command.c,v 1.61 2020/04/16 17:51:56 millert Exp $ */ 2e134e629Smillert 3df930be7Sderaadt /* Copyright 1988,1990,1993,1994 by Paul Vixie 4a5198fa1Smillert * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") 5f454ebdeSmillert * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. 614eea816Sjob * Copyright (c) 2018 Job Snijders <job@openbsd.org> 7df930be7Sderaadt * 8f454ebdeSmillert * Permission to use, copy, modify, and distribute this software for any 9f454ebdeSmillert * purpose with or without fee is hereby granted, provided that the above 10f454ebdeSmillert * copyright notice and this permission notice appear in all copies. 11df930be7Sderaadt * 12a5198fa1Smillert * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES 13a5198fa1Smillert * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14a5198fa1Smillert * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR 15a5198fa1Smillert * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16a5198fa1Smillert * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17a5198fa1Smillert * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 18a5198fa1Smillert * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19df930be7Sderaadt */ 20df930be7Sderaadt 21fa575ea2Smillert #include <sys/types.h> 22fa575ea2Smillert #include <sys/wait.h> 23fa575ea2Smillert 24fa575ea2Smillert #include <bitstring.h> /* for structs.h */ 25fa575ea2Smillert #include <bsd_auth.h> 26fa575ea2Smillert #include <ctype.h> 27b8c5c73dSmillert #include <err.h> 28fa575ea2Smillert #include <errno.h> 29fa575ea2Smillert #include <fcntl.h> 30fa575ea2Smillert #include <limits.h> 31fa575ea2Smillert #include <login_cap.h> 32fa575ea2Smillert #include <pwd.h> 33fa575ea2Smillert #include <signal.h> 34fa575ea2Smillert #include <stdio.h> 35fa575ea2Smillert #include <stdlib.h> 36fa575ea2Smillert #include <string.h> 37a38a3395Smillert #include <syslog.h> 38fa575ea2Smillert #include <time.h> /* for structs.h */ 39fa575ea2Smillert #include <unistd.h> 404ca18184Smillert #include <vis.h> 41df930be7Sderaadt 42fa575ea2Smillert #include "config.h" 43fa575ea2Smillert #include "pathnames.h" 44fa575ea2Smillert #include "macros.h" 45fa575ea2Smillert #include "structs.h" 46fa575ea2Smillert #include "funcs.h" 47fa575ea2Smillert 48f454ebdeSmillert static void child_process(entry *, user *); 49df930be7Sderaadt 50*2e6cac80Smillert pid_t 51785ecd14Stedu do_command(entry *e, user *u) 52785ecd14Stedu { 53*2e6cac80Smillert pid_t pid; 54df930be7Sderaadt 55df930be7Sderaadt /* fork to become asynchronous -- parent process is done immediately, 56df930be7Sderaadt * and continues to run the normal cron code, which means return to 57df930be7Sderaadt * tick(). the child and grandchild don't leave this function, alive. 58df930be7Sderaadt * 59df930be7Sderaadt * vfork() is unsuitable, since we have much to do, and the parent 60df930be7Sderaadt * needs to be able to run off and fork other processes. 61df930be7Sderaadt */ 62*2e6cac80Smillert switch ((pid = fork())) { 63df930be7Sderaadt case -1: 64a38a3395Smillert syslog(LOG_ERR, "(CRON) CAN'T FORK (%m)"); 65df930be7Sderaadt break; 66df930be7Sderaadt case 0: 67df930be7Sderaadt /* child process */ 68df930be7Sderaadt child_process(e, u); 6910ee6820Smillert _exit(EXIT_SUCCESS); 70df930be7Sderaadt break; 71df930be7Sderaadt default: 72df930be7Sderaadt /* parent process */ 73*2e6cac80Smillert if ((e->flags & SINGLE_JOB) == 0) 74*2e6cac80Smillert pid = -1; 75df930be7Sderaadt break; 76df930be7Sderaadt } 77*2e6cac80Smillert 78*2e6cac80Smillert /* only return pid if a singleton */ 79*2e6cac80Smillert return (pid); 80df930be7Sderaadt } 81df930be7Sderaadt 82df930be7Sderaadt static void 83785ecd14Stedu child_process(entry *e, user *u) 84785ecd14Stedu { 852009bb53Smillert FILE *in; 86df930be7Sderaadt int stdin_pipe[2], stdout_pipe[2]; 87b8c5c73dSmillert char **p, *input_data, *usernm; 88b8c5c73dSmillert auth_session_t *as; 89b8c5c73dSmillert login_cap_t *lc; 90b8c5c73dSmillert extern char **environ; 91df930be7Sderaadt 920aec96c6Smillert /* mark ourselves as different to PS command watchers */ 930aec96c6Smillert setproctitle("running job"); 94df930be7Sderaadt 95d2c574e2Sfriehm /* close sockets from parent (i.e. cronSock) */ 96d2c574e2Sfriehm closefrom(3); 97d2c574e2Sfriehm 98df930be7Sderaadt /* discover some useful and important environment settings 99df930be7Sderaadt */ 1002b139a93Smillert usernm = e->pwd->pw_name; 101df930be7Sderaadt 102df930be7Sderaadt /* our parent is watching for our death by catching SIGCHLD. we 103df930be7Sderaadt * do not care to watch for our children's deaths this way -- we 104e4d25771Stodd * use wait() explicitly. so we have to reset the signal (which 105df930be7Sderaadt * was inherited from the parent). 106df930be7Sderaadt */ 1075815f23fSmillert (void) signal(SIGCHLD, SIG_DFL); 108df930be7Sderaadt 109df930be7Sderaadt /* create some pipes to talk to our future child 110df930be7Sderaadt */ 1113b9f8275Smillert if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) { 1123b9f8275Smillert syslog(LOG_ERR, "(CRON) PIPE (%m)"); 1133b9f8275Smillert _exit(EXIT_FAILURE); 1143b9f8275Smillert } 115df930be7Sderaadt 116df930be7Sderaadt /* since we are a forked process, we can diddle the command string 117df930be7Sderaadt * we were passed -- nobody else is going to use it again, right? 118df930be7Sderaadt * 119df930be7Sderaadt * if a % is present in the command, previous characters are the 120df930be7Sderaadt * command, and subsequent characters are the additional input to 1212b139a93Smillert * the command. An escaped % will have the escape character stripped 1222b139a93Smillert * from it. Subsequent %'s will be transformed into newlines, 123df930be7Sderaadt * but that happens later. 124df930be7Sderaadt */ 125df930be7Sderaadt /*local*/{ 126f454ebdeSmillert int escaped = FALSE; 127f454ebdeSmillert int ch; 128f454ebdeSmillert char *p; 129df930be7Sderaadt 130f454ebdeSmillert for (input_data = p = e->cmd; 131f454ebdeSmillert (ch = *input_data) != '\0'; 132d94ebf94Sderaadt input_data++, p++) { 133d94ebf94Sderaadt if (p != input_data) 134d94ebf94Sderaadt *p = ch; 135df930be7Sderaadt if (escaped) { 13694cf10eeSmillert if (ch == '%') 137d94ebf94Sderaadt *--p = ch; 138df930be7Sderaadt escaped = FALSE; 139df930be7Sderaadt continue; 140df930be7Sderaadt } 141df930be7Sderaadt if (ch == '\\') { 142df930be7Sderaadt escaped = TRUE; 143df930be7Sderaadt continue; 144df930be7Sderaadt } 145df930be7Sderaadt if (ch == '%') { 146df930be7Sderaadt *input_data++ = '\0'; 147df930be7Sderaadt break; 148df930be7Sderaadt } 149df930be7Sderaadt } 150d94ebf94Sderaadt *p = '\0'; 151df930be7Sderaadt } 152df930be7Sderaadt 153df930be7Sderaadt /* fork again, this time so we can exec the user's command. 154df930be7Sderaadt */ 15514eea816Sjob 15614eea816Sjob pid_t jobpid; 15714eea816Sjob switch (jobpid = fork()) { 158df930be7Sderaadt case -1: 159a38a3395Smillert syslog(LOG_ERR, "(CRON) CAN'T FORK (%m)"); 16010ee6820Smillert _exit(EXIT_FAILURE); 161df930be7Sderaadt /*NOTREACHED*/ 162df930be7Sderaadt case 0: 163df930be7Sderaadt /* write a log message. we've waited this long to do it 164df930be7Sderaadt * because it was not until now that we knew the PID that 165df930be7Sderaadt * the actual user command shell was going to get and the 166df930be7Sderaadt * PID is part of the log message. 167df930be7Sderaadt */ 168f454ebdeSmillert if ((e->flags & DONT_LOG) == 0) { 1694ca18184Smillert char *x; 1704ca18184Smillert if (stravis(&x, e->cmd, 0) != -1) { 171a38a3395Smillert syslog(LOG_INFO, "(%s) CMD (%s)", usernm, x); 172df930be7Sderaadt free(x); 173df930be7Sderaadt } 1744ca18184Smillert } 175df930be7Sderaadt 176df930be7Sderaadt /* get new pgrp, void tty, etc. 177df930be7Sderaadt */ 178df930be7Sderaadt (void) setsid(); 179df930be7Sderaadt 180df930be7Sderaadt /* close the pipe ends that we won't use. this doesn't affect 181df930be7Sderaadt * the parent, who has to read and write them; it keeps the 182df930be7Sderaadt * kernel from recording us as a potential client TWICE -- 183df930be7Sderaadt * which would keep it from sending SIGPIPE in otherwise 184df930be7Sderaadt * appropriate circumstances. 185df930be7Sderaadt */ 186df930be7Sderaadt close(stdin_pipe[WRITE_PIPE]); 187df930be7Sderaadt close(stdout_pipe[READ_PIPE]); 188df930be7Sderaadt 189df930be7Sderaadt /* grandchild process. make std{in,out} be the ends of 190df930be7Sderaadt * pipes opened by our daddy; make stderr go to stdout. 191df930be7Sderaadt */ 19210ee6820Smillert if (stdin_pipe[READ_PIPE] != STDIN_FILENO) { 19310ee6820Smillert dup2(stdin_pipe[READ_PIPE], STDIN_FILENO); 194df930be7Sderaadt close(stdin_pipe[READ_PIPE]); 19514852038Smillert } 19610ee6820Smillert if (stdout_pipe[WRITE_PIPE] != STDOUT_FILENO) { 19710ee6820Smillert dup2(stdout_pipe[WRITE_PIPE], STDOUT_FILENO); 198a0f600ecSmillert close(stdout_pipe[WRITE_PIPE]); 19914852038Smillert } 20010ee6820Smillert dup2(STDOUT_FILENO, STDERR_FILENO); 201df930be7Sderaadt 202b8c5c73dSmillert /* 203b8c5c73dSmillert * From this point on, anything written to stderr will be 204b8c5c73dSmillert * mailed to the user as output. 205df930be7Sderaadt */ 206fed231abSmillert 207fed231abSmillert /* XXX - should just pass in a login_cap_t * */ 2082b139a93Smillert if ((lc = login_getclass(e->pwd->pw_class)) == NULL) { 209b8c5c73dSmillert warnx("unable to get login class for %s", 210b8c5c73dSmillert e->pwd->pw_name); 211b8c5c73dSmillert syslog(LOG_ERR, "(CRON) CAN'T GET LOGIN CLASS (%s)", 2122b139a93Smillert e->pwd->pw_name); 21310ee6820Smillert _exit(EXIT_FAILURE); 214fed231abSmillert } 215df69c215Sderaadt if (setusercontext(lc, e->pwd, e->pwd->pw_uid, LOGIN_SETALL) == -1) { 216b8c5c73dSmillert warn("setusercontext failed for %s", e->pwd->pw_name); 217b8c5c73dSmillert syslog(LOG_ERR, "(%s) SETUSERCONTEXT FAILED (%m)", 218e134e629Smillert e->pwd->pw_name); 21910ee6820Smillert _exit(EXIT_FAILURE); 220fed231abSmillert } 221b746b40dSmillert as = auth_open(); 222b746b40dSmillert if (as == NULL || auth_setpwd(as, e->pwd) != 0) { 223b8c5c73dSmillert warn("auth_setpwd"); 224b8c5c73dSmillert syslog(LOG_ERR, "(%s) AUTH_SETPWD FAILED (%m)", 225b8c5c73dSmillert e->pwd->pw_name); 22610ee6820Smillert _exit(EXIT_FAILURE); 227b746b40dSmillert } 228b746b40dSmillert if (auth_approval(as, lc, usernm, "cron") <= 0) { 229b8c5c73dSmillert warnx("approval failed for %s", e->pwd->pw_name); 230b8c5c73dSmillert syslog(LOG_ERR, "(%s) APPROVAL FAILED (cron)", 2312b139a93Smillert e->pwd->pw_name); 23210ee6820Smillert _exit(EXIT_FAILURE); 233f454ebdeSmillert } 234b746b40dSmillert auth_close(as); 2352b139a93Smillert login_close(lc); 2362b139a93Smillert 237fed231abSmillert /* If no PATH specified in crontab file but 238f454ebdeSmillert * we just added one via login.conf, add it to 239fed231abSmillert * the crontab environment. 240fed231abSmillert */ 2412b139a93Smillert if (env_get("PATH", e->envp) == NULL && environ != NULL) { 2422b139a93Smillert for (p = environ; *p; p++) { 2432b139a93Smillert if (strncmp(*p, "PATH=", 5) == 0) { 2442b139a93Smillert e->envp = env_set(e->envp, *p); 2452b139a93Smillert break; 246fed231abSmillert } 247fed231abSmillert } 2482b139a93Smillert } 249df930be7Sderaadt chdir(env_get("HOME", e->envp)); 250df930be7Sderaadt 2518b18fb30Smillert (void) signal(SIGPIPE, SIG_DFL); 2528b18fb30Smillert 253f454ebdeSmillert /* 254f454ebdeSmillert * Exec the command. 255df930be7Sderaadt */ 256df930be7Sderaadt { 257df930be7Sderaadt char *shell = env_get("SHELL", e->envp); 258df930be7Sderaadt 259cce75673Smillert execle(shell, shell, "-c", e->cmd, (char *)NULL, e->envp); 260b8c5c73dSmillert warn("unable to execute %s", shell); 261b8c5c73dSmillert syslog(LOG_ERR, "(%s) CAN'T EXEC (%s: %m)", 262b8c5c73dSmillert e->pwd->pw_name, shell); 26310ee6820Smillert _exit(EXIT_FAILURE); 264df930be7Sderaadt } 265df930be7Sderaadt break; 266df930be7Sderaadt default: 267df930be7Sderaadt /* parent process */ 268df930be7Sderaadt break; 269df930be7Sderaadt } 270df930be7Sderaadt 271df930be7Sderaadt /* middle process, child of original cron, parent of process running 272df930be7Sderaadt * the user's command. 273df930be7Sderaadt */ 274df930be7Sderaadt 275df930be7Sderaadt /* close the ends of the pipe that will only be referenced in the 276df930be7Sderaadt * grandchild process... 277df930be7Sderaadt */ 278df930be7Sderaadt close(stdin_pipe[READ_PIPE]); 279df930be7Sderaadt close(stdout_pipe[WRITE_PIPE]); 280df930be7Sderaadt 281df930be7Sderaadt /* 282df930be7Sderaadt * write, to the pipe connected to child's stdin, any input specified 283df930be7Sderaadt * after a % in the crontab entry. while we copy, convert any 284df930be7Sderaadt * additional %'s to newlines. when done, if some characters were 285df930be7Sderaadt * written and the last one wasn't a newline, write a newline. 286df930be7Sderaadt * 287df930be7Sderaadt * Note that if the input data won't fit into one pipe buffer (2K 288df930be7Sderaadt * or 4K on most BSD systems), and the child doesn't read its stdin, 289df930be7Sderaadt * we would block here. thus we must fork again. 290df930be7Sderaadt */ 291df930be7Sderaadt 29214eea816Sjob pid_t stdinjob; 29314eea816Sjob if (*input_data && (stdinjob = fork()) == 0) { 294f454ebdeSmillert FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); 295f454ebdeSmillert int need_newline = FALSE; 296f454ebdeSmillert int escaped = FALSE; 297f454ebdeSmillert int ch; 298df930be7Sderaadt 299df930be7Sderaadt /* close the pipe we don't use, since we inherited it and 300df930be7Sderaadt * are part of its reference count now. 301df930be7Sderaadt */ 302df930be7Sderaadt close(stdout_pipe[READ_PIPE]); 303df930be7Sderaadt 304df930be7Sderaadt /* translation: 305df930be7Sderaadt * \% -> % 306df930be7Sderaadt * % -> \n 307df930be7Sderaadt * \x -> \x for all x != % 308df930be7Sderaadt */ 3099f52c269Sderaadt while ((ch = *input_data++) != '\0') { 310df930be7Sderaadt if (escaped) { 311df930be7Sderaadt if (ch != '%') 312df930be7Sderaadt putc('\\', out); 313df930be7Sderaadt } else { 314df930be7Sderaadt if (ch == '%') 315df930be7Sderaadt ch = '\n'; 316df930be7Sderaadt } 317df930be7Sderaadt 318df930be7Sderaadt if (!(escaped = (ch == '\\'))) { 319df930be7Sderaadt putc(ch, out); 320df930be7Sderaadt need_newline = (ch != '\n'); 321df930be7Sderaadt } 322df930be7Sderaadt } 323df930be7Sderaadt if (escaped) 324df930be7Sderaadt putc('\\', out); 325df930be7Sderaadt if (need_newline) 326df930be7Sderaadt putc('\n', out); 327df930be7Sderaadt 328df930be7Sderaadt /* close the pipe, causing an EOF condition. fclose causes 329df930be7Sderaadt * stdin_pipe[WRITE_PIPE] to be closed, too. 330df930be7Sderaadt */ 331df930be7Sderaadt fclose(out); 332df930be7Sderaadt 33310ee6820Smillert _exit(EXIT_SUCCESS); 334df930be7Sderaadt } 335df930be7Sderaadt 336df930be7Sderaadt /* close the pipe to the grandkiddie's stdin, since its wicked uncle 337df930be7Sderaadt * ernie back there has it open and will close it when he's done. 338df930be7Sderaadt */ 339df930be7Sderaadt close(stdin_pipe[WRITE_PIPE]); 340df930be7Sderaadt 341df930be7Sderaadt /* 342df930be7Sderaadt * read output from the grandchild. it's stderr has been redirected to 343df930be7Sderaadt * it's stdout, which has been redirected to our pipe. if there is any 344df930be7Sderaadt * output, we'll be mailing it to the user whose crontab this is... 345df930be7Sderaadt * when the grandchild exits, we'll get EOF. 346df930be7Sderaadt */ 347df930be7Sderaadt 3482009bb53Smillert (void) signal(SIGPIPE, SIG_IGN); 3492009bb53Smillert in = fdopen(stdout_pipe[READ_PIPE], "r"); 35014eea816Sjob 35114eea816Sjob char *mailto; 35214eea816Sjob FILE *mail = NULL; 35314eea816Sjob int status = 0; 35414eea816Sjob pid_t mailpid; 35514eea816Sjob size_t bytes = 1; 35614eea816Sjob 3572009bb53Smillert if (in != NULL) { 358f454ebdeSmillert int ch = getc(in); 359df930be7Sderaadt 360df930be7Sderaadt if (ch != EOF) { 361df930be7Sderaadt 362df930be7Sderaadt /* get name of recipient. this is MAILTO if set to a 363df930be7Sderaadt * valid local username; USER otherwise. 364df930be7Sderaadt */ 3652009bb53Smillert mailto = env_get("MAILTO", e->envp); 36670805688Smillert if (!mailto) { 367df930be7Sderaadt /* MAILTO not present, set to USER. 368df930be7Sderaadt */ 369df930be7Sderaadt mailto = usernm; 37070805688Smillert } else if (!*mailto || !safe_p(usernm, mailto)) { 37170805688Smillert mailto = NULL; 372df930be7Sderaadt } 373df930be7Sderaadt 374df930be7Sderaadt /* if we are supposed to be mailing, MAILTO will 375df930be7Sderaadt * be non-NULL. only in this case should we set 376df930be7Sderaadt * up the mail command and subjects and stuff... 377df930be7Sderaadt */ 378df930be7Sderaadt 37970805688Smillert if (mailto) { 380f454ebdeSmillert char **env; 381f454ebdeSmillert char mailcmd[MAX_COMMAND]; 38275e8a723Smillert char hostname[HOST_NAME_MAX + 1]; 383df930be7Sderaadt 3844af80cabSmpech gethostname(hostname, sizeof(hostname)); 385f454ebdeSmillert if (snprintf(mailcmd, sizeof mailcmd, MAILFMT, 386f454ebdeSmillert MAILARG) >= sizeof mailcmd) { 387b8c5c73dSmillert syslog(LOG_ERR, 388b8c5c73dSmillert "(%s) ERROR (mailcmd too long)", 389b8c5c73dSmillert e->pwd->pw_name); 39010ee6820Smillert (void) _exit(EXIT_FAILURE); 391f454ebdeSmillert } 39294b4a649Stedu if (!(mail = cron_popen(mailcmd, "w", e->pwd, 39394b4a649Stedu &mailpid))) { 394b8c5c73dSmillert syslog(LOG_ERR, "(%s) POPEN (%s)", 395b8c5c73dSmillert e->pwd->pw_name, mailcmd); 39610ee6820Smillert (void) _exit(EXIT_FAILURE); 397df930be7Sderaadt } 398df930be7Sderaadt fprintf(mail, "From: root (Cron Daemon)\n"); 399df930be7Sderaadt fprintf(mail, "To: %s\n", mailto); 400df930be7Sderaadt fprintf(mail, "Subject: Cron <%s@%s> %s\n", 401df930be7Sderaadt usernm, first_word(hostname, "."), 402df930be7Sderaadt e->cmd); 40313c10301Smillert fprintf(mail, "Auto-Submitted: auto-generated\n"); 404df930be7Sderaadt for (env = e->envp; *env; env++) 405df930be7Sderaadt fprintf(mail, "X-Cron-Env: <%s>\n", 406df930be7Sderaadt *env); 407df930be7Sderaadt fprintf(mail, "\n"); 408df930be7Sderaadt 409df930be7Sderaadt /* this was the first char from the pipe 410df930be7Sderaadt */ 411f454ebdeSmillert fputc(ch, mail); 412df930be7Sderaadt } 413df930be7Sderaadt 414df930be7Sderaadt /* we have to read the input pipe no matter whether 415df930be7Sderaadt * we mail or not, but obviously we only write to 416df930be7Sderaadt * mail pipe if we ARE mailing. 417df930be7Sderaadt */ 418df930be7Sderaadt 419df930be7Sderaadt while (EOF != (ch = getc(in))) { 420df930be7Sderaadt bytes++; 4212009bb53Smillert if (mail) 422f454ebdeSmillert fputc(ch, mail); 423df930be7Sderaadt } 424df930be7Sderaadt 42514eea816Sjob } /*if data from grandchild*/ 42614eea816Sjob 42714eea816Sjob fclose(in); /* also closes stdout_pipe[READ_PIPE] */ 42814eea816Sjob } 42914eea816Sjob 43014eea816Sjob /* wait for children to die. 43114eea816Sjob */ 43214eea816Sjob int waiter; 43314eea816Sjob if (jobpid > 0) { 434df69c215Sderaadt while (waitpid(jobpid, &waiter, 0) == -1 && errno == EINTR) 43514eea816Sjob ; 43614eea816Sjob 43714eea816Sjob /* If everything went well, and -n was set, _and_ we have mail, 43814eea816Sjob * we won't be mailing... so shoot the messenger! 43914eea816Sjob */ 44014eea816Sjob if (WIFEXITED(waiter) && WEXITSTATUS(waiter) == 0 44114eea816Sjob && (e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR 44214eea816Sjob && mail) { 44314eea816Sjob kill(mailpid, SIGKILL); 44414eea816Sjob (void)fclose(mail); 44514eea816Sjob mail = NULL; 44614eea816Sjob } 44714eea816Sjob 448787ffcc0Stb /* only close pipe if we opened it -- i.e., we're mailing... */ 4492009bb53Smillert if (mail) { 45014eea816Sjob /* 45114eea816Sjob * Note: the pclose will probably see the termination 45214eea816Sjob * of the grandchild in addition to the mail process, 45314eea816Sjob * since it (the grandchild) is likely to exit after 45414eea816Sjob * closing its stdout. 455df930be7Sderaadt */ 45694b4a649Stedu status = cron_pclose(mail, mailpid); 457df930be7Sderaadt } 458df930be7Sderaadt 459df930be7Sderaadt /* if there was output and we could not mail it, 460df930be7Sderaadt * log the facts so the poor user can figure out 461df930be7Sderaadt * what's going on. 462df930be7Sderaadt */ 4632009bb53Smillert if (mail && status) { 464a38a3395Smillert syslog(LOG_NOTICE, "(%s) MAIL (mailed %zu byte" 465a38a3395Smillert "%s of output but got status 0x%04x)", usernm, 466a38a3395Smillert bytes, (bytes == 1) ? "" : "s", status); 467df930be7Sderaadt } 468df930be7Sderaadt } 469df930be7Sderaadt 47014eea816Sjob if (stdinjob > 0) 471df69c215Sderaadt while (waitpid(stdinjob, &waiter, 0) == -1 && errno == EINTR) 472e134e629Smillert ; 473df930be7Sderaadt } 474df930be7Sderaadt 4752b139a93Smillert int 476785ecd14Stedu safe_p(const char *usernm, const char *s) 477785ecd14Stedu { 4781e998768Smillert static const char safe_delim[] = "@!:%+-.,"; /* conservative! */ 479f454ebdeSmillert const char *t; 480f454ebdeSmillert int ch, first; 481df930be7Sderaadt 482db091496Sderaadt for (t = s, first = 1; (ch = (unsigned char)*t++) != '\0'; first = 0) { 483f454ebdeSmillert if (isascii(ch) && isprint(ch) && 4843c2f3a11Smillert (isalnum(ch) || ch == '_' || 4853c2f3a11Smillert (!first && strchr(safe_delim, ch)))) 486f454ebdeSmillert continue; 487a38a3395Smillert syslog(LOG_WARNING, "(%s) UNSAFE (%s)", usernm, s); 488f454ebdeSmillert return (FALSE); 489df930be7Sderaadt } 490f454ebdeSmillert return (TRUE); 491df930be7Sderaadt } 492