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