/*
 * FCRON - periodic command scheduler
 *
 *  Copyright 2000-2024 Thibault Godouet <fcron@free.fr>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  The GNU General Public License can also be found in the file
 *  `LICENSE' that comes with the fcron source distribution.
 */


#include "fcrontab.h"

#include "fileconf.h"

char *get_string(char *ptr);
int get_line(char *str, size_t size, FILE * file);
void init_default_line(cl_t * cl, cf_t * cf);
char *get_time(char *ptr, time_t * time, int zero_allowed);
char *get_num(char *ptr, int *num, int max, short int decimal,
              const char **names);
char *get_nice(char *ptr, int *nice);
char *get_bool(char *ptr, int *i);
char *read_field(char *ptr, bitstr_t * ary, int max, const char **names);
void read_freq(char *ptr, cf_t * cf);
void read_arys(char *ptr, cf_t * cf);
void read_period(char *ptr, cf_t * cf);
int read_shortcut(char *ptr, cf_t * cf);
void read_env(char *ptr, cf_t * cf);
char *read_opt(char *ptr, cl_t * cl);
char *check_username(char *ptr, cf_t * cf, cl_t * cl);
size_t option_strlen(char *value);

char need_correction;
cl_t default_line;              /* default options for a line */
char *file_name;
int line;

/* warning : all names must have the same length */
const char *dows_ary[] = {
    "sun", "mon", "tue", "wed", "thu", "fri", "sat",
    NULL
};

/* warning : all names must have the same length */
const char *mons_ary[] = {
    "jan", "feb", "mar", "apr", "may", "jun",
    "jul", "aug", "sep", "oct", "nov", "dec",
    NULL
};


#define GET_LINE_EOF 999

char *
get_string(char *ptr)
    /* read string pointed by ptr, remove blanks and manage
     * string placed in quotes */
    /* return NULL on mismatched quotes */
{
    char quote = 0;
    int length = 0;
    char *rtn_string = NULL;

    if (*ptr == '\"' || *ptr == '\'') {
        quote = *ptr;
        ptr++;
    }

    length = remove_blanks(ptr);

    if (quote != 0) {
        if (*(ptr + length - 1) == quote)
            *(ptr + length - 1) = '\0';
        else {
            /* mismatched quotes */
            need_correction = 1;
            return NULL;
        }
    }

    Set(rtn_string, ptr);
    return rtn_string;

}


int
get_line(char *str, size_t size, FILE * file)
    /* similar to fgets, but increase line if necessary,
     * and continue over an "\" followed by an "\n" char */
{
    size_t size_max = size - 1;
    int i = 0;
    int c;

    while (i < size_max) {

        switch (c = getc(file)) {

        case '\n':
            /* check if the \n char is preceded by a "\" char :
             *  in this case, suppress the "\", don't copy the \n,
             *  and continue */
            if (i > 0 && *(str + i - 1) == '\\') {
                i--;
                line++;
                continue;
            }
            else {
                *(str + i) = (char)'\0';
                return OK;
            }
            break;

        case EOF:
            *(str + i) = (char)'\0';
            /* we couldn't return EOF ( equal to ERR by default )
             * nor ERR, which is used for another error */
            return GET_LINE_EOF;

        default:
            *(str + i) = (char)c;
            i++;

        }

    }

    /* line is too long : goto next line and return ERR */
    while (((c = getc(file)) != EOF) && (c != '\n')) ;
    line++;
    need_correction = 1;
    return ERR;

}

void
init_default_line(cl_t * cl, cf_t * cf)
/* clear all context/options from cl */
{
    bzero(cl, sizeof(cl_t));
    Set(cl->cl_runas, runas);
    Set(cl->cl_mailto, runas);
    Free_safe(cl->cl_tz);
    set_default_opt(cl->cl_option);
    cl->cl_file = cf;
}


int
read_file(char *filename, int fd)
    /* read file "name" and append cf_t list */
{
    cf_t *cf = NULL;
    FILE *file = NULL;
    char buf[LINE_LEN];
    int max_lines;
    int max_entries = MAXENTRIES;
    int entries = 0;
    char *ptr = NULL;
    int ret;

    bzero(buf, sizeof(buf));
    need_correction = 0;
    line = 1;
    file_name = filename;

    /* open file */

    if ((file = fdopen(fd, "r")) == NULL) {
        fprintf(stderr, "Could not open \"%s\": %s\n", file_name,
                strerror(errno));
        return ERR;
    }

    /* Rewind, just in case */
    rewind(file);

    Alloc(cf, cf_t);
    cf->cf_env_list = env_list_init();
    Set(cf->cf_user, user);
    init_default_line(&default_line, cf);

    if (debug_opt)
        fprintf(stderr, "FILE %s\n", file_name);

    if (strcmp(runas, ROOTNAME) == 0)
        max_entries = 65535;

    /* max_lines acts here as a security counter to avoid endless loop. */
    max_lines = (max_entries * 10) + 10;

    while (entries < max_entries && line <= max_lines) {

        ret = get_line(buf, sizeof(buf), file);

        if (ret == ERR) {
            fprintf(stderr,
                    "%s:%d: Line is too long (more than %d): skipping line.\n",
                    file_name, line, (int)sizeof(buf));
            continue;
        }

        ptr = buf;
        Skip_blanks(ptr);

        if (debug_opt && *ptr != '#' && *ptr != '\0')
            fprintf(stderr, "      %s\n", buf);

        switch (*ptr) {
        case '#':
        case '\0':
            /* comments or empty line: skipping */
            break;
        case '@':
            /* if it is not a shortcut line then read_shortcut() won't do anything. */
            if (!read_shortcut(ptr, cf))
                read_freq(ptr, cf);
            entries++;
            break;
        case '&':
            read_arys(ptr, cf);
            entries++;
            break;
        case '%':
            read_period(ptr, cf);
            entries++;
            break;
        case '!':
            ptr = read_opt(ptr, &default_line);
            if (ptr != NULL && *ptr != '\0') {
                fprintf(stderr, "%s:%d: Syntax error: string \"%s\" ignored\n",
                        file_name, line, ptr);
                need_correction = 1;
            }
            break;
        default:
            if (isdigit((int)*ptr) || *ptr == '*') {
                read_arys(ptr, cf);
                entries++;
            }
            else
                read_env(ptr, cf);
        }

        line++;

        if (ret != OK)
            /* in this case, ret == GET_LINE_EOF :
             * no more lines, so we exit the loop */
            break;

    }

    if (entries == max_entries) {
        error("%s:%d: maximum number of entries (%d) has been reached by %s",
              file_name, line, user);
        fprintf(stderr, "Anything after this line will be ignored\n");
    }
    else if (line == max_lines)
        error("%s:%d: maximum number of lines (%d) has been reached by %s",
              file_name, line, user);

    cf->cf_next = file_base;
    file_base = cf;

    /* don't close as underlying fd may still be used by calling function */
    if (fflush(file) != 0)
        error_e("could not fflush() file_name");

    Free_safe(default_line.cl_runas);
    Free_safe(default_line.cl_mailfrom);
    Free_safe(default_line.cl_mailto);
    Free_safe(default_line.cl_tz);

    if (!need_correction)
        return OK;
    else
        return 2;

}

