/*
 * FCRON - periodic command scheduler 
 *
 *  Copyright 2000-2007 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.
 */

 /* $Id: fileconf.c,v 1.80 2007/04/14 18:04:15 thib Exp thib $ */

#include "fcrontab.h"

#include "fileconf.h"

char *get_string(char *ptr);
int get_line(char *str, size_t size, FILE *file);
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);
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);


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;

    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;
	}
    }
    

    return strdup2(ptr);

}


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;

}

int
read_file(char *filename)
    /* 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));
    bzero(&default_line, sizeof(cl_t));
    need_correction = 0;
    line = 1;
    file_name = filename;

    /* open file */

    /* check if user is allowed to read file */
    if ( access(file_name, R_OK) != 0 )
	die_e("User %s can't read file \"%s\"", user, file_name);
    else if ( (file = fopen(file_name, "r")) == NULL ) {
	fprintf(stderr, "Could not open \"%s\": %s\n", file_name,
		strerror(errno));
	return ERR;
    }

    Alloc(cf, cf_t);
    cf->cf_user = strdup2(user);
    default_line.cl_file = cf;
    default_line.cl_runas = strdup2(runas);
    default_line.cl_mailto = strdup2(runas);
    default_line.cl_tz = NULL;
    set_default_opt(default_line.cl_option);

    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 '@':
	    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;

    fclose(file);
    
    free(default_line.cl_runas);
    free(default_line.cl_mailto);
    free(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];
    env_t *env = NULL;
    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' )
	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's assignment */
    if ( strcmp(name, "USER") == 0 ) {
	fprintf(stderr, "%s:%d: USER assignement is not allowed: ignored.\n",
		file_name, line);	
	return;
    }

    /* the 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, "MAILTO") == 0 ) {
	if ( strcmp(val, "\0") == 0 )
	    clear_mail(default_line.cl_option);
	else {
	    Set(default_line.cl_mailto, val);
	    set_mail(default_line.cl_option);
	}
	    
    }
    else {

	Alloc(env, env_t);	

	strncat(name, "=", sizeof(name) - strlen(name) - 1);
	name[sizeof(name)-1]='\0';
	strncat(name,val,sizeof(name)-strlen(name)-1);
	name[sizeof(name)-1]='\0';
	env->e_val = strdup2( name );
	env->e_next = cf->cf_env_base;
	cf->cf_env_base = env;
    }
    
    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;

}


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;
    else if ( *ptr == '0' )
	goto false;
    else if ( strncmp(ptr, "true", 4) == 0 ) {
	ptr += 3;
	goto true;
    }
    else if ( strncmp(ptr, "yes", 3) == 0 ) {
	ptr += 2;
	goto true;
    }
    else if ( strncmp(ptr, "false", 5) == 0 ) {
	ptr += 4;
	goto false;
    }
    else if ( strncmp(ptr, "no", 2) == 0 ) {
	ptr += 1;
	goto false;
    }
    else
	return NULL;

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

  false:
    *i = 0;
    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++;
	}

	/* 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 ) {
	    char buf[50];
	    bzero(buf, sizeof(buf));

	    if( ! in_brackets )
		Handle_err;

	    i = 0;
	    while ( *ptr != ')' && i + 1 < sizeof(buf) )
		buf[i++] = *ptr++;
	    
	    if ( strcmp(buf, "\0") == 0 ) {
		Flush(cl->cl_tz);
	    }
	    else {
		Set(cl->cl_tz, buf);
	    }
 	    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, "reset")==0 ) {
	    if ( in_brackets && (ptr = get_bool(ptr, &i)) == NULL )
		Handle_err;
	    if ( i == 1 ) {
		bzero(cl, sizeof(cl_t));
		Set(cl->cl_runas, runas);
		Set(cl->cl_mailto, runas);
		Flush(cl->cl_tz);
		set_default_opt(cl->cl_option);
	    }
	    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);
	    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);	
 	    if (debug_opt)
		fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
	}

	else if( strcmp(opt_name, "mailto") == 0) {
	    char buf[50];
	    bzero(buf, sizeof(buf));

	    if( ! in_brackets )
		Handle_err;

	    /* please note that we check if the mailto is valid in conf.c */
	    i = 0;
	    while ( *ptr != ')' && i + 1 < sizeof(buf) )
		buf[i++] = *ptr++;
	    if ( strcmp(buf, "\0") == 0 )
		clear_mail(cl->cl_option);
	    else {
		    Set(cl->cl_mailto, buf);
		    set_mail(cl->cl_option);
	    }
 	    if (debug_opt)
		fprintf(stderr, "  Opt : \"%s\" \"%s\"\n", opt_name, buf);
	}

	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) {
	    if (getuid() != rootuid) {
		fprintf(stderr, "must be privileged to use option runas: "
			"skipping option\n");
		need_correction = 1;
		if ( ! in_brackets )
		    Handle_err;
		while( *ptr != ')' && *ptr != ' ' && *ptr != '\0' )
		    ptr++;
	    }
	    else {
		char name[USER_NAME_LEN];
		struct passwd *pas;
		int i = 0;

		if( ! in_brackets )
		    Handle_err;

		bzero(name, sizeof(name));

		while ( *ptr != ')' && i + 1 < sizeof(name))
		    name[i++] = *ptr++;
    
		if ((pas = getpwnam(name)) == NULL) {
		    fprintf(stderr, "runas: \"%s\" is not in passwd file : "
			    "ignored", name);
		    need_correction = 1;
		} 
		else {
		    Set(cl->cl_runas, name);
		    if (debug_opt)
			fprintf(stderr, "  Opt : \"%s\" %s\n", opt_name, name);
		}

	    }
	}

	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);
	}

	/* 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;
}


void
read_freq(char *ptr, cf_t *cf)
    /* read a freq entry, and append a line to cf */
{
    cl_t *cl = NULL;
    
    Alloc(cl, cl_t);
    memcpy(cl, &default_line, sizeof(cl_t));
    cl->cl_runas = strdup2(default_line.cl_runas);
    cl->cl_mailto = strdup2(default_line.cl_mailto);
    if ( cl->cl_tz != NULL ) 
	cl->cl_tz = strdup2(default_line.cl_tz);
    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 < 10 ) {
	fprintf(stderr, "%s:%d: Error while reading frequency %s: skipping line.\n",
		file_name, line, (cl->cl_timefreq < 10) ? "(lower than 10s) " : "");
	goto exiterr;
    }
	
    if ( cl->cl_timefreq == 0) {
	fprintf(stderr, "%s:%d: no freq specified: skipping line.\n",
		file_name, line);
	goto exiterr;
    }

    if ( is_volatile(cl->cl_option) && cl->cl_first == -1 )
	/* time before first execution is not specified */
	cl->cl_first = cl->cl_timefreq;
    else
	cl->cl_nextexe = (cl->cl_first == -1 ) ? cl->cl_timefreq : cl->cl_first;

    /* 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(cl->cl_shell);
	goto exiterr;
    }

#ifndef SENDMAIL
    clear_mail(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(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(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;

    Alloc(cl, cl_t);
    memcpy(cl, &default_line, sizeof(cl_t));
    cl->cl_runas = strdup2(default_line.cl_runas);
    cl->cl_mailto = strdup2(default_line.cl_mailto);
    if ( cl->cl_tz != NULL ) 
	cl->cl_tz = strdup2(default_line.cl_tz);

    /* 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);
	goto exiterr;
    }

#ifndef SENDMAIL
    clear_mail(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(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;

    Alloc(cl, cl_t);
    memcpy(cl, &default_line, sizeof(cl_t));
    cl->cl_runas = strdup2(default_line.cl_runas);
    cl->cl_mailto = strdup2(default_line.cl_mailto);
    if ( cl->cl_tz != NULL ) 
	cl->cl_tz = strdup2(default_line.cl_tz);

    /* 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);
	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 SENDMAIL
    clear_mail(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(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;

	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 *num */
	    *num = *num - 1;

    }

    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 {

	    if ( (ptr = get_num(ptr, &start, max, 0, names)) == NULL )
		return NULL;

	    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++;
		if ( (ptr = get_num(ptr, &stop, max, 0, names)) == NULL )
		    return NULL;
	    } 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 (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;
    env_t *env = NULL;
    env_t *cur_env = 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->cl_shell);
		free(line->cl_mailto);
		free(line->cl_runas);
		free(line->cl_tz);
		free(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 */
    cur_env = file->cf_env_base;
    while  ( (env = cur_env) != NULL ) {
	cur_env = env->e_next;
	free(env->e_val);
	free(env);
    }

    /* finally free file itself */
    free(file->cf_user);
    free(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) */
	if ( save_file_safe(file, path, "fcrontab", asuid, fcrontab_gid, 0) == ERR )
	    return ERR;

    }

    return OK;
}


syntax highlighted by Code2HTML, v. 0.9.1