/* vipw, vigr edit the password or group file with -s will edit shadow or gshadow file * * SPDX-FileCopyrightText: 1997 , Guy Maor * SPDX-FileCopyrightText: 1999 - 2000, Marek Michałkiewicz * SPDX-FileCopyrightText: 2002 - 2006, Tomasz Kłoczko * SPDX-FileCopyrightText: 2007 - 2013, Nicolas François * * SPDX-License-Identifier: GPL-2.0-or-later */ #include #ident "$Id$" #include #include #ifdef WITH_SELINUX #include #endif /* WITH_SELINUX */ #include #include #include #include #include #include #include #include "defines.h" #include "groupio.h" #include "nscd.h" #include "sssd.h" #include "prototypes.h" #include "pwio.h" #include "sgroupio.h" #include "shadowio.h" /*@-exitarg@*/ #include "exitcodes.h" #ifdef WITH_TCB #include #include "tcbfuncs.h" #endif /* WITH_TCB */ #include "shadowlog.h" #define MSG_WARN_EDIT_OTHER_FILE _( \ "You have modified %s.\n"\ "You may need to modify %s for consistency.\n"\ "Please use the command '%s' to do so.\n") /* * Global variables */ const char *Prog; static const char *filename, *fileeditname; static bool filelocked = false; static bool createedit = false; static int (*unlock) (void); static bool quiet = false; #ifdef WITH_TCB static const char *user = NULL; static bool tcb_mode = false; #define SHADOWTCB_SCRATCHDIR ":tmp" #endif /* WITH_TCB */ /* local function prototypes */ static void usage (int status); static int create_backup_file (FILE *, const char *, struct stat *); static void vipwexit (const char *msg, int syserr, int ret); static void vipwedit (const char *, int (*)(void), int (*)(void)); /* * usage - display usage message and exit */ static void usage (int status) { FILE *usageout = (E_SUCCESS != status) ? stderr : stdout; (void) fprintf (stderr, _("Usage: %s [options]\n" "\n" "Options:\n"), Prog); (void) fputs (_(" -g, --group edit group database\n"), usageout); (void) fputs (_(" -h, --help display this help message and exit\n"), usageout); (void) fputs (_(" -p, --passwd edit passwd database\n"), usageout); (void) fputs (_(" -q, --quiet quiet mode\n"), usageout); (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout); (void) fputs (_(" -s, --shadow edit shadow or gshadow database\n"), usageout); #ifdef WITH_TCB (void) fputs (_(" -u, --user which user's tcb shadow file to edit\n"), usageout); #endif /* WITH_TCB */ (void) fputs (_("\n"), usageout); exit (status); } /* * */ static int create_backup_file (FILE * fp, const char *backup, struct stat *sb) { struct utimbuf ub; FILE *bkfp; int c; mode_t mask; mask = umask (077); bkfp = fopen (backup, "w"); (void) umask (mask); if (NULL == bkfp) { return -1; } c = 0; if (fseeko (fp, 0, SEEK_SET) == 0) while ((c = getc (fp)) != EOF) { if (putc (c, bkfp) == EOF) { break; } } if ((EOF != c) || (ferror (fp) != 0) || (fflush (bkfp) != 0)) { fclose (bkfp); unlink (backup); return -1; } if (fsync (fileno (bkfp)) != 0) { (void) fclose (bkfp); unlink (backup); return -1; } if (fclose (bkfp) != 0) { unlink (backup); return -1; } ub.actime = sb->st_atime; ub.modtime = sb->st_mtime; if ( (utime (backup, &ub) != 0) || (chmod (backup, sb->st_mode) != 0) || (chown (backup, sb->st_uid, sb->st_gid) != 0)) { unlink (backup); return -1; } return 0; } /* * */ static void vipwexit (const char *msg, int syserr, int ret) { int err = errno; if (createedit) { if (unlink (fileeditname) != 0) { fprintf (stderr, _("%s: failed to remove %s\n"), Prog, fileeditname); /* continue */ } } if (filelocked) { if ((*unlock) () == 0) { fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, fileeditname); SYSLOG ((LOG_ERR, "failed to unlock %s", fileeditname)); /* continue */ } } if (NULL != msg) { fprintf (stderr, "%s: %s", Prog, msg); } if (0 != syserr) { fprintf (stderr, ": %s", strerror (err)); } if ( (NULL != msg) || (0 != syserr)) { (void) fputs ("\n", stderr); } if (!quiet) { fprintf (stdout, _("%s: %s is unchanged\n"), Prog, filename); } exit (ret); } #ifndef DEFAULT_EDITOR #define DEFAULT_EDITOR "vi" #endif /* * */ static void vipwedit (const char *file, int (*file_lock) (void), int (*file_unlock) (void)) { const char *editor; pid_t pid; struct stat st1, st2; int status; FILE *f; pid_t orig_pgrp, editor_pgrp = -1; sigset_t mask, omask; /* FIXME: the following should have variable sizes */ char filebackup[1024], fileedit[1024]; char *to_rename; snprintf (filebackup, sizeof filebackup, "%s-", file); #ifdef WITH_TCB if (tcb_mode) { if ( (mkdir (TCB_DIR "/" SHADOWTCB_SCRATCHDIR, 0700) != 0) && (errno != EEXIST)) { vipwexit (_("failed to create scratch directory"), errno, 1); } if (shadowtcb_drop_priv () == SHADOWTCB_FAILURE) { vipwexit (_("failed to drop privileges"), errno, 1); } snprintf (fileedit, sizeof fileedit, TCB_DIR "/" SHADOWTCB_SCRATCHDIR "/.vipw.shadow.%s", user); } else { #endif /* WITH_TCB */ snprintf (fileedit, sizeof fileedit, "%s.edit", file); #ifdef WITH_TCB } #endif /* WITH_TCB */ unlock = file_unlock; filename = file; fileeditname = fileedit; if (access (file, F_OK) != 0) { vipwexit (file, 1, 1); } #ifdef WITH_SELINUX /* if SE Linux is enabled then set the context of all new files to be the context of the file we are editing */ if (is_selinux_enabled () != 0) { char *passwd_context_raw = NULL; int ret = 0; if (getfilecon_raw (file, &passwd_context_raw) < 0) { vipwexit (_("Couldn't get file context"), errno, 1); } ret = setfscreatecon_raw (passwd_context_raw); freecon (passwd_context_raw); if (0 != ret) { vipwexit (_("setfscreatecon () failed"), errno, 1); } } #endif /* WITH_SELINUX */ #ifdef WITH_TCB if (tcb_mode && (shadowtcb_gain_priv () == SHADOWTCB_FAILURE)) { vipwexit (_("failed to gain privileges"), errno, 1); } #endif /* WITH_TCB */ if (file_lock () == 0) { vipwexit (_("Couldn't lock file"), errno, 5); } filelocked = true; #ifdef WITH_TCB if (tcb_mode && (shadowtcb_drop_priv () == SHADOWTCB_FAILURE)) { vipwexit (_("failed to drop privileges"), errno, 1); } #endif /* WITH_TCB */ /* edited copy has same owners, perm */ if (stat (file, &st1) != 0) { vipwexit (file, 1, 1); } f = fopen (file, "r"); if (NULL == f) { vipwexit (file, 1, 1); } #ifdef WITH_TCB if (tcb_mode && (shadowtcb_gain_priv () == SHADOWTCB_FAILURE)) vipwexit (_("failed to gain privileges"), errno, 1); #endif /* WITH_TCB */ if (create_backup_file (f, fileedit, &st1) != 0) { vipwexit (_("Couldn't make backup"), errno, 1); } (void) fclose (f); createedit = true; editor = getenv ("VISUAL"); if (NULL == editor) { editor = getenv ("EDITOR"); } if (NULL == editor) { editor = DEFAULT_EDITOR; } orig_pgrp = tcgetpgrp(STDIN_FILENO); pid = fork (); if (-1 == pid) { vipwexit ("fork", 1, 1); } else if (0 == pid) { /* use the system() call to invoke the editor so that it accepts command line args in the EDITOR and VISUAL environment vars */ char *buf; /* Wait for parent to make us the foreground pgrp. */ if (orig_pgrp != -1) { pid = getpid(); setpgid(0, 0); while (tcgetpgrp(STDIN_FILENO) != pid) continue; } buf = (char *) malloc (strlen (editor) + strlen (fileedit) + 2); snprintf (buf, strlen (editor) + strlen (fileedit) + 2, "%s %s", editor, fileedit); status = system (buf); if (-1 == status) { fprintf (stderr, _("%s: %s: %s\n"), Prog, editor, strerror (errno)); exit (1); } else if ( WIFEXITED (status) && (WEXITSTATUS (status) != 0)) { fprintf (stderr, _("%s: %s returned with status %d\n"), Prog, editor, WEXITSTATUS (status)); exit (WEXITSTATUS (status)); } else if (WIFSIGNALED (status)) { fprintf (stderr, _("%s: %s killed by signal %d\n"), Prog, editor, WTERMSIG (status)); exit (1); } else { exit (0); } } /* Run child in a new pgrp and make it the foreground pgrp. */ if (orig_pgrp != -1) { setpgid(pid, pid); tcsetpgrp(STDIN_FILENO, pid); /* Avoid SIGTTOU when changing foreground pgrp below. */ sigemptyset(&mask); sigaddset(&mask, SIGTTOU); sigprocmask(SIG_BLOCK, &mask, &omask); } /* set SIGCHLD to default for waitpid */ signal(SIGCHLD, SIG_DFL); for (;;) { pid = waitpid (pid, &status, WUNTRACED); if ((pid != -1) && (WIFSTOPPED (status) != 0)) { /* The child (editor) was suspended. * Restore terminal pgrp and suspend vipw. */ if (orig_pgrp != -1) { editor_pgrp = tcgetpgrp(STDIN_FILENO); if (editor_pgrp == -1) { fprintf (stderr, "%s: %s: %s", Prog, "tcgetpgrp", strerror (errno)); } if (tcsetpgrp(STDIN_FILENO, orig_pgrp) == -1) { fprintf (stderr, "%s: %s: %s", Prog, "tcsetpgrp", strerror (errno)); } } kill (getpid (), SIGSTOP); /* wake child when resumed */ if (editor_pgrp != -1) { if (tcsetpgrp(STDIN_FILENO, editor_pgrp) == -1) { fprintf (stderr, "%s: %s: %s", Prog, "tcsetpgrp", strerror (errno)); } } killpg (pid, SIGCONT); } else { break; } } if (orig_pgrp != -1) sigprocmask(SIG_SETMASK, &omask, NULL); if (-1 == pid) { vipwexit (editor, 1, 1); } else if ( WIFEXITED (status) && (WEXITSTATUS (status) != 0)) { vipwexit (NULL, 0, WEXITSTATUS (status)); } else if (WIFSIGNALED (status)) { fprintf (stderr, _("%s: %s killed by signal %d\n"), Prog, editor, WTERMSIG(status)); vipwexit (NULL, 0, 1); } if (stat (fileedit, &st2) != 0) { vipwexit (fileedit, 1, 1); } if (st1.st_mtime == st2.st_mtime) { vipwexit (0, 0, 0); } #ifdef WITH_SELINUX /* unset the fscreatecon */ if (is_selinux_enabled () != 0) { if (setfscreatecon_raw (NULL) != 0) { vipwexit (_("setfscreatecon () failed"), errno, 1); } } #endif /* WITH_SELINUX */ /* * XXX - here we should check fileedit for errors; if there are any, * ask the user what to do (edit again, save changes anyway, or quit * without saving). Use pwck or grpck to do the check. --marekm */ createedit = false; #ifdef WITH_TCB if (tcb_mode) { f = fopen (fileedit, "r"); if (NULL == f) { vipwexit (_("failed to open scratch file"), errno, 1); } if (unlink (fileedit) != 0) { vipwexit (_("failed to unlink scratch file"), errno, 1); } if (shadowtcb_drop_priv () == SHADOWTCB_FAILURE) { vipwexit (_("failed to drop privileges"), errno, 1); } if (stat (file, &st1) != 0) { vipwexit (_("failed to stat edited file"), errno, 1); } to_rename = malloc (strlen (file) + 2); if (NULL == to_rename) { vipwexit (_("failed to allocate memory"), errno, 1); } snprintf (to_rename, strlen (file) + 2, "%s+", file); if (create_backup_file (f, to_rename, &st1) != 0) { free (to_rename); vipwexit (_("failed to create backup file"), errno, 1); } (void) fclose (f); } else { #endif /* WITH_TCB */ to_rename = fileedit; #ifdef WITH_TCB } #endif /* WITH_TCB */ unlink (filebackup); link (file, filebackup); if (rename (to_rename, file) == -1) { fprintf (stderr, _("%s: can't restore %s: %s (your changes are in %s)\n"), Prog, file, strerror (errno), to_rename); #ifdef WITH_TCB if (tcb_mode) { free (to_rename); } #endif /* WITH_TCB */ vipwexit (0, 0, 1); } #ifdef WITH_TCB if (tcb_mode) { free (to_rename); if (shadowtcb_gain_priv () == SHADOWTCB_FAILURE) { vipwexit (_("failed to gain privileges"), errno, 1); } } #endif /* WITH_TCB */ if ((*file_unlock) () == 0) { fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, fileeditname); SYSLOG ((LOG_ERR, "failed to unlock %s", fileeditname)); /* continue */ } SYSLOG ((LOG_INFO, "file %s edited", fileeditname)); } int main (int argc, char **argv) { bool editshadow = false; bool do_vipw; Prog = Basename (argv[0]); log_set_progname(Prog); log_set_logfd(stderr); (void) setlocale (LC_ALL, ""); (void) bindtextdomain (PACKAGE, LOCALEDIR); (void) textdomain (PACKAGE); process_root_flag ("-R", argc, argv); do_vipw = (strcmp (Prog, "vigr") != 0); OPENLOG (do_vipw ? "vipw" : "vigr"); { /* * Parse the command line options. */ int c; static struct option long_options[] = { {"group", no_argument, NULL, 'g'}, {"help", no_argument, NULL, 'h'}, {"passwd", no_argument, NULL, 'p'}, {"quiet", no_argument, NULL, 'q'}, {"root", required_argument, NULL, 'R'}, {"shadow", no_argument, NULL, 's'}, #ifdef WITH_TCB {"user", required_argument, NULL, 'u'}, #endif /* WITH_TCB */ {NULL, 0, NULL, '\0'} }; while ((c = getopt_long (argc, argv, #ifdef WITH_TCB "ghpqR:su:", #else /* !WITH_TCB */ "ghpqR:s", #endif /* !WITH_TCB */ long_options, NULL)) != -1) { switch (c) { case 'g': do_vipw = false; break; case 'h': usage (E_SUCCESS); break; case 'p': do_vipw = true; break; case 'q': quiet = true; break; case 'R': /* no-op, handled in process_root_flag () */ break; case 's': editshadow = true; break; #ifdef WITH_TCB case 'u': user = optarg; break; #endif /* WITH_TCB */ default: usage (E_USAGE); } } if (optind != argc) { usage (E_USAGE); } } if (do_vipw) { if (editshadow) { #ifdef WITH_TCB if (getdef_bool ("USE_TCB") && (NULL != user)) { if (shadowtcb_set_user (user) == SHADOWTCB_FAILURE) { fprintf (stderr, _("%s: failed to find tcb directory for %s\n"), Prog, user); return E_SHADOW_NOTFOUND; } tcb_mode = true; } #endif /* WITH_TCB */ vipwedit (spw_dbname (), spw_lock, spw_unlock); printf (MSG_WARN_EDIT_OTHER_FILE, spw_dbname (), pw_dbname (), "vipw"); } else { vipwedit (pw_dbname (), pw_lock, pw_unlock); if (spw_file_present ()) { printf (MSG_WARN_EDIT_OTHER_FILE, pw_dbname (), spw_dbname (), "vipw -s"); } } } else { #ifdef SHADOWGRP if (editshadow) { vipwedit (sgr_dbname (), sgr_lock, sgr_unlock); printf (MSG_WARN_EDIT_OTHER_FILE, sgr_dbname (), gr_dbname (), "vigr"); } else { #endif /* SHADOWGRP */ vipwedit (gr_dbname (), gr_lock, gr_unlock); #ifdef SHADOWGRP if (sgr_file_present ()) { printf (MSG_WARN_EDIT_OTHER_FILE, gr_dbname (), sgr_dbname (), "vigr -s"); } } #endif /* SHADOWGRP */ } nscd_flush_cache ("passwd"); nscd_flush_cache ("group"); sssd_flush_cache (SSSD_DB_PASSWD | SSSD_DB_GROUP); return E_SUCCESS; }