void
read_env(char *ptr, cf_t * cf)
    /* append env variable list.
     * (remove blanks) */
{
    char name[LINE_LEN];
    int j = 0;
    char *val = NULL;

    bzero(name, sizeof(name));

    /* copy env variable's name */
    while ((isalnum((int)*ptr) || *ptr == '_') && *ptr != '='
           && !isspace((int)*ptr) && j < sizeof(name)) {
        name[j++] = *ptr;
        ptr++;
    }
    name[j] = '\0';

    if (name[0] == '\0')
        goto error;

    /* skip '=' and spaces around */
    while (isspace((int)*ptr))
        ptr++;

    /* if j == 0 name is a zero length string */
    if (*ptr++ != '=' || j == 0)
        goto error;

    while (isspace((int)*ptr))
        ptr++;

    /* get value */
    if ((val = get_string(ptr)) == NULL) {
        fprintf(stderr, "%s:%d: Mismatched  quotes: skipping line.\n",
                file_name, line);
        need_correction = 1;
        return;
    }

    if (debug_opt)
        fprintf(stderr, "  Env : '%s=%s'\n", name, val);

    /* we ignore USER/LOGNAME's assignment */
    if (strcmp(name, "USER") == 0 || strcmp(name, "LOGNAME") == 0) {
        fprintf(stderr,
                "%s:%d: USER or LOGNAME assignement is not allowed: ignored.\n",
                file_name, line);
        return;
    }

    /* the MAILFROM/MAILTO assignment is, in fact, an fcron option :
     *  we don't store it in the same way. */
    /* please note that we check if the mailto is valid in conf.c */
    if (strcmp(name, "MAILFROM") == 0) {
        Set(default_line.cl_mailfrom, val);
    }
    else if (strcmp(name, "MAILTO") == 0) {
        /* cl_mailto must not be NULL (as expected in
         * conf.c:add_line_to_file()), so we check if the length is >= 0 */
        if (strcmp(val, "\0") == 0) {
            clear_mail(default_line.cl_option);
            clear_mailzerolength(default_line.cl_option);
        }
        else {
            Set(default_line.cl_mailto, val);
            set_mail(default_line.cl_option);
        }

    }
    else {
        env_list_setenv(cf->cf_env_list, name, val, 1);
    }

    Free_safe(val);

    return;

 error:
    fprintf(stderr, "%s:%d: Syntax error: skipping line.\n", file_name, line);
    need_correction = 1;
    return;

}


char *
get_nice(char *ptr, int *nice)
    /* read a nice value and put it in variable nice */
{
    char negative = 0;

    if (*ptr == '-') {
        negative = 1;
        ptr++;
    }

    if ((ptr = get_num(ptr, nice, 20, 0, NULL)) == NULL)
        return NULL;

    if (negative == 1) {
        if (getuid() != rootuid) {
            fprintf(stderr, "must be privileged to use a negative argument "
                    "with nice: set to 0\n");
            need_correction = 1;
            *nice = 0;
        }

        *nice *= (-1);
    }

    return ptr;

}

size_t
option_strlen(char *value)
/* return the length of the string value of an option */
{
    char *ptr = value;
    size_t len = 0;

    /* look for the end of the option value */
    while (ptr != NULL && *ptr != ')' && *ptr != '\0') {
        ptr++;
        len++;
    }

    return len;
}

int
assign_option_string(char **var, char *value)
/* Read the value of an option: if non-empty, assign it to the var,
 * otherwise set to NULL.
 * Returns the length of the value (0 if empty), or -1 on error. */
{
    char start = value[0];
    char end = '\0';
    size_t len = 0;

    Free_safe(*var);

    len = option_strlen(value);

    if (len <= 0) {
        return len;
    }

    end = value[len - 1];

    /* spaces and quotes are not allowed before or after the value */
    if (isspace((int)start) || isspace((int)end)
        || start == '\'' || start == '"' || end == '\'' || end == '"') {
        return -1;
    }

    *var = strndup2(value, len);

    return len;
}


char *
get_bool(char *ptr, int *i)
    /* get a bool value : either true (1) or false (0)
     * return NULL on error */
{
    if (*ptr == '1')
        goto true_val;
    else if (*ptr == '0')
        goto false_val;
    else if (strncmp(ptr, "true", 4) == 0) {
        ptr += 3;
        goto true_val;
    }
    else if (strncmp(ptr, "yes", 3) == 0) {
        ptr += 2;
        goto true_val;
    }
    else if (strncmp(ptr, "false", 5) == 0) {
        ptr += 4;
        goto false_val;
    }
    else if (strncmp(ptr, "no", 2) == 0) {
        ptr += 1;
        goto false_val;
    }
    else
        return NULL;

 true_val:
    *i = true;
    ptr++;
    return ptr;

 false_val:
    *i = false;
    ptr++;
    return ptr;

}


