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, 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");
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);
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 */
277 explain("removing %s's fcrontab", user);
279 /* remove source and formated file */
280 if ((rm_orig && remove_as_user(buf, fcrontab_uid, fcrontab_gid)) != 0) {
284 error_e("could not remove %s", buf);
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);
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);
300 error_e("Can't create file %s", buf);
302 else if (asuid == rootuid && fchown(fd, rootuid, fcrontab_gid) != 0)
303 error_e("Could not fchown %s to root", buf);
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 ) */
324 /* copy original file to fcrontabs dir */
325 snprintf(buf, sizeof(buf), "%s.orig", user);
326 if (copy_src(fd, buf) == ERR) {
331 if (file_base->cf_line_base == NULL) {
333 explain("%s's fcrontab contains no entries : removed.", user);
337 /* write the binary fcrontab on disk */
338 snprintf(buf, sizeof(buf), "new.%s", user);
339 if (save_file(buf) != OK)
349 make_file(char *file, int fd)
351 explain("installing file %s for user %s", file, user);
353 /* read file and create a list in memory */
354 switch (read_file(file, fd)) {
358 if (write_file(fd) == ERR)
361 /* tell daemon to update the conf */
364 /* free memory used to store the list */
379 list_file(char *file)
385 explain("listing %s's fcrontab", user);
387 fd = open_as_user(file, useruid, fcrontab_gid, O_RDONLY);
389 if (errno == ENOENT) {
390 explain("user %s has no fcrontab.", user);
394 die_e("User %s could not read file \"%s\"", user, file);
400 die_e("User %s could not read file \"%s\"", user, file);
403 while ((c = getc(f)) != EOF)
411 edit_file(char *fcron_orig)
412 /* copy file to a temp file, edit that file, and install it
415 char *cureditor = NULL;
416 char editorcmd[PATH_LEN];
422 FILE *f = NULL, *fi = NULL;
423 int file = -1, origfd = -1;
426 short return_val = EXIT_OK;
428 explain("fcrontab : editing %s's fcrontab", user);
430 if ((cureditor = getenv("VISUAL")) == NULL || strcmp(cureditor, "\0") == 0)
431 if ((cureditor = getenv("EDITOR")) == NULL
432 || strcmp(cureditor, "\0") == 0)
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);
473 error_e("Error while writing new fcrontab to %s");
476 /* Don't close fi, because we still need the file descriptor 'file' */
478 die_e("Could not fflush(%s)", fi);
483 if (fstat(file, &st) == 0)
486 error_e("could not stat \"%s\"", tmp_str);
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);
498 /* close the file before the user edits it */
501 switch (pid = fork()) {
504 if (useruid != rootuid) {
505 if (setgid(asgid) < 0) {
506 error_e("setgid(asgid)");
509 if (setuid(asuid) < 0) {
510 error_e("setuid(asuid)");
515 /* Some programs, like perl, require gid=egid : */
516 if (setgid(getgid()) < 0) {
517 error_e("setgid(getgid())");
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);
537 /* only reached by parent */
538 waitpid(pid, &status, 0);
539 if (!WIFEXITED(status)) {
541 "Editor exited abnormally:" " fcrontab is unchanged.\n");
545 /* re-open the file that has just been edited */
546 file = open_as_user(tmp_str, useruid, usergid, O_RDONLY);
548 error_e("Could not open file %s", tmp_str);
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);
561 /* check if file has been modified */
562 if (fstat(file, &st) != 0) {
563 error_e("could not stat %s", tmp_str);
567 else if (st.st_mtime > mtime || correction == 1) {
571 switch (read_file(tmp_str, file)) {
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')) ;
585 /* free memory used to store the list */
592 "Please press c to correct, "
604 "Fcrontab is unchanged :" " no need to install it.\n");
608 } while (correction == 1);
610 if (write_file(file) != OK)
611 return_val = EXIT_ERR;
613 /* tell daemon to update the conf */
617 /* free memory used to store the list */
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);
629 if (remove_as_user(tmp_str, useruid, fcrontab_gid) != 0)
630 error_e("could not remove %s", tmp_str);
645 /* install what we get through stdin */
648 FILE *tmp_file = NULL;
649 char *tmp_str = NULL;
651 short return_val = EXIT_OK;
653 tmp_fd = temp_file(&tmp_str);
655 if ((tmp_file = fdopen(tmp_fd, "w")) == NULL)
656 die_e("Could not fdopen file %s", tmp_str);
658 while ((c = getc(stdin)) != EOF)
661 debug("Copied stdin to %s, about to parse file %s...", tmp_str, tmp_str);
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);
667 if (make_file(tmp_str, tmp_fd) == ERR)
673 return_val = EXIT_ERR;
675 if (remove(tmp_str) != 0)
676 error_e("Could not remove %s", tmp_str);
683 reinstall(char *fcron_orig)
687 explain("reinstalling %s's fcrontab", user);
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",
695 fprintf(stderr, "Could not open \"%s\": %s\n", fcron_orig,
705 xexit(install_stdin());
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. */
717 struct pam_response *reply;
722 reply = (struct pam_response *)calloc(num_msg, sizeof(struct pam_response));
724 debug("no memory for responses");
728 for (count = 0; count < num_msg; ++count) {
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;
738 case PAM_PROMPT_ECHO_ON:
739 string = read_string(CONV_ECHO_ON, msgm[count]->msg);
740 if (string == NULL) {
741 goto failed_conversation;
745 if (fprintf(stderr, "%s\n", msgm[count]->msg) < 0) {
746 goto failed_conversation;
750 if (fprintf(stdout, "%s\n", msgm[count]->msg) < 0) {
751 goto failed_conversation;
755 fprintf(stderr, "erroneous conversation (%d)\n",
756 msgm[count]->msg_style);
757 goto failed_conversation;
760 if (string) { /* must add to reply array */
761 /* add string to list of responses */
763 reply[count].resp_retcode = 0;
764 reply[count].resp = string;
769 /* New (0.59+) behavior is to always have a reply - this is
770 * compatable with the X/Open (March 1997) spec. */
779 for (count = 0; count < num_msg; ++count) {
780 if (reply[count].resp == NULL) {
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);
791 /* should not actually be able to get here... */
792 free(reply[count].resp);
794 reply[count].resp = NULL;
796 /* forget reply too */
803 #endif /* HAVE_LIBPAM */
807 parseopt(int argc, char *argv[])
813 extern int optind, opterr, optopt;
816 char is_sysfcrontab = 0;
819 /* constants and variables defined by command line */
822 c = getopt(argc, argv, "u:lrezdnhVc:");
836 if (useruid != rootuid) {
837 fprintf(stderr, "must be privileged to use -u\n");
840 user = strdup2(optarg);
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");
854 rm_opt = edit_opt = reinstall_opt = 0;
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");
864 list_opt = edit_opt = reinstall_opt = 0;
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");
874 list_opt = rm_opt = reinstall_opt = 0;
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");
883 reinstall_opt = ignore_prev = 1;
884 list_opt = rm_opt = edit_opt = 0;
892 if (optarg[0] == '/') {
893 Set(fcronconf, optarg);
897 snprintf(buf, sizeof(buf), "%s/%s", orig_dir, optarg);
903 fprintf(stderr, "(setopt) Missing parameter.\n");
910 fprintf(stderr, "(setopt) Warning: getopt returned %c.\n", c);
914 /* read fcron.conf and update global parameters */
917 /* read the file name and/or user and check validity of the arguments */
918 if (argc - optind > 2)
920 else if (argc - optind == 2) {
921 if (list_opt + rm_opt + edit_opt + reinstall_opt == 0)
926 if (useruid != rootuid) {
927 fprintf(stderr, "must be privileged to use -u\n");
930 Set(user, argv[optind]);
932 else if (argc - optind == 1) {
933 if (list_opt + rm_opt + edit_opt + reinstall_opt == 0)
936 if (useruid != rootuid) {
937 fprintf(stderr, "must be privileged to use [user|-u user]\n");
940 Set(user, argv[optind]);
943 else if (list_opt + rm_opt + edit_opt + reinstall_opt != 1)
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;
958 if (strcmp(user, SYSFCRONTAB) == 0) {
964 #endif /* def SYSFCRONTAB */
967 if ((pass = getpwnam(user))) {
968 asuid = pass->pw_uid;
969 asgid = pass->pw_gid;
972 die_e("user \"%s\" is not in passwd file. Aborting.", user);
981 die("User \"%s\" is not allowed to use %s. Aborting.", user, prog_name);
995 main(int argc, char **argv)
1000 const char *const *env;
1002 struct passwd *pass;
1004 rootuid = get_user_uid_safe(ROOTNAME);
1005 rootgid = get_group_gid_safe(ROOTGROUP);
1007 memset(buf, 0, sizeof(buf));
1008 memset(file, 0, sizeof(file));
1010 if (strrchr(argv[0], '/') == NULL)
1011 prog_name = argv[0];
1013 prog_name = strrchr(argv[0], '/') + 1;
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);
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;
1030 /* get current dir */
1032 if (getcwd(orig_dir, sizeof(orig_dir)) == NULL)
1035 /* interpret command line options */
1036 parseopt(argc, argv);
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);
1046 /* Open PAM session for the user and obtain any security
1047 * credentials we might need */
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)",
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)",
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");
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");
1075 /* Close the log here, because PAM calls openlog(3) and
1076 * our log messages could go to the wrong facility */
1078 #endif /* USE_PAM */
1081 seteuid_safe(fcrontab_uid);
1082 /* change directory */
1083 if (chdir(fcrontabs) != 0) {
1084 error_e("Could not chdir to %s", fcrontabs);
1087 seteuid_safe(useruid);
1088 #else /* USE_SETE_ID */
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);
1099 #endif /* USE_SETE_ID */
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 */
1108 snprintf(buf, sizeof(buf), "%s.orig", user);
1110 /* determine what action should be taken */
1113 if (strcmp(argv[file_opt], "-") == 0)
1115 xexit(install_stdin());
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]);
1124 strncpy(file, argv[file_opt], sizeof(file) - 1);
1125 file[sizeof(file) - 1] = '\0';
1128 fd = open_as_user(file, useruid, usergid, O_RDONLY);
1130 die_e("Could not open file %s", file);
1131 if (make_file(file, fd) == OK)
1140 /* remove user's entries */
1142 if (remove_fcrontab(1) == ENOENT)
1143 fprintf(stderr, "no fcrontab for %s\n", user);
1147 /* list user's entries */
1148 if (list_opt == 1) {
1154 /* edit user's entries */
1155 if (edit_opt == 1) {
1160 /* reinstall user's entries */
1161 if (reinstall_opt == 1) {