2 * FCRON - periodic command scheduler
4 * Copyright 2000-2012 Thibault Godouet <fcron@free.fr>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * The GNU General Public License can also be found in the file
21 * `LICENSE' that comes with the fcron source distribution.
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
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
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.
45 #include "temp_file.h"
46 #include "read_string.h"
53 /* used in temp_file() */
54 char *tmp_path = "/tmp/";
56 /* command line options */
60 char reinstall_opt = 0;
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) */
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 */
78 char need_sig = 0; /* do we need to signal fcron daemon */
80 char orig_dir[PATH_LEN];
81 cf_t *file_base = NULL;
85 /* needed by log part : */
86 char *prog_name = NULL;
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 */
99 /* print some informations about this program :
100 * version, license */
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");
115 /* print a help message about command line options and exit */
118 "fcrontab [-n] file [user|-u user]\n"
119 "fcrontab { -l | -r | -e | -z } [-n] [user|-u user]\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");
138 /* launch signal if needed and exit */
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
148 /* fork and exec fcronsighup */
149 switch (pid = fork()) {
152 if (getegid() != fcrontab_gid && setegid(fcrontab_gid) != 0) {
153 error_e("could not change egid to fcrontab_gid[%d]",
157 execl(BINDIREX "/fcronsighup", BINDIREX "/fcronsighup", fcronconf,
160 error_e("Could not exec " BINDIREX " fcronsighup");
165 error_e("Could not fork (fcron has not been signaled)");
171 waitpid(pid, NULL, 0);
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));
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. */
198 char *copy_buf[LINE_LEN];
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);
206 die("copy_src() called with an invalid 'from' argument");
209 /* just in case the file was read in the past... */
210 lseek(from, 0, SEEK_SET);
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);
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
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);
228 error_e("could not open file %s", tmp_filename_str);
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);
237 if (fchown(to_fd, rootuid, fcrontab_gid) != 0) {
238 error_e("Could not fchown %s to root", tmp_filename_str);
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");
250 xclose_check(&to_fd, dest);
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);
262 xclose_check(&to_fd, dest);
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 */
276 explain("removing %s's fcrontab", user);
278 /* remove source and formated file */
279 if ((rm_orig && remove_as_user(buf, fcrontab_uid, fcrontab_gid)) != 0) {
283 error_e("could not remove %s", buf);
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);
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);
299 error_e("Can't create file %s", buf);
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);
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 ) */
323 /* copy original file to fcrontabs dir */
324 snprintf(buf, sizeof(buf), "%s.orig", user);
325 if (copy_src(fd, buf) == ERR) {
330 if (file_base->cf_line_base == NULL) {
332 explain("%s's fcrontab contains no entries : removed.", user);
336 /* write the binary fcrontab on disk */
337 snprintf(buf, sizeof(buf), "new.%s", user);
338 if (save_file(buf) != OK)
348 make_file(char *file, int fd)
350 explain("installing file %s for user %s", file, user);
352 /* read file and create a list in memory */
353 switch (read_file(file, fd)) {
357 if (write_file(fd) == ERR)
360 /* tell daemon to update the conf */
363 /* free memory used to store the list */
378 list_file(const char *file)
384 explain("listing %s's fcrontab", user);
386 fd = open_as_user(file, useruid, fcrontab_gid, O_RDONLY);
388 if (errno == ENOENT) {
389 explain("user %s has no fcrontab.", user);
393 die_e("User %s could not read file \"%s\"", user, file);
398 xclose_check(&fd, file);
399 die_e("User %s could not read file \"%s\"", user, file);
402 while ((c = getc(f)) != EOF)
405 xfclose_check(&f, file);
410 edit_file(const char *fcron_orig)
411 /* copy file to a temp file, edit that file, and install it
414 char *cureditor = NULL;
415 char editorcmd[PATH_LEN];
420 char *tmp_str = NULL;
421 FILE *f = NULL, *fi = NULL;
422 int file = -1, origfd = -1;
425 short return_val = EXIT_OK;
427 explain("fcrontab : editing %s's fcrontab", user);
429 if ((cureditor = getenv("VISUAL")) == NULL || strcmp(cureditor, "\0") == 0)
430 if ((cureditor = getenv("EDITOR")) == NULL
431 || strcmp(cureditor, "\0") == 0)
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");
441 if (fchown(file, asuid, asgid) != 0) {
442 error_e("Could not fchown %s to asuid and asgid", tmp_str);
446 /* copy user's fcrontab (if any) to a temp file */
447 origfd = open_as_user(fcron_orig, useruid, fcrontab_gid, O_RDONLY);
449 if (errno != ENOENT) {
450 error_e("could not open file %s", fcron_orig);
454 fprintf(stderr, "no fcrontab for %s - using an empty one\n", user);
457 f = fdopen(origfd, "r");
459 error_e("could not fdopen file %s", fcron_orig);
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);
469 xfclose_check(&f, fcron_orig);
472 error_e("Error while writing new fcrontab to %s");
475 /* Don't close fi, because we still need the file descriptor 'file' */
477 die_e("Could not fflush(%s)", fi);
482 if (fstat(file, &st) == 0)
485 error_e("could not stat \"%s\"", tmp_str);
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);
497 /* close the file before the user edits it */
498 xclose_check(&file, tmp_str);
500 switch (pid = fork()) {
503 if (useruid != rootuid) {
504 if (setgid(asgid) < 0) {
505 error_e("setgid(asgid)");
508 if (setuid(asuid) < 0) {
509 error_e("setuid(asuid)");
514 /* Some programs, like perl, require gid=egid : */
515 if (setgid(getgid()) < 0) {
516 error_e("setgid(getgid())");
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);
536 /* only reached by parent */
537 waitpid(pid, &status, 0);
538 if (!WIFEXITED(status)) {
540 "Editor exited abnormally:" " fcrontab is unchanged.\n");
544 /* re-open the file that has just been edited */
545 file = open_as_user(tmp_str, useruid, usergid, O_RDONLY);
547 error_e("Could not open file %s", tmp_str);
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);
560 /* check if file has been modified */
561 if (fstat(file, &st) != 0) {
562 error_e("could not stat %s", tmp_str);
566 else if (st.st_mtime > mtime || correction == 1) {
570 switch (read_file(tmp_str, file)) {
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')) ;
584 /* free memory used to store the list */
591 "Please press c to correct, "
603 "Fcrontab is unchanged :" " no need to install it.\n");
607 } while (correction == 1);
609 if (write_file(file) != OK)
610 return_val = EXIT_ERR;
612 /* tell daemon to update the conf */
616 /* free memory used to store the list */
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);
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);
639 /* install what we get through stdin */
642 FILE *tmp_file = NULL;
643 char *tmp_str = NULL;
645 short return_val = EXIT_OK;
647 tmp_fd = temp_file(&tmp_str);
649 if ((tmp_file = fdopen(tmp_fd, "w")) == NULL)
650 die_e("Could not fdopen file %s", tmp_str);
652 while ((c = getc(stdin)) != EOF)
655 debug("Copied stdin to %s, about to parse file %s...", tmp_str, tmp_str);
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);
661 if (make_file(tmp_str, tmp_fd) == ERR)
667 return_val = EXIT_ERR;
669 if (remove(tmp_str) != 0)
670 error_e("Could not remove %s", tmp_str);
677 reinstall(char *fcron_orig)
681 explain("reinstalling %s's fcrontab", user);
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",
689 fprintf(stderr, "Could not open \"%s\": %s\n", fcron_orig,
699 xexit(install_stdin());
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. */
711 struct pam_response *reply;
716 reply = (struct pam_response *)calloc(num_msg, sizeof(struct pam_response));
718 debug("no memory for responses");
722 for (count = 0; count < num_msg; ++count) {
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;
732 case PAM_PROMPT_ECHO_ON:
733 string = read_string(CONV_ECHO_ON, msgm[count]->msg);
734 if (string == NULL) {
735 goto failed_conversation;
739 if (fprintf(stderr, "%s\n", msgm[count]->msg) < 0) {
740 goto failed_conversation;
744 if (fprintf(stdout, "%s\n", msgm[count]->msg) < 0) {
745 goto failed_conversation;
749 fprintf(stderr, "erroneous conversation (%d)\n",
750 msgm[count]->msg_style);
751 goto failed_conversation;
754 if (string) { /* must add to reply array */
755 /* add string to list of responses */
757 reply[count].resp_retcode = 0;
758 reply[count].resp = string;
763 /* New (0.59+) behavior is to always have a reply - this is
764 * compatable with the X/Open (March 1997) spec. */
773 for (count = 0; count < num_msg; ++count) {
774 if (reply[count].resp == NULL) {
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);
785 /* should not actually be able to get here... */
786 free(reply[count].resp);
788 reply[count].resp = NULL;
790 /* forget reply too */
797 #endif /* HAVE_LIBPAM */
801 parseopt(int argc, char *argv[])
807 extern int optind, opterr, optopt;
810 char is_sysfcrontab = 0;
813 /* constants and variables defined by command line */
816 c = getopt(argc, argv, "u:lrezdnhVc:");
830 if (useruid != rootuid) {
831 fprintf(stderr, "must be privileged to use -u\n");
834 user = strdup2(optarg);
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");
848 rm_opt = edit_opt = reinstall_opt = 0;
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");
858 list_opt = edit_opt = reinstall_opt = 0;
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");
868 list_opt = rm_opt = reinstall_opt = 0;
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");
877 reinstall_opt = ignore_prev = 1;
878 list_opt = rm_opt = edit_opt = 0;
886 if (optarg[0] == '/') {
887 Set(fcronconf, optarg);
891 snprintf(buf, sizeof(buf), "%s/%s", orig_dir, optarg);
897 fprintf(stderr, "(setopt) Missing parameter.\n");
904 fprintf(stderr, "(setopt) Warning: getopt returned %c.\n", c);
908 /* read fcron.conf and update global parameters */
911 /* read the file name and/or user and check validity of the arguments */
912 if (argc - optind > 2)
914 else if (argc - optind == 2) {
915 if (list_opt + rm_opt + edit_opt + reinstall_opt == 0)
920 if (useruid != rootuid) {
921 fprintf(stderr, "must be privileged to use -u\n");
924 Set(user, argv[optind]);
926 else if (argc - optind == 1) {
927 if (list_opt + rm_opt + edit_opt + reinstall_opt == 0)
930 if (useruid != rootuid) {
931 fprintf(stderr, "must be privileged to use [user|-u user]\n");
934 Set(user, argv[optind]);
937 else if (list_opt + rm_opt + edit_opt + reinstall_opt != 1)
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;
952 if (strcmp(user, SYSFCRONTAB) == 0) {
958 #endif /* def SYSFCRONTAB */
961 if ((pass = getpwnam(user))) {
962 asuid = pass->pw_uid;
963 asgid = pass->pw_gid;
966 die_e("user \"%s\" is not in passwd file. Aborting.", user);
975 die("User \"%s\" is not allowed to use %s. Aborting.", user, prog_name);
989 main(int argc, char **argv)
994 const char *const *env;
998 rootuid = get_user_uid_safe(ROOTNAME);
999 rootgid = get_group_gid_safe(ROOTGROUP);
1001 memset(buf, 0, sizeof(buf));
1002 memset(file, 0, sizeof(file));
1004 if (strrchr(argv[0], '/') == NULL)
1005 prog_name = argv[0];
1007 prog_name = strrchr(argv[0], '/') + 1;
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);
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;
1024 /* get current dir */
1026 if (getcwd(orig_dir, sizeof(orig_dir)) == NULL)
1029 /* interpret command line options */
1030 parseopt(argc, argv);
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);
1040 /* Open PAM session for the user and obtain any security
1041 * credentials we might need */
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)",
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)",
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");
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");
1069 /* Close the log here, because PAM calls openlog(3) and
1070 * our log messages could go to the wrong facility */
1072 #endif /* USE_PAM */
1075 seteuid_safe(fcrontab_uid);
1076 /* change directory */
1077 if (chdir(fcrontabs) != 0) {
1078 error_e("Could not chdir to %s", fcrontabs);
1081 seteuid_safe(useruid);
1082 #else /* USE_SETE_ID */
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);
1093 #endif /* USE_SETE_ID */
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 */
1102 snprintf(buf, sizeof(buf), "%s.orig", user);
1104 /* determine what action should be taken */
1107 if (strcmp(argv[file_opt], "-") == 0)
1109 xexit(install_stdin());
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]);
1118 strncpy(file, argv[file_opt], sizeof(file) - 1);
1119 file[sizeof(file) - 1] = '\0';
1122 fd = open_as_user(file, useruid, usergid, O_RDONLY);
1124 die_e("Could not open file %s", file);
1125 if (make_file(file, fd) == OK)
1134 /* remove user's entries */
1136 if (remove_fcrontab(1) == ENOENT)
1137 fprintf(stderr, "no fcrontab for %s\n", user);
1141 /* list user's entries */
1142 if (list_opt == 1) {
1148 /* edit user's entries */
1149 if (edit_opt == 1) {
1154 /* reinstall user's entries */
1155 if (reinstall_opt == 1) {