char *
read_opt(char *ptr, cl_t * cl)
    /* read one or several options and fill in the field "option" */
{
    char opt_name[20];
    int i;
    char in_brackets;

#define Handle_err \
    { \
        fprintf(stderr, "%s:%d: Argument(s) for option \"%s\" not valid: " \
		"skipping end of line.\n", file_name, line, opt_name); \
        need_correction = 1; \
        return NULL; \
    }

    if (*ptr == '!')
        ptr++;

    do {
        i = 0;
        bzero(opt_name, sizeof(opt_name));

        while (isalnum((int)*ptr) && i < sizeof(opt_name))
            opt_name[i++] = *ptr++;

        i = 1;
        in_brackets = 0;

        if (*ptr == '(') {
            in_brackets = 1;
            ptr++;

            /* spaces are not allowed -- make sure there is no leading space. */
            if (isspace((int)*ptr)) {
                Handle_err;
            }
        }

        /* global options for a file */

        if (strcmp(opt_name, "tzdiff") == 0) {
            char negative = 0;

            if (!in_brackets)
                Handle_err;
            if (*ptr == '-') {
                negative = 1;
                ptr++;
            }
            if ((ptr = get_num(ptr, &i, 24, 0, NULL)) == NULL)
                Handle_err;
            if (negative)
                cl->cl_file->cf_tzdiff = (-i);
            else
                cl->cl_file->cf_tzdiff = i;

            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" (-)%d\n", opt_name, i);
        }

        /* options related to a line (or a set of lines) */

        else if (strcmp(opt_name, "timezone") == 0) {
            int len = -1;

            if (!in_brackets) {
                Handle_err;
            }

            /* assign_option_string() will set cl_tz to NULL is the value is empty,
             * which means "use the system timezone" */
            len = assign_option_string(&(cl->cl_tz), ptr);
            if (len < 0) {
                Handle_err;
            }
            else {
                ptr += len;
            }

            if (debug_opt) {
                fprintf(stderr, "  Opt : \"%s\" \"%s\"\n", opt_name, cl->cl_tz);
            }
        }


        else if (strcmp(opt_name, "s") == 0 || strcmp(opt_name, "serial") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_serial(cl->cl_option);
            else
                set_serial(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "serialonce") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                set_serial_sev(cl->cl_option);
            else
                clear_serial_sev(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "lavgonce") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                set_lavg_sev(cl->cl_option);
            else
                clear_lavg_sev(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "exesev") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_exe_sev(cl->cl_option);
            else
                set_exe_sev(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "b") == 0 || strcmp(opt_name, "bootrun") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_bootrun(cl->cl_option);
            else
                set_bootrun(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "rebootreset") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_rebootreset(cl->cl_option);
            else
                set_rebootreset(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }


        else if (strcmp(opt_name, "runatreboot") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_runatreboot(cl->cl_option);
            else
                set_runatreboot(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }


        else if (strcmp(opt_name, "runatresume") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_runatresume(cl->cl_option);
            else
                set_runatresume(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }


        else if (strcmp(opt_name, "runonce") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_runonce(cl->cl_option);
            else
                set_runonce(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "reset") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 1) {
                init_default_line(cl, cl->cl_file);
            }
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
        }

        else if (strcmp(opt_name, "f") == 0 || strcmp(opt_name, "first") == 0) {
            if (!in_brackets
                || (ptr = get_time(ptr, &(cl->cl_first), 1)) == NULL)
                Handle_err;
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %ld\n", opt_name,
                        (long int)cl->cl_first);
        }

        else if (strcmp(opt_name, "r") == 0 || strcmp(opt_name, "runfreq") == 0) {
            if (cl->cl_runfreq == 1) {
                fprintf(stderr, "cannot change runfreq value in a %%-line");
                Handle_err;
            }
            if (!in_brackets
                || (ptr = get_num(ptr, &i, USHRT_MAX, 0, NULL)) == NULL)
                Handle_err;
            if (i <= 1) {
                fprintf(stderr, "runfreq must be 2 or more.\n");
                Handle_err;
            }
            cl->cl_runfreq = i;
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "strict") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_strict(cl->cl_option);
            else
                set_strict(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "noticenotrun") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_notice_notrun(cl->cl_option);
            else
                set_notice_notrun(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "lavg") == 0) {
            if (!in_brackets
                || (ptr = get_num(ptr, &i, UCHAR_MAX, 1, NULL)) == NULL)
                Handle_err;
            cl->cl_lavg[0] = i;
            if (debug_opt)
                fprintf(stderr, "  Opt : 'lavg1' %d\n", i);
            if (*ptr++ != ',')
                Handle_err;
            if (!in_brackets
                || (ptr = get_num(ptr, &i, UCHAR_MAX, 1, NULL)) == NULL)
                Handle_err;
            cl->cl_lavg[1] = i;
            if (debug_opt)
                fprintf(stderr, "  Opt : 'lavg5' %d\n", i);
            if (*ptr++ != ',')
                Handle_err;
            if (!in_brackets
                || (ptr = get_num(ptr, &i, UCHAR_MAX, 1, NULL)) == NULL)
                Handle_err;
            cl->cl_lavg[2] = i;
            if (debug_opt)
                fprintf(stderr, "  Opt : 'lavg15' %d\n", i);
            if (cl->cl_lavg[0] || cl->cl_lavg[1] || cl->cl_lavg[2])
                set_lavg(cl->cl_option);
            else
                clear_lavg(cl->cl_option);
#ifdef NOLOADAVG
            warn("As fcron has been compiled with no procfs support,\n"
                 "you will not be able to use the lavg* options");
#endif                          /* NOLOADAVG */
        }

        else if (strcmp(opt_name, "lavg1") == 0) {
            if (!in_brackets
                || (ptr = get_num(ptr, &i, UCHAR_MAX, 1, NULL)) == NULL)
                Handle_err;
            cl->cl_lavg[0] = i;
            if (cl->cl_lavg[0] || cl->cl_lavg[1] || cl->cl_lavg[2])
                set_lavg(cl->cl_option);
            else
                clear_lavg(cl->cl_option);
#if NOLOADAVG
            warn("As fcron has been compiled with no procfs support,\n"
                 "you will not be able to use the lavg* options");
#endif                          /* NOLOADAVG */
            if (debug_opt)
                fprintf(stderr, "  Opt : 'lavg1' %d\n", i);
        }

        else if (strcmp(opt_name, "lavg5") == 0) {
            if (!in_brackets
                || (ptr = get_num(ptr, &i, UCHAR_MAX, 1, NULL)) == NULL)
                Handle_err;
            cl->cl_lavg[1] = i;
            if (cl->cl_lavg[0] || cl->cl_lavg[1] || cl->cl_lavg[2])
                set_lavg(cl->cl_option);
            else
                clear_lavg(cl->cl_option);
#ifdef NOLOADAVG
            warn("As fcron has been compiled with no procfs support,\n"
                 "you will not be able to use the lavg* options");
#endif                          /* NOLOADAVG = 0 */
            if (debug_opt)
                fprintf(stderr, "  Opt : 'lavg5' %d\n", i);
        }

        else if (strcmp(opt_name, "lavg15") == 0) {
            if (!in_brackets
                || (ptr = get_num(ptr, &i, UCHAR_MAX, 1, NULL)) == NULL)
                Handle_err;
            cl->cl_lavg[2] = i;
            if (cl->cl_lavg[0] || cl->cl_lavg[1] || cl->cl_lavg[2])
                set_lavg(cl->cl_option);
            else
                clear_lavg(cl->cl_option);
#ifdef NOLOADAVG
            warn("As fcron has been compiled with no procfs support,\n"
                 "you will not be able to use the lavg* options");
#endif                          /* NOLOADAVG = 0 */
            if (debug_opt)
                fprintf(stderr, "  Opt : 'lavg15' %d\n", i);
        }

        else if (strcmp(opt_name, "lavgand") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                set_lor(cl->cl_option);
            else
                set_land(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "lavgor") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                set_land(cl->cl_option);
            else
                set_lor(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "u") == 0 || strcmp(opt_name, "until") == 0) {
            if (!in_brackets
                || (ptr = get_time(ptr, &(cl->cl_until), 0)) == NULL)
                Handle_err;
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %ld\n", opt_name,
                        (long int)cl->cl_until);
        }

        else if (strcmp(opt_name, "m") == 0 || strcmp(opt_name, "mail") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0) {
                clear_mail(cl->cl_option);
                clear_mailzerolength(cl->cl_option);
            }
            else
                set_mail(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "forcemail") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_mailzerolength(cl->cl_option);
            else {
                set_mailzerolength(cl->cl_option);
                set_mail(cl->cl_option);
            }
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "mailfrom") == 0) {
            int len = -1;

            if (!in_brackets) {
                Handle_err;
            }

            /* Also please note that we check if the mailfrom is valid in conf.c */
            len = assign_option_string(&(cl->cl_mailfrom), ptr);
            if (len < 0) {
                Handle_err;
            }
            else {
                ptr += len;
            }

            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" \"%s\"\n", opt_name,
                        cl->cl_mailfrom);
        }

        else if (strcmp(opt_name, "mailto") == 0) {
            int len = -1;

            if (!in_brackets) {
                Handle_err;
            }

            /* assign_option_string() set the value to NULL if the length is zero.
             * However cl_mailto must not be NULL (as expected in
             * conf.c:add_line_to_file()), so we check if the length is >= 0
             * before calling assign_option_string() */
            /* Also please note that we check if the mailto is valid in conf.c */
            len = option_strlen(ptr);
            if (len <= 0) {
                clear_mail(cl->cl_option);
                clear_mailzerolength(cl->cl_option);
                if (debug_opt) {
                    fprintf(stderr, "  Opt : \"mail\" 0\n");
                    fprintf(stderr, "  Opt : \"forcemail\" 0\n");
                }
            }
            else {
                len = assign_option_string(&(cl->cl_mailto), ptr);
                if (len < 0) {
                    Handle_err;
                }
                else {
                    ptr += len;
                    set_mail(cl->cl_option);
                }
            }

            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" \"%s\"\n", opt_name,
                        cl->cl_mailto);
        }

        else if (strcmp(opt_name, "erroronlymail") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_erroronlymail(cl->cl_option);
            else
                set_erroronlymail(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "dayand") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                set_dayor(cl->cl_option);
            else
                set_dayand(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "dayor") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                set_dayand(cl->cl_option);
            else
                set_dayor(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "nolog") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_nolog(cl->cl_option);
            else
                set_nolog(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "volatile") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_volatile(cl->cl_option);
            else
                set_volatile(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "stdout") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_stdout(cl->cl_option);
            else
                set_stdout(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "n") == 0 || strcmp(opt_name, "nice") == 0) {
            if (!in_brackets || (ptr = get_nice(ptr, &i)) == NULL)
                Handle_err;
            cl->cl_nice = (char)i;
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" (-)%d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "runas") == 0) {
            int len = -1;
            char *runas = NULL;
            struct passwd *pas;

            if (!in_brackets) {
                Handle_err;
            }

            len = assign_option_string(&runas, ptr);
            if (len <= 0) {
                Handle_err;
            }
            else {
                ptr += len;
            }

            if (getuid() != rootuid) {
                fprintf(stderr, "must be privileged to use option runas: "
                        "skipping option\n");
                need_correction = 1;
            }
            else if (len > USER_NAME_LEN) {
                fprintf(stderr, "runas: user name \"%s\" longer than %d"
                        "characters: skipping option\n", runas, USER_NAME_LEN);
                need_correction = 1;
            }
            else if ((pas = getpwnam(runas)) == NULL) {
                fprintf(stderr, "runas: \"%s\" is not in passwd file : "
                        "ignored", runas);
                need_correction = 1;
            }

            if (need_correction) {
                Free_safe(runas);
            }
            else {
                /* only set cl_runas if all is well, as cl_runas MUST always
                 * be set to a valid value */
                Set(cl->cl_runas, runas);
            }

            /* all good */
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %s ptr=%p\n", opt_name,
                        cl->cl_runas, cl->cl_runas);

        }

        else if (strcmp(opt_name, "random") == 0) {
            if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
                Handle_err;
            if (i == 0)
                clear_random(cl->cl_option);
            else
                set_random(cl->cl_option);
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        else if (strcmp(opt_name, "jitter") == 0) {
            if (!in_brackets
                || (ptr = get_num(ptr, &i, UCHAR_MAX, 0, NULL)) == NULL)
                Handle_err;
            cl->cl_jitter = i;
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }

        /* handle %-line : we check if we are really in a %-line (which we do not do
         * for other options), because writing "&hourly" in a fcrontab results in an
         * error (hourly ignored) hard to find, and, in any case, annoying. */
        else if (cl->cl_runfreq == 1) {
            /* options to run once per interval :
             * ignore every fields below the limit */
            if (strcmp(opt_name, "mins") == 0) {
                /* nothing to do */
                if (debug_opt)
                    fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
            }
            else if (strcmp(opt_name, "hours") == 0) {
                set_freq_mins(cl->cl_option);
                if (debug_opt)
                    fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
            }
            else if (strcmp(opt_name, "days") == 0) {
                set_freq_mins(cl->cl_option);
                set_freq_hrs(cl->cl_option);
                if (debug_opt)
                    fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
            }
            else if (strcmp(opt_name, "mons") == 0) {
                set_freq_mins(cl->cl_option);
                set_freq_hrs(cl->cl_option);
                set_freq_days(cl->cl_option);
                if (debug_opt)
                    fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
            }
            else if (strcmp(opt_name, "dow") == 0) {
                set_freq_mins(cl->cl_option);
                set_freq_hrs(cl->cl_option);
                set_freq_days(cl->cl_option);
                set_freq_mons(cl->cl_option);
                if (debug_opt)
                    fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
            }

            /* run once an element of the selected field
             * (once an hour, once a day, etc) */
            else if (strcmp(opt_name, "hourly") == 0) {
                set_freq_hrs(cl->cl_option);
                set_freq_periodically(cl->cl_option);
                if (debug_opt)
                    fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
            }
            else if (strcmp(opt_name, "daily") == 0) {
                set_freq_days(cl->cl_option);
                set_freq_periodically(cl->cl_option);
                if (debug_opt)
                    fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
            }
            else if (strcmp(opt_name, "monthly") == 0) {
                set_freq_mons(cl->cl_option);
                set_freq_periodically(cl->cl_option);
                if (debug_opt)
                    fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
            }
            else if (strcmp(opt_name, "weekly") == 0) {
                set_freq_dow(cl->cl_option);
                set_freq_periodically(cl->cl_option);
                if (debug_opt)
                    fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
            }

            /* run once an element of the selected field
             * from middle to middle of that field
             * (ie once from 12h to 12h the following day) */
            else if (strcmp(opt_name, "midhourly") == 0) {
                set_freq_hrs(cl->cl_option);
                set_freq_periodically(cl->cl_option);
                set_freq_mid(cl->cl_option);
                if (debug_opt)
                    fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
            }
            else if (strcmp(opt_name, "middaily") == 0
                     || strcmp(opt_name, "nightly") == 0) {
                set_freq_days(cl->cl_option);
                set_freq_periodically(cl->cl_option);
                set_freq_mid(cl->cl_option);
                if (debug_opt)
                    fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
            }
            else if (strcmp(opt_name, "midmonthly") == 0) {
                set_freq_mons(cl->cl_option);
                set_freq_periodically(cl->cl_option);
                set_freq_mid(cl->cl_option);
                if (debug_opt)
                    fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
            }
            else if (strcmp(opt_name, "midweekly") == 0) {
                set_freq_dow(cl->cl_option);
                set_freq_periodically(cl->cl_option);
                set_freq_mid(cl->cl_option);
                if (debug_opt)
                    fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
            }
        }

        else {
            fprintf(stderr, "%s:%d: Option \"%s\" unknown: "
                    "skipping option.\n", file_name, line, opt_name);
            need_correction = 1;
        }

        if (in_brackets) {
            if (*ptr != ')') {
            Handle_err}
            else
                ptr++;
        }

    } while (*ptr == ',' && ptr++);

    Skip_blanks(ptr);
    return ptr;
}


