]> granicus.if.org Git - fcron/blob - fcrontab.c
41931fc24043c1fd203edb70e4c49e9681c2fb2a
[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, const 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     xclose_check(&to_fd, dest);
251
252     if (rename_as_user(tmp_filename_str, dest, useruid, fcrontab_gid) < 0) {
253         error_e("Unable to rename %s to %s : old source file kept",
254                 tmp_filename_str, dest);
255         goto exiterr;
256     }
257
258     return OK;
259
260  exiterr:
261     if (to_fd != -1)
262         xclose_check(&to_fd, dest);
263     return ERR;
264 }
265
266
267 int
268 remove_fcrontab(char rm_orig)
269     /* remove user's fcrontab and tell daemon to update his conf */
270     /* note : the binary fcrontab is removed by fcron */
271 {
272     int return_val = OK;
273     int fd;
274
275     if (rm_orig)
276         explain("removing %s's fcrontab", user);
277
278     /* remove source and formated file */
279     if ((rm_orig && remove_as_user(buf, fcrontab_uid, fcrontab_gid)) != 0) {
280         if (errno == ENOENT)
281             return_val = ENOENT;
282         else
283             error_e("could not remove %s", buf);
284     }
285
286     /* try to remove the temp file in case he has not
287      * been read by fcron daemon */
288     snprintf(buf, sizeof(buf), "new.%s", user);
289     remove_as_user(buf, useruid, fcrontab_gid);
290
291     /* finally create a file in order to tell the daemon
292      * a file was removed, and launch a signal to daemon */
293     snprintf(buf, sizeof(buf), "rm.%s", user);
294     fd = open_as_user(buf, fcrontab_uid, fcrontab_gid,
295                       O_CREAT | O_TRUNC | O_EXCL, S_IRUSR | S_IWUSR);
296
297     if (fd == -1) {
298         if (errno != EEXIST)
299             error_e("Can't create file %s", buf);
300     }
301     else if (asuid == rootuid && fchown(fd, rootuid, fcrontab_gid) != 0)
302         error_e("Could not fchown %s to root", buf);
303     xclose_check(&fd, buf);
304
305     need_sig = 1;
306
307     return return_val;
308
309 }
310
311
312 int
313 write_file(int fd)
314 {
315     int return_val = OK;
316
317     if (ignore_prev == 1)
318         /* if user wants to ignore previous version, we remove it *
319          * ( fcron daemon remove files no longer wanted before
320          *   adding new ones ) */
321         remove_fcrontab(0);
322
323     /* copy original file to fcrontabs dir */
324     snprintf(buf, sizeof(buf), "%s.orig", user);
325     if (copy_src(fd, buf) == ERR) {
326         return_val = ERR;
327     }
328     else {
329
330         if (file_base->cf_line_base == NULL) {
331             /* no entries */
332             explain("%s's fcrontab contains no entries : removed.", user);
333             remove_fcrontab(0);
334         }
335         else {
336             /* write the binary fcrontab on disk */
337             snprintf(buf, sizeof(buf), "new.%s", user);
338             if (save_file(buf) != OK)
339                 return_val = ERR;
340         }
341
342     }
343
344     return return_val;
345 }
346
347 int
348 make_file(char *file, int fd)
349 {
350     explain("installing file %s for user %s", file, user);
351
352     /* read file and create a list in memory */
353     switch (read_file(file, fd)) {
354     case 2:
355     case OK:
356
357         if (write_file(fd) == ERR)
358             return ERR;
359         else
360             /* tell daemon to update the conf */
361             need_sig = 1;
362
363         /* free memory used to store the list */
364         delete_file(user);
365
366         break;
367
368     case ERR:
369         return ERR;
370     }
371
372     return OK;
373
374 }
375
376
377 void
378 list_file(const char *file)
379 {
380     FILE *f = NULL;
381     int c;
382     int fd = -1;
383
384     explain("listing %s's fcrontab", user);
385
386     fd = open_as_user(file, useruid, fcrontab_gid, O_RDONLY);
387     if (fd < 0) {
388         if (errno == ENOENT) {
389             explain("user %s has no fcrontab.", user);
390             return;
391         }
392         else
393             die_e("User %s could not read file \"%s\"", user, file);
394     }
395
396     f = fdopen(fd, "r");
397     if (f == NULL) {
398         xclose_check(&fd, file);
399         die_e("User %s could not read file \"%s\"", user, file);
400     }
401
402     while ((c = getc(f)) != EOF)
403         putchar(c);
404
405     xfclose_check(&f, file);
406
407 }
408
409 void
410 edit_file(const char *fcron_orig)
411     /* copy file to a temp file, edit that file, and install it
412      * if necessary */
413 {
414     char *cureditor = NULL;
415     char editorcmd[PATH_LEN];
416     pid_t pid;
417     int status;
418     struct stat st;
419     time_t mtime = 0;
420     char *tmp_str = NULL;
421     FILE *f = NULL, *fi = NULL;
422     int file = -1, origfd = -1;
423     int c;
424     char correction = 0;
425     short return_val = EXIT_OK;
426
427     explain("fcrontab : editing %s's fcrontab", user);
428
429     if ((cureditor = getenv("VISUAL")) == NULL || strcmp(cureditor, "\0") == 0)
430         if ((cureditor = getenv("EDITOR")) == NULL
431             || strcmp(cureditor, "\0") == 0)
432             cureditor = editor;
433
434     /* temp_file() dies on error, so tmp_str is always set */
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         xfclose_check(&f, fcron_orig);
470
471         if (ferror(fi))
472             error_e("Error while writing new fcrontab to %s");
473     }
474
475     /* Don't close fi, because we still need the file descriptor 'file' */
476     if (fflush(fi) != 0)
477         die_e("Could not fflush(%s)", fi);
478     fi = NULL;
479
480     do {
481
482         if (fstat(file, &st) == 0)
483             mtime = st.st_mtime;
484         else {
485             error_e("could not stat \"%s\"", tmp_str);
486             goto exiterr;
487         }
488
489 #ifndef USE_SETE_ID
490         /* chown the file (back if correction) to asuid/asgid so as user can edit it */
491         if (fchown(file, asuid, asgid) != 0
492             || fchmod(file, S_IRUSR | S_IWUSR) != 0) {
493             fprintf(stderr, "Can't chown or chmod %s.\n", tmp_str);
494             goto exiterr;
495         }
496 #endif
497         /* close the file before the user edits it */
498         xclose_check(&file, tmp_str);
499
500         switch (pid = fork()) {
501         case 0:
502             /* child */
503             if (useruid != rootuid) {
504                 if (setgid(asgid) < 0) {
505                     error_e("setgid(asgid)");
506                     goto exiterr;
507                 }
508                 if (setuid(asuid) < 0) {
509                     error_e("setuid(asuid)");
510                     goto exiterr;
511                 }
512             }
513             else {
514                 /* Some programs, like perl, require gid=egid : */
515                 if (setgid(getgid()) < 0) {
516                     error_e("setgid(getgid())");
517                     goto exiterr;
518                 }
519             }
520             snprintf(editorcmd, sizeof(editorcmd), "%s %s", cureditor, tmp_str);
521             if (chdir(tmp_path) != 0)
522                 error_e("Could not chdir to %s", tmp_path);
523             execlp(shell, shell, "-c", editorcmd, tmp_str, NULL);
524             error_e("Error while running \"%s\"", cureditor);
525             goto exiterr;
526
527         case -1:
528             error_e("fork");
529             goto exiterr;
530
531         default:
532             /* parent */
533             break;
534         }
535
536         /* only reached by parent */
537         waitpid(pid, &status, 0);
538         if (!WIFEXITED(status)) {
539             fprintf(stderr,
540                     "Editor exited abnormally:" " fcrontab is unchanged.\n");
541             goto exiterr;
542         }
543
544         /* re-open the file that has just been edited */
545         file = open_as_user(tmp_str, useruid, usergid, O_RDONLY);
546         if (file < 0) {
547             error_e("Could not open file %s", tmp_str);
548             goto exiterr;
549         }
550
551 #ifndef USE_SETE_ID
552         /* chown the file back to rootuid/rootgid */
553         if (fchown(file, rootuid, rootgid) != 0
554             || fchmod(file, S_IRUSR | S_IWUSR) != 0) {
555             fprintf(stderr, "Can't chown or chmod %s.\n", tmp_str);
556             goto exiterr;
557         }
558 #endif
559
560         /* check if file has been modified */
561         if (fstat(file, &st) != 0) {
562             error_e("could not stat %s", tmp_str);
563             goto exiterr;
564         }
565
566         else if (st.st_mtime > mtime || correction == 1) {
567
568             correction = 0;
569
570             switch (read_file(tmp_str, file)) {
571             case ERR:
572                 goto exiterr;
573             case 2:
574                 fprintf(stderr, "\nFile contains some errors. "
575                         "Ignore [i] or Correct [c] ? ");
576                 while ((c = getchar())) {
577                     /* consume the rest of the line, e.g. the newline char (\n) */
578                     while (c != '\n' && (getchar() != '\n')) ;
579
580                     if (c == 'i') {
581                         break;
582                     }
583                     else if (c == 'c') {
584                         /* free memory used to store the list */
585                         delete_file(user);
586                         correction = 1;
587                         break;
588                     }
589                     else {
590                         fprintf(stderr,
591                                 "Please press c to correct, "
592                                 "or i to ignore: ");
593                     }
594                 }
595                 break;
596             default:
597                 break;
598             }
599
600         }
601         else {
602             fprintf(stderr,
603                     "Fcrontab is unchanged :" " no need to install it.\n");
604             goto end;
605         }
606
607     } while (correction == 1);
608
609     if (write_file(file) != OK)
610         return_val = EXIT_ERR;
611     else
612         /* tell daemon to update the conf */
613         need_sig = 1;
614
615
616     /* free memory used to store the list */
617     delete_file(user);
618
619  end:
620     xclose_check(&file, tmp_str);
621     if (remove_as_user(tmp_str, useruid, fcrontab_gid) != 0)
622         error_e("could not remove %s", tmp_str);
623     Free_safe(tmp_str);
624     xexit(return_val);
625
626  exiterr:
627     xfclose_check(&fi, tmp_str);
628     xclose_check(&file, tmp_str);
629     if (remove_as_user(tmp_str, useruid, fcrontab_gid) != 0)
630         error_e("could not remove %s", tmp_str);
631     xfclose_check(&f, fcron_orig);
632     Free_safe(tmp_str);
633     xexit(EXIT_ERR);
634 }
635
636
637 int
638 install_stdin(void)
639     /* install what we get through stdin */
640 {
641     int tmp_fd = 0;
642     FILE *tmp_file = NULL;
643     char *tmp_str = NULL;
644     int c;
645     short return_val = EXIT_OK;
646
647     tmp_fd = temp_file(&tmp_str);
648
649     if ((tmp_file = fdopen(tmp_fd, "w")) == NULL)
650         die_e("Could not fdopen file %s", tmp_str);
651
652     while ((c = getc(stdin)) != EOF)
653         putc(c, tmp_file);
654     /* // */
655     debug("Copied stdin to %s, about to parse file %s...", tmp_str, tmp_str);
656
657     /* don't closes tmp_fd as it will be used for make_file(): */
658     if (fflush(tmp_file) != 0)
659         die_e("Could not fflush(%s)", tmp_file);
660
661     if (make_file(tmp_str, tmp_fd) == ERR)
662         goto exiterr;
663     else
664         goto exit;
665
666  exiterr:
667     return_val = EXIT_ERR;
668  exit:
669     if (remove(tmp_str) != 0)
670         error_e("Could not remove %s", tmp_str);
671     free(tmp_str);
672     return return_val;
673
674 }
675
676 void
677 reinstall(char *fcron_orig)
678 {
679     int i = -1;
680
681     explain("reinstalling %s's fcrontab", user);
682
683     if ((i = open_as_user(fcron_orig, useruid, fcrontab_gid, O_RDONLY)) < 0) {
684         if (errno == ENOENT) {
685             fprintf(stderr, "Could not reinstall: user %s has no fcrontab\n",
686                     user);
687         }
688         else
689             fprintf(stderr, "Could not open \"%s\": %s\n", fcron_orig,
690                     strerror(errno));
691
692         xexit(EXIT_ERR);
693     }
694
695     close(0);
696     dup2(i, 0);
697     xclose(&i);
698
699     xexit(install_stdin());
700
701 }
702
703
704 #ifdef HAVE_LIBPAM
705 int
706 conv_pam(int num_msg, const struct pam_message **msgm,
707          struct pam_response **response, void *appdata_ptr)
708     /* text based conversation for pam. */
709 {
710     int count = 0;
711     struct pam_response *reply;
712
713     if (num_msg <= 0)
714         return PAM_CONV_ERR;
715
716     reply = (struct pam_response *)calloc(num_msg, sizeof(struct pam_response));
717     if (reply == NULL) {
718         debug("no memory for responses");
719         return PAM_CONV_ERR;
720     }
721
722     for (count = 0; count < num_msg; ++count) {
723         char *string = NULL;
724
725         switch (msgm[count]->msg_style) {
726         case PAM_PROMPT_ECHO_OFF:
727             string = read_string(CONV_ECHO_OFF, msgm[count]->msg);
728             if (string == NULL) {
729                 goto failed_conversation;
730             }
731             break;
732         case PAM_PROMPT_ECHO_ON:
733             string = read_string(CONV_ECHO_ON, msgm[count]->msg);
734             if (string == NULL) {
735                 goto failed_conversation;
736             }
737             break;
738         case PAM_ERROR_MSG:
739             if (fprintf(stderr, "%s\n", msgm[count]->msg) < 0) {
740                 goto failed_conversation;
741             }
742             break;
743         case PAM_TEXT_INFO:
744             if (fprintf(stdout, "%s\n", msgm[count]->msg) < 0) {
745                 goto failed_conversation;
746             }
747             break;
748         default:
749             fprintf(stderr, "erroneous conversation (%d)\n",
750                     msgm[count]->msg_style);
751             goto failed_conversation;
752         }
753
754         if (string) {           /* must add to reply array */
755             /* add string to list of responses */
756
757             reply[count].resp_retcode = 0;
758             reply[count].resp = string;
759             string = NULL;
760         }
761     }
762
763     /* New (0.59+) behavior is to always have a reply - this is
764      * compatable with the X/Open (March 1997) spec. */
765     *response = reply;
766     reply = NULL;
767
768     return PAM_SUCCESS;
769
770  failed_conversation:
771
772     if (reply) {
773         for (count = 0; count < num_msg; ++count) {
774             if (reply[count].resp == NULL) {
775                 continue;
776             }
777             switch (msgm[count]->msg_style) {
778             case PAM_PROMPT_ECHO_ON:
779             case PAM_PROMPT_ECHO_OFF:
780                 Overwrite(reply[count].resp);
781                 free(reply[count].resp);
782                 break;
783             case PAM_ERROR_MSG:
784             case PAM_TEXT_INFO:
785                 /* should not actually be able to get here... */
786                 free(reply[count].resp);
787             }
788             reply[count].resp = NULL;
789         }
790         /* forget reply too */
791         free(reply);
792         reply = NULL;
793     }
794
795     return PAM_CONV_ERR;
796 }
797 #endif                          /* HAVE_LIBPAM */
798
799
800 void
801 parseopt(int argc, char *argv[])
802   /* set options */
803 {
804
805     int c;
806     extern char *optarg;
807     extern int optind, opterr, optopt;
808     struct passwd *pass;
809 #ifdef SYSFCRONTAB
810     char is_sysfcrontab = 0;
811 #endif
812
813     /* constants and variables defined by command line */
814
815     while (1) {
816         c = getopt(argc, argv, "u:lrezdnhVc:");
817         if (c == EOF)
818             break;
819         switch (c) {
820
821         case 'V':
822             info();
823             break;
824
825         case 'h':
826             usage();
827             break;
828
829         case 'u':
830             if (useruid != rootuid) {
831                 fprintf(stderr, "must be privileged to use -u\n");
832                 xexit(EXIT_ERR);
833             }
834             user = strdup2(optarg);
835             break;
836
837         case 'd':
838             debug_opt = 1;
839             break;
840
841         case 'l':
842             if (rm_opt || edit_opt || reinstall_opt) {
843                 fprintf(stderr, "Only one of the options -l, -r, -e and -z"
844                         "may be used simultaneously.\n");
845                 xexit(EXIT_ERR);
846             }
847             list_opt = 1;
848             rm_opt = edit_opt = reinstall_opt = 0;
849             break;
850
851         case 'r':
852             if (list_opt || edit_opt || reinstall_opt) {
853                 fprintf(stderr, "Only one of the options -l, -r, -e and -z"
854                         "may be used simultaneously.\n");
855                 xexit(EXIT_ERR);
856             }
857             rm_opt = 1;
858             list_opt = edit_opt = reinstall_opt = 0;
859             break;
860
861         case 'e':
862             if (list_opt || rm_opt || reinstall_opt) {
863                 fprintf(stderr, "Only one of the options -l, -r, -e and -z"
864                         "may be used simultaneously.\n");
865                 xexit(EXIT_ERR);
866             }
867             edit_opt = 1;
868             list_opt = rm_opt = reinstall_opt = 0;
869             break;
870
871         case 'z':
872             if (list_opt || rm_opt || edit_opt) {
873                 fprintf(stderr, "Only one of the options -l, -r, -e and -z"
874                         "may be used simultaneously.\n");
875                 xexit(EXIT_ERR);
876             }
877             reinstall_opt = ignore_prev = 1;
878             list_opt = rm_opt = edit_opt = 0;
879             break;
880
881         case 'n':
882             ignore_prev = 1;
883             break;
884
885         case 'c':
886             if (optarg[0] == '/') {
887                 Set(fcronconf, optarg);
888             }
889             else {
890                 char buf[PATH_LEN];
891                 snprintf(buf, sizeof(buf), "%s/%s", orig_dir, optarg);
892                 Set(fcronconf, buf);
893             }
894             break;
895
896         case ':':
897             fprintf(stderr, "(setopt) Missing parameter.\n");
898             usage();
899
900         case '?':
901             usage();
902
903         default:
904             fprintf(stderr, "(setopt) Warning: getopt returned %c.\n", c);
905         }
906     }
907
908     /* read fcron.conf and update global parameters */
909     read_conf();
910
911     /* read the file name and/or user and check validity of the arguments */
912     if (argc - optind > 2)
913         usage();
914     else if (argc - optind == 2) {
915         if (list_opt + rm_opt + edit_opt + reinstall_opt == 0)
916             file_opt = optind++;
917         else
918             usage();
919
920         if (useruid != rootuid) {
921             fprintf(stderr, "must be privileged to use -u\n");
922             xexit(EXIT_ERR);
923         }
924         Set(user, argv[optind]);
925     }
926     else if (argc - optind == 1) {
927         if (list_opt + rm_opt + edit_opt + reinstall_opt == 0)
928             file_opt = optind;
929         else {
930             if (useruid != rootuid) {
931                 fprintf(stderr, "must be privileged to use [user|-u user]\n");
932                 xexit(EXIT_ERR);
933             }
934             Set(user, argv[optind]);
935         }
936     }
937     else if (list_opt + rm_opt + edit_opt + reinstall_opt != 1)
938         usage();
939
940     if (user == NULL) {
941         /* get user's name using getpwuid() */
942         if (!(pass = getpwuid(useruid)))
943             die_e("user \"%s\" is not in passwd file. Aborting.", USERNAME);
944         /* we need to strdup2 the value given by getpwuid() because we free
945          * file->cf_user in delete_file */
946         user = strdup2(pass->pw_name);
947         asuid = pass->pw_uid;
948         asgid = pass->pw_gid;
949     }
950     else {
951 #ifdef SYSFCRONTAB
952         if (strcmp(user, SYSFCRONTAB) == 0) {
953             is_sysfcrontab = 1;
954             asuid = rootuid;
955             asgid = rootgid;
956         }
957         else
958 #endif                          /* def SYSFCRONTAB */
959         {
960             errno = 0;
961             if ((pass = getpwnam(user))) {
962                 asuid = pass->pw_uid;
963                 asgid = pass->pw_gid;
964             }
965             else
966                 die_e("user \"%s\" is not in passwd file. Aborting.", user);
967         }
968     }
969
970     if (
971 #ifdef SYSFCRONTAB
972            !is_sysfcrontab &&
973 #endif
974            !is_allowed(user)) {
975         die("User \"%s\" is not allowed to use %s. Aborting.", user, prog_name);
976     }
977
978 #ifdef SYSFCRONTAB
979     if (is_sysfcrontab)
980         runas = ROOTNAME;
981     else
982 #endif
983         runas = user;
984
985 }
986
987
988 int
989 main(int argc, char **argv)
990 {
991
992 #ifdef HAVE_LIBPAM
993     int retcode = 0;
994     const char *const *env;
995 #endif
996     struct passwd *pass;
997
998     rootuid = get_user_uid_safe(ROOTNAME);
999     rootgid = get_group_gid_safe(ROOTGROUP);
1000
1001     memset(buf, 0, sizeof(buf));
1002     memset(file, 0, sizeof(file));
1003
1004     if (strrchr(argv[0], '/') == NULL)
1005         prog_name = argv[0];
1006     else
1007         prog_name = strrchr(argv[0], '/') + 1;
1008
1009     useruid = getuid();
1010     usergid = getgid();
1011
1012 #ifdef USE_SETE_ID
1013     /* drop any suid privilege (that we use to write files) but keep sgid
1014      * one for now: we need it for read_conf() and is_allowed() */
1015     seteuid_safe(useruid);
1016 #endif
1017
1018     errno = 0;
1019     if (!(pass = getpwnam(USERNAME)))
1020         die_e("user \"%s\" is not in passwd file. Aborting.", USERNAME);
1021     fcrontab_uid = pass->pw_uid;
1022     fcrontab_gid = pass->pw_gid;
1023
1024     /* get current dir */
1025     orig_dir[0] = '\0';
1026     if (getcwd(orig_dir, sizeof(orig_dir)) == NULL)
1027         die_e("getcwd");
1028
1029     /* interpret command line options */
1030     parseopt(argc, argv);
1031
1032 #ifdef USE_SETE_ID
1033     /* drop any privilege we may have: we will only get them back
1034      * temporarily every time we need it. */
1035     seteuid_safe(useruid);
1036     setegid_safe(usergid);
1037 #endif
1038
1039 #ifdef HAVE_LIBPAM
1040     /* Open PAM session for the user and obtain any security
1041      * credentials we might need */
1042
1043     debug("username: %s, runas: %s", user, runas);
1044     retcode = pam_start("fcrontab", runas, &apamconv, &pamh);
1045     if (retcode != PAM_SUCCESS)
1046         die_pame(pamh, retcode, "Could not start PAM");
1047     retcode = pam_authenticate(pamh, 0);        /* is user really user? */
1048     if (retcode != PAM_SUCCESS)
1049         die_pame(pamh, retcode, "Could not authenticate user using PAM (%d)",
1050                  retcode);
1051     retcode = pam_acct_mgmt(pamh, 0);   /* permitted access? */
1052     if (retcode != PAM_SUCCESS)
1053         die_pame(pamh, retcode, "Could not init PAM account management (%d)",
1054                  retcode);
1055     retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
1056     if (retcode != PAM_SUCCESS)
1057         die_pame(pamh, retcode, "Could not set PAM credentials");
1058     retcode = pam_open_session(pamh, 0);
1059     if (retcode != PAM_SUCCESS)
1060         die_pame(pamh, retcode, "Could not open PAM session");
1061
1062     env = (const char *const *)pam_getenvlist(pamh);
1063     while (env && *env) {
1064         if (putenv((char *)*env))
1065             die_e("Could not copy PAM environment");
1066         env++;
1067     }
1068
1069     /* Close the log here, because PAM calls openlog(3) and
1070      * our log messages could go to the wrong facility */
1071     xcloselog();
1072 #endif                          /* USE_PAM */
1073
1074 #ifdef USE_SETE_ID
1075     seteuid_safe(fcrontab_uid);
1076     /* change directory */
1077     if (chdir(fcrontabs) != 0) {
1078         error_e("Could not chdir to %s", fcrontabs);
1079         xexit(EXIT_ERR);
1080     }
1081     seteuid_safe(useruid);
1082 #else                           /* USE_SETE_ID */
1083
1084     if (setuid(rootuid) != 0)
1085         die_e("Could not change uid to rootuid");
1086     if (setgid(rootgid) != 0)
1087         die_e("Could not change gid to rootgid");
1088     /* change directory */
1089     if (chdir(fcrontabs) != 0) {
1090         error_e("Could not chdir to %s", fcrontabs);
1091         xexit(EXIT_ERR);
1092     }
1093 #endif                          /* USE_SETE_ID */
1094
1095     /* this program is seteuid : we set default permission mode
1096      * to 640 for a normal user, 600 for root, for security reasons */
1097     if (asuid == rootuid)
1098         umask(066);             /* octal : '0' + number in octal notation */
1099     else
1100         umask(026);
1101
1102     snprintf(buf, sizeof(buf), "%s.orig", user);
1103
1104     /* determine what action should be taken */
1105     if (file_opt) {
1106
1107         if (strcmp(argv[file_opt], "-") == 0)
1108
1109             xexit(install_stdin());
1110
1111         else {
1112             int fd = -1;
1113
1114             if (*argv[file_opt] != '/')
1115                 /* this is just the file name, not the path : complete it */
1116                 snprintf(file, sizeof(file), "%s/%s", orig_dir, argv[file_opt]);
1117             else {
1118                 strncpy(file, argv[file_opt], sizeof(file) - 1);
1119                 file[sizeof(file) - 1] = '\0';
1120             }
1121
1122             fd = open_as_user(file, useruid, usergid, O_RDONLY);
1123             if (fd < 0)
1124                 die_e("Could not open file %s", file);
1125             if (make_file(file, fd) == OK)
1126                 xexit(EXIT_OK);
1127             else
1128                 xexit(EXIT_ERR);
1129
1130         }
1131
1132     }
1133
1134     /* remove user's entries */
1135     if (rm_opt == 1) {
1136         if (remove_fcrontab(1) == ENOENT)
1137             fprintf(stderr, "no fcrontab for %s\n", user);
1138         xexit(EXIT_OK);
1139     }
1140
1141     /* list user's entries */
1142     if (list_opt == 1) {
1143         list_file(buf);
1144         xexit(EXIT_OK);
1145     }
1146
1147
1148     /* edit user's entries */
1149     if (edit_opt == 1) {
1150         edit_file(buf);
1151         xexit(EXIT_OK);
1152     }
1153
1154     /* reinstall user's entries */
1155     if (reinstall_opt == 1) {
1156         reinstall(buf);
1157         xexit(EXIT_OK);
1158     }
1159
1160     /* never reached */
1161     return EXIT_OK;
1162 }