summaryrefslogtreecommitdiffstats
path: root/src/vipw.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vipw.c')
-rw-r--r--src/vipw.c599
1 files changed, 599 insertions, 0 deletions
diff --git a/src/vipw.c b/src/vipw.c
new file mode 100644
index 0000000..488a97d
--- /dev/null
+++ b/src/vipw.c
@@ -0,0 +1,599 @@
+/*
+ vipw, vigr edit the password or group file
+ with -s will edit shadow or gshadow file
+ *
+ * SPDX-FileCopyrightText: 1997 , Guy Maor <maor@ece.utexas.edu>
+ * 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 <config.h>
+
+#ident "$Id$"
+
+#include <errno.h>
+#include <getopt.h>
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#endif /* WITH_SELINUX */
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utime.h>
+#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 <tcb.h>
+#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;
+}
+