File: | src/usr.sbin/cron/do_command.c |
Warning: | line 470, column 15 The left operand of '>' is a garbage value |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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(*__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 | |||
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_ERR3, "(CRON) CAN'T FORK (%m)"); | |||
65 | break; | |||
66 | case 0: | |||
67 | /* child process */ | |||
68 | child_process(e, u); | |||
69 | _exit(EXIT_SUCCESS0); | |||
70 | break; | |||
71 | default: | |||
72 | /* parent process */ | |||
73 | if ((e->flags & SINGLE_JOB0x80) == 0) | |||
74 | pid = -1; | |||
75 | break; | |||
76 | } | |||
77 | ||||
78 | /* only return pid if a singleton */ | |||
79 | return (pid); | |||
80 | } | |||
81 | ||||
82 | static void | |||
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(SIGCHLD20, SIG_DFL(void (*)(int))0); | |||
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_ERR3, "(CRON) PIPE (%m)"); | |||
113 | _exit(EXIT_FAILURE1); | |||
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 = FALSE0; | |||
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 = FALSE0; | |||
139 | continue; | |||
140 | } | |||
141 | if (ch == '\\') { | |||
142 | escaped = TRUE1; | |||
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_ERR3, "(CRON) CAN'T FORK (%m)"); | |||
160 | _exit(EXIT_FAILURE1); | |||
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_LOG0x20) == 0) { | |||
169 | char *x; | |||
170 | if (stravis(&x, e->cmd, 0) != -1) { | |||
171 | syslog(LOG_INFO6, "(%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_PIPE1]); | |||
187 | close(stdout_pipe[READ_PIPE0]); | |||
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_PIPE0] != STDIN_FILENO0) { | |||
193 | dup2(stdin_pipe[READ_PIPE0], STDIN_FILENO0); | |||
194 | close(stdin_pipe[READ_PIPE0]); | |||
195 | } | |||
196 | if (stdout_pipe[WRITE_PIPE1] != STDOUT_FILENO1) { | |||
197 | dup2(stdout_pipe[WRITE_PIPE1], STDOUT_FILENO1); | |||
198 | close(stdout_pipe[WRITE_PIPE1]); | |||
199 | } | |||
200 | dup2(STDOUT_FILENO1, STDERR_FILENO2); | |||
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((void *)0)) { | |||
209 | warnx("unable to get login class for %s", | |||
210 | e->pwd->pw_name); | |||
211 | syslog(LOG_ERR3, "(CRON) CAN'T GET LOGIN CLASS (%s)", | |||
212 | e->pwd->pw_name); | |||
213 | _exit(EXIT_FAILURE1); | |||
214 | } | |||
215 | if (setusercontext(lc, e->pwd, e->pwd->pw_uid, LOGIN_SETALL0x01ff) == -1) { | |||
216 | warn("setusercontext failed for %s", e->pwd->pw_name); | |||
217 | syslog(LOG_ERR3, "(%s) SETUSERCONTEXT FAILED (%m)", | |||
218 | e->pwd->pw_name); | |||
219 | _exit(EXIT_FAILURE1); | |||
220 | } | |||
221 | as = auth_open(); | |||
222 | if (as == NULL((void *)0) || auth_setpwd(as, e->pwd) != 0) { | |||
223 | warn("auth_setpwd"); | |||
224 | syslog(LOG_ERR3, "(%s) AUTH_SETPWD FAILED (%m)", | |||
225 | e->pwd->pw_name); | |||
226 | _exit(EXIT_FAILURE1); | |||
227 | } | |||
228 | if (auth_approval(as, lc, usernm, "cron") <= 0) { | |||
229 | warnx("approval failed for %s", e->pwd->pw_name); | |||
230 | syslog(LOG_ERR3, "(%s) APPROVAL FAILED (cron)", | |||
231 | e->pwd->pw_name); | |||
232 | _exit(EXIT_FAILURE1); | |||
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((void *)0) && environ != NULL((void *)0)) { | |||
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(SIGPIPE13, SIG_DFL(void (*)(int))0); | |||
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((void *)0), e->envp); | |||
260 | warn("unable to execute %s", shell); | |||
261 | syslog(LOG_ERR3, "(%s) CAN'T EXEC (%s: %m)", | |||
262 | e->pwd->pw_name, shell); | |||
263 | _exit(EXIT_FAILURE1); | |||
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_PIPE0]); | |||
279 | close(stdout_pipe[WRITE_PIPE1]); | |||
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_PIPE1], "w"); | |||
295 | int need_newline = FALSE0; | |||
296 | int escaped = FALSE0; | |||
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_PIPE0]); | |||
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)(!__isthreaded ? __sputc('\\', out) : (putc)('\\', out)); | |||
313 | } else { | |||
314 | if (ch == '%') | |||
315 | ch = '\n'; | |||
316 | } | |||
317 | ||||
318 | if (!(escaped = (ch == '\\'))) { | |||
319 | putc(ch, out)(!__isthreaded ? __sputc(ch, out) : (putc)(ch, out)); | |||
320 | need_newline = (ch != '\n'); | |||
321 | } | |||
322 | } | |||
323 | if (escaped) | |||
324 | putc('\\', out)(!__isthreaded ? __sputc('\\', out) : (putc)('\\', out)); | |||
325 | if (need_newline) | |||
326 | putc('\n', out)(!__isthreaded ? __sputc('\n', out) : (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_SUCCESS0); | |||
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_PIPE1]); | |||
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(SIGPIPE13, SIG_IGN(void (*)(int))1); | |||
349 | in = fdopen(stdout_pipe[READ_PIPE0], "r"); | |||
350 | ||||
351 | char *mailto; | |||
352 | FILE *mail = NULL((void *)0); | |||
353 | int status = 0; | |||
354 | pid_t mailpid; | |||
355 | size_t bytes = 1; | |||
356 | ||||
357 | if (in != NULL((void *)0)) { | |||
358 | int ch = getc(in)(!__isthreaded ? (--(in)->_r < 0 ? __srget(in) : (int)( *(in)->_p++)) : (getc)(in)); | |||
359 | ||||
360 | if (ch != EOF(-1)) { | |||
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((void *)0); | |||
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_COMMAND1000]; | |||
382 | char hostname[HOST_NAME_MAX255 + 1]; | |||
383 | ||||
384 | gethostname(hostname, sizeof(hostname)); | |||
385 | if (snprintf(mailcmd, sizeof mailcmd, MAILFMT"%s -FCronDaemon -odi -oem -oi -t", | |||
386 | MAILARG"/usr/sbin/sendmail") >= sizeof mailcmd) { | |||
387 | syslog(LOG_ERR3, | |||
388 | "(%s) ERROR (mailcmd too long)", | |||
389 | e->pwd->pw_name); | |||
390 | (void) _exit(EXIT_FAILURE1); | |||
391 | } | |||
392 | if (!(mail = cron_popen(mailcmd, "w", e->pwd, | |||
393 | &mailpid))) { | |||
394 | syslog(LOG_ERR3, "(%s) POPEN (%s)", | |||
395 | e->pwd->pw_name, mailcmd); | |||
396 | (void) _exit(EXIT_FAILURE1); | |||
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)(!__isthreaded ? (--(in)->_r < 0 ? __srget(in) : (int)( *(in)->_p++)) : (getc)(in))) != EOF(-1)) { | |||
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(*__errno()) == EINTR4) | |||
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)(((waiter) & 0177) == 0) && WEXITSTATUS(waiter)(int)(((unsigned)(waiter) >> 8) & 0xff) == 0 | |||
441 | && (e->flags & MAIL_WHEN_ERR0x40) == MAIL_WHEN_ERR0x40 | |||
442 | && mail) { | |||
443 | kill(mailpid, SIGKILL9); | |||
444 | (void)fclose(mail); | |||
445 | mail = NULL((void *)0); | |||
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_NOTICE5, "(%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(*__errno()) == EINTR4) | |||
472 | ; | |||
473 | } | |||
474 | ||||
475 | int | |||
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_WARNING4, "(%s) UNSAFE (%s)", usernm, s); | |||
488 | return (FALSE0); | |||
489 | } | |||
490 | return (TRUE1); | |||
491 | } |