do_command.c revision 1.27
1/* $OpenBSD: do_command.c,v 1.27 2004/06/03 19:54:04 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.27 2004/06/03 19:54:04 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 if (setgid(e->pwd->pw_gid) || initgroups(usernm, e->pwd->pw_gid) { 237 fprintf(stderr, 238 "unable to set groups for %s\n", e->pwd->pw_name); 239 _exit(ERROR_EXIT); 240 } 241#if (defined(BSD)) && (BSD >= 199103) 242 setlogin(usernm); 243#endif /* BSD */ 244 if (setuid(e->pwd->pw_uid)) { 245 fprintf(stderr, 246 "unable to set uid to %ld\n", (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 505int 506safe_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) || 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