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