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