char *
get_time(char *ptr, time_t * time, int zero_allowed)
    /* convert time read in string in time_t format */
{
    time_t sum;

    *time = 0;

    while ((*ptr != ' ') && (*ptr != '\t') && (*ptr != '\0') && (*ptr != ')')) {

        sum = 0;

        while (isdigit((int)*ptr)) {
            sum *= 10;
            sum += *ptr - 48;
            ptr++;
        }

        /* interpret multipliers */
        switch (*ptr) {
        case 'm':              /* months */
            sum *= 4;
        case 'w':              /* weeks */
            sum *= 7;
        case 'd':              /* days */
            sum *= 24;
        case 'h':              /* hours */
            sum *= 3600;
        case 's':              /* seconds */
            ptr++;
            break;
        case ' ':
        case '\t':
        case ')':
            sum *= 60;          /* minutes */
            break;
        default:
            need_correction = 1;
            return NULL;
        }

        *time += sum;

    }

    Skip_blanks(ptr);
    if (*time == 0 && !zero_allowed) {
        need_correction = 1;
        return NULL;
    }
    else
        return ptr;
}


char *
check_username(char *ptr, cf_t * cf, cl_t * cl)
    /* check ptr to see if the first word is a username, returns new ptr */
{
    short int indx = 0;
    char username[USER_NAME_LEN];
    struct passwd *userpwent;

    /* check to see if next word is a username */
    /* we don't allow quotes, to be able to distinguish a user name from
     * a command line (where quotes are allowed) */
    while (isalnum((int)ptr[indx]) || ptr[indx] == '-' || ptr[indx] == '_')
        indx++;
    if (indx >= USER_NAME_LEN)
        indx = USER_NAME_LEN - 1;
    strncpy(username, ptr, indx);
    username[indx] = '\0';

    if ((userpwent = getpwnam(username)) != NULL) {
        /* found the user */
        ptr = ptr + indx;       /* move ptr to the next word */
        Skip_blanks(ptr);

        if (getuid() != rootuid) {
            fprintf(stderr, "must be privileged to run as another user : "
                    "ignoring\n");
        }
        else {
            Set(cl->cl_runas, username);
            if (debug_opt)
                fprintf(stderr, "  Opt : inline_runas %s\n", username);
        }
    }

    return ptr;
}


