1*cd115652Schristos /* $NetBSD: do_command.c,v 1.15 2020/04/18 19:32:19 christos Exp $ */
2032a4398Schristos
30061c6a5Schristos /* Copyright 1988,1990,1993,1994 by Paul Vixie
40061c6a5Schristos * All rights reserved
50061c6a5Schristos */
60061c6a5Schristos
70061c6a5Schristos /*
80061c6a5Schristos * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
90061c6a5Schristos * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
100061c6a5Schristos *
110061c6a5Schristos * Permission to use, copy, modify, and distribute this software for any
120061c6a5Schristos * purpose with or without fee is hereby granted, provided that the above
130061c6a5Schristos * copyright notice and this permission notice appear in all copies.
140061c6a5Schristos *
150061c6a5Schristos * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
160061c6a5Schristos * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
170061c6a5Schristos * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
180061c6a5Schristos * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
190061c6a5Schristos * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
200061c6a5Schristos * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
210061c6a5Schristos * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
220061c6a5Schristos */
23032a4398Schristos #include <sys/cdefs.h>
240061c6a5Schristos #if !defined(lint) && !defined(LINT)
25032a4398Schristos #if 0
260061c6a5Schristos static char rcsid[] = "Id: do_command.c,v 1.9 2004/01/23 18:56:42 vixie Exp";
27032a4398Schristos #else
28*cd115652Schristos __RCSID("$NetBSD: do_command.c,v 1.15 2020/04/18 19:32:19 christos Exp $");
29032a4398Schristos #endif
300061c6a5Schristos #endif
310061c6a5Schristos
320061c6a5Schristos #include "cron.h"
33032a4398Schristos #include <unistd.h>
340061c6a5Schristos
35065057e6Schristos static int child_process(entry *);
360061c6a5Schristos static int safe_p(const char *, const char *);
370061c6a5Schristos
38*cd115652Schristos pid_t
do_command(entry * e,user * u)390061c6a5Schristos do_command(entry *e, user *u) {
40065057e6Schristos int retval;
41065057e6Schristos
420061c6a5Schristos Debug(DPROC, ("[%ld] do_command(%s, (%s,%ld,%ld))\n",
430061c6a5Schristos (long)getpid(), e->cmd, u->name,
44032a4398Schristos (long)e->pwd->pw_uid, (long)e->pwd->pw_gid));
450061c6a5Schristos
460061c6a5Schristos /* fork to become asynchronous -- parent process is done immediately,
470061c6a5Schristos * and continues to run the normal cron code, which means return to
480061c6a5Schristos * tick(). the child and grandchild don't leave this function, alive.
490061c6a5Schristos *
500061c6a5Schristos * vfork() is unsuitable, since we have much to do, and the parent
510061c6a5Schristos * needs to be able to run off and fork other processes.
520061c6a5Schristos */
53fdf2ea13Schristos
54fdf2ea13Schristos pid_t jobpid;
55fdf2ea13Schristos switch (jobpid = fork()) {
560061c6a5Schristos case -1:
570061c6a5Schristos log_it("CRON", getpid(), "error", "can't fork");
580061c6a5Schristos break;
590061c6a5Schristos case 0:
600061c6a5Schristos /* child process */
610061c6a5Schristos acquire_daemonlock(1);
62065057e6Schristos retval = child_process(e);
63065057e6Schristos Debug(DPROC, ("[%ld] child process done (rc=%d), exiting\n",
64065057e6Schristos (long)getpid(), retval));
65065057e6Schristos _exit(retval);
660061c6a5Schristos break;
670061c6a5Schristos default:
680061c6a5Schristos /* parent process */
69*cd115652Schristos if ((e->flags & SINGLE_JOB) == 0)
70*cd115652Schristos jobpid = -1;
710061c6a5Schristos break;
720061c6a5Schristos }
73032a4398Schristos Debug(DPROC, ("[%ld] main process returning to work\n",(long)getpid()));
74*cd115652Schristos
75*cd115652Schristos /* only return pid if a singleton */
76*cd115652Schristos return jobpid;
770061c6a5Schristos }
780061c6a5Schristos
790061c6a5Schristos static void
sigchld_handler(int signo)80065057e6Schristos sigchld_handler(int signo) {
81065057e6Schristos for (;;) {
82065057e6Schristos WAIT_T waiter;
83065057e6Schristos PID_T pid = waitpid(-1, &waiter, WNOHANG);
84065057e6Schristos
85065057e6Schristos switch (pid) {
86065057e6Schristos case -1:
87065057e6Schristos if (errno == EINTR)
88065057e6Schristos continue;
89065057e6Schristos case 0:
90065057e6Schristos return;
91065057e6Schristos default:
92065057e6Schristos break;
93065057e6Schristos }
94065057e6Schristos }
95065057e6Schristos }
96065057e6Schristos
977e10eaceSchristos static void
write_data(char * volatile input_data,int * stdin_pipe,int * stdout_pipe)987e10eaceSchristos write_data(char *volatile input_data, int *stdin_pipe, int *stdout_pipe)
997e10eaceSchristos {
1007e10eaceSchristos FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
1017e10eaceSchristos int need_newline = FALSE;
1027e10eaceSchristos int escaped = FALSE;
1037e10eaceSchristos int ch;
1047e10eaceSchristos
1057e10eaceSchristos Debug(DPROC, ("[%ld] child2 sending data to grandchild\n",
1067e10eaceSchristos (long)getpid()));
1077e10eaceSchristos
1087e10eaceSchristos #ifdef USE_PAM
1097e10eaceSchristos cron_pam_child_close();
1107e10eaceSchristos #else
1117e10eaceSchristos log_close();
1127e10eaceSchristos #endif
1137e10eaceSchristos
1147e10eaceSchristos /* close the pipe we don't use, since we inherited it and
1157e10eaceSchristos * are part of its reference count now.
1167e10eaceSchristos */
1177e10eaceSchristos (void)close(stdout_pipe[READ_PIPE]);
1187e10eaceSchristos
1197e10eaceSchristos /* translation:
1207e10eaceSchristos * \% -> %
1217e10eaceSchristos * % -> \n
1227e10eaceSchristos * \x -> \x for all x != %
1237e10eaceSchristos */
1247e10eaceSchristos while ((ch = *input_data++) != '\0') {
1257e10eaceSchristos if (escaped) {
1267e10eaceSchristos if (ch != '%')
1277e10eaceSchristos (void)putc('\\', out);
1287e10eaceSchristos } else {
1297e10eaceSchristos if (ch == '%')
1307e10eaceSchristos ch = '\n';
1317e10eaceSchristos }
1327e10eaceSchristos
1337e10eaceSchristos if (!(escaped = (ch == '\\'))) {
1347e10eaceSchristos (void)putc(ch, out);
1357e10eaceSchristos need_newline = (ch != '\n');
1367e10eaceSchristos }
1377e10eaceSchristos }
1387e10eaceSchristos if (escaped)
1397e10eaceSchristos (void)putc('\\', out);
1407e10eaceSchristos if (need_newline)
1417e10eaceSchristos (void)putc('\n', out);
1427e10eaceSchristos
1437e10eaceSchristos /* close the pipe, causing an EOF condition. fclose causes
1447e10eaceSchristos * stdin_pipe[WRITE_PIPE] to be closed, too.
1457e10eaceSchristos */
1467e10eaceSchristos (void)fclose(out);
1477e10eaceSchristos
1487e10eaceSchristos Debug(DPROC, ("[%ld] child2 done sending to grandchild\n",
1497e10eaceSchristos (long)getpid()));
1507e10eaceSchristos }
1517e10eaceSchristos
1527e10eaceSchristos static int
read_data(entry * e,const char * mailto,const char * usernm,char ** envp,int * stdout_pipe,pid_t jobpid)1537e10eaceSchristos read_data(entry *e, const char *mailto, const char *usernm, char **envp,
154fdf2ea13Schristos int *stdout_pipe, pid_t jobpid)
1557e10eaceSchristos {
1567e10eaceSchristos FILE *in = fdopen(stdout_pipe[READ_PIPE], "r");
1577e10eaceSchristos FILE *mail = NULL;
1587e10eaceSchristos int bytes = 1;
1597e10eaceSchristos int status = 0;
1607e10eaceSchristos int ch = getc(in);
1617e10eaceSchristos int retval = 0;
1627e10eaceSchristos sig_t oldchld = NULL;
1637e10eaceSchristos
1647e10eaceSchristos if (ch == EOF)
1657e10eaceSchristos goto out;
1667e10eaceSchristos
1677e10eaceSchristos Debug(DPROC|DEXT, ("[%ld] got data (%x:%c) from grandchild\n",
1687e10eaceSchristos (long)getpid(), ch, ch));
1697e10eaceSchristos
1707e10eaceSchristos /* get name of recipient. this is MAILTO if set to a
1717e10eaceSchristos * valid local username; USER otherwise.
1727e10eaceSchristos */
1737e10eaceSchristos if (mailto) {
1747e10eaceSchristos /* MAILTO was present in the environment
1757e10eaceSchristos */
1767e10eaceSchristos if (!*mailto) {
1777e10eaceSchristos /* ... but it's empty. set to NULL
1787e10eaceSchristos */
1797e10eaceSchristos mailto = NULL;
1807e10eaceSchristos }
1817e10eaceSchristos } else {
1827e10eaceSchristos /* MAILTO not present, set to USER.
1837e10eaceSchristos */
1847e10eaceSchristos mailto = usernm;
1857e10eaceSchristos }
1867e10eaceSchristos
1877e10eaceSchristos /*
1887e10eaceSchristos * Unsafe, disable mailing.
1897e10eaceSchristos */
190558326f7Schristos if (mailto && !safe_p(usernm, mailto))
1917e10eaceSchristos mailto = NULL;
1927e10eaceSchristos
1937e10eaceSchristos /* if we are supposed to be mailing, MAILTO will
1947e10eaceSchristos * be non-NULL. only in this case should we set
1957e10eaceSchristos * up the mail command and subjects and stuff...
1967e10eaceSchristos */
1977e10eaceSchristos
1987e10eaceSchristos if (mailto) {
1997e10eaceSchristos char **env;
2007e10eaceSchristos char mailcmd[MAX_COMMAND];
2017e10eaceSchristos char hostname[MAXHOSTNAMELEN + 1];
2027e10eaceSchristos
2037e10eaceSchristos (void)gethostname(hostname, MAXHOSTNAMELEN);
2047e10eaceSchristos if (strlens(MAILFMT, MAILARG, NULL) + 1 >= sizeof mailcmd) {
2057e10eaceSchristos log_it(usernm, getpid(), "MAIL", "mailcmd too long");
2067e10eaceSchristos retval = ERROR_EXIT;
2077e10eaceSchristos goto out;
2087e10eaceSchristos }
2097e10eaceSchristos (void)snprintf(mailcmd, sizeof(mailcmd), MAILFMT, MAILARG);
2107e10eaceSchristos oldchld = signal(SIGCHLD, SIG_DFL);
2117e10eaceSchristos if (!(mail = cron_popen(mailcmd, "w", e->pwd))) {
2127e10eaceSchristos log_itx(usernm, getpid(), "MAIL",
2137e10eaceSchristos "cannot run `%s'", mailcmd);
2147e10eaceSchristos (void) signal(SIGCHLD, oldchld);
2157e10eaceSchristos retval = ERROR_EXIT;
2167e10eaceSchristos goto out;
2177e10eaceSchristos }
2187e10eaceSchristos (void)fprintf(mail, "From: root (Cron Daemon)\n");
2197e10eaceSchristos (void)fprintf(mail, "To: %s\n", mailto);
2207e10eaceSchristos (void)fprintf(mail, "Subject: Cron <%s@%s> %s\n",
2217e10eaceSchristos usernm, hostname, e->cmd);
2227e10eaceSchristos (void)fprintf(mail, "Auto-Submitted: auto-generated\n");
2237e10eaceSchristos #ifdef MAIL_DATE
2247e10eaceSchristos (void)fprintf(mail, "Date: %s\n", arpadate(&StartTime));
2257e10eaceSchristos #endif /*MAIL_DATE*/
2267e10eaceSchristos for (env = envp; *env; env++)
2277e10eaceSchristos (void)fprintf(mail, "X-Cron-Env: <%s>\n", *env);
2287e10eaceSchristos (void)fprintf(mail, "\n");
2297e10eaceSchristos
2307e10eaceSchristos /* this was the first char from the pipe
2317e10eaceSchristos */
2327e10eaceSchristos (void)putc(ch, mail);
2337e10eaceSchristos }
2347e10eaceSchristos
2357e10eaceSchristos /* we have to read the input pipe no matter whether
2367e10eaceSchristos * we mail or not, but obviously we only write to
2377e10eaceSchristos * mail pipe if we ARE mailing.
2387e10eaceSchristos */
2397e10eaceSchristos
2407e10eaceSchristos while (EOF != (ch = getc(in))) {
2417e10eaceSchristos bytes++;
2427e10eaceSchristos if (mailto)
2437e10eaceSchristos (void)putc(ch, mail);
2447e10eaceSchristos }
2457e10eaceSchristos
2467e10eaceSchristos /* only close pipe if we opened it -- i.e., we're
2477e10eaceSchristos * mailing...
2487e10eaceSchristos */
2497e10eaceSchristos
2507e10eaceSchristos if (mailto) {
251fdf2ea13Schristos if (e->flags & MAIL_WHEN_ERR) {
252fdf2ea13Schristos int jstatus = -1;
253fdf2ea13Schristos if (jobpid <= 0)
254fdf2ea13Schristos log_it("CRON", getpid(), "error",
255fdf2ea13Schristos "no job pid");
256fdf2ea13Schristos else {
257fdf2ea13Schristos while (waitpid(jobpid, &jstatus, WNOHANG) == -1)
258fdf2ea13Schristos if (errno != EINTR) {
259fdf2ea13Schristos log_it("CRON", getpid(),
260fdf2ea13Schristos "error", "no job pid");
261fdf2ea13Schristos break;
262fdf2ea13Schristos }
263fdf2ea13Schristos }
264fdf2ea13Schristos /* If everything went well, and -n was set, _and_ we
265fdf2ea13Schristos * have mail, we won't be mailing... so shoot the
266fdf2ea13Schristos * messenger!
267fdf2ea13Schristos */
268fdf2ea13Schristos if (WIFEXITED(jstatus) && WEXITSTATUS(jstatus) == 0) {
269fdf2ea13Schristos Debug(DPROC, ("[%ld] aborting pipe to mail\n",
270fdf2ea13Schristos (long)getpid()));
271fdf2ea13Schristos status = cron_pabort(mail);
272fdf2ea13Schristos mailto = NULL;
273fdf2ea13Schristos }
274fdf2ea13Schristos }
275fdf2ea13Schristos
276fdf2ea13Schristos if (mailto) {
277fdf2ea13Schristos Debug(DPROC, ("[%ld] closing pipe to mail\n",
278fdf2ea13Schristos (long)getpid()));
2797e10eaceSchristos /* Note: the pclose will probably see
2807e10eaceSchristos * the termination of the grandchild
2817e10eaceSchristos * in addition to the mail process, since
2827e10eaceSchristos * it (the grandchild) is likely to exit
2837e10eaceSchristos * after closing its stdout.
2847e10eaceSchristos */
2857e10eaceSchristos status = cron_pclose(mail);
286fdf2ea13Schristos mail = NULL;
287fdf2ea13Schristos }
2887e10eaceSchristos (void) signal(SIGCHLD, oldchld);
2897e10eaceSchristos }
2907e10eaceSchristos
2917e10eaceSchristos /* if there was output and we could not mail it,
2927e10eaceSchristos * log the facts so the poor user can figure out
2937e10eaceSchristos * what's going on.
2947e10eaceSchristos */
2957e10eaceSchristos if (mailto && status) {
2967e10eaceSchristos log_itx(usernm, getpid(), "MAIL",
2977e10eaceSchristos "mailed %d byte%s of output to `%s' but"
2987e10eaceSchristos " got status %#04x", bytes,
2997e10eaceSchristos bytes == 1 ? "" : "s", mailto, status);
3007e10eaceSchristos }
3017e10eaceSchristos
3027e10eaceSchristos out:
3037e10eaceSchristos Debug(DPROC, ("[%ld] got EOF from grandchild\n", (long)getpid()));
3047e10eaceSchristos
3057e10eaceSchristos (void)fclose(in); /* also closes stdout_pipe[READ_PIPE] */
3067e10eaceSchristos return retval;
3077e10eaceSchristos }
3087e10eaceSchristos
309065057e6Schristos extern char **environ;
310065057e6Schristos static int
exec_user_command(entry * e,char ** envp,char * usernm,int * stdin_pipe,int * stdout_pipe,pid_t * jobpid)311bdb24028Schristos exec_user_command(entry *e, char **envp, char *usernm, int *stdin_pipe,
312fdf2ea13Schristos int *stdout_pipe, pid_t *jobpid)
313bdb24028Schristos {
314bdb24028Schristos char *homedir;
315fdf2ea13Schristos char * volatile *ep;
3160061c6a5Schristos
317fdf2ea13Schristos switch (*jobpid = vfork()) {
3180061c6a5Schristos case -1:
319bdb24028Schristos return -1;
3200061c6a5Schristos case 0:
321fdf2ea13Schristos ep = envp;
3220061c6a5Schristos Debug(DPROC, ("[%ld] grandchild process vfork()'ed\n",
323032a4398Schristos (long)getpid()));
3240061c6a5Schristos
3250061c6a5Schristos /* write a log message. we've waited this long to do it
3260061c6a5Schristos * because it was not until now that we knew the PID that
3270061c6a5Schristos * the actual user command shell was going to get and the
3280061c6a5Schristos * PID is part of the log message.
3290061c6a5Schristos */
3300061c6a5Schristos if ((e->flags & DONT_LOG) == 0) {
331032a4398Schristos char *x = mkprints(e->cmd, strlen(e->cmd));
3320061c6a5Schristos
333032a4398Schristos log_it(usernm, getpid(), "CMD START", x);
3340061c6a5Schristos free(x);
3350061c6a5Schristos }
3360061c6a5Schristos
3370061c6a5Schristos /* that's the last thing we'll log. close the log files.
3380061c6a5Schristos */
3390061c6a5Schristos log_close();
3400061c6a5Schristos
3410061c6a5Schristos /* get new pgrp, void tty, etc.
3420061c6a5Schristos */
343032a4398Schristos if (setsid() == -1)
344032a4398Schristos syslog(LOG_ERR, "setsid() failure: %m");
3450061c6a5Schristos
3460061c6a5Schristos /* close the pipe ends that we won't use. this doesn't affect
3470061c6a5Schristos * the parent, who has to read and write them; it keeps the
3480061c6a5Schristos * kernel from recording us as a potential client TWICE --
3490061c6a5Schristos * which would keep it from sending SIGPIPE in otherwise
3500061c6a5Schristos * appropriate circumstances.
3510061c6a5Schristos */
352032a4398Schristos (void)close(stdin_pipe[WRITE_PIPE]);
353032a4398Schristos (void)close(stdout_pipe[READ_PIPE]);
3540061c6a5Schristos
3550061c6a5Schristos /* grandchild process. make std{in,out} be the ends of
3560061c6a5Schristos * pipes opened by our daddy; make stderr go to stdout.
3570061c6a5Schristos */
3580061c6a5Schristos if (stdin_pipe[READ_PIPE] != STDIN) {
359032a4398Schristos (void)dup2(stdin_pipe[READ_PIPE], STDIN);
360032a4398Schristos (void)close(stdin_pipe[READ_PIPE]);
3610061c6a5Schristos }
3620061c6a5Schristos if (stdout_pipe[WRITE_PIPE] != STDOUT) {
363032a4398Schristos (void)dup2(stdout_pipe[WRITE_PIPE], STDOUT);
364032a4398Schristos (void)close(stdout_pipe[WRITE_PIPE]);
3650061c6a5Schristos }
366032a4398Schristos (void)dup2(STDOUT, STDERR);
3670061c6a5Schristos
3680061c6a5Schristos /* set our directory, uid and gid. Set gid first, since once
3690061c6a5Schristos * we set uid, we've lost root privledges.
3700061c6a5Schristos */
3710061c6a5Schristos #ifdef LOGIN_CAP
3720061c6a5Schristos {
3730061c6a5Schristos #ifdef BSD_AUTH
3740061c6a5Schristos auth_session_t *as;
3750061c6a5Schristos #endif
3760061c6a5Schristos login_cap_t *lc;
377032a4398Schristos char *p;
3780061c6a5Schristos
3790061c6a5Schristos if ((lc = login_getclass(e->pwd->pw_class)) == NULL) {
380032a4398Schristos warnx("unable to get login class for `%s'",
3810061c6a5Schristos e->pwd->pw_name);
3820061c6a5Schristos _exit(ERROR_EXIT);
3830061c6a5Schristos }
3840061c6a5Schristos if (setusercontext(lc, e->pwd, e->pwd->pw_uid, LOGIN_SETALL) < 0) {
385032a4398Schristos warnx("setusercontext failed for `%s'",
3860061c6a5Schristos e->pwd->pw_name);
3870061c6a5Schristos _exit(ERROR_EXIT);
3880061c6a5Schristos }
3890061c6a5Schristos #ifdef BSD_AUTH
3900061c6a5Schristos as = auth_open();
3910061c6a5Schristos if (as == NULL || auth_setpwd(as, e->pwd) != 0) {
392032a4398Schristos warn("can't malloc");
3930061c6a5Schristos _exit(ERROR_EXIT);
3940061c6a5Schristos }
3950061c6a5Schristos if (auth_approval(as, lc, usernm, "cron") <= 0) {
396032a4398Schristos warnx("approval failed for `%s'",
3970061c6a5Schristos e->pwd->pw_name);
3980061c6a5Schristos _exit(ERROR_EXIT);
3990061c6a5Schristos }
4000061c6a5Schristos auth_close(as);
4010061c6a5Schristos #endif /* BSD_AUTH */
4020061c6a5Schristos login_close(lc);
4030061c6a5Schristos
4040061c6a5Schristos /* If no PATH specified in crontab file but
4050061c6a5Schristos * we just added one via login.conf, add it to
4060061c6a5Schristos * the crontab environment.
4070061c6a5Schristos */
408065057e6Schristos if (env_get("PATH", envp) == NULL && environ != NULL) {
409032a4398Schristos if ((p = getenv("PATH")) != NULL)
410bdb24028Schristos ep = env_set(envp, p);
4110061c6a5Schristos }
4120061c6a5Schristos }
4130061c6a5Schristos #else
414032a4398Schristos if (setgid(e->pwd->pw_gid) != 0) {
415a362d640Schristos syslog(LOG_ERR, "setgid(%d) failed for %s: %m",
416a362d640Schristos e->pwd->pw_gid, e->pwd->pw_name);
417032a4398Schristos _exit(ERROR_EXIT);
418032a4398Schristos }
419032a4398Schristos if (initgroups(usernm, e->pwd->pw_gid) != 0) {
420a362d640Schristos syslog(LOG_ERR, "initgroups(%s, %d) failed for %s: %m",
421a362d640Schristos usernm, e->pwd->pw_gid, e->pwd->pw_name);
422032a4398Schristos _exit(ERROR_EXIT);
423032a4398Schristos }
4240061c6a5Schristos #if (defined(BSD)) && (BSD >= 199103)
425032a4398Schristos if (setlogin(usernm) < 0) {
426a362d640Schristos syslog(LOG_ERR, "setlogin(%s) failure for %s: %m",
427a362d640Schristos usernm, e->pwd->pw_name);
428032a4398Schristos _exit(ERROR_EXIT);
429032a4398Schristos }
4300061c6a5Schristos #endif /* BSD */
431065057e6Schristos #ifdef USE_PAM
432065057e6Schristos if (!cron_pam_setcred())
433065057e6Schristos _exit(ERROR_EXIT);
434065057e6Schristos cron_pam_child_close();
435065057e6Schristos #endif
436032a4398Schristos if (setuid(e->pwd->pw_uid) != 0) {
437a362d640Schristos syslog(LOG_ERR, "setuid(%d) failed for %s: %m",
438a362d640Schristos e->pwd->pw_uid, e->pwd->pw_name);
439032a4398Schristos _exit(ERROR_EXIT);
440032a4398Schristos }
441032a4398Schristos /* we aren't root after this... */
4420061c6a5Schristos #endif /* LOGIN_CAP */
443bdb24028Schristos homedir = env_get("HOME", __UNVOLATILE(ep));
444a362d640Schristos if (chdir(homedir) != 0) {
445a362d640Schristos syslog(LOG_ERR, "chdir(%s) $HOME failed for %s: %m",
446a362d640Schristos homedir, e->pwd->pw_name);
447032a4398Schristos _exit(ERROR_EXIT);
448032a4398Schristos }
449032a4398Schristos
450032a4398Schristos #ifdef USE_SIGCHLD
451032a4398Schristos /* our grandparent is watching for our death by catching
452032a4398Schristos * SIGCHLD. the parent is ignoring SIGCHLD's; we want
453032a4398Schristos * to restore default behaviour.
454032a4398Schristos */
455032a4398Schristos (void) signal(SIGCHLD, SIG_DFL);
456032a4398Schristos #endif
457065057e6Schristos (void) signal(SIGPIPE, SIG_DFL);
458065057e6Schristos (void) signal(SIGUSR1, SIG_DFL);
459032a4398Schristos (void) signal(SIGHUP, SIG_DFL);
4600061c6a5Schristos
4610061c6a5Schristos /*
4620061c6a5Schristos * Exec the command.
4630061c6a5Schristos */
4640061c6a5Schristos {
465bdb24028Schristos char *shell = env_get("SHELL", __UNVOLATILE(ep));
4660061c6a5Schristos
4670061c6a5Schristos # if DEBUGGING
4680061c6a5Schristos if (DebugFlags & DTEST) {
469032a4398Schristos (void)fprintf(stderr,
4700061c6a5Schristos "debug DTEST is on, not exec'ing command.\n");
471032a4398Schristos (void)fprintf(stderr,
4720061c6a5Schristos "\tcmd='%s' shell='%s'\n", e->cmd, shell);
4730061c6a5Schristos _exit(OK_EXIT);
4740061c6a5Schristos }
4750061c6a5Schristos # endif /*DEBUGGING*/
476065057e6Schristos (void)execle(shell, shell, "-c", e->cmd, NULL, envp);
477032a4398Schristos warn("execl: couldn't exec `%s'", shell);
4780061c6a5Schristos _exit(ERROR_EXIT);
4790061c6a5Schristos }
480bdb24028Schristos return 0;
4810061c6a5Schristos default:
4820061c6a5Schristos /* parent process */
483bdb24028Schristos return 0;
484bdb24028Schristos }
485bdb24028Schristos }
486bdb24028Schristos
487bdb24028Schristos static int
child_process(entry * e)488bdb24028Schristos child_process(entry *e) {
489bdb24028Schristos int stdin_pipe[2], stdout_pipe[2];
490bdb24028Schristos char * volatile input_data;
491bdb24028Schristos char *usernm, * volatile mailto;
492bdb24028Schristos struct sigaction sact;
493bdb24028Schristos char **envp = e->envp;
494bdb24028Schristos int retval = OK_EXIT;
495fdf2ea13Schristos pid_t jobpid = 0;
496bdb24028Schristos
497bdb24028Schristos Debug(DPROC, ("[%ld] child_process('%s')\n", (long)getpid(), e->cmd));
498bdb24028Schristos
499bdb24028Schristos setproctitle("running job");
500bdb24028Schristos
501bdb24028Schristos /* discover some useful and important environment settings
502bdb24028Schristos */
503bdb24028Schristos usernm = e->pwd->pw_name;
504bdb24028Schristos mailto = env_get("MAILTO", envp);
505bdb24028Schristos
506bdb24028Schristos memset(&sact, 0, sizeof(sact));
507bdb24028Schristos sigemptyset(&sact.sa_mask);
508bdb24028Schristos sact.sa_flags = 0;
509bdb24028Schristos #ifdef SA_RESTART
510bdb24028Schristos sact.sa_flags |= SA_RESTART;
511bdb24028Schristos #endif
512bdb24028Schristos sact.sa_handler = sigchld_handler;
513bdb24028Schristos (void) sigaction(SIGCHLD, &sact, NULL);
514bdb24028Schristos
515bdb24028Schristos /* create some pipes to talk to our future child
516bdb24028Schristos */
517bdb24028Schristos if (pipe(stdin_pipe) == -1) /* child's stdin */
518bdb24028Schristos log_it("CRON", getpid(), "error", "create child stdin pipe");
519bdb24028Schristos if (pipe(stdout_pipe) == -1) /* child's stdout */
520bdb24028Schristos log_it("CRON", getpid(), "error", "create child stdout pipe");
521bdb24028Schristos
522bdb24028Schristos /* since we are a forked process, we can diddle the command string
523bdb24028Schristos * we were passed -- nobody else is going to use it again, right?
524bdb24028Schristos *
525bdb24028Schristos * if a % is present in the command, previous characters are the
526bdb24028Schristos * command, and subsequent characters are the additional input to
527bdb24028Schristos * the command. An escaped % will have the escape character stripped
528bdb24028Schristos * from it. Subsequent %'s will be transformed into newlines,
529bdb24028Schristos * but that happens later.
530bdb24028Schristos */
531bdb24028Schristos /*local*/{
532bdb24028Schristos int escaped = FALSE;
533bdb24028Schristos int ch;
534bdb24028Schristos char *p;
535bdb24028Schristos
536bdb24028Schristos /* translation:
537bdb24028Schristos * \% -> %
538bdb24028Schristos * % -> end of command, following is command input.
539bdb24028Schristos * \x -> \x for all x != %
540bdb24028Schristos */
541bdb24028Schristos input_data = p = e->cmd;
542bdb24028Schristos while ((ch = *input_data++) != '\0') {
543bdb24028Schristos if (escaped) {
544bdb24028Schristos if (ch != '%')
545bdb24028Schristos *p++ = '\\';
546bdb24028Schristos } else {
547bdb24028Schristos if (ch == '%') {
5480061c6a5Schristos break;
5490061c6a5Schristos }
550bdb24028Schristos }
551bdb24028Schristos
552bdb24028Schristos if (!(escaped = (ch == '\\'))) {
553bdb24028Schristos *p++ = (char)ch;
554bdb24028Schristos }
555bdb24028Schristos }
556bdb24028Schristos if (ch == '\0') {
557bdb24028Schristos /* move pointer back, so that code below
558bdb24028Schristos * won't think we encountered % sequence */
559bdb24028Schristos input_data--;
560bdb24028Schristos }
561bdb24028Schristos if (escaped)
562bdb24028Schristos *p++ = '\\';
563bdb24028Schristos
564bdb24028Schristos *p = '\0';
565bdb24028Schristos }
566bdb24028Schristos
567bdb24028Schristos #ifdef USE_PAM
568bdb24028Schristos if (!cron_pam_start(usernm))
569bdb24028Schristos return ERROR_EXIT;
570bdb24028Schristos
571bdb24028Schristos if (!(envp = cron_pam_getenvlist(envp))) {
572bdb24028Schristos retval = ERROR_EXIT;
573bdb24028Schristos goto child_process_end;
574bdb24028Schristos }
575bdb24028Schristos #endif
576bdb24028Schristos
577bdb24028Schristos /* fork again, this time so we can exec the user's command.
578bdb24028Schristos */
579fdf2ea13Schristos if (exec_user_command(e, envp, usernm, stdin_pipe, stdout_pipe,
580fdf2ea13Schristos &jobpid) == -1) {
581bdb24028Schristos retval = ERROR_EXIT;
582bdb24028Schristos goto child_process_end;
583bdb24028Schristos }
584bdb24028Schristos
5850061c6a5Schristos
5860061c6a5Schristos /* middle process, child of original cron, parent of process running
5870061c6a5Schristos * the user's command.
5880061c6a5Schristos */
5890061c6a5Schristos
590032a4398Schristos Debug(DPROC, ("[%ld] child continues, closing pipes\n",(long)getpid()));
5910061c6a5Schristos
5920061c6a5Schristos /* close the ends of the pipe that will only be referenced in the
5930061c6a5Schristos * grandchild process...
5940061c6a5Schristos */
595032a4398Schristos (void)close(stdin_pipe[READ_PIPE]);
596032a4398Schristos (void)close(stdout_pipe[WRITE_PIPE]);
5970061c6a5Schristos
5980061c6a5Schristos /*
5990061c6a5Schristos * write, to the pipe connected to child's stdin, any input specified
6000061c6a5Schristos * after a % in the crontab entry. while we copy, convert any
6010061c6a5Schristos * additional %'s to newlines. when done, if some characters were
6020061c6a5Schristos * written and the last one wasn't a newline, write a newline.
6030061c6a5Schristos *
6040061c6a5Schristos * Note that if the input data won't fit into one pipe buffer (2K
6050061c6a5Schristos * or 4K on most BSD systems), and the child doesn't read its stdin,
6060061c6a5Schristos * we would block here. thus we must fork again.
6070061c6a5Schristos */
6080061c6a5Schristos
6097e10eaceSchristos if (*input_data) {
6107e10eaceSchristos switch (fork()) {
6117e10eaceSchristos case 0:
6127e10eaceSchristos write_data(input_data, stdin_pipe, stdout_pipe);
6137e10eaceSchristos exit(EXIT_SUCCESS);
6147e10eaceSchristos case -1:
6157e10eaceSchristos retval = ERROR_EXIT;
6167e10eaceSchristos goto child_process_end;
6177e10eaceSchristos default:
6187e10eaceSchristos break;
6190061c6a5Schristos }
6200061c6a5Schristos }
6210061c6a5Schristos
6220061c6a5Schristos /* close the pipe to the grandkiddie's stdin, since its wicked uncle
6230061c6a5Schristos * ernie back there has it open and will close it when he's done.
6240061c6a5Schristos */
625032a4398Schristos (void)close(stdin_pipe[WRITE_PIPE]);
6260061c6a5Schristos
6270061c6a5Schristos /*
6280061c6a5Schristos * read output from the grandchild. it's stderr has been redirected to
6290061c6a5Schristos * it's stdout, which has been redirected to our pipe. if there is any
6300061c6a5Schristos * output, we'll be mailing it to the user whose crontab this is...
6310061c6a5Schristos * when the grandchild exits, we'll get EOF.
6320061c6a5Schristos */
6330061c6a5Schristos
6340061c6a5Schristos Debug(DPROC, ("[%ld] child reading output from grandchild\n",
635032a4398Schristos (long)getpid()));
6360061c6a5Schristos
637fdf2ea13Schristos retval = read_data(e, mailto, usernm, envp, stdout_pipe, jobpid);
6387e10eaceSchristos if (retval)
639065057e6Schristos goto child_process_end;
6400061c6a5Schristos
6410061c6a5Schristos
6420061c6a5Schristos /* wait for children to die.
6430061c6a5Schristos */
644065057e6Schristos sigchld_handler(0);
645032a4398Schristos
646032a4398Schristos /* Log the time when we finished deadling with the job */
647032a4398Schristos /*local*/{
648032a4398Schristos char *x = mkprints(e->cmd, strlen(e->cmd));
649032a4398Schristos
650032a4398Schristos log_it(usernm, getpid(), "CMD FINISH", x);
651032a4398Schristos free(x);
6520061c6a5Schristos }
653065057e6Schristos
654065057e6Schristos child_process_end:
655065057e6Schristos #ifdef USE_PAM
656065057e6Schristos cron_pam_finish();
657065057e6Schristos #endif
658065057e6Schristos return retval;
6590061c6a5Schristos }
6600061c6a5Schristos
6610061c6a5Schristos static int
safe_p(const char * usernm,const char * s)6620061c6a5Schristos safe_p(const char *usernm, const char *s) {
6630061c6a5Schristos static const char safe_delim[] = "@!:%-.,"; /* conservative! */
6640061c6a5Schristos const char *t;
6650061c6a5Schristos int ch, first;
6660061c6a5Schristos
6670061c6a5Schristos for (t = s, first = 1; (ch = *t++) != '\0'; first = 0) {
6680061c6a5Schristos if (isascii(ch) && isprint(ch) &&
6690061c6a5Schristos (isalnum(ch) || (!first && strchr(safe_delim, ch))))
6700061c6a5Schristos continue;
6710061c6a5Schristos log_it(usernm, getpid(), "UNSAFE", s);
6720061c6a5Schristos return (FALSE);
6730061c6a5Schristos }
6740061c6a5Schristos return (TRUE);
6750061c6a5Schristos }
676