/[pkg-cron]/tags/debian_version_3.0pl1-115/do_command.c
ViewVC logotype

Contents of /tags/debian_version_3.0pl1-115/do_command.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 621 - (show annotations) (download)
Sat Oct 16 11:33:29 2010 UTC (2 years, 8 months ago) by jfs
File MIME type: text/plain
File size: 18403 byte(s)
Debian package version 3.0pl1-115
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 2.12 1994/01/15 20:43:43 vixie Exp $";
20 #endif
21
22
23 #include "cron.h"
24 #include <signal.h>
25 #include <grp.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #if defined(sequent)
29 # include <sys/universe.h>
30 #endif
31 #if defined(SYSLOG)
32 # include <syslog.h>
33 #endif
34 #if defined(USE_PAM)
35 #include <security/pam_appl.h>
36 static pam_handle_t *pamh = NULL;
37 static const struct pam_conv conv = {
38 NULL
39 };
40 #define PAM_FAIL_CHECK if (retcode != PAM_SUCCESS) { \
41 fprintf(stderr,"\n%s\n",pam_strerror(pamh, retcode)); \
42 syslog(LOG_ERR,"%s",pam_strerror(pamh, retcode)); \
43 pam_end(pamh, retcode); exit(1); \
44 }
45 #endif
46
47 #ifdef WITH_SELINUX
48 #include <selinux/selinux.h>
49 /* #include <selinux/get_context_list.h> */
50 #endif
51
52
53 static void child_process __P((entry *, user *)),
54 do_univ __P((user *));
55
56 /* Build up the job environment from the PAM environment plus the
57 crontab environment */
58 static char ** build_env(char **cronenv)
59 {
60 char **jobenv = cronenv;
61 #if defined(USE_PAM)
62 char **pamenv = pam_getenvlist(pamh);
63 char *cronvar;
64 int count = 0;
65
66 jobenv = env_copy(pamenv);
67
68 /* Now add the cron environment variables. Since env_set()
69 overwrites existing variables, this will let cron's
70 environment settings override pam's */
71
72 while ((cronvar = cronenv[count++])) {
73 if (!(jobenv = env_set(jobenv, cronvar))) {
74 syslog(LOG_ERR, "Setting Cron environment variable %s failed", cronvar);
75 return NULL;
76 }
77 }
78 #endif
79 return jobenv;
80 }
81
82 void
83 do_command(e, u)
84 entry *e;
85 user *u;
86 {
87 Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
88 getpid(), e->cmd, u->name, e->uid, e->gid))
89
90 /* fork to become asynchronous -- parent process is done immediately,
91 * and continues to run the normal cron code, which means return to
92 * tick(). the child and grandchild don't leave this function, alive.
93 *
94 * vfork() is unsuitable, since we have much to do, and the parent
95 * needs to be able to run off and fork other processes.
96 */
97 switch (fork()) {
98 case -1:
99 log_it("CRON",getpid(),"error","can't fork");
100 break;
101 case 0:
102 /* child process */
103 acquire_daemonlock(1);
104 child_process(e, u);
105 Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
106 _exit(OK_EXIT);
107 break;
108 default:
109 /* parent process */
110 break;
111 }
112 Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
113 }
114
115
116 /*
117 * CROND
118 * - cron (runs child_process);
119 * - cron (runs exec sh -c 'tab entry');
120 * - cron (writes any %-style stdin to the command);
121 * - mail (popen writes any stdout to mailcmd);
122 */
123
124 static void
125 child_process(e, u)
126 entry *e;
127 user *u;
128 {
129 int stdin_pipe[2];
130 FILE *tmpout;
131 register char *input_data;
132 char *usernm, *mailto;
133 int children = 0;
134
135 #if defined(USE_PAM)
136 int retcode = 0;
137 #endif
138
139 Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
140
141 /* mark ourselves as different to PS command watchers by upshifting
142 * our program name. This has no effect on some kernels.
143 */
144 /*local*/{
145 register char *pch;
146
147 for (pch = ProgramName; *pch; pch++)
148 *pch = MkUpper(*pch);
149 }
150
151 /* discover some useful and important environment settings
152 */
153 usernm = env_get("LOGNAME", e->envp);
154 mailto = env_get("MAILTO", e->envp);
155
156 /* Check for arguments */
157 if (mailto) {
158 const char *end;
159
160 /* These chars have to match those cron_popen()
161 * uses to split the command string */
162 mailto += strspn(mailto, " \t\n");
163 end = mailto + strcspn(mailto, " \t\n");
164 if (*mailto == '-' || *end != '\0') {
165 printf("Bad Mailto karma.\n");
166 log_it("CRON",getpid(),"error","bad mailto");
167 mailto = NULL;
168 }
169 }
170
171 #ifdef USE_SIGCHLD
172 /* our parent is watching for our death by catching SIGCHLD. we
173 * do not care to watch for our children's deaths this way -- we
174 * use wait() explictly. so we have to disable the signal (which
175 * was inherited from the parent).
176 */
177 #ifdef DEBIAN
178 (void) signal(SIGCHLD, SIG_DFL);
179 #else
180 (void) signal(SIGCHLD, SIG_IGN);
181 #endif
182 #else
183 /* on system-V systems, we are ignoring SIGCLD. we have to stop
184 * ignoring it now or the wait() in cron_pclose() won't work.
185 * because of this, we have to wait() for our children here, as well.
186 */
187 (void) signal(SIGCLD, SIG_DFL);
188 #endif /*BSD*/
189
190 /* create a pipe to talk to our future child
191 */
192 pipe(stdin_pipe); /* child's stdin */
193 /* child's stdout */
194 if ((tmpout = tmpfile()) == NULL) {
195 log_it("CRON", getpid(), "error", "create tmpfile");
196 exit(ERROR_EXIT);
197 }
198
199 /* since we are a forked process, we can diddle the command string
200 * we were passed -- nobody else is going to use it again, right?
201 *
202 * if a % is present in the command, previous characters are the
203 * command, and subsequent characters are the additional input to
204 * the command. Subsequent %'s will be transformed into newlines,
205 * but that happens later.
206 *
207 * If there are escaped %'s, remove the escape character.
208 */
209 /*local*/{
210 register int escaped = FALSE;
211 register int ch;
212 register char *p;
213
214 for (input_data = p = e->cmd; (ch = *input_data);
215 input_data++, p++) {
216 if (p != input_data)
217 *p = ch;
218 if (escaped) {
219 if (ch == '%' || ch == '\\')
220 *--p = ch;
221 escaped = FALSE;
222 continue;
223 }
224 if (ch == '\\') {
225 escaped = TRUE;
226 continue;
227 }
228 if (ch == '%') {
229 *input_data++ = '\0';
230 break;
231 }
232 }
233 *p = '\0';
234 }
235
236 #if defined(USE_PAM)
237 retcode = pam_start("cron", usernm, &conv, &pamh);
238 PAM_FAIL_CHECK;
239 retcode = pam_set_item(pamh, PAM_TTY, "cron");
240 PAM_FAIL_CHECK;
241 retcode = pam_acct_mgmt(pamh, PAM_SILENT);
242 PAM_FAIL_CHECK;
243 retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED | PAM_SILENT);
244 PAM_FAIL_CHECK;
245 retcode = pam_open_session(pamh, PAM_SILENT);
246 PAM_FAIL_CHECK;
247
248 #endif
249
250 /* fork again, this time so we can exec the user's command.
251 */
252 switch (vfork()) {
253 case -1:
254 log_it("CRON",getpid(),"error","can't vfork");
255 exit(ERROR_EXIT);
256 /*NOTREACHED*/
257 case 0:
258 Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n",
259 getpid()))
260
261 /* write a log message. we've waited this long to do it
262 * because it was not until now that we knew the PID that
263 * the actual user command shell was going to get and the
264 * PID is part of the log message.
265 */
266 if (log_level >= 1) {
267 char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
268
269 log_it(usernm, getpid(), "CMD", x);
270 free(x);
271 }
272
273 /* that's the last thing we'll log. close the log files.
274 */
275 log_close();
276
277 /* get new pgrp, void tty, etc.
278 */
279 (void) setsid();
280
281 /* close the pipe ends that we won't use. this doesn't affect
282 * the parent, who has to read and write them; it keeps the
283 * kernel from recording us as a potential client TWICE --
284 * which would keep it from sending SIGPIPE in otherwise
285 * appropriate circumstances.
286 */
287 close(stdin_pipe[WRITE_PIPE]);
288
289 /* grandchild process. make std{in,out} be the ends of
290 * pipes opened by our daddy; make stderr go to stdout.
291 */
292 /* Closes are unnecessary -- let dup2() do it */
293
294 /* close(STDIN) */; dup2(stdin_pipe[READ_PIPE], STDIN);
295 dup2(fileno(tmpout), STDOUT);
296 /* close(STDERR)*/; dup2(STDOUT, STDERR);
297
298
299 /* close the pipe we just dup'ed. The resources will remain.
300 */
301 close(stdin_pipe[READ_PIPE]);
302 // Don't do this: fclose(tmpout);
303
304 /* set our login universe. Do this in the grandchild
305 * so that the child can invoke /usr/lib/sendmail
306 * without surprises.
307 */
308 do_univ(u);
309
310 /* set our directory, uid and gid. Set gid first, since once
311 * we set uid, we've lost root privledges.
312 */
313 if (setgid(e->gid) !=0) {
314 char msg[256];
315 snprintf(msg, 256, "do_command:setgid(%lu) failed: %s",
316 (unsigned long) e->gid, strerror(errno));
317 log_it("CRON",getpid(),"error",msg);
318 exit(ERROR_EXIT);
319 }
320 # if defined(BSD) || defined(POSIX)
321 if (initgroups(env_get("LOGNAME", e->envp), e->gid) !=0) {
322 char msg[256];
323 snprintf(msg, 256, "do_command:initgroups(%lu) failed: %s",
324 (unsigned long) e->gid, strerror(errno));
325 log_it("CRON",getpid(),"error",msg);
326 exit(ERROR_EXIT);
327 }
328 # endif
329 if (setuid(e->uid) !=0) { /* we aren't root after this... */
330 char msg[256];
331 snprintf(msg, 256, "do_command:setuid(%lu) failed: %s",
332 (unsigned long) e->uid, strerror(errno));
333 log_it("CRON",getpid(),"error",msg);
334 exit(ERROR_EXIT);
335 }
336 chdir(env_get("HOME", e->envp));
337
338 /* exec the command.
339 */
340 {
341 char **jobenv = build_env(e->envp);
342 char *shell = env_get("SHELL", jobenv);
343 # if DEBUGGING
344 if (DebugFlags & DTEST) {
345 fprintf(stderr,
346 "debug DTEST is on, not exec'ing command.\n");
347 fprintf(stderr,
348 "\tcmd='%s' shell='%s'\n", e->cmd, shell);
349 _exit(OK_EXIT);
350 }
351 # endif /*DEBUGGING*/
352 #if 0
353 {
354 struct sigaction oact;
355 sigaction(SIGCHLD, NULL, &oact);
356 }
357 fprintf(stdout,"error");
358 #endif
359 #ifdef WITH_SELINUX
360 if (is_selinux_enabled() > 0) {
361 if (u->scontext != 0L) {
362 if (setexeccon(u->scontext) < 0) {
363 if (security_getenforce() > 0) {
364 fprintf(stderr, "Could not set exec context to %s for user %s\n", u->scontext,u->name);
365 _exit(ERROR_EXIT);
366 }
367 }
368 }
369 else if(security_getenforce() > 0)
370 {
371 fprintf(stderr, "Error, must have a security context for the cron job when in enforcing mode.\nUser %s.\n", u->name);
372 _exit(ERROR_EXIT);
373 }
374 }
375 #endif
376 execle(shell, shell, "-c", e->cmd, (char *)0, jobenv);
377 fprintf(stderr, "%s: execle: %s\n", shell, strerror(errno));
378 _exit(ERROR_EXIT);
379 }
380 break;
381 default:
382 /* parent process */
383 break;
384 }
385
386 children++;
387
388 /* middle process, child of original cron, parent of process running
389 * the user's command.
390 */
391
392 Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
393
394 /* close the end of the pipe that will only be referenced in the
395 * grandchild process...
396 */
397 close(stdin_pipe[READ_PIPE]);
398
399 /*
400 * write, to the pipe connected to child's stdin, any input specified
401 * after a % in the crontab entry. while we copy, convert any
402 * additional %'s to newlines. when done, if some characters were
403 * written and the last one wasn't a newline, write a newline.
404 *
405 * Note that if the input data won't fit into one pipe buffer (2K
406 * or 4K on most BSD systems), and the child doesn't read its stdin,
407 * we would block here. thus we must fork again.
408 */
409
410 if (*input_data && fork() == 0) {
411 register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
412 register int need_newline = FALSE;
413 register int escaped = FALSE;
414 register int ch;
415
416 Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
417
418 /* translation:
419 * \% -> %
420 * % -> \n
421 * \x -> \x for all x != %
422 */
423 while ((ch = *input_data++) != '\0') {
424 if (escaped) {
425 if (ch != '%')
426 putc('\\', out);
427 } else {
428 if (ch == '%')
429 ch = '\n';
430 }
431
432 if (!(escaped = (ch == '\\'))) {
433 putc(ch, out);
434 need_newline = (ch != '\n');
435 }
436 }
437 if (escaped)
438 putc('\\', out);
439 if (need_newline)
440 putc('\n', out);
441
442 /* close the pipe, causing an EOF condition. fclose causes
443 * stdin_pipe[WRITE_PIPE] to be closed, too.
444 */
445 fclose(out);
446
447 Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
448 exit(0);
449 }
450
451 /* close the pipe to the grandkiddie's stdin, since its wicked uncle
452 * ernie back there has it open and will close it when he's done.
453 */
454 close(stdin_pipe[WRITE_PIPE]);
455
456 children++;
457
458 /*
459 * read output from the grandchild. it's stderr has been redirected to
460 * it's stdout, which has been redirected to our pipe. if there is any
461 * output, we'll be mailing it to the user whose crontab this is...
462 * when the grandchild exits, we'll get EOF.
463 */
464
465 /* wait for children to die.
466 */
467 int status = 0;
468 for (; children > 0; children--)
469 {
470 char msg[256];
471 WAIT_T waiter;
472 PID_T pid;
473
474 Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
475 getpid(), children))
476 pid = wait(&waiter);
477 if (pid < OK) {
478 Debug(DPROC, ("[%d] no more grandchildren\n", getpid()))
479 break;
480 }
481 Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x\n",
482 getpid(), pid, WEXITSTATUS(waiter)))
483
484 if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
485 status = waiter;
486 snprintf(msg, 256, "grandchild #%d failed with exit "
487 "status %d", pid, WEXITSTATUS(waiter));
488 log_it("CRON", getpid(), "error", msg);
489 } else if (WIFSIGNALED(waiter)) {
490 status = waiter;
491 snprintf(msg, 256, "grandchild #%d terminated by signal"
492 " %d%s", pid, WTERMSIG(waiter),
493 WCOREDUMP(waiter) ? ", dumped core" : "");
494 log_it("CRON", getpid(), "error", msg);
495 }
496 }
497
498 // Finally, send any output of the command to the mailer; also, alert
499 // the user if their job failed. Avoid popening the mailcmd until now
500 // since sendmail may time out, and to write info about the exit
501 // status.
502
503 long pos;
504 struct stat mcsb;
505 int statret;
506
507 fseek(tmpout, 0, SEEK_END);
508 pos = ftell(tmpout);
509 fseek(tmpout, 0, SEEK_SET);
510
511 Debug(DPROC|DEXT, ("[%d] got %ld bytes data from grandchild tmpfile\n",
512 getpid(), (long) pos))
513 if (pos == 0)
514 goto mail_finished;
515
516 // get name of recipient.
517 if (mailto == NULL)
518 mailto = usernm;
519 else if (!*mailto)
520 goto mail_finished;
521
522 /* Don't send mail if MAILCMD is not available */
523 if ((statret = stat(MAILCMD, &mcsb)) != 0) {
524 Debug(DPROC|DEXT, ("%s not found, not sending mail\n", MAILCMD))
525 if (pos > 0) {
526 log_it("CRON", getpid(), "info", "No MTA installed, discarding output");
527 }
528 goto mail_finished;
529 } else {
530 Debug(DPROC|DEXT, ("%s found, will send mail\n", MAILCMD))
531 }
532
533 register FILE *mail = NULL;
534 register int bytes = 1;
535
536 register char **env;
537 char **jobenv = build_env(e->envp);
538 auto char mailcmd[MAX_COMMAND];
539 auto char hostname[MAXHOSTNAMELEN];
540 char *content_type = env_get("CONTENT_TYPE",jobenv),
541 *content_transfer_encoding = env_get("CONTENT_TRANSFER_ENCODING",jobenv);
542
543 (void) gethostname(hostname, MAXHOSTNAMELEN);
544 (void) snprintf(mailcmd, sizeof(mailcmd),
545 MAILARGS, MAILCMD, mailto);
546 if (!(mail = cron_popen(mailcmd, "w", e))) {
547 perror(MAILCMD);
548 (void) _exit(ERROR_EXIT);
549 }
550 fprintf(mail, "From: root (Cron Daemon)\n");
551 fprintf(mail, "To: %s\n", mailto);
552 fprintf(mail, "Subject: Cron <%s@%s> %s%s\n",
553 usernm, first_word(hostname, "."),
554 e->cmd, status?" (failed)":"");
555 # if defined(MAIL_DATE)
556 fprintf(mail, "Date: %s\n",
557 arpadate(&StartTime));
558 # endif /* MAIL_DATE */
559 if ( content_type == 0L ) {
560 fprintf(mail, "Content-Type: text/plain; charset=%s\n",
561 cron_default_mail_charset
562 );
563 } else {
564 /* user specified Content-Type header.
565 * disallow new-lines for security reasons
566 * (else users could specify arbitrary mail headers!)
567 */
568 char *nl=content_type;
569 size_t ctlen = strlen(content_type);
570
571 while( (*nl != '\0')
572 && ((nl=strchr(nl,'\n')) != 0L)
573 && (nl < (content_type+ctlen))
574 ) *nl = ' ';
575 fprintf(mail,"Content-Type: %s\n", content_type);
576 }
577 if ( content_transfer_encoding != 0L ) {
578 char *nl=content_transfer_encoding;
579 size_t ctlen = strlen(content_transfer_encoding);
580 while( (*nl != '\0')
581 && ((nl=strchr(nl,'\n')) != 0L)
582 && (nl < (content_transfer_encoding+ctlen))
583 ) *nl = ' ';
584
585 fprintf(mail,"Content-Transfer-Encoding: %s\n", content_transfer_encoding);
586 }
587
588 for (env = e->envp; *env; env++)
589 fprintf(mail, "X-Cron-Env: <%s>\n",
590 *env);
591 fputc('\n', mail);
592
593 // Append the actual output of the child to the mail
594
595 char buf[4096];
596 int ret, remain;
597
598 while(1) {
599 if ((ret = fread(buf, 1, sizeof(buf), tmpout)) == 0)
600 break;
601 for (remain = ret; remain != 0; ) {
602 ret = fwrite(buf, 1, remain, mail);
603 if (ret > 0) {
604 remain -= ret;
605 continue;
606 }
607 // XXX error
608 break;
609 }
610 }
611
612 Debug(DPROC, ("[%d] closing pipe to mail\n", getpid()))
613 status = cron_pclose(mail);
614
615 /* if there was output and we could not mail it,
616 * log the facts so the poor user can figure out
617 * what's going on.
618 */
619 if (status) {
620 char buf[MAX_TEMPSTR];
621 snprintf(buf, MAX_TEMPSTR,
622 "mailed %d byte%s of output; "
623 "but got status 0x%04x, "
624 "\n",
625 bytes, (bytes==1)?"":"s", status);
626 log_it(usernm, getpid(), "MAIL", buf);
627 }
628
629 if (ferror(tmpout)) {
630 log_it(usernm, getpid(), "MAIL", "stream error reading output");
631 }
632
633 mail_finished:
634 fclose(tmpout);
635
636 if (log_level >= 2) {
637 char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
638 log_it(usernm, getpid(), "END", x);
639 free(x);
640 }
641
642 #if defined(USE_PAM)
643 pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT);
644 retcode = pam_close_session(pamh, PAM_SILENT);
645 pam_end(pamh, retcode);
646 #endif
647 }
648
649
650 static void
651 do_univ(u)
652 user *u;
653 {
654 #if defined(sequent)
655 /* Dynix (Sequent) hack to put the user associated with
656 * the passed user structure into the ATT universe if
657 * necessary. We have to dig the gecos info out of
658 * the user's password entry to see if the magic
659 * "universe(att)" string is present.
660 */
661
662 struct passwd *p;
663 char *s;
664 int i;
665
666 p = getpwuid(u->uid);
667 (void) endpwent();
668
669 if (p == NULL)
670 return;
671
672 s = p->pw_gecos;
673
674 for (i = 0; i < 4; i++)
675 {
676 if ((s = strchr(s, ',')) == NULL)
677 return;
678 s++;
679 }
680 if (strcmp(s, "universe(att)"))
681 return;
682
683 (void) universe(U_ATT);
684 #endif
685 }

Properties

Name Value
svn:eol-style native

  ViewVC Help
Powered by ViewVC 1.1.5