]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/cron/lib/entry.c
Add two missing eventhandler.h headers
[FreeBSD/FreeBSD.git] / usr.sbin / cron / lib / entry.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 /* vix 26jan87 [RCS'd; rest of log is in RCS file]
24  * vix 01jan87 [added line-level error recovery]
25  * vix 31dec86 [added /step to the from-to range, per bob@acornrc]
26  * vix 30dec86 [written]
27  */
28
29
30 #include "cron.h"
31 #include <grp.h>
32 #ifdef LOGIN_CAP
33 #include <login_cap.h>
34 #endif
35
36 typedef enum ecode {
37         e_none, e_minute, e_hour, e_dom, e_month, e_dow,
38         e_cmd, e_timespec, e_username, e_group, e_mem
39 #ifdef LOGIN_CAP
40         , e_class
41 #endif
42 } ecode_e;
43
44 static char     get_list(bitstr_t *, int, int, char *[], int, FILE *),
45                 get_range(bitstr_t *, int, int, char *[], int, FILE *),
46                 get_number(int *, int, char *[], int, FILE *);
47 static int      set_element(bitstr_t *, int, int, int);
48
49 static char *ecodes[] =
50         {
51                 "no error",
52                 "bad minute",
53                 "bad hour",
54                 "bad day-of-month",
55                 "bad month",
56                 "bad day-of-week",
57                 "bad command",
58                 "bad time specifier",
59                 "bad username",
60                 "bad group name",
61                 "out of memory",
62 #ifdef LOGIN_CAP
63                 "bad class name",
64 #endif
65         };
66
67
68 void
69 free_entry(e)
70         entry   *e;
71 {
72 #ifdef LOGIN_CAP
73         if (e->class != NULL)
74                 free(e->class);
75 #endif
76         if (e->cmd != NULL)
77                 free(e->cmd);
78         if (e->envp != NULL)
79                 env_free(e->envp);
80         free(e);
81 }
82
83
84 /* return NULL if eof or syntax error occurs;
85  * otherwise return a pointer to a new entry.
86  */
87 entry *
88 load_entry(file, error_func, pw, envp)
89         FILE            *file;
90         void            (*error_func)(char *);
91         struct passwd   *pw;
92         char            **envp;
93 {
94         /* this function reads one crontab entry -- the next -- from a file.
95          * it skips any leading blank lines, ignores comments, and returns
96          * EOF if for any reason the entry can't be read and parsed.
97          *
98          * the entry is also parsed here.
99          *
100          * syntax:
101          *   user crontab:
102          *      minutes hours doms months dows cmd\n
103          *   system crontab (/etc/crontab):
104          *      minutes hours doms months dows USERNAME cmd\n
105          */
106
107         ecode_e ecode = e_none;
108         entry   *e;
109         int     ch;
110         char    cmd[MAX_COMMAND];
111         char    envstr[MAX_ENVSTR];
112         char    **prev_env;
113
114         Debug(DPARS, ("load_entry()...about to eat comments\n"))
115
116         skip_comments(file);
117
118         ch = get_char(file);
119         if (ch == EOF)
120                 return NULL;
121
122         /* ch is now the first useful character of a useful line.
123          * it may be an @special or it may be the first character
124          * of a list of minutes.
125          */
126
127         e = (entry *) calloc(sizeof(entry), sizeof(char));
128
129         if (e == NULL) {
130                 warn("load_entry: calloc failed");
131                 return NULL;
132         }
133
134         if (ch == '@') {
135                 long interval;
136                 char *endptr;
137
138                 /* all of these should be flagged and load-limited; i.e.,
139                  * instead of @hourly meaning "0 * * * *" it should mean
140                  * "close to the front of every hour but not 'til the
141                  * system load is low".  Problems are: how do you know
142                  * what "low" means? (save me from /etc/cron.conf!) and:
143                  * how to guarantee low variance (how low is low?), which
144                  * means how to we run roughly every hour -- seems like
145                  * we need to keep a history or let the first hour set
146                  * the schedule, which means we aren't load-limited
147                  * anymore.  too much for my overloaded brain. (vix, jan90)
148                  * HINT
149                  */
150                 Debug(DPARS, ("load_entry()...about to test shortcuts\n"))
151                 ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
152                 if (!strcmp("reboot", cmd)) {
153                         Debug(DPARS, ("load_entry()...reboot shortcut\n"))
154                         e->flags |= WHEN_REBOOT;
155                 } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
156                         Debug(DPARS, ("load_entry()...yearly shortcut\n"))
157                         bit_set(e->second, 0);
158                         bit_set(e->minute, 0);
159                         bit_set(e->hour, 0);
160                         bit_set(e->dom, 0);
161                         bit_set(e->month, 0);
162                         bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
163                         e->flags |= DOW_STAR;
164                 } else if (!strcmp("monthly", cmd)) {
165                         Debug(DPARS, ("load_entry()...monthly shortcut\n"))
166                         bit_set(e->second, 0);
167                         bit_set(e->minute, 0);
168                         bit_set(e->hour, 0);
169                         bit_set(e->dom, 0);
170                         bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
171                         bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
172                         e->flags |= DOW_STAR;
173                 } else if (!strcmp("weekly", cmd)) {
174                         Debug(DPARS, ("load_entry()...weekly shortcut\n"))
175                         bit_set(e->second, 0);
176                         bit_set(e->minute, 0);
177                         bit_set(e->hour, 0);
178                         bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
179                         e->flags |= DOM_STAR;
180                         bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
181                         bit_set(e->dow, 0);
182                 } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
183                         Debug(DPARS, ("load_entry()...daily shortcut\n"))
184                         bit_set(e->second, 0);
185                         bit_set(e->minute, 0);
186                         bit_set(e->hour, 0);
187                         bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
188                         bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
189                         bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
190                 } else if (!strcmp("hourly", cmd)) {
191                         Debug(DPARS, ("load_entry()...hourly shortcut\n"))
192                         bit_set(e->second, 0);
193                         bit_set(e->minute, 0);
194                         bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
195                         bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
196                         bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
197                         bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
198                 } else if (!strcmp("every_minute", cmd)) {
199                         Debug(DPARS, ("load_entry()...every_minute shortcut\n"))
200                         bit_set(e->second, 0);
201                         bit_nset(e->minute, 0, (LAST_MINUTE-FIRST_MINUTE+1));
202                         bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
203                         bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
204                         bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
205                         bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
206                 } else if (!strcmp("every_second", cmd)) {
207                         Debug(DPARS, ("load_entry()...every_second shortcut\n"))
208                         e->flags |= SEC_RES;
209                         bit_nset(e->second, 0, (LAST_SECOND-FIRST_SECOND+1));
210                         bit_nset(e->minute, 0, (LAST_MINUTE-FIRST_MINUTE+1));
211                         bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
212                         bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
213                         bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
214                         bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
215                 } else if (*cmd != '\0' &&
216                     (interval = strtol(cmd, &endptr, 10)) > 0 &&
217                     *endptr == '\0') {
218                         Debug(DPARS, ("load_entry()... %ld seconds "
219                             "since last run\n", interval))
220                         e->interval = interval;
221                         e->flags = INTERVAL;
222                 } else {
223                         ecode = e_timespec;
224                         goto eof;
225                 }
226                 /* Advance past whitespace between shortcut and
227                  * username/command.
228                  */
229                 Skip_Blanks(ch, file);
230                 if (ch == EOF) {
231                         ecode = e_cmd;
232                         goto eof;
233                 }
234         } else {
235                 Debug(DPARS, ("load_entry()...about to parse numerics\n"))
236                 bit_set(e->second, 0);
237
238                 ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
239                               PPC_NULL, ch, file);
240                 if (ch == EOF) {
241                         ecode = e_minute;
242                         goto eof;
243                 }
244
245                 /* hours
246                  */
247
248                 ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
249                               PPC_NULL, ch, file);
250                 if (ch == EOF) {
251                         ecode = e_hour;
252                         goto eof;
253                 }
254
255                 /* DOM (days of month)
256                  */
257
258                 if (ch == '*')
259                         e->flags |= DOM_STAR;
260                 ch = get_list(e->dom, FIRST_DOM, LAST_DOM,
261                               PPC_NULL, ch, file);
262                 if (ch == EOF) {
263                         ecode = e_dom;
264                         goto eof;
265                 }
266
267                 /* month
268                  */
269
270                 ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
271                               MonthNames, ch, file);
272                 if (ch == EOF) {
273                         ecode = e_month;
274                         goto eof;
275                 }
276
277                 /* DOW (days of week)
278                  */
279
280                 if (ch == '*')
281                         e->flags |= DOW_STAR;
282                 ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
283                               DowNames, ch, file);
284                 if (ch == EOF) {
285                         ecode = e_dow;
286                         goto eof;
287                 }
288         }
289
290         /* make sundays equivalent */
291         if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
292                 bit_set(e->dow, 0);
293                 bit_set(e->dow, 7);
294         }
295
296         /* ch is the first character of a command, or a username */
297         unget_char(ch, file);
298
299         if (!pw) {
300                 char            *username = cmd;        /* temp buffer */
301                 char            *s;
302                 struct group    *grp;
303 #ifdef LOGIN_CAP
304                 login_cap_t *lc;
305 #endif
306
307                 Debug(DPARS, ("load_entry()...about to parse username\n"))
308                 ch = get_string(username, MAX_COMMAND, file, " \t");
309
310                 Debug(DPARS, ("load_entry()...got %s\n",username))
311                 if (ch == EOF) {
312                         ecode = e_cmd;
313                         goto eof;
314                 }
315
316 #ifdef LOGIN_CAP
317                 if ((s = strrchr(username, '/')) != NULL) {
318                         *s = '\0';
319                         e->class = strdup(s + 1);
320                         if (e->class == NULL)
321                                 warn("strdup(\"%s\")", s + 1);
322                 } else {
323                         e->class = strdup(RESOURCE_RC);
324                         if (e->class == NULL)
325                                 warn("strdup(\"%s\")", RESOURCE_RC);
326                 }
327                 if (e->class == NULL) {
328                         ecode = e_mem;
329                         goto eof;
330                 }
331                 if ((lc = login_getclass(e->class)) == NULL) {
332                         ecode = e_class;
333                         goto eof;
334                 }
335                 login_close(lc);
336 #endif
337                 grp = NULL;
338                 if ((s = strrchr(username, ':')) != NULL) {
339                         *s = '\0';
340                         if ((grp = getgrnam(s + 1)) == NULL) {
341                                 ecode = e_group;
342                                 goto eof;
343                         }
344                 }
345
346                 pw = getpwnam(username);
347                 if (pw == NULL) {
348                         ecode = e_username;
349                         goto eof;
350                 }
351                 if (grp != NULL)
352                         pw->pw_gid = grp->gr_gid;
353                 Debug(DPARS, ("load_entry()...uid %d, gid %d\n",pw->pw_uid,pw->pw_gid))
354 #ifdef LOGIN_CAP
355                 Debug(DPARS, ("load_entry()...class %s\n",e->class))
356 #endif
357         }
358
359 #ifndef PAM     /* PAM takes care of account expiration by itself */
360         if (pw->pw_expire && time(NULL) >= pw->pw_expire) {
361                 ecode = e_username;
362                 goto eof;
363         }
364 #endif /* !PAM */
365
366         e->uid = pw->pw_uid;
367         e->gid = pw->pw_gid;
368
369         /* copy and fix up environment.  some variables are just defaults and
370          * others are overrides.
371          */
372         e->envp = env_copy(envp);
373         if (e->envp == NULL) {
374                 warn("env_copy");
375                 ecode = e_mem;
376                 goto eof;
377         }
378         if (!env_get("SHELL", e->envp)) {
379                 prev_env = e->envp;
380                 sprintf(envstr, "SHELL=%s", _PATH_BSHELL);
381                 e->envp = env_set(e->envp, envstr);
382                 if (e->envp == NULL) {
383                         warn("env_set(%s)", envstr);
384                         env_free(prev_env);
385                         ecode = e_mem;
386                         goto eof;
387                 }
388         }
389         if (!env_get("HOME", e->envp)) {
390                 prev_env = e->envp;
391                 sprintf(envstr, "HOME=%s", pw->pw_dir);
392                 e->envp = env_set(e->envp, envstr);
393                 if (e->envp == NULL) {
394                         warn("env_set(%s)", envstr);
395                         env_free(prev_env);
396                         ecode = e_mem;
397                         goto eof;
398                 }
399         }
400         if (!env_get("PATH", e->envp)) {
401                 prev_env = e->envp;
402                 sprintf(envstr, "PATH=%s", _PATH_DEFPATH);
403                 e->envp = env_set(e->envp, envstr);
404                 if (e->envp == NULL) {
405                         warn("env_set(%s)", envstr);
406                         env_free(prev_env);
407                         ecode = e_mem;
408                         goto eof;
409                 }
410         }
411         prev_env = e->envp;
412         sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name);
413         e->envp = env_set(e->envp, envstr);
414         if (e->envp == NULL) {
415                 warn("env_set(%s)", envstr);
416                 env_free(prev_env);
417                 ecode = e_mem;
418                 goto eof;
419         }
420 #if defined(BSD)
421         prev_env = e->envp;
422         sprintf(envstr, "%s=%s", "USER", pw->pw_name);
423         e->envp = env_set(e->envp, envstr);
424         if (e->envp == NULL) {
425                 warn("env_set(%s)", envstr);
426                 env_free(prev_env);
427                 ecode = e_mem;
428                 goto eof;
429         }
430 #endif
431
432         Debug(DPARS, ("load_entry()...about to parse command\n"))
433
434         /* Everything up to the next \n or EOF is part of the command...
435          * too bad we don't know in advance how long it will be, since we
436          * need to malloc a string for it... so, we limit it to MAX_COMMAND.
437          * XXX - should use realloc().
438          */
439         ch = get_string(cmd, MAX_COMMAND, file, "\n");
440
441         /* a file without a \n before the EOF is rude, so we'll complain...
442          */
443         if (ch == EOF) {
444                 ecode = e_cmd;
445                 goto eof;
446         }
447
448         /* got the command in the 'cmd' string; save it in *e.
449          */
450         e->cmd = strdup(cmd);
451         if (e->cmd == NULL) {
452                 warn("strdup(\"%s\")", cmd);
453                 ecode = e_mem;
454                 goto eof;
455         }
456         Debug(DPARS, ("load_entry()...returning successfully\n"))
457
458         /* success, fini, return pointer to the entry we just created...
459          */
460         return e;
461
462  eof:
463         free_entry(e);
464         if (ecode != e_none && error_func)
465                 (*error_func)(ecodes[(int)ecode]);
466         while (ch != EOF && ch != '\n')
467                 ch = get_char(file);
468         return NULL;
469 }
470
471
472 static char
473 get_list(bits, low, high, names, ch, file)
474         bitstr_t        *bits;          /* one bit per flag, default=FALSE */
475         int             low, high;      /* bounds, impl. offset for bitstr */
476         char            *names[];       /* NULL or *[] of names for these elements */
477         int             ch;             /* current character being processed */
478         FILE            *file;          /* file being read */
479 {
480         register int    done;
481
482         /* we know that we point to a non-blank character here;
483          * must do a Skip_Blanks before we exit, so that the
484          * next call (or the code that picks up the cmd) can
485          * assume the same thing.
486          */
487
488         Debug(DPARS|DEXT, ("get_list()...entered\n"))
489
490         /* list = range {"," range}
491          */
492
493         /* clear the bit string, since the default is 'off'.
494          */
495         bit_nclear(bits, 0, (high-low+1));
496
497         /* process all ranges
498          */
499         done = FALSE;
500         while (!done) {
501                 ch = get_range(bits, low, high, names, ch, file);
502                 if (ch == ',')
503                         ch = get_char(file);
504                 else
505                         done = TRUE;
506         }
507
508         /* exiting.  skip to some blanks, then skip over the blanks.
509          */
510         Skip_Nonblanks(ch, file)
511         Skip_Blanks(ch, file)
512
513         Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
514
515         return ch;
516 }
517
518
519 static char
520 get_range(bits, low, high, names, ch, file)
521         bitstr_t        *bits;          /* one bit per flag, default=FALSE */
522         int             low, high;      /* bounds, impl. offset for bitstr */
523         char            *names[];       /* NULL or names of elements */
524         int             ch;             /* current character being processed */
525         FILE            *file;          /* file being read */
526 {
527         /* range = number | number "-" number [ "/" number ]
528          */
529
530         register int    i;
531         auto int        num1, num2, num3;
532
533         Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
534
535         if (ch == '*') {
536                 /* '*' means "first-last" but can still be modified by /step
537                  */
538                 num1 = low;
539                 num2 = high;
540                 ch = get_char(file);
541                 if (ch == EOF)
542                         return EOF;
543         } else {
544                 if (EOF == (ch = get_number(&num1, low, names, ch, file)))
545                         return EOF;
546
547                 if (ch == '/')
548                         num2 = high;
549                 else if (ch != '-') {
550                         /* not a range, it's a single number.
551                          */
552                         if (EOF == set_element(bits, low, high, num1))
553                                 return EOF;
554                         return ch;
555                 } else {
556                         /* eat the dash
557                          */
558                         ch = get_char(file);
559                         if (ch == EOF)
560                                 return EOF;
561
562                         /* get the number following the dash
563                          */
564                         ch = get_number(&num2, low, names, ch, file);
565                         if (ch == EOF)
566                                 return EOF;
567                 }
568         }
569
570         /* check for step size
571          */
572         if (ch == '/') {
573                 /* eat the slash
574                  */
575                 ch = get_char(file);
576                 if (ch == EOF)
577                         return EOF;
578
579                 /* get the step size -- note: we don't pass the
580                  * names here, because the number is not an
581                  * element id, it's a step size.  'low' is
582                  * sent as a 0 since there is no offset either.
583                  */
584                 ch = get_number(&num3, 0, PPC_NULL, ch, file);
585                 if (ch == EOF || num3 == 0)
586                         return EOF;
587         } else {
588                 /* no step.  default==1.
589                  */
590                 num3 = 1;
591         }
592
593         /* range. set all elements from num1 to num2, stepping
594          * by num3.  (the step is a downward-compatible extension
595          * proposed conceptually by bob@acornrc, syntactically
596          * designed then implmented by paul vixie).
597          */
598         for (i = num1;  i <= num2;  i += num3)
599                 if (EOF == set_element(bits, low, high, i))
600                         return EOF;
601
602         return ch;
603 }
604
605
606 static char
607 get_number(numptr, low, names, ch, file)
608         int     *numptr;        /* where does the result go? */
609         int     low;            /* offset applied to result if symbolic enum used */
610         char    *names[];       /* symbolic names, if any, for enums */
611         int     ch;             /* current character */
612         FILE    *file;          /* source */
613 {
614         char    temp[MAX_TEMPSTR], *pc;
615         int     len, i, all_digits;
616
617         /* collect alphanumerics into our fixed-size temp array
618          */
619         pc = temp;
620         len = 0;
621         all_digits = TRUE;
622         while (isalnum(ch)) {
623                 if (++len >= MAX_TEMPSTR)
624                         return EOF;
625
626                 *pc++ = ch;
627
628                 if (!isdigit(ch))
629                         all_digits = FALSE;
630
631                 ch = get_char(file);
632         }
633         *pc = '\0';
634         if (len == 0)
635             return (EOF);
636
637         /* try to find the name in the name list
638          */
639         if (names) {
640                 for (i = 0;  names[i] != NULL;  i++) {
641                         Debug(DPARS|DEXT,
642                                 ("get_num, compare(%s,%s)\n", names[i], temp))
643                         if (!strcasecmp(names[i], temp)) {
644                                 *numptr = i+low;
645                                 return ch;
646                         }
647                 }
648         }
649
650         /* no name list specified, or there is one and our string isn't
651          * in it.  either way: if it's all digits, use its magnitude.
652          * otherwise, it's an error.
653          */
654         if (all_digits) {
655                 *numptr = atoi(temp);
656                 return ch;
657         }
658
659         return EOF;
660 }
661
662
663 static int
664 set_element(bits, low, high, number)
665         bitstr_t        *bits;          /* one bit per flag, default=FALSE */
666         int             low;
667         int             high;
668         int             number;
669 {
670         Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
671
672         if (number < low || number > high)
673                 return EOF;
674
675         bit_set(bits, (number-low));
676         return OK;
677 }