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