1cd115652Schristos /* $OpenBSD: entry.c,v 1.51 2020/04/16 17:51:56 millert Exp $ */ 2032a4398Schristos 30061c6a5Schristos /* 40061c6a5Schristos * Copyright 1988,1990,1993,1994 by Paul Vixie 50061c6a5Schristos * All rights reserved 60061c6a5Schristos */ 70061c6a5Schristos 80061c6a5Schristos /* 90061c6a5Schristos * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") 100061c6a5Schristos * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. 110061c6a5Schristos * 120061c6a5Schristos * Permission to use, copy, modify, and distribute this software for any 130061c6a5Schristos * purpose with or without fee is hereby granted, provided that the above 140061c6a5Schristos * copyright notice and this permission notice appear in all copies. 150061c6a5Schristos * 160061c6a5Schristos * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES 170061c6a5Schristos * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 180061c6a5Schristos * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR 190061c6a5Schristos * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 200061c6a5Schristos * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 210061c6a5Schristos * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 220061c6a5Schristos * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 230061c6a5Schristos */ 24032a4398Schristos #include <sys/cdefs.h> 250061c6a5Schristos #if !defined(lint) && !defined(LINT) 26032a4398Schristos #if 0 270061c6a5Schristos static char rcsid[] = "Id: entry.c,v 1.17 2004/01/23 18:56:42 vixie Exp"; 28032a4398Schristos #else 29*493e0895Schristos __RCSID("$NetBSD: entry.c,v 1.13 2024/08/19 23:50:23 christos Exp $"); 30032a4398Schristos #endif 310061c6a5Schristos #endif 320061c6a5Schristos 330061c6a5Schristos /* vix 26jan87 [RCS'd; rest of log is in RCS file] 340061c6a5Schristos * vix 01jan87 [added line-level error recovery] 350061c6a5Schristos * vix 31dec86 [added /step to the from-to range, per bob@acornrc] 360061c6a5Schristos * vix 30dec86 [written] 370061c6a5Schristos */ 380061c6a5Schristos 390061c6a5Schristos #include "cron.h" 400061c6a5Schristos 410061c6a5Schristos typedef enum ecode { 420061c6a5Schristos e_none, e_minute, e_hour, e_dom, e_month, e_dow, 430061c6a5Schristos e_cmd, e_timespec, e_username, e_option, e_memory 440061c6a5Schristos } ecode_e; 450061c6a5Schristos 46032a4398Schristos static const char * const ecodes[] = 470061c6a5Schristos { 480061c6a5Schristos "no error", 490061c6a5Schristos "bad minute", 500061c6a5Schristos "bad hour", 510061c6a5Schristos "bad day-of-month", 520061c6a5Schristos "bad month", 530061c6a5Schristos "bad day-of-week", 540061c6a5Schristos "bad command", 550061c6a5Schristos "bad time specifier", 560061c6a5Schristos "bad username", 570061c6a5Schristos "bad option", 580061c6a5Schristos "out of memory" 590061c6a5Schristos }; 600061c6a5Schristos 61032a4398Schristos static int get_list(bitstr_t *, int, int, const char * const [], int, FILE *), 62032a4398Schristos get_range(bitstr_t *, int, int, const char * const [], int, FILE *), 63032a4398Schristos get_number(int *, int, const char * const [], int, FILE *, const char *), 640061c6a5Schristos set_element(bitstr_t *, int, int, int); 650061c6a5Schristos 660061c6a5Schristos void 670061c6a5Schristos free_entry(entry *e) { 680061c6a5Schristos free(e->cmd); 690061c6a5Schristos free(e->pwd); 700061c6a5Schristos env_free(e->envp); 710061c6a5Schristos free(e); 720061c6a5Schristos } 730061c6a5Schristos 740061c6a5Schristos /* return NULL if eof or syntax error occurs; 750061c6a5Schristos * otherwise return a pointer to a new entry. 760061c6a5Schristos */ 770061c6a5Schristos entry * 78032a4398Schristos load_entry(FILE *file, void (*error_func)(const char *), struct passwd *pw, 79032a4398Schristos char **envp) { 800061c6a5Schristos /* this function reads one crontab entry -- the next -- from a file. 810061c6a5Schristos * it skips any leading blank lines, ignores comments, and returns 820061c6a5Schristos * NULL if for any reason the entry can't be read and parsed. 830061c6a5Schristos * 840061c6a5Schristos * the entry is also parsed here. 850061c6a5Schristos * 860061c6a5Schristos * syntax: 870061c6a5Schristos * user crontab: 880061c6a5Schristos * minutes hours doms months dows cmd\n 890061c6a5Schristos * system crontab (/etc/crontab): 900061c6a5Schristos * minutes hours doms months dows USERNAME cmd\n 910061c6a5Schristos */ 920061c6a5Schristos 930061c6a5Schristos ecode_e ecode = e_none; 940061c6a5Schristos entry *e; 950061c6a5Schristos int ch; 960061c6a5Schristos char cmd[MAX_COMMAND]; 970061c6a5Schristos char envstr[MAX_ENVSTR]; 980061c6a5Schristos char **tenvp; 990061c6a5Schristos 100032a4398Schristos Debug(DPARS, ("load_entry()...about to eat comments\n")); 1010061c6a5Schristos 1020061c6a5Schristos skip_comments(file); 1030061c6a5Schristos 1040061c6a5Schristos ch = get_char(file); 1050061c6a5Schristos if (ch == EOF) 1060061c6a5Schristos return (NULL); 1070061c6a5Schristos 1080061c6a5Schristos /* ch is now the first useful character of a useful line. 1090061c6a5Schristos * it may be an @special or it may be the first character 1100061c6a5Schristos * of a list of minutes. 1110061c6a5Schristos */ 1120061c6a5Schristos 1137035a6c5Schristos e = calloc(sizeof(*e), sizeof(char)); 1140061c6a5Schristos 1150061c6a5Schristos if (ch == '@') { 1160061c6a5Schristos /* all of these should be flagged and load-limited; i.e., 1170061c6a5Schristos * instead of @hourly meaning "0 * * * *" it should mean 1180061c6a5Schristos * "close to the front of every hour but not 'til the 1190061c6a5Schristos * system load is low". Problems are: how do you know 1200061c6a5Schristos * what "low" means? (save me from /etc/cron.conf!) and: 1210061c6a5Schristos * how to guarantee low variance (how low is low?), which 1220061c6a5Schristos * means how to we run roughly every hour -- seems like 1230061c6a5Schristos * we need to keep a history or let the first hour set 1240061c6a5Schristos * the schedule, which means we aren't load-limited 1250061c6a5Schristos * anymore. too much for my overloaded brain. (vix, jan90) 1260061c6a5Schristos * HINT 1270061c6a5Schristos */ 1280061c6a5Schristos ch = get_string(cmd, MAX_COMMAND, file, " \t\n"); 1290061c6a5Schristos if (!strcmp("reboot", cmd)) { 1300061c6a5Schristos e->flags |= WHEN_REBOOT; 1310061c6a5Schristos } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){ 1320061c6a5Schristos bit_set(e->minute, 0); 1330061c6a5Schristos bit_set(e->hour, 0); 1340061c6a5Schristos bit_set(e->dom, 0); 1350061c6a5Schristos bit_set(e->month, 0); 1360061c6a5Schristos bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 1370061c6a5Schristos e->flags |= DOW_STAR; 1380061c6a5Schristos } else if (!strcmp("monthly", cmd)) { 1390061c6a5Schristos bit_set(e->minute, 0); 1400061c6a5Schristos bit_set(e->hour, 0); 1410061c6a5Schristos bit_set(e->dom, 0); 1420061c6a5Schristos bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 1430061c6a5Schristos bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 1440061c6a5Schristos e->flags |= DOW_STAR; 1450061c6a5Schristos } else if (!strcmp("weekly", cmd)) { 1460061c6a5Schristos bit_set(e->minute, 0); 1470061c6a5Schristos bit_set(e->hour, 0); 1480061c6a5Schristos bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 1490061c6a5Schristos bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 1500061c6a5Schristos bit_set(e->dow, 0); 151032a4398Schristos e->flags |= DOM_STAR; 1520061c6a5Schristos } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) { 1530061c6a5Schristos bit_set(e->minute, 0); 1540061c6a5Schristos bit_set(e->hour, 0); 1550061c6a5Schristos bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 1560061c6a5Schristos bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 1570061c6a5Schristos bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 158032a4398Schristos e->flags |= DOM_STAR | DOW_STAR; 1590061c6a5Schristos } else if (!strcmp("hourly", cmd)) { 1600061c6a5Schristos bit_set(e->minute, 0); 1610061c6a5Schristos bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); 1620061c6a5Schristos bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 1630061c6a5Schristos bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 1640061c6a5Schristos bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 165032a4398Schristos e->flags |= DOM_STAR | DOW_STAR; 1660061c6a5Schristos } else { 1670061c6a5Schristos ecode = e_timespec; 1680061c6a5Schristos goto eof; 1690061c6a5Schristos } 1700061c6a5Schristos /* Advance past whitespace between shortcut and 1710061c6a5Schristos * username/command. 1720061c6a5Schristos */ 1730061c6a5Schristos Skip_Blanks(ch, file); 1740061c6a5Schristos if (ch == EOF || ch == '\n') { 1750061c6a5Schristos ecode = e_cmd; 1760061c6a5Schristos goto eof; 1770061c6a5Schristos } 1780061c6a5Schristos } else { 179032a4398Schristos Debug(DPARS, ("load_entry()...about to parse numerics\n")); 1800061c6a5Schristos 1810061c6a5Schristos if (ch == '*') 1820061c6a5Schristos e->flags |= MIN_STAR; 1830061c6a5Schristos ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, 1840061c6a5Schristos PPC_NULL, ch, file); 1850061c6a5Schristos if (ch == EOF) { 1860061c6a5Schristos ecode = e_minute; 1870061c6a5Schristos goto eof; 1880061c6a5Schristos } 1890061c6a5Schristos 1900061c6a5Schristos /* hours 1910061c6a5Schristos */ 1920061c6a5Schristos 1930061c6a5Schristos if (ch == '*') 1940061c6a5Schristos e->flags |= HR_STAR; 1950061c6a5Schristos ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, 1960061c6a5Schristos PPC_NULL, ch, file); 1970061c6a5Schristos if (ch == EOF) { 1980061c6a5Schristos ecode = e_hour; 1990061c6a5Schristos goto eof; 2000061c6a5Schristos } 2010061c6a5Schristos 2020061c6a5Schristos /* DOM (days of month) 2030061c6a5Schristos */ 2040061c6a5Schristos 2050061c6a5Schristos if (ch == '*') 2060061c6a5Schristos e->flags |= DOM_STAR; 2070061c6a5Schristos ch = get_list(e->dom, FIRST_DOM, LAST_DOM, 2080061c6a5Schristos PPC_NULL, ch, file); 2090061c6a5Schristos if (ch == EOF) { 2100061c6a5Schristos ecode = e_dom; 2110061c6a5Schristos goto eof; 2120061c6a5Schristos } 2130061c6a5Schristos 2140061c6a5Schristos /* month 2150061c6a5Schristos */ 2160061c6a5Schristos 2170061c6a5Schristos ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, 2180061c6a5Schristos MonthNames, ch, file); 2190061c6a5Schristos if (ch == EOF) { 2200061c6a5Schristos ecode = e_month; 2210061c6a5Schristos goto eof; 2220061c6a5Schristos } 2230061c6a5Schristos 2240061c6a5Schristos /* DOW (days of week) 2250061c6a5Schristos */ 2260061c6a5Schristos 2270061c6a5Schristos if (ch == '*') 2280061c6a5Schristos e->flags |= DOW_STAR; 2290061c6a5Schristos ch = get_list(e->dow, FIRST_DOW, LAST_DOW, 2300061c6a5Schristos DowNames, ch, file); 2310061c6a5Schristos if (ch == EOF) { 2320061c6a5Schristos ecode = e_dow; 2330061c6a5Schristos goto eof; 2340061c6a5Schristos } 2350061c6a5Schristos } 2360061c6a5Schristos 2370061c6a5Schristos /* make sundays equivalent */ 2380061c6a5Schristos if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { 2390061c6a5Schristos bit_set(e->dow, 0); 2400061c6a5Schristos bit_set(e->dow, 7); 2410061c6a5Schristos } 2420061c6a5Schristos 2430061c6a5Schristos /* check for permature EOL and catch a common typo */ 2440061c6a5Schristos if (ch == '\n' || ch == '*') { 2450061c6a5Schristos ecode = e_cmd; 2460061c6a5Schristos goto eof; 2470061c6a5Schristos } 2480061c6a5Schristos 2490061c6a5Schristos /* ch is the first character of a command, or a username */ 2500061c6a5Schristos unget_char(ch, file); 2510061c6a5Schristos 2520061c6a5Schristos if (!pw) { 2530061c6a5Schristos char *username = cmd; /* temp buffer */ 2540061c6a5Schristos 255032a4398Schristos Debug(DPARS, ("load_entry()...about to parse username\n")); 2560061c6a5Schristos ch = get_string(username, MAX_COMMAND, file, " \t\n"); 2570061c6a5Schristos 258032a4398Schristos Debug(DPARS, ("load_entry()...got %s\n",username)); 2590061c6a5Schristos if (ch == EOF || ch == '\n' || ch == '*') { 2600061c6a5Schristos ecode = e_cmd; 2610061c6a5Schristos goto eof; 2620061c6a5Schristos } 2630061c6a5Schristos 2640061c6a5Schristos pw = getpwnam(username); 2650061c6a5Schristos if (pw == NULL) { 2660061c6a5Schristos ecode = e_username; 2670061c6a5Schristos goto eof; 2680061c6a5Schristos } 2690061c6a5Schristos Debug(DPARS, ("load_entry()...uid %ld, gid %ld\n", 270032a4398Schristos (long)pw->pw_uid, (long)pw->pw_gid)); 2710061c6a5Schristos } 2720061c6a5Schristos 2730061c6a5Schristos if ((e->pwd = pw_dup(pw)) == NULL) { 2740061c6a5Schristos ecode = e_memory; 2750061c6a5Schristos goto eof; 2760061c6a5Schristos } 277032a4398Schristos (void)memset(e->pwd->pw_passwd, 0, strlen(e->pwd->pw_passwd)); 2780061c6a5Schristos 2790061c6a5Schristos /* copy and fix up environment. some variables are just defaults and 2800061c6a5Schristos * others are overrides. 2810061c6a5Schristos */ 2820061c6a5Schristos if ((e->envp = env_copy(envp)) == NULL) { 2830061c6a5Schristos ecode = e_memory; 2840061c6a5Schristos goto eof; 2850061c6a5Schristos } 2860061c6a5Schristos if (!env_get("SHELL", e->envp)) { 2870061c6a5Schristos if (glue_strings(envstr, sizeof envstr, "SHELL", 2880061c6a5Schristos _PATH_BSHELL, '=')) { 2890061c6a5Schristos if ((tenvp = env_set(e->envp, envstr)) == NULL) { 2900061c6a5Schristos ecode = e_memory; 2910061c6a5Schristos goto eof; 2920061c6a5Schristos } 2930061c6a5Schristos e->envp = tenvp; 2940061c6a5Schristos } else 2950061c6a5Schristos log_it("CRON", getpid(), "error", "can't set SHELL"); 2960061c6a5Schristos } 2970061c6a5Schristos if (!env_get("HOME", e->envp)) { 2980061c6a5Schristos if (glue_strings(envstr, sizeof envstr, "HOME", 2990061c6a5Schristos pw->pw_dir, '=')) { 3000061c6a5Schristos if ((tenvp = env_set(e->envp, envstr)) == NULL) { 3010061c6a5Schristos ecode = e_memory; 3020061c6a5Schristos goto eof; 3030061c6a5Schristos } 3040061c6a5Schristos e->envp = tenvp; 3050061c6a5Schristos } else 3060061c6a5Schristos log_it("CRON", getpid(), "error", "can't set HOME"); 3070061c6a5Schristos } 3080061c6a5Schristos /* If login.conf is in used we will get the default PATH later. */ 3090061c6a5Schristos if (!env_get("PATH", e->envp)) { 3100061c6a5Schristos if (glue_strings(envstr, sizeof envstr, "PATH", 3110061c6a5Schristos _PATH_DEFPATH, '=')) { 3120061c6a5Schristos if ((tenvp = env_set(e->envp, envstr)) == NULL) { 3130061c6a5Schristos ecode = e_memory; 3140061c6a5Schristos goto eof; 3150061c6a5Schristos } 3160061c6a5Schristos e->envp = tenvp; 3170061c6a5Schristos } else 3180061c6a5Schristos log_it("CRON", getpid(), "error", "can't set PATH"); 3190061c6a5Schristos } 3200061c6a5Schristos if (glue_strings(envstr, sizeof envstr, "LOGNAME", 3210061c6a5Schristos pw->pw_name, '=')) { 3220061c6a5Schristos if ((tenvp = env_set(e->envp, envstr)) == NULL) { 3230061c6a5Schristos ecode = e_memory; 3240061c6a5Schristos goto eof; 3250061c6a5Schristos } 3260061c6a5Schristos e->envp = tenvp; 3270061c6a5Schristos } else 3280061c6a5Schristos log_it("CRON", getpid(), "error", "can't set LOGNAME"); 3290061c6a5Schristos #if defined(BSD) || defined(__linux) 3300061c6a5Schristos if (glue_strings(envstr, sizeof envstr, "USER", 3310061c6a5Schristos pw->pw_name, '=')) { 3320061c6a5Schristos if ((tenvp = env_set(e->envp, envstr)) == NULL) { 3330061c6a5Schristos ecode = e_memory; 3340061c6a5Schristos goto eof; 3350061c6a5Schristos } 3360061c6a5Schristos e->envp = tenvp; 3370061c6a5Schristos } else 3380061c6a5Schristos log_it("CRON", getpid(), "error", "can't set USER"); 3390061c6a5Schristos #endif 3400061c6a5Schristos 341032a4398Schristos Debug(DPARS, ("load_entry()...about to parse command\n")); 3420061c6a5Schristos 3430061c6a5Schristos /* If the first character of the command is '-' it is a cron option. 3440061c6a5Schristos */ 345fdf2ea13Schristos ch = get_char(file); 346fdf2ea13Schristos while (ch == '-') { 3470061c6a5Schristos switch (ch = get_char(file)) { 348fdf2ea13Schristos case 'n': 349fdf2ea13Schristos /* only allow the user to set the option once */ 350fdf2ea13Schristos if ((e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR) { 351fdf2ea13Schristos ecode = e_option; 352fdf2ea13Schristos goto eof; 353fdf2ea13Schristos } 354fdf2ea13Schristos e->flags |= MAIL_WHEN_ERR; 355fdf2ea13Schristos break; 3560061c6a5Schristos case 'q': 357fdf2ea13Schristos /* only allow the user to set the option once */ 358fdf2ea13Schristos if ((e->flags & DONT_LOG) == DONT_LOG) { 359fdf2ea13Schristos ecode = e_option; 360fdf2ea13Schristos goto eof; 361fdf2ea13Schristos } 3620061c6a5Schristos e->flags |= DONT_LOG; 3630061c6a5Schristos break; 364cd115652Schristos case 's': 365cd115652Schristos /* only allow the user to set the option once */ 366cd115652Schristos if ((e->flags & SINGLE_JOB) == SINGLE_JOB) { 367cd115652Schristos ecode = e_option; 368cd115652Schristos goto eof; 369cd115652Schristos } 370cd115652Schristos e->flags |= SINGLE_JOB; 371cd115652Schristos break; 3720061c6a5Schristos default: 3730061c6a5Schristos ecode = e_option; 3740061c6a5Schristos goto eof; 3750061c6a5Schristos } 376fdf2ea13Schristos ch = get_char(file); 377fdf2ea13Schristos if (ch != '\t' && ch != ' ') { 378fdf2ea13Schristos ecode = e_option; 379fdf2ea13Schristos goto eof; 380fdf2ea13Schristos } 381032a4398Schristos Skip_Blanks(ch, file); 3820061c6a5Schristos if (ch == EOF || ch == '\n') { 3830061c6a5Schristos ecode = e_cmd; 3840061c6a5Schristos goto eof; 3850061c6a5Schristos } 3860061c6a5Schristos } 3870061c6a5Schristos unget_char(ch, file); 3880061c6a5Schristos 3890061c6a5Schristos /* Everything up to the next \n or EOF is part of the command... 3900061c6a5Schristos * too bad we don't know in advance how long it will be, since we 3910061c6a5Schristos * need to malloc a string for it... so, we limit it to MAX_COMMAND. 3920061c6a5Schristos */ 3930061c6a5Schristos ch = get_string(cmd, MAX_COMMAND, file, "\n"); 3940061c6a5Schristos 3950061c6a5Schristos /* a file without a \n before the EOF is rude, so we'll complain... 3960061c6a5Schristos */ 3970061c6a5Schristos if (ch == EOF) { 3980061c6a5Schristos ecode = e_cmd; 3990061c6a5Schristos goto eof; 4000061c6a5Schristos } 4010061c6a5Schristos 4020061c6a5Schristos /* got the command in the 'cmd' string; save it in *e. 4030061c6a5Schristos */ 4040061c6a5Schristos if ((e->cmd = strdup(cmd)) == NULL) { 4050061c6a5Schristos ecode = e_memory; 4060061c6a5Schristos goto eof; 4070061c6a5Schristos } 4080061c6a5Schristos 409032a4398Schristos Debug(DPARS, ("load_entry()...returning successfully\n")); 4100061c6a5Schristos 4110061c6a5Schristos /* success, fini, return pointer to the entry we just created... 4120061c6a5Schristos */ 4130061c6a5Schristos return (e); 4140061c6a5Schristos 4150061c6a5Schristos eof: 4160061c6a5Schristos if (e->envp) 4170061c6a5Schristos env_free(e->envp); 4180061c6a5Schristos if (e->pwd) 4190061c6a5Schristos free(e->pwd); 4200061c6a5Schristos if (e->cmd) 4210061c6a5Schristos free(e->cmd); 4220061c6a5Schristos free(e); 4230061c6a5Schristos while (ch != '\n' && !feof(file)) 4240061c6a5Schristos ch = get_char(file); 4250061c6a5Schristos if (ecode != e_none && error_func) 4260061c6a5Schristos (*error_func)(ecodes[(int)ecode]); 4270061c6a5Schristos return (NULL); 4280061c6a5Schristos } 4290061c6a5Schristos 4300061c6a5Schristos static int 431032a4398Schristos get_list(bitstr_t *bits, int low, int high, const char * const names[], 4320061c6a5Schristos int ch, FILE *file) 4330061c6a5Schristos { 4340061c6a5Schristos int done; 4350061c6a5Schristos 4360061c6a5Schristos /* we know that we point to a non-blank character here; 4370061c6a5Schristos * must do a Skip_Blanks before we exit, so that the 4380061c6a5Schristos * next call (or the code that picks up the cmd) can 4390061c6a5Schristos * assume the same thing. 4400061c6a5Schristos */ 4410061c6a5Schristos 442032a4398Schristos Debug(DPARS|DEXT, ("get_list()...entered\n")); 4430061c6a5Schristos 4440061c6a5Schristos /* list = range {"," range} 4450061c6a5Schristos */ 4460061c6a5Schristos 4470061c6a5Schristos /* clear the bit string, since the default is 'off'. 4480061c6a5Schristos */ 449bc0d7f41Schristos bit_nclear(bits, 0, (size_t)(high-low+1)); 4500061c6a5Schristos 4510061c6a5Schristos /* process all ranges 4520061c6a5Schristos */ 4530061c6a5Schristos done = FALSE; 4540061c6a5Schristos while (!done) { 4550061c6a5Schristos if (EOF == (ch = get_range(bits, low, high, names, ch, file))) 4560061c6a5Schristos return (EOF); 4570061c6a5Schristos if (ch == ',') 4580061c6a5Schristos ch = get_char(file); 4590061c6a5Schristos else 4600061c6a5Schristos done = TRUE; 4610061c6a5Schristos } 4620061c6a5Schristos 4630061c6a5Schristos /* exiting. skip to some blanks, then skip over the blanks. 4640061c6a5Schristos */ 465032a4398Schristos Skip_Nonblanks(ch, file); 466032a4398Schristos Skip_Blanks(ch, file); 4670061c6a5Schristos 468032a4398Schristos Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch)); 4690061c6a5Schristos 4700061c6a5Schristos return (ch); 4710061c6a5Schristos } 4720061c6a5Schristos 4730061c6a5Schristos 4740061c6a5Schristos static int 475032a4398Schristos random_with_range(int low, int high) 476032a4398Schristos { 477032a4398Schristos /* Kind of crappy error detection, but... 478032a4398Schristos */ 4797801b53dSkre if (low < 0 || low >= high) 480032a4398Schristos return low; 481032a4398Schristos else 4828650a842Skre return (int)(arc4random() % (unsigned)((high - low + 1) + low)); 483032a4398Schristos } 484032a4398Schristos 485032a4398Schristos static int 486032a4398Schristos get_range(bitstr_t *bits, int low, int high, const char * const names[], 4870061c6a5Schristos int ch, FILE *file) 4880061c6a5Schristos { 4890061c6a5Schristos /* range = number | number "-" number [ "/" number ] 4900061c6a5Schristos */ 4910061c6a5Schristos 4920061c6a5Schristos int i, num1, num2, num3; 493032a4398Schristos int qmark, star; 4940061c6a5Schristos 495032a4398Schristos qmark = star = FALSE; 496032a4398Schristos Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n")); 4970061c6a5Schristos 4980061c6a5Schristos if (ch == '*') { 4990061c6a5Schristos /* '*' means "first-last" but can still be modified by /step 5000061c6a5Schristos */ 501032a4398Schristos star = TRUE; 5020061c6a5Schristos num1 = low; 5030061c6a5Schristos num2 = high; 5040061c6a5Schristos ch = get_char(file); 5050061c6a5Schristos if (ch == EOF) 5060061c6a5Schristos return (EOF); 507032a4398Schristos } else if (ch == '?') { 508032a4398Schristos qmark = TRUE; 509032a4398Schristos ch = get_char(file); 510032a4398Schristos if (ch == EOF) 511032a4398Schristos return EOF; 512032a4398Schristos if (!isdigit(ch)) { 513032a4398Schristos num1 = random_with_range(low, high); 514032a4398Schristos if (EOF == set_element(bits, low, high, num1)) 515032a4398Schristos return EOF; 516032a4398Schristos return ch; 517032a4398Schristos } 518032a4398Schristos } 519032a4398Schristos 520032a4398Schristos if (!star) { 5210061c6a5Schristos ch = get_number(&num1, low, names, ch, file, ",- \t\n"); 522*493e0895Schristos if (ch == EOF) 5230061c6a5Schristos return (EOF); 5240061c6a5Schristos 5250061c6a5Schristos if (ch != '-') { 5260061c6a5Schristos /* not a range, it's a single number. 527032a4398Schristos * a single number after '?' is bogus. 5280061c6a5Schristos */ 529032a4398Schristos if (qmark) 530032a4398Schristos return EOF; 5310061c6a5Schristos if (EOF == set_element(bits, low, high, num1)) { 5320061c6a5Schristos unget_char(ch, file); 5330061c6a5Schristos return (EOF); 5340061c6a5Schristos } 5350061c6a5Schristos return (ch); 5360061c6a5Schristos } else { 5370061c6a5Schristos /* eat the dash 5380061c6a5Schristos */ 5390061c6a5Schristos ch = get_char(file); 5400061c6a5Schristos if (ch == EOF) 5410061c6a5Schristos return (EOF); 5420061c6a5Schristos 5430061c6a5Schristos /* get the number following the dash 5440061c6a5Schristos */ 5450061c6a5Schristos ch = get_number(&num2, low, names, ch, file, "/, \t\n"); 5460061c6a5Schristos if (ch == EOF || num1 > num2) 5470061c6a5Schristos return (EOF); 548032a4398Schristos 549032a4398Schristos /* if we have a random range, it is really 550032a4398Schristos * like having a single number. 551032a4398Schristos */ 552032a4398Schristos if (qmark) { 553032a4398Schristos if (num1 > num2) 554032a4398Schristos return EOF; 555032a4398Schristos num1 = random_with_range(num1, num2); 556032a4398Schristos if (EOF == set_element(bits, low, high, num1)) 557032a4398Schristos return EOF; 558032a4398Schristos return ch; 559032a4398Schristos } 5600061c6a5Schristos } 5610061c6a5Schristos } 5620061c6a5Schristos 5630061c6a5Schristos /* check for step size 5640061c6a5Schristos */ 5650061c6a5Schristos if (ch == '/') { 566032a4398Schristos /* '?' is incompatible with '/' 567032a4398Schristos */ 568032a4398Schristos if (qmark) 569032a4398Schristos return EOF; 5700061c6a5Schristos /* eat the slash 5710061c6a5Schristos */ 5720061c6a5Schristos ch = get_char(file); 5730061c6a5Schristos if (ch == EOF) 5740061c6a5Schristos return (EOF); 5750061c6a5Schristos 5760061c6a5Schristos /* get the step size -- note: we don't pass the 5770061c6a5Schristos * names here, because the number is not an 5780061c6a5Schristos * element id, it's a step size. 'low' is 5790061c6a5Schristos * sent as a 0 since there is no offset either. 5800061c6a5Schristos */ 5810061c6a5Schristos ch = get_number(&num3, 0, PPC_NULL, ch, file, ", \t\n"); 582*493e0895Schristos if (ch == EOF || num3 == 0) 5830061c6a5Schristos return (EOF); 5840061c6a5Schristos } else { 5850061c6a5Schristos /* no step. default==1. 5860061c6a5Schristos */ 5870061c6a5Schristos num3 = 1; 5880061c6a5Schristos } 5890061c6a5Schristos 5900061c6a5Schristos /* range. set all elements from num1 to num2, stepping 5910061c6a5Schristos * by num3. (the step is a downward-compatible extension 5920061c6a5Schristos * proposed conceptually by bob@acornrc, syntactically 5930061c6a5Schristos * designed then implemented by paul vixie). 5940061c6a5Schristos */ 5950061c6a5Schristos for (i = num1; i <= num2; i += num3) 5960061c6a5Schristos if (EOF == set_element(bits, low, high, i)) { 5970061c6a5Schristos unget_char(ch, file); 5980061c6a5Schristos return (EOF); 5990061c6a5Schristos } 6000061c6a5Schristos 6010061c6a5Schristos return (ch); 6020061c6a5Schristos } 6030061c6a5Schristos 6040061c6a5Schristos static int 605032a4398Schristos get_number(int *numptr, int low, const char * const names[], int ch, FILE *file, 6060061c6a5Schristos const char *terms) { 6070061c6a5Schristos char temp[MAX_TEMPSTR], *pc; 6080061c6a5Schristos int len, i; 6090061c6a5Schristos 6100061c6a5Schristos pc = temp; 6110061c6a5Schristos len = 0; 6120061c6a5Schristos 6130061c6a5Schristos /* first look for a number */ 6140061c6a5Schristos while (isdigit((unsigned char)ch)) { 6150061c6a5Schristos if (++len >= MAX_TEMPSTR) 6160061c6a5Schristos goto bad; 617ae5dea0aSchristos *pc++ = (char)ch; 6180061c6a5Schristos ch = get_char(file); 6190061c6a5Schristos } 6200061c6a5Schristos *pc = '\0'; 6210061c6a5Schristos if (len != 0) { 6220061c6a5Schristos /* got a number, check for valid terminator */ 6230061c6a5Schristos if (!strchr(terms, ch)) 6240061c6a5Schristos goto bad; 6250061c6a5Schristos *numptr = atoi(temp); 6260061c6a5Schristos return (ch); 6270061c6a5Schristos } 6280061c6a5Schristos 6290061c6a5Schristos /* no numbers, look for a string if we have any */ 6300061c6a5Schristos if (names) { 6310061c6a5Schristos while (isalpha((unsigned char)ch)) { 6320061c6a5Schristos if (++len >= MAX_TEMPSTR) 6330061c6a5Schristos goto bad; 634ae5dea0aSchristos *pc++ = (char)ch; 6350061c6a5Schristos ch = get_char(file); 6360061c6a5Schristos } 6370061c6a5Schristos *pc = '\0'; 6380061c6a5Schristos if (len != 0 && strchr(terms, ch)) { 6390061c6a5Schristos for (i = 0; names[i] != NULL; i++) { 6400061c6a5Schristos Debug(DPARS|DEXT, 6410061c6a5Schristos ("get_num, compare(%s,%s)\n", names[i], 642032a4398Schristos temp)); 6430061c6a5Schristos if (!strcasecmp(names[i], temp)) { 6440061c6a5Schristos *numptr = i+low; 6450061c6a5Schristos return (ch); 6460061c6a5Schristos } 6470061c6a5Schristos } 6480061c6a5Schristos } 6490061c6a5Schristos } 6500061c6a5Schristos 6510061c6a5Schristos bad: 6520061c6a5Schristos unget_char(ch, file); 6530061c6a5Schristos return (EOF); 6540061c6a5Schristos } 6550061c6a5Schristos 6560061c6a5Schristos static int 6570061c6a5Schristos set_element(bitstr_t *bits, int low, int high, int number) { 658032a4398Schristos Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number)); 6590061c6a5Schristos 6600061c6a5Schristos if (number < low || number > high) 6610061c6a5Schristos return (EOF); 6620061c6a5Schristos 6630061c6a5Schristos bit_set(bits, (number-low)); 6640061c6a5Schristos return (OK); 6650061c6a5Schristos } 666