char *
read_word(char *ptr, char *buf, size_t buf_size)
/* copy a word to buf with a max_size of buf_size and return a pointer
 * to the next char after the word */
{
    int i = 0;

    bzero(buf, buf_size);

    while (isalnum((int)*ptr) && i < buf_size)
        buf[i++] = *ptr++;

    return ptr;
}

int
read_shortcut(char *ptr, cf_t * cf)
/* try to read a shortcut entry, and if it is one then append a line to cf
 * Return 1 if that was a shortcut entry. If it wasn't, return 0 and make sure
 * ptr is back to its orig value. */
{
    cl_t *cl = NULL;
    char shortcut[20];
    char *ptr_orig = ptr;

    cl = dups_cl(&default_line);

    /* skip the @ */
    ptr++;

    ptr = read_word(ptr, shortcut, sizeof(shortcut));

    /* time&date by default -- we'll switch to freq if @reboot */
    set_td(cl->cl_option);
    cl->cl_remain = cl->cl_runfreq;     /* FIXME: necessary?? */

    if (strcmp(shortcut, "reboot") == 0) {
        set_freq(cl->cl_option);
        set_runatreboot(cl->cl_option);
        set_runonce(cl->cl_option);
        clear_volatile(cl->cl_option);
        cl->cl_runfreq = 0;
        cl->cl_first = 0;
        /* the job will not be rescheduled after the first execution (flag is_hasrun),
         * we set timefreq to LONG_MAX just in case */
        cl->cl_timefreq = LONG_MAX;

        if (debug_opt)
            fprintf(stderr, "  Shc : @reboot\n");
    }
    else if (strcmp(shortcut, "resume") == 0) {
        set_freq(cl->cl_option);
        set_runatresume(cl->cl_option);
        set_runonce(cl->cl_option);
        clear_volatile(cl->cl_option);
        cl->cl_runfreq = 0;
        cl->cl_first = 0;
        /* the job will not be rescheduled after the first execution (flag is_hasrun),
         * we set timefreq to LONG_MAX just in case */
        cl->cl_timefreq = LONG_MAX;

        if (debug_opt)
            fprintf(stderr, "  Shc : @resume\n");
    }
    else if (strcmp(shortcut, "yearly") == 0
             || strcmp(shortcut, "annually") == 0) {
        bit_set(cl->cl_mins, 0);
        bit_set(cl->cl_hrs, 0);
        bit_set(cl->cl_days, 1);
        bit_set(cl->cl_mons, 0);
        bit_nset(cl->cl_dow, 0, 7);

        if (debug_opt)
            fprintf(stderr, "  Shc : @yearly\n");
    }
    else if (strcmp(shortcut, "monthly") == 0) {
        bit_set(cl->cl_mins, 0);
        bit_set(cl->cl_hrs, 0);
        bit_set(cl->cl_days, 1);
        bit_nset(cl->cl_mons, 0, 11);
        bit_nset(cl->cl_dow, 0, 7);

        if (debug_opt)
            fprintf(stderr, "  Shc : @monthly\n");
    }
    else if (strcmp(shortcut, "weekly") == 0) {
        bit_set(cl->cl_mins, 0);
        bit_set(cl->cl_hrs, 0);
        bit_nset(cl->cl_days, 0, 31);
        bit_nset(cl->cl_mons, 0, 11);
        bit_set(cl->cl_dow, 0);
        bit_set(cl->cl_dow, 7); /* 0 and 7 are both sunday */

        if (debug_opt)
            fprintf(stderr, "  Shc : @weekly\n");
    }
    else if (strcmp(shortcut, "daily") == 0
             || strcmp(shortcut, "midnight") == 0) {
        bit_set(cl->cl_mins, 0);
        bit_set(cl->cl_hrs, 0);
        bit_nset(cl->cl_days, 0, 31);
        bit_nset(cl->cl_mons, 0, 11);
        bit_nset(cl->cl_dow, 0, 7);

        if (debug_opt)
            fprintf(stderr, "  Shc : @daily\n");
    }
    else if (strcmp(shortcut, "hourly") == 0) {
        bit_set(cl->cl_mins, 0);
        bit_nset(cl->cl_hrs, 0, 23);
        bit_nset(cl->cl_days, 0, 31);
        bit_nset(cl->cl_mons, 0, 11);
        bit_nset(cl->cl_dow, 0, 7);

        if (debug_opt)
            fprintf(stderr, "  Shc : @hourly\n");
    }
    else {
        /* this is not a shortcut line but a normal @-line:  */
        ptr = ptr_orig;
        return 0;
    }

    /* The next char must be a space (no other option allowed when using
     * a shortcut: if the user wants to use options, they should use the
     * native fcron lines */
    if (!isspace((int)*ptr)) {
        fprintf(stderr, "%s:%d: No space after shortcut: skipping line.\n",
                file_name, line);
        goto exiterr;
    }

    /* skip spaces before the username / shell command */
    while (isspace((int)*ptr))
        ptr++;

    /* check for inline runas */
    ptr = check_username(ptr, cf, cl);

    /* get cl_shell field ( remove trailing blanks ) */
    if ((cl->cl_shell = get_string(ptr)) == NULL) {
        fprintf(stderr, "%s:%d: Mismatched quotes: skipping line.\n",
                file_name, line);
        goto exiterr;
    }
    if (strcmp(cl->cl_shell, "\0") == 0) {
        fprintf(stderr, "%s:%d: No shell command: skipping line.\n",
                file_name, line);
        Free_safe(cl->cl_shell);
        goto exiterr;
    }

#ifndef USE_SENDMAIL
    clear_mail(cl->cl_option);
    clear_mailzerolength(cl->cl_option);
#endif

    cl->cl_next = cf->cf_line_base;
    cf->cf_line_base = cl;

    if (debug_opt)
        fprintf(stderr, "  Cmd \"%s\" (shortcut)\n", cl->cl_shell);
    return 1;

 exiterr:
    Free_safe(cl);
    need_correction = 1;
    return 1;
}

