/* * FCRON - periodic command scheduler * * Copyright 2000-2012 Thibault Godouet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * The GNU General Public License can also be found in the file * `LICENSE' that comes with the fcron source distribution. */ /* * The goal of this program is simple : giving a user interface to fcron * daemon, by allowing each user to see, modify, append or remove his * fcrontabs. * Fcron daemon use a specific formated format of file, so fcrontab generate * that kind of file from human readable files. In order allowing users to * see and modify their fcrontabs, the source file is always saved with the * formated one. * Fcrontab makes a temporary formated file, and then sends a signal * to the daemon to force it to update its configuration, remove the temp * file and save a new and final formated file. * That way, not the simple, allows the daemon to keep a maximum of * informations like the time remaining before next execution, or the date * and hour of next execution. */ #include "fcrontab.h" #include "allow.h" #include "fileconf.h" #include "temp_file.h" #include "read_string.h" void info(void); void usage(void); /* used in temp_file() */ char *tmp_path = "/tmp/"; /* command line options */ char rm_opt = 0; char list_opt = 0; char edit_opt = 0; char reinstall_opt = 0; char ignore_prev = 0; int file_opt = 0; /* uid/gid of users/groups * (we don't use the static UID or GID as we ask for user and group names * in the configure script) */ char *user = NULL; char *runas = NULL; uid_t useruid = 0; /* uid of the user */ gid_t usergid = 0; /* gid of the user */ uid_t asuid = 0; /* uid of the user whose fcrontab we are working on */ gid_t asgid = 0; /* gid of the user whose fcrontab we are working on */ uid_t fcrontab_uid = 0; /* uid of the fcron user */ gid_t fcrontab_gid = 0; /* gid of the fcron user */ uid_t rootuid = 0; /* uid of root */ gid_t rootgid = 0; /* gid of root */ char need_sig = 0; /* do we need to signal fcron daemon */ char orig_dir[PATH_LEN]; cf_t *file_base = NULL; char buf[PATH_LEN]; char file[PATH_LEN]; /* needed by log part : */ char *prog_name = NULL; char foreground = 1; pid_t daemon_pid = 0; #ifdef HAVE_LIBPAM int conv_pam(int num_msg, const struct pam_message **msgm, struct pam_response **response, void *appdata_ptr); pam_handle_t *pamh = NULL; const struct pam_conv apamconv = { conv_pam, NULL }; #endif /* HAVE_LIBPAM */ void info(void) /* print some informations about this program : * version, license */ { fprintf(stderr, "fcrontab " VERSION_QUOTED " - user interface to daemon fcron\n" "Copyright " COPYRIGHT_QUOTED " Thibault Godouet \n" "This program is free software distributed WITHOUT ANY WARRANTY.\n" "See the GNU General Public License for more details.\n"); exit(EXIT_OK); } void usage(void) /* print a help message about command line options and exit */ { fprintf(stderr, "fcrontab [-n] file [user|-u user]\n" "fcrontab { -l | -r | -e | -z } [-n] [user|-u user]\n" "fcrontab -h\n" " -u user specify user name.\n" " -l list user's current fcrontab.\n" " -r remove user's current fcrontab.\n" " -e edit user's current fcrontab.\n" " -z reinstall user's fcrontab from source code.\n" " -n ignore previous version of file.\n" " -c f make fcrontab use config file f.\n" " -d set up debug mode.\n" " -h display this help message.\n" " -V display version & infos about fcrontab.\n" "\n"); exit(EXIT_ERR); } void xexit(int exit_val) /* launch signal if needed and exit */ { pid_t pid = 0; /* WARNING: make sure we never call die_e() or something that could call * die_e() here, as die_e() would then call xexit() and we could * go into a loop! */ if (need_sig == 1) { /* fork and exec fcronsighup */ switch (pid = fork()) { case 0: /* child */ if (getegid() != fcrontab_gid && setegid(fcrontab_gid) != 0) { error_e("could not change egid to fcrontab_gid[%d]", fcrontab_gid); exit(ERR); } execl(BINDIREX "/fcronsighup", BINDIREX "/fcronsighup", fcronconf, NULL); error_e("Could not exec " BINDIREX " fcronsighup"); exit(ERR); break; case -1: error_e("Could not fork (fcron has not been signaled)"); exit(ERR); break; default: /* parent */ waitpid(pid, NULL, 0); break; } } #ifdef HAVE_LIBPAM /* we need those rights for pam to close properly */ if (geteuid() != fcrontab_uid && seteuid(fcrontab_uid) != 0) die_e("could not change euid to %d", fcrontab_uid); if (getegid() != fcrontab_gid && setegid(fcrontab_gid) != 0) die_e("could not change egid to %d", fcrontab_gid); pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT); pam_end(pamh, pam_close_session(pamh, PAM_SILENT)); #endif exit(exit_val); } int copy_src(int from, const char *dest) /* copy src file orig (already opened) to dest */ /* we first copy the file to a temp file name, and then we rename it, * so as to avoid data loss if the filesystem is full. */ { int to_fd = -1; int nb; char *copy_buf[LINE_LEN]; char tmp_filename_str[PATH_LEN + 4]; int dest_path_len, tmp_filename_index; char *tmp_suffix_str = ".tmp"; int max_dest_len = sizeof(tmp_filename_str) - sizeof(tmp_suffix_str); if (from < 0) { die("copy_src() called with an invalid 'from' argument"); } /* just in case the file was read in the past... */ lseek(from, 0, SEEK_SET); /* the temp file must be in the same directory as the dest file */ dest_path_len = strlen(dest); strncpy(tmp_filename_str, dest, max_dest_len); tmp_filename_index = (dest_path_len > max_dest_len) ? max_dest_len : dest_path_len; strcpy(&tmp_filename_str[tmp_filename_index], tmp_suffix_str); /* create it as fcrontab_uid (to avoid problem if user's uid changed) * except for root. Root requires filesystem uid root for security * reasons */ to_fd = open_as_user(tmp_filename_str, (asuid == rootuid) ? rootuid : fcrontab_uid, fcrontab_gid, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR | S_IRGRP); if (to_fd < 0) { error_e("could not open file %s", tmp_filename_str); goto exiterr; } if (asuid == rootuid) { if (fchmod(to_fd, S_IWUSR | S_IRUSR) != 0) { error_e("Could not fchmod %s to 600", tmp_filename_str); goto exiterr; } if (fchown(to_fd, rootuid, fcrontab_gid) != 0) { error_e("Could not fchown %s to root", tmp_filename_str); goto exiterr; } } while ((nb = read(from, copy_buf, sizeof(copy_buf))) != -1 && nb != 0) if (write(to_fd, copy_buf, nb) != nb) { error_e("Error while copying file (no space left ?)." " Aborting : old source file kept"); goto exiterr; } xclose_check(&to_fd, dest); to_fd = -1; if (rename_as_user(tmp_filename_str, dest, useruid, fcrontab_gid) < 0) { error_e("Unable to rename %s to %s : old source file kept", tmp_filename_str, dest); goto exiterr; } return OK; exiterr: if (to_fd != -1) xclose_check(&to_fd, dest); return ERR; } int remove_fcrontab(char rm_orig) /* remove user's fcrontab and tell daemon to update his conf */ /* note : the binary fcrontab is removed by fcron */ { int return_val = OK; int fd; if (rm_orig) explain("removing %s's fcrontab", user); /* remove source and formated file */ if ((rm_orig && remove_as_user(buf, fcrontab_uid, fcrontab_gid)) != 0) { if (errno == ENOENT) return_val = ENOENT; else error_e("could not remove %s", buf); } /* try to remove the temp file in case he has not * been read by fcron daemon */ snprintf(buf, sizeof(buf), "new.%s", user); remove_as_user(buf, useruid, fcrontab_gid); /* finally create a file in order to tell the daemon * a file was removed, and launch a signal to daemon */ snprintf(buf, sizeof(buf), "rm.%s", user); fd = open_as_user(buf, fcrontab_uid, fcrontab_gid, O_CREAT | O_TRUNC | O_EXCL, S_IRUSR | S_IWUSR); if (fd == -1) { if (errno != EEXIST) error_e("Can't create file %s", buf); } else if (asuid == rootuid && fchown(fd, rootuid, fcrontab_gid) != 0) error_e("Could not fchown %s to root", buf); xclose_check(&fd, buf); need_sig = 1; return return_val; } int write_file(int fd) { int return_val = OK; if (ignore_prev == 1) /* if user wants to ignore previous version, we remove it * * ( fcron daemon remove files no longer wanted before * adding new ones ) */ remove_fcrontab(0); /* copy original file to fcrontabs dir */ snprintf(buf, sizeof(buf), "%s.orig", user); if (copy_src(fd, buf) == ERR) { return_val = ERR; } else { if (file_base->cf_line_base == NULL) { /* no entries */ explain("%s's fcrontab contains no entries : removed.", user); remove_fcrontab(0); } else { /* write the binary fcrontab on disk */ snprintf(buf, sizeof(buf), "new.%s", user); if (save_file(buf) != OK) return_val = ERR; } } return return_val; } int make_file(char *file, int fd) { explain("installing file %s for user %s", file, user); /* read file and create a list in memory */ switch (read_file(file, fd)) { case 2: case OK: if (write_file(fd) == ERR) return ERR; else /* tell daemon to update the conf */ need_sig = 1; /* free memory used to store the list */ delete_file(user); break; case ERR: return ERR; } return OK; } void list_file(const char *file) { FILE *f = NULL; int c; int fd = -1; explain("listing %s's fcrontab", user); fd = open_as_user(file, useruid, fcrontab_gid, O_RDONLY); if (fd < 0) { if (errno == ENOENT) { explain("user %s has no fcrontab.", user); return; } else die_e("User %s could not read file \"%s\"", user, file); } f = fdopen(fd, "r"); if (f == NULL) { xclose_check(&fd, file); die_e("User %s could not read file \"%s\"", user, file); } while ((c = getc(f)) != EOF) putchar(c); xfclose_check(&f, file); } void edit_file(const char *fcron_orig) /* copy file to a temp file, edit that file, and install it * if necessary */ { char *cureditor = NULL; char editorcmd[PATH_LEN]; pid_t pid; int status; struct stat st; time_t mtime = 0; char *tmp_str = NULL; FILE *f = NULL, *fi = NULL; int file = -1, origfd = -1; int c; char correction = 0; short return_val = EXIT_OK; explain("fcrontab : editing %s's fcrontab", user); if ((cureditor = getenv("VISUAL")) == NULL || strcmp(cureditor, "\0") == 0) if ((cureditor = getenv("EDITOR")) == NULL || strcmp(cureditor, "\0") == 0) cureditor = editor; /* temp_file() dies on error, so tmp_str is always set */ file = temp_file(&tmp_str); if ((fi = fdopen(file, "w")) == NULL) { error_e("could not fdopen"); goto exiterr; } #ifndef USE_SETE_ID if (fchown(file, asuid, asgid) != 0) { error_e("Could not fchown %s to asuid and asgid", tmp_str); goto exiterr; } #endif /* copy user's fcrontab (if any) to a temp file */ origfd = open_as_user(fcron_orig, useruid, fcrontab_gid, O_RDONLY); if (origfd < 0) { if (errno != ENOENT) { error_e("could not open file %s", fcron_orig); goto exiterr; } else fprintf(stderr, "no fcrontab for %s - using an empty one\n", user); } else { f = fdopen(origfd, "r"); if (f == NULL) { error_e("could not fdopen file %s", fcron_orig); goto exiterr; } /* copy original file to temp file */ while ((c = getc(f)) != EOF) { if (putc(c, fi) == EOF) { error_e("could not write to file %s", tmp_str); goto exiterr; } } xfclose_check(&f, fcron_orig); if (ferror(fi)) error_e("Error while writing new fcrontab to %s"); } /* Don't close fi, because we still need the file descriptor 'file' */ if (fflush(fi) != 0) die_e("Could not fflush(%s)", fi); fi = NULL; do { if (fstat(file, &st) == 0) mtime = st.st_mtime; else { error_e("could not stat \"%s\"", tmp_str); goto exiterr; } #ifndef USE_SETE_ID /* chown the file (back if correction) to asuid/asgid so as user can edit it */ if (fchown(file, asuid, asgid) != 0 || fchmod(file, S_IRUSR | S_IWUSR) != 0) { fprintf(stderr, "Can't chown or chmod %s.\n", tmp_str); goto exiterr; } #endif /* close the file before the user edits it */ xclose_check(&file, tmp_str); switch (pid = fork()) { case 0: /* child */ if (useruid != rootuid) { if (setgid(asgid) < 0) { error_e("setgid(asgid)"); goto exiterr; } if (setuid(asuid) < 0) { error_e("setuid(asuid)"); goto exiterr; } } else { /* Some programs, like perl, require gid=egid : */ if (setgid(getgid()) < 0) { error_e("setgid(getgid())"); goto exiterr; } } snprintf(editorcmd, sizeof(editorcmd), "%s %s", cureditor, tmp_str); if (chdir(tmp_path) != 0) error_e("Could not chdir to %s", tmp_path); execlp(shell, shell, "-c", editorcmd, tmp_str, NULL); error_e("Error while running \"%s\"", cureditor); goto exiterr; case -1: error_e("fork"); goto exiterr; default: /* parent */ break; } /* only reached by parent */ waitpid(pid, &status, 0); if (!WIFEXITED(status)) { fprintf(stderr, "Editor exited abnormally:" " fcrontab is unchanged.\n"); goto exiterr; } /* re-open the file that has just been edited */ file = open_as_user(tmp_str, useruid, usergid, O_RDONLY); if (file < 0) { error_e("Could not open file %s", tmp_str); goto exiterr; } #ifndef USE_SETE_ID /* chown the file back to rootuid/rootgid */ if (fchown(file, rootuid, rootgid) != 0 || fchmod(file, S_IRUSR | S_IWUSR) != 0) { fprintf(stderr, "Can't chown or chmod %s.\n", tmp_str); goto exiterr; } #endif /* check if file has been modified */ if (fstat(file, &st) != 0) { error_e("could not stat %s", tmp_str); goto exiterr; } else if (st.st_mtime > mtime || correction == 1) { correction = 0; switch (read_file(tmp_str, file)) { case ERR: goto exiterr; case 2: fprintf(stderr, "\nFile contains some errors. " "Ignore [i] or Correct [c] ? "); while ((c = getchar())) { /* consume the rest of the line, e.g. the newline char (\n) */ while (c != '\n' && (getchar() != '\n')) ; if (c == 'i') { break; } else if (c == 'c') { /* free memory used to store the list */ delete_file(user); correction = 1; break; } else { fprintf(stderr, "Please press c to correct, " "or i to ignore: "); } } break; default: break; } } else { fprintf(stderr, "Fcrontab is unchanged :" " no need to install it.\n"); goto end; } } while (correction == 1); if (write_file(file) != OK) return_val = EXIT_ERR; else /* tell daemon to update the conf */ need_sig = 1; /* free memory used to store the list */ delete_file(user); end: xclose_check(&file, tmp_str); if (remove_as_user(tmp_str, useruid, fcrontab_gid) != 0) error_e("could not remove %s", tmp_str); Free_safe(tmp_str); xexit(return_val); exiterr: xfclose_check(&fi, tmp_str); xclose_check(&file, tmp_str); if (remove_as_user(tmp_str, useruid, fcrontab_gid) != 0) error_e("could not remove %s", tmp_str); xfclose_check(&f, fcron_orig); Free_safe(tmp_str); xexit(EXIT_ERR); } int install_stdin(void) /* install what we get through stdin */ { int tmp_fd = 0; FILE *tmp_file = NULL; char *tmp_str = NULL; int c; short return_val = EXIT_OK; tmp_fd = temp_file(&tmp_str); if ((tmp_file = fdopen(tmp_fd, "w")) == NULL) die_e("Could not fdopen file %s", tmp_str); while ((c = getc(stdin)) != EOF) putc(c, tmp_file); /* // */ debug("Copied stdin to %s, about to parse file %s...", tmp_str, tmp_str); /* don't closes tmp_fd as it will be used for make_file(): */ if (fflush(tmp_file) != 0) die_e("Could not fflush(%s)", tmp_file); if (make_file(tmp_str, tmp_fd) == ERR) goto exiterr; else goto exit; exiterr: return_val = EXIT_ERR; exit: if (remove(tmp_str) != 0) error_e("Could not remove %s", tmp_str); free(tmp_str); return return_val; } void reinstall(char *fcron_orig) { int i = 0; explain("reinstalling %s's fcrontab", user); if ((i = open_as_user(fcron_orig, useruid, fcrontab_gid, O_RDONLY)) < 0) { if (errno == ENOENT) { fprintf(stderr, "Could not reinstall: user %s has no fcrontab\n", user); } else fprintf(stderr, "Could not open \"%s\": %s\n", fcron_orig, strerror(errno)); xexit(EXIT_ERR); } close(0); dup2(i, 0); xclose(&i); xexit(install_stdin()); } #ifdef HAVE_LIBPAM int conv_pam(int num_msg, const struct pam_message **msgm, struct pam_response **response, void *appdata_ptr) /* text based conversation for pam. */ { int count = 0; struct pam_response *reply; if (num_msg <= 0) return PAM_CONV_ERR; reply = (struct pam_response *)calloc(num_msg, sizeof(struct pam_response)); if (reply == NULL) { debug("no memory for responses"); return PAM_CONV_ERR; } for (count = 0; count < num_msg; ++count) { char *string = NULL; switch (msgm[count]->msg_style) { case PAM_PROMPT_ECHO_OFF: string = read_string(CONV_ECHO_OFF, msgm[count]->msg); if (string == NULL) { goto failed_conversation; } break; case PAM_PROMPT_ECHO_ON: string = read_string(CONV_ECHO_ON, msgm[count]->msg); if (string == NULL) { goto failed_conversation; } break; case PAM_ERROR_MSG: if (fprintf(stderr, "%s\n", msgm[count]->msg) < 0) { goto failed_conversation; } break; case PAM_TEXT_INFO: if (fprintf(stdout, "%s\n", msgm[count]->msg) < 0) { goto failed_conversation; } break; default: fprintf(stderr, "erroneous conversation (%d)\n", msgm[count]->msg_style); goto failed_conversation; } if (string) { /* must add to reply array */ /* add string to list of responses */ reply[count].resp_retcode = 0; reply[count].resp = string; string = NULL; } } /* New (0.59+) behavior is to always have a reply - this is * compatable with the X/Open (March 1997) spec. */ *response = reply; reply = NULL; return PAM_SUCCESS; failed_conversation: if (reply) { for (count = 0; count < num_msg; ++count) { if (reply[count].resp == NULL) { continue; } switch (msgm[count]->msg_style) { case PAM_PROMPT_ECHO_ON: case PAM_PROMPT_ECHO_OFF: Overwrite(reply[count].resp); free(reply[count].resp); break; case PAM_ERROR_MSG: case PAM_TEXT_INFO: /* should not actually be able to get here... */ free(reply[count].resp); } reply[count].resp = NULL; } /* forget reply too */ free(reply); reply = NULL; } return PAM_CONV_ERR; } #endif /* HAVE_LIBPAM */ void parseopt(int argc, char *argv[]) /* set options */ { int c; extern char *optarg; extern int optind, opterr, optopt; struct passwd *pass; #ifdef SYSFCRONTAB char is_sysfcrontab = 0; #endif /* constants and variables defined by command line */ while (1) { c = getopt(argc, argv, "u:lrezdnhVc:"); if (c == EOF) break; switch (c) { case 'V': info(); break; case 'h': usage(); break; case 'u': if (useruid != rootuid) { fprintf(stderr, "must be privileged to use -u\n"); xexit(EXIT_ERR); } user = strdup2(optarg); break; case 'd': debug_opt = 1; break; case 'l': if (rm_opt || edit_opt || reinstall_opt) { fprintf(stderr, "Only one of the options -l, -r, -e and -z" "may be used simultaneously.\n"); xexit(EXIT_ERR); } list_opt = 1; rm_opt = edit_opt = reinstall_opt = 0; break; case 'r': if (list_opt || edit_opt || reinstall_opt) { fprintf(stderr, "Only one of the options -l, -r, -e and -z" "may be used simultaneously.\n"); xexit(EXIT_ERR); } rm_opt = 1; list_opt = edit_opt = reinstall_opt = 0; break; case 'e': if (list_opt || rm_opt || reinstall_opt) { fprintf(stderr, "Only one of the options -l, -r, -e and -z" "may be used simultaneously.\n"); xexit(EXIT_ERR); } edit_opt = 1; list_opt = rm_opt = reinstall_opt = 0; break; case 'z': if (list_opt || rm_opt || edit_opt) { fprintf(stderr, "Only one of the options -l, -r, -e and -z" "may be used simultaneously.\n"); xexit(EXIT_ERR); } reinstall_opt = ignore_prev = 1; list_opt = rm_opt = edit_opt = 0; break; case 'n': ignore_prev = 1; break; case 'c': if (optarg[0] == '/') { Set(fcronconf, optarg); } else { char buf[PATH_LEN]; snprintf(buf, sizeof(buf), "%s/%s", orig_dir, optarg); Set(fcronconf, buf); } break; case ':': fprintf(stderr, "(setopt) Missing parameter.\n"); usage(); case '?': usage(); default: fprintf(stderr, "(setopt) Warning: getopt returned %c.\n", c); } } /* read fcron.conf and update global parameters */ read_conf(); /* read the file name and/or user and check validity of the arguments */ if (argc - optind > 2) usage(); else if (argc - optind == 2) { if (list_opt + rm_opt + edit_opt + reinstall_opt == 0) file_opt = optind++; else usage(); if (useruid != rootuid) { fprintf(stderr, "must be privileged to use -u\n"); xexit(EXIT_ERR); } Set(user, argv[optind]); } else if (argc - optind == 1) { if (list_opt + rm_opt + edit_opt + reinstall_opt == 0) file_opt = optind; else { if (useruid != rootuid) { fprintf(stderr, "must be privileged to use [user|-u user]\n"); xexit(EXIT_ERR); } Set(user, argv[optind]); } } else if (list_opt + rm_opt + edit_opt + reinstall_opt != 1) usage(); if (user == NULL) { /* get user's name using getpwuid() */ if (!(pass = getpwuid(useruid))) die_e("user \"%s\" is not in passwd file. Aborting.", USERNAME); /* we need to strdup2 the value given by getpwuid() because we free * file->cf_user in delete_file */ user = strdup2(pass->pw_name); asuid = pass->pw_uid; asgid = pass->pw_gid; } else { #ifdef SYSFCRONTAB if (strcmp(user, SYSFCRONTAB) == 0) { is_sysfcrontab = 1; asuid = rootuid; asgid = rootgid; } else #endif /* def SYSFCRONTAB */ { errno = 0; if ((pass = getpwnam(user))) { asuid = pass->pw_uid; asgid = pass->pw_gid; } else die_e("user \"%s\" is not in passwd file. Aborting.", user); } } if ( #ifdef SYSFCRONTAB !is_sysfcrontab && #endif !is_allowed(user)) { die("User \"%s\" is not allowed to use %s. Aborting.", user, prog_name); } #ifdef SYSFCRONTAB if (is_sysfcrontab) runas = ROOTNAME; else #endif runas = user; } int main(int argc, char **argv) { #ifdef HAVE_LIBPAM int retcode = 0; const char *const *env; #endif struct passwd *pass; rootuid = get_user_uid_safe(ROOTNAME); rootgid = get_group_gid_safe(ROOTGROUP); memset(buf, 0, sizeof(buf)); memset(file, 0, sizeof(file)); if (strrchr(argv[0], '/') == NULL) prog_name = argv[0]; else prog_name = strrchr(argv[0], '/') + 1; useruid = getuid(); usergid = getgid(); #ifdef USE_SETE_ID /* drop any suid privilege (that we use to write files) but keep sgid * one for now: we need it for read_conf() and is_allowed() */ seteuid_safe(useruid); #endif errno = 0; if (!(pass = getpwnam(USERNAME))) die_e("user \"%s\" is not in passwd file. Aborting.", USERNAME); fcrontab_uid = pass->pw_uid; fcrontab_gid = pass->pw_gid; /* get current dir */ orig_dir[0] = '\0'; if (getcwd(orig_dir, sizeof(orig_dir)) == NULL) die_e("getcwd"); /* interpret command line options */ parseopt(argc, argv); #ifdef USE_SETE_ID /* drop any privilege we may have: we will only get them back * temporarily every time we need it. */ seteuid_safe(useruid); setegid_safe(usergid); #endif #ifdef HAVE_LIBPAM /* Open PAM session for the user and obtain any security * credentials we might need */ debug("username: %s, runas: %s", user, runas); retcode = pam_start("fcrontab", runas, &apamconv, &pamh); if (retcode != PAM_SUCCESS) die_pame(pamh, retcode, "Could not start PAM"); retcode = pam_authenticate(pamh, 0); /* is user really user? */ if (retcode != PAM_SUCCESS) die_pame(pamh, retcode, "Could not authenticate user using PAM (%d)", retcode); retcode = pam_acct_mgmt(pamh, 0); /* permitted access? */ if (retcode != PAM_SUCCESS) die_pame(pamh, retcode, "Could not init PAM account management (%d)", retcode); retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED); if (retcode != PAM_SUCCESS) die_pame(pamh, retcode, "Could not set PAM credentials"); retcode = pam_open_session(pamh, 0); if (retcode != PAM_SUCCESS) die_pame(pamh, retcode, "Could not open PAM session"); env = (const char *const *)pam_getenvlist(pamh); while (env && *env) { if (putenv((char *)*env)) die_e("Could not copy PAM environment"); env++; } /* Close the log here, because PAM calls openlog(3) and * our log messages could go to the wrong facility */ xcloselog(); #endif /* USE_PAM */ #ifdef USE_SETE_ID seteuid_safe(fcrontab_uid); /* change directory */ if (chdir(fcrontabs) != 0) { error_e("Could not chdir to %s", fcrontabs); xexit(EXIT_ERR); } seteuid_safe(useruid); #else /* USE_SETE_ID */ if (setuid(rootuid) != 0) die_e("Could not change uid to rootuid"); if (setgid(rootgid) != 0) die_e("Could not change gid to rootgid"); /* change directory */ if (chdir(fcrontabs) != 0) { error_e("Could not chdir to %s", fcrontabs); xexit(EXIT_ERR); } #endif /* USE_SETE_ID */ /* this program is seteuid : we set default permission mode * to 640 for a normal user, 600 for root, for security reasons */ if (asuid == rootuid) umask(066); /* octal : '0' + number in octal notation */ else umask(026); snprintf(buf, sizeof(buf), "%s.orig", user); /* determine what action should be taken */ if (file_opt) { if (strcmp(argv[file_opt], "-") == 0) xexit(install_stdin()); else { int fd = -1; if (*argv[file_opt] != '/') /* this is just the file name, not the path : complete it */ snprintf(file, sizeof(file), "%s/%s", orig_dir, argv[file_opt]); else { strncpy(file, argv[file_opt], sizeof(file) - 1); file[sizeof(file) - 1] = '\0'; } fd = open_as_user(file, useruid, usergid, O_RDONLY); if (fd < 0) die_e("Could not open file %s", file); if (make_file(file, fd) == OK) xexit(EXIT_OK); else xexit(EXIT_ERR); } } /* remove user's entries */ if (rm_opt == 1) { if (remove_fcrontab(1) == ENOENT) fprintf(stderr, "no fcrontab for %s\n", user); xexit(EXIT_OK); } /* list user's entries */ if (list_opt == 1) { list_file(buf); xexit(EXIT_OK); } /* edit user's entries */ if (edit_opt == 1) { edit_file(buf); xexit(EXIT_OK); } /* reinstall user's entries */ if (reinstall_opt == 1) { reinstall(buf); xexit(EXIT_OK); } /* never reached */ return EXIT_OK; }