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