root/src/entry.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. free_entry
  2. load_entry
  3. get_list
  4. get_range
  5. get_number
  6. set_element

   1 /*
   2  * Copyright 1988,1990,1993,1994 by Paul Vixie
   3  * All rights reserved
   4  */
   5 
   6 /*
   7  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
   8  * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
   9  *
  10  * Permission to use, copy, modify, and distribute this software for any
  11  * purpose with or without fee is hereby granted, provided that the above
  12  * copyright notice and this permission notice appear in all copies.
  13  *
  14  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
  15  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  16  * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
  17  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  18  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  19  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  20  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  21  */
  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 #include "config.h"
  30 
  31 #include <ctype.h>
  32 #include <pwd.h>
  33 #include <stdio.h>
  34 #include <stdlib.h>
  35 #include <string.h>
  36 #include <sys/types.h>
  37 #include <unistd.h>
  38 #include <errno.h>
  39 
  40 #include "bitstring.h"
  41 #include "funcs.h"
  42 #include "globals.h"
  43 #include "macros.h"
  44 #include "pathnames.h"
  45 
  46 typedef enum ecode {
  47         e_none, e_minute, e_hour, e_dom, e_month, e_dow,
  48         e_cmd, e_timespec, e_username, e_option, e_memory
  49 } ecode_e;
  50 
  51 static const char *ecodes[] = {
  52         "no error",
  53         "bad minute",
  54         "bad hour",
  55         "bad day-of-month",
  56         "bad month",
  57         "bad day-of-week",
  58         "bad command",
  59         "bad time specifier",
  60         "bad username",
  61         "bad option",
  62         "out of memory"
  63 };
  64 
  65 static int get_list(bitstr_t *, int, int, const char *[], int, FILE *),
  66 get_range(bitstr_t *, int, int, const char *[], int, FILE *),
  67 get_number(int *, int, const char *[], int, FILE *, const char *),
  68 set_element(bitstr_t *, int, int, int);
  69 
  70 void free_entry(entry * e) {
     /* [previous][next][first][last][top][bottom][index][help]  */
  71         free(e->cmd);
  72         free(e->pwd);
  73         env_free(e->envp);
  74         free(e);
  75 }
  76 
  77 /* return NULL if eof or syntax error occurs;
  78  * otherwise return a pointer to a new entry.
  79  */
  80 entry *load_entry(FILE * file, void (*error_func) (), struct passwd *pw,
     /* [previous][next][first][last][top][bottom][index][help]  */
  81         char **envp) {
  82         /* this function reads one crontab entry -- the next -- from a file.
  83          * it skips any leading blank lines, ignores comments, and returns
  84          * NULL if for any reason the entry can't be read and parsed.
  85          *
  86          * the entry is also parsed here.
  87          *
  88          * syntax:
  89          *   user crontab:
  90          *  minutes hours doms months dows cmd\n
  91          *   system crontab (/etc/crontab):
  92          *  minutes hours doms months dows USERNAME cmd\n
  93          */
  94 
  95         ecode_e ecode = e_none;
  96         entry *e;
  97         int ch;
  98         char cmd[MAX_COMMAND];
  99         char envstr[MAX_ENVSTR];
 100         char **tenvp;
 101         char *p;
 102         struct passwd temppw;
 103 
 104         Debug(DPARS, ("load_entry()...about to eat comments\n"));
 105 
 106         skip_comments(file);
 107 
 108         ch = get_char(file);
 109         if (ch == EOF)
 110                 return (NULL);
 111 
 112         /* ch is now the first useful character of a useful line.
 113          * it may be an @special or it may be the first character
 114          * of a list of minutes.
 115          */
 116 
 117         e = (entry *) calloc(sizeof (entry), sizeof (char));
 118 
 119         /* check for '-' as a first character, this option will disable 
 120         * writing a syslog message about command getting executed
 121         */
 122         if (ch == '-') {
 123         /* if we are editing system crontab or user uid is 0 (root) 
 124         * we are allowed to disable logging 
 125         */
 126                 if (pw == NULL || pw->pw_uid == 0)
 127                         e->flags |= DONT_LOG;
 128                 else {
 129                         log_it("CRON", getpid(), "ERROR", "Only privileged user can disable logging", 0);
 130                         ecode = e_option;
 131                         goto eof;
 132                 }
 133                 ch = get_char(file);
 134                 if (ch == EOF) {
 135                         free(e);
 136                         return NULL;
 137                 }
 138         }
 139 
 140         if (ch == '@') {
 141                 /* all of these should be flagged and load-limited; i.e.,
 142                  * instead of @hourly meaning "0 * * * *" it should mean
 143                  * "close to the front of every hour but not 'til the
 144                  * system load is low".  Problems are: how do you know
 145                  * what "low" means? (save me from /etc/cron.conf!) and:
 146                  * how to guarantee low variance (how low is low?), which
 147                  * means how to we run roughly every hour -- seems like
 148                  * we need to keep a history or let the first hour set
 149                  * the schedule, which means we aren't load-limited
 150                  * anymore.  too much for my overloaded brain. (vix, jan90)
 151                  * HINT
 152                  */
 153                 ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
 154                 if (!strcmp("reboot", cmd)) {
 155                         e->flags |= WHEN_REBOOT;
 156                 }
 157                 else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)) {
 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);
 163                         e->flags |= DOW_STAR;
 164                 }
 165                 else if (!strcmp("monthly", cmd)) {
 166                         bit_set(e->minute, 0);
 167                         bit_set(e->hour, 0);
 168                         bit_set(e->dom, 0);
 169                         bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH);
 170                         bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW);
 171                         e->flags |= DOW_STAR;
 172                 }
 173                 else if (!strcmp("weekly", cmd)) {
 174                         bit_set(e->minute, 0);
 175                         bit_set(e->hour, 0);
 176                         bit_nset(e->dom, 0, LAST_DOM - FIRST_DOM);
 177                         bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH);
 178                         bit_set(e->dow, 0);
 179                         e->flags |= DOW_STAR;
 180                 }
 181                 else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
 182                         bit_set(e->minute, 0);
 183                         bit_set(e->hour, 0);
 184                         bit_nset(e->dom, 0, LAST_DOM - FIRST_DOM);
 185                         bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH);
 186                         bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW);
 187                 }
 188                 else if (!strcmp("hourly", cmd)) {
 189                         bit_set(e->minute, 0);
 190                         bit_nset(e->hour, 0, LAST_HOUR - FIRST_HOUR);
 191                         bit_nset(e->dom, 0, LAST_DOM - FIRST_DOM);
 192                         bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH);
 193                         bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW);
 194                         e->flags |= HR_STAR;
 195                 }
 196                 else {
 197                         ecode = e_timespec;
 198                         goto eof;
 199                 }
 200                 /* Advance past whitespace between shortcut and
 201                  * username/command.
 202                  */
 203                 Skip_Blanks(ch, file);
 204                 if (ch == EOF || ch == '\n') {
 205                         ecode = e_cmd;
 206                         goto eof;
 207                 }
 208         }
 209         else {
 210                 Debug(DPARS, ("load_entry()...about to parse numerics\n"));
 211 
 212                 if (ch == '*')
 213                         e->flags |= MIN_STAR;
 214                 ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, PPC_NULL, ch, file);
 215                 if (ch == EOF) {
 216                         ecode = e_minute;
 217                         goto eof;
 218                 }
 219 
 220                 /* hours
 221                  */
 222 
 223                 if (ch == '*')
 224                         e->flags |= HR_STAR;
 225                 ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, PPC_NULL, ch, file);
 226                 if (ch == EOF) {
 227                         ecode = e_hour;
 228                         goto eof;
 229                 }
 230 
 231                 /* DOM (days of month)
 232                  */
 233 
 234                 if (ch == '*')
 235                         e->flags |= DOM_STAR;
 236                 ch = get_list(e->dom, FIRST_DOM, LAST_DOM, PPC_NULL, ch, file);
 237                 if (ch == EOF) {
 238                         ecode = e_dom;
 239                         goto eof;
 240                 }
 241 
 242                 /* month
 243                  */
 244 
 245                 ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, MonthNames, ch, file);
 246                 if (ch == EOF) {
 247                         ecode = e_month;
 248                         goto eof;
 249                 }
 250 
 251                 /* DOW (days of week)
 252                  */
 253 
 254                 if (ch == '*')
 255                         e->flags |= DOW_STAR;
 256                 ch = get_list(e->dow, FIRST_DOW, LAST_DOW, DowNames, ch, file);
 257                 if (ch == EOF) {
 258                         ecode = e_dow;
 259                         goto eof;
 260                 }
 261         }
 262 
 263         /* make sundays equivalent */
 264         if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
 265                 bit_set(e->dow, 0);
 266                 bit_set(e->dow, 7);
 267         }
 268 
 269         /* check for permature EOL and catch a common typo */
 270         if (ch == '\n' || ch == '*') {
 271                 ecode = e_cmd;
 272                 goto eof;
 273         }
 274 
 275         /* ch is the first character of a command, or a username */
 276         unget_char(ch, file);
 277 
 278         if (!pw) {
 279                 char *username = cmd;   /* temp buffer */
 280 
 281                 Debug(DPARS, ("load_entry()...about to parse username\n"));
 282                 ch = get_string(username, MAX_COMMAND, file, " \t\n");
 283 
 284                 Debug(DPARS, ("load_entry()...got %s\n", username));
 285                 if (ch == EOF || ch == '\n' || ch == '*') {
 286                         ecode = e_cmd;
 287                         goto eof;
 288                 }
 289 
 290                 pw = getpwnam(username);
 291                 if (pw == NULL) {
 292                         Debug(DPARS, ("load_entry()...unknown user entry\n"));
 293                         memset(&temppw, 0, sizeof (temppw));
 294                         temppw.pw_name = username;
 295                         temppw.pw_passwd = "";
 296                         pw = &temppw;
 297                 } else {
 298                         Debug(DPARS, ("load_entry()...uid %ld, gid %ld\n",
 299                                 (long) pw->pw_uid, (long) pw->pw_gid));
 300                 }
 301         }
 302 
 303         if ((e->pwd = pw_dup(pw)) == NULL) {
 304                 ecode = e_memory;
 305                 goto eof;
 306         }
 307         memset(e->pwd->pw_passwd, 0, strlen(e->pwd->pw_passwd));
 308 
 309         p = env_get("RANDOM_DELAY", envp);
 310         if (p) {
 311                 char *endptr;
 312                 long val;
 313 
 314                 errno = 0;    /* To distinguish success/failure after call */
 315                 val = strtol(p, &endptr, 10);
 316                 if (errno != 0 || val < 0 || val > 24*60) {
 317                         log_it("CRON", getpid(), "ERROR", "bad value of RANDOM_DELAY", 0);
 318                 } else {
 319                         e->delay = (int)((double)val * RandomScale);
 320                 }
 321         }
 322 
 323         /* copy and fix up environment.  some variables are just defaults and
 324          * others are overrides.
 325          */
 326         if ((e->envp = env_copy(envp)) == NULL) {
 327                 ecode = e_memory;
 328                 goto eof;
 329         }
 330         if (!env_get("SHELL", e->envp)) {
 331                 if (glue_strings(envstr, sizeof envstr, "SHELL", _PATH_BSHELL, '=')) {
 332                         if ((tenvp = env_set(e->envp, envstr)) == NULL) {
 333                                 ecode = e_memory;
 334                                 goto eof;
 335                         }
 336                         e->envp = tenvp;
 337                 }
 338                 else
 339                         log_it("CRON", getpid(), "ERROR", "can't set SHELL", 0);
 340         }
 341         if ((tenvp = env_update_home(e->envp, pw->pw_dir)) == NULL) {
 342                 ecode = e_memory;
 343                 goto eof;
 344         }
 345         e->envp = tenvp;
 346 #ifndef LOGIN_CAP
 347         /* If login.conf is in used we will get the default PATH later. */
 348         if (!env_get("PATH", e->envp)) {
 349                 char *defpath;
 350 
 351                 if (ChangePath)
 352                         defpath = _PATH_DEFPATH;
 353                 else {
 354                         defpath = getenv("PATH");
 355                         if (defpath == NULL)
 356                                 defpath = _PATH_DEFPATH;
 357                 }
 358 
 359                 if (glue_strings(envstr, sizeof envstr, "PATH", defpath, '=')) {
 360                         if ((tenvp = env_set(e->envp, envstr)) == NULL) {
 361                                 ecode = e_memory;
 362                                 goto eof;
 363                         }
 364                         e->envp = tenvp;
 365                 }
 366                 else
 367                         log_it("CRON", getpid(), "ERROR", "can't set PATH", 0);
 368         }
 369 #endif /* LOGIN_CAP */
 370         if (glue_strings(envstr, sizeof envstr, "LOGNAME", pw->pw_name, '=')) {
 371                 if ((tenvp = env_set(e->envp, envstr)) == NULL) {
 372                         ecode = e_memory;
 373                         goto eof;
 374                 }
 375                 e->envp = tenvp;
 376         }
 377         else
 378                 log_it("CRON", getpid(), "ERROR", "can't set LOGNAME", 0);
 379 #if defined(BSD) || defined(__linux)
 380         if (glue_strings(envstr, sizeof envstr, "USER", pw->pw_name, '=')) {
 381                 if ((tenvp = env_set(e->envp, envstr)) == NULL) {
 382                         ecode = e_memory;
 383                         goto eof;
 384                 }
 385                 e->envp = tenvp;
 386         }
 387         else
 388                 log_it("CRON", getpid(), "ERROR", "can't set USER", 0);
 389 #endif
 390 
 391         Debug(DPARS, ("load_entry()...about to parse command\n"));
 392 
 393         /* Everything up to the next \n or EOF is part of the command...
 394          * too bad we don't know in advance how long it will be, since we
 395          * need to malloc a string for it... so, we limit it to MAX_COMMAND.
 396          */
 397         ch = get_string(cmd, MAX_COMMAND, file, "\n");
 398 
 399         /* a file without a \n before the EOF is rude, so we'll complain...
 400          */
 401         if (ch == EOF) {
 402                 ecode = e_cmd;
 403                 goto eof;
 404         }
 405 
 406         /* got the command in the 'cmd' string; save it in *e.
 407          */
 408         if ((e->cmd = strdup(cmd)) == NULL) {
 409                 ecode = e_memory;
 410                 goto eof;
 411         }
 412 
 413         Debug(DPARS, ("load_entry()...returning successfully\n"));
 414 
 415                 /* success, fini, return pointer to the entry we just created...
 416                  */
 417                 return (e);
 418 
 419   eof:
 420         if (e->envp)
 421                 env_free(e->envp);
 422         free(e->pwd);
 423         free(e->cmd);
 424         free(e);
 425         while (ch != '\n' && !feof(file))
 426                 ch = get_char(file);
 427         if (ecode != e_none && error_func)
 428                 (*error_func) (ecodes[(int) ecode]);
 429         return (NULL);
 430 }
 431 
 432 static int
 433 get_list(bitstr_t * bits, int low, int high, const char *names[],
     /* [previous][next][first][last][top][bottom][index][help]  */
 434         int ch, FILE * file) {
 435         int done;
 436 
 437         /* we know that we point to a non-blank character here;
 438          * must do a Skip_Blanks before we exit, so that the
 439          * next call (or the code that picks up the cmd) can
 440          * assume the same thing.
 441          */
 442 
 443         Debug(DPARS | DEXT, ("get_list()...entered\n"));
 444 
 445         /* list = range {"," range}
 446          */
 447         /* clear the bit string, since the default is 'off'.
 448          */
 449         bit_nclear(bits, 0, (high - low));
 450 
 451         /* process all ranges
 452          */
 453         done = FALSE;
 454         while (!done) {
 455                 if (EOF == (ch = get_range(bits, low, high, names, ch, file)))
 456                         return (EOF);
 457                 if (ch == ',')
 458                         ch = get_char(file);
 459                 else
 460                         done = TRUE;
 461         }
 462 
 463         /* exiting.  skip to some blanks, then skip over the blanks.
 464          */
 465         Skip_Nonblanks(ch, file)
 466         Skip_Blanks(ch, file)
 467 
 468         Debug(DPARS | DEXT, ("get_list()...exiting w/ %02x\n", ch));
 469 
 470         return (ch);
 471 }
 472 
 473 
 474 static int
 475 get_range(bitstr_t * bits, int low, int high, const char *names[],
     /* [previous][next][first][last][top][bottom][index][help]  */
 476         int ch, FILE * file) {
 477         /* range = number | number "-" number [ "/" number ]
 478          */
 479 
 480         int i, num1, num2, num3;
 481 
 482         Debug(DPARS | DEXT, ("get_range()...entering, exit won't show\n"));
 483 
 484                 if (ch == '*') {
 485                 /* '*' means "first-last" but can still be modified by /step
 486                  */
 487                 num1 = low;
 488                 num2 = high;
 489                 ch = get_char(file);
 490                 if (ch == EOF)
 491                         return (EOF);
 492         }
 493         else {
 494                 ch = get_number(&num1, low, names, ch, file, ",- \t\n");
 495                 if (ch == EOF)
 496                         return (EOF);
 497 
 498                 if (ch != '-') {
 499                         /* not a range, it's a single number.
 500                          */
 501                         if (EOF == set_element(bits, low, high, num1)) {
 502                                 unget_char(ch, file);
 503                                 return (EOF);
 504                         }
 505                         return (ch);
 506                 }
 507                 else {
 508                         /* eat the dash
 509                          */
 510                         ch = get_char(file);
 511                         if (ch == EOF)
 512                                 return (EOF);
 513 
 514                         /* get the number following the dash
 515                          */
 516                         ch = get_number(&num2, low, names, ch, file, "/, \t\n");
 517                         if (ch == EOF || num1 > num2)
 518                                 return (EOF);
 519                 }
 520         }
 521 
 522         /* check for step size
 523          */
 524         if (ch == '/') {
 525                 /* eat the slash
 526                  */
 527                 ch = get_char(file);
 528                 if (ch == EOF)
 529                         return (EOF);
 530 
 531                 /* get the step size -- note: we don't pass the
 532                  * names here, because the number is not an
 533                  * element id, it's a step size.  'low' is
 534                  * sent as a 0 since there is no offset either.
 535                  */
 536                 ch = get_number(&num3, 0, PPC_NULL, ch, file, ", \t\n");
 537                 if (ch == EOF || num3 == 0)
 538                         return (EOF);
 539         }
 540         else {
 541                 /* no step.  default==1.
 542                  */
 543                 num3 = 1;
 544         }
 545 
 546         /* range. set all elements from num1 to num2, stepping
 547          * by num3.  (the step is a downward-compatible extension
 548          * proposed conceptually by bob@acornrc, syntactically
 549          * designed then implemented by paul vixie).
 550          */
 551         for (i = num1; i <= num2; i += num3)
 552                 if (EOF == set_element(bits, low, high, i)) {
 553                         unget_char(ch, file);
 554                         return (EOF);
 555                 }
 556 
 557         return (ch);
 558 }
 559 
 560 static int
 561 get_number(int *numptr, int low, const char *names[], int ch, FILE * file,
     /* [previous][next][first][last][top][bottom][index][help]  */
 562         const char *terms) {
 563         char temp[MAX_TEMPSTR], *pc;
 564         int len, i;
 565 
 566         pc = temp;
 567         len = 0;
 568 
 569         /* first look for a number */
 570         while (isdigit((unsigned char) ch)) {
 571                 if (++len >= MAX_TEMPSTR)
 572                         goto bad;
 573                 *pc++ = (char)ch;
 574                 ch = get_char(file);
 575         }
 576         *pc = '\0';
 577         if (len != 0) {
 578                 /* got a number, check for valid terminator */
 579                 if (!strchr(terms, ch))
 580                         goto bad;
 581                 *numptr = atoi(temp);
 582                 return (ch);
 583         }
 584 
 585         /* no numbers, look for a string if we have any */
 586         if (names) {
 587                 while (isalpha((unsigned char) ch)) {
 588                         if (++len >= MAX_TEMPSTR)
 589                                 goto bad;
 590                         *pc++ = (char)ch;
 591                         ch = get_char(file);
 592                 }
 593                 *pc = '\0';
 594                 if (len != 0 && strchr(terms, ch)) {
 595                         for (i = 0; names[i] != NULL; i++) {
 596                                 Debug(DPARS | DEXT,
 597                                         ("get_num, compare(%s,%s)\n", names[i], temp));
 598                                         if (!strcasecmp(names[i], temp)) {
 599                                         *numptr = i + low;
 600                                         return (ch);
 601                                 }
 602                         }
 603                 }
 604         }
 605 
 606   bad:
 607         unget_char(ch, file);
 608         return (EOF);
 609 }
 610 
 611 static int set_element(bitstr_t * bits, int low, int high, int number) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 612         Debug(DPARS | DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number));
 613 
 614                 if (number < low || number > high)
 615                 return (EOF);
 616 
 617         bit_set(bits, (number - low));
 618         return (OK);
 619 }

/* [previous][next][first][last][top][bottom][index][help]  */