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