1 /* Copyright 1988,1990,1993,1994 by Paul Vixie 2 * All rights reserved 3 * 4 * Distribute freely, except: don't remove my name from the source or 5 * documentation (don't take credit for my work), mark your changes (don't 6 * get me blamed for your possible bugs), don't alter or remove this 7 * notice. May be sold if buildable source is provided to buyer. No 8 * warrantee of any kind, express or implied, is included with this 9 * software; use at your own risk, responsibility for damages (if any) to 10 * anyone resulting from the use of this software rests entirely with the 11 * user. 12 * 13 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and 14 * I'll try to keep a version up to date. I can be reached as follows: 15 * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul 16 */ 17 18 #if !defined(lint) && !defined(LINT) 19 static char rcsid[] = "$Id: do_command.c,v 1.4 1995/04/14 21:54:18 ache Exp $"; 20 #endif 21 22 23 #include "cron.h" 24 #include <sys/signal.h> 25 #if defined(sequent) 26 # include <sys/universe.h> 27 #endif 28 #if defined(SYSLOG) 29 # include <syslog.h> 30 #endif 31 32 33 static void child_process __P((entry *, user *)), 34 do_univ __P((user *)); 35 36 37 void 38 do_command(e, u) 39 entry *e; 40 user *u; 41 { 42 Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", 43 getpid(), e->cmd, u->name, e->uid, e->gid)) 44 45 /* fork to become asynchronous -- parent process is done immediately, 46 * and continues to run the normal cron code, which means return to 47 * tick(). the child and grandchild don't leave this function, alive. 48 * 49 * vfork() is unsuitable, since we have much to do, and the parent 50 * needs to be able to run off and fork other processes. 51 */ 52 switch (fork()) { 53 case -1: 54 log_it("CRON",getpid(),"error","can't fork"); 55 break; 56 case 0: 57 /* child process */ 58 acquire_daemonlock(1); 59 child_process(e, u); 60 Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) 61 _exit(OK_EXIT); 62 break; 63 default: 64 /* parent process */ 65 break; 66 } 67 Debug(DPROC, ("[%d] main process returning to work\n", getpid())) 68 } 69 70 71 static void 72 child_process(e, u) 73 entry *e; 74 user *u; 75 { 76 int stdin_pipe[2], stdout_pipe[2]; 77 register char *input_data; 78 char *usernm, *mailto; 79 int children = 0; 80 81 Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd)) 82 83 /* mark ourselves as different to PS command watchers by upshifting 84 * our program name. This has no effect on some kernels. 85 */ 86 /*local*/{ 87 register char *pch; 88 89 for (pch = ProgramName; *pch; pch++) 90 *pch = MkUpper(*pch); 91 } 92 93 /* discover some useful and important environment settings 94 */ 95 usernm = env_get("LOGNAME", e->envp); 96 mailto = env_get("MAILTO", e->envp); 97 98 #ifdef USE_SIGCHLD 99 /* our parent is watching for our death by catching SIGCHLD. we 100 * do not care to watch for our children's deaths this way -- we 101 * use wait() explictly. so we have to disable the signal (which 102 * was inherited from the parent). 103 */ 104 (void) signal(SIGCHLD, SIG_IGN); 105 #else 106 /* on system-V systems, we are ignoring SIGCLD. we have to stop 107 * ignoring it now or the wait() in cron_pclose() won't work. 108 * because of this, we have to wait() for our children here, as well. 109 */ 110 (void) signal(SIGCLD, SIG_DFL); 111 #endif /*BSD*/ 112 113 /* create some pipes to talk to our future child 114 */ 115 pipe(stdin_pipe); /* child's stdin */ 116 pipe(stdout_pipe); /* child's stdout */ 117 118 /* since we are a forked process, we can diddle the command string 119 * we were passed -- nobody else is going to use it again, right? 120 * 121 * if a % is present in the command, previous characters are the 122 * command, and subsequent characters are the additional input to 123 * the command. Subsequent %'s will be transformed into newlines, 124 * but that happens later. 125 */ 126 /*local*/{ 127 register int escaped = FALSE; 128 register int ch; 129 130 for (input_data = e->cmd; ch = *input_data; input_data++) { 131 if (escaped) { 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 } 145 146 /* fork again, this time so we can exec the user's command. 147 */ 148 switch (vfork()) { 149 case -1: 150 log_it("CRON",getpid(),"error","can't vfork"); 151 exit(ERROR_EXIT); 152 /*NOTREACHED*/ 153 case 0: 154 Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n", 155 getpid())) 156 157 /* write a log message. we've waited this long to do it 158 * because it was not until now that we knew the PID that 159 * the actual user command shell was going to get and the 160 * PID is part of the log message. 161 */ 162 /*local*/{ 163 char *x = mkprints((u_char *)e->cmd, strlen(e->cmd)); 164 165 log_it(usernm, getpid(), "CMD", x); 166 free(x); 167 } 168 169 /* that's the last thing we'll log. close the log files. 170 */ 171 #ifdef SYSLOG 172 closelog(); 173 #endif 174 175 /* get new pgrp, void tty, etc. 176 */ 177 (void) setsid(); 178 179 /* close the pipe ends that we won't use. this doesn't affect 180 * the parent, who has to read and write them; it keeps the 181 * kernel from recording us as a potential client TWICE -- 182 * which would keep it from sending SIGPIPE in otherwise 183 * appropriate circumstances. 184 */ 185 close(stdin_pipe[WRITE_PIPE]); 186 close(stdout_pipe[READ_PIPE]); 187 188 /* grandchild process. make std{in,out} be the ends of 189 * pipes opened by our daddy; make stderr go to stdout. 190 */ 191 close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN); 192 close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT); 193 close(STDERR); dup2(STDOUT, STDERR); 194 195 /* close the pipes we just dup'ed. The resources will remain. 196 */ 197 close(stdin_pipe[READ_PIPE]); 198 close(stdout_pipe[WRITE_PIPE]); 199 200 /* set our login universe. Do this in the grandchild 201 * so that the child can invoke /usr/lib/sendmail 202 * without surprises. 203 */ 204 do_univ(u); 205 206 /* set our directory, uid and gid. Set gid first, since once 207 * we set uid, we've lost root privledges. 208 */ 209 chdir(env_get("HOME", e->envp)); 210 # if defined(BSD) 211 initgroups(env_get("LOGNAME", e->envp), e->gid); 212 # endif 213 setgid(e->gid); 214 setuid(e->uid); /* we aren't root after this... */ 215 216 /* exec the command. 217 */ 218 { 219 char *shell = env_get("SHELL", e->envp); 220 221 # if DEBUGGING 222 if (DebugFlags & DTEST) { 223 fprintf(stderr, 224 "debug DTEST is on, not exec'ing command.\n"); 225 fprintf(stderr, 226 "\tcmd='%s' shell='%s'\n", e->cmd, shell); 227 _exit(OK_EXIT); 228 } 229 # endif /*DEBUGGING*/ 230 execle(shell, shell, "-c", e->cmd, (char *)0, e->envp); 231 fprintf(stderr, "execl: couldn't exec `%s'\n", shell); 232 perror("execl"); 233 _exit(ERROR_EXIT); 234 } 235 break; 236 default: 237 /* parent process */ 238 break; 239 } 240 241 children++; 242 243 /* middle process, child of original cron, parent of process running 244 * the user's command. 245 */ 246 247 Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid())) 248 249 /* close the ends of the pipe that will only be referenced in the 250 * grandchild process... 251 */ 252 close(stdin_pipe[READ_PIPE]); 253 close(stdout_pipe[WRITE_PIPE]); 254 255 /* 256 * write, to the pipe connected to child's stdin, any input specified 257 * after a % in the crontab entry. while we copy, convert any 258 * additional %'s to newlines. when done, if some characters were 259 * written and the last one wasn't a newline, write a newline. 260 * 261 * Note that if the input data won't fit into one pipe buffer (2K 262 * or 4K on most BSD systems), and the child doesn't read its stdin, 263 * we would block here. thus we must fork again. 264 */ 265 266 if (*input_data && fork() == 0) { 267 register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); 268 register int need_newline = FALSE; 269 register int escaped = FALSE; 270 register int ch; 271 272 Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) 273 274 /* close the pipe we don't use, since we inherited it and 275 * are part of its reference count now. 276 */ 277 close(stdout_pipe[READ_PIPE]); 278 279 /* translation: 280 * \% -> % 281 * % -> \n 282 * \x -> \x for all x != % 283 */ 284 while (ch = *input_data++) { 285 if (escaped) { 286 if (ch != '%') 287 putc('\\', out); 288 } else { 289 if (ch == '%') 290 ch = '\n'; 291 } 292 293 if (!(escaped = (ch == '\\'))) { 294 putc(ch, out); 295 need_newline = (ch != '\n'); 296 } 297 } 298 if (escaped) 299 putc('\\', out); 300 if (need_newline) 301 putc('\n', out); 302 303 /* close the pipe, causing an EOF condition. fclose causes 304 * stdin_pipe[WRITE_PIPE] to be closed, too. 305 */ 306 fclose(out); 307 308 Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid())) 309 exit(0); 310 } 311 312 /* close the pipe to the grandkiddie's stdin, since its wicked uncle 313 * ernie back there has it open and will close it when he's done. 314 */ 315 close(stdin_pipe[WRITE_PIPE]); 316 317 children++; 318 319 /* 320 * read output from the grandchild. it's stderr has been redirected to 321 * it's stdout, which has been redirected to our pipe. if there is any 322 * output, we'll be mailing it to the user whose crontab this is... 323 * when the grandchild exits, we'll get EOF. 324 */ 325 326 Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) 327 328 /*local*/{ 329 register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); 330 register int ch = getc(in); 331 332 if (ch != EOF) { 333 register FILE *mail; 334 register int bytes = 1; 335 int status = 0; 336 337 Debug(DPROC|DEXT, 338 ("[%d] got data (%x:%c) from grandchild\n", 339 getpid(), ch, ch)) 340 341 /* get name of recipient. this is MAILTO if set to a 342 * valid local username; USER otherwise. 343 */ 344 if (mailto) { 345 /* MAILTO was present in the environment 346 */ 347 if (!*mailto) { 348 /* ... but it's empty. set to NULL 349 */ 350 mailto = NULL; 351 } 352 } else { 353 /* MAILTO not present, set to USER. 354 */ 355 mailto = usernm; 356 } 357 358 /* if we are supposed to be mailing, MAILTO will 359 * be non-NULL. only in this case should we set 360 * up the mail command and subjects and stuff... 361 */ 362 363 if (mailto) { 364 register char **env; 365 auto char mailcmd[MAX_COMMAND]; 366 auto char hostname[MAXHOSTNAMELEN]; 367 368 (void) gethostname(hostname, MAXHOSTNAMELEN); 369 (void) sprintf(mailcmd, MAILARGS, 370 MAILCMD); 371 if (!(mail = cron_popen(mailcmd, "w"))) { 372 perror(MAILCMD); 373 (void) _exit(ERROR_EXIT); 374 } 375 fprintf(mail, "From: root (Cron Daemon)\n"); 376 fprintf(mail, "To: %s\n", mailto); 377 fprintf(mail, "Subject: Cron <%s@%s> %s\n", 378 usernm, first_word(hostname, "."), 379 e->cmd); 380 # if defined(MAIL_DATE) 381 fprintf(mail, "Date: %s\n", 382 arpadate(&TargetTime)); 383 # endif /* MAIL_DATE */ 384 for (env = e->envp; *env; env++) 385 fprintf(mail, "X-Cron-Env: <%s>\n", 386 *env); 387 fprintf(mail, "\n"); 388 389 /* this was the first char from the pipe 390 */ 391 putc(ch, mail); 392 } 393 394 /* we have to read the input pipe no matter whether 395 * we mail or not, but obviously we only write to 396 * mail pipe if we ARE mailing. 397 */ 398 399 while (EOF != (ch = getc(in))) { 400 bytes++; 401 if (mailto) 402 putc(ch, mail); 403 } 404 405 /* only close pipe if we opened it -- i.e., we're 406 * mailing... 407 */ 408 409 if (mailto) { 410 Debug(DPROC, ("[%d] closing pipe to mail\n", 411 getpid())) 412 /* Note: the pclose will probably see 413 * the termination of the grandchild 414 * in addition to the mail process, since 415 * it (the grandchild) is likely to exit 416 * after closing its stdout. 417 */ 418 status = cron_pclose(mail); 419 } 420 421 /* if there was output and we could not mail it, 422 * log the facts so the poor user can figure out 423 * what's going on. 424 */ 425 if (mailto && status) { 426 char buf[MAX_TEMPSTR]; 427 428 sprintf(buf, 429 "mailed %d byte%s of output but got status 0x%04x\n", 430 bytes, (bytes==1)?"":"s", 431 status); 432 log_it(usernm, getpid(), "MAIL", buf); 433 } 434 435 } /*if data from grandchild*/ 436 437 Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid())) 438 439 fclose(in); /* also closes stdout_pipe[READ_PIPE] */ 440 } 441 442 /* wait for children to die. 443 */ 444 for (; children > 0; children--) 445 { 446 WAIT_T waiter; 447 PID_T pid; 448 449 Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n", 450 getpid(), children)) 451 pid = wait(&waiter); 452 if (pid < OK) { 453 Debug(DPROC, ("[%d] no more grandchildren--mail written?\n", 454 getpid())) 455 break; 456 } 457 Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x", 458 getpid(), pid, WEXITSTATUS(waiter))) 459 if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) 460 Debug(DPROC, (", dumped core")) 461 Debug(DPROC, ("\n")) 462 } 463 } 464 465 466 static void 467 do_univ(u) 468 user *u; 469 { 470 #if defined(sequent) 471 /* Dynix (Sequent) hack to put the user associated with 472 * the passed user structure into the ATT universe if 473 * necessary. We have to dig the gecos info out of 474 * the user's password entry to see if the magic 475 * "universe(att)" string is present. 476 */ 477 478 struct passwd *p; 479 char *s; 480 int i; 481 482 p = getpwuid(u->uid); 483 (void) endpwent(); 484 485 if (p == NULL) 486 return; 487 488 s = p->pw_gecos; 489 490 for (i = 0; i < 4; i++) 491 { 492 if ((s = strchr(s, ',')) == NULL) 493 return; 494 s++; 495 } 496 if (strcmp(s, "universe(att)")) 497 return; 498 499 (void) universe(U_ATT); 500 #endif 501 } 502