]> granicus.if.org Git - fcron/blob - fcrontab.c
Merge branch 'log-to-file'
[fcron] / fcrontab.c
1 /*
2  * FCRON - periodic command scheduler 
3  *
4  *  Copyright 2000-2012 Thibault Godouet <fcron@free.fr>
5  *
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.
10  *
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.
15  * 
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
19  * 
20  *  The GNU General Public License can also be found in the file
21  *  `LICENSE' that comes with the fcron source distribution.
22  */
23
24
25 /* 
26  * The goal of this program is simple : giving a user interface to fcron
27  * daemon, by allowing each user to see, modify, append or remove his 
28  * fcrontabs. 
29  * Fcron daemon use a specific formated format of file, so fcrontab generate
30  * that kind of file from human readable files. In order allowing users to
31  * see and modify their fcrontabs, the source file is always saved with the
32  * formated one. 
33  * Fcrontab makes a temporary formated file, and then sends a signal 
34  * to the daemon to force it to update its configuration, remove the temp
35  * file and save a new and final formated file.
36  * That way, not the simple, allows the daemon to keep a maximum of 
37  * informations like the time remaining before next execution, or the date
38  * and hour of next execution.
39  */
40
41 #include "fcrontab.h"
42
43 #include "allow.h"
44 #include "fileconf.h"
45 #include "temp_file.h"
46 #include "read_string.h"
47
48
49 void info(void);
50 void usage(void);
51
52
53 /* used in temp_file() */
54 char *tmp_path = "/tmp/";
55
56 /* command line options */
57 char rm_opt = 0;
58 char list_opt = 0;
59 char edit_opt = 0;
60 char reinstall_opt = 0;
61 char ignore_prev = 0;
62 int file_opt = 0;
63
64 /* uid/gid of users/groups 
65  * (we don't use the static UID or GID as we ask for user and group names
66  * in the configure script) */
67 char *user = NULL;
68 char *runas = NULL;
69 uid_t useruid = 0;              /* uid of the user */
70 gid_t usergid = 0;              /* gid of the user */
71 uid_t asuid = 0;                /* uid of the user whose fcrontab we are working on */
72 gid_t asgid = 0;                /* gid of the user whose fcrontab we are working on */
73 uid_t fcrontab_uid = 0;         /* uid of the fcron user */
74 gid_t fcrontab_gid = 0;         /* gid of the fcron user */
75 uid_t rootuid = 0;              /* uid of root */
76 gid_t rootgid = 0;              /* gid of root */
77
78 char need_sig = 0;              /* do we need to signal fcron daemon */
79
80 char orig_dir[PATH_LEN];
81 cf_t *file_base = NULL;
82 char buf[PATH_LEN];
83 char file[PATH_LEN];
84
85 /* needed by log part : */
86 char *prog_name = NULL;
87 char foreground = 1;
88 pid_t daemon_pid = 0;
89
90 #ifdef HAVE_LIBPAM
91 int conv_pam(int num_msg, const struct pam_message **msgm,
92              struct pam_response **response, void *appdata_ptr);
93 pam_handle_t *pamh = NULL;
94 const struct pam_conv apamconv = { conv_pam, NULL };
95 #endif                          /* HAVE_LIBPAM */
96
97 void
98 info(void)
99     /* print some informations about this program :
100      * version, license */
101 {
102     fprintf(stderr,
103             "fcrontab " VERSION_QUOTED " - user interface to daemon fcron\n"
104             "Copyright " COPYRIGHT_QUOTED " Thibault Godouet <fcron@free.fr>\n"
105             "This program is free software distributed WITHOUT ANY WARRANTY.\n"
106             "See the GNU General Public License for more details.\n");
107
108     exit(EXIT_OK);
109
110 }
111
112
113 void
114 usage(void)
115   /*  print a help message about command line options and exit */
116 {
117     fprintf(stderr,
118             "fcrontab [-n] file [user|-u user]\n"
119             "fcrontab { -l | -r | -e | -z } [-n] [user|-u user]\n"
120             "fcrontab -h\n"
121             "  -u user    specify user name.\n"
122             "  -l         list user's current fcrontab.\n"
123             "  -r         remove user's current fcrontab.\n"
124             "  -e         edit user's current fcrontab.\n"
125             "  -z         reinstall user's fcrontab from source code.\n"
126             "  -n         ignore previous version of file.\n"
127             "  -c f       make fcrontab use config file f.\n"
128             "  -d         set up debug mode.\n"
129             "  -h         display this help message.\n"
130             "  -V         display version & infos about fcrontab.\n" "\n");
131
132     exit(EXIT_ERR);
133 }
134
135
136 void
137 xexit(int exit_val)
138     /* launch signal if needed and exit */
139 {
140     pid_t pid = 0;
141
142     /* WARNING: make sure we never call die_e() or something that could call
143      *          die_e() here, as die_e() would then call xexit() and we could
144      *          go into a loop! */
145
146     if (need_sig == 1) {
147
148         /* fork and exec fcronsighup */
149         switch (pid = fork()) {
150         case 0:
151             /* child */
152             if (getegid() != fcrontab_gid && setegid(fcrontab_gid) != 0) {
153                 error_e("could not change egid to fcrontab_gid[%d]",
154                         fcrontab_gid);
155                 exit(ERR);
156             }
157             execl(BINDIREX "/fcronsighup", BINDIREX "/fcronsighup", fcronconf,
158                   NULL);
159
160             error_e("Could not exec " BINDIREX " fcronsighup");
161             exit(ERR);
162             break;
163
164         case -1:
165             error_e("Could not fork (fcron has not been signaled)");
166             exit(ERR);
167             break;
168
169         default:
170             /* parent */
171             waitpid(pid, NULL, 0);
172             break;
173         }
174     }
175
176 #ifdef HAVE_LIBPAM
177     /* we need those rights for pam to close properly */
178     if (geteuid() != fcrontab_uid && seteuid(fcrontab_uid) != 0)
179         die_e("could not change euid to %d", fcrontab_uid);
180     if (getegid() != fcrontab_gid && setegid(fcrontab_gid) != 0)
181         die_e("could not change egid to %d", fcrontab_gid);
182     pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT);
183     pam_end(pamh, pam_close_session(pamh, PAM_SILENT));
184 #endif
185
186     exit(exit_val);
187
188 }
189
190 int
191 copy_src(int from, char *dest)
192     /* copy src file orig (already opened) to dest */
193     /* we first copy the file to a temp file name, and then we rename it,
194      * so as to avoid data loss if the filesystem is full. */
195 {
196     int to_fd = -1;
197     int nb;
198     char *copy_buf[LINE_LEN];
199
200     char tmp_filename_str[PATH_LEN + 4];
201     int dest_path_len, tmp_filename_index;
202     char *tmp_suffix_str = ".tmp";
203     int max_dest_len = sizeof(tmp_filename_str) - sizeof(tmp_suffix_str);
204
205     if (from < 0) {
206         die("copy_src() called with an invalid 'from' argument");
207     }
208
209     /* just in case the file was read in the past... */
210     lseek(from, 0, SEEK_SET);
211
212     /* the temp file must be in the same directory as the dest file */
213     dest_path_len = strlen(dest);
214     strncpy(tmp_filename_str, dest, max_dest_len);
215     tmp_filename_index = (dest_path_len > max_dest_len) ?
216         max_dest_len : dest_path_len;
217     strcpy(&tmp_filename_str[tmp_filename_index], tmp_suffix_str);
218
219     /* create it as fcrontab_uid (to avoid problem if user's uid changed)
220      * except for root. Root requires filesystem uid root for security
221      * reasons */
222     to_fd =
223         open_as_user(tmp_filename_str,
224                      (asuid == rootuid) ? rootuid : fcrontab_uid, fcrontab_gid,
225                      O_WRONLY | O_CREAT | O_TRUNC | O_SYNC,
226                      S_IRUSR | S_IWUSR | S_IRGRP);
227     if (to_fd < 0) {
228         error_e("could not open file %s", tmp_filename_str);
229         goto exiterr;
230     }
231
232     if (asuid == rootuid) {
233         if (fchmod(to_fd, S_IWUSR | S_IRUSR) != 0) {
234             error_e("Could not fchmod %s to 600", tmp_filename_str);
235             goto exiterr;
236         }
237         if (fchown(to_fd, rootuid, fcrontab_gid) != 0) {
238             error_e("Could not fchown %s to root", tmp_filename_str);
239             goto exiterr;
240         }
241     }
242
243     while ((nb = read(from, copy_buf, sizeof(copy_buf))) != -1 && nb != 0)
244         if (write(to_fd, copy_buf, nb) != nb) {
245             error_e("Error while copying file (no space left ?)."
246                     " Aborting : old source file kept");
247             goto exiterr;
248         }
249
250     close(to_fd);
251     to_fd = -1;
252
253     if (rename_as_user(tmp_filename_str, dest, useruid, fcrontab_gid) < 0) {
254         error_e("Unable to rename %s to %s : old source file kept",
255                 tmp_filename_str, dest);
256         goto exiterr;
257     }
258
259     return OK;
260
261  exiterr:
262     if (to_fd != -1)
263         close(to_fd);
264     return ERR;
265 }
266
267
268 int
269 remove_fcrontab(char rm_orig)
270     /* remove user's fcrontab and tell daemon to update his conf */
271     /* note : the binary fcrontab is removed by fcron */
272 {
273     int return_val = OK;
274     int fd;
275
276     if (rm_orig)
277         explain("removing %s's fcrontab", user);
278
279     /* remove source and formated file */
280     if ((rm_orig && remove_as_user(buf, fcrontab_uid, fcrontab_gid)) != 0) {
281         if (errno == ENOENT)
282             return_val = ENOENT;
283         else
284             error_e("could not remove %s", buf);
285     }
286
287     /* try to remove the temp file in case he has not
288      * been read by fcron daemon */
289     snprintf(buf, sizeof(buf), "new.%s", user);
290     remove_as_user(buf, useruid, fcrontab_gid);
291
292     /* finally create a file in order to tell the daemon
293      * a file was removed, and launch a signal to daemon */
294     snprintf(buf, sizeof(buf), "rm.%s", user);
295     fd = open_as_user(buf, fcrontab_uid, fcrontab_gid,
296                       O_CREAT | O_TRUNC | O_EXCL, S_IRUSR | S_IWUSR);
297
298     if (fd == -1) {
299         if (errno != EEXIST)
300             error_e("Can't create file %s", buf);
301     }
302     else if (asuid == rootuid && fchown(fd, rootuid, fcrontab_gid) != 0)
303         error_e("Could not fchown %s to root", buf);
304     close(fd);
305
306     need_sig = 1;
307
308     return return_val;
309
310 }
311
312
313 int
314 write_file(int fd)
315 {
316     int return_val = OK;
317
318     if (ignore_prev == 1)
319         /* if user wants to ignore previous version, we remove it *
320          * ( fcron daemon remove files no longer wanted before
321          *   adding new ones ) */
322         remove_fcrontab(0);
323
324     /* copy original file to fcrontabs dir */
325     snprintf(buf, sizeof(buf), "%s.orig", user);
326     if (copy_src(fd, buf) == ERR) {
327         return_val = ERR;
328     }
329     else {
330
331         if (file_base->cf_line_base == NULL) {
332             /* no entries */
333             explain("%s's fcrontab contains no entries : removed.", user);
334             remove_fcrontab(0);
335         }
336         else {
337             /* write the binary fcrontab on disk */
338             snprintf(buf, sizeof(buf), "new.%s", user);
339             if (save_file(buf) != OK)
340                 return_val = ERR;
341         }
342
343     }
344
345     return return_val;
346 }
347
348 int
349 make_file(char *file, int fd)
350 {
351     explain("installing file %s for user %s", file, user);
352
353     /* read file and create a list in memory */
354     switch (read_file(file, fd)) {
355     case 2:
356     case OK:
357
358         if (write_file(fd) == ERR)
359             return ERR;
360         else
361             /* tell daemon to update the conf */
362             need_sig = 1;
363
364         /* free memory used to store the list */
365         delete_file(user);
366
367         break;
368
369     case ERR:
370         return ERR;
371     }
372
373     return OK;
374
375 }
376
377
378 void
379 list_file(char *file)
380 {
381     FILE *f = NULL;
382     int c;
383     int fd = -1;
384
385     explain("listing %s's fcrontab", user);
386
387     fd = open_as_user(file, useruid, fcrontab_gid, O_RDONLY);
388     if (fd < 0) {
389         if (errno == ENOENT) {
390             explain("user %s has no fcrontab.", user);
391             return;
392         }
393         else
394             die_e("User %s could not read file \"%s\"", user, file);
395     }
396
397     f = fdopen(fd, "r");
398     if (f == NULL) {
399         close(fd);
400         die_e("User %s could not read file \"%s\"", user, file);
401     }
402
403     while ((c = getc(f)) != EOF)
404         putchar(c);
405
406     fclose(f);
407
408 }
409
410 void
411 edit_file(char *fcron_orig)
412     /* copy file to a temp file, edit that file, and install it
413      * if necessary */
414 {
415     char *cureditor = NULL;
416     char editorcmd[PATH_LEN];
417     pid_t pid;
418     int status;
419     struct stat st;
420     time_t mtime = 0;
421     char *tmp_str;
422     FILE *f = NULL, *fi = NULL;
423     int file = -1, origfd = -1;
424     int c;
425     char correction = 0;
426     short return_val = EXIT_OK;
427
428     explain("fcrontab : editing %s's fcrontab", user);
429
430     if ((cureditor = getenv("VISUAL")) == NULL || strcmp(cureditor, "\0") == 0)
431         if ((cureditor = getenv("EDITOR")) == NULL
432             || strcmp(cureditor, "\0") == 0)
433             cureditor = editor;
434
435     file = temp_file(&tmp_str);
436     if ((fi = fdopen(file, "w")) == NULL) {
437         error_e("could not fdopen");
438         goto exiterr;
439     }
440 #ifndef USE_SETE_ID
441     if (fchown(file, asuid, asgid) != 0) {
442         error_e("Could not fchown %s to asuid and asgid", tmp_str);
443         goto exiterr;
444     }
445 #endif
446     /* copy user's fcrontab (if any) to a temp file */
447     origfd = open_as_user(fcron_orig, useruid, fcrontab_gid, O_RDONLY);
448     if (origfd < 0) {
449         if (errno != ENOENT) {
450             error_e("could not open file %s", fcron_orig);
451             goto exiterr;
452         }
453         else
454             fprintf(stderr, "no fcrontab for %s - using an empty one\n", user);
455     }
456     else {
457         f = fdopen(origfd, "r");
458         if (f == NULL) {
459             error_e("could not fdopen file %s", fcron_orig);
460             goto exiterr;
461         }
462         /* copy original file to temp file */
463         while ((c = getc(f)) != EOF) {
464             if (putc(c, fi) == EOF) {
465                 error_e("could not write to file %s", tmp_str);
466                 goto exiterr;
467             }
468         }
469         fclose(f);
470         f = NULL;
471
472         if (ferror(fi))
473             error_e("Error while writing new fcrontab to %s");
474     }
475
476     /* Don't close fi, because we still need the file descriptor 'file' */
477     if (fflush(fi) != 0)
478         die_e("Could not fflush(%s)", fi);
479     fi = NULL;
480
481     do {
482
483         if (fstat(file, &st) == 0)
484             mtime = st.st_mtime;
485         else {
486             error_e("could not stat \"%s\"", tmp_str);
487             goto exiterr;
488         }
489
490 #ifndef USE_SETE_ID
491         /* chown the file (back if correction) to asuid/asgid so as user can edit it */
492         if (fchown(file, asuid, asgid) != 0
493             || fchmod(file, S_IRUSR | S_IWUSR) != 0) {
494             fprintf(stderr, "Can't chown or chmod %s.\n", tmp_str);
495             goto exiterr;
496         }
497 #endif
498         /* close the file before the user edits it */
499         close(file);
500
501         switch (pid = fork()) {
502         case 0:
503             /* child */
504             if (useruid != rootuid) {
505                 if (setgid(asgid) < 0) {
506                     error_e("setgid(asgid)");
507                     goto exiterr;
508                 }
509                 if (setuid(asuid) < 0) {
510                     error_e("setuid(asuid)");
511                     goto exiterr;
512                 }
513             }
514             else {
515                 /* Some programs, like perl, require gid=egid : */
516                 if (setgid(getgid()) < 0) {
517                     error_e("setgid(getgid())");
518                     goto exiterr;
519                 }
520             }
521             snprintf(editorcmd, sizeof(editorcmd), "%s %s", cureditor, tmp_str);
522             if (chdir(tmp_path) != 0)
523                 error_e("Could not chdir to %s", tmp_path);
524             execlp(shell, shell, "-c", editorcmd, tmp_str, NULL);
525             error_e("Error while running \"%s\"", cureditor);
526             goto exiterr;
527
528         case -1:
529             error_e("fork");
530             goto exiterr;
531
532         default:
533             /* parent */
534             break;
535         }
536
537         /* only reached by parent */
538         waitpid(pid, &status, 0);
539         if (!WIFEXITED(status)) {
540             fprintf(stderr,
541                     "Editor exited abnormally:" " fcrontab is unchanged.\n");
542             goto exiterr;
543         }
544
545         /* re-open the file that has just been edited */
546         file = open_as_user(tmp_str, useruid, usergid, O_RDONLY);
547         if (file < 0) {
548             error_e("Could not open file %s", tmp_str);
549             goto exiterr;
550         }
551
552 #ifndef USE_SETE_ID
553         /* chown the file back to rootuid/rootgid */
554         if (fchown(file, rootuid, rootgid) != 0
555             || fchmod(file, S_IRUSR | S_IWUSR) != 0) {
556             fprintf(stderr, "Can't chown or chmod %s.\n", tmp_str);
557             goto exiterr;
558         }
559 #endif
560
561         /* check if file has been modified */
562         if (fstat(file, &st) != 0) {
563             error_e("could not stat %s", tmp_str);
564             goto exiterr;
565         }
566
567         else if (st.st_mtime > mtime || correction == 1) {
568
569             correction = 0;
570
571             switch (read_file(tmp_str, file)) {
572             case ERR:
573                 goto exiterr;
574             case 2:
575                 fprintf(stderr, "\nFile contains some errors. "
576                         "Ignore [i] or Correct [c] ? ");
577                 while ((c = getchar())) {
578                     /* consume the rest of the line, e.g. the newline char (\n) */
579                     while (c != '\n' && (getchar() != '\n')) ;
580
581                     if (c == 'i') {
582                         break;
583                     }
584                     else if (c == 'c') {
585                         /* free memory used to store the list */
586                         delete_file(user);
587                         correction = 1;
588                         break;
589                     }
590                     else {
591                         fprintf(stderr,
592                                 "Please press c to correct, "
593                                 "or i to ignore: ");
594                     }
595                 }
596                 break;
597             default:
598                 break;
599             }
600
601         }
602         else {
603             fprintf(stderr,
604                     "Fcrontab is unchanged :" " no need to install it.\n");
605             goto end;
606         }
607
608     } while (correction == 1);
609
610     if (write_file(file) != OK)
611         return_val = EXIT_ERR;
612     else
613         /* tell daemon to update the conf */
614         need_sig = 1;
615
616
617     /* free memory used to store the list */
618     delete_file(user);
619
620  end:
621     if (file != -1 && close(file) != 0)
622         error_e("could not close %s", tmp_str);
623     if (remove_as_user(tmp_str, useruid, fcrontab_gid) != 0)
624         error_e("could not remove %s", tmp_str);
625     free(tmp_str);
626     xexit(return_val);
627
628  exiterr:
629     if (remove_as_user(tmp_str, useruid, fcrontab_gid) != 0)
630         error_e("could not remove %s", tmp_str);
631     free(tmp_str);
632     if (f != NULL)
633         fclose(f);
634     if (fi != NULL)
635         fclose(fi);
636     if (file != -1)
637         close(file);
638     xexit(EXIT_ERR);
639
640 }
641
642
643 int
644 install_stdin(void)
645     /* install what we get through stdin */
646 {
647     int tmp_fd = 0;
648     FILE *tmp_file = NULL;
649     char *tmp_str = NULL;
650     int c;
651     short return_val = EXIT_OK;
652
653     tmp_fd = temp_file(&tmp_str);
654
655     if ((tmp_file = fdopen(tmp_fd, "w")) == NULL)
656         die_e("Could not fdopen file %s", tmp_str);
657
658     while ((c = getc(stdin)) != EOF)
659         putc(c, tmp_file);
660     /* // */
661     debug("Copied stdin to %s, about to parse file %s...", tmp_str, tmp_str);
662
663     /* don't closes tmp_fd as it will be used for make_file(): */
664     if (fflush(tmp_file) != 0)
665         die_e("Could not fflush(%s)", tmp_file);
666
667     if (make_file(tmp_str, tmp_fd) == ERR)
668         goto exiterr;
669     else
670         goto exit;
671
672  exiterr:
673     return_val = EXIT_ERR;
674  exit:
675     if (remove(tmp_str) != 0)
676         error_e("Could not remove %s", tmp_str);
677     free(tmp_str);
678     return return_val;
679
680 }
681
682 void
683 reinstall(char *fcron_orig)
684 {
685     int i = 0;
686
687     explain("reinstalling %s's fcrontab", user);
688
689     if ((i = open_as_user(fcron_orig, useruid, fcrontab_gid, O_RDONLY)) < 0) {
690         if (errno == ENOENT) {
691             fprintf(stderr, "Could not reinstall: user %s has no fcrontab\n",
692                     user);
693         }
694         else
695             fprintf(stderr, "Could not open \"%s\": %s\n", fcron_orig,
696                     strerror(errno));
697
698         xexit(EXIT_ERR);
699     }
700
701     close(0);
702     dup2(i, 0);
703     close(i);
704
705     xexit(install_stdin());
706
707 }
708
709
710 #ifdef HAVE_LIBPAM
711 int
712 conv_pam(int num_msg, const struct pam_message **msgm,
713          struct pam_response **response, void *appdata_ptr)
714     /* text based conversation for pam. */
715 {
716     int count = 0;
717     struct pam_response *reply;
718
719     if (num_msg <= 0)
720         return PAM_CONV_ERR;
721
722     reply = (struct pam_response *)calloc(num_msg, sizeof(struct pam_response));
723     if (reply == NULL) {
724         debug("no memory for responses");
725         return PAM_CONV_ERR;
726     }
727
728     for (count = 0; count < num_msg; ++count) {
729         char *string = NULL;
730
731         switch (msgm[count]->msg_style) {
732         case PAM_PROMPT_ECHO_OFF:
733             string = read_string(CONV_ECHO_OFF, msgm[count]->msg);
734             if (string == NULL) {
735                 goto failed_conversation;
736             }
737             break;
738         case PAM_PROMPT_ECHO_ON:
739             string = read_string(CONV_ECHO_ON, msgm[count]->msg);
740             if (string == NULL) {
741                 goto failed_conversation;
742             }
743             break;
744         case PAM_ERROR_MSG:
745             if (fprintf(stderr, "%s\n", msgm[count]->msg) < 0) {
746                 goto failed_conversation;
747             }
748             break;
749         case PAM_TEXT_INFO:
750             if (fprintf(stdout, "%s\n", msgm[count]->msg) < 0) {
751                 goto failed_conversation;
752             }
753             break;
754         default:
755             fprintf(stderr, "erroneous conversation (%d)\n",
756                     msgm[count]->msg_style);
757             goto failed_conversation;
758         }
759
760         if (string) {           /* must add to reply array */
761             /* add string to list of responses */
762
763             reply[count].resp_retcode = 0;
764             reply[count].resp = string;
765             string = NULL;
766         }
767     }
768
769     /* New (0.59+) behavior is to always have a reply - this is
770      * compatable with the X/Open (March 1997) spec. */
771     *response = reply;
772     reply = NULL;
773
774     return PAM_SUCCESS;
775
776  failed_conversation:
777
778     if (reply) {
779         for (count = 0; count < num_msg; ++count) {
780             if (reply[count].resp == NULL) {
781                 continue;
782             }
783             switch (msgm[count]->msg_style) {
784             case PAM_PROMPT_ECHO_ON:
785             case PAM_PROMPT_ECHO_OFF:
786                 Overwrite(reply[count].resp);
787                 free(reply[count].resp);
788                 break;
789             case PAM_ERROR_MSG:
790             case PAM_TEXT_INFO:
791                 /* should not actually be able to get here... */
792                 free(reply[count].resp);
793             }
794             reply[count].resp = NULL;
795         }
796         /* forget reply too */
797         free(reply);
798         reply = NULL;
799     }
800
801     return PAM_CONV_ERR;
802 }
803 #endif                          /* HAVE_LIBPAM */
804
805
806 void
807 parseopt(int argc, char *argv[])
808   /* set options */
809 {
810
811     int c;
812     extern char *optarg;
813     extern int optind, opterr, optopt;
814     struct passwd *pass;
815 #ifdef SYSFCRONTAB
816     char is_sysfcrontab = 0;
817 #endif
818
819     /* constants and variables defined by command line */
820
821     while (1) {
822         c = getopt(argc, argv, "u:lrezdnhVc:");
823         if (c == EOF)
824             break;
825         switch (c) {
826
827         case 'V':
828             info();
829             break;
830
831         case 'h':
832             usage();
833             break;
834
835         case 'u':
836             if (useruid != rootuid) {
837                 fprintf(stderr, "must be privileged to use -u\n");
838                 xexit(EXIT_ERR);
839             }
840             user = strdup2(optarg);
841             break;
842
843         case 'd':
844             debug_opt = 1;
845             break;
846
847         case 'l':
848             if (rm_opt || edit_opt || reinstall_opt) {
849                 fprintf(stderr, "Only one of the options -l, -r, -e and -z"
850                         "may be used simultaneously.\n");
851                 xexit(EXIT_ERR);
852             }
853             list_opt = 1;
854             rm_opt = edit_opt = reinstall_opt = 0;
855             break;
856
857         case 'r':
858             if (list_opt || edit_opt || reinstall_opt) {
859                 fprintf(stderr, "Only one of the options -l, -r, -e and -z"
860                         "may be used simultaneously.\n");
861                 xexit(EXIT_ERR);
862             }
863             rm_opt = 1;
864             list_opt = edit_opt = reinstall_opt = 0;
865             break;
866
867         case 'e':
868             if (list_opt || rm_opt || reinstall_opt) {
869                 fprintf(stderr, "Only one of the options -l, -r, -e and -z"
870                         "may be used simultaneously.\n");
871                 xexit(EXIT_ERR);
872             }
873             edit_opt = 1;
874             list_opt = rm_opt = reinstall_opt = 0;
875             break;
876
877         case 'z':
878             if (list_opt || rm_opt || edit_opt) {
879                 fprintf(stderr, "Only one of the options -l, -r, -e and -z"
880                         "may be used simultaneously.\n");
881                 xexit(EXIT_ERR);
882             }
883             reinstall_opt = ignore_prev = 1;
884             list_opt = rm_opt = edit_opt = 0;
885             break;
886
887         case 'n':
888             ignore_prev = 1;
889             break;
890
891         case 'c':
892             if (optarg[0] == '/') {
893                 Set(fcronconf, optarg);
894             }
895             else {
896                 char buf[PATH_LEN];
897                 snprintf(buf, sizeof(buf), "%s/%s", orig_dir, optarg);
898                 Set(fcronconf, buf);
899             }
900             break;
901
902         case ':':
903             fprintf(stderr, "(setopt) Missing parameter.\n");
904             usage();
905
906         case '?':
907             usage();
908
909         default:
910             fprintf(stderr, "(setopt) Warning: getopt returned %c.\n", c);
911         }
912     }
913
914     /* read fcron.conf and update global parameters */
915     read_conf();
916
917     /* read the file name and/or user and check validity of the arguments */
918     if (argc - optind > 2)
919         usage();
920     else if (argc - optind == 2) {
921         if (list_opt + rm_opt + edit_opt + reinstall_opt == 0)
922             file_opt = optind++;
923         else
924             usage();
925
926         if (useruid != rootuid) {
927             fprintf(stderr, "must be privileged to use -u\n");
928             xexit(EXIT_ERR);
929         }
930         Set(user, argv[optind]);
931     }
932     else if (argc - optind == 1) {
933         if (list_opt + rm_opt + edit_opt + reinstall_opt == 0)
934             file_opt = optind;
935         else {
936             if (useruid != rootuid) {
937                 fprintf(stderr, "must be privileged to use [user|-u user]\n");
938                 xexit(EXIT_ERR);
939             }
940             Set(user, argv[optind]);
941         }
942     }
943     else if (list_opt + rm_opt + edit_opt + reinstall_opt != 1)
944         usage();
945
946     if (user == NULL) {
947         /* get user's name using getpwuid() */
948         if (!(pass = getpwuid(useruid)))
949             die_e("user \"%s\" is not in passwd file. Aborting.", USERNAME);
950         /* we need to strdup2 the value given by getpwuid() because we free
951          * file->cf_user in delete_file */
952         user = strdup2(pass->pw_name);
953         asuid = pass->pw_uid;
954         asgid = pass->pw_gid;
955     }
956     else {
957 #ifdef SYSFCRONTAB
958         if (strcmp(user, SYSFCRONTAB) == 0) {
959             is_sysfcrontab = 1;
960             asuid = rootuid;
961             asgid = rootgid;
962         }
963         else
964 #endif                          /* def SYSFCRONTAB */
965         {
966             errno = 0;
967             if ((pass = getpwnam(user))) {
968                 asuid = pass->pw_uid;
969                 asgid = pass->pw_gid;
970             }
971             else
972                 die_e("user \"%s\" is not in passwd file. Aborting.", user);
973         }
974     }
975
976     if (
977 #ifdef SYSFCRONTAB
978            !is_sysfcrontab &&
979 #endif
980            !is_allowed(user)) {
981         die("User \"%s\" is not allowed to use %s. Aborting.", user, prog_name);
982     }
983
984 #ifdef SYSFCRONTAB
985     if (is_sysfcrontab)
986         runas = ROOTNAME;
987     else
988 #endif
989         runas = user;
990
991 }
992
993
994 int
995 main(int argc, char **argv)
996 {
997
998 #ifdef HAVE_LIBPAM
999     int retcode = 0;
1000     const char *const *env;
1001 #endif
1002     struct passwd *pass;
1003
1004     rootuid = get_user_uid_safe(ROOTNAME);
1005     rootgid = get_group_gid_safe(ROOTGROUP);
1006
1007     memset(buf, 0, sizeof(buf));
1008     memset(file, 0, sizeof(file));
1009
1010     if (strrchr(argv[0], '/') == NULL)
1011         prog_name = argv[0];
1012     else
1013         prog_name = strrchr(argv[0], '/') + 1;
1014
1015     useruid = getuid();
1016     usergid = getgid();
1017
1018 #ifdef USE_SETE_ID
1019     /* drop any suid privilege (that we use to write files) but keep sgid
1020      * one for now: we need it for read_conf() and is_allowed() */
1021     seteuid_safe(useruid);
1022 #endif
1023
1024     errno = 0;
1025     if (!(pass = getpwnam(USERNAME)))
1026         die_e("user \"%s\" is not in passwd file. Aborting.", USERNAME);
1027     fcrontab_uid = pass->pw_uid;
1028     fcrontab_gid = pass->pw_gid;
1029
1030     /* get current dir */
1031     orig_dir[0] = '\0';
1032     if (getcwd(orig_dir, sizeof(orig_dir)) == NULL)
1033         die_e("getcwd");
1034
1035     /* interpret command line options */
1036     parseopt(argc, argv);
1037
1038 #ifdef USE_SETE_ID
1039     /* drop any privilege we may have: we will only get them back
1040      * temporarily every time we need it. */
1041     seteuid_safe(useruid);
1042     setegid_safe(usergid);
1043 #endif
1044
1045 #ifdef HAVE_LIBPAM
1046     /* Open PAM session for the user and obtain any security
1047      * credentials we might need */
1048
1049     debug("username: %s, runas: %s", user, runas);
1050     retcode = pam_start("fcrontab", runas, &apamconv, &pamh);
1051     if (retcode != PAM_SUCCESS)
1052         die_pame(pamh, retcode, "Could not start PAM");
1053     retcode = pam_authenticate(pamh, 0);        /* is user really user? */
1054     if (retcode != PAM_SUCCESS)
1055         die_pame(pamh, retcode, "Could not authenticate user using PAM (%d)",
1056                  retcode);
1057     retcode = pam_acct_mgmt(pamh, 0);   /* permitted access? */
1058     if (retcode != PAM_SUCCESS)
1059         die_pame(pamh, retcode, "Could not init PAM account management (%d)",
1060                  retcode);
1061     retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
1062     if (retcode != PAM_SUCCESS)
1063         die_pame(pamh, retcode, "Could not set PAM credentials");
1064     retcode = pam_open_session(pamh, 0);
1065     if (retcode != PAM_SUCCESS)
1066         die_pame(pamh, retcode, "Could not open PAM session");
1067
1068     env = (const char *const *)pam_getenvlist(pamh);
1069     while (env && *env) {
1070         if (putenv((char *)*env))
1071             die_e("Could not copy PAM environment");
1072         env++;
1073     }
1074
1075     /* Close the log here, because PAM calls openlog(3) and
1076      * our log messages could go to the wrong facility */
1077     xcloselog();
1078 #endif                          /* USE_PAM */
1079
1080 #ifdef USE_SETE_ID
1081     seteuid_safe(fcrontab_uid);
1082     /* change directory */
1083     if (chdir(fcrontabs) != 0) {
1084         error_e("Could not chdir to %s", fcrontabs);
1085         xexit(EXIT_ERR);
1086     }
1087     seteuid_safe(useruid);
1088 #else                           /* USE_SETE_ID */
1089
1090     if (setuid(rootuid) != 0)
1091         die_e("Could not change uid to rootuid");
1092     if (setgid(rootgid) != 0)
1093         die_e("Could not change gid to rootgid");
1094     /* change directory */
1095     if (chdir(fcrontabs) != 0) {
1096         error_e("Could not chdir to %s", fcrontabs);
1097         xexit(EXIT_ERR);
1098     }
1099 #endif                          /* USE_SETE_ID */
1100
1101     /* this program is seteuid : we set default permission mode
1102      * to 640 for a normal user, 600 for root, for security reasons */
1103     if (asuid == rootuid)
1104         umask(066);             /* octal : '0' + number in octal notation */
1105     else
1106         umask(026);
1107
1108     snprintf(buf, sizeof(buf), "%s.orig", user);
1109
1110     /* determine what action should be taken */
1111     if (file_opt) {
1112
1113         if (strcmp(argv[file_opt], "-") == 0)
1114
1115             xexit(install_stdin());
1116
1117         else {
1118             int fd = -1;
1119
1120             if (*argv[file_opt] != '/')
1121                 /* this is just the file name, not the path : complete it */
1122                 snprintf(file, sizeof(file), "%s/%s", orig_dir, argv[file_opt]);
1123             else {
1124                 strncpy(file, argv[file_opt], sizeof(file) - 1);
1125                 file[sizeof(file) - 1] = '\0';
1126             }
1127
1128             fd = open_as_user(file, useruid, usergid, O_RDONLY);
1129             if (fd < 0)
1130                 die_e("Could not open file %s", file);
1131             if (make_file(file, fd) == OK)
1132                 xexit(EXIT_OK);
1133             else
1134                 xexit(EXIT_ERR);
1135
1136         }
1137
1138     }
1139
1140     /* remove user's entries */
1141     if (rm_opt == 1) {
1142         if (remove_fcrontab(1) == ENOENT)
1143             fprintf(stderr, "no fcrontab for %s\n", user);
1144         xexit(EXIT_OK);
1145     }
1146
1147     /* list user's entries */
1148     if (list_opt == 1) {
1149         list_file(buf);
1150         xexit(EXIT_OK);
1151     }
1152
1153
1154     /* edit user's entries */
1155     if (edit_opt == 1) {
1156         edit_file(buf);
1157         xexit(EXIT_OK);
1158     }
1159
1160     /* reinstall user's entries */
1161     if (reinstall_opt == 1) {
1162         reinstall(buf);
1163         xexit(EXIT_OK);
1164     }
1165
1166     /* never reached */
1167     return EXIT_OK;
1168 }