2 * FCRON - periodic command scheduler
4 * Copyright 2000-2014 Thibault Godouet <fcron@free.fr>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * The GNU General Public License can also be found in the file
21 * `LICENSE' that comes with the fcron source distribution.
29 char *get_string(char *ptr);
30 int get_line(char *str, size_t size, FILE * file);
31 void init_default_line(cl_t * cl, cf_t * cf);
32 char *get_time(char *ptr, time_t * time, int zero_allowed);
33 char *get_num(char *ptr, int *num, int max, short int decimal,
35 char *get_nice(char *ptr, int *nice);
36 char *get_bool(char *ptr, int *i);
37 char *read_field(char *ptr, bitstr_t * ary, int max, const char **names);
38 void read_freq(char *ptr, cf_t * cf);
39 void read_arys(char *ptr, cf_t * cf);
40 void read_period(char *ptr, cf_t * cf);
41 int read_shortcut(char *ptr, cf_t * cf);
42 void read_env(char *ptr, cf_t * cf);
43 char *read_opt(char *ptr, cl_t * cl);
44 char *check_username(char *ptr, cf_t * cf, cl_t * cl);
45 size_t option_strlen(char *value);
48 cl_t default_line; /* default options for a line */
52 /* warning : all names must have the same length */
53 const char *dows_ary[] = {
54 "sun", "mon", "tue", "wed", "thu", "fri", "sat",
58 /* warning : all names must have the same length */
59 const char *mons_ary[] = {
60 "jan", "feb", "mar", "apr", "may", "jun",
61 "jul", "aug", "sep", "oct", "nov", "dec",
66 #define GET_LINE_EOF 999
70 /* read string pointed by ptr, remove blanks and manage
71 * string placed in quotes */
72 /* return NULL on mismatched quotes */
76 char *rtn_string = NULL;
78 if (*ptr == '\"' || *ptr == '\'') {
83 length = remove_blanks(ptr);
86 if (*(ptr + length - 1) == quote)
87 *(ptr + length - 1) = '\0';
89 /* mismatched quotes */
102 get_line(char *str, size_t size, FILE * file)
103 /* similar to fgets, but increase line if necessary,
104 * and continue over an "\" followed by an "\n" char */
106 size_t size_max = size - 1;
110 while (i < size_max) {
112 switch (c = getc(file)) {
115 /* check if the \n char is preceded by a "\" char :
116 * in this case, suppress the "\", don't copy the \n,
118 if (i > 0 && *(str + i - 1) == '\\') {
124 *(str + i) = (char)'\0';
130 *(str + i) = (char)'\0';
131 /* we couldn't return EOF ( equal to ERR by default )
132 * nor ERR, which is used for another error */
136 *(str + i) = (char)c;
143 /* line is too long : goto next line and return ERR */
144 while (((c = getc(file)) != EOF) && (c != '\n')) ;
152 init_default_line(cl_t * cl, cf_t * cf)
153 /* clear all context/options from cl */
155 bzero(cl, sizeof(cl_t));
156 Set(cl->cl_runas, runas);
157 Set(cl->cl_mailto, runas);
158 Free_safe(cl->cl_tz);
159 set_default_opt(cl->cl_option);
165 read_file(char *filename, int fd)
166 /* read file "name" and append cf_t list */
172 int max_entries = MAXENTRIES;
177 bzero(buf, sizeof(buf));
180 file_name = filename;
184 if ((file = fdopen(fd, "r")) == NULL) {
185 fprintf(stderr, "Could not open \"%s\": %s\n", file_name,
190 /* Rewind, just in case */
194 cf->cf_env_list = env_list_init();
195 Set(cf->cf_user, user);
196 init_default_line(&default_line, cf);
199 fprintf(stderr, "FILE %s\n", file_name);
201 if (strcmp(runas, ROOTNAME) == 0)
204 /* max_lines acts here as a security counter to avoid endless loop. */
205 max_lines = (max_entries * 10) + 10;
207 while (entries < max_entries && line <= max_lines) {
209 ret = get_line(buf, sizeof(buf), file);
213 "%s:%d: Line is too long (more than %d): skipping line.\n",
214 file_name, line, (int)sizeof(buf));
221 if (debug_opt && *ptr != '#' && *ptr != '\0')
222 fprintf(stderr, " %s\n", buf);
227 /* comments or empty line: skipping */
230 /* if it is not a shortcut line then read_shortcut() won't do anything. */
231 if (!read_shortcut(ptr, cf))
240 read_period(ptr, cf);
244 ptr = read_opt(ptr, &default_line);
245 if (ptr != NULL && *ptr != '\0') {
246 fprintf(stderr, "%s:%d: Syntax error: string \"%s\" ignored\n",
247 file_name, line, ptr);
252 if (isdigit((int)*ptr) || *ptr == '*') {
263 /* in this case, ret == GET_LINE_EOF :
264 * no more lines, so we exit the loop */
269 if (entries == max_entries) {
270 error("%s:%d: maximum number of entries (%d) has been reached by %s",
271 file_name, line, user);
272 fprintf(stderr, "Anything after this line will be ignored\n");
274 else if (line == max_lines)
275 error("%s:%d: maximum number of lines (%d) has been reached by %s",
276 file_name, line, user);
278 cf->cf_next = file_base;
281 /* don't close as underlying fd may still be used by calling function */
282 if (fflush(file) != 0)
283 error_e("could not fflush() file_name");
285 Free_safe(default_line.cl_runas);
286 Free_safe(default_line.cl_mailto);
287 Free_safe(default_line.cl_tz);
289 if (!need_correction)
297 read_env(char *ptr, cf_t * cf)
298 /* append env variable list.
305 bzero(name, sizeof(name));
307 /* copy env variable's name */
308 while ((isalnum((int)*ptr) || *ptr == '_') && *ptr != '='
309 && !isspace((int)*ptr) && j < sizeof(name)) {
318 /* skip '=' and spaces around */
319 while (isspace((int)*ptr))
322 /* if j == 0 name is a zero length string */
323 if (*ptr++ != '=' || j == 0)
326 while (isspace((int)*ptr))
330 if ((val = get_string(ptr)) == NULL) {
331 fprintf(stderr, "%s:%d: Mismatched quotes: skipping line.\n",
338 fprintf(stderr, " Env : '%s=%s'\n", name, val);
340 /* we ignore USER/LOGNAME's assignment */
341 if (strcmp(name, "USER") == 0 || strcmp(name, "LOGNAME") == 0) {
343 "%s:%d: USER or LOGNAME assignement is not allowed: ignored.\n",
348 /* the MAILTO assignment is, in fact, an fcron option :
349 * we don't store it in the same way. */
350 /* please note that we check if the mailto is valid in conf.c */
351 if (strcmp(name, "MAILTO") == 0) {
352 if (strcmp(val, "\0") == 0) {
353 clear_mail(default_line.cl_option);
354 clear_mailzerolength(default_line.cl_option);
357 Set(default_line.cl_mailto, val);
358 set_mail(default_line.cl_option);
363 env_list_setenv(cf->cf_env_list, name, val, 1);
371 fprintf(stderr, "%s:%d: Syntax error: skipping line.\n", file_name, line);
379 get_nice(char *ptr, int *nice)
380 /* read a nice value and put it in variable nice */
389 if ((ptr = get_num(ptr, nice, 20, 0, NULL)) == NULL)
393 if (getuid() != rootuid) {
394 fprintf(stderr, "must be privileged to use a negative argument "
395 "with nice: set to 0\n");
408 option_strlen(char *value)
409 /* return the length of the string value of an option */
414 /* look for the end of the option value */
415 while (ptr != NULL && *ptr != ')' && *ptr != '\0') {
424 assign_option_string(char **var, char *value)
425 /* Read the value of an option: if non-empty, assign it to the var,
426 * otherwise set to NULL.
427 * Returns the length of the value (0 if empty), or -1 on error. */
429 char start = value[0];
435 len = option_strlen(value);
441 end = value[len - 1];
443 /* spaces and quotes are not allowed before or after the value */
444 if (isspace((int)start) || isspace((int)end)
445 || start == '\'' || start == '"' || end == '\'' || end == '"') {
449 *var = strndup2(value, len);
456 get_bool(char *ptr, int *i)
457 /* get a bool value : either true (1) or false (0)
458 * return NULL on error */
462 else if (*ptr == '0')
464 else if (strncmp(ptr, "true", 4) == 0) {
468 else if (strncmp(ptr, "yes", 3) == 0) {
472 else if (strncmp(ptr, "false", 5) == 0) {
476 else if (strncmp(ptr, "no", 2) == 0) {
497 read_opt(char *ptr, cl_t * cl)
498 /* read one or several options and fill in the field "option" */
506 fprintf(stderr, "%s:%d: Argument(s) for option \"%s\" not valid: " \
507 "skipping end of line.\n", file_name, line, opt_name); \
508 need_correction = 1; \
517 bzero(opt_name, sizeof(opt_name));
519 while (isalnum((int)*ptr) && i < sizeof(opt_name))
520 opt_name[i++] = *ptr++;
529 /* spaces are not allowed -- make sure there is no leading space. */
530 if (isspace((int)*ptr)) {
535 /* global options for a file */
537 if (strcmp(opt_name, "tzdiff") == 0) {
546 if ((ptr = get_num(ptr, &i, 24, 0, NULL)) == NULL)
549 cl->cl_file->cf_tzdiff = (-i);
551 cl->cl_file->cf_tzdiff = i;
554 fprintf(stderr, " Opt : \"%s\" (-)%d\n", opt_name, i);
557 /* options related to a line (or a set of lines) */
559 else if (strcmp(opt_name, "timezone") == 0) {
566 /* assign_option_string() will set cl_tz to NULL is the value is empty,
567 * which means "use the system timezone" */
568 len = assign_option_string(&(cl->cl_tz), ptr);
577 fprintf(stderr, " Opt : \"%s\" \"%s\"\n", opt_name, cl->cl_tz);
582 else if (strcmp(opt_name, "s") == 0 || strcmp(opt_name, "serial") == 0) {
583 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
586 clear_serial(cl->cl_option);
588 set_serial(cl->cl_option);
590 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
593 else if (strcmp(opt_name, "serialonce") == 0) {
594 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
597 set_serial_sev(cl->cl_option);
599 clear_serial_sev(cl->cl_option);
601 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
604 else if (strcmp(opt_name, "lavgonce") == 0) {
605 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
608 set_lavg_sev(cl->cl_option);
610 clear_lavg_sev(cl->cl_option);
612 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
615 else if (strcmp(opt_name, "exesev") == 0) {
616 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
619 clear_exe_sev(cl->cl_option);
621 set_exe_sev(cl->cl_option);
623 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
626 else if (strcmp(opt_name, "b") == 0 || strcmp(opt_name, "bootrun") == 0) {
627 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
630 clear_bootrun(cl->cl_option);
632 set_bootrun(cl->cl_option);
634 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
637 else if (strcmp(opt_name, "rebootreset") == 0) {
638 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
641 clear_rebootreset(cl->cl_option);
643 set_rebootreset(cl->cl_option);
645 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
649 else if (strcmp(opt_name, "runatreboot") == 0) {
650 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
653 clear_runatreboot(cl->cl_option);
655 set_runatreboot(cl->cl_option);
657 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
661 else if (strcmp(opt_name, "runonce") == 0) {
662 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
665 clear_runonce(cl->cl_option);
667 set_runonce(cl->cl_option);
669 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
672 else if (strcmp(opt_name, "reset") == 0) {
673 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
676 init_default_line(cl, cl->cl_file);
679 fprintf(stderr, " Opt : \"%s\"\n", opt_name);
682 else if (strcmp(opt_name, "f") == 0 || strcmp(opt_name, "first") == 0) {
684 || (ptr = get_time(ptr, &(cl->cl_first), 1)) == NULL)
687 fprintf(stderr, " Opt : \"%s\" %ld\n", opt_name,
688 (long int)cl->cl_first);
691 else if (strcmp(opt_name, "r") == 0 || strcmp(opt_name, "runfreq") == 0) {
692 if (cl->cl_runfreq == 1) {
693 fprintf(stderr, "cannot change runfreq value in a %%-line");
697 || (ptr = get_num(ptr, &i, USHRT_MAX, 0, NULL)) == NULL)
700 fprintf(stderr, "runfreq must be 2 or more.\n");
705 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
708 else if (strcmp(opt_name, "strict") == 0) {
709 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
712 clear_strict(cl->cl_option);
714 set_strict(cl->cl_option);
716 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
719 else if (strcmp(opt_name, "noticenotrun") == 0) {
720 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
723 clear_notice_notrun(cl->cl_option);
725 set_notice_notrun(cl->cl_option);
727 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
730 else if (strcmp(opt_name, "lavg") == 0) {
732 || (ptr = get_num(ptr, &i, UCHAR_MAX, 1, NULL)) == NULL)
736 fprintf(stderr, " Opt : 'lavg1' %d\n", i);
740 || (ptr = get_num(ptr, &i, UCHAR_MAX, 1, NULL)) == NULL)
744 fprintf(stderr, " Opt : 'lavg5' %d\n", i);
748 || (ptr = get_num(ptr, &i, UCHAR_MAX, 1, NULL)) == NULL)
752 fprintf(stderr, " Opt : 'lavg15' %d\n", i);
753 if (cl->cl_lavg[0] || cl->cl_lavg[1] || cl->cl_lavg[2])
754 set_lavg(cl->cl_option);
756 clear_lavg(cl->cl_option);
758 warn("As fcron has been compiled with no procfs support,\n"
759 "you will not be able to use the lavg* options");
760 #endif /* NOLOADAVG */
763 else if (strcmp(opt_name, "lavg1") == 0) {
765 || (ptr = get_num(ptr, &i, UCHAR_MAX, 1, NULL)) == NULL)
768 if (cl->cl_lavg[0] || cl->cl_lavg[1] || cl->cl_lavg[2])
769 set_lavg(cl->cl_option);
771 clear_lavg(cl->cl_option);
773 warn("As fcron has been compiled with no procfs support,\n"
774 "you will not be able to use the lavg* options");
775 #endif /* NOLOADAVG */
777 fprintf(stderr, " Opt : 'lavg1' %d\n", i);
780 else if (strcmp(opt_name, "lavg5") == 0) {
782 || (ptr = get_num(ptr, &i, UCHAR_MAX, 1, NULL)) == NULL)
785 if (cl->cl_lavg[0] || cl->cl_lavg[1] || cl->cl_lavg[2])
786 set_lavg(cl->cl_option);
788 clear_lavg(cl->cl_option);
790 warn("As fcron has been compiled with no procfs support,\n"
791 "you will not be able to use the lavg* options");
792 #endif /* NOLOADAVG = 0 */
794 fprintf(stderr, " Opt : 'lavg5' %d\n", i);
797 else if (strcmp(opt_name, "lavg15") == 0) {
799 || (ptr = get_num(ptr, &i, UCHAR_MAX, 1, NULL)) == NULL)
802 if (cl->cl_lavg[0] || cl->cl_lavg[1] || cl->cl_lavg[2])
803 set_lavg(cl->cl_option);
805 clear_lavg(cl->cl_option);
807 warn("As fcron has been compiled with no procfs support,\n"
808 "you will not be able to use the lavg* options");
809 #endif /* NOLOADAVG = 0 */
811 fprintf(stderr, " Opt : 'lavg15' %d\n", i);
814 else if (strcmp(opt_name, "lavgand") == 0) {
815 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
818 set_lor(cl->cl_option);
820 set_land(cl->cl_option);
822 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
825 else if (strcmp(opt_name, "lavgor") == 0) {
826 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
829 set_land(cl->cl_option);
831 set_lor(cl->cl_option);
833 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
836 else if (strcmp(opt_name, "u") == 0 || strcmp(opt_name, "until") == 0) {
838 || (ptr = get_time(ptr, &(cl->cl_until), 0)) == NULL)
841 fprintf(stderr, " Opt : \"%s\" %ld\n", opt_name,
842 (long int)cl->cl_until);
845 else if (strcmp(opt_name, "m") == 0 || strcmp(opt_name, "mail") == 0) {
846 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
849 clear_mail(cl->cl_option);
850 clear_mailzerolength(cl->cl_option);
853 set_mail(cl->cl_option);
855 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
858 else if (strcmp(opt_name, "forcemail") == 0) {
859 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
862 clear_mailzerolength(cl->cl_option);
864 set_mailzerolength(cl->cl_option);
865 set_mail(cl->cl_option);
868 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
871 else if (strcmp(opt_name, "mailto") == 0) {
878 /* assign_option_string() set the value to NULL if the length is zero.
879 * However cl_mailto must not be NULL (as expected in
880 * conf.c:add_line_to_file()), so we check if the length is >= 0
881 * before calling assign_option_string() */
882 /* Also please note that we check if the mailto is valid in conf.c */
883 len = option_strlen(ptr);
885 clear_mail(cl->cl_option);
886 clear_mailzerolength(cl->cl_option);
888 fprintf(stderr, " Opt : \"mail\" 0\n");
889 fprintf(stderr, " Opt : \"forcemail\" 0\n");
893 len = assign_option_string(&(cl->cl_mailto), ptr);
899 set_mail(cl->cl_option);
904 fprintf(stderr, " Opt : \"%s\" \"%s\"\n", opt_name,
908 else if (strcmp(opt_name, "erroronlymail") == 0) {
909 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
912 clear_erroronlymail(cl->cl_option);
914 set_erroronlymail(cl->cl_option);
916 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
919 else if (strcmp(opt_name, "dayand") == 0) {
920 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
923 set_dayor(cl->cl_option);
925 set_dayand(cl->cl_option);
927 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
930 else if (strcmp(opt_name, "dayor") == 0) {
931 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
934 set_dayand(cl->cl_option);
936 set_dayor(cl->cl_option);
938 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
941 else if (strcmp(opt_name, "nolog") == 0) {
942 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
945 clear_nolog(cl->cl_option);
947 set_nolog(cl->cl_option);
949 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
952 else if (strcmp(opt_name, "volatile") == 0) {
953 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
956 clear_volatile(cl->cl_option);
958 set_volatile(cl->cl_option);
960 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
963 else if (strcmp(opt_name, "stdout") == 0) {
964 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
967 clear_stdout(cl->cl_option);
969 set_stdout(cl->cl_option);
971 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
974 else if (strcmp(opt_name, "n") == 0 || strcmp(opt_name, "nice") == 0) {
975 if (!in_brackets || (ptr = get_nice(ptr, &i)) == NULL)
977 cl->cl_nice = (char)i;
979 fprintf(stderr, " Opt : \"%s\" (-)%d\n", opt_name, i);
982 else if (strcmp(opt_name, "runas") == 0) {
991 len = assign_option_string(&runas, ptr);
999 if (getuid() != rootuid) {
1000 fprintf(stderr, "must be privileged to use option runas: "
1001 "skipping option\n");
1002 need_correction = 1;
1004 else if (len > USER_NAME_LEN) {
1005 fprintf(stderr, "runas: user name \"%s\" longer than %d"
1006 "characters: skipping option\n", runas, USER_NAME_LEN);
1007 need_correction = 1;
1009 else if ((pas = getpwnam(runas)) == NULL) {
1010 fprintf(stderr, "runas: \"%s\" is not in passwd file : "
1012 need_correction = 1;
1015 if (need_correction) {
1019 /* only set cl_runas if all is well, as cl_runas MUST always
1020 * be set to a valid value */
1021 Set(cl->cl_runas, runas);
1026 fprintf(stderr, " Opt : \"%s\" %s ptr=%p\n", opt_name,
1027 cl->cl_runas, cl->cl_runas);
1031 else if (strcmp(opt_name, "random") == 0) {
1032 if (in_brackets && (ptr = get_bool(ptr, &i)) == NULL)
1035 clear_random(cl->cl_option);
1037 set_random(cl->cl_option);
1039 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
1042 else if (strcmp(opt_name, "jitter") == 0) {
1044 || (ptr = get_num(ptr, &i, UCHAR_MAX, 0, NULL)) == NULL)
1048 fprintf(stderr, " Opt : \"%s\" %d\n", opt_name, i);
1051 /* handle %-line : we check if we are really in a %-line (which we do not do
1052 * for other options), because writing "&hourly" in a fcrontab results in an
1053 * error (hourly ignored) hard to find, and, in any case, annoying. */
1054 else if (cl->cl_runfreq == 1) {
1055 /* options to run once per interval :
1056 * ignore every fields below the limit */
1057 if (strcmp(opt_name, "mins") == 0) {
1060 fprintf(stderr, " Opt : \"%s\"\n", opt_name);
1062 else if (strcmp(opt_name, "hours") == 0) {
1063 set_freq_mins(cl->cl_option);
1065 fprintf(stderr, " Opt : \"%s\"\n", opt_name);
1067 else if (strcmp(opt_name, "days") == 0) {
1068 set_freq_mins(cl->cl_option);
1069 set_freq_hrs(cl->cl_option);
1071 fprintf(stderr, " Opt : \"%s\"\n", opt_name);
1073 else if (strcmp(opt_name, "mons") == 0) {
1074 set_freq_mins(cl->cl_option);
1075 set_freq_hrs(cl->cl_option);
1076 set_freq_days(cl->cl_option);
1078 fprintf(stderr, " Opt : \"%s\"\n", opt_name);
1080 else if (strcmp(opt_name, "dow") == 0) {
1081 set_freq_mins(cl->cl_option);
1082 set_freq_hrs(cl->cl_option);
1083 set_freq_days(cl->cl_option);
1084 set_freq_mons(cl->cl_option);
1086 fprintf(stderr, " Opt : \"%s\"\n", opt_name);
1089 /* run once an element of the selected field
1090 * (once an hour, once a day, etc) */
1091 else if (strcmp(opt_name, "hourly") == 0) {
1092 set_freq_hrs(cl->cl_option);
1093 set_freq_periodically(cl->cl_option);
1095 fprintf(stderr, " Opt : \"%s\"\n", opt_name);
1097 else if (strcmp(opt_name, "daily") == 0) {
1098 set_freq_days(cl->cl_option);
1099 set_freq_periodically(cl->cl_option);
1101 fprintf(stderr, " Opt : \"%s\"\n", opt_name);
1103 else if (strcmp(opt_name, "monthly") == 0) {
1104 set_freq_mons(cl->cl_option);
1105 set_freq_periodically(cl->cl_option);
1107 fprintf(stderr, " Opt : \"%s\"\n", opt_name);
1109 else if (strcmp(opt_name, "weekly") == 0) {
1110 set_freq_dow(cl->cl_option);
1111 set_freq_periodically(cl->cl_option);
1113 fprintf(stderr, " Opt : \"%s\"\n", opt_name);
1116 /* run once an element of the selected field
1117 * from middle to middle of that field
1118 * (ie once from 12h to 12h the following day) */
1119 else if (strcmp(opt_name, "midhourly") == 0) {
1120 set_freq_hrs(cl->cl_option);
1121 set_freq_periodically(cl->cl_option);
1122 set_freq_mid(cl->cl_option);
1124 fprintf(stderr, " Opt : \"%s\"\n", opt_name);
1126 else if (strcmp(opt_name, "middaily") == 0
1127 || strcmp(opt_name, "nightly") == 0) {
1128 set_freq_days(cl->cl_option);
1129 set_freq_periodically(cl->cl_option);
1130 set_freq_mid(cl->cl_option);
1132 fprintf(stderr, " Opt : \"%s\"\n", opt_name);
1134 else if (strcmp(opt_name, "midmonthly") == 0) {
1135 set_freq_mons(cl->cl_option);
1136 set_freq_periodically(cl->cl_option);
1137 set_freq_mid(cl->cl_option);
1139 fprintf(stderr, " Opt : \"%s\"\n", opt_name);
1141 else if (strcmp(opt_name, "midweekly") == 0) {
1142 set_freq_dow(cl->cl_option);
1143 set_freq_periodically(cl->cl_option);
1144 set_freq_mid(cl->cl_option);
1146 fprintf(stderr, " Opt : \"%s\"\n", opt_name);
1151 fprintf(stderr, "%s:%d: Option \"%s\" unknown: "
1152 "skipping option.\n", file_name, line, opt_name);
1153 need_correction = 1;
1163 } while (*ptr == ',' && ptr++);
1171 get_time(char *ptr, time_t * time, int zero_allowed)
1172 /* convert time read in string in time_t format */
1178 while ((*ptr != ' ') && (*ptr != '\t') && (*ptr != '\0') && (*ptr != ')')) {
1182 while (isdigit((int)*ptr)) {
1188 /* interpret multipliers */
1190 case 'm': /* months */
1192 case 'w': /* weeks */
1194 case 'd': /* days */
1196 case 'h': /* hours */
1198 case 's': /* seconds */
1204 sum *= 60; /* minutes */
1207 need_correction = 1;
1216 if (*time == 0 && !zero_allowed) {
1217 need_correction = 1;
1226 check_username(char *ptr, cf_t * cf, cl_t * cl)
1227 /* check ptr to see if the first word is a username, returns new ptr */
1230 char username[USER_NAME_LEN];
1231 struct passwd *userpwent;
1233 /* check to see if next word is a username */
1234 /* we don't allow quotes, to be able to distinguish a user name from
1235 * a command line (where quotes are allowed) */
1236 while (isalnum((int)ptr[indx]) || ptr[indx] == '-' || ptr[indx] == '_')
1238 if (indx >= USER_NAME_LEN)
1239 indx = USER_NAME_LEN - 1;
1240 strncpy(username, ptr, indx);
1241 username[indx] = '\0';
1243 if ((userpwent = getpwnam(username)) != NULL) {
1244 /* found the user */
1245 ptr = ptr + indx; /* move ptr to the next word */
1248 if (getuid() != rootuid) {
1249 fprintf(stderr, "must be privileged to run as another user : "
1253 Set(cl->cl_runas, username);
1255 fprintf(stderr, " Opt : inline_runas %s\n", username);
1264 read_word(char *ptr, char *buf, size_t buf_size)
1265 /* copy a word to buf with a max_size of buf_size and return a pointer
1266 * to the next char after the word */
1270 bzero(buf, buf_size);
1272 while (isalnum((int)*ptr) && i < buf_size)
1279 read_shortcut(char *ptr, cf_t * cf)
1280 /* try to read a shortcut entry, and if it is one then append a line to cf
1281 * Return 1 if that was a shortcut entry. If it wasn't, return 0 and make sure
1282 * ptr is back to its orig value. */
1286 char *ptr_orig = ptr;
1288 cl = dups_cl(&default_line);
1293 ptr = read_word(ptr, shortcut, sizeof(shortcut));
1295 /* time&date by default -- we'll switch to freq if @reboot */
1296 set_td(cl->cl_option);
1297 cl->cl_remain = cl->cl_runfreq; /* FIXME: necessary?? */
1299 if (strcmp(shortcut, "reboot") == 0) {
1300 set_freq(cl->cl_option);
1301 set_runatreboot(cl->cl_option);
1302 set_runonce(cl->cl_option);
1303 clear_volatile(cl->cl_option);
1306 /* the job will not be rescheduled after the first execution (flag is_hasrun),
1307 * we set timefreq to LONG_MAX just in case */
1308 cl->cl_timefreq = LONG_MAX;
1311 fprintf(stderr, " Shc : @reboot\n");
1313 else if (strcmp(shortcut, "yearly") == 0
1314 || strcmp(shortcut, "annually") == 0) {
1315 bit_set(cl->cl_mins, 0);
1316 bit_set(cl->cl_hrs, 0);
1317 bit_set(cl->cl_days, 1);
1318 bit_set(cl->cl_mons, 0);
1319 bit_nset(cl->cl_dow, 0, 7);
1322 fprintf(stderr, " Shc : @yearly\n");
1324 else if (strcmp(shortcut, "monthly") == 0) {
1325 bit_set(cl->cl_mins, 0);
1326 bit_set(cl->cl_hrs, 0);
1327 bit_set(cl->cl_days, 1);
1328 bit_nset(cl->cl_mons, 0, 11);
1329 bit_nset(cl->cl_dow, 0, 7);
1332 fprintf(stderr, " Shc : @monthly\n");
1334 else if (strcmp(shortcut, "weekly") == 0) {
1335 bit_set(cl->cl_mins, 0);
1336 bit_set(cl->cl_hrs, 0);
1337 bit_nset(cl->cl_days, 0, 31);
1338 bit_nset(cl->cl_mons, 0, 11);
1339 bit_set(cl->cl_dow, 0);
1340 bit_set(cl->cl_dow, 7); /* 0 and 7 are both sunday */
1343 fprintf(stderr, " Shc : @weekly\n");
1345 else if (strcmp(shortcut, "daily") == 0
1346 || strcmp(shortcut, "midnight") == 0) {
1347 bit_set(cl->cl_mins, 0);
1348 bit_set(cl->cl_hrs, 0);
1349 bit_nset(cl->cl_days, 0, 31);
1350 bit_nset(cl->cl_mons, 0, 11);
1351 bit_nset(cl->cl_dow, 0, 7);
1354 fprintf(stderr, " Shc : @daily\n");
1356 else if (strcmp(shortcut, "hourly") == 0) {
1357 bit_set(cl->cl_mins, 0);
1358 bit_nset(cl->cl_hrs, 0, 23);
1359 bit_nset(cl->cl_days, 0, 31);
1360 bit_nset(cl->cl_mons, 0, 11);
1361 bit_nset(cl->cl_dow, 0, 7);
1364 fprintf(stderr, " Shc : @hourly\n");
1367 /* this is not a shortcut line but a normal @-line: */
1372 /* The next char must be a space (no other option allowed when using
1373 * a shortcut: if the user wants to use options, they should use the
1374 * native fcron lines */
1375 if (!isspace((int)*ptr)) {
1376 fprintf(stderr, "%s:%d: No space after shortcut: skipping line.\n",
1381 /* skip spaces before the username / shell command */
1382 while (isspace((int)*ptr))
1385 /* check for inline runas */
1386 ptr = check_username(ptr, cf, cl);
1388 /* get cl_shell field ( remove trailing blanks ) */
1389 if ((cl->cl_shell = get_string(ptr)) == NULL) {
1390 fprintf(stderr, "%s:%d: Mismatched quotes: skipping line.\n",
1394 if (strcmp(cl->cl_shell, "\0") == 0) {
1395 fprintf(stderr, "%s:%d: No shell command: skipping line.\n",
1397 Free_safe(cl->cl_shell);
1401 #ifndef USE_SENDMAIL
1402 clear_mail(cl->cl_option);
1403 clear_mailzerolength(cl->cl_option);
1406 cl->cl_next = cf->cf_line_base;
1407 cf->cf_line_base = cl;
1410 fprintf(stderr, " Cmd \"%s\" (shortcut)\n", cl->cl_shell);
1415 need_correction = 1;
1420 read_freq(char *ptr, cf_t * cf)
1421 /* read a freq entry, and append a line to cf */
1425 cl = dups_cl(&default_line);
1427 cl->cl_first = -1; /* 0 is a valid value, so we have to use -1 to detect unset */
1432 /* get the time before first execution or the options */
1433 if (isdigit((int)*ptr)) {
1434 if ((ptr = get_time(ptr, &(cl->cl_first), 1)) == NULL) {
1435 fprintf(stderr, "%s:%d: Error while reading first delay:"
1436 " skipping line.\n", file_name, line);
1442 else if (isalnum((int)*ptr)) {
1443 if ((ptr = read_opt(ptr, cl)) == NULL)
1449 /* we set this here, because it may be unset by read_opt (reset option) */
1451 set_freq(cl->cl_option);
1453 /* then cl_timefreq */
1454 if ((ptr = get_time(ptr, (time_t *) & (cl->cl_timefreq), 0)) == NULL
1455 || cl->cl_timefreq < 1) {
1457 "%s:%d: Error while reading frequency %s: skipping line.\n",
1459 (cl->cl_timefreq < 10) ? "(lower than 10s) " : "");
1463 if (cl->cl_timefreq == 0) {
1464 fprintf(stderr, "%s:%d: no freq specified: skipping line.\n",
1469 if (cl->cl_first == -1)
1470 /* time before first execution was not specified explicitely */
1471 cl->cl_first = cl->cl_timefreq;
1473 /* check for inline runas */
1474 ptr = check_username(ptr, cf, cl);
1476 /* get cl_shell field ( remove trailing blanks ) */
1477 if ((cl->cl_shell = get_string(ptr)) == NULL) {
1478 fprintf(stderr, "%s:%d: Mismatched quotes: skipping line.\n",
1482 if (strcmp(cl->cl_shell, "\0") == 0) {
1483 fprintf(stderr, "%s:%d: No shell command: skipping line.\n",
1485 Free_safe(cl->cl_shell);
1489 #ifndef USE_SENDMAIL
1490 clear_mail(cl->cl_option);
1491 clear_mailzerolength(cl->cl_option);
1494 cl->cl_next = cf->cf_line_base;
1495 cf->cf_line_base = cl;
1498 fprintf(stderr, " Cmd \"%s\", timefreq %ld, first %ld\n",
1499 cl->cl_shell, cl->cl_timefreq, (long int)cl->cl_first);
1505 need_correction = 1;
1511 #define R_field(PTR, ARY, MAX, ARYCONST, DESCRP) \
1512 if((PTR = read_field(PTR, ARY, MAX, ARYCONST)) == NULL) { \
1514 fprintf(stderr, "\n"); \
1515 fprintf(stderr, "%s:%d: Error while reading " DESCRP " field: " \
1516 "skipping line.\n", file_name, line); \
1523 read_arys(char *ptr, cf_t * cf)
1524 /* read a run freq number plus a normal fcron line */
1529 cl = dups_cl(&default_line);
1531 /* set cl_remain if not specified */
1534 if (isdigit((int)*ptr)) {
1535 if ((ptr = get_num(ptr, &i, USHRT_MAX, 0, NULL)) == NULL) {
1536 fprintf(stderr, "%s:%d: Error while reading runfreq:"
1537 " skipping line.\n", file_name, line);
1542 fprintf(stderr, "%s:%d: runfreq must be 2 or more :"
1543 " skipping line.\n", file_name, line);
1546 cl->cl_runfreq = (unsigned short)i;
1549 else if (isalnum((int)*ptr))
1550 if ((ptr = read_opt(ptr, cl)) == NULL) {
1556 cl->cl_remain = cl->cl_runfreq;
1558 /* we set this here, because it may be unset by read_opt (reset option) */
1559 set_td(cl->cl_option);
1562 fprintf(stderr, " ");
1564 /* get the fields (check for errors) */
1565 R_field(ptr, cl->cl_mins, 59, NULL, "minutes");
1566 R_field(ptr, cl->cl_hrs, 23, NULL, "hours");
1567 R_field(ptr, cl->cl_days, 31, NULL, "days");
1568 /* month are defined by user from 1 to 12 : max is 12 */
1569 R_field(ptr, cl->cl_mons, 12, mons_ary, "months");
1570 R_field(ptr, cl->cl_dow, 7, dows_ary, "days of week");
1573 /* if debug_opt is set, we print informations in read_field function,
1574 * but no end line : we print it here */
1575 fprintf(stderr, " remain %d\n", cl->cl_remain);
1577 /* check for inline runas */
1578 ptr = check_username(ptr, cf, cl);
1580 /* get the shell command (remove trailing blanks) */
1581 if ((cl->cl_shell = get_string(ptr)) == NULL) {
1582 fprintf(stderr, "%s:%d: Mismatched quotes: skipping line.\n",
1586 if (strcmp(cl->cl_shell, "\0") == 0) {
1587 fprintf(stderr, "%s:%d: No shell command: skipping line.\n",
1589 Free_safe(cl->cl_shell);
1593 #ifndef USE_SENDMAIL
1594 clear_mail(cl->cl_option);
1595 clear_mailzerolength(cl->cl_option);
1598 cl->cl_next = cf->cf_line_base;
1599 cf->cf_line_base = cl;
1602 fprintf(stderr, " Cmd \"%s\"\n", cl->cl_shell);
1606 need_correction = 1;
1613 read_period(char *ptr, cf_t * cf)
1614 /* read a line to run periodically (i.e. once a day, once a week, etc) */
1617 short int remain = 8;
1619 cl = dups_cl(&default_line);
1624 /* a runfreq set to 1 means : this is a periodical line
1625 * (runfreq cannot be changed by read_opt() if already set to 1) */
1626 cl->cl_remain = cl->cl_runfreq = 1;
1628 /* set cl_remain if not specified */
1629 if ((ptr = read_opt(ptr, cl)) == NULL) {
1634 /* we set this here, because it may be unset by read_opt (reset option) */
1635 set_td(cl->cl_option);
1638 fprintf(stderr, " ");
1640 if (is_freq_periodically(cl->cl_option)) {
1641 if (is_freq_mins(cl->cl_option))
1643 else if (is_freq_hrs(cl->cl_option))
1645 else if (is_freq_days(cl->cl_option))
1647 else if (is_freq_mons(cl->cl_option))
1649 else if (is_freq_dow(cl->cl_option))
1653 /* get the fields (check for errors) */
1655 R_field(ptr, cl->cl_mins, 59, NULL, "minutes")}
1657 bit_nset(cl->cl_mins, 0, 59);
1659 R_field(ptr, cl->cl_hrs, 23, NULL, "hours")}
1661 bit_nset(cl->cl_hrs, 0, 23);
1663 R_field(ptr, cl->cl_days, 31, NULL, "days")}
1665 bit_nset(cl->cl_days, 1, 32);
1666 /* month are defined by user from 1 to 12 : max is 12 */
1668 R_field(ptr, cl->cl_mons, 12, mons_ary, "months")}
1670 bit_nset(cl->cl_mons, 0, 11);
1672 R_field(ptr, cl->cl_dow, 7, dows_ary, "days of week")}
1674 bit_nset(cl->cl_dow, 0, 7);
1677 /* if debug_opt is set, we print informations in read_field function,
1678 * but no end line : we print it here */
1679 fprintf(stderr, " remain %d\n", cl->cl_remain);
1681 /* check for inline runas */
1682 ptr = check_username(ptr, cf, cl);
1684 /* get the shell command (remove trailing blanks) */
1685 if ((cl->cl_shell = get_string(ptr)) == NULL) {
1686 fprintf(stderr, "%s:%d: Mismatched quotes: skipping line.\n",
1690 if (strcmp(cl->cl_shell, "\0") == 0) {
1691 fprintf(stderr, "%s:%d: No shell command: skipping line.\n",
1693 Free_safe(cl->cl_shell);
1696 else if (cl->cl_shell[0] == '*' || isdigit((int)cl->cl_shell[0]))
1697 fprintf(stderr, "%s:%d: Warning : shell command beginning by '%c'.\n",
1698 file_name, line, cl->cl_shell[0]);
1700 /* check for non matching if option runfreq is set to 1 */
1701 if (!is_freq_periodically(cl->cl_option)) {
1702 const size_t s_mins = 60, s_hrs = 24;
1703 const size_t s_days = 32, s_mons = 12;
1704 const size_t s_dow = 8;
1707 if (!is_freq_mins(cl->cl_option)) {
1708 bit_ffc(cl->cl_mins, s_mins, &j);
1709 if (j != -1 && j < s_mins)
1712 if (!is_freq_hrs(cl->cl_option)) {
1713 bit_ffc(cl->cl_hrs, s_hrs, &j);
1714 if (j != -1 && j < s_hrs)
1717 if (!is_freq_days(cl->cl_option)) {
1718 bit_ffc(cl->cl_days, s_days, &j);
1719 if (j != -1 && j < s_days) {
1720 if (is_dayand(cl->cl_option))
1723 if (!is_freq_dow(cl->cl_option)) {
1724 bit_ffc(cl->cl_dow, s_dow, &j);
1725 if (j != -1 && j < s_dow)
1731 if (!is_freq_mons(cl->cl_option)) {
1732 bit_ffc(cl->cl_mons, s_mons, &j);
1733 if (j != -1 && j < s_mons)
1736 if (!is_freq_dow(cl->cl_option)) {
1737 bit_ffc(cl->cl_dow, s_dow, &j);
1738 if (j != -1 && j < s_dow && is_dayand(cl->cl_option))
1742 fprintf(stderr, "%s:%d: periodical line with no intervals: "
1743 "skipping line.\n", file_name, line);
1748 #ifndef USE_SENDMAIL
1749 clear_mail(cl->cl_option);
1750 clear_mailzerolength(cl->cl_option);
1753 cl->cl_next = cf->cf_line_base;
1754 cf->cf_line_base = cl;
1757 fprintf(stderr, " Cmd \"%s\"\n", cl->cl_shell);
1761 need_correction = 1;
1768 get_num(char *ptr, int *num, int max, short int decimal, const char **names)
1769 /* read a string's number and return it under int format.
1770 * Also check if that number is less than max */
1775 if (isalpha((int)*ptr)) {
1777 if (names == NULL) {
1778 need_correction = 1;
1782 /* set string to lower case */
1783 for (i = 0; i < strlen(names[0]); i++)
1784 *(ptr + i) = tolower(*(ptr + i));
1786 for (i = 0; names[i]; ++i)
1787 if (strncmp(ptr, names[i], strlen(names[i])) == 0) {
1789 ptr += strlen(names[i]);
1794 /* string is not in name list */
1795 need_correction = 1;
1801 while (isdigit((int)*ptr) || *ptr == '.') {
1803 if (*ptr == '.' && ptr++ && i++ > 0)
1805 if (i > 0 && --decimal < 0) {
1806 /* the decimal number is exceeded : we round off,
1807 * skip the other decimals and return */
1810 while (isdigit((int)*(++ptr))) ;
1819 need_correction = 1;
1828 *num *= 10 * decimal;
1837 read_field(char *ptr, bitstr_t * ary, int max, const char **names)
1838 /* read a field like "2,5-8,10-20/2,21-30~25" and fill ary */
1846 while ((*ptr != ' ') && (*ptr != '\t') && (*ptr != '\0')) {
1848 start = stop = step = 0;
1850 /* there may be a "," */
1855 /* we have to fill everything (may be modified by a step ) */
1857 /* user set month from 1 to 12, but we manage it internally
1858 * as a number from 0 to 11 */
1859 stop = (max == 12) ? 11 : max;
1864 ptr = get_num(ptr, &start, max, 0, names);
1868 /* this number is part of the month field.
1869 * user set it from 1 to 12, but we manage it internally
1870 * as a number from 0 to 11 : we remove 1 to start */
1873 if (*ptr == ',' || *ptr == ' ' || *ptr == '\t') {
1874 /* this is a single number : set up array and continue */
1876 fprintf(stderr, " %d", start);
1877 bit_set(ary, start);
1881 /* check for a dash */
1882 else if (*ptr == '-') {
1884 ptr = get_num(ptr, &stop, max, 0, names);
1886 /* we reached the end of the string to parse */
1889 /* this number is part of the month field.
1890 * user set it from 1 to 12, but we manage it internally
1891 * as a number from 0 to 11 : we remove 1 to stop */
1896 need_correction = 1;
1901 /* check for step size */
1904 if ((ptr = get_num(ptr, &step, max, 0, names)) == NULL || step == 0)
1908 /* step undefined : default is 0 */
1913 fprintf(stderr, " %d-%d/%d", start, stop, step);
1916 for (i = start; i <= stop; i += step)
1919 short int field_max = (max == 12) ? 11 : max;
1920 /* this is a field like (for hours field) "22-3" :
1921 * we should set from 22 to 3 (not from 3 to 22 or nothing :)) ) */
1922 for (i = start; i <= field_max; i += step)
1924 for (i -= (field_max + 1); i <= stop; i += step)
1928 /* finally, remove unwanted values */
1929 while (*ptr == '~') {
1932 if ((ptr = get_num(ptr, &rm, max, 0, names)) == NULL)
1935 /* this number is part of the month field.
1936 * user set it from 1 to 12, but we manage it internally
1937 * as a number from 0 to 11 : we remove 1 to rm */
1941 fprintf(stderr, " ~%d", rm);
1944 /* if we remove one value of Sunday, remove the other */
1945 if (max == 7 && rm == 0) {
1948 fprintf(stderr, " ~%d", 7);
1950 else if (max == 7 && rm == 7) {
1953 fprintf(stderr, " ~%d", 0);
1960 /* Sunday is both 0 and 7 : if one is set, set the other */
1962 if (bit_test(ary, 0))
1964 else if (bit_test(ary, 7))
1971 fprintf(stderr, " #");
1977 delete_file(const char *user_name)
1978 /* free a file if user_name is not null
1979 * otherwise free all files */
1982 cf_t *prev_file = NULL;
1984 cl_t *cur_line = NULL;
1987 while (file != NULL) {
1988 if (strcmp(user_name, file->cf_user) == 0) {
1991 cur_line = file->cf_line_base;
1992 while ((line = cur_line) != NULL) {
1993 cur_line = line->cl_next;
2001 file = file->cf_next;
2006 /* file not in list */
2009 /* remove file from list */
2010 if (prev_file == NULL)
2011 file_base = file->cf_next;
2013 prev_file->cf_next = file->cf_next;
2015 /* free env variables */
2016 env_list_destroy(file->cf_env_list);
2018 /* finally free file itself */
2019 Free_safe(file->cf_user);
2025 save_file(char *path)
2026 /* Store the informations relatives to the executions
2027 * of tasks at a defined frequency of system's running time */
2032 fprintf(stderr, "Saving ...\n");
2034 for (file = file_base; file; file = file->cf_next) {
2036 /* save_file() is run under user's rights.
2037 * If fcrontab is run by root for a normal user, we must change the file's
2038 * ownership to this user, in order to make fcron check the runas fields.
2039 * (a malicious user could put a runas(root) and wait for the fcrontab to be
2040 * installed by root) */
2042 if (save_file_safe(file, path, "fcrontab", asuid, fcrontab_gid, 0) ==
2047 (file, path, "fcrontab", fcrontab_uid, fcrontab_gid, 0) == ERR)