void
read_freq(char *ptr, cf_t * cf)
    /* read a freq entry, and append a line to cf */
{
    cl_t *cl = NULL;

    cl = dups_cl(&default_line);

    cl->cl_first = -1;          /* 0 is a valid value, so we have to use -1 to detect unset */

    /* skip the @ */
    ptr++;

    /* get the time before first execution or the options */
    if (isdigit((int)*ptr)) {
        if ((ptr = get_time(ptr, &(cl->cl_first), 1)) == NULL) {
            fprintf(stderr, "%s:%d: Error while reading first delay:"
                    " skipping line.\n", file_name, line);
            goto exiterr;
        }

        Skip_blanks(ptr);
    }
    else if (isalnum((int)*ptr)) {
        if ((ptr = read_opt(ptr, cl)) == NULL)
            goto exiterr;
    }
    else
        Skip_blanks(ptr);

    /* we set this here, because it may be unset by read_opt (reset option) */
    cl->cl_runfreq = 0;
    set_freq(cl->cl_option);

    /* then cl_timefreq */
    if ((ptr = get_time(ptr, (time_t *) & (cl->cl_timefreq), 0)) == NULL
        || cl->cl_timefreq < 1) {
        fprintf(stderr,
                "%s:%d: Error while reading frequency %s: skipping line.\n",
                file_name, line,
                (cl->cl_timefreq < 1) ? "(lower than 1s) " : "");
        goto exiterr;
    }

    if (cl->cl_timefreq == 0) {
        fprintf(stderr, "%s:%d: no freq specified: skipping line.\n",
                file_name, line);
        goto exiterr;
    }

    if (cl->cl_first == -1)
        /* time before first execution was not specified explicitely */
        cl->cl_first = cl->cl_timefreq;

    /* check for inline runas */
    ptr = check_username(ptr, cf, cl);

    /* get cl_shell field ( remove trailing blanks ) */
    if ((cl->cl_shell = get_string(ptr)) == NULL) {
        fprintf(stderr, "%s:%d: Mismatched quotes: skipping line.\n",
                file_name, line);
        goto exiterr;
    }
    if (strcmp(cl->cl_shell, "\0") == 0) {
        fprintf(stderr, "%s:%d: No shell command: skipping line.\n",
                file_name, line);
        Free_safe(cl->cl_shell);
        goto exiterr;
    }

#ifndef USE_SENDMAIL
    clear_mail(cl->cl_option);
    clear_mailzerolength(cl->cl_option);
#endif

    cl->cl_next = cf->cf_line_base;
    cf->cf_line_base = cl;

    if (debug_opt)
        fprintf(stderr, "  Cmd \"%s\", timefreq %ld, first %ld\n",
                cl->cl_shell, cl->cl_timefreq, (long int)cl->cl_first);

    return;

 exiterr:
    free_line(cl);
    need_correction = 1;
    return;
}



#define R_field(PTR, ARY, MAX, ARYCONST, DESCRP) \
  if((PTR = read_field(PTR, ARY, MAX, ARYCONST)) == NULL) { \
      if (debug_opt) \
          fprintf(stderr, "\n"); \
      fprintf(stderr, "%s:%d: Error while reading " DESCRP " field: " \
             "skipping line.\n", file_name, line); \
      Free_safe(cl); \
      return; \
  }


void
read_arys(char *ptr, cf_t * cf)
    /* read a run freq number plus a normal fcron line */
{
    cl_t *cl = NULL;
    int i = 0;

    cl = dups_cl(&default_line);

    /* set cl_remain if not specified */
    if (*ptr == '&') {
        ptr++;
        if (isdigit((int)*ptr)) {
            if ((ptr = get_num(ptr, &i, USHRT_MAX, 0, NULL)) == NULL) {
                fprintf(stderr, "%s:%d: Error while reading runfreq:"
                        " skipping line.\n", file_name, line);
                goto exiterr;
            }
            else {
                if (i <= 1) {
                    fprintf(stderr, "%s:%d: runfreq must be 2 or more :"
                            " skipping line.\n", file_name, line);
                    goto exiterr;
                }
                cl->cl_runfreq = (unsigned short)i;
            }
        }
        else if (isalnum((int)*ptr))
            if ((ptr = read_opt(ptr, cl)) == NULL) {
                goto exiterr;
            }
        Skip_blanks(ptr);
    }

    cl->cl_remain = cl->cl_runfreq;

    /* we set this here, because it may be unset by read_opt (reset option) */
    set_td(cl->cl_option);

    if (debug_opt)
        fprintf(stderr, "     ");

    /* get the fields (check for errors) */
    R_field(ptr, cl->cl_mins, 59, NULL, "minutes");
    R_field(ptr, cl->cl_hrs, 23, NULL, "hours");
    R_field(ptr, cl->cl_days, 31, NULL, "days");
    /* month are defined by user from 1 to 12 : max is 12 */
    R_field(ptr, cl->cl_mons, 12, mons_ary, "months");
    R_field(ptr, cl->cl_dow, 7, dows_ary, "days of week");

    if (debug_opt)
        /* if debug_opt is set, we print informations in read_field function,
         *  but no end line : we print it here */
        fprintf(stderr, " remain %d\n", cl->cl_remain);

    /* check for inline runas */
    ptr = check_username(ptr, cf, cl);

    /* get the shell command (remove trailing blanks) */
    if ((cl->cl_shell = get_string(ptr)) == NULL) {
        fprintf(stderr, "%s:%d: Mismatched quotes: skipping line.\n",
                file_name, line);
        goto exiterr;
    }
    if (strcmp(cl->cl_shell, "\0") == 0) {
        fprintf(stderr, "%s:%d: No shell command: skipping line.\n",
                file_name, line);
        Free_safe(cl->cl_shell);
        goto exiterr;
    }

#ifndef USE_SENDMAIL
    clear_mail(cl->cl_option);
    clear_mailzerolength(cl->cl_option);
#endif

    cl->cl_next = cf->cf_line_base;
    cf->cf_line_base = cl;

    if (debug_opt)
        fprintf(stderr, "  Cmd \"%s\"\n", cl->cl_shell);
    return;

 exiterr:
    need_correction = 1;
    free_line(cl);
    return;

}

