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