]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - usr.sbin/cron/cron/do_command.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / usr.sbin / cron / cron / do_command.c
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 const char rcsid[] =
20   "$FreeBSD$";
21 #endif
22
23
24 #include "cron.h"
25 #include <sys/signal.h>
26 #if defined(sequent)
27 # include <sys/universe.h>
28 #endif
29 #if defined(SYSLOG)
30 # include <syslog.h>
31 #endif
32 #if defined(LOGIN_CAP)
33 # include <login_cap.h>
34 #endif
35 #ifdef PAM
36 # include <security/pam_appl.h>
37 # include <security/openpam.h>
38 #endif
39
40
41 static void             child_process(entry *, user *),
42                         do_univ(user *);
43
44
45 void
46 do_command(e, u)
47         entry   *e;
48         user    *u;
49 {
50         Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
51                 getpid(), e->cmd, u->name, e->uid, e->gid))
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                 log_it("CRON",getpid(),"error","can't fork");
63                 break;
64         case 0:
65                 /* child process */
66                 pidfile_close(pfh);
67                 child_process(e, u);
68                 Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
69                 _exit(OK_EXIT);
70                 break;
71         default:
72                 /* parent process */
73                 break;
74         }
75         Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
76 }
77
78
79 static void
80 child_process(e, u)
81         entry   *e;
82         user    *u;
83 {
84         int             stdin_pipe[2], stdout_pipe[2];
85         register char   *input_data;
86         char            *usernm, *mailto;
87         int             children = 0;
88 # if defined(LOGIN_CAP)
89         struct passwd   *pwd;
90         login_cap_t *lc;
91 # endif
92
93         Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
94
95         /* mark ourselves as different to PS command watchers by upshifting
96          * our program name.  This has no effect on some kernels.
97          */
98         setproctitle("running job");
99
100         /* discover some useful and important environment settings
101          */
102         usernm = env_get("LOGNAME", e->envp);
103         mailto = env_get("MAILTO", e->envp);
104
105 #ifdef PAM
106         /* use PAM to see if the user's account is available,
107          * i.e., not locked or expired or whatever.  skip this
108          * for system tasks from /etc/crontab -- they can run
109          * as any user.
110          */
111         if (strcmp(u->name, SYS_NAME)) {        /* not equal */
112                 pam_handle_t *pamh = NULL;
113                 int pam_err;
114                 struct pam_conv pamc = {
115                         .conv = openpam_nullconv,
116                         .appdata_ptr = NULL
117                 };
118
119                 Debug(DPROC, ("[%d] checking account with PAM\n", getpid()))
120
121                 /* u->name keeps crontab owner name while LOGNAME is the name
122                  * of user to run command on behalf of.  they should be the
123                  * same for a task from a per-user crontab.
124                  */
125                 if (strcmp(u->name, usernm)) {
126                         log_it(usernm, getpid(), "username ambiguity", u->name);
127                         exit(ERROR_EXIT);
128                 }
129
130                 pam_err = pam_start("cron", usernm, &pamc, &pamh);
131                 if (pam_err != PAM_SUCCESS) {
132                         log_it("CRON", getpid(), "error", "can't start PAM");
133                         exit(ERROR_EXIT);
134                 }
135
136                 pam_err = pam_acct_mgmt(pamh, PAM_SILENT);
137                 /* Expired password shouldn't prevent the job from running. */
138                 if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD) {
139                         log_it(usernm, getpid(), "USER", "account unavailable");
140                         exit(ERROR_EXIT);
141                 }
142
143                 pam_end(pamh, pam_err);
144         }
145 #endif
146
147 #ifdef USE_SIGCHLD
148         /* our parent is watching for our death by catching SIGCHLD.  we
149          * do not care to watch for our children's deaths this way -- we
150          * use wait() explicitly.  so we have to disable the signal (which
151          * was inherited from the parent).
152          */
153         (void) signal(SIGCHLD, SIG_DFL);
154 #else
155         /* on system-V systems, we are ignoring SIGCLD.  we have to stop
156          * ignoring it now or the wait() in cron_pclose() won't work.
157          * because of this, we have to wait() for our children here, as well.
158          */
159         (void) signal(SIGCLD, SIG_DFL);
160 #endif /*BSD*/
161
162         /* create some pipes to talk to our future child
163          */
164         pipe(stdin_pipe);       /* child's stdin */
165         pipe(stdout_pipe);      /* child's stdout */
166
167         /* since we are a forked process, we can diddle the command string
168          * we were passed -- nobody else is going to use it again, right?
169          *
170          * if a % is present in the command, previous characters are the
171          * command, and subsequent characters are the additional input to
172          * the command.  Subsequent %'s will be transformed into newlines,
173          * but that happens later.
174          *
175          * If there are escaped %'s, remove the escape character.
176          */
177         /*local*/{
178                 register int escaped = FALSE;
179                 register int ch;
180                 register char *p;
181
182                 for (input_data = p = e->cmd; (ch = *input_data);
183                      input_data++, p++) {
184                         if (p != input_data)
185                             *p = ch;
186                         if (escaped) {
187                                 if (ch == '%' || ch == '\\')
188                                         *--p = ch;
189                                 escaped = FALSE;
190                                 continue;
191                         }
192                         if (ch == '\\') {
193                                 escaped = TRUE;
194                                 continue;
195                         }
196                         if (ch == '%') {
197                                 *input_data++ = '\0';
198                                 break;
199                         }
200                 }
201                 *p = '\0';
202         }
203
204         /* fork again, this time so we can exec the user's command.
205          */
206         switch (vfork()) {
207         case -1:
208                 log_it("CRON",getpid(),"error","can't vfork");
209                 exit(ERROR_EXIT);
210                 /*NOTREACHED*/
211         case 0:
212                 Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n",
213                               getpid()))
214
215                 if (e->uid == ROOT_UID)
216                         Jitter = RootJitter;
217                 if (Jitter != 0) {
218                         srandom(getpid());
219                         sleep(random() % Jitter);
220                 }
221
222                 /* write a log message.  we've waited this long to do it
223                  * because it was not until now that we knew the PID that
224                  * the actual user command shell was going to get and the
225                  * PID is part of the log message.
226                  */
227                 /*local*/{
228                         char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
229
230                         log_it(usernm, getpid(), "CMD", x);
231                         free(x);
232                 }
233
234                 /* that's the last thing we'll log.  close the log files.
235                  */
236 #ifdef SYSLOG
237                 closelog();
238 #endif
239
240                 /* get new pgrp, void tty, etc.
241                  */
242                 (void) setsid();
243
244                 /* close the pipe ends that we won't use.  this doesn't affect
245                  * the parent, who has to read and write them; it keeps the
246                  * kernel from recording us as a potential client TWICE --
247                  * which would keep it from sending SIGPIPE in otherwise
248                  * appropriate circumstances.
249                  */
250                 close(stdin_pipe[WRITE_PIPE]);
251                 close(stdout_pipe[READ_PIPE]);
252
253                 /* grandchild process.  make std{in,out} be the ends of
254                  * pipes opened by our daddy; make stderr go to stdout.
255                  */
256                 close(STDIN);   dup2(stdin_pipe[READ_PIPE], STDIN);
257                 close(STDOUT);  dup2(stdout_pipe[WRITE_PIPE], STDOUT);
258                 close(STDERR);  dup2(STDOUT, STDERR);
259
260                 /* close the pipes we just dup'ed.  The resources will remain.
261                  */
262                 close(stdin_pipe[READ_PIPE]);
263                 close(stdout_pipe[WRITE_PIPE]);
264
265                 /* set our login universe.  Do this in the grandchild
266                  * so that the child can invoke /usr/lib/sendmail
267                  * without surprises.
268                  */
269                 do_univ(u);
270
271 # if defined(LOGIN_CAP)
272                 /* Set user's entire context, but skip the environment
273                  * as cron provides a separate interface for this
274                  */
275                 if ((pwd = getpwnam(usernm)) == NULL)
276                         pwd = getpwuid(e->uid);
277                 lc = NULL;
278                 if (pwd != NULL) {
279                         pwd->pw_gid = e->gid;
280                         if (e->class != NULL)
281                                 lc = login_getclass(e->class);
282                 }
283                 if (pwd &&
284                     setusercontext(lc, pwd, e->uid,
285                             LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0)
286                         (void) endpwent();
287                 else {
288                         /* fall back to the old method */
289                         (void) endpwent();
290 # endif
291                         /* set our directory, uid and gid.  Set gid first,
292                          * since once we set uid, we've lost root privileges.
293                          */
294                         if (setgid(e->gid) != 0) {
295                                 log_it(usernm, getpid(),
296                                     "error", "setgid failed");
297                                 exit(ERROR_EXIT);
298                         }
299 # if defined(BSD)
300                         if (initgroups(usernm, e->gid) != 0) {
301                                 log_it(usernm, getpid(),
302                                     "error", "initgroups failed");
303                                 exit(ERROR_EXIT);
304                         }
305 # endif
306                         if (setlogin(usernm) != 0) {
307                                 log_it(usernm, getpid(),
308                                     "error", "setlogin failed");
309                                 exit(ERROR_EXIT);
310                         }
311                         if (setuid(e->uid) != 0) {
312                                 log_it(usernm, getpid(),
313                                     "error", "setuid failed");
314                                 exit(ERROR_EXIT);
315                         }
316                         /* we aren't root after this..*/
317 #if defined(LOGIN_CAP)
318                 }
319                 if (lc != NULL)
320                         login_close(lc);
321 #endif
322                 chdir(env_get("HOME", e->envp));
323
324                 /* exec the command.
325                  */
326                 {
327                         char    *shell = env_get("SHELL", e->envp);
328
329 # if DEBUGGING
330                         if (DebugFlags & DTEST) {
331                                 fprintf(stderr,
332                                 "debug DTEST is on, not exec'ing command.\n");
333                                 fprintf(stderr,
334                                 "\tcmd='%s' shell='%s'\n", e->cmd, shell);
335                                 _exit(OK_EXIT);
336                         }
337 # endif /*DEBUGGING*/
338                         execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);
339                         warn("execl: couldn't exec `%s'", shell);
340                         _exit(ERROR_EXIT);
341                 }
342                 break;
343         default:
344                 /* parent process */
345                 break;
346         }
347
348         children++;
349
350         /* middle process, child of original cron, parent of process running
351          * the user's command.
352          */
353
354         Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
355
356         /* close the ends of the pipe that will only be referenced in the
357          * grandchild process...
358          */
359         close(stdin_pipe[READ_PIPE]);
360         close(stdout_pipe[WRITE_PIPE]);
361
362         /*
363          * write, to the pipe connected to child's stdin, any input specified
364          * after a % in the crontab entry.  while we copy, convert any
365          * additional %'s to newlines.  when done, if some characters were
366          * written and the last one wasn't a newline, write a newline.
367          *
368          * Note that if the input data won't fit into one pipe buffer (2K
369          * or 4K on most BSD systems), and the child doesn't read its stdin,
370          * we would block here.  thus we must fork again.
371          */
372
373         if (*input_data && fork() == 0) {
374                 register FILE   *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
375                 register int    need_newline = FALSE;
376                 register int    escaped = FALSE;
377                 register int    ch;
378
379                 if (out == NULL) {
380                         warn("fdopen failed in child2");
381                         _exit(ERROR_EXIT);
382                 }
383
384                 Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
385
386                 /* close the pipe we don't use, since we inherited it and
387                  * are part of its reference count now.
388                  */
389                 close(stdout_pipe[READ_PIPE]);
390
391                 /* translation:
392                  *      \% -> %
393                  *      %  -> \n
394                  *      \x -> \x        for all x != %
395                  */
396                 while ((ch = *input_data++)) {
397                         if (escaped) {
398                                 if (ch != '%')
399                                         putc('\\', out);
400                         } else {
401                                 if (ch == '%')
402                                         ch = '\n';
403                         }
404
405                         if (!(escaped = (ch == '\\'))) {
406                                 putc(ch, out);
407                                 need_newline = (ch != '\n');
408                         }
409                 }
410                 if (escaped)
411                         putc('\\', out);
412                 if (need_newline)
413                         putc('\n', out);
414
415                 /* close the pipe, causing an EOF condition.  fclose causes
416                  * stdin_pipe[WRITE_PIPE] to be closed, too.
417                  */
418                 fclose(out);
419
420                 Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
421                 exit(0);
422         }
423
424         /* close the pipe to the grandkiddie's stdin, since its wicked uncle
425          * ernie back there has it open and will close it when he's done.
426          */
427         close(stdin_pipe[WRITE_PIPE]);
428
429         children++;
430
431         /*
432          * read output from the grandchild.  it's stderr has been redirected to
433          * it's stdout, which has been redirected to our pipe.  if there is any
434          * output, we'll be mailing it to the user whose crontab this is...
435          * when the grandchild exits, we'll get EOF.
436          */
437
438         Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
439
440         /*local*/{
441                 register FILE   *in = fdopen(stdout_pipe[READ_PIPE], "r");
442                 register int    ch;
443
444                 if (in == NULL) {
445                         warn("fdopen failed in child");
446                         _exit(ERROR_EXIT);
447                 }
448
449                 ch = getc(in);
450                 if (ch != EOF) {
451                         register FILE   *mail;
452                         register int    bytes = 1;
453                         int             status = 0;
454
455                         Debug(DPROC|DEXT,
456                                 ("[%d] got data (%x:%c) from grandchild\n",
457                                         getpid(), ch, ch))
458
459                         /* get name of recipient.  this is MAILTO if set to a
460                          * valid local username; USER otherwise.
461                          */
462                         if (mailto == NULL) {
463                                 /* MAILTO not present, set to USER,
464                                  * unless globally overriden.
465                                  */
466                                 if (defmailto)
467                                         mailto = defmailto;
468                                 else
469                                         mailto = usernm;
470                         }
471                         if (mailto && *mailto == '\0')
472                                 mailto = NULL;
473
474                         /* if we are supposed to be mailing, MAILTO will
475                          * be non-NULL.  only in this case should we set
476                          * up the mail command and subjects and stuff...
477                          */
478
479                         if (mailto) {
480                                 register char   **env;
481                                 auto char       mailcmd[MAX_COMMAND];
482                                 auto char       hostname[MAXHOSTNAMELEN];
483
484                                 (void) gethostname(hostname, MAXHOSTNAMELEN);
485                                 (void) snprintf(mailcmd, sizeof(mailcmd),
486                                                MAILARGS, MAILCMD);
487                                 if (!(mail = cron_popen(mailcmd, "w", e))) {
488                                         warn("%s", MAILCMD);
489                                         (void) _exit(ERROR_EXIT);
490                                 }
491                                 fprintf(mail, "From: %s (Cron Daemon)\n", usernm);
492                                 fprintf(mail, "To: %s\n", mailto);
493                                 fprintf(mail, "Subject: Cron <%s@%s> %s\n",
494                                         usernm, first_word(hostname, "."),
495                                         e->cmd);
496 # if defined(MAIL_DATE)
497                                 fprintf(mail, "Date: %s\n",
498                                         arpadate(&TargetTime));
499 # endif /* MAIL_DATE */
500                                 for (env = e->envp;  *env;  env++)
501                                         fprintf(mail, "X-Cron-Env: <%s>\n",
502                                                 *env);
503                                 fprintf(mail, "\n");
504
505                                 /* this was the first char from the pipe
506                                  */
507                                 putc(ch, mail);
508                         }
509
510                         /* we have to read the input pipe no matter whether
511                          * we mail or not, but obviously we only write to
512                          * mail pipe if we ARE mailing.
513                          */
514
515                         while (EOF != (ch = getc(in))) {
516                                 bytes++;
517                                 if (mailto)
518                                         putc(ch, mail);
519                         }
520
521                         /* only close pipe if we opened it -- i.e., we're
522                          * mailing...
523                          */
524
525                         if (mailto) {
526                                 Debug(DPROC, ("[%d] closing pipe to mail\n",
527                                         getpid()))
528                                 /* Note: the pclose will probably see
529                                  * the termination of the grandchild
530                                  * in addition to the mail process, since
531                                  * it (the grandchild) is likely to exit
532                                  * after closing its stdout.
533                                  */
534                                 status = cron_pclose(mail);
535                         }
536
537                         /* if there was output and we could not mail it,
538                          * log the facts so the poor user can figure out
539                          * what's going on.
540                          */
541                         if (mailto && status) {
542                                 char buf[MAX_TEMPSTR];
543
544                                 snprintf(buf, sizeof(buf),
545                         "mailed %d byte%s of output but got status 0x%04x\n",
546                                         bytes, (bytes==1)?"":"s",
547                                         status);
548                                 log_it(usernm, getpid(), "MAIL", buf);
549                         }
550
551                 } /*if data from grandchild*/
552
553                 Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
554
555                 fclose(in);     /* also closes stdout_pipe[READ_PIPE] */
556         }
557
558         /* wait for children to die.
559          */
560         for (;  children > 0;  children--)
561         {
562                 WAIT_T          waiter;
563                 PID_T           pid;
564
565                 Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
566                         getpid(), children))
567                 pid = wait(&waiter);
568                 if (pid < OK) {
569                         Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
570                                 getpid()))
571                         break;
572                 }
573                 Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
574                         getpid(), pid, WEXITSTATUS(waiter)))
575                 if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
576                         Debug(DPROC, (", dumped core"))
577                 Debug(DPROC, ("\n"))
578         }
579 }
580
581
582 static void
583 do_univ(u)
584         user    *u;
585 {
586 #if defined(sequent)
587 /* Dynix (Sequent) hack to put the user associated with
588  * the passed user structure into the ATT universe if
589  * necessary.  We have to dig the gecos info out of
590  * the user's password entry to see if the magic
591  * "universe(att)" string is present.
592  */
593
594         struct  passwd  *p;
595         char    *s;
596         int     i;
597
598         p = getpwuid(u->uid);
599         (void) endpwent();
600
601         if (p == NULL)
602                 return;
603
604         s = p->pw_gecos;
605
606         for (i = 0; i < 4; i++)
607         {
608                 if ((s = strchr(s, ',')) == NULL)
609                         return;
610                 s++;
611         }
612         if (strcmp(s, "universe(att)"))
613                 return;
614
615         (void) universe(U_ATT);
616 #endif
617 }