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