void
read_period(char *ptr, cf_t * cf)
    /* read a line to run periodically (i.e. once a day, once a week, etc) */
{
    cl_t *cl = NULL;
    short int remain = 8;

    cl = dups_cl(&default_line);

    /* skip the % */
    ptr++;

    /* a runfreq set to 1 means : this is a periodical line
     * (runfreq cannot be changed by read_opt() if already set to 1) */
    cl->cl_remain = cl->cl_runfreq = 1;

    /* set cl_remain if not specified */
    if ((ptr = read_opt(ptr, cl)) == NULL) {
        goto exiterr;
    }
    Skip_blanks(ptr);

    /* we set this here, because it may be unset by read_opt (reset option) */
    set_td(cl->cl_option);

    if (debug_opt)
        fprintf(stderr, "     ");

    if (is_freq_periodically(cl->cl_option)) {
        if (is_freq_mins(cl->cl_option))
            remain = 0;
        else if (is_freq_hrs(cl->cl_option))
            remain = 1;
        else if (is_freq_days(cl->cl_option))
            remain = 2;
        else if (is_freq_mons(cl->cl_option))
            remain = 3;
        else if (is_freq_dow(cl->cl_option))
            remain = 2;
    }

    /* get the fields (check for errors) */
    if (remain-- > 0) {
    R_field(ptr, cl->cl_mins, 59, NULL, "minutes")}
    else
        bit_nset(cl->cl_mins, 0, 59);
    if (remain-- > 0) {
    R_field(ptr, cl->cl_hrs, 23, NULL, "hours")}
    else
        bit_nset(cl->cl_hrs, 0, 23);
    if (remain-- > 0) {
    R_field(ptr, cl->cl_days, 31, NULL, "days")}
    else
        bit_nset(cl->cl_days, 1, 32);
    /* month are defined by user from 1 to 12 : max is 12 */
    if (remain-- > 0) {
    R_field(ptr, cl->cl_mons, 12, mons_ary, "months")}
    else
        bit_nset(cl->cl_mons, 0, 11);
    if (remain-- > 0) {
    R_field(ptr, cl->cl_dow, 7, dows_ary, "days of week")}
    else
        bit_nset(cl->cl_dow, 0, 7);

    if (debug_opt)
        /* if debug_opt is set, we print informations in read_field function,
         *  but no end line : we print it here */
        fprintf(stderr, " remain %d\n", cl->cl_remain);

    /* check for inline runas */
    ptr = check_username(ptr, cf, cl);

    /* get the shell command (remove trailing blanks) */
    if ((cl->cl_shell = get_string(ptr)) == NULL) {
        fprintf(stderr, "%s:%d: Mismatched quotes: skipping line.\n",
                file_name, line);
        goto exiterr;
    }
    if (strcmp(cl->cl_shell, "\0") == 0) {
        fprintf(stderr, "%s:%d: No shell command: skipping line.\n",
                file_name, line);
        Free_safe(cl->cl_shell);
        goto exiterr;
    }
    else if (cl->cl_shell[0] == '*' || isdigit((int)cl->cl_shell[0]))
        fprintf(stderr, "%s:%d: Warning : shell command beginning by '%c'.\n",
                file_name, line, cl->cl_shell[0]);

    /* check for non matching if option runfreq is set to 1 */
    if (!is_freq_periodically(cl->cl_option)) {
        const size_t s_mins = 60, s_hrs = 24;
        const size_t s_days = 32, s_mons = 12;
        const size_t s_dow = 8;
        int j = 0;

        if (!is_freq_mins(cl->cl_option)) {
            bit_ffc(cl->cl_mins, s_mins, &j);
            if (j != -1 && j < s_mins)
                goto ok;
        }
        if (!is_freq_hrs(cl->cl_option)) {
            bit_ffc(cl->cl_hrs, s_hrs, &j);
            if (j != -1 && j < s_hrs)
                goto ok;
        }
        if (!is_freq_days(cl->cl_option)) {
            bit_ffc(cl->cl_days, s_days, &j);
            if (j != -1 && j < s_days) {
                if (is_dayand(cl->cl_option))
                    goto ok;
                else {
                    if (!is_freq_dow(cl->cl_option)) {
                        bit_ffc(cl->cl_dow, s_dow, &j);
                        if (j != -1 && j < s_dow)
                            goto ok;
                    }
                }
            }
        }
        if (!is_freq_mons(cl->cl_option)) {
            bit_ffc(cl->cl_mons, s_mons, &j);
            if (j != -1 && j < s_mons)
                goto ok;
        }
        if (!is_freq_dow(cl->cl_option)) {
            bit_ffc(cl->cl_dow, s_dow, &j);
            if (j != -1 && j < s_dow && is_dayand(cl->cl_option))
                goto ok;
        }

        fprintf(stderr, "%s:%d: periodical line with no intervals: "
                "skipping line.\n", file_name, line);
        goto exiterr;
    }

 ok:
#ifndef USE_SENDMAIL
    clear_mail(cl->cl_option);
    clear_mailzerolength(cl->cl_option);
#endif

    cl->cl_next = cf->cf_line_base;
    cf->cf_line_base = cl;

    if (debug_opt)
        fprintf(stderr, "  Cmd \"%s\"\n", cl->cl_shell);
    return;

 exiterr:
    need_correction = 1;
    free_line(cl);
    return;

}

