Remove __P macros from src/usr.bin and src/usr.sbin.
[dragonfly.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  * $FreeBSD: src/usr.sbin/cron/cron/do_command.c,v 1.15.2.5 2001/05/04 00:59:40 peter Exp $
18  * $DragonFly: src/usr.sbin/cron/cron/do_command.c,v 1.3 2003/11/03 19:31:36 eirikn Exp $
19  */
20
21 #include "cron.h"
22 #include <sys/signal.h>
23 #if defined(sequent)
24 # include <sys/universe.h>
25 #endif
26 #if defined(SYSLOG)
27 # include <syslog.h>
28 #endif
29 #if defined(LOGIN_CAP)
30 # include <login_cap.h>
31 #endif
32
33
34 static void             child_process(entry *, user *),
35                         do_univ(user *);
36
37
38 void
39 do_command(e, u)
40         entry   *e;
41         user    *u;
42 {
43         Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
44                 getpid(), e->cmd, u->name, e->uid, e->gid))
45
46         /* fork to become asynchronous -- parent process is done immediately,
47          * and continues to run the normal cron code, which means return to
48          * tick().  the child and grandchild don't leave this function, alive.
49          *
50          * vfork() is unsuitable, since we have much to do, and the parent
51          * needs to be able to run off and fork other processes.
52          */
53         switch (fork()) {
54         case -1:
55                 log_it("CRON",getpid(),"error","can't fork");
56                 break;
57         case 0:
58                 /* child process */
59                 acquire_daemonlock(1);
60                 child_process(e, u);
61                 Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
62                 _exit(OK_EXIT);
63                 break;
64         default:
65                 /* parent process */
66                 break;
67         }
68         Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
69 }
70
71
72 static void
73 child_process(e, u)
74         entry   *e;
75         user    *u;
76 {
77         int             stdin_pipe[2], stdout_pipe[2];
78         register char   *input_data;
79         char            *usernm, *mailto;
80         int             children = 0;
81 # if defined(LOGIN_CAP)
82         struct passwd   *pwd;
83         login_cap_t *lc;
84 # endif
85
86         Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
87
88         /* mark ourselves as different to PS command watchers by upshifting
89          * our program name.  This has no effect on some kernels.
90          */
91         setproctitle("running job");
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_DFL);
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          * If there are escaped %'s, remove the escape character.
127          */
128         /*local*/{
129                 register int escaped = FALSE;
130                 register int ch;
131                 register char *p;
132
133                 for (input_data = p = e->cmd; (ch = *input_data);
134                      input_data++, p++) {
135                         if (p != input_data)
136                             *p = ch;
137                         if (escaped) {
138                                 if (ch == '%' || ch == '\\')
139                                         *--p = ch;
140                                 escaped = FALSE;
141                                 continue;
142                         }
143                         if (ch == '\\') {
144                                 escaped = TRUE;
145                                 continue;
146                         }
147                         if (ch == '%') {
148                                 *input_data++ = '\0';
149                                 break;
150                         }
151                 }
152                 *p = '\0';
153         }
154
155         /* fork again, this time so we can exec the user's command.
156          */
157         switch (vfork()) {
158         case -1:
159                 log_it("CRON",getpid(),"error","can't vfork");
160                 exit(ERROR_EXIT);
161                 /*NOTREACHED*/
162         case 0:
163                 Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n",
164                               getpid()))
165
166                 /* write a log message.  we've waited this long to do it
167                  * because it was not until now that we knew the PID that
168                  * the actual user command shell was going to get and the
169                  * PID is part of the log message.
170                  */
171                 /*local*/{
172                         char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
173
174                         log_it(usernm, getpid(), "CMD", x);
175                         free(x);
176                 }
177
178                 /* that's the last thing we'll log.  close the log files.
179                  */
180 #ifdef SYSLOG
181                 closelog();
182 #endif
183
184                 /* get new pgrp, void tty, etc.
185                  */
186                 (void) setsid();
187
188                 /* close the pipe ends that we won't use.  this doesn't affect
189                  * the parent, who has to read and write them; it keeps the
190                  * kernel from recording us as a potential client TWICE --
191                  * which would keep it from sending SIGPIPE in otherwise
192                  * appropriate circumstances.
193                  */
194                 close(stdin_pipe[WRITE_PIPE]);
195                 close(stdout_pipe[READ_PIPE]);
196
197                 /* grandchild process.  make std{in,out} be the ends of
198                  * pipes opened by our daddy; make stderr go to stdout.
199                  */
200                 close(STDIN);   dup2(stdin_pipe[READ_PIPE], STDIN);
201                 close(STDOUT);  dup2(stdout_pipe[WRITE_PIPE], STDOUT);
202                 close(STDERR);  dup2(STDOUT, STDERR);
203
204                 /* close the pipes we just dup'ed.  The resources will remain.
205                  */
206                 close(stdin_pipe[READ_PIPE]);
207                 close(stdout_pipe[WRITE_PIPE]);
208
209                 /* set our login universe.  Do this in the grandchild
210                  * so that the child can invoke /usr/lib/sendmail
211                  * without surprises.
212                  */
213                 do_univ(u);
214
215 # if defined(LOGIN_CAP)
216                 /* Set user's entire context, but skip the environment
217                  * as cron provides a separate interface for this
218                  */
219                 if ((pwd = getpwnam(usernm)) == NULL)
220                         pwd = getpwuid(e->uid);
221                 lc = NULL;
222                 if (pwd != NULL) {
223                         pwd->pw_gid = e->gid;
224                         if (e->class != NULL)
225                                 lc = login_getclass(e->class);
226                 }
227                 if (pwd &&
228                     setusercontext(lc, pwd, e->uid,
229                             LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0)
230                         (void) endpwent();
231                 else {
232                         /* fall back to the old method */
233                         (void) endpwent();
234 # endif
235                         /* set our directory, uid and gid.  Set gid first,
236                          * since once we set uid, we've lost root privledges.
237                          */
238                         setgid(e->gid);
239 # if defined(BSD)
240                         initgroups(usernm, e->gid);
241 # endif
242                         setlogin(usernm);
243                         setuid(e->uid);         /* we aren't root after this..*/
244 #if defined(LOGIN_CAP)
245                 }
246                 if (lc != NULL)
247                         login_close(lc);
248 #endif
249                 chdir(env_get("HOME", e->envp));
250
251                 /* exec the command.
252                  */
253                 {
254                         char    *shell = env_get("SHELL", e->envp);
255
256 # if DEBUGGING
257                         if (DebugFlags & DTEST) {
258                                 fprintf(stderr,
259                                 "debug DTEST is on, not exec'ing command.\n");
260                                 fprintf(stderr,
261                                 "\tcmd='%s' shell='%s'\n", e->cmd, shell);
262                                 _exit(OK_EXIT);
263                         }
264 # endif /*DEBUGGING*/
265                         execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);
266                         warn("execl: couldn't exec `%s'", shell);
267                         _exit(ERROR_EXIT);
268                 }
269                 break;
270         default:
271                 /* parent process */
272                 break;
273         }
274
275         children++;
276
277         /* middle process, child of original cron, parent of process running
278          * the user's command.
279          */
280
281         Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
282
283         /* close the ends of the pipe that will only be referenced in the
284          * grandchild process...
285          */
286         close(stdin_pipe[READ_PIPE]);
287         close(stdout_pipe[WRITE_PIPE]);
288
289         /*
290          * write, to the pipe connected to child's stdin, any input specified
291          * after a % in the crontab entry.  while we copy, convert any
292          * additional %'s to newlines.  when done, if some characters were
293          * written and the last one wasn't a newline, write a newline.
294          *
295          * Note that if the input data won't fit into one pipe buffer (2K
296          * or 4K on most BSD systems), and the child doesn't read its stdin,
297          * we would block here.  thus we must fork again.
298          */
299
300         if (*input_data && fork() == 0) {
301                 register FILE   *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
302                 register int    need_newline = FALSE;
303                 register int    escaped = FALSE;
304                 register int    ch;
305
306                 if (out == NULL) {
307                         warn("fdopen failed in child2");
308                         _exit(ERROR_EXIT);
309                 }
310
311                 Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
312
313                 /* close the pipe we don't use, since we inherited it and
314                  * are part of its reference count now.
315                  */
316                 close(stdout_pipe[READ_PIPE]);
317
318                 /* translation:
319                  *      \% -> %
320                  *      %  -> \n
321                  *      \x -> \x        for all x != %
322                  */
323                 while ((ch = *input_data++)) {
324                         if (escaped) {
325                                 if (ch != '%')
326                                         putc('\\', out);
327                         } else {
328                                 if (ch == '%')
329                                         ch = '\n';
330                         }
331
332                         if (!(escaped = (ch == '\\'))) {
333                                 putc(ch, out);
334                                 need_newline = (ch != '\n');
335                         }
336                 }
337                 if (escaped)
338                         putc('\\', out);
339                 if (need_newline)
340                         putc('\n', out);
341
342                 /* close the pipe, causing an EOF condition.  fclose causes
343                  * stdin_pipe[WRITE_PIPE] to be closed, too.
344                  */
345                 fclose(out);
346
347                 Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
348                 exit(0);
349         }
350
351         /* close the pipe to the grandkiddie's stdin, since its wicked uncle
352          * ernie back there has it open and will close it when he's done.
353          */
354         close(stdin_pipe[WRITE_PIPE]);
355
356         children++;
357
358         /*
359          * read output from the grandchild.  it's stderr has been redirected to
360          * it's stdout, which has been redirected to our pipe.  if there is any
361          * output, we'll be mailing it to the user whose crontab this is...
362          * when the grandchild exits, we'll get EOF.
363          */
364
365         Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
366
367         /*local*/{
368                 register FILE   *in = fdopen(stdout_pipe[READ_PIPE], "r");
369                 register int    ch = getc(in);
370
371                 if (in == NULL) {
372                         warn("fdopen failed in child");
373                         _exit(ERROR_EXIT);
374                 }
375
376                 if (ch != EOF) {
377                         register FILE   *mail;
378                         register int    bytes = 1;
379                         int             status = 0;
380
381                         Debug(DPROC|DEXT,
382                                 ("[%d] got data (%x:%c) from grandchild\n",
383                                         getpid(), ch, ch))
384
385                         /* get name of recipient.  this is MAILTO if set to a
386                          * valid local username; USER otherwise.
387                          */
388                         if (mailto) {
389                                 /* MAILTO was present in the environment
390                                  */
391                                 if (!*mailto) {
392                                         /* ... but it's empty. set to NULL
393                                          */
394                                         mailto = NULL;
395                                 }
396                         } else {
397                                 /* MAILTO not present, set to USER.
398                                  */
399                                 mailto = usernm;
400                         }
401
402                         /* if we are supposed to be mailing, MAILTO will
403                          * be non-NULL.  only in this case should we set
404                          * up the mail command and subjects and stuff...
405                          */
406
407                         if (mailto) {
408                                 register char   **env;
409                                 auto char       mailcmd[MAX_COMMAND];
410                                 auto char       hostname[MAXHOSTNAMELEN];
411
412                                 (void) gethostname(hostname, MAXHOSTNAMELEN);
413                                 (void) snprintf(mailcmd, sizeof(mailcmd),
414                                                MAILARGS, MAILCMD);
415                                 if (!(mail = cron_popen(mailcmd, "w", e))) {
416                                         warn("%s", MAILCMD);
417                                         (void) _exit(ERROR_EXIT);
418                                 }
419                                 fprintf(mail, "From: %s (Cron Daemon)\n", usernm);
420                                 fprintf(mail, "To: %s\n", mailto);
421                                 fprintf(mail, "Subject: Cron <%s@%s> %s\n",
422                                         usernm, first_word(hostname, "."),
423                                         e->cmd);
424 # if defined(MAIL_DATE)
425                                 fprintf(mail, "Date: %s\n",
426                                         arpadate(&TargetTime));
427 # endif /* MAIL_DATE */
428                                 for (env = e->envp;  *env;  env++)
429                                         fprintf(mail, "X-Cron-Env: <%s>\n",
430                                                 *env);
431                                 fprintf(mail, "\n");
432
433                                 /* this was the first char from the pipe
434                                  */
435                                 putc(ch, mail);
436                         }
437
438                         /* we have to read the input pipe no matter whether
439                          * we mail or not, but obviously we only write to
440                          * mail pipe if we ARE mailing.
441                          */
442
443                         while (EOF != (ch = getc(in))) {
444                                 bytes++;
445                                 if (mailto)
446                                         putc(ch, mail);
447                         }
448
449                         /* only close pipe if we opened it -- i.e., we're
450                          * mailing...
451                          */
452
453                         if (mailto) {
454                                 Debug(DPROC, ("[%d] closing pipe to mail\n",
455                                         getpid()))
456                                 /* Note: the pclose will probably see
457                                  * the termination of the grandchild
458                                  * in addition to the mail process, since
459                                  * it (the grandchild) is likely to exit
460                                  * after closing its stdout.
461                                  */
462                                 status = cron_pclose(mail);
463                         }
464
465                         /* if there was output and we could not mail it,
466                          * log the facts so the poor user can figure out
467                          * what's going on.
468                          */
469                         if (mailto && status) {
470                                 char buf[MAX_TEMPSTR];
471
472                                 snprintf(buf, sizeof(buf),
473                         "mailed %d byte%s of output but got status 0x%04x\n",
474                                         bytes, (bytes==1)?"":"s",
475                                         status);
476                                 log_it(usernm, getpid(), "MAIL", buf);
477                         }
478
479                 } /*if data from grandchild*/
480
481                 Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
482
483                 fclose(in);     /* also closes stdout_pipe[READ_PIPE] */
484         }
485
486         /* wait for children to die.
487          */
488         for (;  children > 0;  children--)
489         {
490                 WAIT_T          waiter;
491                 PID_T           pid;
492
493                 Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
494                         getpid(), children))
495                 pid = wait(&waiter);
496                 if (pid < OK) {
497                         Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
498                                 getpid()))
499                         break;
500                 }
501                 Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
502                         getpid(), pid, WEXITSTATUS(waiter)))
503                 if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
504                         Debug(DPROC, (", dumped core"))
505                 Debug(DPROC, ("\n"))
506         }
507 }
508
509
510 static void
511 do_univ(u)
512         user    *u;
513 {
514 #if defined(sequent)
515 /* Dynix (Sequent) hack to put the user associated with
516  * the passed user structure into the ATT universe if
517  * necessary.  We have to dig the gecos info out of
518  * the user's password entry to see if the magic
519  * "universe(att)" string is present.
520  */
521
522         struct  passwd  *p;
523         char    *s;
524         int     i;
525
526         p = getpwuid(u->uid);
527         (void) endpwent();
528
529         if (p == NULL)
530                 return;
531
532         s = p->pw_gecos;
533
534         for (i = 0; i < 4; i++)
535         {
536                 if ((s = strchr(s, ',')) == NULL)
537                         return;
538                 s++;
539         }
540         if (strcmp(s, "universe(att)"))
541                 return;
542
543         (void) universe(U_ATT);
544 #endif
545 }