1 /* $OpenBSD: do_command.c,v 1.32 2007/05/25 17:50:41 millert Exp $ */ 2 3 /* Copyright 1988,1990,1993,1994 by Paul Vixie 4 * All rights reserved 5 */ 6 7 /* 8 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") 9 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. 10 * 11 * Permission to use, copy, modify, and distribute this software for any 12 * purpose with or without fee is hereby granted, provided that the above 13 * copyright notice and this permission notice appear in all copies. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES 16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR 18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 21 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 22 */ 23 24 #if !defined(lint) && !defined(LINT) 25 static char const rcsid[] = "$OpenBSD: do_command.c,v 1.32 2007/05/25 17:50:41 millert Exp $"; 26 #endif 27 28 #include "cron.h" 29 30 static void child_process(entry *, user *); 31 32 void 33 do_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 64 static void 65 child_process(entry *e, user *u) { 66 FILE *in; 67 int stdin_pipe[2], stdout_pipe[2]; 68 char *input_data, *usernm; 69 int children = 0; 70 71 Debug(DPROC, ("[%ld] child_process('%s')\n", (long)getpid(), e->cmd)) 72 73 /* mark ourselves as different to PS command watchers */ 74 setproctitle("running job"); 75 76 /* discover some useful and important environment settings 77 */ 78 usernm = e->pwd->pw_name; 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 privileges. 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 %lu\n", 247 (unsigned long)e->pwd->pw_uid); 248 _exit(ERROR_EXIT); 249 } 250 251 #endif /* LOGIN_CAP */ 252 chdir(env_get("HOME", e->envp)); 253 254 /* 255 * Exec the command. 256 */ 257 { 258 char *shell = env_get("SHELL", e->envp); 259 260 # if DEBUGGING 261 if (DebugFlags & DTEST) { 262 fprintf(stderr, 263 "debug DTEST is on, not exec'ing command.\n"); 264 fprintf(stderr, 265 "\tcmd='%s' shell='%s'\n", e->cmd, shell); 266 _exit(OK_EXIT); 267 } 268 # endif /*DEBUGGING*/ 269 execle(shell, shell, "-c", e->cmd, (char *)NULL, e->envp); 270 fprintf(stderr, "execle: couldn't exec `%s'\n", shell); 271 perror("execle"); 272 _exit(ERROR_EXIT); 273 } 274 break; 275 default: 276 /* parent process */ 277 break; 278 } 279 280 children++; 281 282 /* middle process, child of original cron, parent of process running 283 * the user's command. 284 */ 285 286 Debug(DPROC, ("[%ld] child continues, closing pipes\n",(long)getpid())) 287 288 /* close the ends of the pipe that will only be referenced in the 289 * grandchild process... 290 */ 291 close(stdin_pipe[READ_PIPE]); 292 close(stdout_pipe[WRITE_PIPE]); 293 294 /* 295 * write, to the pipe connected to child's stdin, any input specified 296 * after a % in the crontab entry. while we copy, convert any 297 * additional %'s to newlines. when done, if some characters were 298 * written and the last one wasn't a newline, write a newline. 299 * 300 * Note that if the input data won't fit into one pipe buffer (2K 301 * or 4K on most BSD systems), and the child doesn't read its stdin, 302 * we would block here. thus we must fork again. 303 */ 304 305 if (*input_data && fork() == 0) { 306 FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); 307 int need_newline = FALSE; 308 int escaped = FALSE; 309 int ch; 310 311 Debug(DPROC, ("[%ld] child2 sending data to grandchild\n", 312 (long)getpid())) 313 314 /* close the pipe we don't use, since we inherited it and 315 * are part of its reference count now. 316 */ 317 close(stdout_pipe[READ_PIPE]); 318 319 /* translation: 320 * \% -> % 321 * % -> \n 322 * \x -> \x for all x != % 323 */ 324 while ((ch = *input_data++) != '\0') { 325 if (escaped) { 326 if (ch != '%') 327 putc('\\', out); 328 } else { 329 if (ch == '%') 330 ch = '\n'; 331 } 332 333 if (!(escaped = (ch == '\\'))) { 334 putc(ch, out); 335 need_newline = (ch != '\n'); 336 } 337 } 338 if (escaped) 339 putc('\\', out); 340 if (need_newline) 341 putc('\n', out); 342 343 /* close the pipe, causing an EOF condition. fclose causes 344 * stdin_pipe[WRITE_PIPE] to be closed, too. 345 */ 346 fclose(out); 347 348 Debug(DPROC, ("[%ld] child2 done sending to grandchild\n", 349 (long)getpid())) 350 exit(0); 351 } 352 353 /* close the pipe to the grandkiddie's stdin, since its wicked uncle 354 * ernie back there has it open and will close it when he's done. 355 */ 356 close(stdin_pipe[WRITE_PIPE]); 357 358 children++; 359 360 /* 361 * read output from the grandchild. it's stderr has been redirected to 362 * it's stdout, which has been redirected to our pipe. if there is any 363 * output, we'll be mailing it to the user whose crontab this is... 364 * when the grandchild exits, we'll get EOF. 365 */ 366 367 Debug(DPROC, ("[%ld] child reading output from grandchild\n", 368 (long)getpid())) 369 370 (void) signal(SIGPIPE, SIG_IGN); 371 in = fdopen(stdout_pipe[READ_PIPE], "r"); 372 if (in != NULL) { 373 int ch = getc(in); 374 375 if (ch != EOF) { 376 FILE *mail = NULL; 377 char *mailto; 378 int bytes = 1; 379 int status = 0; 380 381 Debug(DPROC|DEXT, 382 ("[%ld] got data (%x:%c) from grandchild\n", 383 (long)getpid(), ch, ch)) 384 385 /* get name of recipient. this is MAILTO if set to a 386 * valid local username; USER otherwise. 387 */ 388 mailto = env_get("MAILTO", e->envp); 389 if (!mailto) { 390 /* MAILTO not present, set to USER. 391 */ 392 mailto = usernm; 393 } else if (!*mailto || !safe_p(usernm, mailto)) { 394 mailto = NULL; 395 } 396 397 /* if we are supposed to be mailing, MAILTO will 398 * be non-NULL. only in this case should we set 399 * up the mail command and subjects and stuff... 400 */ 401 402 if (mailto) { 403 char **env; 404 char mailcmd[MAX_COMMAND]; 405 char hostname[MAXHOSTNAMELEN]; 406 407 gethostname(hostname, sizeof(hostname)); 408 if (snprintf(mailcmd, sizeof mailcmd, MAILFMT, 409 MAILARG) >= sizeof mailcmd) { 410 fprintf(stderr, "mailcmd too long\n"); 411 (void) _exit(ERROR_EXIT); 412 } 413 if (!(mail = cron_popen(mailcmd, "w", e->pwd))) { 414 perror(mailcmd); 415 (void) _exit(ERROR_EXIT); 416 } 417 fprintf(mail, "From: root (Cron Daemon)\n"); 418 fprintf(mail, "To: %s\n", mailto); 419 fprintf(mail, "Subject: Cron <%s@%s> %s\n", 420 usernm, first_word(hostname, "."), 421 e->cmd); 422 fprintf(mail, "Auto-Submitted: auto-generated\n"); 423 #ifdef MAIL_DATE 424 fprintf(mail, "Date: %s\n", 425 arpadate(&StartTime)); 426 #endif /*MAIL_DATE*/ 427 for (env = e->envp; *env; env++) 428 fprintf(mail, "X-Cron-Env: <%s>\n", 429 *env); 430 fprintf(mail, "\n"); 431 432 /* this was the first char from the pipe 433 */ 434 fputc(ch, mail); 435 } 436 437 /* we have to read the input pipe no matter whether 438 * we mail or not, but obviously we only write to 439 * mail pipe if we ARE mailing. 440 */ 441 442 while (EOF != (ch = getc(in))) { 443 bytes++; 444 if (mail) 445 fputc(ch, mail); 446 } 447 448 /* only close pipe if we opened it -- i.e., we're 449 * mailing... 450 */ 451 452 if (mail) { 453 Debug(DPROC, ("[%ld] closing pipe to mail\n", 454 (long)getpid())) 455 /* Note: the pclose will probably see 456 * the termination of the grandchild 457 * in addition to the mail process, since 458 * it (the grandchild) is likely to exit 459 * after closing its stdout. 460 */ 461 status = cron_pclose(mail); 462 } 463 464 /* if there was output and we could not mail it, 465 * log the facts so the poor user can figure out 466 * what's going on. 467 */ 468 if (mail && status) { 469 char buf[MAX_TEMPSTR]; 470 471 snprintf(buf, sizeof buf, 472 "mailed %d byte%s of output but got status 0x%04x\n", 473 bytes, (bytes==1)?"":"s", 474 status); 475 log_it(usernm, getpid(), "MAIL", buf); 476 } 477 478 } /*if data from grandchild*/ 479 480 Debug(DPROC, ("[%ld] got EOF from grandchild\n", 481 (long)getpid())) 482 483 fclose(in); /* also closes stdout_pipe[READ_PIPE] */ 484 } 485 486 /* wait for children to die. 487 */ 488 for (; children > 0; children--) { 489 WAIT_T waiter; 490 PID_T pid; 491 492 Debug(DPROC, ("[%ld] waiting for grandchild #%d to finish\n", 493 (long)getpid(), children)) 494 while ((pid = wait(&waiter)) < OK && errno == EINTR) 495 ; 496 if (pid < OK) { 497 Debug(DPROC, 498 ("[%ld] no more grandchildren--mail written?\n", 499 (long)getpid())) 500 break; 501 } 502 Debug(DPROC, ("[%ld] grandchild #%ld finished, status=%04x", 503 (long)getpid(), (long)pid, WEXITSTATUS(waiter))) 504 if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) 505 Debug(DPROC, (", dumped core")) 506 Debug(DPROC, ("\n")) 507 } 508 } 509 510 int 511 safe_p(const char *usernm, const char *s) { 512 static const char safe_delim[] = "@!:%-.,"; /* conservative! */ 513 const char *t; 514 int ch, first; 515 516 for (t = s, first = 1; (ch = *t++) != '\0'; first = 0) { 517 if (isascii(ch) && isprint(ch) && 518 (isalnum(ch) || ch == '_' || 519 (!first && strchr(safe_delim, ch)))) 520 continue; 521 log_it(usernm, getpid(), "UNSAFE", s); 522 return (FALSE); 523 } 524 return (TRUE); 525 } 526