char *
get_num(char *ptr, int *num, int max, short int decimal, const char **names)
    /* read a string's number and return it under int format.
     *  Also check if that number is less than max */
{
    int i = 0;
    *num = 0;

    if (isalpha((int)*ptr)) {

        if (names == NULL) {
            need_correction = 1;
            return NULL;
        }

        /* set string to lower case */
        for (i = 0; i < strlen(names[0]); i++)
            *(ptr + i) = tolower(*(ptr + i));

        for (i = 0; names[i]; ++i)
            if (strncmp(ptr, names[i], strlen(names[i])) == 0) {
                *num = i;
                ptr += strlen(names[i]);
                return ptr;
                break;
            }

        /* string is not in name list */
        need_correction = 1;
        return NULL;

    }
    else {

        while (isdigit((int)*ptr) || *ptr == '.') {

            if (*ptr == '.' && ptr++ && i++ > 0)
                return NULL;
            if (i > 0 && --decimal < 0) {
                /* the decimal number is exceeded : we round off,
                 * skip the other decimals and return */
                if (*ptr >= '5')
                    *num += 1;
                while (isdigit((int)*(++ptr))) ;
                ptr--;
            }
            else {
                *num *= 10;
                *num += *ptr - 48;
            }

            if (*num > max) {
                need_correction = 1;
                return NULL;
            }

            ptr++;

        }

        if (decimal > 0)
            *num *= 10 * decimal;

    }

    return ptr;
}


char *
read_field(char *ptr, bitstr_t * ary, int max, const char **names)
    /* read a field like "2,5-8,10-20/2,21-30~25" and fill ary */
{
    int start = 0;
    int stop = 0;
    int step = 0;
    int rm = 0;
    int i = 0;

    while ((*ptr != ' ') && (*ptr != '\t') && (*ptr != '\0')) {

        start = stop = step = 0;

        /* there may be a "," */
        if (*ptr == ',')
            ptr++;

        if (*ptr == '*') {
            /* we have to fill everything (may be modified by a step ) */
            start = 0;
            /* user set month from 1 to 12, but we manage it internally
             * as a number from 0 to 11 */
            stop = (max == 12) ? 11 : max;
            ptr++;
        }
        else {

            ptr = get_num(ptr, &start, max, 0, names);
            if (ptr == NULL)
                return NULL;
            if (max == 12)
                /* this number is part of the month field.
                 * user set it from 1 to 12, but we manage it internally
                 * as a number from 0 to 11 : we remove 1 to start */
                start -= 1;

            if (*ptr == ',' || *ptr == ' ' || *ptr == '\t') {
                /* this is a single number : set up array and continue */
                if (debug_opt)
                    fprintf(stderr, " %d", start);
                bit_set(ary, start);
                continue;
            }

            /* check for a dash */
            else if (*ptr == '-') {
                ptr++;
                ptr = get_num(ptr, &stop, max, 0, names);
                if (ptr == NULL)
                    /* we reached the end of the string to parse */
                    return NULL;
                if (max == 12)
                    /* this number is part of the month field.
                     * user set it from 1 to 12, but we manage it internally
                     * as a number from 0 to 11 : we remove 1 to stop */
                    stop -= 1;
            }
            else {
                /* syntax error */
                need_correction = 1;
                return NULL;
            }
        }

        /* check for step size */
        if (*ptr == '/') {
            ptr++;
            if ((ptr = get_num(ptr, &step, max, 0, names)) == NULL || step == 0)
                return NULL;
        }
        else
            /* step undefined : default is 0 */
            step = 1;

        /* fill array */
        if (debug_opt)
            fprintf(stderr, " %d-%d/%d", start, stop, step);

        if (start < stop)
            for (i = start; i <= stop; i += step)
                bit_set(ary, i);
        else {
            short int field_max = (max == 12) ? 11 : max;
            /* this is a field like (for hours field) "22-3" :
             * we should set from 22 to 3 (not from 3 to 22 or nothing :)) ) */
            for (i = start; i <= field_max; i += step)
                bit_set(ary, i);
            for (i -= (field_max + 1); i <= stop; i += step)
                bit_set(ary, i);
        }

        /* finally, remove unwanted values */
        while (*ptr == '~') {
            ptr++;
            rm = 0;
            if ((ptr = get_num(ptr, &rm, max, 0, names)) == NULL)
                return NULL;
            if (max == 12)
                /* this number is part of the month field.
                 * user set it from 1 to 12, but we manage it internally
                 * as a number from 0 to 11 : we remove 1 to rm */
                rm -= 1;

            if (debug_opt)
                fprintf(stderr, " ~%d", rm);
            bit_clear(ary, rm);

            /* if we remove one value of Sunday, remove the other */
            if (max == 7 && rm == 0) {
                bit_clear(ary, 7);
                if (debug_opt)
                    fprintf(stderr, " ~%d", 7);
            }
            else if (max == 7 && rm == 7) {
                bit_clear(ary, 0);
                if (debug_opt)
                    fprintf(stderr, " ~%d", 0);
            }

        }

    }

    /* Sunday is both 0 and 7 : if one is set, set the other */
    if (max == 7) {
        if (bit_test(ary, 0))
            bit_set(ary, 7);
        else if (bit_test(ary, 7))
            bit_set(ary, 0);
    }

    Skip_blanks(ptr);

    if (debug_opt)
        fprintf(stderr, " #");

    return ptr;
}

void
delete_file(const char *user_name)
    /* free a file if user_name is not null
     *   otherwise free all files */
{
    cf_t *file = NULL;
    cf_t *prev_file = NULL;
    cl_t *line = NULL;
    cl_t *cur_line = NULL;

    file = file_base;
    while (file != NULL) {
        if (strcmp(user_name, file->cf_user) == 0) {

            /* free lines */
            cur_line = file->cf_line_base;
            while ((line = cur_line) != NULL) {
                cur_line = line->cl_next;
                free_line(line);
            }
            break;

        }
        else {
            prev_file = file;
            file = file->cf_next;
        }
    }

    if (file == NULL)
        /* file not in list */
        return;

    /* remove file from list */
    if (prev_file == NULL)
        file_base = file->cf_next;
    else
        prev_file->cf_next = file->cf_next;

    /* free env variables */
    env_list_destroy(file->cf_env_list);

    /* finally free file itself */
    Free_safe(file->cf_user);
    Free_safe(file);

}

int
save_file(char *path)
    /* Store the informations relatives to the executions
     * of tasks at a defined frequency of system's running time */
{
    cf_t *file = NULL;

    if (debug_opt)
        fprintf(stderr, "Saving ...\n");

    for (file = file_base; file; file = file->cf_next) {

        /* save_file() is run under user's rights.
         * If fcrontab is run by root for a normal user, we must change the file's
         * ownership to this user, in order to make fcron check the runas fields.
         * (a malicious user could put a runas(root) and wait for the fcrontab to be
         * installed by root) */
#ifdef USE_SETE_ID
        if (save_file_safe(file, path, "fcrontab", asuid, fcrontab_gid, 0) ==
            ERR)
            return ERR;
#else
        if (save_file_safe
            (file, path, "fcrontab", fcrontab_uid, fcrontab_gid, 0) == ERR)
            return ERR;
#endif

    }

    return OK;
}
