]> granicus.if.org Git - fcron/blob - conf.c
48f851f40fd470e3633aad06f84e1356d0aab591
[fcron] / conf.c
1
2 /*
3  * FCRON - periodic command scheduler 
4  *
5  *  Copyright 2000-2012 Thibault Godouet <fcron@free.fr>
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  * 
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  * 
21  *  The GNU General Public License can also be found in the file
22  *  `LICENSE' that comes with the fcron source distribution.
23  */
24
25
26 #include "fcron.h"
27
28 #include "conf.h"
29 #include "database.h"
30
31 int read_file(const char *file_name, cf_t * cf, int is_system_startup);
32 int add_line_to_file(cl_t * cl, cf_t * cf, uid_t runas, char *runas_str,
33                      time_t t_save, int is_system_startup);
34 int read_strn(int fd, char **str, short int size);
35 int read_type(int fd, short int *type, short int *size);
36 void synchronize_file(char *file_name, int is_system_startup);
37
38
39 /* this is used to create a list of files to remove, to add */
40 typedef struct list_t {
41     char *str;
42     struct list_t *next;
43 } list_t;
44
45
46 void
47 reload_all(const char *dir_name)
48     /* save all current configuration, remove it from the memory,
49      * and reload from dir_name */
50 {
51     cf_t *f = NULL;
52
53     explain("Removing current configuration from memory");
54
55     f = file_base;
56     while (f != NULL) {
57         if (f->cf_running > 0)
58             wait_all(&f->cf_running);
59         save_file(f);
60         delete_file(f->cf_user);
61
62         /* delete_file remove the f file from the list :
63          * next file to remove is now pointed by file_base. */
64         f = file_base;
65     }
66
67     synchronize_dir(dir_name, 0);
68
69 }
70
71
72 void
73 synchronize_dir(const char *dir_name, int is_system_startup)
74     /* read dir_name and create three list of files to remove,
75      * new files and normal files. Then remove each file
76      * listed in the first list, then read normal files,
77      * finally add new files. */
78 {
79
80     list_t *rm_list = NULL;
81     list_t *new_list = NULL;
82     list_t *file_list = NULL;
83     list_t *list_cur = NULL;
84     DIR *dir;
85     struct dirent *den;
86
87     if (strcmp(dir_name, ".") == 0)
88         explain("updating configuration from %s", fcrontabs);
89     else
90         explain("updating configuration from %s", dir_name);
91
92     if ((dir = opendir("."))) {
93         while ((den = readdir(dir))) {
94
95             if (strncmp(den->d_name, "rm.", 3) == 0) {
96                 /* this is a file to remove from database */
97                 Alloc(list_cur, list_t);
98                 list_cur->str = strdup2(den->d_name);
99                 list_cur->next = rm_list;
100                 rm_list = list_cur;
101             }
102             else if (strncmp(den->d_name, "new.", 4) == 0) {
103                 /* this is a file to append to database */
104                 Alloc(list_cur, list_t);
105                 list_cur->str = strdup2(den->d_name);
106                 list_cur->next = new_list;
107                 new_list = list_cur;
108             }
109             else if (strchr(den->d_name, '.') != NULL)
110                 continue;
111             else
112                 /* this is a normal file : if file_base is not null,
113                  * so if a database has already been created, we
114                  * ignore it */
115             if (file_base == NULL) {
116                 Alloc(list_cur, list_t);
117                 list_cur->str = strdup2(den->d_name);
118                 list_cur->next = file_list;
119                 file_list = list_cur;
120             }
121
122         }
123         closedir(dir);
124     }
125     else
126         die("Unable to open current dir!");
127
128
129     /* proceed to adds or removes */
130
131     /* begin by removing files which are no longer wanted */
132     for (list_cur = rm_list; list_cur; list_cur = list_cur->next) {
133         explain("removing file %s", list_cur->str + 3);
134         delete_file(list_cur->str + 3); /* len("rm.") = 3 */
135         if (remove(list_cur->str + 3) != 0 && errno != ENOENT)
136             error_e("Could not remove %s", list_cur->str + 3);
137         if (remove(list_cur->str) != 0 && errno != ENOENT)
138             error_e("Could not remove %s", list_cur->str);
139     }
140
141     /* then add normal files, if any, to database */
142     for (list_cur = file_list; list_cur; list_cur = list_cur->next) {
143         errno = 0;
144         if (getpwnam(list_cur->str)
145 #ifdef SYSFCRONTAB
146             || strcmp(list_cur->str, SYSFCRONTAB) == 0
147 #endif
148             ) {
149             explain("adding file %s", list_cur->str);
150             synchronize_file(list_cur->str, is_system_startup);
151         }
152         else
153             error_e("ignoring file \"%s\" : not in passwd file.",
154                     list_cur->str);
155     }
156
157     /* finally add new files */
158     for (list_cur = new_list; list_cur; list_cur = list_cur->next) {
159         /* len("new.") = 4 : */
160         errno = 0;
161         if (getpwnam(list_cur->str + 4)
162 #ifdef SYSFCRONTAB
163             || strcmp(list_cur->str + 4, SYSFCRONTAB) == 0
164 #endif
165             ) {
166             explain("adding new file %s", list_cur->str + 4);
167             synchronize_file(list_cur->str, is_system_startup);
168         }
169         else
170             error_e("ignoring file %s : not in passwd file.",
171                     (list_cur->str + 4));
172     }
173
174     /* free lists */
175     {
176         list_t *l = NULL;
177         list_t *next = NULL;
178
179         next = rm_list;
180         while ((l = next) != NULL) {
181             next = l->next;
182             Free_safe(l->str);
183             Free_safe(l);
184         }
185
186         next = new_list;
187         while ((l = next) != NULL) {
188             next = l->next;
189             Free_safe(l->str);
190             Free_safe(l);
191         }
192
193         next = file_list;
194         while ((l = next) != NULL) {
195             next = l->next;
196             Free_safe(l->str);
197             Free_safe(l);
198         }
199
200     }
201
202 }
203
204
205 void
206 synchronize_file(char *file_name, int is_system_startup)
207 {
208
209     cf_t *cur_f = NULL;
210     char *user = NULL;
211
212     if (strchr(file_name, '.') != NULL) {
213         /* this is a new file : we have to check if there is an old
214          * version in database in order to keep a maximum of fields
215          * (cl_nextexe) to their current value */
216
217         cf_t *prev = NULL;
218
219         /* set user name  */
220         /* we add 4 to file_name pointer because of the "new."
221          * string at the beginning of a new file */
222         user = (file_name + 4);
223
224         for (cur_f = file_base; cur_f; cur_f = cur_f->cf_next) {
225             if (strcmp(user, cur_f->cf_user) == 0)
226                 break;
227             prev = cur_f;
228         }
229
230         if (cur_f != NULL) {
231             /* an old version of this file exist in database */
232
233             cf_t *old = NULL;
234             cl_t *old_l = NULL;
235             cl_t *new_l = NULL;
236             /* size used when comparing two line :
237              * it's the size of all time table (mins, days ...) */
238             const size_t size = (bitstr_size(60) + bitstr_size(24) +
239                                  bitstr_size(32) + bitstr_size(12) +
240                                  bitstr_size(7));
241
242             old = cur_f;
243
244             /* load new file */
245             Alloc(cur_f, cf_t);
246             if (read_file(file_name, cur_f, is_system_startup) != 0) {
247                 /* an error as occured */
248                 return;
249             }
250
251             /* assign old pointer to the old file, and move it to the first
252              * place of the list : delete_file() only remove the first
253              * occurrence of the file which has the name given in argument */
254             if (prev != NULL) {
255                 prev->cf_next = old->cf_next;
256                 old->cf_next = file_base;
257                 file_base = old;
258             }
259             else
260                 /* this is the first file in the list : no need to move it */
261                 ;
262
263             /* compare each lines between the new and the old
264              * version of the file */
265             for (new_l = cur_f->cf_line_base; new_l; new_l = new_l->cl_next)
266                 for (old_l = old->cf_line_base; old_l; old_l = old_l->cl_next) {
267
268                     /* compare the shell command and the fields
269                      * from cl_mins down to cl_runfreq or the timefreq */
270                     if (strcmp(new_l->cl_shell, old_l->cl_shell) == 0
271                         && ((is_freq(new_l->cl_option)
272                              && new_l->cl_timefreq == old_l->cl_timefreq)
273                             || (is_td(new_l->cl_option)
274                                 && memcmp(&(new_l->cl_mins), &(old_l->cl_mins),
275                                           size) == 0
276                                 && is_dayor(new_l->cl_option) ==
277                                 is_dayor(old_l->cl_option))
278                         )) {
279
280                         if (new_l->cl_runfreq == old_l->cl_runfreq)
281                             new_l->cl_remain = old_l->cl_remain;
282                         /* check if there is a change about the tz diff */
283                         if ((new_l->cl_file->cf_tzdiff !=
284                              old_l->cl_file->cf_tzdiff) &&
285                             (old_l->cl_nextexe - old_l->cl_file->cf_tzdiff
286                              + new_l->cl_file->cf_tzdiff > now))
287                             new_l->cl_nextexe = old_l->cl_nextexe
288                                 - old_l->cl_file->cf_tzdiff +
289                                 new_l->cl_file->cf_tzdiff;
290                         else
291                             new_l->cl_nextexe = old_l->cl_nextexe;
292
293                         if (is_runonce(new_l->cl_option)
294                             && is_runonce(old_l->cl_option)
295                             && is_hasrun(old_l->cl_option)) {
296                             explain
297                                 ("  from last conf: job '%s' with runonce set has "
298                                  "already run since last system startup: not "
299                                  "re-scheduling.", new_l->cl_shell);
300                             set_hasrun(new_l->cl_option);
301                             /* job has already run: remove from the queue */
302                             job_queue_remove(new_l);
303                         }
304                         else
305                             /* update the position in the queue */
306                             insert_nextexe(new_l);
307
308                         if (debug_opt && !is_hasrun(new_l->cl_option)) {
309                             struct tm *ftime;
310                             ftime = localtime(&new_l->cl_nextexe);
311                             debug("  from last conf: %s next exec %d/%d/%d"
312                                   " wday:%d %02d:%02d:%02d (system time)",
313                                   new_l->cl_shell,
314                                   (ftime->tm_mon + 1), ftime->tm_mday,
315                                   (ftime->tm_year + 1900), ftime->tm_wday,
316                                   ftime->tm_hour, ftime->tm_min, ftime->tm_sec);
317                         }
318
319                         break;
320
321                     }
322                 }
323
324             /* remove old file from the list */
325             delete_file(user);
326
327             /* insert new file in the list */
328             cur_f->cf_next = file_base;
329             file_base = cur_f;
330
331             /* save final file */
332             save_file(cur_f);
333
334             /* delete new.user file */
335             if (remove(file_name) != 0)
336                 error_e("could not remove %s", file_name);
337
338         }
339
340         else {
341             /* no old version exist in database : load this file
342              * as a normal file, but change its name */
343
344             Alloc(cur_f, cf_t);
345
346             if (read_file(file_name, cur_f, is_system_startup) != 0) {
347                 /* an error as occured */
348                 return;
349             }
350
351             /* insert the file in the list */
352             cur_f->cf_next = file_base;
353             file_base = cur_f;
354
355             /* save as a normal file */
356             save_file(cur_f);
357
358             /* delete new.user file */
359             if (remove(file_name) != 0)
360                 error_e("could not remove %s", file_name);
361         }
362
363     }
364
365     else {
366         /* this is a normal file */
367
368         Alloc(cur_f, cf_t);
369
370         if (read_file(file_name, cur_f, is_system_startup) != 0) {
371             /* an error as occured */
372             return;
373         }
374
375         /* insert the file in the list */
376         cur_f->cf_next = file_base;
377         file_base = cur_f;
378
379     }
380
381 }
382
383
384 int
385 read_strn(int fd, char **str, short int size)
386 /* read a "size"-length string in a binary fcrontab file */
387 {
388     if ((*str = calloc(size + 1, sizeof(char))) == NULL)
389         goto err;
390
391     if (read(fd, *str, size) < size)
392         goto err;
393     (*str)[size] = '\0';
394     return OK;
395
396  err:
397     Free_safe(*str);
398     return ERR;
399
400 }
401
402 int
403 read_type(int fd, short int *type, short int *size)
404 /* read the type and size of the next field in a binary fcrontab file */
405 {
406     if (read(fd, type, sizeof(short int)) < sizeof(short int))
407         goto err;
408     if (read(fd, size, sizeof(short int)) < sizeof(short int))
409         goto err;
410
411     return OK;
412
413  err:
414     return ERR;
415
416 }
417
418
419 /* macros for read_file() */
420 /* read "size" bytes from file "ff", put them in "to", and check for errors */
421 #define Read(TO, SIZE, ERR_STR) \
422         { \
423           if ( read(fileno(ff), &(TO), SIZE) < SIZE ) { \
424             error_e(ERR_STR); \
425             goto err; \
426           } \
427         }
428
429 #define Read_strn(TO, SIZE, ERR_STR) \
430         { \
431           if ( read_strn(fileno(ff), &(TO), SIZE) != OK ) { \
432             error_e(ERR_STR); \
433             goto err; \
434           } \
435         }
436
437 int
438 read_file(const char *file_name, cf_t * cf, int is_system_startup)
439     /* read a formated fcrontab.
440      * return ERR on error, OK otherwise */
441 {
442     FILE *ff = NULL;
443     cl_t *cl = NULL;
444     long int bufi = 0;
445     time_t t_save = 0;
446     uid_t runas = 0;
447     char *runas_str = NULL;
448     struct stat file_stat;
449     struct passwd *pass = NULL;
450     short int type = 0, size = 0;
451     int rc;
452     int has_read_cl_first = 0;  /* have we read S_FIRST_T? */
453 #ifdef WITH_SELINUX
454     int flask_enabled = is_selinux_enabled();
455     int retval;
456     struct av_decision avd;
457     const char *user_name;
458 #endif
459
460     /* open file */
461     if ((ff = fopen(file_name, "r")) == NULL) {
462         warn_e("Could not read %s (may have just been removed)", file_name);
463         goto err;
464     }
465
466     /* check if this file is owned by root : otherwise, all runas fields
467      * of this field should be set to the owner */
468     rc = fstat(fileno(ff), &file_stat);
469     if (rc != 0) {
470         error_e("Could not stat %s", file_name);
471         goto err;
472     }
473 #ifdef WITH_SELINUX
474     if (flask_enabled && fgetfilecon(fileno(ff), &cf->cf_file_context) < 0) {
475         error_e("Could not get context of %s", file_name);
476         goto err;
477     }
478 #endif
479
480     if (strncmp(file_name, "new.", 4) == 0) {
481         if (file_stat.st_uid == rootuid) {
482             /* file is owned by root : no test needed : set runas to rootuid */
483             runas = rootuid;
484         }
485         else {
486             /* this is a standard user's new fcrontab : set the runas field to
487              * the owner of the file */
488             runas = file_stat.st_uid;
489             if ((pass = getpwuid(file_stat.st_uid)) == NULL) {
490                 error_e("Could not getpwuid(%d)", file_stat.st_uid);
491                 goto err;
492             }
493             runas_str = strdup2(pass->pw_name);
494         }
495         cf->cf_user = strdup2(file_name + 4);
496     }
497     else {
498         if (!cf->cf_user)
499             cf->cf_user = strdup2(file_name);
500         if (file_stat.st_uid == rootuid) {
501             /* file is owned by root : either this file has already been parsed
502              * at least once by fcron, or it is root's fcrontab */
503             runas = rootuid;
504         }
505         else {
506             error("Non-new file %s owned by someone else than root", file_name);
507             goto err;
508         }
509     }
510
511 #ifdef WITH_SELINUX
512     /*
513      * Since fcrontab files are not directly executed,
514      * fcrond must ensure that the fcrontab file has
515      * a context that is appropriate for the context of
516      * the user fcron job.  It performs an entrypoint
517      * permission check for this purpose.
518      */
519 #ifdef SYSFCRONTAB
520     if (!strcmp(cf->cf_user, SYSFCRONTAB))
521         user_name = "system_u";
522     else
523 #endif                          /* def SYSFCRONTAB */
524         user_name = cf->cf_user;
525     if (flask_enabled) {
526         if (get_default_context(user_name, NULL, &cf->cf_user_context))
527             error_e("NO CONTEXT for user \"%s\"", cf->cf_user_context);
528         retval =
529             security_compute_av(cf->cf_user_context, cf->cf_file_context,
530                                 SECCLASS_FILE, FILE__ENTRYPOINT, &avd);
531
532         if (retval || ((FILE__ENTRYPOINT & avd.allowed) != FILE__ENTRYPOINT)) {
533             syslog(LOG_ERR, "ENTRYPOINT FAILED for user \"%s\" "
534                    "(CONTEXT %s) for file CONTEXT %s", cf->cf_user,
535                    cf->cf_user_context, cf->cf_file_context);
536             goto err;
537         }
538     }
539 #endif
540
541     debug("User %s Entry", file_name);
542
543     /* get version of fcrontab file: it permits to daemon not to load
544      * a file which he won't understand the syntax, for example
545      * a file using a depreciated format generated by an old fcrontab,
546      * if the syntax has changed */
547     if (read_type(fileno(ff), &type, &size) != OK || type != S_HEADER_T ||
548         read(fileno(ff), &bufi, size) < size || bufi != S_FILEVERSION) {
549         error("File %s is not valid: ignored.", file_name);
550         error("This file may have been generated by an old version of fcron.");
551         error("In that case, you should try to use the converter given in the "
552               "source package, or install it again using fcrontab.");
553         goto err;
554     }
555
556     if (read_type(fileno(ff), &type, &size) != OK || type != S_USER_T) {
557         error("Invalid binary fcrontab (no USER field)");
558         goto err;
559     }
560     /* get the owner's name */
561     /* we set cf->cf_user before for SE Linux, so we need to free it here */
562     Free_safe(cf->cf_user);
563     if (read_strn(fileno(ff), &cf->cf_user, size) != OK) {
564         error("Cannot read user's name : file ignored");
565         goto err;
566     }
567     if (runas != rootuid) {
568         /* we use file owner's name for more security (see above) */
569         /* free the value obtained by read_strn() (we need to read it anyway
570          * to set the file ptr to the next thing to read) */
571         Free_safe(cf->cf_user);
572         cf->cf_user = runas_str;
573     }
574
575     /* get the time & date of the saving */
576     /* a new file generated by fcrontab has t_save set to 0 */
577     if (read_type(fileno(ff), &type, &size) != OK || type != S_TIMEDATE_T
578         || read(fileno(ff), &t_save, size) < size) {
579         error("could not get time and date of saving");
580         goto err;
581     }
582
583     if (cf->cf_env_list == NULL)
584         cf->cf_env_list = env_list_init();
585
586     Alloc(cl, cl_t);
587     /* main loop : read env variables, and lines */
588     while (read_type(fileno(ff), &type, &size) == OK) {
589         /* action is determined by the type of the field */
590         switch (type) {
591
592         case S_ENVVAR_T:
593             /* read a env variable and add it to the env var list */
594             {
595                 char *envvar = NULL;
596
597                 /* Read_strn go to "err" on error */
598                 Read_strn(envvar, size, "Error while reading env var");
599                 debug("  Env: \"%s\"", envvar);
600                 /* we do not allow USER or LOGNAME assignment.
601                  * this was already checked by fcrontab, but we check again
602                  * just in case... */
603                 if (strcmp_until(envvar, "USER", '=') == 0
604                     || strcmp_until(envvar, "LOGNAME", '=') == 0) {
605                     error
606                         ("USER or LOGNAME assignement is not allowed: ignored.");
607                 }
608                 else {
609                     env_list_putenv(cf->cf_env_list, envvar, 1);
610                 }
611                 Free_safe(envvar);
612             }
613             break;
614
615         case S_TZDIFF_T:
616             /* time diff between local (real) and system hour */
617             Read(bufi, size, "Error while reading tzdiff field");
618             cf->cf_tzdiff = (signed char)bufi;
619             break;
620
621         case S_TZ_T:
622             /* read the timezone (string) in which the line should run */
623             Read_strn(cl->cl_tz, size, "Error while reading timezone field");
624             break;
625
626         case S_SHELL_T:
627             Read_strn(cl->cl_shell, size, "Error while reading shell field");
628             break;
629
630         case S_RUNAS_T:
631             Read_strn(cl->cl_runas, size, "Error while reading runas field");
632             break;
633
634         case S_MAILTO_T:
635             Read_strn(cl->cl_mailto, size, "Error while reading mailto field");
636             break;
637
638         case S_NEXTEXE_T:
639             Read(bufi, size, "Error while reading nextexe field");
640             cl->cl_nextexe = (time_t) bufi;
641             break;
642
643         case S_FIRST_T:
644             Read(bufi, size, "Error while reading first field");
645             cl->cl_first = (time_t) bufi;
646             has_read_cl_first = 1;
647             break;
648
649         case S_OPTION_T:
650             if (size < OPTION_SIZE)
651                 /* set the options not defined in the savefile to default */
652                 set_default_opt(cl->cl_option);
653             Read(cl->cl_option, size, "Error while reading option field");
654             break;
655
656         case S_NUMEXE_T:
657             Read(cl->cl_numexe, size, "Error while reading numexe field");
658             break;
659
660         case S_LAVG_T:
661             Read(cl->cl_lavg, size, "Error while reading lavg field");
662             break;
663
664         case S_UNTIL_T:
665             Read(bufi, size, "Error while reading until field");
666             cl->cl_until = (time_t) bufi;
667             break;
668
669         case S_NICE_T:
670             Read(cl->cl_nice, size, "Error while reading nice field");
671             break;
672
673         case S_RUNFREQ_T:
674             Read(bufi, size, "Error while reading runfreq field");
675             cl->cl_runfreq = (unsigned short)bufi;
676             break;
677
678         case S_REMAIN_T:
679             Read(bufi, size, "Error while reading remain field");
680             cl->cl_remain = (unsigned short)bufi;
681             break;
682
683         case S_TIMEFREQ_T:
684             Read(bufi, size, "Error while reading timefreq field");
685             cl->cl_timefreq = (time_t) bufi;
686             break;
687
688         case S_JITTER_T:
689             /* read the jitter (uchar) to use to set next execution time */
690             Read(bufi, size, "Error while reading jitter field");
691             cl->cl_jitter = (unsigned char)bufi;
692             break;
693
694         case S_MINS_T:
695             Read(cl->cl_mins, size, "Error while reading mins field");
696             break;
697
698         case S_HRS_T:
699             Read(cl->cl_hrs, size, "Error while reading hrs field");
700             break;
701
702         case S_DAYS_T:
703             Read(cl->cl_days, size, "Error while reading days field");
704             break;
705
706         case S_MONS_T:
707             Read(cl->cl_mons, size, "Error while reading mons field");
708             break;
709
710         case S_DOW_T:
711             Read(cl->cl_dow, size, "Error while reading dow field");
712             break;
713
714         case S_ENDLINE_T:
715             if (is_freq(cl->cl_option) && !has_read_cl_first) {
716                 /* Up to fcron 3.0.X, cl_first/S_FIRST_T was not saved for all @-lines */
717                 cl->cl_first = cl->cl_nextexe;
718             }
719             if (add_line_to_file
720                 (cl, cf, runas, runas_str, t_save, is_system_startup) != 0)
721                 free_line(cl);
722             Alloc(cl, cl_t);
723             break;
724
725             /* default case in "switch(type)" */
726         default:
727             error("Error while loading %s : unknown field type %d (ignored)",
728                   file_name, type);
729             free_line(cl);
730             Alloc(cl, cl_t);
731             /* skip the data corresponding to the unknown field */
732             {
733                 /* we avoid using fseek(), as it seems not to work correctly
734                  * on some systems when we use read() on the FILE stream */
735                 int i;
736                 for (i = 0; i < size; i++)
737                     if (getc(ff) == EOF)
738                         goto err;
739             }
740         }
741     }
742
743     /* free last cl Alloc : unused */
744     Free_safe(cl);
745
746     /* check for an error */
747     if (ferror(ff) != 0)
748         error("file %s is truncated : you should reinstall it with fcrontab",
749               file_name);
750
751     xfclose_check(&ff, file_name);
752
753     return OK;
754
755  err:
756     if (ff != NULL)
757         xfclose_check(&ff, file_name);
758
759     if (cl != NULL && cl->cl_next == NULL) {
760         /* line is not yet in the line list of the file : free it */
761         Free_safe(cl->cl_shell);
762         Free_safe(cl->cl_runas);
763         Free_safe(cl->cl_mailto);
764         Free_safe(cl);
765     }
766
767     /* check if we have started to read the lines and env var */
768     if (cl != NULL) {
769         /* insert the line in the file list in order to be able to free
770          * the memory using delete_file() */
771
772         cf->cf_next = file_base;
773         file_base = cf;
774
775         delete_file(cf->cf_user);
776
777     }
778     else {
779         Free_safe(cf->cf_user);
780     }
781
782     return ERR;
783
784 }
785
786
787 int
788 add_line_to_file(cl_t * cl, cf_t * cf, uid_t runas, char *runas_str,
789                  time_t t_save, int is_system_startup)
790     /* check if the line is valid, and if yes, add it to the file cf */
791 {
792     time_t slept = now - t_save;
793
794     if (cl->cl_shell == NULL || cl->cl_runas == NULL || cl->cl_mailto == NULL) {
795         error("Line is not valid (empty shell, runas or mailto field)"
796               " : ignored");
797         return 1;
798     }
799
800     /* set runas field if necessary (to improve security) */
801     if (runas != rootuid) {
802         if (strcmp(cl->cl_runas, runas_str) != 0)
803             warn("warning: runas(%s) is not owner (%s): overridden.",
804                  cl->cl_runas, runas_str);
805         Set(cl->cl_runas, runas_str);
806     }
807
808     /* we need that here because the user's name contained in the
809      * struct cf_t may be required */
810     cl->cl_file = cf;
811
812     /* check if the mailto field is valid */
813     if (cl->cl_mailto && (*(cl->cl_mailto) == '-' ||
814                           strcspn(cl->cl_mailto,
815                                   " \t\n") != strlen(cl->cl_mailto))) {
816         error("mailto field \'%s\' is not valid : set to owner %s.",
817               cl->cl_mailto, cl->cl_file->cf_user);
818         Set(cl->cl_mailto, cl->cl_file->cf_user);
819     }
820
821     /* job has been stopped during execution: insert it in lavg or serial queue
822      * if it was in one at fcron's stops.  */
823     /* NOTE: runatreboot is prioritary over jobs that were still running
824      * when fcron stops, because the former will get run quicker as they are not
825      * put into the serial queue. runatreboot jobs will be handled later on. */
826     if (cl->cl_numexe > 0 && !is_runatreboot(cl->cl_option)) {
827
828         cl->cl_numexe = 0;
829         if (is_lavg(cl->cl_option)) {
830             if (!is_strict(cl->cl_option))
831                 add_lavg_job(cl, -1);
832         }
833         else if (is_serial(cl->cl_option)
834                  || is_serial_once(cl->cl_option))
835             add_serial_job(cl, -1);
836         else {
837             /* job has been stopped during execution :
838              * launch it again */
839             warn("job %s did not finish : running it again.", cl->cl_shell);
840             set_serial_once(cl->cl_option);
841             add_serial_job(cl, -1);
842         }
843     }
844
845     if (is_system_startup || is_volatile(cl->cl_option)) {
846         clear_hasrun(cl->cl_option);
847     }
848
849     if (is_runonce(cl->cl_option) && is_hasrun(cl->cl_option)) {
850         /* if we get here, then is_system_startup is_volatile are both false */
851         /* do nothing: don't re-schedule or add to the job queue */
852         explain("job '%s' with runonce set has already run since last "
853                 "system startup: not re-scheduling.", cl->cl_shell);
854     }
855     else if (is_td(cl->cl_option)) {
856
857         /* set the time and date of the next execution  */
858         if (is_system_startup && is_runatreboot(cl->cl_option)) {
859
860             if (is_notice_notrun(cl->cl_option)) {
861
862                 if (cl->cl_runfreq == 1) {
863                     /* %-line */
864                     set_next_exe_notrun(cl, SYSDOWN_RUNATREBOOT);
865                 }
866                 else {
867                     /* set next exe and mail user */
868                     time_t since = cl->cl_nextexe;
869
870                     cl->cl_nextexe = now;
871                     mail_notrun_time_t(cl, SYSDOWN, since);
872                 }
873
874             }
875             else {
876                 cl->cl_nextexe = now;
877             }
878
879             insert_nextexe(cl);
880
881         }
882         else if (cl->cl_nextexe <= now) {
883             if (cl->cl_nextexe == 0)
884                 /* the is a line from a new file */
885                 set_next_exe(cl, NO_GOTO, -1);
886             else if (cl->cl_runfreq == 1 && is_notice_notrun(cl->cl_option))
887                 set_next_exe_notrun(cl, SYSDOWN);
888             else if (is_bootrun(cl->cl_option) && t_save != 0
889                      && cl->cl_runfreq != 1) {
890                 if (cl->cl_remain > 0 && --cl->cl_remain > 0) {
891                     debug("    cl_remain: %d", cl->cl_remain);
892                 }
893                 else {
894                     /* run bootrun jobs */
895                     cl->cl_remain = cl->cl_runfreq;
896                     debug("   boot-run %s", cl->cl_shell);
897                     if (!is_lavg(cl->cl_option)) {
898                         set_serial_once(cl->cl_option);
899                         add_serial_job(cl, -1);
900                     }
901                     else
902                         add_lavg_job(cl, -1);
903                 }
904                 set_next_exe(cl, STD, -1);
905             }
906             else {
907                 if (is_notice_notrun(cl->cl_option)) {
908                     /* set next exe and mail user */
909                     time_t since = cl->cl_nextexe;
910
911                     set_next_exe(cl, NO_GOTO, -1);
912                     mail_notrun_time_t(cl, SYSDOWN, since);
913
914                 }
915                 else
916                     set_next_exe(cl, NO_GOTO, -1);
917             }
918         }
919         else {
920             /* value of nextexe is valid : just insert line in queue */
921             insert_nextexe(cl);
922         }
923     }
924     else {                      /* is_td(cl->cl_option) */
925         /* standard @-lines */
926         if (is_system_startup && is_runatreboot(cl->cl_option)) {
927             cl->cl_nextexe = now;
928         }
929         /* t_save == 0 means this is a new file, hence a new line */
930         else if (t_save == 0 || is_volatile(cl->cl_option)
931                  || (is_system_startup && (is_rebootreset(cl->cl_option)
932                                            || is_runonce(cl->cl_option)))) {
933             /* cl_first is always saved to disk for a volatile line */
934             if (cl->cl_first == LONG_MAX) {
935                 cl->cl_nextexe = LONG_MAX;
936             }
937             else {
938                 cl->cl_nextexe = now + cl->cl_first;
939                 if (cl->cl_nextexe < now) {
940                     /* there was an integer overflow! */
941                     error
942                         ("Error while setting next exe time for job %s: cl_nextexe"
943                          " overflowed. now=%lu, cl_timefreq=%lu, cl_nextexe=%lu.",
944                          cl->cl_shell, now, cl->cl_timefreq, cl->cl_nextexe);
945                     error
946                         ("Setting cl_nextexe to LONG_MAX to prevent an infinite loop.");
947                     cl->cl_nextexe = LONG_MAX;
948                 }
949             }
950         }
951         else {
952             if (cl->cl_nextexe != LONG_MAX) {
953                 cl->cl_nextexe += slept;
954                 if (cl->cl_nextexe < now) {
955                     /* there was an integer overflow! */
956                     error
957                         ("Error while setting next exe time for job %s: cl_nextexe"
958                          " overflowed. now=%lu, cl_timefreq=%lu, cl_nextexe=%lu.",
959                          cl->cl_shell, now, cl->cl_timefreq, cl->cl_nextexe);
960                     error
961                         ("Setting cl_nextexe to LONG_MAX to prevent an infinite loop.");
962                     cl->cl_nextexe = LONG_MAX;
963                 }
964             }
965         }
966
967         if (cl->cl_timefreq < 10) {
968             error("Invalid timefreq for %s: set to 1 day", cl->cl_shell);
969             cl->cl_timefreq = 3600 * 24;
970         }
971
972         insert_nextexe(cl);
973     }
974
975     if (debug_opt && !(is_runonce(cl->cl_option) && is_hasrun(cl->cl_option))) {
976         struct tm *ftime;
977         ftime = localtime(&(cl->cl_nextexe));
978         debug("  cmd %s next exec %d/%d/%d wday:%d %02d:%02d:%02d"
979               " (system time)",
980               cl->cl_shell, (ftime->tm_mon + 1), ftime->tm_mday,
981               (ftime->tm_year + 1900), ftime->tm_wday,
982               ftime->tm_hour, ftime->tm_min, ftime->tm_sec);
983     }
984
985     /* add the current line to the list, and allocate a new line */
986     if ((cl->cl_id = next_id++) >= ULONG_MAX - 1) {
987         warn("Line id reached %ld: cycling back to zero.", ULONG_MAX);
988         next_id = 0;
989     }
990     cl->cl_next = cf->cf_line_base;
991     cf->cf_line_base = cl;
992     return 0;
993 }
994
995 void
996 delete_file(const char *user_name)
997     /* free a file if user_name is not null 
998      *   otherwise free all files */
999 {
1000     cf_t *file;
1001     cf_t *prev_file = NULL;
1002     cl_t *line;
1003     cl_t *cur_line;
1004     struct job_t *j = NULL;
1005     struct job_t *prev_j;
1006     int i, k;
1007     struct cl_t **s_a = NULL;
1008     exe_t *e = NULL;
1009     lavg_t *l = NULL;
1010
1011     file = file_base;
1012     while (file != NULL) {
1013         if (strcmp(user_name, file->cf_user) != 0) {
1014             prev_file = file;
1015             file = file->cf_next;
1016             continue;
1017         }
1018
1019         for (e = exe_list_first(exe_list); e != NULL;
1020              e = exe_list_next(exe_list))
1021             if (e->e_line != NULL && e->e_line->cl_file == file) {
1022                 /* we set the e_line to NULL, as so we know in wait_chld()
1023                  * and wait_all() the corresponding file has been removed.
1024                  * Plus, we decrement serial_running and lavg_serial_running
1025                  * as we won't be able to do it at the end of the job */
1026                 if ((is_serial(e->e_line->cl_option) ||
1027                      is_serial_once(e->e_line->cl_option)) &&
1028                     !is_lavg(e->e_line->cl_option))
1029                     serial_running--;
1030                 else if (is_serial(e->e_line->cl_option) &&
1031                          is_lavg(e->e_line->cl_option))
1032                     lavg_serial_running--;
1033                 e->e_line = NULL;
1034             }
1035
1036         /* free lavg queue entries */
1037         for (l = lavg_list_first(lavg_list); l != NULL;
1038              l = lavg_list_next(lavg_list))
1039             if (l->l_line->cl_file == file) {
1040                 debug("removing %s from lavg queue", l->l_line->cl_shell);
1041                 lavg_list_remove_cur(lavg_list);
1042             }
1043
1044         /* free serial queue entries */
1045         for (i = 0; i < serial_array_size; i++)
1046             if (serial_array[i] != NULL && serial_array[i]->cl_file == file) {
1047                 if (!s_a) {
1048                     s_a =
1049                         alloc_safe(serial_array_size * sizeof(cl_t *),
1050                                    "serial queue");
1051                 }
1052                 debug("removing %s from serial queue",
1053                       serial_array[i]->cl_shell);
1054                 serial_num--;
1055                 serial_array[i]->cl_numexe--;
1056                 serial_array[i] = NULL;
1057             }
1058         /* remove from queue and move the rest of the jobs to get
1059          * a queue in order without empty entries */
1060         if (!s_a)
1061             goto end_of_serial_recomputing;
1062
1063         if ((k = serial_array_index + serial_num) >= serial_array_size)
1064             k -= serial_array_size;
1065         for (i = k = 0; i < serial_array_size; i++) {
1066             if (serial_array_index + i < serial_array_size) {
1067                 if ((s_a[k] = serial_array[serial_array_index + i]) != NULL)
1068                     k++;
1069             }
1070             else if ((s_a[k] =
1071                       serial_array[serial_array_index + i - serial_array_size])
1072                      != NULL)
1073                 k++;
1074         }
1075         Free_safe(serial_array);
1076         serial_array = s_a;
1077         serial_array_index = 0;
1078
1079  end_of_serial_recomputing:
1080
1081         /* free lines */
1082         cur_line = file->cf_line_base;
1083         while ((line = cur_line) != NULL) {
1084             cur_line = line->cl_next;
1085
1086             /* remove from the main queue */
1087             prev_j = NULL;
1088             for (j = queue_base; j != NULL; j = j->j_next)
1089                 if (j->j_line == line) {
1090                     if (prev_j != NULL)
1091                         prev_j->j_next = j->j_next;
1092                     else
1093                         queue_base = j->j_next;
1094                     Free_safe(j);
1095                     break;
1096                 }
1097                 else
1098                     prev_j = j;
1099
1100             /* free line itself */
1101             free_line(line);
1102         }
1103         /* delete_file() MUST remove only the first occurrence :
1104          * this is needed by synchronize_file() */
1105         break;
1106     }
1107
1108     if (file == NULL)
1109         /* file not in the file list */
1110         return;
1111
1112     /* remove file from file list */
1113     if (prev_file == NULL)
1114         file_base = file->cf_next;
1115     else
1116         prev_file->cf_next = file->cf_next;
1117
1118     /* free env variables */
1119     env_list_destroy(file->cf_env_list);
1120
1121     /* finally free file itself */
1122     Free_safe(file->cf_user);
1123     Free_safe(file);
1124
1125 }
1126
1127
1128 void
1129 save_file(cf_t * arg_file)
1130 /* Store the informations relatives to the executions
1131  * of tasks at a defined frequency of system's running time */
1132 {
1133     cf_t *file = NULL;
1134     cf_t *start_file = NULL;
1135
1136     if (arg_file != NULL)
1137         start_file = arg_file;
1138     else
1139         start_file = file_base;
1140
1141
1142     for (file = start_file; file; file = file->cf_next) {
1143
1144         debug("Saving %s...", file->cf_user);
1145
1146         /* save the file safely : save it to a temporary name, then rename() it */
1147         /* chown the file to root:root : this file should only be read and
1148          * modified by fcron (not fcrontab) */
1149         save_file_safe(file, file->cf_user, "fcron", rootuid, rootgid, now);
1150
1151         if (arg_file != NULL)
1152             /* we have to save only a single file */
1153             break;
1154     }
1155 }