1 /* $OpenBSD: do_command.c,v 1.63 2022/05/21 01:21:29 deraadt Exp $ */
2
3 /* Copyright 1988,1990,1993,1994 by Paul Vixie
4 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
5 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
6 * Copyright (c) 2018 Job Snijders <job@openbsd.org>
7 *
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
18 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 */
20
21 #include <sys/types.h>
22 #include <sys/wait.h>
23
24 #include <bitstring.h> /* for structs.h */
25 #include <bsd_auth.h>
26 #include <ctype.h>
27 #include <err.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <limits.h>
31 #include <login_cap.h>
32 #include <pwd.h>
33 #include <signal.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <syslog.h>
38 #include <time.h> /* for structs.h */
39 #include <unistd.h>
40 #include <vis.h>
41
42 #include "config.h"
43 #include "pathnames.h"
44 #include "macros.h"
45 #include "structs.h"
46 #include "funcs.h"
47
48 static void child_process(entry *, user *);
49
50 pid_t
do_command(entry * e,user * u)51 do_command(entry *e, user *u)
52 {
53 pid_t pid;
54
55 /* fork to become asynchronous -- parent process is done immediately,
56 * and continues to run the normal cron code, which means return to
57 * tick(). the child and grandchild don't leave this function, alive.
58 *
59 * vfork() is unsuitable, since we have much to do, and the parent
60 * needs to be able to run off and fork other processes.
61 */
62 switch ((pid = fork())) {
63 case -1:
64 syslog(LOG_ERR, "(CRON) CAN'T FORK (%m)");
65 break;
66 case 0:
67 /* child process */
68 child_process(e, u);
69 _exit(EXIT_SUCCESS);
70 break;
71 default:
72 /* parent process */
73 if ((e->flags & SINGLE_JOB) == 0)
74 pid = -1;
75 break;
76 }
77
78 /* only return pid if a singleton */
79 return (pid);
80 }
81
82 static void
child_process(entry * e,user * u)83 child_process(entry *e, user *u)
84 {
85 FILE *in;
86 int stdin_pipe[2], stdout_pipe[2];
87 char **p, *input_data, *usernm;
88 auth_session_t *as;
89 login_cap_t *lc;
90 extern char **environ;
91
92 /* mark ourselves as different to PS command watchers */
93 setproctitle("running job");
94
95 /* close sockets from parent (i.e. cronSock) */
96 closefrom(3);
97
98 /* discover some useful and important environment settings
99 */
100 usernm = e->pwd->pw_name;
101
102 /* our parent is watching for our death by catching SIGCHLD. we
103 * do not care to watch for our children's deaths this way -- we
104 * use wait() explicitly. so we have to reset the signal (which
105 * was inherited from the parent).
106 */
107 (void) signal(SIGCHLD, SIG_DFL);
108
109 /* create some pipes to talk to our future child
110 */
111 if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) {
112 syslog(LOG_ERR, "(CRON) PIPE (%m)");
113 _exit(EXIT_FAILURE);
114 }
115
116 /* since we are a forked process, we can diddle the command string
117 * we were passed -- nobody else is going to use it again, right?
118 *
119 * if a % is present in the command, previous characters are the
120 * command, and subsequent characters are the additional input to
121 * the command. An escaped % will have the escape character stripped
122 * from it. Subsequent %'s will be transformed into newlines,
123 * but that happens later.
124 */
125 /*local*/{
126 int escaped = FALSE;
127 int ch;
128 char *p;
129
130 for (input_data = p = e->cmd;
131 (ch = *input_data) != '\0';
132 input_data++, p++) {
133 if (p != input_data)
134 *p = ch;
135 if (escaped) {
136 if (ch == '%')
137 *--p = ch;
138 escaped = FALSE;
139 continue;
140 }
141 if (ch == '\\') {
142 escaped = TRUE;
143 continue;
144 }
145 if (ch == '%') {
146 *input_data++ = '\0';
147 break;
148 }
149 }
150 *p = '\0';
151 }
152
153 /* fork again, this time so we can exec the user's command.
154 */
155
156 pid_t jobpid;
157 switch (jobpid = fork()) {
158 case -1:
159 syslog(LOG_ERR, "(CRON) CAN'T FORK (%m)");
160 _exit(EXIT_FAILURE);
161 /*NOTREACHED*/
162 case 0:
163 /* write a log message. we've waited this long to do it
164 * because it was not until now that we knew the PID that
165 * the actual user command shell was going to get and the
166 * PID is part of the log message.
167 */
168 if ((e->flags & DONT_LOG) == 0) {
169 char *x;
170 if (stravis(&x, e->cmd, 0) != -1) {
171 syslog(LOG_INFO, "(%s) CMD (%s)", usernm, x);
172 free(x);
173 }
174 }
175
176 /* get new pgrp, void tty, etc.
177 */
178 (void) setsid();
179
180 /* close the pipe ends that we won't use. this doesn't affect
181 * the parent, who has to read and write them; it keeps the
182 * kernel from recording us as a potential client TWICE --
183 * which would keep it from sending SIGPIPE in otherwise
184 * appropriate circumstances.
185 */
186 close(stdin_pipe[WRITE_PIPE]);
187 close(stdout_pipe[READ_PIPE]);
188
189 /* grandchild process. make std{in,out} be the ends of
190 * pipes opened by our daddy; make stderr go to stdout.
191 */
192 if (stdin_pipe[READ_PIPE] != STDIN_FILENO) {
193 dup2(stdin_pipe[READ_PIPE], STDIN_FILENO);
194 close(stdin_pipe[READ_PIPE]);
195 }
196 if (stdout_pipe[WRITE_PIPE] != STDOUT_FILENO) {
197 dup2(stdout_pipe[WRITE_PIPE], STDOUT_FILENO);
198 close(stdout_pipe[WRITE_PIPE]);
199 }
200 dup2(STDOUT_FILENO, STDERR_FILENO);
201
202 /*
203 * From this point on, anything written to stderr will be
204 * mailed to the user as output.
205 */
206
207 /* XXX - should just pass in a login_cap_t * */
208 if ((lc = login_getclass(e->pwd->pw_class)) == NULL) {
209 warnx("unable to get login class for %s",
210 e->pwd->pw_name);
211 syslog(LOG_ERR, "(CRON) CAN'T GET LOGIN CLASS (%s)",
212 e->pwd->pw_name);
213 _exit(EXIT_FAILURE);
214 }
215 if (setusercontext(lc, e->pwd, e->pwd->pw_uid, LOGIN_SETALL) == -1) {
216 warn("setusercontext failed for %s", e->pwd->pw_name);
217 syslog(LOG_ERR, "(%s) SETUSERCONTEXT FAILED (%m)",
218 e->pwd->pw_name);
219 _exit(EXIT_FAILURE);
220 }
221 as = auth_open();
222 if (as == NULL || auth_setpwd(as, e->pwd) != 0) {
223 warn("auth_setpwd");
224 syslog(LOG_ERR, "(%s) AUTH_SETPWD FAILED (%m)",
225 e->pwd->pw_name);
226 _exit(EXIT_FAILURE);
227 }
228 if (auth_approval(as, lc, usernm, "cron") <= 0) {
229 warnx("approval failed for %s", e->pwd->pw_name);
230 syslog(LOG_ERR, "(%s) APPROVAL FAILED (cron)",
231 e->pwd->pw_name);
232 _exit(EXIT_FAILURE);
233 }
234 auth_close(as);
235 login_close(lc);
236
237 /* If no PATH specified in crontab file but
238 * we just added one via login.conf, add it to
239 * the crontab environment.
240 */
241 if (env_get("PATH", e->envp) == NULL && environ != NULL) {
242 for (p = environ; *p; p++) {
243 if (strncmp(*p, "PATH=", 5) == 0) {
244 e->envp = env_set(e->envp, *p);
245 break;
246 }
247 }
248 }
249 chdir(env_get("HOME", e->envp));
250
251 (void) signal(SIGPIPE, SIG_DFL);
252
253 /*
254 * Exec the command.
255 */
256 {
257 char *shell = env_get("SHELL", e->envp);
258
259 execle(shell, shell, "-c", e->cmd, (char *)NULL, e->envp);
260 warn("unable to execute %s", shell);
261 syslog(LOG_ERR, "(%s) CAN'T EXEC (%s: %m)",
262 e->pwd->pw_name, shell);
263 _exit(EXIT_FAILURE);
264 }
265 break;
266 default:
267 /* parent process */
268 break;
269 }
270
271 /* middle process, child of original cron, parent of process running
272 * the user's command.
273 */
274
275 /* close the ends of the pipe that will only be referenced in the
276 * grandchild process...
277 */
278 close(stdin_pipe[READ_PIPE]);
279 close(stdout_pipe[WRITE_PIPE]);
280
281 /*
282 * write, to the pipe connected to child's stdin, any input specified
283 * after a % in the crontab entry. while we copy, convert any
284 * additional %'s to newlines. when done, if some characters were
285 * written and the last one wasn't a newline, write a newline.
286 *
287 * Note that if the input data won't fit into one pipe buffer (2K
288 * or 4K on most BSD systems), and the child doesn't read its stdin,
289 * we would block here. thus we must fork again.
290 */
291
292 pid_t stdinjob;
293 if (*input_data && (stdinjob = fork()) == 0) {
294 FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
295 int need_newline = FALSE;
296 int escaped = FALSE;
297 int ch;
298
299 /* close the pipe we don't use, since we inherited it and
300 * are part of its reference count now.
301 */
302 close(stdout_pipe[READ_PIPE]);
303
304 /* translation:
305 * \% -> %
306 * % -> \n
307 * \x -> \x for all x != %
308 */
309 while ((ch = *input_data++) != '\0') {
310 if (escaped) {
311 if (ch != '%')
312 putc('\\', out);
313 } else {
314 if (ch == '%')
315 ch = '\n';
316 }
317
318 if (!(escaped = (ch == '\\'))) {
319 putc(ch, out);
320 need_newline = (ch != '\n');
321 }
322 }
323 if (escaped)
324 putc('\\', out);
325 if (need_newline)
326 putc('\n', out);
327
328 /* close the pipe, causing an EOF condition. fclose causes
329 * stdin_pipe[WRITE_PIPE] to be closed, too.
330 */
331 fclose(out);
332
333 _exit(EXIT_SUCCESS);
334 }
335
336 /* close the pipe to the grandkiddie's stdin, since its wicked uncle
337 * ernie back there has it open and will close it when he's done.
338 */
339 close(stdin_pipe[WRITE_PIPE]);
340
341 /*
342 * read output from the grandchild. Its stderr has been redirected to
343 * its stdout, which has been redirected to our pipe. if there is any
344 * output, we'll be mailing it to the user whose crontab this is...
345 * when the grandchild exits, we'll get EOF.
346 */
347
348 (void) signal(SIGPIPE, SIG_IGN);
349 in = fdopen(stdout_pipe[READ_PIPE], "r");
350
351 char *mailto;
352 FILE *mail = NULL;
353 int status = 0;
354 pid_t mailpid;
355 size_t bytes = 1;
356
357 if (in != NULL) {
358 int ch = getc(in);
359
360 if (ch != EOF) {
361
362 /* get name of recipient. this is MAILTO if set to a
363 * valid local username; USER otherwise.
364 */
365 mailto = env_get("MAILTO", e->envp);
366 if (!mailto) {
367 /* MAILTO not present, set to USER.
368 */
369 mailto = usernm;
370 } else if (!*mailto || !safe_p(usernm, mailto)) {
371 mailto = NULL;
372 }
373
374 /* if we are supposed to be mailing, MAILTO will
375 * be non-NULL. only in this case should we set
376 * up the mail command and subjects and stuff...
377 */
378
379 if (mailto) {
380 char **env;
381 char mailcmd[MAX_COMMAND];
382 char hostname[HOST_NAME_MAX + 1];
383
384 gethostname(hostname, sizeof(hostname));
385 if (snprintf(mailcmd, sizeof mailcmd, MAILFMT,
386 MAILARG) >= sizeof mailcmd) {
387 syslog(LOG_ERR,
388 "(%s) ERROR (mailcmd too long)",
389 e->pwd->pw_name);
390 (void) _exit(EXIT_FAILURE);
391 }
392 if (!(mail = cron_popen(mailcmd, "w", e->pwd,
393 &mailpid))) {
394 syslog(LOG_ERR, "(%s) POPEN (%s)",
395 e->pwd->pw_name, mailcmd);
396 (void) _exit(EXIT_FAILURE);
397 }
398 fprintf(mail, "From: root (Cron Daemon)\n");
399 fprintf(mail, "To: %s\n", mailto);
400 fprintf(mail, "Subject: Cron <%s@%s> %s\n",
401 usernm, first_word(hostname, "."),
402 e->cmd);
403 fprintf(mail, "Auto-Submitted: auto-generated\n");
404 for (env = e->envp; *env; env++)
405 fprintf(mail, "X-Cron-Env: <%s>\n",
406 *env);
407 fprintf(mail, "\n");
408
409 /* this was the first char from the pipe
410 */
411 fputc(ch, mail);
412 }
413
414 /* we have to read the input pipe no matter whether
415 * we mail or not, but obviously we only write to
416 * mail pipe if we ARE mailing.
417 */
418
419 while ((ch = getc(in)) != EOF) {
420 bytes++;
421 if (mail)
422 fputc(ch, mail);
423 }
424
425 } /*if data from grandchild*/
426
427 fclose(in); /* also closes stdout_pipe[READ_PIPE] */
428 }
429
430 /* wait for children to die.
431 */
432 int waiter;
433 if (jobpid > 0) {
434 while (waitpid(jobpid, &waiter, 0) == -1 && errno == EINTR)
435 ;
436
437 /* If everything went well, and -n was set, _and_ we have mail,
438 * we won't be mailing... so shoot the messenger!
439 */
440 if (WIFEXITED(waiter) && WEXITSTATUS(waiter) == 0
441 && (e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR
442 && mail) {
443 kill(mailpid, SIGKILL);
444 (void)fclose(mail);
445 mail = NULL;
446 }
447
448 /* only close pipe if we opened it -- i.e., we're mailing... */
449 if (mail) {
450 /*
451 * Note: the pclose will probably see the termination
452 * of the grandchild in addition to the mail process,
453 * since it (the grandchild) is likely to exit after
454 * closing its stdout.
455 */
456 status = cron_pclose(mail, mailpid);
457 }
458
459 /* if there was output and we could not mail it,
460 * log the facts so the poor user can figure out
461 * what's going on.
462 */
463 if (mail && status) {
464 syslog(LOG_NOTICE, "(%s) MAIL (mailed %zu byte"
465 "%s of output but got status 0x%04x)", usernm,
466 bytes, (bytes == 1) ? "" : "s", status);
467 }
468 }
469
470 if (stdinjob > 0)
471 while (waitpid(stdinjob, &waiter, 0) == -1 && errno == EINTR)
472 ;
473 }
474
475 int
safe_p(const char * usernm,const char * s)476 safe_p(const char *usernm, const char *s)
477 {
478 static const char safe_delim[] = "@!:%+-.,"; /* conservative! */
479 const char *t;
480 int ch, first;
481
482 for (t = s, first = 1; (ch = (unsigned char)*t++) != '\0'; first = 0) {
483 if (isascii(ch) && isprint(ch) &&
484 (isalnum(ch) || ch == '_' ||
485 (!first && strchr(safe_delim, ch))))
486 continue;
487 syslog(LOG_WARNING, "(%s) UNSAFE (%s)", usernm, s);
488 return (FALSE);
489 }
490 return (TRUE);
491 }
492