summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore35
-rw-r--r--src/.indent.pro5
-rw-r--r--src/Makefile.am138
-rw-r--r--src/chage.c948
-rw-r--r--src/chfn.c753
-rw-r--r--src/chgpasswd.c587
-rw-r--r--src/chpasswd.c630
-rw-r--r--src/chsh.c564
-rw-r--r--src/expiry.c210
-rw-r--r--src/faillog.c741
-rw-r--r--src/gpasswd.c1207
-rw-r--r--src/groupadd.c624
-rw-r--r--src/groupdel.c491
-rw-r--r--src/groupmems.c652
-rw-r--r--src/groupmod.c871
-rw-r--r--src/groups.c218
-rw-r--r--src/grpck.c887
-rw-r--r--src/grpconv.c286
-rw-r--r--src/grpunconv.c250
-rw-r--r--src/id.c207
-rw-r--r--src/lastlog.c428
-rw-r--r--src/login.c1339
-rw-r--r--src/login_nopam.c336
-rw-r--r--src/logoutd.c299
-rw-r--r--src/newgidmap.c186
-rw-r--r--src/newgrp.c853
-rw-r--r--src/newuidmap.c186
-rw-r--r--src/newusers.c1251
-rw-r--r--src/nologin.c55
-rw-r--r--src/passwd.c1168
-rw-r--r--src/pwck.c893
-rw-r--r--src/pwconv.c333
-rw-r--r--src/pwunconv.c256
-rw-r--r--src/su.c1222
-rw-r--r--src/suauth.c239
-rw-r--r--src/sulogin.c257
-rw-r--r--src/useradd.c2315
-rw-r--r--src/userdel.c1283
-rw-r--r--src/usermod.c2285
-rw-r--r--src/vipw.c562
40 files changed, 26050 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..d5716b9
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,35 @@
+/chage
+/chfn
+/chgpasswd
+/chpasswd
+/chsh
+/expiry
+/faillog
+/gpasswd
+/groupadd
+/groupdel
+/groupmems
+/groupmod
+/groups
+/grpck
+/grpconv
+/grpunconv
+/id
+/lastlog
+/login
+/logoutd
+/newgrp
+/newgidmap
+/newuidmap
+/newusers
+/nologin
+/passwd
+/pwck
+/pwconv
+/pwunconv
+/su
+/sulogin
+/useradd
+/userdel
+/usermod
+/vipw
diff --git a/src/.indent.pro b/src/.indent.pro
new file mode 100644
index 0000000..fe572bb
--- /dev/null
+++ b/src/.indent.pro
@@ -0,0 +1,5 @@
+-kr
+-i8
+-bad
+-pcs
+-l80
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..12ef630
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,138 @@
+
+EXTRA_DIST = \
+ .indent.pro
+
+ubindir = ${prefix}/bin
+usbindir = ${prefix}/sbin
+suidperms = 4755
+sgidperms = 2755
+
+AM_CPPFLAGS = \
+ -I${top_srcdir}/lib \
+ -I$(top_srcdir)/libmisc \
+ -DLOCALEDIR=\"$(datadir)/locale\"
+
+# XXX why are login and su in /bin anyway (other than for
+# historical reasons)?
+#
+# if the system is screwed so badly that it can't mount /usr,
+# you can (hopefully) boot single user, and then you're root
+# so you don't need these programs for recovery.
+#
+# also /lib/libshadow.so.x.xx (if any) could be moved to /usr/lib
+# and installation would be much simpler (just two directories,
+# $prefix/bin and $prefix/sbin, no install-data hacks...)
+
+bin_PROGRAMS = groups login su
+sbin_PROGRAMS = nologin
+ubin_PROGRAMS = faillog lastlog chage chfn chsh expiry gpasswd newgrp passwd
+if ENABLE_SUBIDS
+ubin_PROGRAMS += newgidmap newuidmap
+endif
+usbin_PROGRAMS = \
+ chgpasswd \
+ chpasswd \
+ groupadd \
+ groupdel \
+ groupmems \
+ groupmod \
+ grpck \
+ grpconv \
+ grpunconv \
+ logoutd \
+ newusers \
+ pwck \
+ pwconv \
+ pwunconv \
+ useradd \
+ userdel \
+ usermod \
+ vipw
+
+# id and groups are from gnu, sulogin from sysvinit
+noinst_PROGRAMS = id sulogin
+
+suidbins = su
+suidubins = chage chfn chsh expiry gpasswd newgrp passwd
+if ACCT_TOOLS_SETUID
+suidubins += chage chgpasswd chpasswd groupadd groupdel groupmod newusers useradd userdel usermod
+endif
+if ENABLE_SUBIDS
+suidubins += newgidmap newuidmap
+endif
+
+if WITH_TCB
+suidubins -= passwd
+shadowsgidubins = passwd
+endif
+
+LDADD = $(INTLLIBS) \
+ $(LIBTCB) \
+ $(top_builddir)/libmisc/libmisc.a \
+ $(top_builddir)/lib/libshadow.la
+
+if ACCT_TOOLS_SETUID
+LIBPAM_SUID = $(LIBPAM)
+else
+LIBPAM_SUID =
+endif
+
+if USE_PAM
+LIBCRYPT_NOPAM =
+else
+LIBCRYPT_NOPAM = $(LIBCRYPT)
+endif
+
+chage_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX)
+newuidmap_LDADD = $(LDADD) $(LIBSELINUX)
+newgidmap_LDADD = $(LDADD) $(LIBSELINUX)
+chfn_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD)
+chgpasswd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBSELINUX) $(LIBCRYPT)
+chsh_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD)
+chpasswd_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT)
+gpasswd_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT)
+groupadd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX)
+groupdel_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX)
+groupmems_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX)
+groupmod_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX)
+grpck_LDADD = $(LDADD) $(LIBSELINUX)
+grpconv_LDADD = $(LDADD) $(LIBSELINUX)
+grpunconv_LDADD = $(LDADD) $(LIBSELINUX)
+lastlog_LDADD = $(LDADD) $(LIBAUDIT)
+login_SOURCES = \
+ login.c \
+ login_nopam.c
+login_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD)
+newgrp_LDADD = $(LDADD) $(LIBAUDIT) $(LIBCRYPT)
+newusers_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT)
+nologin_LDADD =
+passwd_LDADD = $(LDADD) $(LIBPAM) $(LIBCRACK) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT_NOPAM)
+pwck_LDADD = $(LDADD) $(LIBSELINUX)
+pwconv_LDADD = $(LDADD) $(LIBSELINUX)
+pwunconv_LDADD = $(LDADD) $(LIBSELINUX)
+su_SOURCES = \
+ su.c \
+ suauth.c
+su_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD)
+sulogin_LDADD = $(LDADD) $(LIBCRYPT)
+useradd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBSEMANAGE) $(LIBACL) $(LIBATTR)
+userdel_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBSEMANAGE)
+usermod_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBSEMANAGE) $(LIBACL) $(LIBATTR)
+vipw_LDADD = $(LDADD) $(LIBSELINUX)
+
+install-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+ ln -sf newgrp $(DESTDIR)$(ubindir)/sg
+ ln -sf vipw $(DESTDIR)$(usbindir)/vigr
+ for i in $(suidbins); do \
+ chmod $(suidperms) $(DESTDIR)$(bindir)/$$i; \
+ done
+ for i in $(suidubins); do \
+ chmod $(suidperms) $(DESTDIR)$(ubindir)/$$i; \
+ done
+if WITH_TCB
+ for i in $(shadowsgidubins); do \
+ chown root:shadow $(DESTDIR)$(ubindir)/$$i; \
+ chmod $(sgidperms) $(DESTDIR)$(ubindir)/$$i; \
+ done
+endif
diff --git a/src/chage.c b/src/chage.c
new file mode 100644
index 0000000..617e90f
--- /dev/null
+++ b/src/chage.c
@@ -0,0 +1,948 @@
+/*
+ * Copyright (c) 1989 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <time.h>
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+#include <pwd.h>
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#include <selinux/av_permissions.h>
+#endif
+#include "prototypes.h"
+#include "defines.h"
+#include "pwio.h"
+#include "shadowio.h"
+#ifdef WITH_TCB
+#include "tcbfuncs.h"
+#endif
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static bool
+ dflg = false, /* set last password change date */
+ Eflg = false, /* set account expiration date */
+ Iflg = false, /* set password inactive after expiration */
+ lflg = false, /* show account aging information */
+ mflg = false, /* set minimum number of days before password change */
+ Mflg = false, /* set maximum number of days before password change */
+ Wflg = false; /* set expiration warning days */
+static bool amroot = false;
+
+static bool pw_locked = false; /* Indicate if the password file is locked */
+static bool spw_locked = false; /* Indicate if the shadow file is locked */
+/* The name and UID of the user being worked on */
+static char user_name[BUFSIZ] = "";
+static uid_t user_uid = -1;
+
+static long mindays;
+static long maxdays;
+static long lstchgdate;
+static long warndays;
+static long inactdays;
+static long expdate;
+
+/* local function prototypes */
+static /*@noreturn@*/void usage (int status);
+static void date_to_str (char *buf, size_t maxsize, time_t date);
+static int new_fields (void);
+static void print_date (time_t date);
+static void list_fields (void);
+static void process_flags (int argc, char **argv);
+static void check_flags (int argc, int opt_index);
+static void check_perms (void);
+static void open_files (bool readonly);
+static void close_files (void);
+static /*@noreturn@*/void fail_exit (int code);
+
+/*
+ * fail_exit - do some cleanup and exit with the given error code
+ */
+static /*@noreturn@*/void fail_exit (int code)
+{
+ if (spw_locked) {
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ }
+ if (pw_locked) {
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ }
+ closelog ();
+
+#ifdef WITH_AUDIT
+ if (E_SUCCESS != code) {
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "change age",
+ user_name, (unsigned int) user_uid, 0);
+ }
+#endif
+
+ exit (code);
+}
+
+/*
+ * usage - print command line syntax and exit
+ */
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options] LOGIN\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -d, --lastday LAST_DAY set date of last password change to LAST_DAY\n"), usageout);
+ (void) fputs (_(" -E, --expiredate EXPIRE_DATE set account expiration date to EXPIRE_DATE\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -I, --inactive INACTIVE set password inactive after expiration\n"
+ " to INACTIVE\n"), usageout);
+ (void) fputs (_(" -l, --list show account aging information\n"), usageout);
+ (void) fputs (_(" -m, --mindays MIN_DAYS set minimum number of days before password\n"
+ " change to MIN_DAYS\n"), usageout);
+ (void) fputs (_(" -M, --maxdays MAX_DAYS set maximim number of days before password\n"
+ " change to MAX_DAYS\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs (_(" -W, --warndays WARN_DAYS set expiration warning days to WARN_DAYS\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+static void date_to_str (char *buf, size_t maxsize, time_t date)
+{
+ struct tm *tp;
+
+ tp = gmtime (&date);
+#ifdef HAVE_STRFTIME
+ (void) strftime (buf, maxsize, "%Y-%m-%d", tp);
+#else
+ (void) snprintf (buf, maxsize, "%04d-%02d-%02d",
+ tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday);
+#endif /* HAVE_STRFTIME */
+}
+
+/*
+ * new_fields - change the user's password aging information interactively.
+ *
+ * prompt the user for all of the password age values. set the fields
+ * from the user's response, or leave alone if nothing was entered. The
+ * value (-1) is used to indicate the field should be removed if possible.
+ * any other negative value is an error. very large positive values will
+ * be handled elsewhere.
+ */
+static int new_fields (void)
+{
+ char buf[200];
+
+ (void) puts (_("Enter the new value, or press ENTER for the default"));
+ (void) puts ("");
+
+ (void) snprintf (buf, sizeof buf, "%ld", mindays);
+ change_field (buf, sizeof buf, _("Minimum Password Age"));
+ if ( (getlong (buf, &mindays) == 0)
+ || (mindays < -1)) {
+ return 0;
+ }
+
+ (void) snprintf (buf, sizeof buf, "%ld", maxdays);
+ change_field (buf, sizeof buf, _("Maximum Password Age"));
+ if ( (getlong (buf, &maxdays) == 0)
+ || (maxdays < -1)) {
+ return 0;
+ }
+
+ if (-1 == lstchgdate) {
+ strcpy (buf, "-1");
+ } else {
+ date_to_str (buf, sizeof buf, (time_t) lstchgdate * SCALE);
+ }
+
+ change_field (buf, sizeof buf, _("Last Password Change (YYYY-MM-DD)"));
+
+ if (strcmp (buf, "-1") == 0) {
+ lstchgdate = -1;
+ } else {
+ lstchgdate = strtoday (buf);
+ if (lstchgdate <= -1) {
+ return 0;
+ }
+ }
+
+ (void) snprintf (buf, sizeof buf, "%ld", warndays);
+ change_field (buf, sizeof buf, _("Password Expiration Warning"));
+ if ( (getlong (buf, &warndays) == 0)
+ || (warndays < -1)) {
+ return 0;
+ }
+
+ (void) snprintf (buf, sizeof buf, "%ld", inactdays);
+ change_field (buf, sizeof buf, _("Password Inactive"));
+ if ( (getlong (buf, &inactdays) == 0)
+ || (inactdays < -1)) {
+ return 0;
+ }
+
+ if (-1 == expdate) {
+ strcpy (buf, "-1");
+ } else {
+ date_to_str (buf, sizeof buf, (time_t) expdate * SCALE);
+ }
+
+ change_field (buf, sizeof buf,
+ _("Account Expiration Date (YYYY-MM-DD)"));
+
+ if (strcmp (buf, "-1") == 0) {
+ expdate = -1;
+ } else {
+ expdate = strtoday (buf);
+ if (expdate <= -1) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static void print_date (time_t date)
+{
+#ifdef HAVE_STRFTIME
+ struct tm *tp;
+ char buf[80];
+
+ tp = gmtime (&date);
+ if (NULL == tp) {
+ (void) printf ("time_t: %lu\n", (unsigned long)date);
+ } else {
+ (void) strftime (buf, sizeof buf, "%b %d, %Y", tp);
+ (void) puts (buf);
+ }
+#else
+ struct tm *tp;
+ char *cp = NULL;
+
+ tp = gmtime (&date);
+ if (NULL != tp) {
+ cp = asctime (tp);
+ }
+ if (NULL != cp) {
+ (void) printf ("%6.6s, %4.4s\n", cp + 4, cp + 20);
+ } else {
+ (void) printf ("time_t: %lu\n", date);
+ }
+#endif
+}
+
+/*
+ * list_fields - display the current values of the expiration fields
+ *
+ * display the password age information from the password fields. Date
+ * values will be displayed as a calendar date, or the word "never" if
+ * the date is 1/1/70, which is day number 0.
+ */
+static void list_fields (void)
+{
+ long changed = 0;
+ long expires;
+
+ /*
+ * The "last change" date is either "never" or the date the password
+ * was last modified. The date is the number of days since 1/1/1970.
+ */
+ (void) fputs (_("Last password change\t\t\t\t\t: "), stdout);
+ if (lstchgdate < 0) {
+ (void) puts (_("never"));
+ } else if (lstchgdate == 0) {
+ (void) puts (_("password must be changed"));
+ } else {
+ changed = lstchgdate * SCALE;
+ print_date ((time_t) changed);
+ }
+
+ /*
+ * The password expiration date is determined from the last change
+ * date plus the number of days the password is valid for.
+ */
+ (void) fputs (_("Password expires\t\t\t\t\t: "), stdout);
+ if (lstchgdate == 0) {
+ (void) puts (_("password must be changed"));
+ } else if ( (lstchgdate < 0)
+ || (maxdays >= (10000 * (DAY / SCALE)))
+ || (maxdays < 0)) {
+ (void) puts (_("never"));
+ } else {
+ expires = changed + maxdays * SCALE;
+ print_date ((time_t) expires);
+ }
+
+ /*
+ * The account becomes inactive if the password is expired for more
+ * than "inactdays". The expiration date is calculated and the
+ * number of inactive days is added. The resulting date is when the
+ * active will be disabled.
+ */
+ (void) fputs (_("Password inactive\t\t\t\t\t: "), stdout);
+ if (lstchgdate == 0) {
+ (void) puts (_("password must be changed"));
+ } else if ( (lstchgdate < 0)
+ || (inactdays < 0)
+ || (maxdays >= (10000 * (DAY / SCALE)))
+ || (maxdays < 0)) {
+ (void) puts (_("never"));
+ } else {
+ expires = changed + (maxdays + inactdays) * SCALE;
+ print_date ((time_t) expires);
+ }
+
+ /*
+ * The account will expire on the given date regardless of the
+ * password expiring or not.
+ */
+ (void) fputs (_("Account expires\t\t\t\t\t\t: "), stdout);
+ if (expdate < 0) {
+ (void) puts (_("never"));
+ } else {
+ expires = expdate * SCALE;
+ print_date ((time_t) expires);
+ }
+
+ /*
+ * Start with the easy numbers - the number of days before the
+ * password can be changed, the number of days after which the
+ * password must be chaged, the number of days before the password
+ * expires that the user is told, and the number of days after the
+ * password expires that the account becomes unusable.
+ */
+ printf (_("Minimum number of days between password change\t\t: %ld\n"),
+ mindays);
+ printf (_("Maximum number of days between password change\t\t: %ld\n"),
+ maxdays);
+ printf (_("Number of days of warning before password expires\t: %ld\n"),
+ warndays);
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ /*
+ * Parse the command line options.
+ */
+ int c;
+ static struct option long_options[] = {
+ {"lastday", required_argument, NULL, 'd'},
+ {"expiredate", required_argument, NULL, 'E'},
+ {"help", no_argument, NULL, 'h'},
+ {"inactive", required_argument, NULL, 'I'},
+ {"list", no_argument, NULL, 'l'},
+ {"mindays", required_argument, NULL, 'm'},
+ {"maxdays", required_argument, NULL, 'M'},
+ {"root", required_argument, NULL, 'R'},
+ {"warndays", required_argument, NULL, 'W'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "d:E:hI:lm:M:R:W:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ dflg = true;
+ lstchgdate = strtoday (optarg);
+ if (lstchgdate < -1) {
+ fprintf (stderr,
+ _("%s: invalid date '%s'\n"),
+ Prog, optarg);
+ usage (E_USAGE);
+ }
+ break;
+ case 'E':
+ Eflg = true;
+ expdate = strtoday (optarg);
+ if (expdate < -1) {
+ fprintf (stderr,
+ _("%s: invalid date '%s'\n"),
+ Prog, optarg);
+ usage (E_USAGE);
+ }
+ break;
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'I':
+ Iflg = true;
+ if ( (getlong (optarg, &inactdays) == 0)
+ || (inactdays < -1)) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ usage (E_USAGE);
+ }
+ break;
+ case 'l':
+ lflg = true;
+ break;
+ case 'm':
+ mflg = true;
+ if ( (getlong (optarg, &mindays) == 0)
+ || (mindays < -1)) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ usage (E_USAGE);
+ }
+ break;
+ case 'M':
+ Mflg = true;
+ if ( (getlong (optarg, &maxdays) == 0)
+ || (maxdays < -1)) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ usage (E_USAGE);
+ }
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ case 'W':
+ Wflg = true;
+ if ( (getlong (optarg, &warndays) == 0)
+ || (warndays < -1)) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ usage (E_USAGE);
+ }
+ break;
+ default:
+ usage (E_USAGE);
+ }
+ }
+
+ check_flags (argc, optind);
+}
+
+/*
+ * check_flags - check flags and parameters consistency
+ *
+ * It will not return if an error is encountered.
+ */
+static void check_flags (int argc, int opt_index)
+{
+ /*
+ * Make certain the flags do not conflict and that there is a user
+ * name on the command line.
+ */
+
+ if (argc != opt_index + 1) {
+ usage (E_USAGE);
+ }
+
+ if (lflg && (mflg || Mflg || dflg || Wflg || Iflg || Eflg)) {
+ fprintf (stderr,
+ _("%s: do not include \"l\" with other flags\n"),
+ Prog);
+ usage (E_USAGE);
+ }
+}
+
+/*
+ * check_perms - check if the caller is allowed to add a group
+ *
+ * Non-root users are only allowed to display their aging information.
+ * (we will later make sure that the user is only listing her aging
+ * information)
+ *
+ * With PAM support, the setuid bit can be set on chage to allow
+ * non-root users to groups.
+ * Without PAM support, only users who can write in the group databases
+ * can add groups.
+ *
+ * It will not return if the user is not allowed.
+ */
+static void check_perms (void)
+{
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ pam_handle_t *pamh = NULL;
+ struct passwd *pampw;
+ int retval;
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+
+ /*
+ * An unprivileged user can ask for their own aging information, but
+ * only root can change it, or list another user's aging
+ * information.
+ */
+
+ if (!amroot && !lflg) {
+ fprintf (stderr, _("%s: Permission denied.\n"), Prog);
+ fail_exit (E_NOPERM);
+ }
+
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+ if (NULL == pampw) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ exit (E_NOPERM);
+ }
+
+ retval = pam_start ("chage", pampw->pw_name, &conv, &pamh);
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_authenticate (pamh, 0);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_acct_mgmt (pamh, 0);
+ }
+
+ if (PAM_SUCCESS != retval) {
+ fprintf (stderr, _("%s: PAM: %s\n"),
+ Prog, pam_strerror (pamh, retval));
+ SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval)));
+ if (NULL != pamh) {
+ (void) pam_end (pamh, retval);
+ }
+ fail_exit (E_NOPERM);
+ }
+ (void) pam_end (pamh, retval);
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+}
+
+/*
+ * open_files - open the shadow database
+ *
+ * In read-only mode, the databases are not locked and are opened
+ * only for reading.
+ */
+static void open_files (bool readonly)
+{
+ /*
+ * Lock and open the password file. This loads all of the password
+ * file entries into memory. Then we get a pointer to the password
+ * file entry for the requested user.
+ */
+ if (!readonly) {
+ if (pw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, pw_dbname ());
+ fail_exit (E_NOPERM);
+ }
+ pw_locked = true;
+ }
+ if (pw_open (readonly ? O_RDONLY: O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", pw_dbname ()));
+ fail_exit (E_NOPERM);
+ }
+
+ /*
+ * For shadow password files we have to lock the file and read in
+ * the entries as was done for the password file. The user entries
+ * does not have to exist in this case; a new entry will be created
+ * for this user if one does not exist already.
+ */
+ if (!readonly) {
+ if (spw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, spw_dbname ());
+ fail_exit (E_NOPERM);
+ }
+ spw_locked = true;
+ }
+ if (spw_open (readonly ? O_RDONLY: O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", spw_dbname ()));
+ fail_exit (E_NOPERM);
+ }
+}
+
+/*
+ * close_files - close and unlock the password/shadow databases
+ */
+static void close_files (void)
+{
+ /*
+ * Now close the shadow password file, which will cause all of the
+ * entries to be re-written.
+ */
+ if (spw_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ()));
+ fail_exit (E_NOPERM);
+ }
+
+ /*
+ * Close the password file. If any entries were modified, the file
+ * will be re-written.
+ */
+ if (pw_close () == 0) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
+ fail_exit (E_NOPERM);
+ }
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ spw_locked = false;
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ pw_locked = false;
+}
+
+/*
+ * update_age - update the aging information in the database
+ *
+ * It will not return in case of error
+ */
+static void update_age (/*@null@*/const struct spwd *sp,
+ /*@notnull@*/const struct passwd *pw)
+{
+ struct spwd spwent;
+
+ /*
+ * There was no shadow entry. The new entry will have the encrypted
+ * password transferred from the normal password file along with the
+ * aging information.
+ */
+ if (NULL == sp) {
+ struct passwd pwent = *pw;
+
+ memzero (&spwent, sizeof spwent);
+ spwent.sp_namp = xstrdup (pwent.pw_name);
+ spwent.sp_pwdp = xstrdup (pwent.pw_passwd);
+ spwent.sp_flag = SHADOW_SP_FLAG_UNSET;
+
+ pwent.pw_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
+ if (pw_update (&pwent) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"), Prog, pw_dbname (), pwent.pw_name);
+ fail_exit (E_NOPERM);
+ }
+ } else {
+ spwent.sp_namp = xstrdup (sp->sp_namp);
+ spwent.sp_pwdp = xstrdup (sp->sp_pwdp);
+ spwent.sp_flag = sp->sp_flag;
+ }
+
+ /*
+ * Copy the fields back to the shadow file entry and write the
+ * modified entry back to the shadow file. Closing the shadow and
+ * password files will commit any changes that have been made.
+ */
+ spwent.sp_max = maxdays;
+ spwent.sp_min = mindays;
+ spwent.sp_lstchg = lstchgdate;
+ spwent.sp_warn = warndays;
+ spwent.sp_inact = inactdays;
+ spwent.sp_expire = expdate;
+
+ if (spw_update (&spwent) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"), Prog, spw_dbname (), spwent.sp_namp);
+ fail_exit (E_NOPERM);
+ }
+
+}
+
+/*
+ * get_defaults - get the value of the fields not set from the command line
+ */
+static void get_defaults (/*@null@*/const struct spwd *sp)
+{
+ /*
+ * Set the fields that aren't being set from the command line from
+ * the password file.
+ */
+ if (NULL != sp) {
+ if (!Mflg) {
+ maxdays = sp->sp_max;
+ }
+ if (!mflg) {
+ mindays = sp->sp_min;
+ }
+ if (!dflg) {
+ lstchgdate = sp->sp_lstchg;
+ }
+ if (!Wflg) {
+ warndays = sp->sp_warn;
+ }
+ if (!Iflg) {
+ inactdays = sp->sp_inact;
+ }
+ if (!Eflg) {
+ expdate = sp->sp_expire;
+ }
+ } else {
+ /*
+ * Use default values that will not change the behavior of the
+ * account.
+ */
+ if (!Mflg) {
+ maxdays = -1;
+ }
+ if (!mflg) {
+ mindays = -1;
+ }
+ if (!dflg) {
+ lstchgdate = -1;
+ }
+ if (!Wflg) {
+ warndays = -1;
+ }
+ if (!Iflg) {
+ inactdays = -1;
+ }
+ if (!Eflg) {
+ expdate = -1;
+ }
+ }
+}
+
+/*
+ * chage - change a user's password aging information
+ *
+ * This command controls the password aging information.
+ *
+ * The valid options are
+ *
+ * -d set last password change date (*)
+ * -E set account expiration date (*)
+ * -I set password inactive after expiration (*)
+ * -l show account aging information
+ * -M set maximim number of days before password change (*)
+ * -m set minimum number of days before password change (*)
+ * -W set expiration warning days (*)
+ *
+ * (*) requires root permission to execute.
+ *
+ * All of the time fields are entered in the internal format which is
+ * either seconds or days.
+ */
+
+int main (int argc, char **argv)
+{
+ const struct spwd *sp;
+ uid_t ruid;
+ gid_t rgid;
+ const struct passwd *pw;
+
+ /*
+ * Get the program name so that error messages can use it.
+ */
+ Prog = Basename (argv[0]);
+
+ sanitize_env ();
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+#ifdef WITH_AUDIT
+ audit_help_open ();
+#endif
+ OPENLOG ("chage");
+
+ ruid = getuid ();
+ rgid = getgid ();
+ amroot = (ruid == 0);
+#ifdef WITH_SELINUX
+ if (amroot && (is_selinux_enabled () > 0)) {
+ amroot = (selinux_check_passwd_access (PASSWD__ROOTOK) == 0);
+ }
+#endif
+
+ process_flags (argc, argv);
+
+ check_perms ();
+
+ if (!spw_file_present ()) {
+ fprintf (stderr,
+ _("%s: the shadow password file is not present\n"),
+ Prog);
+ SYSLOG ((LOG_WARN, "can't find the shadow password file"));
+ closelog ();
+ exit (E_SHADOW_NOTFOUND);
+ }
+
+ open_files (lflg);
+ /* Drop privileges */
+ if (lflg && ( (setregid (rgid, rgid) != 0)
+ || (setreuid (ruid, ruid) != 0))) {
+ fprintf (stderr, _("%s: failed to drop privileges (%s)\n"),
+ Prog, strerror (errno));
+ fail_exit (E_NOPERM);
+ }
+
+ pw = pw_locate (argv[optind]);
+ if (NULL == pw) {
+ fprintf (stderr, _("%s: user '%s' does not exist in %s\n"),
+ Prog, argv[optind], pw_dbname ());
+ closelog ();
+ fail_exit (E_NOPERM);
+ }
+
+ STRFCPY (user_name, pw->pw_name);
+#ifdef WITH_TCB
+ if (shadowtcb_set_user (pw->pw_name) == SHADOWTCB_FAILURE) {
+ fail_exit (E_NOPERM);
+ }
+#endif
+ user_uid = pw->pw_uid;
+
+ sp = spw_locate (argv[optind]);
+ get_defaults (sp);
+
+ /*
+ * Print out the expiration fields if the user has requested the
+ * list option.
+ */
+ if (lflg) {
+ if (!amroot && (ruid != user_uid)) {
+ fprintf (stderr, _("%s: Permission denied.\n"), Prog);
+ fail_exit (E_NOPERM);
+ }
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "display aging info",
+ user_name, (unsigned int) user_uid, 1);
+#endif
+ list_fields ();
+ fail_exit (E_SUCCESS);
+ }
+
+ /*
+ * If none of the fields were changed from the command line, let the
+ * user interactively change them.
+ */
+ if (!mflg && !Mflg && !dflg && !Wflg && !Iflg && !Eflg) {
+ printf (_("Changing the aging information for %s\n"),
+ user_name);
+ if (new_fields () == 0) {
+ fprintf (stderr, _("%s: error changing fields\n"),
+ Prog);
+ fail_exit (E_NOPERM);
+ }
+#ifdef WITH_AUDIT
+ else {
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "change all aging information",
+ user_name, (unsigned int) user_uid, 1);
+ }
+#endif
+ } else {
+#ifdef WITH_AUDIT
+ if (Mflg) {
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "change max age",
+ user_name, (unsigned int) user_uid, 1);
+ }
+ if (mflg) {
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "change min age",
+ user_name, (unsigned int) user_uid, 1);
+ }
+ if (dflg) {
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "change last change date",
+ user_name, (unsigned int) user_uid, 1);
+ }
+ if (Wflg) {
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "change passwd warning",
+ user_name, (unsigned int) user_uid, 1);
+ }
+ if (Iflg) {
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "change inactive days",
+ user_name, (unsigned int) user_uid, 1);
+ }
+ if (Eflg) {
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "change passwd expiration",
+ user_name, (unsigned int) user_uid, 1);
+ }
+#endif
+ }
+
+ update_age (sp, pw);
+
+ close_files ();
+
+ SYSLOG ((LOG_INFO, "changed password expiry for %s", user_name));
+
+ closelog ();
+ exit (E_SUCCESS);
+}
+
diff --git a/src/chfn.c b/src/chfn.c
new file mode 100644
index 0000000..18aa3de
--- /dev/null
+++ b/src/chfn.c
@@ -0,0 +1,753 @@
+/*
+ * Copyright (c) 1989 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2001 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#include <selinux/av_permissions.h>
+#endif
+#include "defines.h"
+#include "getdef.h"
+#include "nscd.h"
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif
+#include "prototypes.h"
+#include "pwauth.h"
+#include "pwio.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+/*
+ * Global variables.
+ */
+const char *Prog;
+static char fullnm[BUFSIZ];
+static char roomno[BUFSIZ];
+static char workph[BUFSIZ];
+static char homeph[BUFSIZ];
+static char slop[BUFSIZ];
+static bool amroot;
+/* Flags */
+static bool fflg = false; /* -f - set full name */
+static bool rflg = false; /* -r - set room number */
+static bool wflg = false; /* -w - set work phone number */
+static bool hflg = false; /* -h - set home phone number */
+static bool oflg = false; /* -o - set other information */
+static bool pw_locked = false;
+
+/*
+ * External identifiers
+ */
+
+/* local function prototypes */
+static void fail_exit (int code);
+static /*@noreturn@*/void usage (int status);
+static bool may_change_field (int);
+static void new_fields (void);
+static char *copy_field (char *, char *, char *);
+static void process_flags (int argc, char **argv);
+static void check_perms (const struct passwd *pw);
+static void update_gecos (const char *user, char *gecos);
+static void get_old_fields (const char *gecos);
+
+/*
+ * fail_exit - exit with an error and do some cleanup
+ */
+static void fail_exit (int code)
+{
+ if (pw_locked) {
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ }
+ pw_locked = false;
+
+ closelog ();
+
+ exit (code);
+}
+
+/*
+ * usage - print command line syntax and exit
+ */
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options] [LOGIN]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -f, --full-name FULL_NAME change user's full name\n"), usageout);
+ (void) fputs (_(" -h, --home-phone HOME_PHONE change user's home phone number\n"), usageout);
+ (void) fputs (_(" -o, --other OTHER_INFO change user's other GECOS information\n"), usageout);
+ (void) fputs (_(" -r, --room ROOM_NUMBER change user's room number\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs (_(" -u, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -w, --work-phone WORK_PHONE change user's office phone number\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * may_change_field - indicate if the user is allowed to change a given field
+ * of her gecos information
+ *
+ * root can change any field.
+ *
+ * field should be one of 'f', 'r', 'w', 'h'
+ *
+ * Return true if the user can change the field and false otherwise.
+ */
+static bool may_change_field (int field)
+{
+ const char *cp;
+
+ /*
+ * CHFN_RESTRICT can now specify exactly which fields may be changed
+ * by regular users, by using any combination of the following
+ * letters:
+ * f - full name
+ * r - room number
+ * w - work phone
+ * h - home phone
+ *
+ * This makes it possible to disallow changing the room number
+ * information, for example - this feature was suggested by Maciej
+ * 'Tycoon' Majchrowski.
+ *
+ * For backward compatibility, "yes" is equivalent to "rwh",
+ * "no" is equivalent to "frwh". Only root can change anything
+ * if the string is empty or not defined at all.
+ */
+ if (amroot) {
+ return true;
+ }
+
+ cp = getdef_str ("CHFN_RESTRICT");
+ if (NULL == cp) {
+ cp = "";
+ } else if (strcmp (cp, "yes") == 0) {
+ cp = "rwh";
+ } else if (strcmp (cp, "no") == 0) {
+ cp = "frwh";
+ }
+
+ if (strchr (cp, field) != NULL) {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * new_fields - change the user's GECOS information interactively
+ *
+ * prompt the user for each of the four fields and fill in the fields from
+ * the user's response, or leave alone if nothing was entered.
+ */
+static void new_fields (void)
+{
+ puts (_("Enter the new value, or press ENTER for the default"));
+
+ if (may_change_field ('f')) {
+ change_field (fullnm, sizeof fullnm, _("Full Name"));
+ } else {
+ printf (_("\t%s: %s\n"), _("Full Name"), fullnm);
+ }
+
+ if (may_change_field ('r')) {
+ change_field (roomno, sizeof roomno, _("Room Number"));
+ } else {
+ printf (_("\t%s: %s\n"), _("Room Number"), fullnm);
+ }
+
+ if (may_change_field ('w')) {
+ change_field (workph, sizeof workph, _("Work Phone"));
+ } else {
+ printf (_("\t%s: %s\n"), _("Work Phone"), fullnm);
+ }
+
+ if (may_change_field ('h')) {
+ change_field (homeph, sizeof homeph, _("Home Phone"));
+ } else {
+ printf (_("\t%s: %s\n"), _("Home Phone"), fullnm);
+ }
+
+ if (amroot) {
+ change_field (slop, sizeof slop, _("Other"));
+ }
+}
+
+/*
+ * copy_field - get the next field from the gecos field
+ *
+ * copy_field copies the next field from the gecos field, returning a
+ * pointer to the field which follows, or NULL if there are no more fields.
+ *
+ * in - the current GECOS field
+ * out - where to copy the field to
+ * extra - fields with '=' get copied here
+ */
+static char *copy_field (char *in, char *out, char *extra)
+{
+ char *cp = NULL;
+
+ while (NULL != in) {
+ cp = strchr (in, ',');
+ if (NULL != cp) {
+ *cp++ = '\0';
+ }
+
+ if (strchr (in, '=') == NULL) {
+ break;
+ }
+
+ if (NULL != extra) {
+ if ('\0' != extra[0]) {
+ strcat (extra, ",");
+ }
+
+ strcat (extra, in);
+ }
+ in = cp;
+ }
+ if ((NULL != in) && (NULL != out)) {
+ strcpy (out, in);
+ }
+
+ return cp;
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ int c; /* flag currently being processed */
+ static struct option long_options[] = {
+ {"full-name", required_argument, NULL, 'f'},
+ {"home-phone", required_argument, NULL, 'h'},
+ {"other", required_argument, NULL, 'o'},
+ {"room", required_argument, NULL, 'r'},
+ {"root", required_argument, NULL, 'R'},
+ {"help", no_argument, NULL, 'u'},
+ {"work-phone", required_argument, NULL, 'w'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ /*
+ * The remaining arguments will be processed one by one and executed
+ * by this command. The name is the last argument if it does not
+ * begin with a "-", otherwise the name is determined from the
+ * environment and must agree with the real UID. Also, the UID will
+ * be checked for any commands which are restricted to root only.
+ */
+ while ((c = getopt_long (argc, argv, "f:h:o:r:R:uw:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'f':
+ if (!may_change_field ('f')) {
+ fprintf (stderr,
+ _("%s: Permission denied.\n"), Prog);
+ exit (E_NOPERM);
+ }
+ fflg = true;
+ STRFCPY (fullnm, optarg);
+ break;
+ case 'h':
+ if (!may_change_field ('h')) {
+ fprintf (stderr,
+ _("%s: Permission denied.\n"), Prog);
+ exit (E_NOPERM);
+ }
+ hflg = true;
+ STRFCPY (homeph, optarg);
+ break;
+ case 'o':
+ if (!amroot) {
+ fprintf (stderr,
+ _("%s: Permission denied.\n"), Prog);
+ exit (E_NOPERM);
+ }
+ oflg = true;
+ STRFCPY (slop, optarg);
+ break;
+ case 'r':
+ if (!may_change_field ('r')) {
+ fprintf (stderr,
+ _("%s: Permission denied.\n"), Prog);
+ exit (E_NOPERM);
+ }
+ rflg = true;
+ STRFCPY (roomno, optarg);
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ case 'u':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'w':
+ if (!may_change_field ('w')) {
+ fprintf (stderr,
+ _("%s: Permission denied.\n"), Prog);
+ exit (E_NOPERM);
+ }
+ wflg = true;
+ STRFCPY (workph, optarg);
+ break;
+ default:
+ usage (E_USAGE);
+ }
+ }
+}
+
+/*
+ * check_perms - check if the caller is allowed to add a group
+ *
+ * Non-root users are only allowed to change their gecos field.
+ * (see also may_change_field())
+ *
+ * Non-root users must be authenticated.
+ *
+ * It will not return if the user is not allowed.
+ */
+static void check_perms (const struct passwd *pw)
+{
+#ifdef USE_PAM
+ pam_handle_t *pamh = NULL;
+ int retval;
+ struct passwd *pampw;
+#endif
+
+ /*
+ * Non-privileged users are only allowed to change the gecos field
+ * if the UID of the user matches the current real UID.
+ */
+ if (!amroot && pw->pw_uid != getuid ()) {
+ fprintf (stderr, _("%s: Permission denied.\n"), Prog);
+ closelog ();
+ exit (E_NOPERM);
+ }
+#ifdef WITH_SELINUX
+ /*
+ * If the UID of the user does not match the current real UID,
+ * check if the change is allowed by SELinux policy.
+ */
+ if ((pw->pw_uid != getuid ())
+ && (is_selinux_enabled () > 0)
+ && (selinux_check_passwd_access (PASSWD__CHFN) != 0)) {
+ fprintf (stderr, _("%s: Permission denied.\n"), Prog);
+ closelog ();
+ exit (E_NOPERM);
+ }
+#endif
+
+#ifndef USE_PAM
+ /*
+ * Non-privileged users are optionally authenticated (must enter the
+ * password of the user whose information is being changed) before
+ * any changes can be made. Idea from util-linux chfn/chsh.
+ * --marekm
+ */
+ if (!amroot && getdef_bool ("CHFN_AUTH")) {
+ passwd_check (pw->pw_name, pw->pw_passwd, "chfn");
+ }
+
+#else /* !USE_PAM */
+ pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+ if (NULL == pampw) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ exit (E_NOPERM);
+ }
+
+ retval = pam_start ("chfn", pampw->pw_name, &conv, &pamh);
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_authenticate (pamh, 0);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_acct_mgmt (pamh, 0);
+ }
+
+ if (PAM_SUCCESS != retval) {
+ fprintf (stderr, _("%s: PAM: %s\n"),
+ Prog, pam_strerror (pamh, retval));
+ SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval)));
+ if (NULL != pamh) {
+ (void) pam_end (pamh, retval);
+ }
+ exit (E_NOPERM);
+ }
+ (void) pam_end (pamh, retval);
+#endif /* USE_PAM */
+}
+
+/*
+ * update_gecos - update the gecos fields in the password database
+ *
+ * Commit the user's entry after changing her gecos field.
+ */
+static void update_gecos (const char *user, char *gecos)
+{
+ const struct passwd *pw; /* The user's password file entry */
+ struct passwd pwent; /* modified password file entry */
+
+ /*
+ * Before going any further, raise the ulimit to prevent colliding
+ * into a lowered ulimit, and set the real UID to root to protect
+ * against unexpected signals. Any keyboard signals are set to be
+ * ignored.
+ */
+ if (setuid (0) != 0) {
+ fputs (_("Cannot change ID to root.\n"), stderr);
+ SYSLOG ((LOG_ERR, "can't setuid(0)"));
+ fail_exit (E_NOPERM);
+ }
+ pwd_init ();
+
+ /*
+ * The passwd entry is now ready to be committed back to the
+ * password file. Get a lock on the file and open it.
+ */
+ if (pw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, pw_dbname ());
+ fail_exit (E_NOPERM);
+ }
+ pw_locked = true;
+ if (pw_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"), Prog, pw_dbname ());
+ fail_exit (E_NOPERM);
+ }
+
+ /*
+ * Get the entry to update using pw_locate() - we want the real one
+ * from /etc/passwd, not the one from getpwnam() which could contain
+ * the shadow password if (despite the warnings) someone enables
+ * AUTOSHADOW (or SHADOW_COMPAT in libc). --marekm
+ */
+ pw = pw_locate (user);
+ if (NULL == pw) {
+ fprintf (stderr,
+ _("%s: user '%s' does not exist in %s\n"),
+ Prog, user, pw_dbname ());
+ fail_exit (E_NOPERM);
+ }
+
+ /*
+ * Make a copy of the entry, then change the gecos field. The other
+ * fields remain unchanged.
+ */
+ pwent = *pw;
+ pwent.pw_gecos = gecos;
+
+ /*
+ * Update the passwd file entry. If there is a DBM file, update that
+ * entry as well.
+ */
+ if (pw_update (&pwent) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, pw_dbname (), pwent.pw_name);
+ fail_exit (E_NOPERM);
+ }
+
+ /*
+ * Changes have all been made, so commit them and unlock the file.
+ */
+ if (pw_close () == 0) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
+ fail_exit (E_NOPERM);
+ }
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ pw_locked = false;
+}
+
+/*
+ * get_old_fields - parse the old gecos and use the old value for the fields
+ * which are not set on the command line
+ */
+static void get_old_fields (const char *gecos)
+{
+ char *cp; /* temporary character pointer */
+ char old_gecos[BUFSIZ]; /* buffer for old GECOS fields */
+ STRFCPY (old_gecos, gecos);
+
+ /*
+ * Now get the full name. It is the first comma separated field in
+ * the GECOS field.
+ */
+ cp = copy_field (old_gecos, fflg ? (char *) 0 : fullnm, slop);
+
+ /*
+ * Now get the room number. It is the next comma separated field,
+ * if there is indeed one.
+ */
+ if (NULL != cp) {
+ cp = copy_field (cp, rflg ? (char *) 0 : roomno, slop);
+ }
+
+ /*
+ * Now get the work phone number. It is the third field.
+ */
+ if (NULL != cp) {
+ cp = copy_field (cp, wflg ? (char *) 0 : workph, slop);
+ }
+
+ /*
+ * Now get the home phone number. It is the fourth field.
+ */
+ if (NULL != cp) {
+ cp = copy_field (cp, hflg ? (char *) 0 : homeph, slop);
+ }
+
+ /*
+ * Anything left over is "slop".
+ */
+ if ((NULL != cp) && !oflg) {
+ if ('\0' != slop[0]) {
+ strcat (slop, ",");
+ }
+
+ strcat (slop, cp);
+ }
+}
+
+/*
+ * check_fields - check all of the fields for valid information
+ *
+ * It will not return if a field is not valid.
+ */
+static void check_fields (void)
+{
+ int err;
+ err = valid_field (fullnm, ":,=\n");
+ if (err > 0) {
+ fprintf (stderr, _("%s: name with non-ASCII characters: '%s'\n"), Prog, fullnm);
+ } else if (err < 0) {
+ fprintf (stderr, _("%s: invalid name: '%s'\n"), Prog, fullnm);
+ fail_exit (E_NOPERM);
+ }
+ err = valid_field (roomno, ":,=\n");
+ if (err > 0) {
+ fprintf (stderr, _("%s: room number with non-ASCII characters: '%s'\n"), Prog, roomno);
+ } else if (err < 0) {
+ fprintf (stderr, _("%s: invalid room number: '%s'\n"),
+ Prog, roomno);
+ fail_exit (E_NOPERM);
+ }
+ if (valid_field (workph, ":,=\n") != 0) {
+ fprintf (stderr, _("%s: invalid work phone: '%s'\n"),
+ Prog, workph);
+ fail_exit (E_NOPERM);
+ }
+ if (valid_field (homeph, ":,=\n") != 0) {
+ fprintf (stderr, _("%s: invalid home phone: '%s'\n"),
+ Prog, homeph);
+ fail_exit (E_NOPERM);
+ }
+ err = valid_field (slop, ":\n");
+ if (err > 0) {
+ fprintf (stderr, _("%s: '%s' contains non-ASCII characters\n"), Prog, slop);
+ } else if (err < 0) {
+ fprintf (stderr,
+ _("%s: '%s' contains illegal characters\n"),
+ Prog, slop);
+ fail_exit (E_NOPERM);
+ }
+}
+
+/*
+ * chfn - change a user's password file information
+ *
+ * This command controls the GECOS field information in the password
+ * file entry.
+ *
+ * The valid options are
+ *
+ * -f full name
+ * -r room number
+ * -w work phone number
+ * -h home phone number
+ * -o other information (*)
+ *
+ * (*) requires root permission to execute.
+ */
+int main (int argc, char **argv)
+{
+ const struct passwd *pw; /* password file entry */
+ char new_gecos[BUFSIZ]; /* buffer for new GECOS fields */
+ char *user;
+
+ /*
+ * Get the program name. The program name is used as a
+ * prefix to most error messages.
+ */
+ Prog = Basename (argv[0]);
+
+ sanitize_env ();
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ /*
+ * This command behaves different for root and non-root
+ * users.
+ */
+ amroot = (getuid () == 0);
+
+ OPENLOG ("chfn");
+
+ /* parse the command line options */
+ process_flags (argc, argv);
+
+ /*
+ * Get the name of the user to check. It is either the command line
+ * name, or the name getlogin() returns.
+ */
+ if (optind < argc) {
+ user = argv[optind];
+ pw = xgetpwnam (user);
+ if (NULL == pw) {
+ fprintf (stderr, _("%s: user '%s' does not exist\n"), Prog,
+ user);
+ fail_exit (E_NOPERM);
+ }
+ } else {
+ pw = get_my_pwent ();
+ if (NULL == pw) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
+ (unsigned long) getuid ()));
+ fail_exit (E_NOPERM);
+ }
+ user = xstrdup (pw->pw_name);
+ }
+
+#ifdef USE_NIS
+ /*
+ * Now we make sure this is a LOCAL password entry for this user ...
+ */
+ if (__ispwNIS ()) {
+ char *nis_domain;
+ char *nis_master;
+
+ fprintf (stderr,
+ _("%s: cannot change user '%s' on NIS client.\n"),
+ Prog, user);
+
+ if (!yp_get_default_domain (&nis_domain) &&
+ !yp_master (nis_domain, "passwd.byname", &nis_master)) {
+ fprintf (stderr,
+ _
+ ("%s: '%s' is the NIS master for this client.\n"),
+ Prog, nis_master);
+ }
+ fail_exit (E_NOPERM);
+ }
+#endif
+
+ /* Check that the caller is allowed to change the gecos of the
+ * specified user */
+ check_perms (pw);
+
+ /* If some fields were not set on the command line, load the value from
+ * the old gecos fields. */
+ get_old_fields (pw->pw_gecos);
+
+ /*
+ * If none of the fields were changed from the command line, let the
+ * user interactively change them.
+ */
+ if (!fflg && !rflg && !wflg && !hflg && !oflg) {
+ printf (_("Changing the user information for %s\n"), user);
+ new_fields ();
+ }
+
+ /*
+ * Check all of the fields for valid information
+ */
+ check_fields ();
+
+ /*
+ * Build the new GECOS field by plastering all the pieces together,
+ * if they will fit ...
+ */
+ if ((strlen (fullnm) + strlen (roomno) + strlen (workph) +
+ strlen (homeph) + strlen (slop)) > (unsigned int) 80) {
+ fprintf (stderr, _("%s: fields too long\n"), Prog);
+ fail_exit (E_NOPERM);
+ }
+ snprintf (new_gecos, sizeof new_gecos, "%s,%s,%s,%s%s%s",
+ fullnm, roomno, workph, homeph,
+ ('\0' != slop[0]) ? "," : "", slop);
+
+ /* Rewrite the user's gecos in the passwd file */
+ update_gecos (user, new_gecos);
+
+ SYSLOG ((LOG_INFO, "changed user '%s' information", user));
+
+ nscd_flush_cache ("passwd");
+
+ closelog ();
+ exit (E_SUCCESS);
+}
+
diff --git a/src/chgpasswd.c b/src/chgpasswd.c
new file mode 100644
index 0000000..13203a4
--- /dev/null
+++ b/src/chgpasswd.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (c) 1990 - 1994, Julianne Frances Haugh
+ * Copyright (c) 2006 , Tomasz Kłoczko
+ * Copyright (c) 2006 , Jonas Meurer
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+#include "defines.h"
+#include "nscd.h"
+#include "prototypes.h"
+#include "groupio.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+/*
+ * Global variables
+ */
+const char *Prog;
+static bool eflg = false;
+static bool md5flg = false;
+#ifdef USE_SHA_CRYPT
+static bool sflg = false;
+#endif
+
+static /*@null@*//*@observer@*/const char *crypt_method = NULL;
+#define cflg (NULL != crypt_method)
+#ifdef USE_SHA_CRYPT
+static long sha_rounds = 5000;
+#endif
+
+#ifdef SHADOWGRP
+static bool is_shadow_grp;
+static bool sgr_locked = false;
+#endif
+static bool gr_locked = false;
+
+/* local function prototypes */
+static void fail_exit (int code);
+static /*@noreturn@*/void usage (int status);
+static void process_flags (int argc, char **argv);
+static void check_flags (void);
+static void check_perms (void);
+static void open_files (void);
+static void close_files (void);
+
+/*
+ * fail_exit - exit with a failure code after unlocking the files
+ */
+static void fail_exit (int code)
+{
+ if (gr_locked) {
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+ }
+
+#ifdef SHADOWGRP
+ if (sgr_locked) {
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+ }
+#endif
+
+ exit (code);
+}
+
+/*
+ * usage - display usage message and exit
+ */
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fprintf (usageout,
+ _(" -c, --crypt-method METHOD the crypt method (one of %s)\n"),
+#ifndef USE_SHA_CRYPT
+ "NONE DES MD5"
+#else /* USE_SHA_CRYPT */
+ "NONE DES MD5 SHA256 SHA512"
+#endif /* USE_SHA_CRYPT */
+ );
+ (void) fputs (_(" -e, --encrypted supplied passwords are encrypted\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -m, --md5 encrypt the clear text password using\n"
+ " the MD5 algorithm\n"),
+ usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+#ifdef USE_SHA_CRYPT
+ (void) fputs (_(" -s, --sha-rounds number of SHA rounds for the SHA*\n"
+ " crypt algorithms\n"),
+ usageout);
+#endif /* USE_SHA_CRYPT */
+ (void) fputs ("\n", usageout);
+
+ exit (status);
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ int c;
+ static struct option long_options[] = {
+ {"crypt-method", required_argument, NULL, 'c'},
+ {"encrypted", no_argument, NULL, 'e'},
+ {"help", no_argument, NULL, 'h'},
+ {"md5", no_argument, NULL, 'm'},
+ {"root", required_argument, NULL, 'R'},
+#ifdef USE_SHA_CRYPT
+ {"sha-rounds", required_argument, NULL, 's'},
+#endif
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv,
+#ifdef USE_SHA_CRYPT
+ "c:ehmR:s:",
+#else
+ "c:ehmR:",
+#endif
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'c':
+ crypt_method = optarg;
+ break;
+ case 'e':
+ eflg = true;
+ break;
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'm':
+ md5flg = true;
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+#ifdef USE_SHA_CRYPT
+ case 's':
+ sflg = true;
+ if (getlong(optarg, &sha_rounds) == 0) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ usage (E_USAGE);
+ }
+ break;
+#endif
+ default:
+ usage (E_USAGE);
+ /*@notreached@*/break;
+ }
+ }
+
+ /* validate options */
+ check_flags ();
+}
+
+/*
+ * check_flags - check flags and parameters consistency
+ *
+ * It will not return if an error is encountered.
+ */
+static void check_flags (void)
+{
+#ifdef USE_SHA_CRYPT
+ if (sflg && !cflg) {
+ fprintf (stderr,
+ _("%s: %s flag is only allowed with the %s flag\n"),
+ Prog, "-s", "-c");
+ usage (E_USAGE);
+ }
+#endif
+
+ if ((eflg && (md5flg || cflg)) ||
+ (md5flg && cflg)) {
+ fprintf (stderr,
+ _("%s: the -c, -e, and -m flags are exclusive\n"),
+ Prog);
+ usage (E_USAGE);
+ }
+
+ if (cflg) {
+ if ( (0 != strcmp (crypt_method, "DES"))
+ && (0 != strcmp (crypt_method, "MD5"))
+ && (0 != strcmp (crypt_method, "NONE"))
+#ifdef USE_SHA_CRYPT
+ && (0 != strcmp (crypt_method, "SHA256"))
+ && (0 != strcmp (crypt_method, "SHA512"))
+#endif
+ ) {
+ fprintf (stderr,
+ _("%s: unsupported crypt method: %s\n"),
+ Prog, crypt_method);
+ usage (E_USAGE);
+ }
+ }
+}
+
+/*
+ * check_perms - check if the caller is allowed to add a group
+ *
+ * With PAM support, the setuid bit can be set on chgpasswd to allow
+ * non-root users to groups.
+ * Without PAM support, only users who can write in the group databases
+ * can add groups.
+ *
+ * It will not return if the user is not allowed.
+ */
+static void check_perms (void)
+{
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ pam_handle_t *pamh = NULL;
+ int retval;
+ struct passwd *pampw;
+
+ pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+ if (NULL == pampw) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ exit (1);
+ }
+
+ retval = pam_start ("chgpasswd", pampw->pw_name, &conv, &pamh);
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_authenticate (pamh, 0);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_acct_mgmt (pamh, 0);
+ }
+
+ if (PAM_SUCCESS != retval) {
+ fprintf (stderr, _("%s: PAM: %s\n"),
+ Prog, pam_strerror (pamh, retval));
+ SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval)));
+ if (NULL != pamh) {
+ (void) pam_end (pamh, retval);
+ }
+ exit (1);
+ }
+ (void) pam_end (pamh, retval);
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+}
+
+/*
+ * open_files - lock and open the group databases
+ */
+static void open_files (void)
+{
+ /*
+ * Lock the group file and open it for reading and writing. This will
+ * bring all of the entries into memory where they may be updated.
+ */
+ if (gr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, gr_dbname ());
+ fail_exit (1);
+ }
+ gr_locked = true;
+ if (gr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"), Prog, gr_dbname ());
+ fail_exit (1);
+ }
+
+#ifdef SHADOWGRP
+ /* Do the same for the shadowed database, if it exist */
+ if (is_shadow_grp) {
+ if (sgr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sgr_dbname ());
+ fail_exit (1);
+ }
+ sgr_locked = true;
+ if (sgr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"),
+ Prog, sgr_dbname ());
+ fail_exit (1);
+ }
+ }
+#endif
+}
+
+/*
+ * close_files - close and unlock the group databases
+ */
+static void close_files (void)
+{
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", sgr_dbname ()));
+ fail_exit (1);
+ }
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+ sgr_locked = false;
+ }
+#endif
+
+ if (gr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", gr_dbname ()));
+ fail_exit (1);
+ }
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+ gr_locked = false;
+}
+
+int main (int argc, char **argv)
+{
+ char buf[BUFSIZ];
+ char *name;
+ char *newpwd;
+ char *cp;
+
+#ifdef SHADOWGRP
+ const struct sgrp *sg;
+ struct sgrp newsg;
+#endif
+
+ const struct group *gr;
+ struct group newgr;
+ int errors = 0;
+ int line = 0;
+
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ process_flags (argc, argv);
+
+ OPENLOG ("chgpasswd");
+
+ check_perms ();
+
+#ifdef SHADOWGRP
+ is_shadow_grp = sgr_file_present ();
+#endif
+
+ open_files ();
+
+ /*
+ * Read each line, separating the group name from the password. The
+ * group entry for each group will be looked up in the appropriate
+ * file (gshadow or group) and the password changed.
+ */
+ while (fgets (buf, (int) sizeof buf, stdin) != (char *) 0) {
+ line++;
+ cp = strrchr (buf, '\n');
+ if (NULL != cp) {
+ *cp = '\0';
+ } else {
+ fprintf (stderr, _("%s: line %d: line too long\n"),
+ Prog, line);
+ errors++;
+ continue;
+ }
+
+ /*
+ * The group's name is the first field. It is separated from
+ * the password with a ":" character which is replaced with a
+ * NUL to give the new password. The new password will then
+ * be encrypted in the normal fashion with a new salt
+ * generated, unless the '-e' is given, in which case it is
+ * assumed to already be encrypted.
+ */
+
+ name = buf;
+ cp = strchr (name, ':');
+ if (NULL != cp) {
+ *cp = '\0';
+ cp++;
+ } else {
+ fprintf (stderr,
+ _("%s: line %d: missing new password\n"),
+ Prog, line);
+ errors++;
+ continue;
+ }
+ newpwd = cp;
+ if ( (!eflg)
+ && ( (NULL == crypt_method)
+ || (0 != strcmp (crypt_method, "NONE")))) {
+ void *arg = NULL;
+ const char *salt;
+ if (md5flg) {
+ crypt_method = "MD5";
+ }
+#ifdef USE_SHA_CRYPT
+ if (sflg) {
+ arg = &sha_rounds;
+ }
+#endif
+ salt = crypt_make_salt (crypt_method, arg);
+ cp = pw_encrypt (newpwd, salt);
+ if (NULL == cp) {
+ fprintf (stderr,
+ _("%s: failed to crypt password with salt '%s': %s\n"),
+ Prog, salt, strerror (errno));
+ fail_exit (1);
+ }
+ }
+
+ /*
+ * Get the group file entry for this group. The group must
+ * already exist.
+ */
+ gr = gr_locate (name);
+ if (NULL == gr) {
+ fprintf (stderr,
+ _("%s: line %d: group '%s' does not exist\n"), Prog,
+ line, name);
+ errors++;
+ continue;
+ }
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ /* The gshadow entry should be updated if the
+ * group entry has a password set to 'x'.
+ * But on the other hand, if there is already both
+ * a group and a gshadow password, it's preferable
+ * to update both.
+ */
+ sg = sgr_locate (name);
+
+ if ( (NULL == sg)
+ && (strcmp (gr->gr_passwd,
+ SHADOW_PASSWD_STRING) == 0)) {
+ static char *empty = NULL;
+ /* If the password is set to 'x' in
+ * group, but there are no entries in
+ * gshadow, create one.
+ */
+ newsg.sg_name = name;
+ /* newsg.sg_passwd = NULL; will be set later */
+ newsg.sg_adm = &empty;
+ newsg.sg_mem = dup_list (gr->gr_mem);
+ sg = &newsg;
+ }
+ } else {
+ sg = NULL;
+ }
+#endif
+
+ /*
+ * The freshly encrypted new password is merged into the
+ * group's entry.
+ */
+#ifdef SHADOWGRP
+ if (NULL != sg) {
+ newsg = *sg;
+ newsg.sg_passwd = cp;
+ }
+ if ( (NULL == sg)
+ || (strcmp (gr->gr_passwd, SHADOW_PASSWD_STRING) != 0))
+#endif
+ {
+ newgr = *gr;
+ newgr.gr_passwd = cp;
+ }
+
+ /*
+ * The updated group file entry is then put back and will
+ * be written to the group file later, after all the
+ * other entries have been updated as well.
+ */
+#ifdef SHADOWGRP
+ if (NULL != sg) {
+ if (sgr_update (&newsg) == 0) {
+ fprintf (stderr,
+ _("%s: line %d: failed to prepare the new %s entry '%s'\n"),
+ Prog, line, sgr_dbname (), newsg.sg_name);
+ errors++;
+ continue;
+ }
+ }
+ if ( (NULL == sg)
+ || (strcmp (gr->gr_passwd, SHADOW_PASSWD_STRING) != 0))
+#endif
+ {
+ if (gr_update (&newgr) == 0) {
+ fprintf (stderr,
+ _("%s: line %d: failed to prepare the new %s entry '%s'\n"),
+ Prog, line, gr_dbname (), newgr.gr_name);
+ errors++;
+ continue;
+ }
+ }
+ }
+
+ /*
+ * Any detected errors will cause the entire set of changes to be
+ * aborted. Unlocking the group file will cause all of the
+ * changes to be ignored. Otherwise the file is closed, causing the
+ * changes to be written out all at once, and then unlocked
+ * afterwards.
+ */
+ if (0 != errors) {
+ fprintf (stderr,
+ _("%s: error detected, changes ignored\n"), Prog);
+ fail_exit (1);
+ }
+
+ close_files ();
+
+ nscd_flush_cache ("group");
+
+ return (0);
+}
+
diff --git a/src/chpasswd.c b/src/chpasswd.c
new file mode 100644
index 0000000..f985672
--- /dev/null
+++ b/src/chpasswd.c
@@ -0,0 +1,630 @@
+/*
+ * Copyright (c) 1990 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif /* USE_PAM */
+#include "defines.h"
+#include "nscd.h"
+#include "getdef.h"
+#include "prototypes.h"
+#include "pwio.h"
+#include "shadowio.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+/*
+ * Global variables
+ */
+const char *Prog;
+static bool eflg = false;
+static bool md5flg = false;
+#ifdef USE_SHA_CRYPT
+static bool sflg = false;
+#endif /* USE_SHA_CRYPT */
+
+static /*@null@*//*@observer@*/const char *crypt_method = NULL;
+#define cflg (NULL != crypt_method)
+#ifdef USE_SHA_CRYPT
+static long sha_rounds = 5000;
+#endif /* USE_SHA_CRYPT */
+
+static bool is_shadow_pwd;
+static bool pw_locked = false;
+static bool spw_locked = false;
+
+/* local function prototypes */
+static void fail_exit (int code);
+static /*@noreturn@*/void usage (int status);
+static void process_flags (int argc, char **argv);
+static void check_flags (void);
+static void check_perms (void);
+static void open_files (void);
+static void close_files (void);
+
+/*
+ * fail_exit - exit with a failure code after unlocking the files
+ */
+static void fail_exit (int code)
+{
+ if (pw_locked) {
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ }
+
+ if (spw_locked) {
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ }
+
+ exit (code);
+}
+
+/*
+ * usage - display usage message and exit
+ */
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fprintf (usageout,
+ _(" -c, --crypt-method METHOD the crypt method (one of %s)\n"),
+#ifndef USE_SHA_CRYPT
+ "NONE DES MD5"
+#else /* USE_SHA_CRYPT */
+ "NONE DES MD5 SHA256 SHA512"
+#endif /* USE_SHA_CRYPT */
+ );
+ (void) fputs (_(" -e, --encrypted supplied passwords are encrypted\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -m, --md5 encrypt the clear text password using\n"
+ " the MD5 algorithm\n"),
+ usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+#ifdef USE_SHA_CRYPT
+ (void) fputs (_(" -s, --sha-rounds number of SHA rounds for the SHA*\n"
+ " crypt algorithms\n"),
+ usageout);
+#endif /* USE_SHA_CRYPT */
+ (void) fputs ("\n", usageout);
+
+ exit (status);
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ int c;
+ static struct option long_options[] = {
+ {"crypt-method", required_argument, NULL, 'c'},
+ {"encrypted", no_argument, NULL, 'e'},
+ {"help", no_argument, NULL, 'h'},
+ {"md5", no_argument, NULL, 'm'},
+ {"root", required_argument, NULL, 'R'},
+#ifdef USE_SHA_CRYPT
+ {"sha-rounds", required_argument, NULL, 's'},
+#endif /* USE_SHA_CRYPT */
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv,
+#ifdef USE_SHA_CRYPT
+ "c:ehmR:s:",
+#else /* !USE_SHA_CRYPT */
+ "c:ehmR:",
+#endif /* !USE_SHA_CRYPT */
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'c':
+ crypt_method = optarg;
+ break;
+ case 'e':
+ eflg = true;
+ break;
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'm':
+ md5flg = true;
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+#ifdef USE_SHA_CRYPT
+ case 's':
+ sflg = true;
+ if (getlong(optarg, &sha_rounds) == 0) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ usage (E_USAGE);
+ }
+ break;
+#endif /* USE_SHA_CRYPT */
+ default:
+ usage (E_USAGE);
+ /*@notreached@*/break;
+ }
+ }
+
+ /* validate options */
+ check_flags ();
+}
+
+/*
+ * check_flags - check flags and parameters consistency
+ *
+ * It will not return if an error is encountered.
+ */
+static void check_flags (void)
+{
+#ifdef USE_SHA_CRYPT
+ if (sflg && !cflg) {
+ fprintf (stderr,
+ _("%s: %s flag is only allowed with the %s flag\n"),
+ Prog, "-s", "-c");
+ usage (E_USAGE);
+ }
+#endif
+
+ if ((eflg && (md5flg || cflg)) ||
+ (md5flg && cflg)) {
+ fprintf (stderr,
+ _("%s: the -c, -e, and -m flags are exclusive\n"),
+ Prog);
+ usage (E_USAGE);
+ }
+
+ if (cflg) {
+ if ( (0 != strcmp (crypt_method, "DES"))
+ && (0 != strcmp (crypt_method, "MD5"))
+ && (0 != strcmp (crypt_method, "NONE"))
+#ifdef USE_SHA_CRYPT
+ && (0 != strcmp (crypt_method, "SHA256"))
+ && (0 != strcmp (crypt_method, "SHA512"))
+#endif /* USE_SHA_CRYPT */
+ ) {
+ fprintf (stderr,
+ _("%s: unsupported crypt method: %s\n"),
+ Prog, crypt_method);
+ usage (E_USAGE);
+ }
+ }
+}
+
+/*
+ * check_perms - check if the caller is allowed to add a group
+ *
+ * With PAM support, the setuid bit can be set on chpasswd to allow
+ * non-root users to groups.
+ * Without PAM support, only users who can write in the group databases
+ * can add groups.
+ *
+ * It will not return if the user is not allowed.
+ */
+static void check_perms (void)
+{
+#ifdef USE_PAM
+#ifdef ACCT_TOOLS_SETUID
+ /* If chpasswd uses PAM and is SUID, check the permissions,
+ * otherwise, the permissions are enforced by the access to the
+ * passwd and shadow files.
+ */
+ pam_handle_t *pamh = NULL;
+ int retval;
+ struct passwd *pampw;
+
+ pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+ if (NULL == pampw) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ exit (1);
+ }
+
+ retval = pam_start ("chpasswd", pampw->pw_name, &conv, &pamh);
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_authenticate (pamh, 0);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_acct_mgmt (pamh, 0);
+ }
+
+ if (PAM_SUCCESS != retval) {
+ fprintf (stderr, _("%s: PAM: %s\n"),
+ Prog, pam_strerror (pamh, retval));
+ SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval)));
+ if (NULL != pamh) {
+ (void) pam_end (pamh, retval);
+ }
+ exit (1);
+ }
+ (void) pam_end (pamh, retval);
+#endif /* ACCT_TOOLS_SETUID */
+#endif /* USE_PAM */
+}
+
+/*
+ * open_files - lock and open the password databases
+ */
+static void open_files (void)
+{
+ /*
+ * Lock the password file and open it for reading and writing. This
+ * will bring all of the entries into memory where they may be updated.
+ */
+ if (pw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, pw_dbname ());
+ fail_exit (1);
+ }
+ pw_locked = true;
+ if (pw_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"), Prog, pw_dbname ());
+ fail_exit (1);
+ }
+
+ /* Do the same for the shadowed database, if it exist */
+ if (is_shadow_pwd) {
+ if (spw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, spw_dbname ());
+ fail_exit (1);
+ }
+ spw_locked = true;
+ if (spw_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, spw_dbname ());
+ fail_exit (1);
+ }
+ }
+}
+
+/*
+ * close_files - close and unlock the password databases
+ */
+static void close_files (void)
+{
+ if (is_shadow_pwd) {
+ if (spw_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ()));
+ fail_exit (1);
+ }
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ spw_locked = false;
+ }
+
+ if (pw_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
+ fail_exit (1);
+ }
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ pw_locked = false;
+}
+
+int main (int argc, char **argv)
+{
+ char buf[BUFSIZ];
+ char *name;
+ char *newpwd;
+ char *cp;
+
+#ifdef USE_PAM
+ bool use_pam = true;
+#endif /* USE_PAM */
+
+ int errors = 0;
+ int line = 0;
+
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ process_flags (argc, argv);
+
+#ifdef USE_PAM
+ if (md5flg || eflg || cflg) {
+ use_pam = false;
+ }
+#endif /* USE_PAM */
+
+ OPENLOG ("chpasswd");
+
+ check_perms ();
+
+#ifdef USE_PAM
+ if (!use_pam)
+#endif /* USE_PAM */
+ {
+ is_shadow_pwd = spw_file_present ();
+
+ open_files ();
+ }
+
+ /*
+ * Read each line, separating the user name from the password. The
+ * password entry for each user will be looked up in the appropriate
+ * file (shadow or passwd) and the password changed. For shadow
+ * files the last change date is set directly, for passwd files the
+ * last change date is set in the age only if aging information is
+ * present.
+ */
+ while (fgets (buf, (int) sizeof buf, stdin) != (char *) 0) {
+ line++;
+ cp = strrchr (buf, '\n');
+ if (NULL != cp) {
+ *cp = '\0';
+ } else {
+ if (feof (stdin) == 0) {
+ fprintf (stderr,
+ _("%s: line %d: line too long\n"),
+ Prog, line);
+ errors++;
+ continue;
+ }
+ }
+
+ /*
+ * The username is the first field. It is separated from the
+ * password with a ":" character which is replaced with a
+ * NUL to give the new password. The new password will then
+ * be encrypted in the normal fashion with a new salt
+ * generated, unless the '-e' is given, in which case it is
+ * assumed to already be encrypted.
+ */
+
+ name = buf;
+ cp = strchr (name, ':');
+ if (NULL != cp) {
+ *cp = '\0';
+ cp++;
+ } else {
+ fprintf (stderr,
+ _("%s: line %d: missing new password\n"),
+ Prog, line);
+ errors++;
+ continue;
+ }
+ newpwd = cp;
+
+#ifdef USE_PAM
+ if (use_pam){
+ if (do_pam_passwd_non_interractive ("chpasswd", name, newpwd) != 0) {
+ fprintf (stderr,
+ _("%s: (line %d, user %s) password not changed\n"),
+ Prog, line, name);
+ errors++;
+ }
+ } else
+#endif /* USE_PAM */
+ {
+ const struct spwd *sp;
+ struct spwd newsp;
+ const struct passwd *pw;
+ struct passwd newpw;
+
+ if ( !eflg
+ && ( (NULL == crypt_method)
+ || (0 != strcmp (crypt_method, "NONE")))) {
+ void *arg = NULL;
+ const char *salt;
+ if (md5flg) {
+ crypt_method = "MD5";
+ }
+#ifdef USE_SHA_CRYPT
+ if (sflg) {
+ arg = &sha_rounds;
+ }
+#endif
+ salt = crypt_make_salt (crypt_method, arg);
+ cp = pw_encrypt (newpwd, salt);
+ if (NULL == cp) {
+ fprintf (stderr,
+ _("%s: failed to crypt password with salt '%s': %s\n"),
+ Prog, salt, strerror (errno));
+ fail_exit (1);
+ }
+ }
+
+ /*
+ * Get the password file entry for this user. The user must
+ * already exist.
+ */
+ pw = pw_locate (name);
+ if (NULL == pw) {
+ fprintf (stderr,
+ _("%s: line %d: user '%s' does not exist\n"), Prog,
+ line, name);
+ errors++;
+ continue;
+ }
+ if (is_shadow_pwd) {
+ /* The shadow entry should be updated if the
+ * passwd entry has a password set to 'x'.
+ * But on the other hand, if there is already both
+ * a passwd and a shadow password, it's preferable
+ * to update both.
+ */
+ sp = spw_locate (name);
+
+ if ( (NULL == sp)
+ && (strcmp (pw->pw_passwd,
+ SHADOW_PASSWD_STRING) == 0)) {
+ /* If the password is set to 'x' in
+ * passwd, but there are no entries in
+ * shadow, create one.
+ */
+ newsp.sp_namp = name;
+ /* newsp.sp_pwdp = NULL; will be set later */
+ /* newsp.sp_lstchg= 0; will be set later */
+ newsp.sp_min = getdef_num ("PASS_MIN_DAYS", -1);
+ newsp.sp_max = getdef_num ("PASS_MAX_DAYS", -1);
+ newsp.sp_warn = getdef_num ("PASS_WARN_AGE", -1);
+ newsp.sp_inact = -1;
+ newsp.sp_expire= -1;
+ newsp.sp_flag = SHADOW_SP_FLAG_UNSET;
+ sp = &newsp;
+ }
+ } else {
+ sp = NULL;
+ }
+
+ /*
+ * The freshly encrypted new password is merged into the
+ * user's password file entry and the last password change
+ * date is set to the current date.
+ */
+ if (NULL != sp) {
+ newsp = *sp;
+ newsp.sp_pwdp = cp;
+ newsp.sp_lstchg = (long) gettime () / SCALE;
+ if (0 == newsp.sp_lstchg) {
+ /* Better disable aging than requiring a
+ * password change */
+ newsp.sp_lstchg = -1;
+ }
+ }
+
+ if ( (NULL == sp)
+ || (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) != 0)) {
+ newpw = *pw;
+ newpw.pw_passwd = cp;
+ }
+
+ /*
+ * The updated password file entry is then put back and will
+ * be written to the password file later, after all the
+ * other entries have been updated as well.
+ */
+ if (NULL != sp) {
+ if (spw_update (&newsp) == 0) {
+ fprintf (stderr,
+ _("%s: line %d: failed to prepare the new %s entry '%s'\n"),
+ Prog, line, spw_dbname (), newsp.sp_namp);
+ errors++;
+ continue;
+ }
+ }
+ if ( (NULL == sp)
+ || (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) != 0)) {
+ if (pw_update (&newpw) == 0) {
+ fprintf (stderr,
+ _("%s: line %d: failed to prepare the new %s entry '%s'\n"),
+ Prog, line, pw_dbname (), newpw.pw_name);
+ errors++;
+ continue;
+ }
+ }
+ }
+ }
+
+ /*
+ * Any detected errors will cause the entire set of changes to be
+ * aborted. Unlocking the password file will cause all of the
+ * changes to be ignored. Otherwise the file is closed, causing the
+ * changes to be written out all at once, and then unlocked
+ * afterwards.
+ *
+ * With PAM, it is not possible to delay the update of the
+ * password database.
+ */
+ if (0 != errors) {
+#ifdef USE_PAM
+ if (!use_pam)
+#endif /* USE_PAM */
+ {
+ fprintf (stderr,
+ _("%s: error detected, changes ignored\n"),
+ Prog);
+ }
+ fail_exit (1);
+ }
+
+#ifdef USE_PAM
+ if (!use_pam)
+#endif /* USE_PAM */
+ {
+ /* Save the changes */
+ close_files ();
+ }
+
+ nscd_flush_cache ("passwd");
+
+ return (0);
+}
+
diff --git a/src/chsh.c b/src/chsh.c
new file mode 100644
index 0000000..c89708b
--- /dev/null
+++ b/src/chsh.c
@@ -0,0 +1,564 @@
+/*
+ * Copyright (c) 1989 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2001 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#include <selinux/av_permissions.h>
+#endif
+#include "defines.h"
+#include "getdef.h"
+#include "nscd.h"
+#include "prototypes.h"
+#include "pwauth.h"
+#include "pwio.h"
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+#ifndef SHELLS_FILE
+#define SHELLS_FILE "/etc/shells"
+#endif
+/*
+ * Global variables
+ */
+const char *Prog; /* Program name */
+static bool amroot; /* Real UID is root */
+static char loginsh[BUFSIZ]; /* Name of new login shell */
+/* command line options */
+static bool sflg = false; /* -s - set shell from command line */
+static bool pw_locked = false;
+
+/* external identifiers */
+
+/* local function prototypes */
+static /*@noreturn@*/void fail_exit (int code);
+static /*@noreturn@*/void usage (int status);
+static void new_fields (void);
+static bool shell_is_listed (const char *);
+static bool is_restricted_shell (const char *);
+static void process_flags (int argc, char **argv);
+static void check_perms (const struct passwd *pw);
+static void update_shell (const char *user, char *loginsh);
+
+/*
+ * fail_exit - do some cleanup and exit with the given error code
+ */
+static /*@noreturn@*/void fail_exit (int code)
+{
+ if (pw_locked) {
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ }
+
+ closelog ();
+
+ exit (code);
+}
+
+/*
+ * usage - print command line syntax and exit
+ */
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options] [LOGIN]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs (_(" -s, --shell SHELL new login shell for the user account\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * new_fields - change the user's login shell information interactively
+ *
+ * prompt the user for the login shell and change it according to the
+ * response, or leave it alone if nothing was entered.
+ */
+static void new_fields (void)
+{
+ puts (_("Enter the new value, or press ENTER for the default"));
+ change_field (loginsh, sizeof loginsh, _("Login Shell"));
+}
+
+/*
+ * is_restricted_shell - return true if the shell is restricted
+ *
+ */
+static bool is_restricted_shell (const char *sh)
+{
+ /*
+ * Shells not listed in /etc/shells are considered to be restricted.
+ * Changed this to avoid confusion with "rc" (the plan9 shell - not
+ * restricted despite the name starting with 'r'). --marekm
+ */
+ return !shell_is_listed (sh);
+}
+
+/*
+ * shell_is_listed - see if the user's login shell is listed in /etc/shells
+ *
+ * The /etc/shells file is read for valid names of login shells. If the
+ * /etc/shells file does not exist the user cannot set any shell unless
+ * they are root.
+ *
+ * If getusershell() is available (Linux, *BSD, possibly others), use it
+ * instead of re-implementing it.
+ */
+static bool shell_is_listed (const char *sh)
+{
+ char *cp;
+ bool found = false;
+
+#ifndef HAVE_GETUSERSHELL
+ char buf[BUFSIZ];
+ FILE *fp;
+#endif
+
+#ifdef HAVE_GETUSERSHELL
+ setusershell ();
+ while ((cp = getusershell ())) {
+ if (strcmp (cp, sh) == 0) {
+ found = true;
+ break;
+ }
+ }
+ endusershell ();
+#else
+ fp = fopen (SHELLS_FILE, "r");
+ if (NULL == fp) {
+ return false;
+ }
+
+ while (fgets (buf, sizeof (buf), fp) == buf) {
+ cp = strrchr (buf, '\n');
+ if (NULL != cp) {
+ *cp = '\0';
+ }
+
+ if (buf[0] == '#') {
+ continue;
+ }
+
+ if (strcmp (buf, sh) == 0) {
+ found = true;
+ break;
+ }
+ }
+ fclose (fp);
+#endif
+ return found;
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ int c;
+ static struct option long_options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"root", required_argument, NULL, 'R'},
+ {"shell", required_argument, NULL, 's'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "hR:s:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ case 's':
+ sflg = true;
+ STRFCPY (loginsh, optarg);
+ break;
+ default:
+ usage (E_USAGE);
+ }
+ }
+
+ /*
+ * There should be only one remaining argument at most and it should
+ * be the user's name.
+ */
+ if (argc > (optind + 1)) {
+ usage (E_USAGE);
+ }
+}
+
+/*
+ * check_perms - check if the caller is allowed to add a group
+ *
+ * Non-root users are only allowed to change their shell, if their current
+ * shell is not a restricted shell.
+ *
+ * Non-root users must be authenticated.
+ *
+ * It will not return if the user is not allowed.
+ */
+static void check_perms (const struct passwd *pw)
+{
+#ifdef USE_PAM
+ pam_handle_t *pamh = NULL;
+ int retval;
+ struct passwd *pampw;
+#endif
+
+ /*
+ * Non-privileged users are only allowed to change the shell if the
+ * UID of the user matches the current real UID.
+ */
+ if (!amroot && pw->pw_uid != getuid ()) {
+ SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
+ fprintf (stderr,
+ _("You may not change the shell for '%s'.\n"),
+ pw->pw_name);
+ fail_exit (1);
+ }
+
+ /*
+ * Non-privileged users are only allowed to change the shell if it
+ * is not a restricted one.
+ */
+ if (!amroot && is_restricted_shell (pw->pw_shell)) {
+ SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
+ fprintf (stderr,
+ _("You may not change the shell for '%s'.\n"),
+ pw->pw_name);
+ fail_exit (1);
+ }
+#ifdef WITH_SELINUX
+ /*
+ * If the UID of the user does not match the current real UID,
+ * check if the change is allowed by SELinux policy.
+ */
+ if ((pw->pw_uid != getuid ())
+ && (is_selinux_enabled () > 0)
+ && (selinux_check_passwd_access (PASSWD__CHSH) != 0)) {
+ SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
+ fprintf (stderr,
+ _("You may not change the shell for '%s'.\n"),
+ pw->pw_name);
+ fail_exit (1);
+ }
+#endif
+
+#ifndef USE_PAM
+ /*
+ * Non-privileged users are optionally authenticated (must enter
+ * the password of the user whose information is being changed)
+ * before any changes can be made. Idea from util-linux
+ * chfn/chsh. --marekm
+ */
+ if (!amroot && getdef_bool ("CHSH_AUTH")) {
+ passwd_check (pw->pw_name, pw->pw_passwd, "chsh");
+ }
+
+#else /* !USE_PAM */
+ pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+ if (NULL == pampw) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ exit (E_NOPERM);
+ }
+
+ retval = pam_start ("chsh", pampw->pw_name, &conv, &pamh);
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_authenticate (pamh, 0);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_acct_mgmt (pamh, 0);
+ }
+
+ if (PAM_SUCCESS != retval) {
+ fprintf (stderr, _("%s: PAM: %s\n"),
+ Prog, pam_strerror (pamh, retval));
+ SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval)));
+ if (NULL != pamh) {
+ (void) pam_end (pamh, retval);
+ }
+ exit (E_NOPERM);
+ }
+ (void) pam_end (pamh, retval);
+#endif /* USE_PAM */
+}
+
+/*
+ * update_shell - update the user's shell in the passwd database
+ *
+ * Commit the user's entry after changing her shell field.
+ *
+ * It will not return in case of error.
+ */
+static void update_shell (const char *user, char *newshell)
+{
+ const struct passwd *pw; /* Password entry from /etc/passwd */
+ struct passwd pwent; /* New password entry */
+
+ /*
+ * Before going any further, raise the ulimit to prevent
+ * colliding into a lowered ulimit, and set the real UID
+ * to root to protect against unexpected signals. Any
+ * keyboard signals are set to be ignored.
+ */
+ if (setuid (0) != 0) {
+ SYSLOG ((LOG_ERR, "can't setuid(0)"));
+ fputs (_("Cannot change ID to root.\n"), stderr);
+ fail_exit (1);
+ }
+ pwd_init ();
+
+ /*
+ * The passwd entry is now ready to be committed back to
+ * the password file. Get a lock on the file and open it.
+ */
+ if (pw_lock () == 0) {
+ fprintf (stderr, _("%s: cannot lock %s; try again later.\n"),
+ Prog, pw_dbname ());
+ fail_exit (1);
+ }
+ pw_locked = true;
+ if (pw_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", pw_dbname ()));
+ fail_exit (1);
+ }
+
+ /*
+ * Get the entry to update using pw_locate() - we want the real
+ * one from /etc/passwd, not the one from getpwnam() which could
+ * contain the shadow password if (despite the warnings) someone
+ * enables AUTOSHADOW (or SHADOW_COMPAT in libc). --marekm
+ */
+ pw = pw_locate (user);
+ if (NULL == pw) {
+ fprintf (stderr,
+ _("%s: user '%s' does not exist in %s\n"),
+ Prog, user, pw_dbname ());
+ fail_exit (1);
+ }
+
+ /*
+ * Make a copy of the entry, then change the shell field. The other
+ * fields remain unchanged.
+ */
+ pwent = *pw;
+ pwent.pw_shell = newshell;
+
+ /*
+ * Update the passwd file entry. If there is a DBM file, update
+ * that entry as well.
+ */
+ if (pw_update (&pwent) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, pw_dbname (), pwent.pw_name);
+ fail_exit (1);
+ }
+
+ /*
+ * Changes have all been made, so commit them and unlock the file.
+ */
+ if (pw_close () == 0) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
+ fail_exit (1);
+ }
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ pw_locked= false;
+}
+
+/*
+ * chsh - this command controls changes to the user's shell
+ *
+ * The only supported option is -s which permits the the login shell to
+ * be set from the command line.
+ */
+int main (int argc, char **argv)
+{
+ char *user; /* User name */
+ const struct passwd *pw; /* Password entry from /etc/passwd */
+
+ sanitize_env ();
+
+ /*
+ * Get the program name. The program name is used as a prefix to
+ * most error messages.
+ */
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ /*
+ * This command behaves different for root and non-root users.
+ */
+ amroot = (getuid () == 0);
+
+ OPENLOG ("chsh");
+
+ /* parse the command line options */
+ process_flags (argc, argv);
+
+ /*
+ * Get the name of the user to check. It is either the command line
+ * name, or the name getlogin() returns.
+ */
+ if (optind < argc) {
+ user = argv[optind];
+ pw = xgetpwnam (user);
+ if (NULL == pw) {
+ fprintf (stderr,
+ _("%s: user '%s' does not exist\n"), Prog, user);
+ fail_exit (1);
+ }
+ } else {
+ pw = get_my_pwent ();
+ if (NULL == pw) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
+ (unsigned long) getuid ()));
+ fail_exit (1);
+ }
+ user = xstrdup (pw->pw_name);
+ }
+
+#ifdef USE_NIS
+ /*
+ * Now we make sure this is a LOCAL password entry for this user ...
+ */
+ if (__ispwNIS ()) {
+ char *nis_domain;
+ char *nis_master;
+
+ fprintf (stderr,
+ _("%s: cannot change user '%s' on NIS client.\n"),
+ Prog, user);
+
+ if (!yp_get_default_domain (&nis_domain) &&
+ !yp_master (nis_domain, "passwd.byname", &nis_master)) {
+ fprintf (stderr,
+ _("%s: '%s' is the NIS master for this client.\n"),
+ Prog, nis_master);
+ }
+ fail_exit (1);
+ }
+#endif
+
+ check_perms (pw);
+
+ /*
+ * Now get the login shell. Either get it from the password
+ * file, or use the value from the command line.
+ */
+ if (!sflg) {
+ STRFCPY (loginsh, pw->pw_shell);
+ }
+
+ /*
+ * If the login shell was not set on the command line, let the user
+ * interactively change it.
+ */
+ if (!sflg) {
+ printf (_("Changing the login shell for %s\n"), user);
+ new_fields ();
+ }
+
+ /*
+ * Check all of the fields for valid information. The shell
+ * field may not contain any illegal characters. Non-privileged
+ * users are restricted to using the shells in /etc/shells.
+ * The shell must be executable by the user.
+ */
+ if (valid_field (loginsh, ":,=\n") != 0) {
+ fprintf (stderr, _("%s: Invalid entry: %s\n"), Prog, loginsh);
+ fail_exit (1);
+ }
+ if ( !amroot
+ && ( is_restricted_shell (loginsh)
+ || (access (loginsh, X_OK) != 0))) {
+ fprintf (stderr, _("%s: %s is an invalid shell\n"), Prog, loginsh);
+ fail_exit (1);
+ }
+
+ /* Even for root, warn if an invalid shell is specified. */
+ if (access (loginsh, F_OK) != 0) {
+ fprintf (stderr, _("%s: Warning: %s does not exist\n"), Prog, loginsh);
+ } else if (access (loginsh, X_OK) != 0) {
+ fprintf (stderr, _("%s: Warning: %s is not executable\n"), Prog, loginsh);
+ }
+
+ update_shell (user, loginsh);
+
+ SYSLOG ((LOG_INFO, "changed user '%s' shell to '%s'", user, loginsh));
+
+ nscd_flush_cache ("passwd");
+
+ closelog ();
+ exit (E_SUCCESS);
+}
+
diff --git a/src/expiry.c b/src/expiry.c
new file mode 100644
index 0000000..41add94
--- /dev/null
+++ b/src/expiry.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 1994 , Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2001 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+#include "defines.h"
+#include "prototypes.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+/* Global variables */
+const char *Prog;
+static bool cflg = false;
+
+/* local function prototypes */
+static RETSIGTYPE catch_signals (unused int sig);
+static /*@noreturn@*/void usage (int status);
+static void process_flags (int argc, char **argv);
+
+/*
+ * catch_signals - signal catcher
+ */
+static RETSIGTYPE catch_signals (unused int sig)
+{
+ _exit (10);
+}
+
+/*
+ * usage - print syntax message and exit
+ */
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -c, --check check the user's password expiration\n"), usageout);
+ (void) fputs (_(" -f, --force force password change if the user's password\n"
+ " is expired\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ bool fflg = false;
+ int c;
+ static struct option long_options[] = {
+ {"check", no_argument, NULL, 'c'},
+ {"force", no_argument, NULL, 'f'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "cfh",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'c':
+ cflg = true;
+ break;
+ case 'f':
+ fflg = true;
+ break;
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ default:
+ usage (E_USAGE);
+ }
+ }
+
+ if (! (cflg || fflg)) {
+ usage (E_USAGE);
+ }
+
+ if (cflg && fflg) {
+ fprintf (stderr,
+ _("%s: options %s and %s conflict\n"),
+ Prog, "-c", "-f");
+ usage (E_USAGE);
+ }
+
+ if (argc != optind) {
+ fprintf (stderr,
+ _("%s: unexpected argument: %s\n"),
+ Prog, argv[optind]);
+ usage (E_USAGE);
+ }
+}
+
+/*
+ * expiry - check and enforce password expiration policy
+ *
+ * expiry checks (-c) the current password expiration and forces (-f)
+ * changes when required. It is callable as a normal user command.
+ */
+int main (int argc, char **argv)
+{
+ struct passwd *pwd;
+ struct spwd *spwd;
+
+ Prog = Basename (argv[0]);
+
+ sanitize_env ();
+
+ /*
+ * Start by disabling all of the keyboard signals.
+ */
+ (void) signal (SIGHUP, catch_signals);
+ (void) signal (SIGINT, catch_signals);
+ (void) signal (SIGQUIT, catch_signals);
+#ifdef SIGTSTP
+ (void) signal (SIGTSTP, catch_signals);
+#endif
+
+ /*
+ * expiry takes one of two arguments. The default action is to give
+ * the usage message.
+ */
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ OPENLOG ("expiry");
+
+ process_flags (argc, argv);
+
+ /*
+ * Get user entries for /etc/passwd and /etc/shadow
+ */
+ pwd = get_my_pwent ();
+ if (NULL == pwd) {
+ fprintf (stderr, _("%s: Cannot determine your user name.\n"),
+ Prog);
+ SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
+ (unsigned long) getuid ()));
+ exit (10);
+ }
+ spwd = getspnam (pwd->pw_name); /* !USE_PAM, No need for xgetspnam */
+
+ /*
+ * If checking accounts, use agecheck() function.
+ */
+ if (cflg) {
+ /*
+ * Print out number of days until expiration.
+ */
+ agecheck (spwd);
+
+ /*
+ * Exit with status indicating state of account.
+ */
+ exit (isexpired (pwd, spwd));
+ }
+
+ /*
+ * Otherwise, force a password change with the expire() function.
+ * It will force the change or give a message indicating what to
+ * do.
+ * It won't return unless the account is unexpired.
+ */
+ (void) expire (pwd, spwd);
+
+ return E_SUCCESS;
+}
+
diff --git a/src/faillog.c b/src/faillog.c
new file mode 100644
index 0000000..073561c
--- /dev/null
+++ b/src/faillog.c
@@ -0,0 +1,741 @@
+/*
+ * Copyright (c) 1989 - 1993, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2002 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <getopt.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <assert.h>
+#include "defines.h"
+#include "faillog.h"
+#include "prototypes.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+/* local function prototypes */
+static /*@noreturn@*/void usage (int status);
+static void print_one (/*@null@*/const struct passwd *pw, bool force);
+static void set_locktime (long locktime);
+static bool set_locktime_one (uid_t uid, long locktime);
+static void setmax (short max);
+static bool setmax_one (uid_t uid, short max);
+static void print (void);
+static bool reset_one (uid_t uid);
+static void reset (void);
+
+/*
+ * Global variables
+ */
+const char *Prog; /* Program name */
+static FILE *fail; /* failure file stream */
+static time_t seconds; /* that number of days in seconds */
+static unsigned long umin; /* if uflg and has_umin, only display users with uid >= umin */
+static bool has_umin = false;
+static unsigned long umax; /* if uflg and has_umax, only display users with uid <= umax */
+static bool has_umax = false;
+static bool errors = false;
+
+static bool aflg = false; /* set if all users are to be printed always */
+static bool uflg = false; /* set if user is a valid user id */
+static bool tflg = false; /* print is restricted to most recent days */
+static bool lflg = false; /* set the locktime */
+static bool mflg = false; /* set maximum failed login counters */
+static bool rflg = false; /* reset the counters of login failures */
+
+static struct stat statbuf; /* fstat buffer for file size */
+
+#define NOW (time((time_t *) 0))
+
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -a, --all display faillog records for all users\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -l, --lock-secs SEC after failed login lock account for SEC seconds\n"), usageout);
+ (void) fputs (_(" -m, --maximum MAX set maximum failed login counters to MAX\n"), usageout);
+ (void) fputs (_(" -r, --reset reset the counters of login failures\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs (_(" -t, --time DAYS display faillog records more recent than DAYS\n"), usageout);
+ (void) fputs (_(" -u, --user LOGIN/RANGE display faillog record or maintains failure\n"
+ " counters and limits (if used with -r, -m,\n"
+ " or -l) only for the specified LOGIN(s)\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+static void print_one (/*@null@*/const struct passwd *pw, bool force)
+{
+ static bool once = false;
+ struct tm *tm;
+ off_t offset;
+ struct faillog fl;
+ time_t now;
+
+#ifdef HAVE_STRFTIME
+ char *cp;
+ char ptime[80];
+#endif
+
+ if (NULL == pw) {
+ return;
+ }
+
+ offset = (off_t) pw->pw_uid * sizeof (fl);
+ if (offset + sizeof (fl) <= statbuf.st_size) {
+ /* fseeko errors are not really relevant for us. */
+ int err = fseeko (fail, offset, SEEK_SET);
+ assert (0 == err);
+ /* faillog is a sparse file. Even if no entries were
+ * entered for this user, which should be able to get the
+ * empty entry in this case.
+ */
+ if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
+ fprintf (stderr,
+ _("%s: Failed to get the entry for UID %lu\n"),
+ Prog, (unsigned long int)pw->pw_uid);
+ return;
+ }
+ } else {
+ /* Outsize of the faillog file.
+ * Behave as if there were a missing entry (same behavior
+ * as if we were reading an non existing entry in the
+ * sparse faillog file).
+ */
+ memzero (&fl, sizeof (fl));
+ }
+
+ /* Nothing to report */
+ if (!force && (0 == fl.fail_time)) {
+ return;
+ }
+
+ (void) time(&now);
+
+ /* Filter out entries that do not match with the -t option */
+ if (tflg && ((now - fl.fail_time) > seconds)) {
+ return;
+ }
+
+ /* Print the header only once */
+ if (!once) {
+ puts (_("Login Failures Maximum Latest On\n"));
+ once = true;
+ }
+
+ tm = localtime (&fl.fail_time);
+#ifdef HAVE_STRFTIME
+ strftime (ptime, sizeof (ptime), "%D %H:%M:%S %z", tm);
+ cp = ptime;
+#endif
+ printf ("%-9s %5d %5d ",
+ pw->pw_name, fl.fail_cnt, fl.fail_max);
+ /* FIXME: cp is not defined ifndef HAVE_STRFTIME */
+ printf ("%s %s", cp, fl.fail_line);
+ if (0 != fl.fail_locktime) {
+ if ( ((fl.fail_time + fl.fail_locktime) > now)
+ && (0 != fl.fail_cnt)) {
+ printf (_(" [%lus left]"),
+ (unsigned long) fl.fail_time + fl.fail_locktime - now);
+ } else {
+ printf (_(" [%lds lock]"),
+ fl.fail_locktime);
+ }
+ }
+ putchar ('\n');
+}
+
+static void print (void)
+{
+ if (uflg && has_umin && has_umax && (umin==umax)) {
+ print_one (getpwuid ((uid_t)umin), true);
+ } else {
+ /* We only print records for existing users.
+ * Loop based on the user database instead of reading the
+ * whole file. We will have to query the database anyway
+ * so except for very small ranges and large user
+ * database, this should not be a performance issue.
+ */
+ struct passwd *pwent;
+
+ setpwent ();
+ while ( (pwent = getpwent ()) != NULL ) {
+ if ( uflg
+ && ( (has_umin && (pwent->pw_uid < (uid_t)umin))
+ || (has_umax && (pwent->pw_uid > (uid_t)umax)))) {
+ continue;
+ }
+ print_one (pwent, aflg);
+ }
+ endpwent ();
+ }
+}
+
+/*
+ * reset_one - Reset the fail count for one user
+ *
+ * This returns a boolean indicating if an error occurred.
+ */
+static bool reset_one (uid_t uid)
+{
+ off_t offset;
+ struct faillog fl;
+
+ offset = (off_t) uid * sizeof (fl);
+ if (offset + sizeof (fl) <= statbuf.st_size) {
+ /* fseeko errors are not really relevant for us. */
+ int err = fseeko (fail, offset, SEEK_SET);
+ assert (0 == err);
+ /* faillog is a sparse file. Even if no entries were
+ * entered for this user, which should be able to get the
+ * empty entry in this case.
+ */
+ if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
+ fprintf (stderr,
+ _("%s: Failed to get the entry for UID %lu\n"),
+ Prog, (unsigned long int)uid);
+ return true;
+ }
+ } else {
+ /* Outsize of the faillog file.
+ * Behave as if there were a missing entry (same behavior
+ * as if we were reading an non existing entry in the
+ * sparse faillog file).
+ */
+ memzero (&fl, sizeof (fl));
+ }
+
+ if (0 == fl.fail_cnt) {
+ /* If the count is already null, do not write in the file.
+ * This avoids writing 0 when no entries were present for
+ * the user.
+ */
+ return false;
+ }
+
+ fl.fail_cnt = 0;
+
+ if ( (fseeko (fail, offset, SEEK_SET) == 0)
+ && (fwrite ((char *) &fl, sizeof (fl), 1, fail) == 1)) {
+ (void) fflush (fail);
+ return false;
+ }
+
+ fprintf (stderr,
+ _("%s: Failed to reset fail count for UID %lu\n"),
+ Prog, (unsigned long int)uid);
+ return true;
+}
+
+static void reset (void)
+{
+ if (uflg && has_umin && has_umax && (umin==umax)) {
+ if (reset_one ((uid_t)umin)) {
+ errors = true;
+ }
+ } else {
+ /* There is no need to reset outside of the faillog
+ * database.
+ */
+ uid_t uidmax = statbuf.st_size / sizeof (struct faillog);
+ if (uidmax > 1) {
+ uidmax--;
+ }
+ if (has_umax && (uid_t)umax < uidmax) {
+ uidmax = (uid_t)umax;
+ }
+
+ /* Reset all entries in the specified range.
+ * Non existing entries will not be touched.
+ */
+ if (aflg) {
+ /* Entries for non existing users are also reset.
+ */
+ uid_t uid = 0;
+
+ /* Make sure we stay in the umin-umax range if specified */
+ if (has_umin) {
+ uid = (uid_t)umin;
+ }
+
+ while (uid <= uidmax) {
+ if (reset_one (uid)) {
+ errors = true;
+ }
+ uid++;
+ }
+ } else {
+ /* Only reset records for existing users.
+ */
+ struct passwd *pwent;
+
+ setpwent ();
+ while ( (pwent = getpwent ()) != NULL ) {
+ if ( uflg
+ && ( (has_umin && (pwent->pw_uid < (uid_t)umin))
+ || (pwent->pw_uid > (uid_t)uidmax))) {
+ continue;
+ }
+ if (reset_one (pwent->pw_uid)) {
+ errors = true;
+ }
+ }
+ endpwent ();
+ }
+ }
+}
+
+/*
+ * setmax_one - Set the maximum failed login counter for one user
+ *
+ * This returns a boolean indicating if an error occurred.
+ */
+static bool setmax_one (uid_t uid, short max)
+{
+ off_t offset;
+ struct faillog fl;
+
+ offset = (off_t) uid * sizeof (fl);
+ if (offset + sizeof (fl) <= statbuf.st_size) {
+ /* fseeko errors are not really relevant for us. */
+ int err = fseeko (fail, offset, SEEK_SET);
+ assert (0 == err);
+ /* faillog is a sparse file. Even if no entries were
+ * entered for this user, which should be able to get the
+ * empty entry in this case.
+ */
+ if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
+ fprintf (stderr,
+ _("%s: Failed to get the entry for UID %lu\n"),
+ Prog, (unsigned long int)uid);
+ return true;
+ }
+ } else {
+ /* Outsize of the faillog file.
+ * Behave as if there were a missing entry (same behavior
+ * as if we were reading an non existing entry in the
+ * sparse faillog file).
+ */
+ memzero (&fl, sizeof (fl));
+ }
+
+ if (max == fl.fail_max) {
+ /* If the max is already set to the right value, do not
+ * write in the file.
+ * This avoids writing 0 when no entries were present for
+ * the user and the max argument is 0.
+ */
+ return false;
+ }
+
+ fl.fail_max = max;
+
+ if ( (fseeko (fail, offset, SEEK_SET) == 0)
+ && (fwrite ((char *) &fl, sizeof (fl), 1, fail) == 1)) {
+ (void) fflush (fail);
+ return false;
+ }
+
+ fprintf (stderr,
+ _("%s: Failed to set max for UID %lu\n"),
+ Prog, (unsigned long int)uid);
+ return true;
+}
+
+static void setmax (short max)
+{
+ if (uflg && has_umin && has_umax && (umin==umax)) {
+ if (setmax_one ((uid_t)umin, max)) {
+ errors = true;
+ }
+ } else {
+ /* Set max for entries in the specified range.
+ * If max is unchanged for an entry, the entry is not touched.
+ * If max is null, and no entries exist for this user, no
+ * entries will be created.
+ */
+ if (aflg) {
+ /* Entries for non existing user are also taken into
+ * account (in order to define policy for future users).
+ */
+ uid_t uid = 0;
+ /* The default umax value is based on the size of the
+ * faillog database.
+ */
+ uid_t uidmax = statbuf.st_size / sizeof (struct faillog);
+ if (uidmax > 1) {
+ uidmax--;
+ }
+
+ /* Make sure we stay in the umin-umax range if specified */
+ if (has_umin) {
+ uid = (uid_t)umin;
+ }
+ if (has_umax) {
+ uidmax = (uid_t)umax;
+ }
+
+ while (uid <= uidmax) {
+ if (setmax_one (uid, max)) {
+ errors = true;
+ }
+ uid++;
+ }
+ } else {
+ /* Only change records for existing users.
+ */
+ struct passwd *pwent;
+
+ setpwent ();
+ while ( (pwent = getpwent ()) != NULL ) {
+ if ( uflg
+ && ( (has_umin && (pwent->pw_uid < (uid_t)umin))
+ || (has_umax && (pwent->pw_uid > (uid_t)umax)))) {
+ continue;
+ }
+ if (setmax_one (pwent->pw_uid, max)) {
+ errors = true;
+ }
+ }
+ endpwent ();
+ }
+ }
+}
+
+/*
+ * set_locktime_one - Set the locktime for one user
+ *
+ * This returns a boolean indicating if an error occurred.
+ */
+static bool set_locktime_one (uid_t uid, long locktime)
+{
+ off_t offset;
+ struct faillog fl;
+
+ offset = (off_t) uid * sizeof (fl);
+ if (offset + sizeof (fl) <= statbuf.st_size) {
+ /* fseeko errors are not really relevant for us. */
+ int err = fseeko (fail, offset, SEEK_SET);
+ assert (0 == err);
+ /* faillog is a sparse file. Even if no entries were
+ * entered for this user, which should be able to get the
+ * empty entry in this case.
+ */
+ if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
+ fprintf (stderr,
+ _("%s: Failed to get the entry for UID %lu\n"),
+ Prog, (unsigned long int)uid);
+ return true;
+ }
+ } else {
+ /* Outsize of the faillog file.
+ * Behave as if there were a missing entry (same behavior
+ * as if we were reading an non existing entry in the
+ * sparse faillog file).
+ */
+ memzero (&fl, sizeof (fl));
+ }
+
+ if (locktime == fl.fail_locktime) {
+ /* If the locktime is already set to the right value, do not
+ * write in the file.
+ * This avoids writing 0 when no entries were present for
+ * the user and the locktime argument is 0.
+ */
+ return false;
+ }
+
+ fl.fail_locktime = locktime;
+
+ if ( (fseeko (fail, offset, SEEK_SET) == 0)
+ && (fwrite ((char *) &fl, sizeof (fl), 1, fail) == 1)) {
+ (void) fflush (fail);
+ return false;
+ }
+
+ fprintf (stderr,
+ _("%s: Failed to set locktime for UID %lu\n"),
+ Prog, (unsigned long int)uid);
+ return true;
+}
+
+static void set_locktime (long locktime)
+{
+ if (uflg && has_umin && has_umax && (umin==umax)) {
+ if (set_locktime_one ((uid_t)umin, locktime)) {
+ errors = true;
+ }
+ } else {
+ /* Set locktime for entries in the specified range.
+ * If locktime is unchanged for an entry, the entry is not touched.
+ * If locktime is null, and no entries exist for this user, no
+ * entries will be created.
+ */
+ if (aflg) {
+ /* Entries for non existing user are also taken into
+ * account (in order to define policy for future users).
+ */
+ uid_t uid = 0;
+ /* The default umax value is based on the size of the
+ * faillog database.
+ */
+ uid_t uidmax = statbuf.st_size / sizeof (struct faillog);
+ if (uidmax > 1) {
+ uidmax--;
+ }
+
+ /* Make sure we stay in the umin-umax range if specified */
+ if (has_umin) {
+ uid = (uid_t)umin;
+ }
+ if (has_umax) {
+ uidmax = (uid_t)umax;
+ }
+
+ while (uid <= uidmax) {
+ if (set_locktime_one (uid, locktime)) {
+ errors = true;
+ }
+ uid++;
+ }
+ } else {
+ /* Only change records for existing users.
+ */
+ struct passwd *pwent;
+
+ setpwent ();
+ while ( (pwent = getpwent ()) != NULL ) {
+ if ( uflg
+ && ( (has_umin && (pwent->pw_uid < (uid_t)umin))
+ || (has_umax && (pwent->pw_uid > (uid_t)umax)))) {
+ continue;
+ }
+ if (set_locktime_one (pwent->pw_uid, locktime)) {
+ errors = true;
+ }
+ }
+ endpwent ();
+ }
+ }
+}
+
+int main (int argc, char **argv)
+{
+ long fail_locktime;
+ short fail_max;
+ long days;
+
+ /*
+ * Get the program name. The program name is used as a prefix to
+ * most error messages.
+ */
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ {
+ int c;
+ static struct option long_options[] = {
+ {"all", no_argument, NULL, 'a'},
+ {"help", no_argument, NULL, 'h'},
+ {"lock-secs", required_argument, NULL, 'l'},
+ {"maximum", required_argument, NULL, 'm'},
+ {"reset", no_argument, NULL, 'r'},
+ {"root", required_argument, NULL, 'R'},
+ {"time", required_argument, NULL, 't'},
+ {"user", required_argument, NULL, 'u'},
+ {NULL, 0, NULL, '\0'}
+ };
+ while ((c = getopt_long (argc, argv, "ahl:m:rR:t:u:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'a':
+ aflg = true;
+ break;
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'l':
+ if (getlong (optarg, &fail_locktime) == 0) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ lflg = true;
+ break;
+ case 'm':
+ {
+ long int lmax;
+ if ( (getlong (optarg, &lmax) == 0)
+ || ((long int)(short) lmax != lmax)) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ fail_max = (short) lmax;
+ mflg = true;
+ break;
+ }
+ case 'r':
+ rflg = true;
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ case 't':
+ if (getlong (optarg, &days) == 0) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ seconds = (time_t) days * DAY;
+ tflg = true;
+ break;
+ case 'u':
+ {
+ /*
+ * The user can be:
+ * - a login name
+ * - numerical
+ * - a numerical login ID
+ * - a range (-x, x-, x-y)
+ */
+ struct passwd *pwent;
+
+ uflg = true;
+ /* local, no need for xgetpwnam */
+ pwent = getpwnam (optarg);
+ if (NULL != pwent) {
+ umin = (unsigned long) pwent->pw_uid;
+ has_umin = true;
+ umax = umin;
+ has_umax = true;
+ } else {
+ if (getrange (optarg,
+ &umin, &has_umin,
+ &umax, &has_umax) == 0) {
+ fprintf (stderr,
+ _("%s: Unknown user or range: %s\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ }
+
+ break;
+ }
+ default:
+ usage (E_USAGE);
+ }
+ }
+ if (argc > optind) {
+ fprintf (stderr,
+ _("%s: unexpected argument: %s\n"),
+ Prog, argv[optind]);
+ usage (EXIT_FAILURE);
+ }
+ }
+
+ if (tflg && (lflg || mflg || rflg)) {
+ usage (E_USAGE);
+ }
+
+ /* Open the faillog database */
+ if (lflg || mflg || rflg) {
+ fail = fopen (FAILLOG_FILE, "r+");
+ } else {
+ fail = fopen (FAILLOG_FILE, "r");
+ }
+ if (NULL == fail) {
+ fprintf (stderr,
+ _("%s: Cannot open %s: %s\n"),
+ Prog, FAILLOG_FILE, strerror (errno));
+ exit (E_NOPERM);
+ }
+
+ /* Get the size of the faillog */
+ if (fstat (fileno (fail), &statbuf) != 0) {
+ fprintf (stderr,
+ _("%s: Cannot get the size of %s: %s\n"),
+ Prog, FAILLOG_FILE, strerror (errno));
+ exit (E_NOPERM);
+ }
+
+ if (lflg) {
+ set_locktime (fail_locktime);
+ }
+
+ if (mflg) {
+ setmax (fail_max);
+ }
+
+ if (rflg) {
+ reset ();
+ }
+
+ if (!(lflg || mflg || rflg)) {
+ print ();
+ }
+
+ if (lflg || mflg || rflg) {
+ if ( (ferror (fail) != 0)
+ || (fflush (fail) != 0)
+ || (fsync (fileno (fail)) != 0)
+ || (fclose (fail) != 0)) {
+ fprintf (stderr,
+ _("%s: Failed to write %s: %s\n"),
+ Prog, FAILLOG_FILE, strerror (errno));
+ (void) fclose (fail);
+ errors = true;
+ }
+ } else {
+ (void) fclose (fail);
+ }
+
+ exit (errors ? E_NOPERM : E_SUCCESS);
+}
+
diff --git a/src/gpasswd.c b/src/gpasswd.c
new file mode 100644
index 0000000..c4a492b
--- /dev/null
+++ b/src/gpasswd.c
@@ -0,0 +1,1207 @@
+/*
+ * Copyright (c) 1990 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2001 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include "defines.h"
+#include "groupio.h"
+#include "nscd.h"
+#include "prototypes.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+/*
+ * Global variables
+ */
+/* The name of this command, as it is invoked */
+const char *Prog;
+
+#ifdef SHADOWGRP
+/* Indicate if shadow groups are enabled on the system
+ * (/etc/gshadow present) */
+static bool is_shadowgrp;
+#endif
+
+/* Flags set by options */
+static bool aflg = false;
+static bool Aflg = false;
+static bool dflg = false;
+static bool Mflg = false;
+static bool rflg = false;
+static bool Rflg = false;
+/* The name of the group that is being affected */
+static char *group = NULL;
+/* The name of the user being added (-a) or removed (-d) from group */
+static char *user = NULL;
+/* The new list of members set with -M */
+static char *members = NULL;
+#ifdef SHADOWGRP
+/* The new list of group administrators set with -A */
+static char *admins = NULL;
+#endif
+/* The name of the caller */
+static char *myname = NULL;
+/* The UID of the caller */
+static uid_t bywho;
+/* Indicate if gpasswd was called by root */
+#define amroot (0 == bywho)
+
+/* The number of retries for th user to provide and repeat a new password */
+#ifndef RETRIES
+#define RETRIES 3
+#endif
+
+/* local function prototypes */
+static void usage (int status);
+static RETSIGTYPE catch_signals (int killed);
+static bool is_valid_user_list (const char *users);
+static void process_flags (int argc, char **argv);
+static void check_flags (int argc, int opt_index);
+static void open_files (void);
+static void close_files (void);
+#ifdef SHADOWGRP
+static void get_group (struct group *gr, struct sgrp *sg);
+static void check_perms (const struct group *gr, const struct sgrp *sg);
+static void update_group (struct group *gr, struct sgrp *sg);
+static void change_passwd (struct group *gr, struct sgrp *sg);
+#else
+static void get_group (struct group *gr);
+static void check_perms (const struct group *gr);
+static void update_group (struct group *gr);
+static void change_passwd (struct group *gr);
+#endif
+static void log_gpasswd_failure (const char *suffix);
+static void log_gpasswd_failure_system (/*@null@*/unused void *arg);
+static void log_gpasswd_failure_group (/*@null@*/unused void *arg);
+#ifdef SHADOWGRP
+static void log_gpasswd_failure_gshadow (/*@null@*/unused void *arg);
+#endif
+static void log_gpasswd_success (const char *suffix);
+static void log_gpasswd_success_system (/*@null@*/unused void *arg);
+static void log_gpasswd_success_group (/*@null@*/unused void *arg);
+
+/*
+ * usage - display usage message
+ */
+static void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [option] GROUP\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -a, --add USER add USER to GROUP\n"), usageout);
+ (void) fputs (_(" -d, --delete USER remove USER from GROUP\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -Q, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs (_(" -r, --remove-password remove the GROUP's password\n"), usageout);
+ (void) fputs (_(" -R, --restrict restrict access to GROUP to its members\n"), usageout);
+ (void) fputs (_(" -M, --members USER,... set the list of members of GROUP\n"), usageout);
+#ifdef SHADOWGRP
+ (void) fputs (_(" -A, --administrators ADMIN,...\n"
+ " set the list of administrators for GROUP\n"), usageout);
+ (void) fputs (_("Except for the -A and -M options, the options cannot be combined.\n"), usageout);
+#else
+ (void) fputs (_("The options cannot be combined.\n"), usageout);
+#endif
+ exit (status);
+}
+
+/*
+ * catch_signals - set or reset termio modes.
+ *
+ * catch_signals() is called before processing begins. signal() is then
+ * called with catch_signals() as the signal handler. If signal later
+ * calls catch_signals() with a signal number, the terminal modes are
+ * then reset.
+ */
+static RETSIGTYPE catch_signals (int killed)
+{
+ static TERMIO sgtty;
+
+ if (0 != killed) {
+ STTY (0, &sgtty);
+ } else {
+ GTTY (0, &sgtty);
+ }
+
+ if (0 != killed) {
+ (void) write (STDOUT_FILENO, "\n", 1);
+ _exit (killed);
+ }
+}
+
+/*
+ * is_valid_user_list - check a comma-separated list of user names for validity
+ *
+ * is_valid_user_list scans a comma-separated list of user names and
+ * checks that each listed name exists is the user database.
+ *
+ * It returns true if the list of users is valid.
+ */
+static bool is_valid_user_list (const char *users)
+{
+ const char *username;
+ char *end;
+ bool is_valid = true;
+ /*@owned@*/char *tmpusers = xstrdup (users);
+
+ for (username = tmpusers;
+ (NULL != username) && ('\0' != *username);
+ username = end) {
+ end = strchr (username, ',');
+ if (NULL != end) {
+ *end = '\0';
+ end++;
+ }
+
+ /*
+ * This user must exist.
+ */
+
+ /* local, no need for xgetpwnam */
+ if (getpwnam (username) == NULL) {
+ fprintf (stderr, _("%s: user '%s' does not exist\n"),
+ Prog, username);
+ is_valid = false;
+ }
+ }
+
+ free (tmpusers);
+
+ return is_valid;
+}
+
+static void failure (void)
+{
+ fprintf (stderr, _("%s: Permission denied.\n"), Prog);
+ log_gpasswd_failure (": Permission denied");
+ exit (E_NOPERM);
+}
+
+/*
+ * process_flags - process the command line options and arguments
+ */
+static void process_flags (int argc, char **argv)
+{
+ int c;
+ static struct option long_options[] = {
+ {"add", required_argument, NULL, 'a'},
+ {"administrators", required_argument, NULL, 'A'},
+ {"delete", required_argument, NULL, 'd'},
+ {"help", no_argument, NULL, 'h'},
+ {"members", required_argument, NULL, 'M'},
+ {"root", required_argument, NULL, 'Q'},
+ {"remove-password", no_argument, NULL, 'r'},
+ {"restrict", no_argument, NULL, 'R'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "a:A:d:ghM:Q:rR",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'a': /* add a user */
+ aflg = true;
+ user = optarg;
+ /* local, no need for xgetpwnam */
+ if (getpwnam (user) == NULL) {
+ fprintf (stderr,
+ _("%s: user '%s' does not exist\n"),
+ Prog, user);
+ exit (E_BAD_ARG);
+ }
+ break;
+#ifdef SHADOWGRP
+ case 'A': /* set the list of administrators */
+ if (!is_shadowgrp) {
+ fprintf (stderr,
+ _("%s: shadow group passwords required for -A\n"),
+ Prog);
+ exit (E_GSHADOW_NOTFOUND);
+ }
+ admins = optarg;
+ if (!is_valid_user_list (admins)) {
+ exit (E_BAD_ARG);
+ }
+ Aflg = true;
+ break;
+#endif /* SHADOWGRP */
+ case 'd': /* delete a user */
+ dflg = true;
+ user = optarg;
+ break;
+ case 'g': /* no-op from normal password */
+ break;
+ case 'h':
+ usage (E_SUCCESS);
+ break;
+ case 'M': /* set the list of members */
+ members = optarg;
+ if (!is_valid_user_list (members)) {
+ exit (E_BAD_ARG);
+ }
+ Mflg = true;
+ break;
+ case 'Q': /* no-op, handled in process_root_flag () */
+ break;
+ case 'r': /* remove group password */
+ rflg = true;
+ break;
+ case 'R': /* restrict group password */
+ Rflg = true;
+ break;
+ default:
+ usage (E_USAGE);
+ }
+ }
+
+ /* Get the name of the group that is being affected. */
+ group = argv[optind];
+
+ check_flags (argc, optind);
+}
+
+/*
+ * check_flags - check the validity of options
+ */
+static void check_flags (int argc, int opt_index)
+{
+ int exclusive = 0;
+ /*
+ * Make sure exclusive flags are exclusive
+ */
+ if (aflg) {
+ exclusive++;
+ }
+ if (dflg) {
+ exclusive++;
+ }
+ if (rflg) {
+ exclusive++;
+ }
+ if (Rflg) {
+ exclusive++;
+ }
+ if (Aflg || Mflg) {
+ exclusive++;
+ }
+ if (exclusive > 1) {
+ usage (E_USAGE);
+ }
+
+ /*
+ * Make sure one (and only one) group was provided
+ */
+ if ((argc != (opt_index+1)) || (NULL == group)) {
+ usage (E_USAGE);
+ }
+}
+
+/*
+ * open_files - lock and open the group databases
+ *
+ * It will call exit in case of error.
+ */
+static void open_files (void)
+{
+ if (gr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, gr_dbname ());
+ exit (E_NOPERM);
+ }
+ add_cleanup (cleanup_unlock_group, NULL);
+
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ if (sgr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sgr_dbname ());
+ exit (E_NOPERM);
+ }
+ add_cleanup (cleanup_unlock_gshadow, NULL);
+ }
+#endif /* SHADOWGRP */
+
+ add_cleanup (log_gpasswd_failure_system, NULL);
+
+ if (gr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, gr_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
+ exit (E_NOPERM);
+ }
+
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ if (sgr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", sgr_dbname ()));
+ exit (E_NOPERM);
+ }
+ add_cleanup (log_gpasswd_failure_gshadow, NULL);
+ }
+#endif /* SHADOWGRP */
+
+ add_cleanup (log_gpasswd_failure_group, NULL);
+ del_cleanup (log_gpasswd_failure_system);
+}
+
+static void log_gpasswd_failure (const char *suffix)
+{
+#ifdef WITH_AUDIT
+ char buf[1024];
+#endif
+ if (aflg) {
+ SYSLOG ((LOG_ERR,
+ "%s failed to add user %s to group %s%s",
+ myname, user, group, suffix));
+#ifdef WITH_AUDIT
+ snprintf (buf, 1023,
+ "%s failed to add user %s to group %s%s",
+ myname, user, group, suffix);
+ buf[1023] = '\0';
+ audit_logger (AUDIT_USER_ACCT, Prog,
+ buf,
+ group, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ } else if (dflg) {
+ SYSLOG ((LOG_ERR,
+ "%s failed to remove user %s from group %s%s",
+ myname, user, group, suffix));
+#ifdef WITH_AUDIT
+ snprintf (buf, 1023,
+ "%s failed to remove user %s from group %s%s",
+ myname, user, group, suffix);
+ buf[1023] = '\0';
+ audit_logger (AUDIT_USER_ACCT, Prog,
+ buf,
+ group, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ } else if (rflg) {
+ SYSLOG ((LOG_ERR,
+ "%s failed to remove password of group %s%s",
+ myname, group, suffix));
+#ifdef WITH_AUDIT
+ snprintf (buf, 1023,
+ "%s failed to remove password of group %s%s",
+ myname, group, suffix);
+ buf[1023] = '\0';
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ buf,
+ group, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ } else if (Rflg) {
+ SYSLOG ((LOG_ERR,
+ "%s failed to restrict access to group %s%s",
+ myname, group, suffix));
+#ifdef WITH_AUDIT
+ snprintf (buf, 1023,
+ "%s failed to restrict access to group %s%s",
+ myname, group, suffix);
+ buf[1023] = '\0';
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ buf,
+ group, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ } else if (Aflg || Mflg) {
+#ifdef SHADOWGRP
+ if (Aflg) {
+ SYSLOG ((LOG_ERR,
+ "%s failed to set the administrators of group %s to %s%s",
+ myname, group, admins, suffix));
+#ifdef WITH_AUDIT
+ snprintf (buf, 1023,
+ "%s failed to set the administrators of group %s to %s%s",
+ myname, group, admins, suffix);
+ buf[1023] = '\0';
+ audit_logger (AUDIT_USER_ACCT, Prog,
+ buf,
+ group, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ }
+#endif /* SHADOWGRP */
+ if (Mflg) {
+ SYSLOG ((LOG_ERR,
+ "%s failed to set the members of group %s to %s%s",
+ myname, group, members, suffix));
+#ifdef WITH_AUDIT
+ snprintf (buf, 1023,
+ "%s failed to set the members of group %s to %s%s",
+ myname, group, members, suffix);
+ buf[1023] = '\0';
+ audit_logger (AUDIT_USER_ACCT, Prog,
+ buf,
+ group, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ }
+ } else {
+ SYSLOG ((LOG_ERR,
+ "%s failed to change password of group %s%s",
+ myname, group, suffix));
+#ifdef WITH_AUDIT
+ snprintf (buf, 1023,
+ "%s failed to change password of group %s%s",
+ myname, group, suffix);
+ buf[1023] = '\0';
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ buf,
+ group, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ }
+}
+
+static void log_gpasswd_failure_system (unused void *arg)
+{
+ log_gpasswd_failure ("");
+}
+
+static void log_gpasswd_failure_group (unused void *arg)
+{
+ char buf[1024];
+ snprintf (buf, 1023, " in %s", gr_dbname ());
+ buf[1023] = '\0';
+ log_gpasswd_failure (buf);
+}
+
+#ifdef SHADOWGRP
+static void log_gpasswd_failure_gshadow (unused void *arg)
+{
+ char buf[1024];
+ snprintf (buf, 1023, " in %s", sgr_dbname ());
+ buf[1023] = '\0';
+ log_gpasswd_failure (buf);
+}
+#endif /* SHADOWGRP */
+
+static void log_gpasswd_success (const char *suffix)
+{
+#ifdef WITH_AUDIT
+ char buf[1024];
+#endif
+ if (aflg) {
+ SYSLOG ((LOG_INFO,
+ "user %s added by %s to group %s%s",
+ user, myname, group, suffix));
+#ifdef WITH_AUDIT
+ snprintf (buf, 1023,
+ "user %s added by %s to group %s%s",
+ user, myname, group, suffix);
+ buf[1023] = '\0';
+ audit_logger (AUDIT_USER_ACCT, Prog,
+ buf,
+ group, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ } else if (dflg) {
+ SYSLOG ((LOG_INFO,
+ "user %s removed by %s from group %s%s",
+ user, myname, group, suffix));
+#ifdef WITH_AUDIT
+ snprintf (buf, 1023,
+ "user %s removed by %s from group %s%s",
+ user, myname, group, suffix);
+ buf[1023] = '\0';
+ audit_logger (AUDIT_USER_ACCT, Prog,
+ buf,
+ group, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ } else if (rflg) {
+ SYSLOG ((LOG_INFO,
+ "password of group %s removed by %s%s",
+ group, myname, suffix));
+#ifdef WITH_AUDIT
+ snprintf (buf, 1023,
+ "password of group %s removed by %s%s",
+ group, myname, suffix);
+ buf[1023] = '\0';
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ buf,
+ group, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ } else if (Rflg) {
+ SYSLOG ((LOG_INFO,
+ "access to group %s restricted by %s%s",
+ group, myname, suffix));
+#ifdef WITH_AUDIT
+ snprintf (buf, 1023,
+ "access to group %s restricted by %s%s",
+ group, myname, suffix);
+ buf[1023] = '\0';
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ buf,
+ group, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ } else if (Aflg || Mflg) {
+#ifdef SHADOWGRP
+ if (Aflg) {
+ SYSLOG ((LOG_INFO,
+ "administrators of group %s set by %s to %s%s",
+ group, myname, admins, suffix));
+#ifdef WITH_AUDIT
+ snprintf (buf, 1023,
+ "administrators of group %s set by %s to %s%s",
+ group, myname, admins, suffix);
+ buf[1023] = '\0';
+ audit_logger (AUDIT_USER_ACCT, Prog,
+ buf,
+ group, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ }
+#endif /* SHADOWGRP */
+ if (Mflg) {
+ SYSLOG ((LOG_INFO,
+ "members of group %s set by %s to %s%s",
+ group, myname, members, suffix));
+#ifdef WITH_AUDIT
+ snprintf (buf, 1023,
+ "members of group %s set by %s to %s%s",
+ group, myname, members, suffix);
+ buf[1023] = '\0';
+ audit_logger (AUDIT_USER_ACCT, Prog,
+ buf,
+ group, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ }
+ } else {
+ SYSLOG ((LOG_INFO,
+ "password of group %s changed by %s%s",
+ group, myname, suffix));
+#ifdef WITH_AUDIT
+ snprintf (buf, 1023,
+ "password of group %s changed by %s%s",
+ group, myname, suffix);
+ buf[1023] = '\0';
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ buf,
+ group, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ }
+}
+
+static void log_gpasswd_success_system (unused void *arg)
+{
+ log_gpasswd_success ("");
+}
+
+static void log_gpasswd_success_group (unused void *arg)
+{
+ char buf[1024];
+ snprintf (buf, 1023, " in %s", gr_dbname ());
+ buf[1023] = '\0';
+ log_gpasswd_success (buf);
+}
+
+/*
+ * close_files - close and unlock the group databases
+ *
+ * This cause any changes in the databases to be committed.
+ *
+ * It will call exit in case of error.
+ */
+static void close_files (void)
+{
+ if (gr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, gr_dbname ());
+ exit (E_NOPERM);
+ }
+ add_cleanup (log_gpasswd_success_group, NULL);
+ del_cleanup (log_gpasswd_failure_group);
+
+ cleanup_unlock_group (NULL);
+ del_cleanup (cleanup_unlock_group);
+
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ if (sgr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, sgr_dbname ());
+ exit (E_NOPERM);
+ }
+ del_cleanup (log_gpasswd_failure_gshadow);
+
+ cleanup_unlock_gshadow (NULL);
+ del_cleanup (cleanup_unlock_gshadow);
+ }
+#endif /* SHADOWGRP */
+
+ log_gpasswd_success_system (NULL);
+ del_cleanup (log_gpasswd_success_group);
+}
+
+/*
+ * check_perms - check if the user is allowed to change the password of
+ * the specified group.
+ *
+ * It only returns if the user is allowed.
+ */
+#ifdef SHADOWGRP
+static void check_perms (const struct group *gr, const struct sgrp *sg)
+#else
+static void check_perms (const struct group *gr)
+#endif
+{
+ /*
+ * Only root can use the -M and -A options.
+ */
+ if (!amroot && (Aflg || Mflg)) {
+ failure ();
+ }
+
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ /*
+ * The policy here for changing a group is that
+ * 1) you must be root or
+ * 2) you must be listed as an administrative member.
+ * Administrative members can do anything to a group that
+ * the root user can.
+ */
+ if (!amroot && !is_on_list (sg->sg_adm, myname)) {
+ failure ();
+ }
+ } else
+#endif /* SHADOWGRP */
+ {
+#ifdef FIRST_MEMBER_IS_ADMIN
+ /*
+ * The policy here for changing a group is that
+ * 1) you must be root or
+ * 2) you must be the first listed member of the group.
+ * The first listed member of a group can do anything to
+ * that group that the root user can. The rationale for
+ * this hack is that the FIRST user is probably the most
+ * important user in this entire group.
+ *
+ * This feature enabled by default could be a security
+ * problem when installed on existing systems where the
+ * first group member might be just a normal user.
+ * --marekm
+ */
+ if (!amroot) {
+ if (gr->gr_mem[0] == (char *) 0) {
+ failure ();
+ }
+
+ if (strcmp (gr->gr_mem[0], myname) != 0) {
+ failure ();
+ }
+ }
+#else /* ! FIRST_MEMBER_IS_ADMIN */
+ if (!amroot) {
+ failure ();
+ }
+#endif
+ }
+}
+
+/*
+ * update_group - Update the group information in the databases
+ */
+#ifdef SHADOWGRP
+static void update_group (struct group *gr, struct sgrp *sg)
+#else
+static void update_group (struct group *gr)
+#endif
+{
+ if (gr_update (gr) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, gr_dbname (), gr->gr_name);
+ exit (1);
+ }
+#ifdef SHADOWGRP
+ if (is_shadowgrp && (sgr_update (sg) == 0)) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, sgr_dbname (), sg->sg_name);
+ exit (1);
+ }
+#endif /* SHADOWGRP */
+}
+
+/*
+ * get_group - get the current information for the group
+ *
+ * The information are copied in group structure(s) so that they can be
+ * modified later.
+ *
+ * Note: If !is_shadowgrp, *sg will not be initialized.
+ */
+#ifdef SHADOWGRP
+static void get_group (struct group *gr, struct sgrp *sg)
+#else
+static void get_group (struct group *gr)
+#endif
+{
+ struct group const*tmpgr = NULL;
+#ifdef SHADOWGRP
+ struct sgrp const*tmpsg = NULL;
+#endif
+
+ if (gr_open (O_RDONLY) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
+ exit (E_NOPERM);
+ }
+
+ tmpgr = gr_locate (group);
+ if (NULL == tmpgr) {
+ fprintf (stderr,
+ _("%s: group '%s' does not exist in %s\n"),
+ Prog, group, gr_dbname ());
+ exit (E_BAD_ARG);
+ }
+
+ *gr = *tmpgr;
+ gr->gr_name = xstrdup (tmpgr->gr_name);
+ gr->gr_passwd = xstrdup (tmpgr->gr_passwd);
+ gr->gr_mem = dup_list (tmpgr->gr_mem);
+
+ if (gr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while closing read-only %s\n"),
+ Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR,
+ "failure while closing read-only %s",
+ gr_dbname ()));
+ exit (E_NOPERM);
+ }
+
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ if (sgr_open (O_RDONLY) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", sgr_dbname ()));
+ exit (E_NOPERM);
+ }
+ tmpsg = sgr_locate (group);
+ if (NULL != tmpsg) {
+ *sg = *tmpsg;
+ sg->sg_name = xstrdup (tmpsg->sg_name);
+ sg->sg_passwd = xstrdup (tmpsg->sg_passwd);
+
+ sg->sg_mem = dup_list (tmpsg->sg_mem);
+ sg->sg_adm = dup_list (tmpsg->sg_adm);
+ } else {
+ sg->sg_name = xstrdup (group);
+ sg->sg_passwd = gr->gr_passwd;
+ gr->gr_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
+
+ sg->sg_mem = dup_list (gr->gr_mem);
+
+ sg->sg_adm = (char **) xmalloc (sizeof (char *) * 2);
+#ifdef FIRST_MEMBER_IS_ADMIN
+ if (sg->sg_mem[0]) {
+ sg->sg_adm[0] = xstrdup (sg->sg_mem[0]);
+ sg->sg_adm[1] = NULL;
+ } else
+#endif
+ {
+ sg->sg_adm[0] = NULL;
+ }
+
+ }
+ if (sgr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while closing read-only %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR,
+ "failure while closing read-only %s",
+ sgr_dbname ()));
+ exit (E_NOPERM);
+ }
+ }
+#endif /* SHADOWGRP */
+}
+
+/*
+ * change_passwd - change the group's password
+ *
+ * Get the new password from the user and update the password in the
+ * group's structure.
+ *
+ * It will call exit in case of error.
+ */
+#ifdef SHADOWGRP
+static void change_passwd (struct group *gr, struct sgrp *sg)
+#else
+static void change_passwd (struct group *gr)
+#endif
+{
+ char *cp;
+ static char pass[BUFSIZ];
+ int retries;
+ const char *salt;
+
+ /*
+ * A new password is to be entered and it must be encrypted, etc.
+ * The password will be prompted for twice, and both entries must be
+ * identical. There is no need to validate the old password since
+ * the invoker is either the group owner, or root.
+ */
+ printf (_("Changing the password for group %s\n"), group);
+
+ for (retries = 0; retries < RETRIES; retries++) {
+ cp = getpass (_("New Password: "));
+ if (NULL == cp) {
+ exit (1);
+ }
+
+ STRFCPY (pass, cp);
+ strzero (cp);
+ cp = getpass (_("Re-enter new password: "));
+ if (NULL == cp) {
+ exit (1);
+ }
+
+ if (strcmp (pass, cp) == 0) {
+ strzero (cp);
+ break;
+ }
+
+ strzero (cp);
+ memzero (pass, sizeof pass);
+
+ if (retries + 1 < RETRIES) {
+ puts (_("They don't match; try again"));
+ }
+ }
+
+ if (retries == RETRIES) {
+ fprintf (stderr, _("%s: Try again later\n"), Prog);
+ exit (1);
+ }
+
+ salt = crypt_make_salt (NULL, NULL);
+ cp = pw_encrypt (pass, salt);
+ if (NULL == cp) {
+ fprintf (stderr,
+ _("%s: failed to crypt password with salt '%s': %s\n"),
+ Prog, salt, strerror (errno));
+ exit (1);
+ }
+ memzero (pass, sizeof pass);
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ gr->gr_passwd = SHADOW_PASSWD_STRING;
+ sg->sg_passwd = cp;
+ } else
+#endif
+ {
+ gr->gr_passwd = cp;
+ }
+}
+
+/*
+ * gpasswd - administer the /etc/group file
+ */
+int main (int argc, char **argv)
+{
+ struct group grent;
+#ifdef SHADOWGRP
+ struct sgrp sgent;
+#endif
+ struct passwd *pw = NULL;
+
+#ifdef WITH_AUDIT
+ audit_help_open ();
+#endif
+
+ sanitize_env ();
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ /*
+ * Make a note of whether or not this command was invoked by root.
+ * This will be used to bypass certain checks later on. Also, set
+ * the real user ID to match the effective user ID. This will
+ * prevent the invoker from issuing signals which would interfere
+ * with this command.
+ */
+ bywho = getuid ();
+ Prog = Basename (argv[0]);
+
+ OPENLOG ("gpasswd");
+ setbuf (stdout, NULL);
+ setbuf (stderr, NULL);
+
+ process_root_flag ("-Q", argc, argv);
+
+#ifdef SHADOWGRP
+ is_shadowgrp = sgr_file_present ();
+#endif
+
+ /*
+ * Determine the name of the user that invoked this command. This
+ * is really hit or miss because there are so many ways that command
+ * can be executed and so many ways to trip up the routines that
+ * report the user name.
+ */
+ pw = get_my_pwent ();
+ if (NULL == pw) {
+ fprintf (stderr, _("%s: Cannot determine your user name.\n"),
+ Prog);
+ SYSLOG ((LOG_WARN,
+ "Cannot determine the user name of the caller (UID %lu)",
+ (unsigned long) getuid ()));
+ exit (E_NOPERM);
+ }
+ myname = xstrdup (pw->pw_name);
+
+ /*
+ * Register an exit function to warn for any inconsistency that we
+ * could create.
+ */
+ if (atexit (do_cleanups) != 0) {
+ fprintf(stderr, "%s: cannot set exit function\n", Prog);
+ exit (1);
+ }
+
+ /* Parse the options */
+ process_flags (argc, argv);
+
+ /*
+ * Replicate the group so it can be modified later on.
+ */
+#ifdef SHADOWGRP
+ get_group (&grent, &sgent);
+#else
+ get_group (&grent);
+#endif
+
+ /*
+ * Check if the user is allowed to change the password of this group.
+ */
+#ifdef SHADOWGRP
+ check_perms (&grent, &sgent);
+#else
+ check_perms (&grent);
+#endif
+
+ /*
+ * Removing a password is straight forward. Just set the password
+ * field to a "".
+ */
+ if (rflg) {
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ grent.gr_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
+ sgent.sg_passwd = ""; /* XXX warning: const */
+ } else
+#endif /* SHADOWGRP */
+ {
+ grent.gr_passwd = ""; /* XXX warning: const */
+ }
+ goto output;
+ } else if (Rflg) {
+ /*
+ * Same thing for restricting the group. Set the password
+ * field to "!".
+ */
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ grent.gr_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
+ sgent.sg_passwd = "!"; /* XXX warning: const */
+ } else
+#endif /* SHADOWGRP */
+ {
+ grent.gr_passwd = "!"; /* XXX warning: const */
+ }
+ goto output;
+ }
+
+ /*
+ * Adding a member to a member list is pretty straightforward as
+ * well. Call the appropriate routine and split.
+ */
+ if (aflg) {
+ printf (_("Adding user %s to group %s\n"), user, group);
+ grent.gr_mem = add_list (grent.gr_mem, user);
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ sgent.sg_mem = add_list (sgent.sg_mem, user);
+ }
+#endif
+ goto output;
+ }
+
+ /*
+ * Removing a member from the member list is the same deal as adding
+ * one, except the routine is different.
+ */
+ if (dflg) {
+ bool removed = false;
+
+ printf (_("Removing user %s from group %s\n"), user, group);
+
+ if (is_on_list (grent.gr_mem, user)) {
+ removed = true;
+ grent.gr_mem = del_list (grent.gr_mem, user);
+ }
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ if (is_on_list (sgent.sg_mem, user)) {
+ removed = true;
+ sgent.sg_mem = del_list (sgent.sg_mem, user);
+ }
+ }
+#endif
+ if (!removed) {
+ fprintf (stderr,
+ _("%s: user '%s' is not a member of '%s'\n"),
+ Prog, user, group);
+ exit (E_BAD_ARG);
+ }
+ goto output;
+ }
+#ifdef SHADOWGRP
+ /*
+ * Replacing the entire list of administrators is simple. Check the
+ * list to make sure everyone is a real user. Then slap the new list
+ * in place.
+ */
+ if (Aflg) {
+ sgent.sg_adm = comma_to_list (admins);
+ if (!Mflg) {
+ goto output;
+ }
+ }
+#endif /* SHADOWGRP */
+
+ /*
+ * Replacing the entire list of members is simple. Check the list to
+ * make sure everyone is a real user. Then slap the new list in
+ * place.
+ */
+ if (Mflg) {
+#ifdef SHADOWGRP
+ sgent.sg_mem = comma_to_list (members);
+#endif
+ grent.gr_mem = comma_to_list (members);
+ goto output;
+ }
+
+ /*
+ * If the password is being changed, the input and output must both
+ * be a tty. The typical keyboard signals are caught so the termio
+ * modes can be restored.
+ */
+ if ((isatty (0) == 0) || (isatty (1) == 0)) {
+ fprintf (stderr, _("%s: Not a tty\n"), Prog);
+ exit (E_NOPERM);
+ }
+
+ catch_signals (0); /* save tty modes */
+
+ (void) signal (SIGHUP, catch_signals);
+ (void) signal (SIGINT, catch_signals);
+ (void) signal (SIGQUIT, catch_signals);
+ (void) signal (SIGTERM, catch_signals);
+#ifdef SIGTSTP
+ (void) signal (SIGTSTP, catch_signals);
+#endif
+
+ /* Prompt for the new password */
+#ifdef SHADOWGRP
+ change_passwd (&grent, &sgent);
+#else
+ change_passwd (&grent);
+#endif
+
+ /*
+ * This is the common arrival point to output the new group file.
+ * The freshly crafted entry is in allocated space. The group file
+ * will be locked and opened for writing. The new entry will be
+ * output, etc.
+ */
+ output:
+ if (setuid (0) != 0) {
+ fputs (_("Cannot change ID to root.\n"), stderr);
+ SYSLOG ((LOG_ERR, "can't setuid(0)"));
+ closelog ();
+ exit (E_NOPERM);
+ }
+ pwd_init ();
+
+ open_files ();
+
+#ifdef SHADOWGRP
+ update_group (&grent, &sgent);
+#else
+ update_group (&grent);
+#endif
+
+ close_files ();
+
+ nscd_flush_cache ("group");
+
+ exit (E_SUCCESS);
+}
+
diff --git a/src/groupadd.c b/src/groupadd.c
new file mode 100644
index 0000000..179438f
--- /dev/null
+++ b/src/groupadd.c
@@ -0,0 +1,624 @@
+/*
+ * Copyright (c) 1991 - 1993, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <stdio.h>
+#include <sys/types.h>
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+#include "pam_defs.h"
+#include <pwd.h>
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+#include "chkname.h"
+#include "defines.h"
+#include "getdef.h"
+#include "groupio.h"
+#include "nscd.h"
+#include "prototypes.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+
+/*
+ * exit status values
+ */
+/*@-exitarg@*/
+#define E_SUCCESS 0 /* success */
+#define E_USAGE 2 /* invalid command syntax */
+#define E_BAD_ARG 3 /* invalid argument to option */
+#define E_GID_IN_USE 4 /* gid not unique (when -o not used) */
+#define E_NAME_IN_USE 9 /* group name not unique */
+#define E_GRP_UPDATE 10 /* can't update group file */
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static /*@null@*/char *group_name;
+static gid_t group_id;
+static /*@null@*/char *group_passwd;
+static /*@null@*/char *empty_list = NULL;
+
+static bool oflg = false; /* permit non-unique group ID to be specified with -g */
+static bool gflg = false; /* ID value for the new group */
+static bool fflg = false; /* if group already exists, do nothing and exit(0) */
+static bool rflg = false; /* create a system account */
+static bool pflg = false; /* new encrypted password */
+
+#ifdef SHADOWGRP
+static bool is_shadow_grp;
+#endif
+
+/* local function prototypes */
+static /*@noreturn@*/void usage (int status);
+static void new_grent (struct group *grent);
+
+#ifdef SHADOWGRP
+static void new_sgent (struct sgrp *sgent);
+#endif
+static void grp_update (void);
+static void check_new_name (void);
+static void close_files (void);
+static void open_files (void);
+static void process_flags (int argc, char **argv);
+static void check_flags (void);
+static void check_perms (void);
+
+/*
+ * usage - display usage message and exit
+ */
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options] GROUP\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -f, --force exit successfully if the group already exists,\n"
+ " and cancel -g if the GID is already used\n"), usageout);
+ (void) fputs (_(" -g, --gid GID use GID for the new group\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -K, --key KEY=VALUE override /etc/login.defs defaults\n"), usageout);
+ (void) fputs (_(" -o, --non-unique allow to create groups with duplicate\n"
+ " (non-unique) GID\n"), usageout);
+ (void) fputs (_(" -p, --password PASSWORD use this encrypted password for the new group\n"), usageout);
+ (void) fputs (_(" -r, --system create a system account\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * new_grent - initialize the values in a group file entry
+ *
+ * new_grent() takes all of the values that have been entered and fills
+ * in a (struct group) with them.
+ */
+static void new_grent (struct group *grent)
+{
+ memzero (grent, sizeof *grent);
+ grent->gr_name = group_name;
+ if (pflg) {
+ grent->gr_passwd = group_passwd;
+ } else {
+ grent->gr_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
+ }
+ grent->gr_gid = group_id;
+ grent->gr_mem = &empty_list;
+}
+
+#ifdef SHADOWGRP
+/*
+ * new_sgent - initialize the values in a shadow group file entry
+ *
+ * new_sgent() takes all of the values that have been entered and fills
+ * in a (struct sgrp) with them.
+ */
+static void new_sgent (struct sgrp *sgent)
+{
+ memzero (sgent, sizeof *sgent);
+ sgent->sg_name = group_name;
+ if (pflg) {
+ sgent->sg_passwd = group_passwd;
+ } else {
+ sgent->sg_passwd = "!"; /* XXX warning: const */
+ }
+ sgent->sg_adm = &empty_list;
+ sgent->sg_mem = &empty_list;
+}
+#endif /* SHADOWGRP */
+
+/*
+ * grp_update - add new group file entries
+ *
+ * grp_update() writes the new records to the group files.
+ */
+static void grp_update (void)
+{
+ struct group grp;
+
+#ifdef SHADOWGRP
+ struct sgrp sgrp;
+#endif /* SHADOWGRP */
+
+ /*
+ * To add the group, we need to update /etc/group.
+ * Make sure failures will be reported.
+ */
+ add_cleanup (cleanup_report_add_group_group, group_name);
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ /* We also need to update /etc/gshadow */
+ add_cleanup (cleanup_report_add_group_gshadow, group_name);
+ }
+#endif
+
+ /*
+ * Create the initial entries for this new group.
+ */
+ new_grent (&grp);
+#ifdef SHADOWGRP
+ new_sgent (&sgrp);
+ if (is_shadow_grp && pflg) {
+ grp.gr_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
+ }
+#endif /* SHADOWGRP */
+
+ /*
+ * Write out the new group file entry.
+ */
+ if (gr_update (&grp) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, gr_dbname (), grp.gr_name);
+ exit (E_GRP_UPDATE);
+ }
+#ifdef SHADOWGRP
+ /*
+ * Write out the new shadow group entries as well.
+ */
+ if (is_shadow_grp && (sgr_update (&sgrp) == 0)) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, sgr_dbname (), sgrp.sg_name);
+ exit (E_GRP_UPDATE);
+ }
+#endif /* SHADOWGRP */
+}
+
+/*
+ * check_new_name - check the new name for validity
+ *
+ * check_new_name() insures that the new name doesn't contain any
+ * illegal characters.
+ */
+static void check_new_name (void)
+{
+ if (is_valid_group_name (group_name)) {
+ return;
+ }
+
+ /*
+ * All invalid group names land here.
+ */
+
+ fprintf (stderr, _("%s: '%s' is not a valid group name\n"),
+ Prog, group_name);
+
+ exit (E_BAD_ARG);
+}
+
+/*
+ * close_files - close all of the files that were opened
+ *
+ * close_files() closes all of the files that were opened for this new
+ * group. This causes any modified entries to be written out.
+ */
+static void close_files (void)
+{
+ /* First, write the changes in the regular group database */
+ if (gr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, gr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_GROUP, Prog,
+ "adding group to /etc/group",
+ group_name, (unsigned int) group_id,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ SYSLOG ((LOG_INFO, "group added to %s: name=%s, GID=%u",
+ gr_dbname (), group_name, (unsigned int) group_id));
+ del_cleanup (cleanup_report_add_group_group);
+
+ cleanup_unlock_group (NULL);
+ del_cleanup (cleanup_unlock_group);
+
+ /* Now, write the changes in the shadow database */
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, sgr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_GROUP, Prog,
+ "adding group to /etc/gshadow",
+ group_name, (unsigned int) group_id,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ SYSLOG ((LOG_INFO, "group added to %s: name=%s",
+ sgr_dbname (), group_name));
+ del_cleanup (cleanup_report_add_group_gshadow);
+
+ cleanup_unlock_gshadow (NULL);
+ del_cleanup (cleanup_unlock_gshadow);
+ }
+#endif /* SHADOWGRP */
+
+ /* Report success at the system level */
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_GROUP, Prog,
+ "",
+ group_name, (unsigned int) group_id,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ SYSLOG ((LOG_INFO, "new group: name=%s, GID=%u",
+ group_name, (unsigned int) group_id));
+ del_cleanup (cleanup_report_add_group);
+}
+
+/*
+ * open_files - lock and open the group files
+ *
+ * open_files() opens the two group files.
+ */
+static void open_files (void)
+{
+ /* First, lock the databases */
+ if (gr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, gr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+ add_cleanup (cleanup_unlock_group, NULL);
+
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sgr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+ add_cleanup (cleanup_unlock_gshadow, NULL);
+ }
+#endif /* SHADOWGRP */
+
+ /*
+ * Now if the group is not added, it's our fault.
+ * Make sure failures will be reported.
+ */
+ add_cleanup (cleanup_report_add_group, group_name);
+
+ /* And now open the databases */
+ if (gr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
+ exit (E_GRP_UPDATE);
+ }
+
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", sgr_dbname ()));
+ exit (E_GRP_UPDATE);
+ }
+ }
+#endif /* SHADOWGRP */
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ /*
+ * Parse the command line options.
+ */
+ char *cp;
+ int c;
+ static struct option long_options[] = {
+ {"force", no_argument, NULL, 'f'},
+ {"gid", required_argument, NULL, 'g'},
+ {"help", no_argument, NULL, 'h'},
+ {"key", required_argument, NULL, 'K'},
+ {"non-unique", no_argument, NULL, 'o'},
+ {"password", required_argument, NULL, 'p'},
+ {"system", no_argument, NULL, 'r'},
+ {"root", required_argument, NULL, 'R'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "fg:hK:op:rR:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'f':
+ /*
+ * "force" - do nothing, just exit(0), if the
+ * specified group already exists. With -g, if
+ * specified gid already exists, choose another
+ * (unique) gid (turn off -g). Based on the RedHat's
+ * patch from shadow-utils-970616-9.
+ */
+ fflg = true;
+ break;
+ case 'g':
+ gflg = true;
+ if ( (get_gid (optarg, &group_id) == 0)
+ || (group_id == (gid_t)-1)) {
+ fprintf (stderr,
+ _("%s: invalid group ID '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ break;
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'K':
+ /*
+ * override login.defs defaults (-K name=value)
+ * example: -K GID_MIN=100 -K GID_MAX=499
+ * note: -K GID_MIN=10,GID_MAX=499 doesn't work yet
+ */
+ cp = strchr (optarg, '=');
+ if (NULL == cp) {
+ fprintf (stderr,
+ _("%s: -K requires KEY=VALUE\n"),
+ Prog);
+ exit (E_BAD_ARG);
+ }
+ /* terminate name, point to value */
+ *cp++ = '\0';
+ if (putdef_str (optarg, cp) < 0) {
+ exit (E_BAD_ARG);
+ }
+ break;
+ case 'o':
+ oflg = true;
+ break;
+ case 'p':
+ pflg = true;
+ group_passwd = optarg;
+ break;
+ case 'r':
+ rflg = true;
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ default:
+ usage (E_USAGE);
+ }
+ }
+
+ /*
+ * Check the flags consistency
+ */
+ if (optind != argc - 1) {
+ usage (E_USAGE);
+ }
+ group_name = argv[optind];
+
+ check_flags ();
+}
+
+/*
+ * check_flags - check flags and parameters consistency
+ *
+ * It will not return if an error is encountered.
+ */
+static void check_flags (void)
+{
+ /* -o does not make sense without -g */
+ if (oflg && !gflg) {
+ usage (E_USAGE);
+ }
+
+ check_new_name ();
+
+ /*
+ * Check if the group already exist.
+ */
+ /* local, no need for xgetgrnam */
+ if (getgrnam (group_name) != NULL) {
+ /* The group already exist */
+ if (fflg) {
+ /* OK, no need to do anything */
+ exit (E_SUCCESS);
+ }
+ fprintf (stderr,
+ _("%s: group '%s' already exists\n"),
+ Prog, group_name);
+ exit (E_NAME_IN_USE);
+ }
+
+ if (gflg && (getgrgid (group_id) != NULL)) {
+ /* A GID was specified, and a group already exist with that GID
+ * - either we will use this GID anyway (-o)
+ * - either we ignore the specified GID and
+ * we will use another one (-f)
+ * - either it is a failure
+ */
+ if (oflg) {
+ /* Continue with this GID */
+ } else if (fflg) {
+ /* Turn off -g, we can use any GID */
+ gflg = false;
+ } else {
+ fprintf (stderr,
+ _("%s: GID '%lu' already exists\n"),
+ Prog, (unsigned long int) group_id);
+ exit (E_GID_IN_USE);
+ }
+ }
+}
+
+/*
+ * check_perms - check if the caller is allowed to add a group
+ *
+ * With PAM support, the setuid bit can be set on groupadd to allow
+ * non-root users to groups.
+ * Without PAM support, only users who can write in the group databases
+ * can add groups.
+ *
+ * It will not return if the user is not allowed.
+ */
+static void check_perms (void)
+{
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ pam_handle_t *pamh = NULL;
+ int retval;
+ struct passwd *pampw;
+
+ pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+ if (NULL == pampw) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ exit (1);
+ }
+
+ retval = pam_start ("groupadd", pampw->pw_name, &conv, &pamh);
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_authenticate (pamh, 0);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_acct_mgmt (pamh, 0);
+ }
+
+ if (PAM_SUCCESS != retval) {
+ fprintf (stderr, _("%s: PAM: %s\n"),
+ Prog, pam_strerror (pamh, retval));
+ SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval)));
+ if (NULL != pamh) {
+ (void) pam_end (pamh, retval);
+ }
+ exit (1);
+ }
+ (void) pam_end (pamh, retval);
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+}
+
+/*
+ * main - groupadd command
+ */
+int main (int argc, char **argv)
+{
+ /*
+ * Get my name so that I can use it to report errors.
+ */
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ OPENLOG ("groupadd");
+#ifdef WITH_AUDIT
+ audit_help_open ();
+#endif
+
+ if (atexit (do_cleanups) != 0) {
+ fprintf (stderr,
+ _("%s: Cannot setup cleanup service.\n"),
+ Prog);
+ exit (1);
+ }
+
+ /*
+ * Parse the command line options.
+ */
+ process_flags (argc, argv);
+
+ check_perms ();
+
+#ifdef SHADOWGRP
+ is_shadow_grp = sgr_file_present ();
+#endif
+
+ /*
+ * Do the hard stuff - open the files, create the group entries,
+ * then close and update the files.
+ */
+ open_files ();
+
+ if (!gflg) {
+ if (find_new_gid (rflg, &group_id, NULL) < 0) {
+ exit (E_GID_IN_USE);
+ }
+ }
+
+ grp_update ();
+ close_files ();
+
+ nscd_flush_cache ("group");
+
+ return E_SUCCESS;
+}
+
diff --git a/src/groupdel.c b/src/groupdel.c
new file mode 100644
index 0000000..11e522b
--- /dev/null
+++ b/src/groupdel.c
@@ -0,0 +1,491 @@
+/*
+ * Copyright (c) 1991 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+#include "defines.h"
+#include "groupio.h"
+#include "nscd.h"
+#include "prototypes.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static char *group_name;
+static gid_t group_id = -1;
+static bool check_group_busy = true;
+
+#ifdef SHADOWGRP
+static bool is_shadow_grp;
+#endif
+
+/*
+ * exit status values
+ */
+/*@-exitarg@*/
+#define E_SUCCESS 0 /* success */
+#define E_USAGE 2 /* invalid command syntax */
+#define E_NOTFOUND 6 /* specified group doesn't exist */
+#define E_GROUP_BUSY 8 /* can't remove user's primary group */
+#define E_GRP_UPDATE 10 /* can't update group file */
+
+/* local function prototypes */
+static /*@noreturn@*/void usage (int status);
+static void grp_update (void);
+static void close_files (void);
+static void open_files (void);
+static void group_busy (gid_t gid);
+static void process_flags (int argc, char **argv);
+
+/*
+ * usage - display usage message and exit
+ */
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options] GROUP\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs (_(" -f, --force delete group even if it is the primary group of a user\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * grp_update - update group file entries
+ *
+ * grp_update() writes the new records to the group files.
+ */
+static void grp_update (void)
+{
+ /*
+ * To add the group, we need to update /etc/group.
+ * Make sure failures will be reported.
+ */
+ add_cleanup (cleanup_report_del_group_group, group_name);
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ /* We also need to update /etc/gshadow */
+ add_cleanup (cleanup_report_del_group_gshadow, group_name);
+ }
+#endif
+
+ /*
+ * Delete the group entry.
+ */
+ if (gr_remove (group_name) == 0) {
+ fprintf (stderr,
+ _("%s: cannot remove entry '%s' from %s\n"),
+ Prog, group_name, gr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+
+#ifdef SHADOWGRP
+ /*
+ * Delete the shadow group entries as well.
+ */
+ if (is_shadow_grp && (sgr_locate (group_name) != NULL)) {
+ if (sgr_remove (group_name) == 0) {
+ fprintf (stderr,
+ _("%s: cannot remove entry '%s' from %s\n"),
+ Prog, group_name, sgr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+ }
+#endif /* SHADOWGRP */
+}
+
+/*
+ * close_files - close all of the files that were opened
+ *
+ * close_files() closes all of the files that were opened for this
+ * new group. This causes any modified entries to be written out.
+ */
+static void close_files (void)
+{
+ /* First, write the changes in the regular group database */
+ if (gr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, gr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_GROUP, Prog,
+ "removing group from /etc/group",
+ group_name, (unsigned int) group_id,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ SYSLOG ((LOG_INFO,
+ "group '%s' removed from %s",
+ group_name, gr_dbname ()));
+ del_cleanup (cleanup_report_del_group_group);
+
+ cleanup_unlock_group (NULL);
+ del_cleanup (cleanup_unlock_group);
+
+
+ /* Then, write the changes in the shadow database */
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, sgr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_GROUP, Prog,
+ "removing group from /etc/gshadow",
+ group_name, (unsigned int) group_id,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ SYSLOG ((LOG_INFO,
+ "group '%s' removed from %s",
+ group_name, sgr_dbname ()));
+ del_cleanup (cleanup_report_del_group_gshadow);
+
+ cleanup_unlock_gshadow (NULL);
+ del_cleanup (cleanup_unlock_gshadow);
+ }
+#endif /* SHADOWGRP */
+
+ /* Report success at the system level */
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_GROUP, Prog,
+ "",
+ group_name, (unsigned int) group_id,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ SYSLOG ((LOG_INFO, "group '%s' removed\n", group_name));
+ del_cleanup (cleanup_report_del_group);
+}
+
+/*
+ * open_files - lock and open the group files
+ *
+ * open_files() opens the two group files.
+ */
+static void open_files (void)
+{
+ /* First, lock the databases */
+ if (gr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, gr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+ add_cleanup (cleanup_unlock_group, NULL);
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sgr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+ add_cleanup (cleanup_unlock_gshadow, NULL);
+ }
+#endif
+
+ /*
+ * Now, if the group is not removed, it's our fault.
+ * Make sure failures will be reported.
+ */
+ add_cleanup (cleanup_report_del_group, group_name);
+
+ /* An now open the databases */
+ if (gr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, gr_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
+ exit (E_GRP_UPDATE);
+ }
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", sgr_dbname ()));
+ exit (E_GRP_UPDATE);
+ }
+ }
+#endif /* SHADOWGRP */
+}
+
+/*
+ * group_busy - check if this is any user's primary group
+ *
+ * group_busy verifies that this group is not the primary group
+ * for any user. You must remove all users before you remove
+ * the group.
+ */
+static void group_busy (gid_t gid)
+{
+ struct passwd *pwd;
+
+ /*
+ * Nice slow linear search.
+ */
+
+ setpwent ();
+
+ while ( ((pwd = getpwent ()) != NULL) && (pwd->pw_gid != gid) );
+
+ endpwent ();
+
+ /*
+ * If pwd isn't NULL, it stopped because the gid's matched.
+ */
+
+ if (pwd == (struct passwd *) 0) {
+ return;
+ }
+
+ /*
+ * Can't remove the group.
+ */
+ fprintf (stderr,
+ _("%s: cannot remove the primary group of user '%s'\n"),
+ Prog, pwd->pw_name);
+ exit (E_GROUP_BUSY);
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ /*
+ * Parse the command line options.
+ */
+ int c;
+ static struct option long_options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"root", required_argument, NULL, 'R'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "hfR:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ case 'f':
+ check_group_busy = false;
+ break;
+ default:
+ usage (E_USAGE);
+ }
+ }
+
+ if (optind != argc - 1) {
+ usage (E_USAGE);
+ }
+ group_name = argv[optind];
+}
+
+/*
+ * main - groupdel command
+ *
+ * The syntax of the groupdel command is
+ *
+ * groupdel group
+ *
+ * The named group will be deleted.
+ */
+
+int main (int argc, char **argv)
+{
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ pam_handle_t *pamh = NULL;
+ int retval;
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+
+ /*
+ * Get my name so that I can use it to report errors.
+ */
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ OPENLOG ("groupdel");
+#ifdef WITH_AUDIT
+ audit_help_open ();
+#endif
+
+ if (atexit (do_cleanups) != 0) {
+ fprintf (stderr,
+ _("%s: Cannot setup cleanup service.\n"),
+ Prog);
+ exit (1);
+ }
+
+ process_flags (argc, argv);
+
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ {
+ struct passwd *pampw;
+ pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+ if (pampw == NULL) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ exit (1);
+ }
+
+ retval = pam_start ("groupdel", pampw->pw_name, &conv, &pamh);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_authenticate (pamh, 0);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_acct_mgmt (pamh, 0);
+ }
+
+ if (PAM_SUCCESS != retval) {
+ fprintf (stderr, _("%s: PAM: %s\n"),
+ Prog, pam_strerror (pamh, retval));
+ SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval)));
+ if (NULL != pamh) {
+ (void) pam_end (pamh, retval);
+ }
+ exit (1);
+ }
+ (void) pam_end (pamh, retval);
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+
+#ifdef SHADOWGRP
+ is_shadow_grp = sgr_file_present ();
+#endif
+
+ {
+ struct group *grp;
+ /*
+ * Start with a quick check to see if the group exists.
+ */
+ grp = getgrnam (group_name); /* local, no need for xgetgrnam */
+ if (NULL == grp) {
+ fprintf (stderr,
+ _("%s: group '%s' does not exist\n"),
+ Prog, group_name);
+ exit (E_NOTFOUND);
+ }
+
+ group_id = grp->gr_gid;
+ }
+
+#ifdef USE_NIS
+ /*
+ * Make sure this isn't a NIS group
+ */
+ if (__isgrNIS ()) {
+ char *nis_domain;
+ char *nis_master;
+
+ fprintf (stderr,
+ _("%s: group '%s' is a NIS group\n"),
+ Prog, group_name);
+
+ if (!yp_get_default_domain (&nis_domain) &&
+ !yp_master (nis_domain, "group.byname", &nis_master)) {
+ fprintf (stderr,
+ _("%s: %s is the NIS master\n"),
+ Prog, nis_master);
+ }
+ exit (E_NOTFOUND);
+ }
+#endif
+
+ /*
+ * Make sure this isn't the primary group of anyone.
+ */
+ if (check_group_busy) {
+ group_busy (group_id);
+ }
+
+ /*
+ * Do the hard stuff - open the files, delete the group entries,
+ * then close and update the files.
+ */
+ open_files ();
+
+ grp_update ();
+
+ close_files ();
+
+ nscd_flush_cache ("group");
+
+ return E_SUCCESS;
+}
+
diff --git a/src/groupmems.c b/src/groupmems.c
new file mode 100644
index 0000000..4a49e10
--- /dev/null
+++ b/src/groupmems.c
@@ -0,0 +1,652 @@
+/*
+ * Copyright (c) 2000 , International Business Machines
+ * George Kraft IV, gk4@us.ibm.com, 03/23/2000
+ * Copyright (c) 2000 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <stdio.h>
+#include <sys/types.h>
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif /* USE_PAM */
+#include <pwd.h>
+#include "defines.h"
+#include "prototypes.h"
+#include "groupio.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+
+/* Exit Status Values */
+/*@-exitarg@*/
+#define EXIT_SUCCESS 0 /* success */
+#define EXIT_USAGE 1 /* invalid command syntax */
+#define EXIT_GROUP_FILE 2 /* group file access problems */
+#define EXIT_NOT_ROOT 3 /* not superuser */
+#define EXIT_NOT_EROOT 4 /* not effective superuser */
+#define EXIT_NOT_PRIMARY 5 /* not primary owner of group */
+#define EXIT_NOT_MEMBER 6 /* member of group does not exist */
+#define EXIT_MEMBER_EXISTS 7 /* member of group already exists */
+#define EXIT_INVALID_USER 8 /* specified user does not exist */
+#define EXIT_INVALID_GROUP 9 /* specified group does not exist */
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static char *adduser = NULL;
+static char *deluser = NULL;
+static char *thisgroup = NULL;
+static bool purge = false;
+static bool list = false;
+static int exclusive = 0;
+static bool gr_locked = false;
+#ifdef SHADOWGRP
+/* Indicate if shadow groups are enabled on the system
+ * (/etc/gshadow present) */
+static bool is_shadowgrp;
+static bool sgr_locked = false;
+#endif
+
+/* local function prototypes */
+static char *whoami (void);
+static void add_user (const char *user,
+ const struct group *grp);
+static void remove_user (const char *user,
+ const struct group *grp);
+static void purge_members (const struct group *grp);
+static void display_members (const char *const *members);
+static /*@noreturn@*/void usage (int status);
+static void process_flags (int argc, char **argv);
+static void check_perms (void);
+static void fail_exit (int code);
+#define isroot() (getuid () == 0)
+
+static char *whoami (void)
+{
+ /* local, no need for xgetgrgid */
+ struct group *grp = getgrgid (getgid ());
+ /* local, no need for xgetpwuid */
+ struct passwd *usr = getpwuid (getuid ());
+
+ if ( (NULL != usr)
+ && (NULL != grp)
+ && (0 == strcmp (usr->pw_name, grp->gr_name))) {
+ return xstrdup (usr->pw_name);
+ } else {
+ return NULL;
+ }
+}
+
+/*
+ * add_user - Add an user to the specified group
+ */
+static void add_user (const char *user,
+ const struct group *grp)
+{
+ struct group *newgrp;
+
+ /* Make sure the user is not already part of the group */
+ if (is_on_list (grp->gr_mem, user)) {
+ fprintf (stderr,
+ _("%s: user '%s' is already a member of '%s'\n"),
+ Prog, user, grp->gr_name);
+ fail_exit (EXIT_MEMBER_EXISTS);
+ }
+
+ newgrp = __gr_dup(grp);
+ if (NULL == newgrp) {
+ fprintf (stderr,
+ _("%s: Out of memory. Cannot update %s.\n"),
+ Prog, gr_dbname ());
+ fail_exit (13);
+ }
+
+ /* Add the user to the /etc/group group */
+ newgrp->gr_mem = add_list (newgrp->gr_mem, user);
+
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ const struct sgrp *sg = sgr_locate (newgrp->gr_name);
+ struct sgrp *newsg;
+
+ if (NULL == sg) {
+ /* Create a shadow group based on this group */
+ static struct sgrp sgrent;
+ sgrent.sg_name = xstrdup (newgrp->gr_name);
+ sgrent.sg_mem = dup_list (newgrp->gr_mem);
+ sgrent.sg_adm = (char **) xmalloc (sizeof (char *));
+#ifdef FIRST_MEMBER_IS_ADMIN
+ if (sgrent.sg_mem[0]) {
+ sgrent.sg_adm[0] = xstrdup (sgrent.sg_mem[0]);
+ sgrent.sg_adm[1] = NULL;
+ } else
+#endif
+ {
+ sgrent.sg_adm[0] = NULL;
+ }
+
+ /* Move any password to gshadow */
+ sgrent.sg_passwd = newgrp->gr_passwd;
+ newgrp->gr_passwd = SHADOW_PASSWD_STRING;
+
+ newsg = &sgrent;
+ } else {
+ newsg = __sgr_dup (sg);
+ if (NULL == newsg) {
+ fprintf (stderr,
+ _("%s: Out of memory. Cannot update %s.\n"),
+ Prog, sgr_dbname ());
+ fail_exit (13);
+ }
+ /* Add the user to the members */
+ newsg->sg_mem = add_list (newsg->sg_mem, user);
+ /* Do not touch the administrators */
+ }
+
+ if (sgr_update (newsg) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, sgr_dbname (), newsg->sg_name);
+ fail_exit (13);
+ }
+ }
+#endif
+
+ if (gr_update (newgrp) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, gr_dbname (), newgrp->gr_name);
+ fail_exit (13);
+ }
+}
+
+/*
+ * remove_user - Remove an user from a given group
+ */
+static void remove_user (const char *user,
+ const struct group *grp)
+{
+ struct group *newgrp;
+
+ /* Check if the user is a member of the specified group */
+ if (!is_on_list (grp->gr_mem, user)) {
+ fprintf (stderr,
+ _("%s: user '%s' is not a member of '%s'\n"),
+ Prog, user, grp->gr_name);
+ fail_exit (EXIT_NOT_MEMBER);
+ }
+
+ newgrp = __gr_dup (grp);
+ if (NULL == newgrp) {
+ fprintf (stderr,
+ _("%s: Out of memory. Cannot update %s.\n"),
+ Prog, gr_dbname ());
+ fail_exit (13);
+ }
+
+ /* Remove the user from the /etc/group group */
+ newgrp->gr_mem = del_list (newgrp->gr_mem, user);
+
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ const struct sgrp *sg = sgr_locate (newgrp->gr_name);
+ struct sgrp *newsg;
+
+ if (NULL == sg) {
+ /* Create a shadow group based on this group */
+ static struct sgrp sgrent;
+ sgrent.sg_name = xstrdup (newgrp->gr_name);
+ sgrent.sg_mem = dup_list (newgrp->gr_mem);
+ sgrent.sg_adm = (char **) xmalloc (sizeof (char *));
+#ifdef FIRST_MEMBER_IS_ADMIN
+ if (sgrent.sg_mem[0]) {
+ sgrent.sg_adm[0] = xstrdup (sgrent.sg_mem[0]);
+ sgrent.sg_adm[1] = NULL;
+ } else
+#endif
+ {
+ sgrent.sg_adm[0] = NULL;
+ }
+
+ /* Move any password to gshadow */
+ sgrent.sg_passwd = newgrp->gr_passwd;
+ newgrp->gr_passwd = SHADOW_PASSWD_STRING;
+
+ newsg = &sgrent;
+ } else {
+ newsg = __sgr_dup (sg);
+ if (NULL == newsg) {
+ fprintf (stderr,
+ _("%s: Out of memory. Cannot update %s.\n"),
+ Prog, sgr_dbname ());
+ fail_exit (13);
+ }
+ /* Remove the user from the members */
+ newsg->sg_mem = del_list (newsg->sg_mem, user);
+ /* Remove the user from the administrators */
+ newsg->sg_adm = del_list (newsg->sg_adm, user);
+ }
+
+ if (sgr_update (newsg) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, sgr_dbname (), newsg->sg_name);
+ fail_exit (13);
+ }
+ }
+#endif
+
+ if (gr_update (newgrp) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, gr_dbname (), newgrp->gr_name);
+ fail_exit (13);
+ }
+}
+
+/*
+ * purge_members - Rmeove every members of the specified group
+ */
+static void purge_members (const struct group *grp)
+{
+ struct group *newgrp = __gr_dup (grp);
+
+ if (NULL == newgrp) {
+ fprintf (stderr,
+ _("%s: Out of memory. Cannot update %s.\n"),
+ Prog, gr_dbname ());
+ fail_exit (13);
+ }
+
+ /* Remove all the members of the /etc/group group */
+ newgrp->gr_mem[0] = NULL;
+
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ const struct sgrp *sg = sgr_locate (newgrp->gr_name);
+ struct sgrp *newsg;
+
+ if (NULL == sg) {
+ /* Create a shadow group based on this group */
+ static struct sgrp sgrent;
+ sgrent.sg_name = xstrdup (newgrp->gr_name);
+ sgrent.sg_mem = (char **) xmalloc (sizeof (char *));
+ sgrent.sg_mem[0] = NULL;
+ sgrent.sg_adm = (char **) xmalloc (sizeof (char *));
+ sgrent.sg_adm[0] = NULL;
+
+ /* Move any password to gshadow */
+ sgrent.sg_passwd = newgrp->gr_passwd;
+ newgrp->gr_passwd = xstrdup(SHADOW_PASSWD_STRING);
+
+ newsg = &sgrent;
+ } else {
+ newsg = __sgr_dup (sg);
+ if (NULL == newsg) {
+ fprintf (stderr,
+ _("%s: Out of memory. Cannot update %s.\n"),
+ Prog, sgr_dbname ());
+ fail_exit (13);
+ }
+ /* Remove all the members of the /etc/gshadow
+ * group */
+ newsg->sg_mem[0] = NULL;
+ /* Remove all the administrators of the
+ * /etc/gshadow group */
+ newsg->sg_adm[0] = NULL;
+ }
+
+ if (sgr_update (newsg) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, sgr_dbname (), newsg->sg_name);
+ fail_exit (13);
+ }
+ }
+#endif
+
+ if (gr_update (newgrp) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, gr_dbname (), newgrp->gr_name);
+ fail_exit (13);
+ }
+}
+
+static void display_members (const char *const *members)
+{
+ int i;
+
+ for (i = 0; NULL != members[i]; i++) {
+ printf ("%s ", members[i]);
+
+ if (NULL == members[i + 1]) {
+ printf ("\n");
+ } else {
+ printf (" ");
+ }
+ }
+}
+
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (EXIT_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options] [action]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -g, --group groupname change groupname instead of the user's group\n"
+ " (root only)\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs (_("\n"), usageout);
+ (void) fputs (_("Actions:\n"), usageout);
+ (void) fputs (_(" -a, --add username add username to the members of the group\n"), usageout);
+ (void) fputs (_(" -d, --delete username remove username from the members of the group\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -p, --purge purge all members from the group\n"), usageout);
+ (void) fputs (_(" -l, --list list the members of the group\n"), usageout);
+ exit (status);
+}
+
+/*
+ * process_flags - perform command line argument setting
+ */
+static void process_flags (int argc, char **argv)
+{
+ int c;
+ static struct option long_options[] = {
+ {"add", required_argument, NULL, 'a'},
+ {"delete", required_argument, NULL, 'd'},
+ {"group", required_argument, NULL, 'g'},
+ {"help", no_argument, NULL, 'h'},
+ {"list", no_argument, NULL, 'l'},
+ {"purge", no_argument, NULL, 'p'},
+ {"root", required_argument, NULL, 'R'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "a:d:g:hlpR:",
+ long_options, NULL)) != EOF) {
+ switch (c) {
+ case 'a':
+ adduser = xstrdup (optarg);
+ ++exclusive;
+ break;
+ case 'd':
+ deluser = xstrdup (optarg);
+ ++exclusive;
+ break;
+ case 'g':
+ thisgroup = xstrdup (optarg);
+ break;
+ case 'h':
+ usage (EXIT_SUCCESS);
+ /*@notreached@*/break;
+ case 'l':
+ list = true;
+ ++exclusive;
+ break;
+ case 'p':
+ purge = true;
+ ++exclusive;
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ default:
+ usage (EXIT_USAGE);
+ }
+ }
+
+ if ((exclusive > 1) || (optind < argc)) {
+ usage (EXIT_USAGE);
+ }
+
+ /* local, no need for xgetpwnam */
+ if ( (NULL != adduser)
+ && (getpwnam (adduser) == NULL)) {
+ fprintf (stderr, _("%s: user '%s' does not exist\n"),
+ Prog, adduser);
+ fail_exit (EXIT_INVALID_USER);
+ }
+
+}
+
+static void check_perms (void)
+{
+ if (!list) {
+#ifdef USE_PAM
+ pam_handle_t *pamh = NULL;
+ int retval;
+ struct passwd *pampw;
+
+ pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+ if (NULL == pampw) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ fail_exit (1);
+ }
+
+ retval = pam_start ("groupmems", pampw->pw_name, &conv, &pamh);
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_authenticate (pamh, 0);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_acct_mgmt (pamh, 0);
+ }
+
+ if (PAM_SUCCESS != retval) {
+ fprintf (stderr, _("%s: PAM: %s\n"),
+ Prog, pam_strerror (pamh, retval));
+ SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval)));
+ if (NULL != pamh) {
+ (void) pam_end (pamh, retval);
+ }
+ fail_exit (1);
+ }
+ (void) pam_end (pamh, retval);
+#endif
+ }
+}
+
+static void fail_exit (int code)
+{
+ if (gr_locked) {
+ if (gr_unlock () == 0) {
+ fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+ }
+
+#ifdef SHADOWGRP
+ if (sgr_locked) {
+ if (sgr_unlock () == 0) {
+ fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+ }
+#endif
+
+ exit (code);
+}
+
+static void open_files (void)
+{
+ if (!list) {
+ if (gr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, gr_dbname ());
+ fail_exit (EXIT_GROUP_FILE);
+ }
+ gr_locked = true;
+
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ if (sgr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sgr_dbname ());
+ fail_exit (EXIT_GROUP_FILE);
+ }
+ sgr_locked = true;
+ }
+#endif
+ }
+
+ if (gr_open (list ? O_RDONLY : O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
+ fail_exit (EXIT_GROUP_FILE);
+ }
+
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ if (sgr_open (list ? O_RDONLY : O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, sgr_dbname ());
+ fail_exit (EXIT_GROUP_FILE);
+ }
+ }
+#endif
+}
+
+static void close_files (void)
+{
+ if ((gr_close () == 0) && !list) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", gr_dbname ()));
+ fail_exit (EXIT_GROUP_FILE);
+ }
+ if (gr_locked) {
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+ gr_locked = false;
+ }
+
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ if ((sgr_close () == 0) && !list) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", sgr_dbname ()));
+ fail_exit (EXIT_GROUP_FILE);
+ }
+ if (sgr_locked) {
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+ sgr_locked = false;
+ }
+ }
+#endif
+}
+
+int main (int argc, char **argv)
+{
+ char *name;
+ const struct group *grp;
+
+ /*
+ * Get my name so that I can use it to report errors.
+ */
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ OPENLOG ("groupmems");
+
+#ifdef SHADOWGRP
+ is_shadowgrp = sgr_file_present ();
+#endif
+
+ process_flags (argc, argv);
+
+ if (NULL == thisgroup) {
+ name = whoami ();
+ if (!list && (NULL == name)) {
+ fprintf (stderr, _("%s: your groupname does not match your username\n"), Prog);
+ fail_exit (EXIT_NOT_PRIMARY);
+ }
+ } else {
+ name = thisgroup;
+ if (!list && !isroot ()) {
+ fprintf (stderr, _("%s: only root can use the -g/--group option\n"), Prog);
+ fail_exit (EXIT_NOT_ROOT);
+ }
+ }
+
+ check_perms ();
+
+ open_files ();
+
+ grp = gr_locate (name);
+ if (NULL == grp) {
+ fprintf (stderr, _("%s: group '%s' does not exist in %s\n"),
+ Prog, name, gr_dbname ());
+ fail_exit (EXIT_INVALID_GROUP);
+ }
+
+ if (list) {
+ display_members ((const char *const *)grp->gr_mem);
+ } else if (NULL != adduser) {
+ add_user (adduser, grp);
+ } else if (NULL != deluser) {
+ remove_user (deluser, grp);
+ } else if (purge) {
+ purge_members (grp);
+ }
+
+ close_files ();
+
+ exit (EXIT_SUCCESS);
+}
+
diff --git a/src/groupmod.c b/src/groupmod.c
new file mode 100644
index 0000000..757c1a4
--- /dev/null
+++ b/src/groupmod.c
@@ -0,0 +1,871 @@
+/*
+ * Copyright (c) 1991 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <stdio.h>
+#include <sys/types.h>
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+#include "pam_defs.h"
+#include <pwd.h>
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+#include "chkname.h"
+#include "defines.h"
+#include "groupio.h"
+#include "pwio.h"
+#include "nscd.h"
+#include "prototypes.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+/*
+ * exit status values
+ */
+/*@-exitarg@*/
+#define E_SUCCESS 0 /* success */
+#define E_USAGE 2 /* invalid command syntax */
+#define E_BAD_ARG 3 /* invalid argument to option */
+#define E_GID_IN_USE 4 /* gid already in use (and no -o) */
+#define E_NOTFOUND 6 /* specified group doesn't exist */
+#define E_NAME_IN_USE 9 /* group name already in use */
+#define E_GRP_UPDATE 10 /* can't update group file */
+/*
+ * Global variables
+ */
+const char *Prog;
+
+#ifdef SHADOWGRP
+static bool is_shadow_grp;
+#endif /* SHADOWGRP */
+static char *group_name;
+static char *group_newname;
+static char *group_passwd;
+static gid_t group_id;
+static gid_t group_newid;
+
+static struct cleanup_info_mod info_passwd;
+static struct cleanup_info_mod info_group;
+#ifdef SHADOWGRP
+static struct cleanup_info_mod info_gshadow;
+#endif
+
+static bool
+ oflg = false, /* permit non-unique group ID to be specified with -g */
+ gflg = false, /* new ID value for the group */
+ nflg = false, /* a new name has been specified for the group */
+ pflg = false; /* new encrypted password */
+
+/* local function prototypes */
+static void usage (int status);
+static void new_grent (struct group *);
+
+#ifdef SHADOWGRP
+static void new_sgent (struct sgrp *);
+#endif
+static void grp_update (void);
+static void check_new_gid (void);
+static void check_new_name (void);
+static void process_flags (int, char **);
+static void lock_files (void);
+static void prepare_failure_reports (void);
+static void open_files (void);
+static void close_files (void);
+static void update_primary_groups (gid_t ogid, gid_t ngid);
+
+/*
+ * usage - display usage message and exit
+ */
+
+static void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options] GROUP\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -g, --gid GID change the group ID to GID\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -n, --new-name NEW_GROUP change the name to NEW_GROUP\n"), usageout);
+ (void) fputs (_(" -o, --non-unique allow to use a duplicate (non-unique) GID\n"), usageout);
+ (void) fputs (_(" -p, --password PASSWORD change the password to this (encrypted)\n"
+ " PASSWORD\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * new_grent - updates the values in a group file entry
+ *
+ * new_grent() takes all of the values that have been entered and fills
+ * in a (struct group) with them.
+ */
+static void new_grent (struct group *grent)
+{
+ if (nflg) {
+ grent->gr_name = xstrdup (group_newname);
+ }
+
+ if (gflg) {
+ grent->gr_gid = group_newid;
+ }
+
+ if ( pflg
+#ifdef SHADOWGRP
+ && ( (!is_shadow_grp)
+ || (strcmp (grent->gr_passwd, SHADOW_PASSWD_STRING) != 0))
+#endif
+ ) {
+ /* Update the password in group if there is no gshadow
+ * file or if the password is currently in group
+ * (gr_passwd != "x"). We do not force the usage of
+ * shadow passwords if it was not the case before.
+ */
+ grent->gr_passwd = group_passwd;
+ }
+}
+
+#ifdef SHADOWGRP
+/*
+ * new_sgent - updates the values in a shadow group file entry
+ *
+ * new_sgent() takes all of the values that have been entered and fills
+ * in a (struct sgrp) with them.
+ */
+static void new_sgent (struct sgrp *sgent)
+{
+ if (nflg) {
+ sgent->sg_name = xstrdup (group_newname);
+ }
+
+ /* Always update the shadowed password if there is a shadow entry
+ * (even if shadowed passwords might not be enabled for this group
+ * (gr_passwd != "x")).
+ * It seems better to update the password in both places in case a
+ * shadow and a non shadow entry exist.
+ * This might occur only if there were already both entries.
+ */
+ if (pflg) {
+ sgent->sg_passwd = group_passwd;
+ }
+}
+#endif /* SHADOWGRP */
+
+/*
+ * grp_update - update group file entries
+ *
+ * grp_update() updates the new records in the memory databases.
+ */
+static void grp_update (void)
+{
+ struct group grp;
+ const struct group *ogrp;
+
+#ifdef SHADOWGRP
+ struct sgrp sgrp;
+ const struct sgrp *osgrp = NULL;
+#endif /* SHADOWGRP */
+
+ /*
+ * Get the current settings for this group.
+ */
+ ogrp = gr_locate (group_name);
+ if (NULL == ogrp) {
+ fprintf (stderr,
+ _("%s: group '%s' does not exist in %s\n"),
+ Prog, group_name, gr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+ grp = *ogrp;
+ new_grent (&grp);
+#ifdef SHADOWGRP
+ if ( is_shadow_grp
+ && (pflg || nflg)) {
+ osgrp = sgr_locate (group_name);
+ if (NULL != osgrp) {
+ sgrp = *osgrp;
+ new_sgent (&sgrp);
+ } else if ( pflg
+ && (strcmp (grp.gr_passwd, SHADOW_PASSWD_STRING) == 0)) {
+ static char *empty = NULL;
+ /* If there is a gshadow file with no entries for
+ * the group, but the group file indicates a
+ * shadowed password, we force the creation of a
+ * gshadow entry when a new password is requested.
+ */
+ memset (&sgrp, 0, sizeof sgrp);
+ sgrp.sg_name = xstrdup (grp.gr_name);
+ sgrp.sg_passwd = xstrdup (grp.gr_passwd);
+ sgrp.sg_adm = &empty;
+ sgrp.sg_mem = dup_list (grp.gr_mem);
+ new_sgent (&sgrp);
+ osgrp = &sgrp; /* entry needs to be committed */
+ }
+ }
+#endif /* SHADOWGRP */
+
+ if (gflg) {
+ update_primary_groups (ogrp->gr_gid, group_newid);
+ }
+
+ /*
+ * Write out the new group file entry.
+ */
+ if (gr_update (&grp) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, gr_dbname (), grp.gr_name);
+ exit (E_GRP_UPDATE);
+ }
+ if (nflg && (gr_remove (group_name) == 0)) {
+ fprintf (stderr,
+ _("%s: cannot remove entry '%s' from %s\n"),
+ Prog, grp.gr_name, gr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+
+#ifdef SHADOWGRP
+ /*
+ * Make sure there was a shadow entry to begin with.
+ */
+ if (NULL != osgrp) {
+ /*
+ * Write out the new shadow group entries as well.
+ */
+ if (sgr_update (&sgrp) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, sgr_dbname (), sgrp.sg_name);
+ exit (E_GRP_UPDATE);
+ }
+ if (nflg && (sgr_remove (group_name) == 0)) {
+ fprintf (stderr,
+ _("%s: cannot remove entry '%s' from %s\n"),
+ Prog, group_name, sgr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+ }
+#endif /* SHADOWGRP */
+}
+
+/*
+ * check_new_gid - check the new GID value for uniqueness
+ *
+ * check_new_gid() insures that the new GID value is unique.
+ */
+static void check_new_gid (void)
+{
+ /*
+ * First, the easy stuff. If the ID can be duplicated, or if the ID
+ * didn't really change, just return. If the ID didn't change, turn
+ * off those flags. No sense doing needless work.
+ */
+ if (group_id == group_newid) {
+ gflg = 0;
+ return;
+ }
+
+ if (oflg ||
+ (getgrgid (group_newid) == NULL) /* local, no need for xgetgrgid */
+ ) {
+ return;
+ }
+
+ /*
+ * Tell the user what they did wrong.
+ */
+ fprintf (stderr,
+ _("%s: GID '%lu' already exists\n"),
+ Prog, (unsigned long int) group_newid);
+ exit (E_GID_IN_USE);
+}
+
+/*
+ * check_new_name - check the new name for uniqueness
+ *
+ * check_new_name() insures that the new name does not exist already.
+ * You can't have the same name twice, period.
+ */
+static void check_new_name (void)
+{
+ /*
+ * Make sure they are actually changing the name.
+ */
+ if (strcmp (group_name, group_newname) == 0) {
+ nflg = 0;
+ return;
+ }
+
+ if (is_valid_group_name (group_newname)) {
+
+ /*
+ * If the entry is found, too bad.
+ */
+ /* local, no need for xgetgrnam */
+ if (getgrnam (group_newname) != NULL) {
+ fprintf (stderr,
+ _("%s: group '%s' already exists\n"),
+ Prog, group_newname);
+ exit (E_NAME_IN_USE);
+ }
+ return;
+ }
+
+ /*
+ * All invalid group names land here.
+ */
+
+ fprintf (stderr,
+ _("%s: invalid group name '%s'\n"),
+ Prog, group_newname);
+ exit (E_BAD_ARG);
+}
+
+/*
+ * process_flags - perform command line argument setting
+ *
+ * process_flags() interprets the command line arguments and sets the
+ * values that the user will be created with accordingly. The values
+ * are checked for sanity.
+ */
+static void process_flags (int argc, char **argv)
+{
+ int c;
+ static struct option long_options[] = {
+ {"gid", required_argument, NULL, 'g'},
+ {"help", no_argument, NULL, 'h'},
+ {"new-name", required_argument, NULL, 'n'},
+ {"non-unique", no_argument, NULL, 'o'},
+ {"password", required_argument, NULL, 'p'},
+ {"root", required_argument, NULL, 'R'},
+ {NULL, 0, NULL, '\0'}
+ };
+ while ((c = getopt_long (argc, argv, "g:hn:op:R:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'g':
+ gflg = true;
+ if ( (get_gid (optarg, &group_newid) == 0)
+ || (group_newid == (gid_t)-1)) {
+ fprintf (stderr,
+ _("%s: invalid group ID '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ break;
+ case 'h':
+ usage (E_SUCCESS);
+ break;
+ case 'n':
+ nflg = true;
+ group_newname = optarg;
+ break;
+ case 'o':
+ oflg = true;
+ break;
+ case 'p':
+ group_passwd = optarg;
+ pflg = true;
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ default:
+ usage (E_USAGE);
+ }
+ }
+
+ if (oflg && !gflg) {
+ usage (E_USAGE);
+ }
+
+ if (optind != (argc - 1)) {
+ usage (E_USAGE);
+ }
+
+ group_name = argv[argc - 1];
+}
+
+/*
+ * close_files - close all of the files that were opened
+ *
+ * close_files() closes all of the files that were opened for this new
+ * group. This causes any modified entries to be written out.
+ */
+static void close_files (void)
+{
+ if (gr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, gr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_ACCT, Prog,
+ info_group.audit_msg,
+ group_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ SYSLOG ((LOG_INFO,
+ "group changed in %s (%s)",
+ gr_dbname (), info_group.action));
+ del_cleanup (cleanup_report_mod_group);
+
+ cleanup_unlock_group (NULL);
+ del_cleanup (cleanup_unlock_group);
+
+#ifdef SHADOWGRP
+ if ( is_shadow_grp
+ && (pflg || nflg)) {
+ if (sgr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, sgr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_ACCT, Prog,
+ info_gshadow.audit_msg,
+ group_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ SYSLOG ((LOG_INFO,
+ "group changed in %s (%s)",
+ sgr_dbname (), info_gshadow.action));
+ del_cleanup (cleanup_report_mod_gshadow);
+
+ cleanup_unlock_gshadow (NULL);
+ del_cleanup (cleanup_unlock_gshadow);
+ }
+#endif /* SHADOWGRP */
+
+ if (gflg) {
+ if (pw_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, pw_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_ACCT, Prog,
+ info_passwd.audit_msg,
+ group_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ SYSLOG ((LOG_INFO,
+ "group changed in %s (%s)",
+ pw_dbname (), info_passwd.action));
+ del_cleanup (cleanup_report_mod_passwd);
+
+ cleanup_unlock_passwd (NULL);
+ del_cleanup (cleanup_unlock_passwd);
+ }
+
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_ACCT, Prog,
+ "modifying group",
+ group_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+}
+
+/*
+ * prepare_failure_reports - Prepare the cleanup_info structure for logging
+ * of success and failure to syslog or audit.
+ */
+static void prepare_failure_reports (void)
+{
+ info_group.name = group_name;
+#ifdef SHADOWGRP
+ info_gshadow.name = group_name;
+#endif
+ info_passwd.name = group_name;
+
+ info_group.audit_msg = xmalloc (512);
+#ifdef SHADOWGRP
+ info_gshadow.audit_msg = xmalloc (512);
+#endif
+ info_passwd.audit_msg = xmalloc (512);
+
+ (void) snprintf (info_group.audit_msg, 511,
+ "changing %s; ", gr_dbname ());
+#ifdef SHADOWGRP
+ (void) snprintf (info_gshadow.audit_msg, 511,
+ "changing %s; ", sgr_dbname ());
+#endif
+ (void) snprintf (info_passwd.audit_msg, 511,
+ "changing %s; ", pw_dbname ());
+
+ info_group.action = info_group.audit_msg
+ + strlen (info_group.audit_msg);
+#ifdef SHADOWGRP
+ info_gshadow.action = info_gshadow.audit_msg
+ + strlen (info_gshadow.audit_msg);
+#endif
+ info_passwd.action = info_passwd.audit_msg
+ + strlen (info_passwd.audit_msg);
+
+ (void) snprintf (info_group.action,
+ 511 - strlen (info_group.audit_msg),
+ "group %s/%lu",
+ group_name, (unsigned long int) group_id);
+#ifdef SHADOWGRP
+ (void) snprintf (info_gshadow.action,
+ 511 - strlen (info_group.audit_msg),
+ "group %s", group_name);
+#endif
+ (void) snprintf (info_passwd.action,
+ 511 - strlen (info_group.audit_msg),
+ "group %s/%lu",
+ group_name, (unsigned long int) group_id);
+
+ if (nflg) {
+ strncat (info_group.action, ", new name: ",
+ 511 - strlen (info_group.audit_msg));
+ strncat (info_group.action, group_newname,
+ 511 - strlen (info_group.audit_msg));
+
+#ifdef SHADOWGRP
+ strncat (info_gshadow.action, ", new name: ",
+ 511 - strlen (info_gshadow.audit_msg));
+ strncat (info_gshadow.action, group_newname,
+ 511 - strlen (info_gshadow.audit_msg));
+#endif
+
+ strncat (info_passwd.action, ", new name: ",
+ 511 - strlen (info_passwd.audit_msg));
+ strncat (info_passwd.action, group_newname,
+ 511 - strlen (info_passwd.audit_msg));
+ }
+ if (pflg) {
+ strncat (info_group.action, ", new password",
+ 511 - strlen (info_group.audit_msg));
+
+#ifdef SHADOWGRP
+ strncat (info_gshadow.action, ", new password",
+ 511 - strlen (info_gshadow.audit_msg));
+#endif
+ }
+ if (gflg) {
+ strncat (info_group.action, ", new gid: ",
+ 511 - strlen (info_group.audit_msg));
+ (void) snprintf (info_group.action+strlen (info_group.action),
+ 511 - strlen (info_group.audit_msg),
+ "%lu", (unsigned long int) group_newid);
+
+ strncat (info_passwd.action, ", new gid: ",
+ 511 - strlen (info_passwd.audit_msg));
+ (void) snprintf (info_passwd.action+strlen (info_passwd.action),
+ 511 - strlen (info_passwd.audit_msg),
+ "%lu", (unsigned long int) group_newid);
+ }
+ info_group.audit_msg[511] = '\0';
+#ifdef SHADOWGRP
+ info_gshadow.audit_msg[511] = '\0';
+#endif
+ info_passwd.audit_msg[511] = '\0';
+
+// FIXME: add a system cleanup
+ add_cleanup (cleanup_report_mod_group, &info_group);
+#ifdef SHADOWGRP
+ if ( is_shadow_grp
+ && (pflg || nflg)) {
+ add_cleanup (cleanup_report_mod_gshadow, &info_gshadow);
+ }
+#endif
+ if (gflg) {
+ add_cleanup (cleanup_report_mod_passwd, &info_passwd);
+ }
+
+}
+
+/*
+ * lock_files - lock the accounts databases
+ *
+ * lock_files() locks the group, gshadow, and passwd databases.
+ */
+static void lock_files (void)
+{
+ if (gr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, gr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+ add_cleanup (cleanup_unlock_group, NULL);
+
+#ifdef SHADOWGRP
+ if ( is_shadow_grp
+ && (pflg || nflg)) {
+ if (sgr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sgr_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+ add_cleanup (cleanup_unlock_gshadow, NULL);
+ }
+#endif
+
+ if (gflg) {
+ if (pw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, pw_dbname ());
+ exit (E_GRP_UPDATE);
+ }
+ add_cleanup (cleanup_unlock_passwd, NULL);
+ }
+}
+
+
+/*
+ * open_files - open the accounts databases
+ *
+ * open_files() opens the group, gshadow, and passwd databases.
+ */
+static void open_files (void)
+{
+ if (gr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
+ exit (E_GRP_UPDATE);
+ }
+
+#ifdef SHADOWGRP
+ if ( is_shadow_grp
+ && (pflg || nflg)) {
+ if (sgr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", sgr_dbname ()));
+ exit (E_GRP_UPDATE);
+ }
+ }
+#endif /* SHADOWGRP */
+
+ if (gflg) {
+ if (pw_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, pw_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
+ exit (E_GRP_UPDATE);
+ }
+ }
+}
+
+void update_primary_groups (gid_t ogid, gid_t ngid)
+{
+ struct passwd *pwd;
+
+ setpwent ();
+ while ((pwd = getpwent ()) != NULL) {
+ if (pwd->pw_gid == ogid) {
+ const struct passwd *lpwd;
+ struct passwd npwd;
+ lpwd = pw_locate (pwd->pw_name);
+ if (NULL == lpwd) {
+ fprintf (stderr,
+ _("%s: user '%s' does not exist in %s\n"),
+ Prog, pwd->pw_name, pw_dbname ());
+ exit (E_GRP_UPDATE);
+ } else {
+ npwd = *lpwd;
+ npwd.pw_gid = ngid;
+ if (pw_update (&npwd) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, pw_dbname (), npwd.pw_name);
+ exit (E_GRP_UPDATE);
+ }
+ }
+ }
+ }
+ endpwent ();
+}
+
+/*
+ * main - groupmod command
+ *
+ */
+int main (int argc, char **argv)
+{
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ pam_handle_t *pamh = NULL;
+ int retval;
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+
+ /*
+ * Get my name so that I can use it to report errors.
+ */
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ OPENLOG ("groupmod");
+#ifdef WITH_AUDIT
+ audit_help_open ();
+#endif
+
+ if (atexit (do_cleanups) != 0) {
+ fprintf (stderr,
+ _("%s: Cannot setup cleanup service.\n"),
+ Prog);
+ exit (1);
+ }
+
+ process_flags (argc, argv);
+
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ {
+ struct passwd *pampw;
+ pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+ if (NULL == pampw) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ exit (1);
+ }
+
+ retval = pam_start ("groupmod", pampw->pw_name, &conv, &pamh);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_authenticate (pamh, 0);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_acct_mgmt (pamh, 0);
+ }
+
+ if (PAM_SUCCESS != retval) {
+ fprintf (stderr, _("%s: PAM: %s\n"),
+ Prog, pam_strerror (pamh, retval));
+ SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval)));
+ if (NULL != pamh) {
+ (void) pam_end (pamh, retval);
+ }
+ exit (1);
+ }
+ (void) pam_end (pamh, retval);
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+
+#ifdef SHADOWGRP
+ is_shadow_grp = sgr_file_present ();
+#endif
+ {
+ struct group *grp;
+ /*
+ * Start with a quick check to see if the group exists.
+ */
+ grp = getgrnam (group_name); /* local, no need for xgetgrnam */
+ if (NULL == grp) {
+ fprintf (stderr,
+ _("%s: group '%s' does not exist\n"),
+ Prog, group_name);
+ exit (E_NOTFOUND);
+ } else {
+ group_id = grp->gr_gid;
+ }
+ }
+
+#ifdef USE_NIS
+ /*
+ * Now make sure it isn't an NIS group.
+ */
+ if (__isgrNIS ()) {
+ char *nis_domain;
+ char *nis_master;
+
+ fprintf (stderr,
+ _("%s: group %s is a NIS group\n"),
+ Prog, group_name);
+
+ if (!yp_get_default_domain (&nis_domain) &&
+ !yp_master (nis_domain, "group.byname", &nis_master)) {
+ fprintf (stderr,
+ _("%s: %s is the NIS master\n"),
+ Prog, nis_master);
+ }
+ exit (E_NOTFOUND);
+ }
+#endif
+
+ if (gflg) {
+ check_new_gid ();
+ }
+
+ if (nflg) {
+ check_new_name ();
+ }
+
+ lock_files ();
+
+ /*
+ * Now if the group is not changed, it's our fault.
+ * Make sure failures will be reported.
+ */
+ prepare_failure_reports ();
+
+ /*
+ * Do the hard stuff - open the files, create the group entries,
+ * then close and update the files.
+ */
+ open_files ();
+
+ grp_update ();
+
+ close_files ();
+
+ nscd_flush_cache ("group");
+
+ return E_SUCCESS;
+}
+
diff --git a/src/groups.c b/src/groups.c
new file mode 100644
index 0000000..fcd669b
--- /dev/null
+++ b/src/groups.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 1991 - 1993, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2001 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2008, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include "defines.h"
+#include "prototypes.h"
+/*
+ * Global variables
+ */
+const char *Prog;
+
+/* local function prototypes */
+static void print_groups (const char *member);
+
+/*
+ * print_groups - print the groups which the named user is a member of
+ *
+ * print_groups() scans the groups file for the list of groups which
+ * the user is listed as being a member of.
+ */
+static void print_groups (const char *member)
+{
+ int groups = 0;
+ struct group *grp;
+ struct passwd *pwd;
+ bool flag = false;
+
+ pwd = getpwnam (member); /* local, no need for xgetpwnam */
+ if (NULL == pwd) {
+ (void) fprintf (stderr, _("%s: unknown user %s\n"),
+ Prog, member);
+ exit (EXIT_FAILURE);
+ }
+
+ setgrent ();
+ while ((grp = getgrent ()) != NULL) {
+ if (is_on_list (grp->gr_mem, member)) {
+ if (0 != groups) {
+ (void) putchar (' ');
+ }
+ groups++;
+
+ (void) printf ("%s", grp->gr_name);
+ if (grp->gr_gid == pwd->pw_gid) {
+ flag = true;
+ }
+ }
+ }
+ endgrent ();
+
+ /* The user may not be in the list of members of its primary group */
+ if (!flag) {
+ grp = getgrgid (pwd->pw_gid); /* local, no need for xgetgrgid */
+ if (NULL != grp) {
+ if (0 != groups) {
+ (void) putchar (' ');
+ }
+ groups++;
+
+ (void) printf ("%s", grp->gr_name);
+ }
+ }
+
+ if (0 != groups) {
+ (void) putchar ('\n');
+ }
+}
+
+/*
+ * groups - print out the groups a process is a member of
+ */
+int main (int argc, char **argv)
+{
+#ifdef HAVE_GETGROUPS
+ long sys_ngroups;
+ GETGROUPS_T *groups;
+#else
+ char *logname;
+ char *getlogin ();
+#endif
+
+#ifdef HAVE_GETGROUPS
+ sys_ngroups = sysconf (_SC_NGROUPS_MAX);
+ groups = (GETGROUPS_T *) malloc (sizeof (GETGROUPS_T) * sys_ngroups);
+#endif
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ /*
+ * Get the program name so that error messages can use it.
+ */
+ Prog = Basename (argv[0]);
+
+ if (argc == 1) {
+
+ /*
+ * Called with no arguments - give the group set for the
+ * current user.
+ */
+
+#ifdef HAVE_GETGROUPS
+ int i;
+ int pri_grp; /* TODO: should be GETGROUPS_T */
+ /*
+ * This system supports concurrent group sets, so I can ask
+ * the system to tell me which groups are currently set for
+ * this process.
+ */
+ int ngroups = getgroups (sys_ngroups, groups);
+ if (ngroups < 0) {
+ perror ("getgroups");
+ exit (EXIT_FAILURE);
+ }
+
+ /*
+ * The groupset includes the primary group as well.
+ */
+ pri_grp = getegid ();
+ for (i = 0; i < ngroups; i++) {
+ if (pri_grp == (int) groups[i]) {
+ break;
+ }
+ }
+
+ if (i != ngroups) {
+ pri_grp = -1;
+ }
+
+ /*
+ * Print out the name of every group in the current group
+ * set. Unknown groups are printed as their decimal group ID
+ * values.
+ */
+ if (-1 != pri_grp) {
+ struct group *gr;
+ /* local, no need for xgetgrgid */
+ gr = getgrgid (pri_grp);
+ if (NULL != gr) {
+ (void) printf ("%s", gr->gr_name);
+ } else {
+ (void) printf ("%d", pri_grp);
+ }
+ }
+
+ for (i = 0; i < ngroups; i++) {
+ struct group *gr;
+ if ((0 != i) || (-1 != pri_grp)) {
+ (void) putchar (' ');
+ }
+
+ /* local, no need for xgetgrgid */
+ gr = getgrgid (groups[i]);
+ if (NULL != gr) {
+ (void) printf ("%s", gr->gr_name);
+ } else {
+ (void) printf ("%ld", (long) groups[i]);
+ }
+ }
+ (void) putchar ('\n');
+#else
+ /*
+ * This system does not have the getgroups() system call, so
+ * I must check the groups file directly.
+ */
+ logname = getlogin ();
+ if (NULL != logname) {
+ print_groups (logname);
+ } else {
+ exit (EXIT_FAILURE);
+ }
+#endif
+ } else {
+
+ /*
+ * The invoker wanted to know about some other user. Use
+ * that name to look up the groups instead.
+ */
+ print_groups (argv[1]);
+ }
+ return EXIT_SUCCESS;
+}
+
diff --git a/src/grpck.c b/src/grpck.c
new file mode 100644
index 0000000..ea5d3b3
--- /dev/null
+++ b/src/grpck.c
@@ -0,0 +1,887 @@
+/*
+ * Copyright (c) 1992 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2001 , Michał Moskal
+ * Copyright (c) 2001 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <getopt.h>
+#include "chkname.h"
+#include "commonio.h"
+#include "defines.h"
+#include "groupio.h"
+#include "nscd.h"
+#include "prototypes.h"
+
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+
+/*
+ * Exit codes
+ */
+/*@-exitarg@*/
+#define E_OKAY 0
+#define E_SUCCESS 0
+#define E_USAGE 1
+#define E_BAD_ENTRY 2
+#define E_CANT_OPEN 3
+#define E_CANT_LOCK 4
+#define E_CANT_UPDATE 5
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static const char *grp_file = GROUP_FILE;
+static bool use_system_grp_file = true;
+
+#ifdef SHADOWGRP
+static const char *sgr_file = SGROUP_FILE;
+static bool use_system_sgr_file = true;
+static bool is_shadow = false;
+static bool sgr_locked = false;
+#endif
+static bool gr_locked = false;
+/* Options */
+static bool read_only = false;
+static bool sort_mode = false;
+
+/* local function prototypes */
+static void fail_exit (int status);
+static /*@noreturn@*/void usage (int status);
+static void delete_member (char **, const char *);
+static void process_flags (int argc, char **argv);
+static void open_files (void);
+static void close_files (bool changed);
+static int check_members (const char *groupname,
+ char **members,
+ const char *fmt_info,
+ const char *fmt_prompt,
+ const char *fmt_syslog,
+ int *errors);
+static void check_grp_file (int *errors, bool *changed);
+#ifdef SHADOWGRP
+static void compare_members_lists (const char *groupname,
+ char **members,
+ char **other_members,
+ const char *file,
+ const char *other_file);
+static void check_sgr_file (int *errors, bool *changed);
+#endif
+
+/*
+ * fail_exit - exit with an error code after unlocking files
+ */
+static void fail_exit (int status)
+{
+ if (gr_locked) {
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+ }
+
+#ifdef SHADOWGRP
+ if (sgr_locked) {
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+ }
+#endif
+
+ closelog ();
+
+ exit (status);
+}
+
+/*
+ * usage - print syntax message and exit
+ */
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+#ifdef SHADOWGRP
+ (void) fprintf (usageout,
+ _("Usage: %s [options] [group [gshadow]]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+#else /* !SHADOWGRP */
+ (void) fprintf (usageout,
+ _("Usage: %s [options] [group]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+#endif /* !SHADOWGRP */
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -r, --read-only display errors and warnings\n"
+ " but do not change files\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs (_(" -s, --sort sort entries by UID\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * delete_member - delete an entry in a list of members
+ *
+ * It only deletes the first entry with the given name.
+ * The member is defined by its address, no string comparison are
+ * performed.
+ */
+static void delete_member (char **list, const char *member)
+{
+ int i;
+
+ for (i = 0; NULL != list[i]; i++) {
+ if (list[i] == member) {
+ break;
+ }
+ }
+
+ for (; NULL != list[i]; i++) {
+ list[i] = list[i + 1];
+ }
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ int c;
+ static struct option long_options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"read-only", no_argument, NULL, 'r'},
+ {"root", required_argument, NULL, 'R'},
+ {"sort", no_argument, NULL, 's'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ /*
+ * Parse the command line arguments
+ */
+ while ((c = getopt_long (argc, argv, "hqrR:s",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'q':
+ /* quiet - ignored for now */
+ break;
+ case 'r':
+ read_only = true;
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ case 's':
+ sort_mode = true;
+ break;
+ default:
+ usage (E_USAGE);
+ }
+ }
+
+ if (sort_mode && read_only) {
+ fprintf (stderr, _("%s: -s and -r are incompatible\n"), Prog);
+ exit (E_USAGE);
+ }
+
+ /*
+ * Make certain we have the right number of arguments
+ */
+#ifdef SHADOWGRP
+ if (argc > (optind + 2))
+#else
+ if (argc > (optind + 1))
+#endif
+ {
+ usage (E_USAGE);
+ }
+
+ /*
+ * If there are two left over filenames, use those as the group and
+ * group password filenames.
+ */
+ if (optind != argc) {
+ grp_file = argv[optind];
+ gr_setdbname (grp_file);
+ use_system_grp_file = false;
+ }
+#ifdef SHADOWGRP
+ if ((optind + 2) == argc) {
+ sgr_file = argv[optind + 1];
+ sgr_setdbname (sgr_file);
+ is_shadow = true;
+ use_system_sgr_file = false;
+ } else if (optind == argc) {
+ is_shadow = sgr_file_present ();
+ }
+#endif
+}
+
+/*
+ * open_files - open the shadow database
+ *
+ * In read-only mode, the databases are not locked and are opened
+ * only for reading.
+ */
+static void open_files (void)
+{
+ /*
+ * Lock the files if we aren't in "read-only" mode
+ */
+ if (!read_only) {
+ if (gr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, grp_file);
+ fail_exit (E_CANT_LOCK);
+ }
+ gr_locked = true;
+#ifdef SHADOWGRP
+ if (is_shadow) {
+ if (sgr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sgr_file);
+ fail_exit (E_CANT_LOCK);
+ }
+ sgr_locked = true;
+ }
+#endif
+ }
+
+ /*
+ * Open the files. Use O_RDONLY if we are in read_only mode,
+ * O_RDWR otherwise.
+ */
+ if (gr_open (read_only ? O_RDONLY : O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog,
+ grp_file);
+ if (use_system_grp_file) {
+ SYSLOG ((LOG_WARN, "cannot open %s", grp_file));
+ }
+ fail_exit (E_CANT_OPEN);
+ }
+#ifdef SHADOWGRP
+ if (is_shadow && (sgr_open (read_only ? O_RDONLY : O_CREAT | O_RDWR) == 0)) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog,
+ sgr_file);
+ if (use_system_sgr_file) {
+ SYSLOG ((LOG_WARN, "cannot open %s", sgr_file));
+ }
+ fail_exit (E_CANT_OPEN);
+ }
+#endif
+}
+
+/*
+ * close_files - close and unlock the group/gshadow databases
+ *
+ * If changed is not set, the databases are not closed, and no
+ * changes are committed in the databases. The databases are
+ * unlocked anyway.
+ */
+static void close_files (bool changed)
+{
+ /*
+ * All done. If there were no change we can just abandon any
+ * changes to the files.
+ */
+ if (changed) {
+ if (gr_close () == 0) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"),
+ Prog, grp_file);
+ fail_exit (E_CANT_UPDATE);
+ }
+#ifdef SHADOWGRP
+ if (is_shadow && (sgr_close () == 0)) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"),
+ Prog, sgr_file);
+ fail_exit (E_CANT_UPDATE);
+ }
+#endif
+ }
+
+ /*
+ * Don't be anti-social - unlock the files when you're done.
+ */
+#ifdef SHADOWGRP
+ if (sgr_locked) {
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+ sgr_locked = false;
+ }
+#endif
+ if (gr_locked) {
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+ gr_locked = false;
+ }
+}
+
+/*
+ * check_members - check that every members of a group exist
+ *
+ * If an error is detected, *errors is incremented.
+ *
+ * The user will be prompted for the removal of the non-existent
+ * user.
+ *
+ * If any changes are performed, the return value will be 1,
+ * otherwise check_members() returns 0.
+ *
+ * fmt_info, fmt_prompt, and fmt_syslog are used for logging.
+ * * fmt_info must contain two string flags (%s): for the group's
+ * name and the missing member.
+ * * fmt_prompt must contain one string flags (%s): the missing
+ * member.
+ * * fmt_syslog must contain two string flags (%s): for the
+ * group's name and the missing member.
+ */
+static int check_members (const char *groupname,
+ char **members,
+ const char *fmt_info,
+ const char *fmt_prompt,
+ const char *fmt_syslog,
+ int *errors)
+{
+ int i;
+ int members_changed = 0;
+
+ /*
+ * Make sure each member exists
+ */
+ for (i = 0; NULL != members[i]; i++) {
+ /* local, no need for xgetpwnam */
+ if (getpwnam (members[i]) != NULL) {
+ continue;
+ }
+ /*
+ * Can't find this user. Remove them
+ * from the list.
+ */
+ *errors += 1;
+ printf (fmt_info, groupname, members[i]);
+ printf (fmt_prompt, members[i]);
+
+ if (!yes_or_no (read_only)) {
+ continue;
+ }
+
+ SYSLOG ((LOG_INFO, fmt_syslog, members[i], groupname));
+ members_changed = 1;
+ delete_member (members, members[i]);
+
+ /* Rewind in case of removal */
+ i--;
+ }
+
+ return members_changed;
+}
+
+#ifdef SHADOWGRP
+/*
+ * compare_members_lists - make sure the list of members is contained in
+ * another list.
+ *
+ * compare_members_lists() checks that all the members of members are
+ * also in other_members.
+ * file and other_file are used for logging.
+ *
+ * TODO: No changes are performed on the lists.
+ */
+static void compare_members_lists (const char *groupname,
+ char **members,
+ char **other_members,
+ const char *file,
+ const char *other_file)
+{
+ char **pmem, **other_pmem;
+
+ for (pmem = members; NULL != *pmem; pmem++) {
+ for (other_pmem = other_members; NULL != *other_pmem; other_pmem++) {
+ if (strcmp (*pmem, *other_pmem) == 0) {
+ break;
+ }
+ }
+ if (*other_pmem == NULL) {
+ printf
+ ("'%s' is a member of the '%s' group in %s but not in %s\n",
+ *pmem, groupname, file, other_file);
+ }
+ }
+}
+#endif /* SHADOWGRP */
+
+/*
+ * check_grp_file - check the content of the group file
+ */
+static void check_grp_file (int *errors, bool *changed)
+{
+ struct commonio_entry *gre, *tgre;
+ struct group *grp;
+#ifdef SHADOWGRP
+ struct sgrp *sgr;
+#endif
+
+ /*
+ * Loop through the entire group file.
+ */
+ for (gre = __gr_get_head (); NULL != gre; gre = gre->next) {
+ /*
+ * Skip all NIS entries.
+ */
+
+ if ((gre->line[0] == '+') || (gre->line[0] == '-')) {
+ continue;
+ }
+
+ /*
+ * Start with the entries that are completely corrupt. They
+ * have no (struct group) entry because they couldn't be
+ * parsed properly.
+ */
+ if (NULL == gre->eptr) {
+
+ /*
+ * Tell the user this entire line is bogus and ask
+ * them to delete it.
+ */
+ (void) puts (_("invalid group file entry"));
+ printf (_("delete line '%s'? "), gre->line);
+ *errors += 1;
+
+ /*
+ * prompt the user to delete the entry or not
+ */
+ if (!yes_or_no (read_only)) {
+ continue;
+ }
+
+ /*
+ * All group file deletions wind up here. This code
+ * removes the current entry from the linked list.
+ * When done, it skips back to the top of the loop
+ * to try out the next list element.
+ */
+ delete_gr:
+ SYSLOG ((LOG_INFO, "delete group line '%s'",
+ gre->line));
+ *changed = true;
+
+ __gr_del_entry (gre);
+ continue;
+ }
+
+ /*
+ * Group structure is good, start using it.
+ */
+ grp = gre->eptr;
+
+ /*
+ * Make sure this entry has a unique name.
+ */
+ for (tgre = __gr_get_head (); NULL != tgre; tgre = tgre->next) {
+
+ const struct group *ent = tgre->eptr;
+
+ /*
+ * Don't check this entry
+ */
+ if (tgre == gre) {
+ continue;
+ }
+
+ /*
+ * Don't check invalid entries.
+ */
+ if (NULL == ent) {
+ continue;
+ }
+
+ if (strcmp (grp->gr_name, ent->gr_name) != 0) {
+ continue;
+ }
+
+ /*
+ * Tell the user this entry is a duplicate of
+ * another and ask them to delete it.
+ */
+ (void) puts (_("duplicate group entry"));
+ printf (_("delete line '%s'? "), gre->line);
+ *errors += 1;
+
+ /*
+ * prompt the user to delete the entry or not
+ */
+ if (yes_or_no (read_only)) {
+ goto delete_gr;
+ }
+ }
+
+ /*
+ * Check for invalid group names. --marekm
+ */
+ if (!is_valid_group_name (grp->gr_name)) {
+ *errors += 1;
+ printf (_("invalid group name '%s'\n"), grp->gr_name);
+ }
+
+ /*
+ * Check for invalid group ID.
+ */
+ if (grp->gr_gid == (gid_t)-1) {
+ printf (_("invalid group ID '%lu'\n"), (long unsigned int)grp->gr_gid);
+ *errors += 1;
+ }
+
+ /*
+ * Workaround for a NYS libc 5.3.12 bug on RedHat 4.2 -
+ * groups with no members are returned as groups with one
+ * member "", causing grpck to fail. --marekm
+ */
+ if ( (NULL != grp->gr_mem[0])
+ && (NULL == grp->gr_mem[1])
+ && ('\0' == grp->gr_mem[0][0])) {
+ grp->gr_mem[0] = NULL;
+ }
+
+ if (check_members (grp->gr_name, grp->gr_mem,
+ _("group %s: no user %s\n"),
+ _("delete member '%s'? "),
+ "delete member '%s' from group '%s'",
+ errors) == 1) {
+ *changed = true;
+ gre->changed = true;
+ __gr_set_changed ();
+ }
+
+#ifdef SHADOWGRP
+ /*
+ * Make sure this entry exists in the /etc/gshadow file.
+ */
+
+ if (is_shadow) {
+ sgr = (struct sgrp *) sgr_locate (grp->gr_name);
+ if (sgr == NULL) {
+ printf (_("no matching group file entry in %s\n"),
+ sgr_file);
+ printf (_("add group '%s' in %s? "),
+ grp->gr_name, sgr_file);
+ *errors += 1;
+ if (yes_or_no (read_only)) {
+ struct sgrp sg;
+ struct group gr;
+ static char *empty = NULL;
+
+ sg.sg_name = grp->gr_name;
+ sg.sg_passwd = grp->gr_passwd;
+ sg.sg_adm = &empty;
+ sg.sg_mem = grp->gr_mem;
+ SYSLOG ((LOG_INFO,
+ "add group '%s' to '%s'",
+ grp->gr_name, sgr_file));
+ *changed = true;
+
+ if (sgr_update (&sg) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, sgr_dbname (), sg.sg_name);
+ fail_exit (E_CANT_UPDATE);
+ }
+ /* remove password from /etc/group */
+ gr = *grp;
+ gr.gr_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
+ if (gr_update (&gr) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, gr_dbname (), gr.gr_name);
+ fail_exit (E_CANT_UPDATE);
+ }
+ }
+ } else {
+ /**
+ * Verify that all the members defined in /etc/group are also
+ * present in /etc/gshadow.
+ */
+ compare_members_lists (grp->gr_name,
+ grp->gr_mem, sgr->sg_mem,
+ grp_file, sgr_file);
+
+ /* The group entry has a gshadow counterpart.
+ * Make sure no passwords are in group.
+ */
+ if (strcmp (grp->gr_passwd, SHADOW_PASSWD_STRING) != 0) {
+ printf (_("group %s has an entry in %s, but its password field in %s is not set to 'x'\n"),
+ grp->gr_name, sgr_file, grp_file);
+ *errors += 1;
+ }
+ }
+ }
+#endif
+
+ }
+}
+
+#ifdef SHADOWGRP
+/*
+ * check_sgr_file - check the content of the shadowed group file (gshadow)
+ */
+static void check_sgr_file (int *errors, bool *changed)
+{
+ struct group *grp;
+ struct commonio_entry *sge, *tsge;
+ struct sgrp *sgr;
+
+ /*
+ * Loop through the entire shadow group file.
+ */
+ for (sge = __sgr_get_head (); NULL != sge; sge = sge->next) {
+
+ /*
+ * Start with the entries that are completely corrupt. They
+ * have no (struct sgrp) entry because they couldn't be
+ * parsed properly.
+ */
+ if (NULL == sge->eptr) {
+
+ /*
+ * Tell the user this entire line is bogus and ask
+ * them to delete it.
+ */
+ (void) puts (_("invalid shadow group file entry"));
+ printf (_("delete line '%s'? "), sge->line);
+ *errors += 1;
+
+ /*
+ * prompt the user to delete the entry or not
+ */
+ if (!yes_or_no (read_only)) {
+ continue;
+ }
+
+ /*
+ * All shadow group file deletions wind up here.
+ * This code removes the current entry from the
+ * linked list. When done, it skips back to the top
+ * of the loop to try out the next list element.
+ */
+ delete_sg:
+ SYSLOG ((LOG_INFO, "delete shadow line '%s'",
+ sge->line));
+ *changed = true;
+
+ __sgr_del_entry (sge);
+ continue;
+ }
+
+ /*
+ * Shadow group structure is good, start using it.
+ */
+ sgr = sge->eptr;
+
+ /*
+ * Make sure this entry has a unique name.
+ */
+ for (tsge = __sgr_get_head (); NULL != tsge; tsge = tsge->next) {
+
+ const struct sgrp *ent = tsge->eptr;
+
+ /*
+ * Don't check this entry
+ */
+ if (tsge == sge) {
+ continue;
+ }
+
+ /*
+ * Don't check invalid entries.
+ */
+ if (NULL == ent) {
+ continue;
+ }
+
+ if (strcmp (sgr->sg_name, ent->sg_name) != 0) {
+ continue;
+ }
+
+ /*
+ * Tell the user this entry is a duplicate of
+ * another and ask them to delete it.
+ */
+ (void) puts (_("duplicate shadow group entry"));
+ printf (_("delete line '%s'? "), sge->line);
+ *errors += 1;
+
+ /*
+ * prompt the user to delete the entry or not
+ */
+ if (yes_or_no (read_only)) {
+ goto delete_sg;
+ }
+ }
+
+ /*
+ * Make sure this entry exists in the /etc/group file.
+ */
+ grp = (struct group *) gr_locate (sgr->sg_name);
+ if (grp == NULL) {
+ printf (_("no matching group file entry in %s\n"),
+ grp_file);
+ printf (_("delete line '%s'? "), sge->line);
+ *errors += 1;
+ if (yes_or_no (read_only)) {
+ goto delete_sg;
+ }
+ } else {
+ /**
+ * Verify that the all members defined in /etc/gshadow are also
+ * present in /etc/group.
+ */
+ compare_members_lists (sgr->sg_name,
+ sgr->sg_mem, grp->gr_mem,
+ sgr_file, grp_file);
+ }
+
+ /*
+ * Make sure each administrator exists
+ */
+ if (check_members (sgr->sg_name, sgr->sg_adm,
+ _("shadow group %s: no administrative user %s\n"),
+ _("delete administrative member '%s'? "),
+ "delete admin '%s' from shadow group '%s'",
+ errors) == 1) {
+ *changed = true;
+ sge->changed = true;
+ __sgr_set_changed ();
+ }
+
+ /*
+ * Make sure each member exists
+ */
+ if (check_members (sgr->sg_name, sgr->sg_mem,
+ _("shadow group %s: no user %s\n"),
+ _("delete member '%s'? "),
+ "delete member '%s' from shadow group '%s'",
+ errors) == 1) {
+ *changed = true;
+ sge->changed = true;
+ __sgr_set_changed ();
+ }
+ }
+}
+#endif /* SHADOWGRP */
+
+/*
+ * grpck - verify group file integrity
+ */
+int main (int argc, char **argv)
+{
+ int errors = 0;
+ bool changed = false;
+
+ /*
+ * Get my name so that I can use it to report errors.
+ */
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ OPENLOG ("grpck");
+
+ /* Parse the command line arguments */
+ process_flags (argc, argv);
+
+ open_files ();
+
+ if (sort_mode) {
+ gr_sort ();
+#ifdef SHADOWGRP
+ if (is_shadow) {
+ sgr_sort ();
+ }
+ changed = true;
+#endif
+ } else {
+ check_grp_file (&errors, &changed);
+#ifdef SHADOWGRP
+ if (is_shadow) {
+ check_sgr_file (&errors, &changed);
+ }
+#endif
+ }
+
+ /* Commit the change in the database if needed */
+ close_files (changed);
+
+ nscd_flush_cache ("group");
+
+ /*
+ * Tell the user what we did and exit.
+ */
+ if (0 != errors) {
+ if (changed) {
+ printf (_("%s: the files have been updated\n"), Prog);
+ } else {
+ printf (_("%s: no changes\n"), Prog);
+ }
+ }
+
+ return ((0 != errors) ? E_BAD_ENTRY : E_OKAY);
+}
+
diff --git a/src/grpconv.c b/src/grpconv.c
new file mode 100644
index 0000000..f681f07
--- /dev/null
+++ b/src/grpconv.c
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2002 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2011 , Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * grpconv - create or update /etc/gshadow with information from
+ * /etc/group.
+ *
+ */
+
+#include <config.h>
+#ident "$Id$"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <getopt.h>
+#include "nscd.h"
+#include "prototypes.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+#ifdef SHADOWGRP
+#include "groupio.h"
+#include "sgroupio.h"
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static bool gr_locked = false;
+static bool sgr_locked = false;
+
+/* local function prototypes */
+static void fail_exit (int status);
+static void usage (int status);
+static void process_flags (int argc, char **argv);
+
+static void fail_exit (int status)
+{
+ if (gr_locked) {
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+ }
+
+ if (sgr_locked) {
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+ }
+
+ exit (status);
+}
+
+static void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ /*
+ * Parse the command line options.
+ */
+ int c;
+ static struct option long_options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"root", required_argument, NULL, 'R'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "hR:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ default:
+ usage (E_USAGE);
+ }
+ }
+
+ if (optind != argc) {
+ usage (E_USAGE);
+ }
+}
+
+int main (int argc, char **argv)
+{
+ const struct group *gr;
+ struct group grent;
+ const struct sgrp *sg;
+ struct sgrp sgent;
+
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ OPENLOG ("grpconv");
+
+ process_flags (argc, argv);
+
+ if (gr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, gr_dbname ());
+ fail_exit (5);
+ }
+ gr_locked = true;
+ if (gr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
+ fail_exit (1);
+ }
+
+ if (sgr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sgr_dbname ());
+ fail_exit (5);
+ }
+ sgr_locked = true;
+ if (sgr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, sgr_dbname ());
+ fail_exit (1);
+ }
+
+ /*
+ * Remove /etc/gshadow entries for groups not in /etc/group.
+ */
+ (void) sgr_rewind ();
+ while ((sg = sgr_next ()) != NULL) {
+ if (gr_locate (sg->sg_name) != NULL) {
+ continue;
+ }
+
+ if (sgr_remove (sg->sg_name) == 0) {
+ /*
+ * This shouldn't happen (the entry exists) but...
+ */
+ fprintf (stderr,
+ _("%s: cannot remove entry '%s' from %s\n"),
+ Prog, sg->sg_name, sgr_dbname ());
+ fail_exit (3);
+ }
+ }
+
+ /*
+ * Update shadow group passwords if non-shadow password is not "x".
+ * Add any missing shadow group entries.
+ */
+ (void) gr_rewind ();
+ while ((gr = gr_next ()) != NULL) {
+ sg = sgr_locate (gr->gr_name);
+ if (NULL != sg) {
+ /* update existing shadow group entry */
+ sgent = *sg;
+ if (strcmp (gr->gr_passwd, SHADOW_PASSWD_STRING) != 0)
+ sgent.sg_passwd = gr->gr_passwd;
+ } else {
+ static char *empty = 0;
+
+ /* add new shadow group entry */
+ memset (&sgent, 0, sizeof sgent);
+ sgent.sg_name = gr->gr_name;
+ sgent.sg_passwd = gr->gr_passwd;
+ sgent.sg_adm = &empty;
+ }
+ /*
+ * XXX - sg_mem is redundant, it is currently always a copy
+ * of gr_mem. Very few programs actually use sg_mem, and all
+ * of them are in the shadow suite. Maybe this field could
+ * be used for something else? Any suggestions?
+ */
+ sgent.sg_mem = gr->gr_mem;
+
+ if (sgr_update (&sgent) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, sgr_dbname (), sgent.sg_name);
+ fail_exit (3);
+ }
+ /* remove password from /etc/group */
+ grent = *gr;
+ grent.gr_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
+ if (gr_update (&grent) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, gr_dbname (), grent.gr_name);
+ fail_exit (3);
+ }
+ }
+
+ if (sgr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", sgr_dbname ()));
+ fail_exit (3);
+ }
+ if (gr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", gr_dbname ()));
+ fail_exit (3);
+ }
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+
+ nscd_flush_cache ("group");
+
+ return 0;
+}
+#else /* !SHADOWGRP */
+int main (int unused(argc), char **argv)
+{
+ fprintf (stderr,
+ "%s: not configured for shadow group support.\n", argv[0]);
+ exit (1);
+}
+#endif /* !SHADOWGRP */
+
diff --git a/src/grpunconv.c b/src/grpunconv.c
new file mode 100644
index 0000000..253f06f
--- /dev/null
+++ b/src/grpunconv.c
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 1996 , Michael Meskes
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2002 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2008 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * grpunconv - update /etc/group with information from /etc/gshadow.
+ *
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+#include <grp.h>
+#include <getopt.h>
+#include "nscd.h"
+#include "prototypes.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+#ifdef SHADOWGRP
+#include "groupio.h"
+#include "sgroupio.h"
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static bool gr_locked = false;
+static bool sgr_locked = false;
+
+/* local function prototypes */
+static void fail_exit (int status);
+static void usage (int status);
+static void process_flags (int argc, char **argv);
+
+static void fail_exit (int status)
+{
+ if (gr_locked) {
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+ }
+
+ if (sgr_locked) {
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+ }
+
+ exit (status);
+}
+
+static void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ /*
+ * Parse the command line options.
+ */
+ int c;
+ static struct option long_options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"root", required_argument, NULL, 'R'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "hR:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ default:
+ usage (E_USAGE);
+ }
+ }
+
+ if (optind != argc) {
+ usage (E_USAGE);
+ }
+}
+
+int main (int argc, char **argv)
+{
+ const struct group *gr;
+ struct group grent;
+ const struct sgrp *sg;
+
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ OPENLOG ("grpunconv");
+
+ process_flags (argc, argv);
+
+ if (sgr_file_present () == 0) {
+ exit (0); /* no /etc/gshadow, nothing to do */
+ }
+
+ if (gr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, gr_dbname ());
+ fail_exit (5);
+ }
+ gr_locked = true;
+ if (gr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"), Prog, gr_dbname ());
+ fail_exit (1);
+ }
+
+ if (sgr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sgr_dbname ());
+ fail_exit (5);
+ }
+ sgr_locked = true;
+ if (sgr_open (O_RDONLY) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"), Prog, sgr_dbname ());
+ fail_exit (1);
+ }
+
+ /*
+ * Update group passwords if non-shadow password is "x".
+ */
+ (void) gr_rewind ();
+ while ((gr = gr_next ()) != NULL) {
+ sg = sgr_locate (gr->gr_name);
+ if ( (NULL != sg)
+ && (strcmp (gr->gr_passwd, SHADOW_PASSWD_STRING) == 0)) {
+ /* add password to /etc/group */
+ grent = *gr;
+ grent.gr_passwd = sg->sg_passwd;
+ if (gr_update (&grent) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, gr_dbname (), grent.gr_name);
+ fail_exit (3);
+ }
+ }
+ }
+
+ (void) sgr_close (); /* was only open O_RDONLY */
+
+ if (gr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", gr_dbname ()));
+ fail_exit (3);
+ }
+
+ if (unlink (SGROUP_FILE) != 0) {
+ fprintf (stderr,
+ _("%s: cannot delete %s\n"),
+ Prog, SGROUP_FILE);
+ SYSLOG ((LOG_ERR, "cannot delete %s", SGROUP_FILE));
+ fail_exit (3);
+ }
+
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+
+ nscd_flush_cache ("group");
+
+ return 0;
+}
+#else /* !SHADOWGRP */
+int main (int unused(argc), char **argv)
+{
+ fprintf (stderr,
+ "%s: not configured for shadow group support.\n", argv[0]);
+ exit (1);
+}
+#endif /* !SHADOWGRP */
+
diff --git a/src/id.c b/src/id.c
new file mode 100644
index 0000000..4462ae0
--- /dev/null
+++ b/src/id.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 1991 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2001 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2008, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * id - print current process user identification information
+ *
+ * Print the current process identifiers. This includes the
+ * UID, GID, effective-UID and effective-GID. Optionally print
+ * the concurrent group set if the current system supports it.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include "defines.h"
+/* local function prototypes */
+static void usage (void);
+
+static void usage (void)
+{
+#ifdef HAVE_GETGROUPS
+ (void) fputs (_("Usage: id [-a]\n"), stderr);
+#else
+ (void) fputs (_("Usage: id\n"), stderr);
+#endif
+ exit (EXIT_FAILURE);
+}
+
+ /*ARGSUSED*/ int main (int argc, char **argv)
+{
+ uid_t ruid, euid;
+ gid_t rgid, egid;
+ long sys_ngroups;
+
+/*
+ * This block of declarations is particularly strained because of several
+ * different ways of doing concurrent groups. Old BSD systems used int for
+ * gid's, but short for the type passed to getgroups(). Newer systems use
+ * gid_t for everything. Some systems have a small and fixed NGROUPS,
+ * usually about 16 or 32. Others use bigger values.
+ */
+#ifdef HAVE_GETGROUPS
+ GETGROUPS_T *groups;
+ int ngroups;
+ bool aflg = 0;
+#endif
+ struct passwd *pw;
+ struct group *gr;
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ /*
+ * Dynamically get the maximum number of groups from system, instead
+ * of using the symbolic constant NGROUPS_MAX. This ensures that the
+ * group limit is not hard coded into the binary, so it will still
+ * work if the system library is recompiled.
+ */
+ sys_ngroups = sysconf (_SC_NGROUPS_MAX);
+#ifdef HAVE_GETGROUPS
+ groups = (GETGROUPS_T *) malloc (sizeof (GETGROUPS_T) * sys_ngroups);
+ /*
+ * See if the -a flag has been given to print out the concurrent
+ * group set.
+ */
+
+ if (argc > 1) {
+ if ((argc > 2) || (strcmp (argv[1], "-a") != 0)) {
+ usage ();
+ } else {
+ aflg = true;
+ }
+ }
+#else
+ if (argc > 1) {
+ usage ();
+ }
+#endif
+
+ ruid = getuid ();
+ euid = geteuid ();
+ rgid = getgid ();
+ egid = getegid ();
+
+ /*
+ * Print out the real user ID and group ID. If the user or group
+ * does not exist, just give the numerical value.
+ */
+
+ pw = getpwuid (ruid); /* local, no need for xgetpwuid */
+ if (NULL != pw) {
+ (void) printf ("UID=%lu(%s)",
+ (unsigned long) ruid, pw->pw_name);
+ } else {
+ (void) printf ("UID=%lu", (unsigned long) ruid);
+ }
+
+ gr = getgrgid (rgid);; /* local, no need for xgetgrgid */
+ if (NULL != gr) {
+ (void) printf (" GID=%lu(%s)",
+ (unsigned long) rgid, gr->gr_name);
+ } else {
+ (void) printf (" GID=%lu", (unsigned long) rgid);
+ }
+
+ /*
+ * Print out the effective user ID and group ID if they are
+ * different from the real values.
+ */
+
+ if (ruid != euid) {
+ pw = getpwuid (euid); /* local, no need for xgetpwuid */
+ if (NULL != pw) {
+ (void) printf (" EUID=%lu(%s)",
+ (unsigned long) euid, pw->pw_name);
+ } else {
+ (void) printf (" EUID=%lu", (unsigned long) euid);
+ }
+ }
+ if (rgid != egid) {
+ gr = getgrgid (egid); /* local, no need for xgetgrgid */
+ if (NULL != gr) {
+ (void) printf (" EGID=%lu(%s)",
+ (unsigned long) egid, gr->gr_name);
+ } else {
+ (void) printf (" EGID=%lu", (unsigned long) egid);
+ }
+ }
+#ifdef HAVE_GETGROUPS
+ /*
+ * Print out the concurrent group set if the user has requested it.
+ * The group numbers will be printed followed by their names.
+ */
+ if (aflg && (ngroups = getgroups (sys_ngroups, groups)) != -1) {
+ int i;
+
+ /*
+ * Start off the group message. It will be of the format
+ *
+ * groups=###(aaa),###(aaa),###(aaa)
+ *
+ * where "###" is a numerical value and "aaa" is the
+ * corresponding name for each respective numerical value.
+ */
+ (void) puts (_(" groups="));
+ for (i = 0; i < ngroups; i++) {
+ if (0 != i)
+ (void) putchar (',');
+
+ /* local, no need for xgetgrgid */
+ gr = getgrgid (groups[i]);
+ if (NULL != gr) {
+ (void) printf ("%lu(%s)",
+ (unsigned long) groups[i],
+ gr->gr_name);
+ } else {
+ (void) printf ("%lu",
+ (unsigned long) groups[i]);
+ }
+ }
+ }
+ free (groups);
+#endif
+
+ /*
+ * Finish off the line.
+ */
+ (void) putchar ('\n');
+
+ return EXIT_SUCCESS;
+}
+
diff --git a/src/lastlog.c b/src/lastlog.c
new file mode 100644
index 0000000..965691d
--- /dev/null
+++ b/src/lastlog.c
@@ -0,0 +1,428 @@
+/*
+ * Copyright (c) 1989 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <getopt.h>
+#include <lastlog.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <assert.h>
+#include "defines.h"
+#include "prototypes.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+/*
+ * Needed for MkLinux DR1/2/2.1 - J.
+ */
+#ifndef LASTLOG_FILE
+#define LASTLOG_FILE "/var/log/lastlog"
+#endif
+
+/*
+ * Global variables
+ */
+const char *Prog; /* Program name */
+static FILE *lastlogfile; /* lastlog file stream */
+static unsigned long umin; /* if uflg and has_umin, only display users with uid >= umin */
+static bool has_umin = false;
+static unsigned long umax; /* if uflg and has_umax, only display users with uid <= umax */
+static bool has_umax = false;
+static time_t seconds; /* that number of days in seconds */
+static time_t inverse_seconds; /* that number of days in seconds */
+static struct stat statbuf; /* fstat buffer for file size */
+
+
+static bool uflg = false; /* print only an user of range of users */
+static bool tflg = false; /* print is restricted to most recent days */
+static bool bflg = false; /* print excludes most recent days */
+static bool Cflg = false; /* clear record for user */
+static bool Sflg = false; /* set record for user */
+
+#define NOW (time ((time_t *) 0))
+
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -b, --before DAYS print only lastlog records older than DAYS\n"), usageout);
+ (void) fputs (_(" -C, --clear clear lastlog record of an user (usable only with -u)\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs (_(" -S, --set set lastlog record to current time (usable only with -u)\n"), usageout);
+ (void) fputs (_(" -t, --time DAYS print only lastlog records more recent than DAYS\n"), usageout);
+ (void) fputs (_(" -u, --user LOGIN print lastlog record of the specified LOGIN\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+static void print_one (/*@null@*/const struct passwd *pw)
+{
+ static bool once = false;
+ char *cp;
+ struct tm *tm;
+ time_t ll_time;
+ off_t offset;
+ struct lastlog ll;
+
+#ifdef HAVE_STRFTIME
+ char ptime[80];
+#endif
+
+ if (NULL == pw) {
+ return;
+ }
+
+
+ offset = (off_t) pw->pw_uid * sizeof (ll);
+ if (offset + sizeof (ll) <= statbuf.st_size) {
+ /* fseeko errors are not really relevant for us. */
+ int err = fseeko (lastlogfile, offset, SEEK_SET);
+ assert (0 == err);
+ /* lastlog is a sparse file. Even if no entries were
+ * entered for this user, which should be able to get the
+ * empty entry in this case.
+ */
+ if (fread ((char *) &ll, sizeof (ll), 1, lastlogfile) != 1) {
+ fprintf (stderr,
+ _("%s: Failed to get the entry for UID %lu\n"),
+ Prog, (unsigned long int)pw->pw_uid);
+ exit (EXIT_FAILURE);
+ }
+ } else {
+ /* Outsize of the lastlog file.
+ * Behave as if there were a missing entry (same behavior
+ * as if we were reading an non existing entry in the
+ * sparse lastlog file).
+ */
+ memzero (&ll, sizeof (ll));
+ }
+
+ /* Filter out entries that do not match with the -t or -b options */
+ if (tflg && ((NOW - ll.ll_time) > seconds)) {
+ return;
+ }
+
+ if (bflg && ((NOW - ll.ll_time) < inverse_seconds)) {
+ return;
+ }
+
+ /* Print the header only once */
+ if (!once) {
+#ifdef HAVE_LL_HOST
+ puts (_("Username Port From Latest"));
+#else
+ puts (_("Username Port Latest"));
+#endif
+ once = true;
+ }
+
+ ll_time = ll.ll_time;
+ tm = localtime (&ll_time);
+#ifdef HAVE_STRFTIME
+ strftime (ptime, sizeof (ptime), "%a %b %e %H:%M:%S %z %Y", tm);
+ cp = ptime;
+#else
+ cp = asctime (tm);
+ cp[24] = '\0';
+#endif
+
+ if (ll.ll_time == (time_t) 0) {
+ cp = _("**Never logged in**\0");
+ }
+
+#ifdef HAVE_LL_HOST
+ printf ("%-16s %-8.8s %-16.16s %s\n",
+ pw->pw_name, ll.ll_line, ll.ll_host, cp);
+#else
+ printf ("%-16s\t%-8.8s %s\n",
+ pw->pw_name, ll.ll_line, cp);
+#endif
+}
+
+static void print (void)
+{
+ const struct passwd *pwent;
+ if (uflg && has_umin && has_umax && (umin == umax)) {
+ print_one (getpwuid ((uid_t)umin));
+ } else {
+ setpwent ();
+ while ( (pwent = getpwent ()) != NULL ) {
+ if ( uflg
+ && ( (has_umin && (pwent->pw_uid < (uid_t)umin))
+ || (has_umax && (pwent->pw_uid > (uid_t)umax)))) {
+ continue;
+ }
+ print_one (pwent);
+ }
+ endpwent ();
+ }
+}
+
+static void update_one (/*@null@*/const struct passwd *pw)
+{
+ off_t offset;
+ struct lastlog ll;
+ int err;
+
+ if (NULL == pw) {
+ return;
+ }
+
+ offset = (off_t) pw->pw_uid * sizeof (ll);
+ /* fseeko errors are not really relevant for us. */
+ err = fseeko (lastlogfile, offset, SEEK_SET);
+ assert (0 == err);
+
+ memzero (&ll, sizeof (ll));
+
+ if (Sflg) {
+ ll.ll_time = NOW;
+#ifdef HAVE_LL_HOST
+ strcpy (ll.ll_host, "localhost");
+#endif
+ strcpy (ll.ll_line, "lastlog");
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ACCT_UNLOCK, Prog,
+ "clearing-lastlog",
+ pw->pw_name, (unsigned int) pw->pw_uid, SHADOW_AUDIT_SUCCESS);
+#endif
+ }
+#ifdef WITH_AUDIT
+ else {
+ audit_logger (AUDIT_ACCT_UNLOCK, Prog,
+ "refreshing-lastlog",
+ pw->pw_name, (unsigned int) pw->pw_uid, SHADOW_AUDIT_SUCCESS);
+ }
+#endif
+
+ if (fwrite (&ll, sizeof(ll), 1, lastlogfile) != 1) {
+ fprintf (stderr,
+ _("%s: Failed to update the entry for UID %lu\n"),
+ Prog, (unsigned long int)pw->pw_uid);
+ exit (EXIT_FAILURE);
+ }
+}
+
+static void update (void)
+{
+ const struct passwd *pwent;
+
+ if (!uflg) /* safety measure */
+ return;
+
+ if (has_umin && has_umax && (umin == umax)) {
+ update_one (getpwuid ((uid_t)umin));
+ } else {
+ setpwent ();
+ while ( (pwent = getpwent ()) != NULL ) {
+ if ((has_umin && (pwent->pw_uid < (uid_t)umin))
+ || (has_umax && (pwent->pw_uid > (uid_t)umax))) {
+ continue;
+ }
+ update_one (pwent);
+ }
+ endpwent ();
+ }
+
+ if (fflush (lastlogfile) != 0 || fsync (fileno (lastlogfile)) != 0) {
+ fprintf (stderr,
+ _("%s: Failed to update the lastlog file\n"),
+ Prog);
+ exit (EXIT_FAILURE);
+ }
+}
+
+int main (int argc, char **argv)
+{
+ /*
+ * Get the program name. The program name is used as a prefix to
+ * most error messages.
+ */
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+#ifdef WITH_AUDIT
+ audit_help_open ();
+#endif
+
+ {
+ int c;
+ static struct option const longopts[] = {
+ {"before", required_argument, NULL, 'b'},
+ {"clear", no_argument, NULL, 'C'},
+ {"help", no_argument, NULL, 'h'},
+ {"root", required_argument, NULL, 'R'},
+ {"set", no_argument, NULL, 'S'},
+ {"time", required_argument, NULL, 't'},
+ {"user", required_argument, NULL, 'u'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "b:ChR:St:u:", longopts,
+ NULL)) != -1) {
+ switch (c) {
+ case 'b':
+ {
+ unsigned long inverse_days;
+ if (getulong (optarg, &inverse_days) == 0) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ exit (EXIT_FAILURE);
+ }
+ inverse_seconds = (time_t) inverse_days * DAY;
+ bflg = true;
+ break;
+ }
+ case 'C':
+ {
+ Cflg = true;
+ break;
+ }
+ case 'h':
+ usage (EXIT_SUCCESS);
+ /*@notreached@*/break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ case 'S':
+ {
+ Sflg = true;
+ break;
+ }
+ case 't':
+ {
+ unsigned long days;
+ if (getulong (optarg, &days) == 0) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ exit (EXIT_FAILURE);
+ }
+ seconds = (time_t) days * DAY;
+ tflg = true;
+ break;
+ }
+ case 'u':
+ {
+ const struct passwd *pwent;
+ /*
+ * The user can be:
+ * - a login name
+ * - numerical
+ * - a numerical login ID
+ * - a range (-x, x-, x-y)
+ */
+ uflg = true;
+ /* local, no need for xgetpwnam */
+ pwent = getpwnam (optarg);
+ if (NULL != pwent) {
+ umin = (unsigned long) pwent->pw_uid;
+ has_umin = true;
+ umax = umin;
+ has_umax = true;
+ } else {
+ if (getrange (optarg,
+ &umin, &has_umin,
+ &umax, &has_umax) == 0) {
+ fprintf (stderr,
+ _("%s: Unknown user or range: %s\n"),
+ Prog, optarg);
+ exit (EXIT_FAILURE);
+ }
+ }
+ break;
+ }
+ default:
+ usage (EXIT_FAILURE);
+ /*@notreached@*/break;
+ }
+ }
+ if (argc > optind) {
+ fprintf (stderr,
+ _("%s: unexpected argument: %s\n"),
+ Prog, argv[optind]);
+ usage (EXIT_FAILURE);
+ }
+ if (Cflg && Sflg) {
+ fprintf (stderr,
+ _("%s: Option -C cannot be used together with option -S\n"),
+ Prog);
+ usage (EXIT_FAILURE);
+ }
+ if ((Cflg || Sflg) && !uflg) {
+ fprintf (stderr,
+ _("%s: Options -C and -S require option -u to specify the user\n"),
+ Prog);
+ usage (EXIT_FAILURE);
+ }
+ }
+
+ lastlogfile = fopen (LASTLOG_FILE, (Cflg || Sflg)?"r+":"r");
+ if (NULL == lastlogfile) {
+ perror (LASTLOG_FILE);
+ exit (EXIT_FAILURE);
+ }
+
+ /* Get the lastlog size */
+ if (fstat (fileno (lastlogfile), &statbuf) != 0) {
+ fprintf (stderr,
+ _("%s: Cannot get the size of %s: %s\n"),
+ Prog, LASTLOG_FILE, strerror (errno));
+ exit (EXIT_FAILURE);
+ }
+
+ if (Cflg || Sflg)
+ update ();
+ else
+ print ();
+
+ (void) fclose (lastlogfile);
+
+ return EXIT_SUCCESS;
+}
+
diff --git a/src/login.c b/src/login.c
new file mode 100644
index 0000000..2d2e704
--- /dev/null
+++ b/src/login.c
@@ -0,0 +1,1339 @@
+/*
+ * Copyright (c) 1989 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2001, Marek Michałkiewicz
+ * Copyright (c) 2001 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2012, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <errno.h>
+#include <grp.h>
+#ifndef USE_PAM
+#include <lastlog.h>
+#endif /* !USE_PAM */
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <assert.h>
+#include "defines.h"
+#include "faillog.h"
+#include "failure.h"
+#include "getdef.h"
+#include "prototypes.h"
+#include "pwauth.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+#ifdef USE_PAM
+#include "pam_defs.h"
+
+static pam_handle_t *pamh = NULL;
+
+#define PAM_FAIL_CHECK if (retcode != PAM_SUCCESS) { \
+ fprintf(stderr,"\n%s\n",pam_strerror(pamh, retcode)); \
+ SYSLOG((LOG_ERR,"%s",pam_strerror(pamh, retcode))); \
+ (void) pam_end(pamh, retcode); \
+ exit(1); \
+ }
+#define PAM_END { retcode = pam_close_session(pamh,0); \
+ (void) pam_end(pamh,retcode); }
+
+#endif /* USE_PAM */
+
+#ifndef USE_PAM
+/*
+ * Needed for MkLinux DR1/2/2.1 - J.
+ */
+#ifndef LASTLOG_FILE
+#define LASTLOG_FILE "/var/log/lastlog"
+#endif
+#endif /* !USE_PAM */
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static const char *hostname = "";
+static /*@null@*/ /*@only@*/char *username = NULL;
+static int reason = PW_LOGIN;
+
+#ifndef USE_PAM
+static struct lastlog ll;
+#endif /* !USE_PAM */
+static bool pflg = false;
+static bool fflg = false;
+
+#ifdef RLOGIN
+static bool rflg = false;
+#else /* RLOGIN */
+#define rflg false
+#endif /* !RLOGIN */
+static bool hflg = false;
+static bool preauth_flag = false;
+
+static bool amroot;
+static char tmsg[256];
+
+/*
+ * External identifiers.
+ */
+
+extern char **newenvp;
+extern size_t newenvc;
+extern char **environ;
+
+#ifndef ALARM
+#define ALARM 60
+#endif
+
+#ifndef RETRIES
+#define RETRIES 3
+#endif
+
+/* local function prototypes */
+static void usage (void);
+static void setup_tty (void);
+static void process_flags (int argc, char *const *argv);
+static /*@observer@*/const char *get_failent_user (/*@returned@*/const char *user);
+static void update_utmp (const char *user,
+ const char *tty,
+ const char *host,
+ /*@null@*/const struct utmp *utent);
+
+#ifndef USE_PAM
+static struct faillog faillog;
+
+static void bad_time_notify (void);
+static void check_nologin (bool login_to_root);
+#else
+static void get_pam_user (char **ptr_pam_user);
+#endif
+
+static void init_env (void);
+static RETSIGTYPE alarm_handler (int);
+
+/*
+ * usage - print login command usage and exit
+ *
+ * login [ name ]
+ * login -r hostname (for rlogind)
+ * login -h hostname (for telnetd, etc.)
+ * login -f name (for pre-authenticated login: datakit, xterm, etc.)
+ */
+static void usage (void)
+{
+ fprintf (stderr, _("Usage: %s [-p] [name]\n"), Prog);
+ if (!amroot) {
+ exit (1);
+ }
+ fprintf (stderr, _(" %s [-p] [-h host] [-f name]\n"), Prog);
+#ifdef RLOGIN
+ fprintf (stderr, _(" %s [-p] -r host\n"), Prog);
+#endif /* RLOGIN */
+ exit (1);
+}
+
+static void setup_tty (void)
+{
+ TERMIO termio;
+
+ if (GTTY (0, &termio) == 0) { /* get terminal characteristics */
+ int erasechar;
+ int killchar;
+
+ /*
+ * Add your favorite terminal modes here ...
+ */
+ termio.c_lflag |= ISIG | ICANON | ECHO | ECHOE;
+ termio.c_iflag |= ICRNL;
+
+#if defined(ECHOKE) && defined(ECHOCTL)
+ termio.c_lflag |= ECHOKE | ECHOCTL;
+#endif
+#if defined(ECHOPRT) && defined(NOFLSH) && defined(TOSTOP)
+ termio.c_lflag &= ~(ECHOPRT | NOFLSH | TOSTOP);
+#endif
+#ifdef ONLCR
+ termio.c_oflag |= ONLCR;
+#endif
+
+ /* leave these values unchanged if not specified in login.defs */
+ erasechar = getdef_num ("ERASECHAR", (int) termio.c_cc[VERASE]);
+ killchar = getdef_num ("KILLCHAR", (int) termio.c_cc[VKILL]);
+ termio.c_cc[VERASE] = (cc_t) erasechar;
+ termio.c_cc[VKILL] = (cc_t) killchar;
+ /* Make sure the values were valid.
+ * getdef_num cannot validate this.
+ */
+ if (erasechar != (int) termio.c_cc[VERASE]) {
+ fprintf (stderr,
+ _("configuration error - cannot parse %s value: '%d'"),
+ "ERASECHAR", erasechar);
+ exit (1);
+ }
+ if (killchar != (int) termio.c_cc[VKILL]) {
+ fprintf (stderr,
+ _("configuration error - cannot parse %s value: '%d'"),
+ "KILLCHAR", killchar);
+ exit (1);
+ }
+
+ /*
+ * ttymon invocation prefers this, but these settings
+ * won't come into effect after the first username login
+ */
+ (void) STTY (0, &termio);
+ }
+}
+
+
+#ifndef USE_PAM
+/*
+ * Tell the user that this is not the right time to login at this tty
+ */
+static void bad_time_notify (void)
+{
+ (void) puts (_("Invalid login time"));
+ (void) fflush (stdout);
+}
+
+static void check_nologin (bool login_to_root)
+{
+ char *fname;
+
+ /*
+ * Check to see if system is turned off for non-root users.
+ * This would be useful to prevent users from logging in
+ * during system maintenance. We make sure the message comes
+ * out for root so she knows to remove the file if she's
+ * forgotten about it ...
+ */
+ fname = getdef_str ("NOLOGINS_FILE");
+ if ((NULL != fname) && (access (fname, F_OK) == 0)) {
+ FILE *nlfp;
+
+ /*
+ * Cat the file if it can be opened, otherwise just
+ * print a default message
+ */
+ nlfp = fopen (fname, "r");
+ if (NULL != nlfp) {
+ int c;
+ while ((c = getc (nlfp)) != EOF) {
+ if (c == '\n') {
+ (void) putchar ('\r');
+ }
+
+ (void) putchar (c);
+ }
+ (void) fflush (stdout);
+ (void) fclose (nlfp);
+ } else {
+ (void) puts (_("\nSystem closed for routine maintenance"));
+ }
+ /*
+ * Non-root users must exit. Root gets the message, but
+ * gets to login.
+ */
+
+ if (!login_to_root) {
+ closelog ();
+ exit (0);
+ }
+ (void) puts (_("\n[Disconnect bypassed -- root login allowed.]"));
+ }
+}
+#endif /* !USE_PAM */
+
+static void process_flags (int argc, char *const *argv)
+{
+ int arg;
+ int flag;
+
+ /*
+ * Check the flags for proper form. Every argument starting with
+ * "-" must be exactly two characters long. This closes all the
+ * clever rlogin, telnet, and getty holes.
+ */
+ for (arg = 1; arg < argc; arg++) {
+ if (argv[arg][0] == '-' && strlen (argv[arg]) > 2) {
+ usage ();
+ }
+ if (strcmp(argv[arg], "--") == 0) {
+ break; /* stop checking on a "--" */
+ }
+ }
+
+ /*
+ * Process options.
+ */
+ while ((flag = getopt (argc, argv, "d:fh:pr:")) != EOF) {
+ switch (flag) {
+ case 'd':
+ /* "-d device" ignored for compatibility */
+ break;
+ case 'f':
+ fflg = true;
+ break;
+ case 'h':
+ hflg = true;
+ hostname = optarg;
+ reason = PW_TELNET;
+ break;
+#ifdef RLOGIN
+ case 'r':
+ rflg = true;
+ hostname = optarg;
+ reason = PW_RLOGIN;
+ break;
+#endif /* RLOGIN */
+ case 'p':
+ pflg = true;
+ break;
+ default:
+ usage ();
+ }
+ }
+
+#ifdef RLOGIN
+ /*
+ * Neither -h nor -f should be combined with -r.
+ */
+
+ if (rflg && (hflg || fflg)) {
+ usage ();
+ }
+#endif /* RLOGIN */
+
+ /*
+ * Allow authentication bypass only if real UID is zero.
+ */
+
+ if ((rflg || fflg || hflg) && !amroot) {
+ fprintf (stderr, _("%s: Permission denied.\n"), Prog);
+ exit (1);
+ }
+
+ /*
+ * Get the user name.
+ */
+ if (optind < argc) {
+ assert (NULL == username);
+ username = xstrdup (argv[optind]);
+ strzero (argv[optind]);
+ ++optind;
+ }
+
+#ifdef RLOGIN
+ if (rflg && (NULL != username)) {
+ usage ();
+ }
+#endif /* RLOGIN */
+ if (fflg && (NULL == username)) {
+ usage ();
+ }
+
+}
+
+
+static void init_env (void)
+{
+#ifndef USE_PAM
+ char *cp;
+#endif
+ char *tmp;
+
+ tmp = getenv ("LANG");
+ if (NULL != tmp) {
+ addenv ("LANG", tmp);
+ }
+
+ /*
+ * Add the timezone environmental variable so that time functions
+ * work correctly.
+ */
+ tmp = getenv ("TZ");
+ if (NULL != tmp) {
+ addenv ("TZ", tmp);
+ }
+#ifndef USE_PAM
+ else {
+ cp = getdef_str ("ENV_TZ");
+ if (NULL != cp) {
+ addenv (('/' == *cp) ? tz (cp) : cp, NULL);
+ }
+ }
+#endif /* !USE_PAM */
+ /*
+ * Add the clock frequency so that profiling commands work
+ * correctly.
+ */
+ tmp = getenv ("HZ");
+ if (NULL != tmp) {
+ addenv ("HZ", tmp);
+ }
+#ifndef USE_PAM
+ else {
+ cp = getdef_str ("ENV_HZ");
+ if (NULL != cp) {
+ addenv (cp, NULL);
+ }
+ }
+#endif /* !USE_PAM */
+}
+
+
+static RETSIGTYPE alarm_handler (unused int sig)
+{
+ write (STDERR_FILENO, tmsg, strlen (tmsg));
+ _exit (0);
+}
+
+#ifdef USE_PAM
+/*
+ * get_pam_user - Get the username according to PAM
+ *
+ * ptr_pam_user shall point to a malloc'ed string (or NULL).
+ */
+static void get_pam_user (char **ptr_pam_user)
+{
+ int retcode;
+ void *ptr_user;
+
+ assert (NULL != ptr_pam_user);
+
+ retcode = pam_get_item (pamh, PAM_USER, (const void **)&ptr_user);
+ PAM_FAIL_CHECK;
+
+ if (NULL != *ptr_pam_user) {
+ free (*ptr_pam_user);
+ }
+ if (NULL != ptr_user) {
+ *ptr_pam_user = xstrdup ((const char *)ptr_user);
+ } else {
+ *ptr_pam_user = NULL;
+ }
+}
+#endif
+
+/*
+ * get_failent_user - Return a string that can be used to log failure
+ * from an user.
+ *
+ * This will be either the user argument, or "UNKNOWN".
+ *
+ * It is quite common to mistyped the password for username, and passwords
+ * should not be logged.
+ */
+static /*@observer@*/const char *get_failent_user (/*@returned@*/const char *user)
+{
+ const char *failent_user = "UNKNOWN";
+ bool log_unkfail_enab = getdef_bool("LOG_UNKFAIL_ENAB");
+
+ if ((NULL != user) && ('\0' != user[0])) {
+ if ( log_unkfail_enab
+ || (getpwnam (user) != NULL)) {
+ failent_user = user;
+ }
+ }
+
+ return failent_user;
+}
+
+/*
+ * update_utmp - Update or create an utmp entry in utmp, wtmp, utmpw, and
+ * wtmpx
+ *
+ * utent should be the utmp entry returned by get_current_utmp (or
+ * NULL).
+ */
+static void update_utmp (const char *user,
+ const char *tty,
+ const char *host,
+ /*@null@*/const struct utmp *utent)
+{
+ struct utmp *ut = prepare_utmp (user, tty, host, utent);
+#ifdef USE_UTMPX
+ struct utmpx *utx = prepare_utmpx (user, tty, host, utent);
+#endif /* USE_UTMPX */
+
+ (void) setutmp (ut); /* make entry in the utmp & wtmp files */
+ free (ut);
+
+#ifdef USE_UTMPX
+ (void) setutmpx (utx); /* make entry in the utmpx & wtmpx files */
+ free (utx);
+#endif /* USE_UTMPX */
+}
+
+/*
+ * login - create a new login session for a user
+ *
+ * login is typically called by getty as the second step of a
+ * new user session. getty is responsible for setting the line
+ * characteristics to a reasonable set of values and getting
+ * the name of the user to be logged in. login may also be
+ * called to create a new user session on a pty for a variety
+ * of reasons, such as X servers or network logins.
+ *
+ * the flags which login supports are
+ *
+ * -p - preserve the environment
+ * -r - perform autologin protocol for rlogin
+ * -f - do not perform authentication, user is preauthenticated
+ * -h - the name of the remote host
+ */
+int main (int argc, char **argv)
+{
+ const char *tmptty;
+ char tty[BUFSIZ];
+
+#ifdef RLOGIN
+ char term[128] = "";
+#endif /* RLOGIN */
+#if defined(HAVE_STRFTIME) && !defined(USE_PAM)
+ char ptime[80];
+#endif
+ unsigned int delay;
+ unsigned int retries;
+ bool subroot = false;
+#ifndef USE_PAM
+ bool is_console;
+#endif
+ int err;
+ unsigned int timeout;
+ const char *cp;
+ const char *tmp;
+ char fromhost[512];
+ struct passwd *pwd = NULL;
+ char **envp = environ;
+ const char *failent_user;
+ /*@null@*/struct utmp *utent;
+
+#ifdef USE_PAM
+ int retcode;
+ pid_t child;
+ char *pam_user = NULL;
+#else
+ struct spwd *spwd = NULL;
+#endif
+ /*
+ * Some quick initialization.
+ */
+
+ sanitize_env ();
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ initenv ();
+
+ amroot = (getuid () == 0);
+ Prog = Basename (argv[0]);
+
+ if (geteuid() != 0) {
+ fprintf (stderr, _("%s: Cannot possibly work without effective root\n"), Prog);
+ exit (1);
+ }
+
+ process_flags (argc, argv);
+
+ if ((isatty (0) == 0) || (isatty (1) == 0) || (isatty (2) == 0)) {
+ exit (1); /* must be a terminal */
+ }
+
+ utent = get_current_utmp ();
+ /*
+ * Be picky if run by normal users (possible if installed setuid
+ * root), but not if run by root. This way it still allows logins
+ * even if your getty is broken, or if something corrupts utmp,
+ * but users must "exec login" which will use the existing utmp
+ * entry (will not overwrite remote hostname). --marekm
+ */
+ if (!amroot && (NULL == utent)) {
+ (void) puts (_("No utmp entry. You must exec \"login\" from the lowest level \"sh\""));
+ exit (1);
+ }
+ /* NOTE: utent might be NULL afterwards */
+
+ tmptty = ttyname (0);
+ if (NULL == tmptty) {
+ tmptty = "UNKNOWN";
+ }
+ STRFCPY (tty, tmptty);
+
+#ifndef USE_PAM
+ is_console = console (tty);
+#endif
+
+ if (rflg || hflg) {
+ /*
+ * Add remote hostname to the environment. I think
+ * (not sure) I saw it once on Irix. --marekm
+ */
+ addenv ("REMOTEHOST", hostname);
+ }
+ if (fflg) {
+ preauth_flag = true;
+ }
+ if (hflg) {
+ reason = PW_RLOGIN;
+ }
+#ifdef RLOGIN
+ if (rflg) {
+ assert (NULL == username);
+ username = xmalloc (USER_NAME_MAX_LENGTH + 1);
+ username[USER_NAME_MAX_LENGTH] = '\0';
+ if (do_rlogin (hostname, username, USER_NAME_MAX_LENGTH, term, sizeof term)) {
+ preauth_flag = true;
+ } else {
+ free (username);
+ username = NULL;
+ }
+ }
+#endif /* RLOGIN */
+
+ OPENLOG ("login");
+
+ setup_tty ();
+
+#ifndef USE_PAM
+ (void) umask (getdef_num ("UMASK", GETDEF_DEFAULT_UMASK));
+
+ {
+ /*
+ * Use the ULIMIT in the login.defs file, and if
+ * there isn't one, use the default value. The
+ * user may have one for themselves, but otherwise,
+ * just take what you get.
+ */
+ long limit = getdef_long ("ULIMIT", -1L);
+
+ if (limit != -1) {
+ set_filesize_limit (limit);
+ }
+ }
+
+#endif
+ /*
+ * The entire environment will be preserved if the -p flag
+ * is used.
+ */
+ if (pflg) {
+ while (NULL != *envp) { /* add inherited environment, */
+ addenv (*envp, NULL); /* some variables change later */
+ envp++;
+ }
+ }
+
+#ifdef RLOGIN
+ if (term[0] != '\0') {
+ addenv ("TERM", term);
+ } else
+#endif /* RLOGIN */
+ {
+ /* preserve TERM from getty */
+ if (!pflg) {
+ tmp = getenv ("TERM");
+ if (NULL != tmp) {
+ addenv ("TERM", tmp);
+ }
+ }
+ }
+
+ init_env ();
+
+ if (optind < argc) { /* now set command line variables */
+ set_env (argc - optind, &argv[optind]);
+ }
+
+ if (rflg || hflg) {
+ cp = hostname;
+#ifdef HAVE_STRUCT_UTMP_UT_HOST
+ } else if ((NULL != utent) && ('\0' != utent->ut_host[0])) {
+ cp = utent->ut_host;
+#endif /* HAVE_STRUCT_UTMP_UT_HOST */
+ } else {
+ cp = "";
+ }
+
+ if ('\0' != *cp) {
+ snprintf (fromhost, sizeof fromhost,
+ " on '%.100s' from '%.200s'", tty, cp);
+ } else {
+ snprintf (fromhost, sizeof fromhost,
+ " on '%.100s'", tty);
+ }
+
+ top:
+ /* only allow ALARM sec. for login */
+ timeout = getdef_unum ("LOGIN_TIMEOUT", ALARM);
+ snprintf (tmsg, sizeof tmsg,
+ _("\nLogin timed out after %u seconds.\n"), timeout);
+ (void) signal (SIGALRM, alarm_handler);
+ if (timeout > 0) {
+ (void) alarm (timeout);
+ }
+
+ environ = newenvp; /* make new environment active */
+ delay = getdef_unum ("FAIL_DELAY", 1);
+ retries = getdef_unum ("LOGIN_RETRIES", RETRIES);
+
+#ifdef USE_PAM
+ retcode = pam_start ("login", username, &conv, &pamh);
+ if (retcode != PAM_SUCCESS) {
+ fprintf (stderr,
+ _("login: PAM Failure, aborting: %s\n"),
+ pam_strerror (pamh, retcode));
+ SYSLOG ((LOG_ERR, "Couldn't initialize PAM: %s",
+ pam_strerror (pamh, retcode)));
+ exit (99);
+ }
+
+ /*
+ * hostname & tty are either set to NULL or their correct values,
+ * depending on how much we know. We also set PAM's fail delay to
+ * ours.
+ *
+ * PAM_RHOST and PAM_TTY are used for authentication, only use
+ * information coming from login or from the caller (e.g. no utmp)
+ */
+ retcode = pam_set_item (pamh, PAM_RHOST, hostname);
+ PAM_FAIL_CHECK;
+ retcode = pam_set_item (pamh, PAM_TTY, tty);
+ PAM_FAIL_CHECK;
+#ifdef HAS_PAM_FAIL_DELAY
+ retcode = pam_fail_delay (pamh, 1000000 * delay);
+ PAM_FAIL_CHECK;
+#endif
+ /* if fflg, then the user has already been authenticated */
+ if (!fflg) {
+ unsigned int failcount = 0;
+ char hostn[256];
+ char loginprompt[256]; /* That's one hell of a prompt :) */
+
+ /* Make the login prompt look like we want it */
+ if (gethostname (hostn, sizeof (hostn)) == 0) {
+ snprintf (loginprompt,
+ sizeof (loginprompt),
+ _("%s login: "), hostn);
+ } else {
+ strncpy (loginprompt, _("login: "),
+ sizeof (loginprompt));
+ }
+
+ retcode = pam_set_item (pamh, PAM_USER_PROMPT, loginprompt);
+ PAM_FAIL_CHECK;
+
+ /* if we didn't get a user on the command line,
+ set it to NULL */
+ get_pam_user (&pam_user);
+ if ((NULL != pam_user) && ('\0' == pam_user[0])) {
+ retcode = pam_set_item (pamh, PAM_USER, NULL);
+ PAM_FAIL_CHECK;
+ }
+
+ /*
+ * There may be better ways to deal with some of
+ * these conditions, but at least this way I don't
+ * think we'll be giving away information. Perhaps
+ * someday we can trust that all PAM modules will
+ * pay attention to failure count and get rid of
+ * MAX_LOGIN_TRIES?
+ */
+ failcount = 0;
+ while (true) {
+ bool failed = false;
+
+ failcount++;
+#ifdef HAS_PAM_FAIL_DELAY
+ if (delay > 0) {
+ retcode = pam_fail_delay(pamh, 1000000*delay);
+ PAM_FAIL_CHECK;
+ }
+#endif
+
+ retcode = pam_authenticate (pamh, 0);
+
+ get_pam_user (&pam_user);
+ failent_user = get_failent_user (pam_user);
+
+ if (retcode == PAM_MAXTRIES) {
+ SYSLOG ((LOG_NOTICE,
+ "TOO MANY LOGIN TRIES (%u)%s FOR '%s'",
+ failcount, fromhost, failent_user));
+ fprintf (stderr,
+ _("Maximum number of tries exceeded (%u)\n"),
+ failcount);
+ PAM_END;
+ exit(0);
+ } else if (retcode == PAM_ABORT) {
+ /* Serious problems, quit now */
+ (void) fputs (_("login: abort requested by PAM\n"), stderr);
+ SYSLOG ((LOG_ERR,"PAM_ABORT returned from pam_authenticate()"));
+ PAM_END;
+ exit(99);
+ } else if (retcode != PAM_SUCCESS) {
+ SYSLOG ((LOG_NOTICE,"FAILED LOGIN (%u)%s FOR '%s', %s",
+ failcount, fromhost, failent_user,
+ pam_strerror (pamh, retcode)));
+ failed = true;
+ }
+
+ if (!failed) {
+ break;
+ }
+
+#ifdef WITH_AUDIT
+ audit_fd = audit_open ();
+ audit_log_acct_message (audit_fd,
+ AUDIT_USER_LOGIN,
+ NULL, /* Prog. name */
+ "login",
+ failent_user,
+ AUDIT_NO_ID,
+ hostname,
+ NULL, /* addr */
+ tty,
+ 0); /* result */
+ close (audit_fd);
+#endif /* WITH_AUDIT */
+
+ (void) puts ("");
+ (void) puts (_("Login incorrect"));
+
+ if (failcount >= retries) {
+ SYSLOG ((LOG_NOTICE,
+ "TOO MANY LOGIN TRIES (%u)%s FOR '%s'",
+ failcount, fromhost, failent_user));
+ fprintf (stderr,
+ _("Maximum number of tries exceeded (%u)\n"),
+ failcount);
+ PAM_END;
+ exit(0);
+ }
+
+ /*
+ * Let's give it another go around.
+ * Even if a username was given on the command
+ * line, prompt again for the username.
+ */
+ retcode = pam_set_item (pamh, PAM_USER, NULL);
+ PAM_FAIL_CHECK;
+ }
+
+ /* We don't get here unless they were authenticated above */
+ (void) alarm (0);
+ }
+
+ /* Check the account validity */
+ retcode = pam_acct_mgmt (pamh, 0);
+ if (retcode == PAM_NEW_AUTHTOK_REQD) {
+ retcode = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+ }
+ PAM_FAIL_CHECK;
+
+ /* Open the PAM session */
+ get_pam_user (&pam_user);
+ retcode = pam_open_session (pamh, hushed (pam_user) ? PAM_SILENT : 0);
+ PAM_FAIL_CHECK;
+
+ /* Grab the user information out of the password file for future usage
+ * First get the username that we are actually using, though.
+ *
+ * From now on, we will discard changes of the user (PAM_USER) by
+ * PAM APIs.
+ */
+ get_pam_user (&pam_user);
+ if (NULL != username) {
+ free (username);
+ }
+ username = xstrdup (pam_user);
+ failent_user = get_failent_user (username);
+
+ pwd = xgetpwnam (username);
+ if (NULL == pwd) {
+ SYSLOG ((LOG_ERR, "cannot find user %s", failent_user));
+ fprintf (stderr,
+ _("Cannot find user (%s)\n"),
+ username);
+ exit (1);
+ }
+
+ /* This set up the process credential (group) and initialize the
+ * supplementary group access list.
+ * This has to be done before pam_setcred
+ */
+ if (setup_groups (pwd) != 0) {
+ exit (1);
+ }
+
+ retcode = pam_setcred (pamh, PAM_ESTABLISH_CRED);
+ PAM_FAIL_CHECK;
+ /* NOTE: If pam_setcred changes PAM_USER, this will not be taken
+ * into account.
+ */
+
+#else /* ! USE_PAM */
+ while (true) { /* repeatedly get login/password pairs */
+ bool failed;
+ /* user_passwd is always a pointer to this constant string
+ * or a passwd or shadow password that will be memzero by
+ * pw_free / spw_free.
+ * Do not free() user_passwd. */
+ const char *user_passwd = "!";
+
+ /* Do some cleanup to avoid keeping entries we do not need
+ * anymore. */
+ if (NULL != pwd) {
+ pw_free (pwd);
+ pwd = NULL;
+ }
+ if (NULL != spwd) {
+ spw_free (spwd);
+ spwd = NULL;
+ }
+
+ failed = false; /* haven't failed authentication yet */
+ if (NULL == username) { /* need to get a login id */
+ if (subroot) {
+ closelog ();
+ exit (1);
+ }
+ preauth_flag = false;
+ username = xmalloc (USER_NAME_MAX_LENGTH + 1);
+ username[USER_NAME_MAX_LENGTH] = '\0';
+ login_prompt (_("\n%s login: "), username, USER_NAME_MAX_LENGTH);
+
+ if ('\0' == username[0]) {
+ /* Prompt for a new login */
+ free (username);
+ username = NULL;
+ continue;
+ }
+ }
+ /* Get the username to be used to log failures */
+ failent_user = get_failent_user (username);
+
+ pwd = xgetpwnam (username);
+ if (NULL == pwd) {
+ preauth_flag = false;
+ failed = true;
+ } else {
+ user_passwd = pwd->pw_passwd;
+ /*
+ * If the encrypted password begins with a "!",
+ * the account is locked and the user cannot
+ * login, even if they have been
+ * "pre-authenticated."
+ */
+ if ( ('!' == user_passwd[0])
+ || ('*' == user_passwd[0])) {
+ failed = true;
+ }
+ }
+
+ if (strcmp (user_passwd, SHADOW_PASSWD_STRING) == 0) {
+ spwd = xgetspnam (username);
+ if (NULL != spwd) {
+ user_passwd = spwd->sp_pwdp;
+ } else {
+ /* The user exists in passwd, but not in
+ * shadow. SHADOW_PASSWD_STRING indicates
+ * that the password shall be in shadow.
+ */
+ SYSLOG ((LOG_WARN,
+ "no shadow password for '%s'%s",
+ username, fromhost));
+ }
+ }
+
+ /*
+ * The -r and -f flags provide a name which has already
+ * been authenticated by some server.
+ */
+ if (preauth_flag) {
+ goto auth_ok;
+ }
+
+ if (pw_auth (user_passwd, username, reason, (char *) 0) == 0) {
+ goto auth_ok;
+ }
+
+ SYSLOG ((LOG_WARN, "invalid password for '%s' %s",
+ failent_user, fromhost));
+ failed = true;
+
+ auth_ok:
+ /*
+ * This is the point where all authenticated users wind up.
+ * If you reach this far, your password has been
+ * authenticated and so on.
+ */
+ if ( !failed
+ && (NULL != pwd)
+ && (0 == pwd->pw_uid)
+ && !is_console) {
+ SYSLOG ((LOG_CRIT, "ILLEGAL ROOT LOGIN %s", fromhost));
+ failed = true;
+ }
+ if ( !failed
+ && !login_access (username, ('\0' != *hostname) ? hostname : tty)) {
+ SYSLOG ((LOG_WARN, "LOGIN '%s' REFUSED %s",
+ username, fromhost));
+ failed = true;
+ }
+ if ( (NULL != pwd)
+ && getdef_bool ("FAILLOG_ENAB")
+ && !failcheck (pwd->pw_uid, &faillog, failed)) {
+ SYSLOG ((LOG_CRIT,
+ "exceeded failure limit for '%s' %s",
+ username, fromhost));
+ failed = true;
+ }
+ if (!failed) {
+ break;
+ }
+
+ /* don't log non-existent users */
+ if ((NULL != pwd) && getdef_bool ("FAILLOG_ENAB")) {
+ failure (pwd->pw_uid, tty, &faillog);
+ }
+ if (getdef_str ("FTMP_FILE") != NULL) {
+#ifdef USE_UTMPX
+ struct utmpx *failent =
+ prepare_utmpx (failent_user,
+ tty,
+ /* FIXME: or fromhost? */hostname,
+ utent);
+#else /* !USE_UTMPX */
+ struct utmp *failent =
+ prepare_utmp (failent_user,
+ tty,
+ hostname,
+ utent);
+#endif /* !USE_UTMPX */
+ failtmp (failent_user, failent);
+ free (failent);
+ }
+
+ retries--;
+ if (retries <= 0) {
+ SYSLOG ((LOG_CRIT, "REPEATED login failures%s",
+ fromhost));
+ }
+
+ /*
+ * If this was a passwordless account and we get here, login
+ * was denied (securetty, faillog, etc.). There was no
+ * password prompt, so do it now (will always fail - the bad
+ * guys won't see that the passwordless account exists at
+ * all). --marekm
+ */
+ if (user_passwd[0] == '\0') {
+ pw_auth ("!", username, reason, (char *) 0);
+ }
+
+ /*
+ * Authentication of this user failed.
+ * The username must be confirmed in the next try.
+ */
+ free (username);
+ username = NULL;
+
+ /*
+ * Wait a while (a la SVR4 /usr/bin/login) before attempting
+ * to login the user again. If the earlier alarm occurs
+ * before the sleep() below completes, login will exit.
+ */
+ if (delay > 0) {
+ (void) sleep (delay);
+ }
+
+ (void) puts (_("Login incorrect"));
+
+ /* allow only one attempt with -r or -f */
+ if (rflg || fflg || (retries <= 0)) {
+ closelog ();
+ exit (1);
+ }
+ } /* while (true) */
+#endif /* ! USE_PAM */
+ assert (NULL != username);
+ assert (NULL != pwd);
+
+ (void) alarm (0); /* turn off alarm clock */
+
+#ifndef USE_PAM /* PAM does this */
+ /*
+ * porttime checks moved here, after the user has been
+ * authenticated. now prints a message, as suggested
+ * by Ivan Nejgebauer <ian@unsux.ns.ac.yu>. --marekm
+ */
+ if ( getdef_bool ("PORTTIME_CHECKS_ENAB")
+ && !isttytime (username, tty, time ((time_t *) 0))) {
+ SYSLOG ((LOG_WARN, "invalid login time for '%s'%s",
+ username, fromhost));
+ closelog ();
+ bad_time_notify ();
+ exit (1);
+ }
+
+ check_nologin (pwd->pw_uid == 0);
+#endif
+
+ if (getenv ("IFS")) { /* don't export user IFS ... */
+ addenv ("IFS= \t\n", NULL); /* ... instead, set a safe IFS */
+ }
+
+ if (pwd->pw_shell[0] == '*') { /* subsystem root */
+ pwd->pw_shell++; /* skip the '*' */
+ subsystem (pwd); /* figure out what to execute */
+ subroot = true; /* say I was here again */
+ endpwent (); /* close all of the file which were */
+ endgrent (); /* open in the original rooted file */
+ endspent (); /* system. they will be re-opened */
+#ifdef SHADOWGRP
+ endsgent (); /* in the new rooted file system */
+#endif
+ goto top; /* go do all this all over again */
+ }
+
+#ifdef WITH_AUDIT
+ audit_fd = audit_open ();
+ audit_log_acct_message (audit_fd,
+ AUDIT_USER_LOGIN,
+ NULL, /* Prog. name */
+ "login",
+ username,
+ AUDIT_NO_ID,
+ hostname,
+ NULL, /* addr */
+ tty,
+ 1); /* result */
+ close (audit_fd);
+#endif /* WITH_AUDIT */
+
+#ifndef USE_PAM /* pam_lastlog handles this */
+ if (getdef_bool ("LASTLOG_ENAB")) { /* give last login and log this one */
+ dolastlog (&ll, pwd, tty, hostname);
+ }
+#endif
+
+#ifndef USE_PAM /* PAM handles this as well */
+ /*
+ * Have to do this while we still have root privileges, otherwise we
+ * don't have access to /etc/shadow.
+ */
+ if (NULL != spwd) { /* check for age of password */
+ if (expire (pwd, spwd)) {
+ /* The user updated her password, get the new
+ * entries.
+ * Use the x variants because we need to keep the
+ * entry for a long time, and there might be other
+ * getxxyy in between.
+ */
+ pw_free (pwd);
+ pwd = xgetpwnam (username);
+ if (NULL == pwd) {
+ SYSLOG ((LOG_ERR,
+ "cannot find user %s after update of expired password",
+ username));
+ exit (1);
+ }
+ spw_free (spwd);
+ spwd = xgetspnam (username);
+ }
+ }
+ setup_limits (pwd); /* nice, ulimit etc. */
+#endif /* ! USE_PAM */
+ chown_tty (pwd);
+
+#ifdef USE_PAM
+ /*
+ * We must fork before setuid() because we need to call
+ * pam_close_session() as root.
+ */
+ (void) signal (SIGINT, SIG_IGN);
+ child = fork ();
+ if (child < 0) {
+ /* error in fork() */
+ fprintf (stderr, _("%s: failure forking: %s"),
+ Prog, strerror (errno));
+ PAM_END;
+ exit (0);
+ } else if (child != 0) {
+ /*
+ * parent - wait for child to finish, then cleanup
+ * session
+ */
+ wait (NULL);
+ PAM_END;
+ exit (0);
+ }
+ /* child */
+#endif
+
+ /* If we were init, we need to start a new session */
+ if (getppid() == 1) {
+ setsid();
+ if (ioctl(0, TIOCSCTTY, 1) != 0) {
+ fprintf (stderr, _("TIOCSCTTY failed on %s"), tty);
+ }
+ }
+
+ /*
+ * The utmp entry needs to be updated to indicate the new status
+ * of the session, the new PID and SID.
+ */
+ update_utmp (username, tty, hostname, utent);
+
+ /* The pwd and spwd entries for the user have been copied.
+ *
+ * Close all the files so that unauthorized access won't occur.
+ */
+ endpwent (); /* stop access to password file */
+ endgrent (); /* stop access to group file */
+ endspent (); /* stop access to shadow passwd file */
+#ifdef SHADOWGRP
+ endsgent (); /* stop access to shadow group file */
+#endif
+
+ /* Drop root privileges */
+#ifndef USE_PAM
+ if (setup_uid_gid (pwd, is_console))
+#else
+ /* The group privileges were already dropped.
+ * See setup_groups() above.
+ */
+ if (change_uid (pwd))
+#endif
+ {
+ exit (1);
+ }
+
+ setup_env (pwd); /* set env vars, cd to the home dir */
+
+#ifdef USE_PAM
+ {
+ const char *const *env;
+
+ env = (const char *const *) pam_getenvlist (pamh);
+ while ((NULL != env) && (NULL != *env)) {
+ addenv (*env, NULL);
+ env++;
+ }
+ }
+#endif
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ if (!hushed (username)) {
+ addenv ("HUSHLOGIN=FALSE", NULL);
+ /*
+ * pam_unix, pam_mail and pam_lastlog should take care of
+ * this
+ */
+#ifndef USE_PAM
+ motd (); /* print the message of the day */
+ if ( getdef_bool ("FAILLOG_ENAB")
+ && (0 != faillog.fail_cnt)) {
+ failprint (&faillog);
+ /* Reset the lockout times if logged in */
+ if ( (0 != faillog.fail_max)
+ && (faillog.fail_cnt >= faillog.fail_max)) {
+ (void) puts (_("Warning: login re-enabled after temporary lockout."));
+ SYSLOG ((LOG_WARN,
+ "login '%s' re-enabled after temporary lockout (%d failures)",
+ username, (int) faillog.fail_cnt));
+ }
+ }
+ if ( getdef_bool ("LASTLOG_ENAB")
+ && (ll.ll_time != 0)) {
+ time_t ll_time = ll.ll_time;
+
+#ifdef HAVE_STRFTIME
+ (void) strftime (ptime, sizeof (ptime),
+ "%a %b %e %H:%M:%S %z %Y",
+ localtime (&ll_time));
+ printf (_("Last login: %s on %s"),
+ ptime, ll.ll_line);
+#else
+ printf (_("Last login: %.19s on %s"),
+ ctime (&ll_time), ll.ll_line);
+#endif
+#ifdef HAVE_LL_HOST /* __linux__ || SUN4 */
+ if ('\0' != ll.ll_host[0]) {
+ printf (_(" from %.*s"),
+ (int) sizeof ll.ll_host, ll.ll_host);
+ }
+#endif
+ printf (".\n");
+ }
+ agecheck (spwd);
+
+ mailcheck (); /* report on the status of mail */
+#endif /* !USE_PAM */
+ } else {
+ addenv ("HUSHLOGIN=TRUE", NULL);
+ }
+
+ ttytype (tty);
+
+ (void) signal (SIGQUIT, SIG_DFL); /* default quit signal */
+ (void) signal (SIGTERM, SIG_DFL); /* default terminate signal */
+ (void) signal (SIGALRM, SIG_DFL); /* default alarm signal */
+ (void) signal (SIGHUP, SIG_DFL); /* added this. --marekm */
+ (void) signal (SIGINT, SIG_DFL); /* default interrupt signal */
+
+ if (0 == pwd->pw_uid) {
+ SYSLOG ((LOG_NOTICE, "ROOT LOGIN %s", fromhost));
+ } else if (getdef_bool ("LOG_OK_LOGINS")) {
+ SYSLOG ((LOG_INFO, "'%s' logged in %s", username, fromhost));
+ }
+ closelog ();
+ tmp = getdef_str ("FAKE_SHELL");
+ if (NULL != tmp) {
+ err = shell (tmp, pwd->pw_shell, newenvp); /* fake shell */
+ } else {
+ /* exec the shell finally */
+ err = shell (pwd->pw_shell, (char *) 0, newenvp);
+ }
+
+ return ((err == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
+}
+
diff --git a/src/login_nopam.c b/src/login_nopam.c
new file mode 100644
index 0000000..68a3775
--- /dev/null
+++ b/src/login_nopam.c
@@ -0,0 +1,336 @@
+/* Taken from logdaemon-5.0, only minimal changes. --marekm */
+
+/************************************************************************
+* Copyright 1995 by Wietse Venema. All rights reserved. Individual files
+* may be covered by other copyrights (as noted in the file itself.)
+*
+* This material was originally written and compiled by Wietse Venema at
+* Eindhoven University of Technology, The Netherlands, in 1990, 1991,
+* 1992, 1993, 1994 and 1995.
+*
+* Redistribution and use in source and binary forms are permitted
+* provided that this entire copyright notice is duplicated in all such
+* copies.
+*
+* This software is provided "as is" and without any expressed or implied
+* warranties, including, without limitation, the implied warranties of
+* merchantibility and fitness for any particular purpose.
+************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifndef USE_PAM
+#ident "$Id$"
+
+#include "prototypes.h"
+ /*
+ * This module implements a simple but effective form of login access
+ * control based on login names and on host (or domain) names, internet
+ * addresses (or network numbers), or on terminal line names in case of
+ * non-networked logins. Diagnostics are reported through syslog(3).
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+ */
+#include <sys/types.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <ctype.h>
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#include <grp.h>
+#ifdef PRIMARY_GROUP_MATCH
+#include <pwd.h>
+#endif
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h> /* for inet_ntoa() */
+
+#if !defined(MAXHOSTNAMELEN) || (MAXHOSTNAMELEN < 64)
+#undef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 256
+#endif
+
+ /* Path name of the access control file. */
+#ifndef TABLE
+#define TABLE "/etc/login.access"
+#endif
+
+/* Delimiters for fields and for lists of users, ttys or hosts. */
+static char fs[] = ":"; /* field separator */
+static char sep[] = ", \t"; /* list-element separator */
+
+static bool list_match (char *list, const char *item, bool (*match_fn) (const char *, const char *));
+static bool user_match (const char *tok, const char *string);
+static bool from_match (const char *tok, const char *string);
+static bool string_match (const char *tok, const char *string);
+static const char *resolve_hostname (const char *string);
+
+/* login_access - match username/group and host/tty with access control file */
+int login_access (const char *user, const char *from)
+{
+ FILE *fp;
+ char line[BUFSIZ];
+ char *perm; /* becomes permission field */
+ char *users; /* becomes list of login names */
+ char *froms; /* becomes list of terminals or hosts */
+ bool match = false;
+
+ /*
+ * Process the table one line at a time and stop at the first match.
+ * Blank lines and lines that begin with a '#' character are ignored.
+ * Non-comment lines are broken at the ':' character. All fields are
+ * mandatory. The first field should be a "+" or "-" character. A
+ * non-existing table means no access control.
+ */
+ fp = fopen (TABLE, "r");
+ if (NULL != fp) {
+ int lineno = 0; /* for diagnostics */
+ while ( !match
+ && (fgets (line, (int) sizeof (line), fp) == line)) {
+ int end;
+ lineno++;
+ end = (int) strlen (line) - 1;
+ if (line[end] != '\n') {
+ SYSLOG ((LOG_ERR,
+ "%s: line %d: missing newline or line too long",
+ TABLE, lineno));
+ continue;
+ }
+ if (line[0] == '#') {
+ continue; /* comment line */
+ }
+ while (end > 0 && isspace (line[end - 1])) {
+ end--;
+ }
+ line[end] = '\0'; /* strip trailing whitespace */
+ if (line[0] == '\0') { /* skip blank lines */
+ continue;
+ }
+ if ( ((perm = strtok (line, fs)) == NULL)
+ || ((users = strtok ((char *) 0, fs)) == NULL)
+ || ((froms = strtok ((char *) 0, fs)) == NULL)
+ || (strtok ((char *) 0, fs) != NULL)) {
+ SYSLOG ((LOG_ERR,
+ "%s: line %d: bad field count",
+ TABLE, lineno));
+ continue;
+ }
+ if (perm[0] != '+' && perm[0] != '-') {
+ SYSLOG ((LOG_ERR,
+ "%s: line %d: bad first field",
+ TABLE, lineno));
+ continue;
+ }
+ match = ( list_match (froms, from, from_match)
+ && list_match (users, user, user_match));
+ }
+ (void) fclose (fp);
+ } else if (errno != ENOENT) {
+ int err = errno;
+ SYSLOG ((LOG_ERR, "cannot open %s: %s", TABLE, strerror (err)));
+ }
+ return (!match || (line[0] == '+'))?1:0;
+}
+
+/* list_match - match an item against a list of tokens with exceptions */
+static bool list_match (char *list, const char *item, bool (*match_fn) (const char *, const char*))
+{
+ char *tok;
+ bool match = false;
+
+ /*
+ * Process tokens one at a time. We have exhausted all possible matches
+ * when we reach an "EXCEPT" token or the end of the list. If we do find
+ * a match, look for an "EXCEPT" list and recurse to determine whether
+ * the match is affected by any exceptions.
+ */
+ for (tok = strtok (list, sep); tok != NULL; tok = strtok ((char *) 0, sep)) {
+ if (strcasecmp (tok, "EXCEPT") == 0) { /* EXCEPT: give up */
+ break;
+ }
+ match = (*match_fn) (tok, item);
+ if (match) {
+ break;
+ }
+ }
+
+ /* Process exceptions to matches. */
+ if (match) {
+ while ( ((tok = strtok ((char *) 0, sep)) != NULL)
+ && (strcasecmp (tok, "EXCEPT") != 0))
+ /* VOID */ ;
+ if (tok == 0 || !list_match ((char *) 0, item, match_fn)) {
+ return (match);
+ }
+ }
+ return false;
+}
+
+/* myhostname - figure out local machine name */
+static char *myhostname (void)
+{
+ static char name[MAXHOSTNAMELEN + 1] = "";
+
+ if (name[0] == '\0') {
+ gethostname (name, sizeof (name));
+ name[MAXHOSTNAMELEN] = '\0';
+ }
+ return (name);
+}
+
+#if HAVE_INNETGR
+/* netgroup_match - match group against machine or user */
+static bool
+netgroup_match (const char *group, const char *machine, const char *user)
+{
+ static char *mydomain = (char *)0;
+
+ if (mydomain == (char *)0) {
+ static char domain[MAXHOSTNAMELEN + 1];
+
+ getdomainname (domain, MAXHOSTNAMELEN);
+ mydomain = domain;
+ }
+
+ return (innetgr (group, machine, user, mydomain) != 0);
+}
+#endif
+
+/* user_match - match a username against one token */
+static bool user_match (const char *tok, const char *string)
+{
+ struct group *group;
+
+#ifdef PRIMARY_GROUP_MATCH
+ struct passwd *userinf;
+#endif
+ char *at;
+
+ /*
+ * If a token has the magic value "ALL" the match always succeeds.
+ * Otherwise, return true if the token fully matches the username, or if
+ * the token is a group that contains the username.
+ */
+ at = strchr (tok + 1, '@');
+ if (NULL != at) { /* split user@host pattern */
+ *at = '\0';
+ return ( user_match (tok, string)
+ && from_match (at + 1, myhostname ()));
+#if HAVE_INNETGR
+ } else if (tok[0] == '@') { /* netgroup */
+ return (netgroup_match (tok + 1, (char *) 0, string));
+#endif
+ } else if (string_match (tok, string)) { /* ALL or exact match */
+ return true;
+ /* local, no need for xgetgrnam */
+ } else if ((group = getgrnam (tok)) != NULL) { /* try group membership */
+ int i;
+ for (i = 0; NULL != group->gr_mem[i]; i++) {
+ if (strcasecmp (string, group->gr_mem[i]) == 0) {
+ return true;
+ }
+ }
+#ifdef PRIMARY_GROUP_MATCH
+ /*
+ * If the string is an user whose initial GID matches the token,
+ * accept it. May avoid excessively long lines in /etc/group.
+ * Radu-Adrian Feurdean <raf@licj.soroscj.ro>
+ *
+ * XXX - disabled by default for now. Need to verify that
+ * getpwnam() doesn't have some nasty side effects. --marekm
+ */
+ /* local, no need for xgetpwnam */
+ userinf = getpwnam (string);
+ if (NULL != userinf) {
+ if (userinf->pw_gid == group->gr_gid) {
+ return true;
+ }
+ }
+#endif
+ }
+ return false;
+}
+
+static const char *resolve_hostname (const char *string)
+{
+ /*
+ * Resolve hostname to numeric IP address, as suggested
+ * by Dave Hagewood <admin@arrowweb.com>. --marekm
+ */
+ struct hostent *hp;
+
+ hp = gethostbyname (string);
+ if (NULL != hp) {
+ return inet_ntoa (*((struct in_addr *) *(hp->h_addr_list)));
+ }
+
+ SYSLOG ((LOG_ERR, "%s - unknown host", string));
+ return string;
+}
+
+/* from_match - match a host or tty against a list of tokens */
+
+static bool from_match (const char *tok, const char *string)
+{
+ size_t tok_len;
+
+ /*
+ * If a token has the magic value "ALL" the match always succeeds. Return
+ * true if the token fully matches the string. If the token is a domain
+ * name, return true if it matches the last fields of the string. If the
+ * token has the magic value "LOCAL", return true if the string does not
+ * contain a "." character. If the token is a network number, return true
+ * if it matches the head of the string.
+ */
+#if HAVE_INNETGR
+ if (tok[0] == '@') { /* netgroup */
+ return (netgroup_match (tok + 1, string, (char *) 0));
+ } else
+#endif
+ if (string_match (tok, string)) { /* ALL or exact match */
+ return true;
+ } else if (tok[0] == '.') { /* domain: match last fields */
+ size_t str_len;
+ str_len = strlen (string);
+ tok_len = strlen (tok);
+ if ( (str_len > tok_len)
+ && (strcasecmp (tok, string + str_len - tok_len) == 0)) {
+ return true;
+ }
+ } else if (strcasecmp (tok, "LOCAL") == 0) { /* local: no dots */
+ if (strchr (string, '.') == NULL) {
+ return true;
+ }
+ } else if ( (tok[(tok_len = strlen (tok)) - 1] == '.') /* network */
+ && (strncmp (tok, resolve_hostname (string), tok_len) == 0)) {
+ return true;
+ }
+ return false;
+}
+
+/* string_match - match a string against one token */
+static bool string_match (const char *tok, const char *string)
+{
+
+ /*
+ * If the token has the magic value "ALL" the match always succeeds.
+ * Otherwise, return true if the token fully matches the string.
+ */
+ if (strcasecmp (tok, "ALL") == 0) { /* all: always matches */
+ return true;
+ } else if (strcasecmp (tok, string) == 0) { /* try exact match */
+ return true;
+ }
+ return false;
+}
+
+#else /* !USE_PAM */
+extern int errno; /* warning: ANSI C forbids an empty source file */
+#endif /* !USE_PAM */
diff --git a/src/logoutd.c b/src/logoutd.c
new file mode 100644
index 0000000..1503a74
--- /dev/null
+++ b/src/logoutd.c
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 1991 - 1993, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2001 - 2006, Tomasz Kłoczko
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include "defines.h"
+#include "prototypes.h"
+/*
+ * Global variables
+ */
+const char *Prog;
+
+#ifndef DEFAULT_HUP_MESG
+#define DEFAULT_HUP_MESG _("login time exceeded\n\n")
+#endif
+
+#ifndef HUP_MESG_FILE
+#define HUP_MESG_FILE "/etc/logoutd.mesg"
+#endif
+
+/* local function prototypes */
+#ifdef USE_UTMPX
+static int check_login (const struct utmpx *ut);
+#else /* !USE_UTMPX */
+static int check_login (const struct utmp *ut);
+#endif /* !USE_UTMPX */
+static void send_mesg_to_tty (int tty_fd);
+
+/*
+ * check_login - check if user (struct utmpx/utmp) allowed to stay logged in
+ */
+#ifdef USE_UTMPX
+static int check_login (const struct utmpx *ut)
+#else /* !USE_UTMPX */
+static int check_login (const struct utmp *ut)
+#endif /* !USE_UTMPX */
+{
+ char user[sizeof (ut->ut_user) + 1];
+ time_t now;
+
+ /*
+ * ut_user may not have the terminating NUL.
+ */
+ strncpy (user, ut->ut_user, sizeof (ut->ut_user));
+ user[sizeof (ut->ut_user)] = '\0';
+
+ (void) time (&now);
+
+ /*
+ * Check if they are allowed to be logged in right now.
+ */
+ if (!isttytime (user, ut->ut_line, now)) {
+ return 0;
+ }
+ return 1;
+}
+
+
+static void send_mesg_to_tty (int tty_fd)
+{
+ TERMIO oldt, newt;
+ FILE *mesg_file, *tty_file;
+ bool is_tty;
+
+ tty_file = fdopen (tty_fd, "w");
+ if (NULL == tty_file) {
+ return;
+ }
+
+ is_tty = (GTTY (tty_fd, &oldt) == 0);
+ if (is_tty) {
+ /* Suggested by Ivan Nejgebauar <ian@unsux.ns.ac.yu>:
+ set OPOST before writing the message. */
+ newt = oldt;
+ newt.c_oflag |= OPOST;
+ STTY (tty_fd, &newt);
+ }
+
+ mesg_file = fopen (HUP_MESG_FILE, "r");
+ if (NULL != mesg_file) {
+ int c;
+ while ((c = getc (mesg_file)) != EOF) {
+ if (c == '\n') {
+ putc ('\r', tty_file);
+ }
+ putc (c, tty_file);
+ }
+ fclose (mesg_file);
+ } else {
+ fputs (DEFAULT_HUP_MESG, tty_file);
+ }
+ fflush (tty_file);
+ fclose (tty_file);
+
+ if (is_tty) {
+ STTY (tty_fd, &oldt);
+ }
+}
+
+
+/*
+ * logoutd - logout daemon to enforce /etc/porttime file policy
+ *
+ * logoutd is started at system boot time and enforces the login
+ * time and port restrictions specified in /etc/porttime. The
+ * utmpx/utmp file is periodically scanned and offending users are logged
+ * off from the system.
+ */
+int main (int argc, char **argv)
+{
+ int i;
+ int status;
+ pid_t pid;
+
+#ifdef USE_UTMPX
+ struct utmpx *ut;
+#else /* !USE_UTMPX */
+ struct utmp *ut;
+#endif /* !USE_UTMPX */
+ char user[sizeof (ut->ut_user) + 1]; /* terminating NUL */
+ char tty_name[sizeof (ut->ut_line) + 6]; /* /dev/ + NUL */
+ int tty_fd;
+
+ if (1 != argc) {
+ (void) fputs (_("Usage: logoutd\n"), stderr);
+ }
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+#ifndef DEBUG
+ for (i = 0; close (i) == 0; i++);
+
+ setpgrp ();
+
+ /*
+ * Put this process in the background.
+ */
+ pid = fork ();
+ if (pid > 0) {
+ /* parent */
+ exit (EXIT_SUCCESS);
+ } else if (pid < 0) {
+ /* error */
+ perror ("fork");
+ exit (EXIT_FAILURE);
+ }
+#endif /* !DEBUG */
+
+ /*
+ * Start syslogging everything
+ */
+ Prog = Basename (argv[0]);
+
+ OPENLOG ("logoutd");
+
+ /*
+ * Scan the utmpx/utmp file once per minute looking for users that
+ * are not supposed to still be logged in.
+ */
+ while (true) {
+
+ /*
+ * Attempt to re-open the utmpx/utmp file. The file is only
+ * open while it is being used.
+ */
+#ifdef USE_UTMPX
+ setutxent ();
+#else /* !USE_UTMPX */
+ setutent ();
+#endif /* !USE_UTMPX */
+
+ /*
+ * Read all of the entries in the utmpx/utmp file. The entries
+ * for login sessions will be checked to see if the user
+ * is permitted to be signed on at this time.
+ */
+#ifdef USE_UTMPX
+ while ((ut = getutxent ()) != NULL)
+#else /* !USE_UTMPX */
+ while ((ut = getutent ()) != NULL)
+#endif /* !USE_UTMPX */
+ {
+ if (ut->ut_type != USER_PROCESS) {
+ continue;
+ }
+ if (ut->ut_user[0] == '\0') {
+ continue;
+ }
+ if (check_login (ut)) {
+ continue;
+ }
+
+ /*
+ * Put the rest of this in a child process. This
+ * keeps the scan from waiting on other ports to die.
+ */
+
+ pid = fork ();
+ if (pid > 0) {
+ /* parent */
+ continue;
+ } else if (pid < 0) {
+ /* failed - give up until the next scan */
+ break;
+ }
+ /* child */
+
+ if (strncmp (ut->ut_line, "/dev/", 5) != 0) {
+ strcpy (tty_name, "/dev/");
+ } else {
+ tty_name[0] = '\0';
+ }
+
+ strcat (tty_name, ut->ut_line);
+#ifndef O_NOCTTY
+#define O_NOCTTY 0
+#endif
+ tty_fd =
+ open (tty_name, O_WRONLY | O_NDELAY | O_NOCTTY);
+ if (tty_fd != -1) {
+ send_mesg_to_tty (tty_fd);
+ close (tty_fd);
+ sleep (10);
+ }
+
+ if (ut->ut_pid > 1) {
+ kill (-ut->ut_pid, SIGHUP);
+ sleep (10);
+ kill (-ut->ut_pid, SIGKILL);
+ }
+
+ strncpy (user, ut->ut_user, sizeof (user) - 1);
+ user[sizeof (user) - 1] = '\0';
+
+ SYSLOG ((LOG_NOTICE,
+ "logged off user '%s' on '%s'", user,
+ tty_name));
+
+ /*
+ * This child has done all it can, drop dead.
+ */
+ exit (EXIT_SUCCESS);
+ }
+
+#ifdef USE_UTMPX
+ endutxent ();
+#else /* !USE_UTMPX */
+ endutent ();
+#endif /* !USE_UTMPX */
+
+#ifndef DEBUG
+ sleep (60);
+#endif
+ /*
+ * Reap any dead babies ...
+ */
+ while (wait (&status) != -1);
+ }
+
+ return EXIT_FAILURE;
+}
+
diff --git a/src/newgidmap.c b/src/newgidmap.c
new file mode 100644
index 0000000..b1e3351
--- /dev/null
+++ b/src/newgidmap.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2013 Eric Biederman
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "defines.h"
+#include "prototypes.h"
+#include "subordinateio.h"
+#include "idmapping.h"
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static bool verify_range(struct passwd *pw, struct map_range *range)
+{
+ /* An empty range is invalid */
+ if (range->count == 0)
+ return false;
+
+ /* Test /etc/subgid */
+ if (have_sub_gids(pw->pw_name, range->lower, range->count))
+ return true;
+
+ /* Allow a process to map its own gid */
+ if ((range->count == 1) && (pw->pw_gid == range->lower))
+ return true;
+
+ return false;
+}
+
+static void verify_ranges(struct passwd *pw, int ranges,
+ struct map_range *mappings)
+{
+ struct map_range *mapping;
+ int idx;
+
+ mapping = mappings;
+ for (idx = 0; idx < ranges; idx++, mapping++) {
+ if (!verify_range(pw, mapping)) {
+ fprintf(stderr, _( "%s: gid range [%lu-%lu) -> [%lu-%lu) not allowed\n"),
+ Prog,
+ mapping->upper,
+ mapping->upper + mapping->count,
+ mapping->lower,
+ mapping->lower + mapping->count);
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+
+static void usage(void)
+{
+ fprintf(stderr, _("usage: %s <pid> <gid> <lowergid> <count> [ <gid> <lowergid> <count> ] ... \n"), Prog);
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * newgidmap - Set the gid_map for the specified process
+ */
+int main(int argc, char **argv)
+{
+ char proc_dir_name[32];
+ char *target_str;
+ pid_t target, parent;
+ int proc_dir_fd;
+ int ranges;
+ struct map_range *mappings;
+ struct stat st;
+ struct passwd *pw;
+ int written;
+
+ Prog = Basename (argv[0]);
+
+ /*
+ * The valid syntax are
+ * newgidmap target_pid
+ */
+ if (argc < 2)
+ usage();
+
+ /* Find the process that needs its user namespace
+ * gid mapping set.
+ */
+ target_str = argv[1];
+ if (!get_pid(target_str, &target))
+ usage();
+
+ /* max string length is 6 + 10 + 1 + 1 = 18, allocate 32 bytes */
+ written = snprintf(proc_dir_name, sizeof(proc_dir_name), "/proc/%u/",
+ target);
+ if ((written <= 0) || (written >= sizeof(proc_dir_name))) {
+ fprintf(stderr, "%s: snprintf of proc path failed: %s\n",
+ Prog, strerror(errno));
+ }
+
+ proc_dir_fd = open(proc_dir_name, O_DIRECTORY);
+ if (proc_dir_fd < 0) {
+ fprintf(stderr, _("%s: Could not open proc directory for target %u\n"),
+ Prog, target);
+ return EXIT_FAILURE;
+ }
+
+ /* Who am i? */
+ pw = get_my_pwent ();
+ if (NULL == pw) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
+ (unsigned long) getuid ()));
+ return EXIT_FAILURE;
+ }
+
+ /* Get the effective uid and effective gid of the target process */
+ if (fstat(proc_dir_fd, &st) < 0) {
+ fprintf(stderr, _("%s: Could not stat directory for target %u\n"),
+ Prog, target);
+ return EXIT_FAILURE;
+ }
+
+ /* Verify real user and real group matches the password entry
+ * and the effective user and group of the program whose
+ * mappings we have been asked to set.
+ */
+ if ((getuid() != pw->pw_uid) ||
+ (getgid() != pw->pw_gid) ||
+ (pw->pw_uid != st.st_uid) ||
+ (pw->pw_gid != st.st_gid)) {
+ fprintf(stderr, _( "%s: Target %u is owned by a different user: uid:%lu pw_uid:%lu st_uid:%lu, gid:%lu pw_gid:%lu st_gid:%lu\n" ),
+ Prog, target,
+ (unsigned long int)getuid(), (unsigned long int)pw->pw_uid, (unsigned long int)st.st_uid,
+ (unsigned long int)getgid(), (unsigned long int)pw->pw_gid, (unsigned long int)st.st_gid);
+ return EXIT_FAILURE;
+ }
+
+ if (!sub_gid_open(O_RDONLY)) {
+ return EXIT_FAILURE;
+ }
+
+ ranges = ((argc - 2) + 2) / 3;
+ mappings = get_map_ranges(ranges, argc - 2, argv + 2);
+ if (!mappings)
+ usage();
+
+ verify_ranges(pw, ranges, mappings);
+
+ write_mapping(proc_dir_fd, ranges, mappings, "gid_map");
+ sub_gid_close();
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/newgrp.c b/src/newgrp.c
new file mode 100644
index 0000000..b8d3ddc
--- /dev/null
+++ b/src/newgrp.c
@@ -0,0 +1,853 @@
+/*
+ * Copyright (c) 1990 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2001 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2008, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <assert.h>
+#include "defines.h"
+#include "getdef.h"
+#include "prototypes.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+extern char **newenvp;
+extern char **environ;
+
+#ifdef HAVE_SETGROUPS
+static int ngroups;
+static /*@null@*/ /*@only@*/GETGROUPS_T *grouplist;
+#endif
+
+static bool is_newgrp;
+
+#ifdef WITH_AUDIT
+static char audit_buf[80];
+#endif
+
+/* local function prototypes */
+static void usage (void);
+static void check_perms (const struct group *grp,
+ struct passwd *pwd,
+ const char *groupname);
+static void syslog_sg (const char *name, const char *group);
+
+/*
+ * usage - print command usage message
+ */
+static void usage (void)
+{
+ if (is_newgrp) {
+ (void) fputs (_("Usage: newgrp [-] [group]\n"), stderr);
+ } else {
+ (void) fputs (_("Usage: sg group [[-c] command]\n"), stderr);
+ }
+}
+
+/*
+ * find_matching_group - search all groups of a given group id for
+ * membership of a given username
+ */
+static /*@null@*/struct group *find_matching_group (const char *name, gid_t gid)
+{
+ struct group *gr;
+ char **look;
+ bool notfound = true;
+
+ setgrent ();
+ while ((gr = getgrent ()) != NULL) {
+ if (gr->gr_gid != gid) {
+ continue;
+ }
+
+ /*
+ * A group with matching GID was found.
+ * Test for membership of 'name'.
+ */
+ look = gr->gr_mem;
+ while ((NULL != *look) && notfound) {
+ notfound = (strcmp (*look, name) != 0);
+ look++;
+ }
+ if (!notfound) {
+ break;
+ }
+ }
+ endgrent ();
+ return gr;
+}
+
+/*
+ * check_perms - check if the user is allowed to switch to this group
+ *
+ * If needed, the user will be authenticated.
+ *
+ * It will not return if the user could not be authenticated.
+ */
+static void check_perms (const struct group *grp,
+ struct passwd *pwd,
+ const char *groupname)
+{
+ bool needspasswd = false;
+ struct spwd *spwd;
+ char *cp;
+ const char *cpasswd;
+
+ /*
+ * see if she is a member of this group (i.e. in the list of
+ * members of the group, or if the group is her primary group).
+ *
+ * If she isn't a member, she needs to provide the group password.
+ * If there is no group password, she will be denied access
+ * anyway.
+ *
+ */
+ if ( (grp->gr_gid != pwd->pw_gid)
+ && !is_on_list (grp->gr_mem, pwd->pw_name)) {
+ needspasswd = true;
+ }
+
+ /*
+ * If she does not have either a shadowed password, or a regular
+ * password, and the group has a password, she needs to give the
+ * group password.
+ */
+ spwd = xgetspnam (pwd->pw_name);
+ if (NULL != spwd) {
+ pwd->pw_passwd = spwd->sp_pwdp;
+ }
+
+ if ((pwd->pw_passwd[0] == '\0') && (grp->gr_passwd[0] != '\0')) {
+ needspasswd = true;
+ }
+
+ /*
+ * Now I see about letting her into the group she requested. If she
+ * is the root user, I'll let her in without having to prompt for
+ * the password. Otherwise I ask for a password if she flunked one
+ * of the tests above.
+ */
+ if ((getuid () != 0) && needspasswd) {
+ /*
+ * get the password from her, and set the salt for
+ * the decryption from the group file.
+ */
+ cp = getpass (_("Password: "));
+ if (NULL == cp) {
+ goto failure;
+ }
+
+ /*
+ * encrypt the key she gave us using the salt from the
+ * password in the group file. The result of this encryption
+ * must match the previously encrypted value in the file.
+ */
+ cpasswd = pw_encrypt (cp, grp->gr_passwd);
+ strzero (cp);
+
+ if (NULL == cpasswd) {
+ fprintf (stderr,
+ _("%s: failed to crypt password with previous salt: %s\n"),
+ Prog, strerror (errno));
+ SYSLOG ((LOG_INFO,
+ "Failed to crypt password with previous salt of group '%s'",
+ groupname));
+ goto failure;
+ }
+
+ if (grp->gr_passwd[0] == '\0' ||
+ strcmp (cpasswd, grp->gr_passwd) != 0) {
+#ifdef WITH_AUDIT
+ snprintf (audit_buf, sizeof(audit_buf),
+ "authentication new-gid=%lu",
+ (unsigned long) grp->gr_gid);
+ audit_logger (AUDIT_GRP_AUTH, Prog,
+ audit_buf, NULL,
+ (unsigned int) getuid (), 0);
+#endif
+ SYSLOG ((LOG_INFO,
+ "Invalid password for group '%s' from '%s'",
+ groupname, pwd->pw_name));
+ (void) sleep (1);
+ (void) fputs (_("Invalid password.\n"), stderr);
+ goto failure;
+ }
+#ifdef WITH_AUDIT
+ snprintf (audit_buf, sizeof(audit_buf),
+ "authentication new-gid=%lu",
+ (unsigned long) grp->gr_gid);
+ audit_logger (AUDIT_GRP_AUTH, Prog,
+ audit_buf, NULL,
+ (unsigned int) getuid (), 1);
+#endif
+ }
+
+ return;
+
+failure:
+ /* The closelog is probably unnecessary, but it does no
+ * harm. -- JWP
+ */
+ closelog ();
+#ifdef WITH_AUDIT
+ if (groupname) {
+ snprintf (audit_buf, sizeof(audit_buf),
+ "changing new-group=%s", groupname);
+ audit_logger (AUDIT_CHGRP_ID, Prog,
+ audit_buf, NULL,
+ (unsigned int) getuid (), 0);
+ } else {
+ audit_logger (AUDIT_CHGRP_ID, Prog,
+ "changing", NULL,
+ (unsigned int) getuid (), 0);
+ }
+#endif
+ exit (EXIT_FAILURE);
+}
+
+#ifdef USE_SYSLOG
+/*
+ * syslog_sg - log the change of group to syslog
+ *
+ * The loggout will also be logged when the user will quit the
+ * sg/newgrp session.
+ */
+static void syslog_sg (const char *name, const char *group)
+{
+ const char *loginname = getlogin ();
+ const char *tty = ttyname (0);
+ char *free_login = NULL, *free_tty = NULL;
+
+ if (loginname != NULL) {
+ free_login = xstrdup (loginname);
+ loginname = free_login;
+ }
+ if (tty != NULL) {
+ free_tty = xstrdup (tty);
+ tty = free_tty;
+ }
+
+ if (loginname == NULL) {
+ loginname = "???";
+ }
+ if (tty == NULL) {
+ tty = "???";
+ } else if (strncmp (tty, "/dev/", 5) == 0) {
+ tty += 5;
+ }
+ SYSLOG ((LOG_INFO,
+ "user '%s' (login '%s' on %s) switched to group '%s'",
+ name, loginname, tty, group));
+#ifdef USE_PAM
+ /*
+ * We want to fork and exec the new shell in the child, leaving the
+ * parent waiting to log the session close.
+ *
+ * The parent must ignore signals generated from the console
+ * (SIGINT, SIGQUIT, SIGHUP) which might make the parent terminate
+ * before its child. When bash is exec'ed as the subshell, it
+ * generates a new process group id for itself, and consequently
+ * only SIGHUP, which is sent to all process groups in the session,
+ * can reach the parent. However, since arbitrary programs can be
+ * specified as login shells, there is no such guarantee in general.
+ * For the same reason, we must also ignore stop signals generated
+ * from the console (SIGTSTP, SIGTTIN, and SIGTTOU) in order to
+ * avoid any possibility of the parent being stopped when it
+ * receives SIGCHLD from the terminating subshell. -- JWP
+ */
+ {
+ pid_t child;
+
+ /* Ignore these signals. The signal handlers will later be
+ * restored to the default handlers. */
+ (void) signal (SIGINT, SIG_IGN);
+ (void) signal (SIGQUIT, SIG_IGN);
+ (void) signal (SIGHUP, SIG_IGN);
+ (void) signal (SIGTSTP, SIG_IGN);
+ (void) signal (SIGTTIN, SIG_IGN);
+ (void) signal (SIGTTOU, SIG_IGN);
+ child = fork ();
+ if ((pid_t)-1 == child) {
+ /* error in fork() */
+ fprintf (stderr, _("%s: failure forking: %s\n"),
+ is_newgrp ? "newgrp" : "sg", strerror (errno));
+#ifdef WITH_AUDIT
+ if (group) {
+ snprintf (audit_buf, sizeof(audit_buf),
+ "changing new-group=%s", group);
+ audit_logger (AUDIT_CHGRP_ID, Prog,
+ audit_buf, NULL,
+ (unsigned int) getuid (), 0);
+ } else {
+ audit_logger (AUDIT_CHGRP_ID, Prog,
+ "changing", NULL,
+ (unsigned int) getuid (), 0);
+ }
+#endif
+ exit (EXIT_FAILURE);
+ } else if (child != 0) {
+ /* parent - wait for child to finish, then log session close */
+ int cst = 0;
+ gid_t gid = getgid();
+ struct group *grp = getgrgid (gid);
+ pid_t pid;
+
+ do {
+ errno = 0;
+ pid = waitpid (child, &cst, WUNTRACED);
+ if ((pid == child) && (WIFSTOPPED (cst) != 0)) {
+ /* The child (shell) was suspended.
+ * Suspend sg/newgrp. */
+ kill (getpid (), SIGSTOP);
+ /* wake child when resumed */
+ kill (child, SIGCONT);
+ }
+ } while ( ((pid == child) && (WIFSTOPPED (cst) != 0))
+ || ((pid != child) && (errno == EINTR)));
+ /* local, no need for xgetgrgid */
+ if (NULL != grp) {
+ SYSLOG ((LOG_INFO,
+ "user '%s' (login '%s' on %s) returned to group '%s'",
+ name, loginname, tty, grp->gr_name));
+ } else {
+ SYSLOG ((LOG_INFO,
+ "user '%s' (login '%s' on %s) returned to group '%lu'",
+ name, loginname, tty,
+ (unsigned long) gid));
+ /* Either the user's passwd entry has a
+ * GID that does not match with any group,
+ * or the group was deleted while the user
+ * was in a newgrp session.*/
+ SYSLOG ((LOG_WARN,
+ "unknown GID '%lu' used by user '%s'",
+ (unsigned long) gid, name));
+ }
+ closelog ();
+ exit ((0 != WIFEXITED (cst)) ? WEXITSTATUS (cst)
+ : WTERMSIG (cst) + 128);
+ }
+
+ /* child - restore signals to their default state */
+ (void) signal (SIGINT, SIG_DFL);
+ (void) signal (SIGQUIT, SIG_DFL);
+ (void) signal (SIGHUP, SIG_DFL);
+ (void) signal (SIGTSTP, SIG_DFL);
+ (void) signal (SIGTTIN, SIG_DFL);
+ (void) signal (SIGTTOU, SIG_DFL);
+ }
+#endif /* USE_PAM */
+ free(free_login);
+ free(free_tty);
+}
+#endif /* USE_SYSLOG */
+
+/*
+ * newgrp - change the invokers current real and effective group id
+ */
+int main (int argc, char **argv)
+{
+ bool initflag = false;
+ int i;
+ bool cflag = false;
+ int err = 0;
+ gid_t gid;
+ char *cp;
+ const char *name, *prog;
+ char *group = NULL;
+ char *command = NULL;
+ char **envp = environ;
+ struct passwd *pwd;
+ /*@null@*/struct group *grp;
+
+#ifdef SHADOWGRP
+ struct sgrp *sgrp;
+#endif
+
+#ifdef WITH_AUDIT
+ audit_help_open ();
+#endif
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ /*
+ * Save my name for error messages and save my real gid incase of
+ * errors. If there is an error i have to exec a new login shell for
+ * the user since her old shell won't have fork'd to create the
+ * process. Skip over the program name to the next command line
+ * argument.
+ *
+ * This historical comment, and the code itself, suggest that the
+ * behavior of the system/shell on which it was written differed
+ * significantly from the one I am using. If this process was
+ * started from a shell (including the login shell), it was fork'ed
+ * and exec'ed as a child by that shell. In order to get the user
+ * back to that shell, it is only necessary to exit from this
+ * process which terminates the child of the fork. The parent shell,
+ * which is blocked waiting for a signal, will then receive a
+ * SIGCHLD and will continue; any changes made to the process
+ * persona or the environment after the fork never occurred in the
+ * parent process.
+ *
+ * Bottom line: we want to save the name and real gid for messages,
+ * but we do not need to restore the previous process persona and we
+ * don't need to re-exec anything. -- JWP
+ */
+ Prog = Basename (argv[0]);
+ is_newgrp = (strcmp (Prog, "newgrp") == 0);
+ OPENLOG (is_newgrp ? "newgrp" : "sg");
+ gid = getgid ();
+ argc--;
+ argv++;
+
+ initenv ();
+
+ pwd = get_my_pwent ();
+ if (NULL == pwd) {
+ fprintf (stderr, _("%s: Cannot determine your user name.\n"),
+ Prog);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_CHGRP_ID, Prog,
+ "changing", NULL,
+ (unsigned int) getuid (), 0);
+#endif
+ SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
+ (unsigned long) getuid ()));
+ closelog ();
+ exit (EXIT_FAILURE);
+ }
+ name = pwd->pw_name;
+
+ /*
+ * Parse the command line. There are two accepted flags. The first
+ * is "-", which for newgrp means to re-create the entire
+ * environment as though a login had been performed, and "-c", which
+ * for sg causes a command string to be executed.
+ *
+ * The next argument, if present, must be the new group name. Any
+ * remaining remaining arguments will be used to execute a command
+ * as the named group. If the group name isn't present, I just use
+ * the login group ID of the current user.
+ *
+ * The valid syntax are
+ * newgrp [-] [groupid]
+ * newgrp [-l] [groupid]
+ * sg [-]
+ * sg [-] groupid [[-c command]
+ */
+ if ( (argc > 0)
+ && ( (strcmp (argv[0], "-") == 0)
+ || (strcmp (argv[0], "-l") == 0))) {
+ argc--;
+ argv++;
+ initflag = true;
+ }
+ if (!is_newgrp) {
+ /*
+ * Do the command line for everything that is
+ * not "newgrp".
+ */
+ if ((argc > 0) && (argv[0][0] != '-')) {
+ group = argv[0];
+ argc--;
+ argv++;
+ } else {
+ usage ();
+ closelog ();
+ exit (EXIT_FAILURE);
+ }
+ if (argc > 0) {
+
+ /*
+ * skip -c if specified so both forms work:
+ * "sg group -c command" (as in the man page) or
+ * "sg group command" (as in the usage message).
+ */
+ if ((argc > 1) && (strcmp (argv[0], "-c") == 0)) {
+ command = argv[1];
+ } else {
+ command = argv[0];
+ }
+ cflag = true;
+ }
+ } else {
+ /*
+ * Do the command line for "newgrp". It's just making sure
+ * there aren't any flags and getting the new group name.
+ */
+ if ((argc > 0) && (argv[0][0] == '-')) {
+ usage ();
+ goto failure;
+ } else if (argv[0] != (char *) 0) {
+ group = argv[0];
+ } else {
+ /*
+ * get the group file entry for her login group id.
+ * the entry must exist, simply to be annoying.
+ *
+ * Perhaps in the past, but the default behavior now depends on the
+ * group entry, so it had better exist. -- JWP
+ */
+ grp = xgetgrgid (pwd->pw_gid);
+ if (NULL == grp) {
+ fprintf (stderr,
+ _("%s: GID '%lu' does not exist\n"),
+ Prog, (unsigned long) pwd->pw_gid);
+ SYSLOG ((LOG_CRIT, "GID '%lu' does not exist",
+ (unsigned long) pwd->pw_gid));
+ goto failure;
+ } else {
+ group = grp->gr_name;
+ }
+ }
+ }
+
+#ifdef HAVE_SETGROUPS
+ /*
+ * get the current users groupset. The new group will be added to
+ * the concurrent groupset if there is room, otherwise you get a
+ * nasty message but at least your real and effective group id's are
+ * set.
+ */
+ /* don't use getgroups(0, 0) - it doesn't work on some systems */
+ i = 16;
+ for (;;) {
+ grouplist = (GETGROUPS_T *) xmalloc (i * sizeof (GETGROUPS_T));
+ ngroups = getgroups (i, grouplist);
+ if (i > ngroups && !(ngroups == -1 && errno == EINVAL)) {
+ break;
+ }
+ /* not enough room, so try allocating a larger buffer */
+ free (grouplist);
+ i *= 2;
+ }
+ if (ngroups < 0) {
+ perror ("getgroups");
+#ifdef WITH_AUDIT
+ if (group) {
+ snprintf (audit_buf, sizeof(audit_buf),
+ "changing new-group=%s", group);
+ audit_logger (AUDIT_CHGRP_ID, Prog,
+ audit_buf, NULL,
+ (unsigned int) getuid (), 0);
+ } else {
+ audit_logger (AUDIT_CHGRP_ID, Prog,
+ "changing", NULL,
+ (unsigned int) getuid (), 0);
+ }
+#endif
+ exit (EXIT_FAILURE);
+ }
+#endif /* HAVE_SETGROUPS */
+
+ /*
+ * now we put her in the new group. The password file entry for her
+ * current user id has been gotten. If there was no optional group
+ * argument she will have her real and effective group id set to the
+ * set to the value from her password file entry.
+ *
+ * If run as newgrp, or as sg with no command, this process exec's
+ * an interactive subshell with the effective GID of the new group.
+ * If run as sg with a command, that command is exec'ed in this
+ * subshell. When this process terminates, either because the user
+ * exits, or the command completes, the parent of this process
+ * resumes with the current GID.
+ *
+ * If a group is explicitly specified on the command line, the
+ * interactive shell or command is run with that effective GID.
+ * Access will be denied if no entry for that group can be found in
+ * /etc/group. If the current user name appears in the members list
+ * for that group, access will be granted immediately; if not, the
+ * user will be challenged for that group's password. If the
+ * password response is incorrect, if the specified group does not
+ * have a password, or if that group has been locked by gpasswd -R,
+ * access will be denied. This is true even if the group specified
+ * has the user's login GID (as shown in /etc/passwd). If no group
+ * is explicitly specified on the command line, the effect is
+ * exactly the same as if a group name matching the user's login GID
+ * had been explicitly specified. Root, however, is never
+ * challenged for passwords, and is always allowed access.
+ *
+ * The previous behavior was to allow access to the login group if
+ * no explicit group was specified, irrespective of the group
+ * control file(s). This behavior is usually not desirable. A user
+ * wishing to return to the login group has only to exit back to the
+ * login shell. Generating yet more shell levels in order to
+ * provide a convenient "return" to the default group has the
+ * undesirable side effects of confusing the user, scrambling the
+ * history file, and consuming system resources. The default now is
+ * to lock out such behavior. A sys admin can allow it by explicitly
+ * including the user's name in the member list of the user's login
+ * group. -- JWP
+ */
+ grp = getgrnam (group); /* local, no need for xgetgrnam */
+ if (NULL == grp) {
+ fprintf (stderr, _("%s: group '%s' does not exist\n"), Prog, group);
+ goto failure;
+ }
+
+ /*
+ * For splitted groups (due to limitations of NIS), check all
+ * groups of the same GID like the requested group for
+ * membership of the current user.
+ */
+ grp = find_matching_group (name, grp->gr_gid);
+ if (NULL == grp) {
+ /*
+ * No matching group found. As we already know that
+ * the group exists, this happens only in the case
+ * of a requested group where the user is not member.
+ *
+ * Re-read the group entry for further processing.
+ */
+ grp = xgetgrnam (group);
+ assert (NULL != grp);
+ }
+#ifdef SHADOWGRP
+ sgrp = getsgnam (group);
+ if (NULL != sgrp) {
+ grp->gr_passwd = sgrp->sg_passwd;
+ grp->gr_mem = sgrp->sg_mem;
+ }
+#endif
+
+ /*
+ * Check if the user is allowed to access this group.
+ */
+ check_perms (grp, pwd, group);
+
+ /*
+ * all successful validations pass through this point. The group id
+ * will be set, and the group added to the concurrent groupset.
+ */
+#ifdef USE_SYSLOG
+ if (getdef_bool ("SYSLOG_SG_ENAB")) {
+ syslog_sg (name, group);
+ }
+#endif /* USE_SYSLOG */
+
+ gid = grp->gr_gid;
+
+#ifdef HAVE_SETGROUPS
+ /*
+ * I am going to try to add her new group id to her concurrent group
+ * set. If the group id is already present i'll just skip this part.
+ * If the group doesn't fit, i'll complain loudly and skip this
+ * part.
+ */
+ for (i = 0; i < ngroups; i++) {
+ if (gid == grouplist[i]) {
+ break;
+ }
+ }
+ if (i == ngroups) {
+ if (ngroups >= sysconf (_SC_NGROUPS_MAX)) {
+ (void) fputs (_("too many groups\n"), stderr);
+ } else {
+ grouplist[ngroups++] = gid;
+ if (setgroups (ngroups, grouplist) != 0) {
+ perror ("setgroups");
+ }
+ }
+ }
+#endif
+
+ /*
+ * Close all files before changing the user/group IDs.
+ *
+ * The needed structure should have been copied before, or
+ * permission to read the database will be required.
+ */
+ endspent ();
+#ifdef SHADOWGRP
+ endsgent ();
+#endif
+ endpwent ();
+ endgrent ();
+
+ /*
+ * Set the effective GID to the new group id and the effective UID
+ * to the real UID. For root, this also sets the real GID to the
+ * new group id.
+ */
+ if (setgid (gid) != 0) {
+ perror ("setgid");
+#ifdef WITH_AUDIT
+ snprintf (audit_buf, sizeof(audit_buf),
+ "changing new-gid=%lu", (unsigned long) gid);
+ audit_logger (AUDIT_CHGRP_ID, Prog,
+ audit_buf, NULL,
+ (unsigned int) getuid (), 0);
+#endif
+ exit (EXIT_FAILURE);
+ }
+
+ if (setuid (getuid ()) != 0) {
+ perror ("setuid");
+#ifdef WITH_AUDIT
+ snprintf (audit_buf, sizeof(audit_buf),
+ "changing new-gid=%lu", (unsigned long) gid);
+ audit_logger (AUDIT_CHGRP_ID, Prog,
+ audit_buf, NULL,
+ (unsigned int) getuid (), 0);
+#endif
+ exit (EXIT_FAILURE);
+ }
+
+ /*
+ * See if the "-c" flag was used. If it was, i just create a shell
+ * command for her using the argument that followed the "-c" flag.
+ */
+ if (cflag) {
+ closelog ();
+ execl (SHELL, "sh", "-c", command, (char *) 0);
+#ifdef WITH_AUDIT
+ snprintf (audit_buf, sizeof(audit_buf),
+ "changing new-gid=%lu", (unsigned long) gid);
+ audit_logger (AUDIT_CHGRP_ID, Prog,
+ audit_buf, NULL,
+ (unsigned int) getuid (), 0);
+#endif
+ perror (SHELL);
+ exit ((errno == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
+ }
+
+ /*
+ * I have to get the pathname of her login shell. As a favor, i'll
+ * try her environment for a $SHELL value first, and then try the
+ * password file entry. Obviously this shouldn't be in the
+ * restricted command directory since it could be used to leave the
+ * restricted environment.
+ *
+ * Note that the following assumes this user's entry in /etc/passwd
+ * does not have a chroot * prefix. If it does, the * will be copied
+ * verbatim into the exec path. This is probably not an issue
+ * because if this user is operating in a chroot jail, her entry in
+ * the version of /etc/passwd that is accessible here should
+ * probably never have a chroot shell entry (but entries for other
+ * users might). If I have missed something, and this causes you a
+ * problem, try using $SHELL as a workaround; also please notify me
+ * at jparmele@wildbear.com -- JWP
+ */
+ cp = getenv ("SHELL");
+ if (!initflag && (NULL != cp)) {
+ prog = cp;
+ } else if ((NULL != pwd->pw_shell) && ('\0' != pwd->pw_shell[0])) {
+ prog = pwd->pw_shell;
+ } else {
+ prog = SHELL;
+ }
+
+ /*
+ * Now I try to find the basename of the login shell. This will
+ * become argv[0] of the spawned command.
+ */
+ cp = Basename ((char *) prog);
+
+ /*
+ * Switch back to her home directory if i am doing login
+ * initialization.
+ */
+ if (initflag) {
+ if (chdir (pwd->pw_dir) != 0) {
+ perror ("chdir");
+ }
+
+ while (NULL != *envp) {
+ if (strncmp (*envp, "PATH=", 5) == 0 ||
+ strncmp (*envp, "HOME=", 5) == 0 ||
+ strncmp (*envp, "SHELL=", 6) == 0 ||
+ strncmp (*envp, "TERM=", 5) == 0)
+ addenv (*envp, NULL);
+
+ envp++;
+ }
+ } else {
+ while (NULL != *envp) {
+ addenv (*envp, NULL);
+ envp++;
+ }
+ }
+
+#ifdef WITH_AUDIT
+ snprintf (audit_buf, sizeof(audit_buf), "changing new-gid=%lu",
+ (unsigned long) gid);
+ audit_logger (AUDIT_CHGRP_ID, Prog,
+ audit_buf, NULL,
+ (unsigned int) getuid (), 1);
+#endif
+ /*
+ * Exec the login shell and go away. We are trying to get back to
+ * the previous environment which should be the user's login shell.
+ */
+ err = shell (prog, initflag ? (char *) 0 : cp, newenvp);
+ exit ((err == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
+ /*@notreached@*/
+ failure:
+
+ /*
+ * The previous code, when run as newgrp, re-exec'ed the shell in
+ * the current process with the original gid on error conditions.
+ * See the comment above. This historical behavior now has the
+ * effect of creating unlogged extraneous shell layers when the
+ * command line has an error or there is an authentication failure.
+ * We now just want to exit with error status back to the parent
+ * process. The closelog is probably unnecessary, but it does no
+ * harm. -- JWP
+ */
+ closelog ();
+#ifdef WITH_AUDIT
+ if (NULL != group) {
+ snprintf (audit_buf, sizeof(audit_buf),
+ "changing new-group=%s", group);
+ audit_logger (AUDIT_CHGRP_ID, Prog,
+ audit_buf, NULL,
+ (unsigned int) getuid (), 0);
+ } else {
+ audit_logger (AUDIT_CHGRP_ID, Prog,
+ "changing", NULL,
+ (unsigned int) getuid (), 0);
+ }
+#endif
+ exit (EXIT_FAILURE);
+}
+
diff --git a/src/newuidmap.c b/src/newuidmap.c
new file mode 100644
index 0000000..1ba25e7
--- /dev/null
+++ b/src/newuidmap.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2013 Eric Biederman
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "defines.h"
+#include "prototypes.h"
+#include "subordinateio.h"
+#include "idmapping.h"
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static bool verify_range(struct passwd *pw, struct map_range *range)
+{
+ /* An empty range is invalid */
+ if (range->count == 0)
+ return false;
+
+ /* Test /etc/subuid */
+ if (have_sub_uids(pw->pw_name, range->lower, range->count))
+ return true;
+
+ /* Allow a process to map its own uid */
+ if ((range->count == 1) && (pw->pw_uid == range->lower))
+ return true;
+
+ return false;
+}
+
+static void verify_ranges(struct passwd *pw, int ranges,
+ struct map_range *mappings)
+{
+ struct map_range *mapping;
+ int idx;
+
+ mapping = mappings;
+ for (idx = 0; idx < ranges; idx++, mapping++) {
+ if (!verify_range(pw, mapping)) {
+ fprintf(stderr, _( "%s: uid range [%lu-%lu) -> [%lu-%lu) not allowed\n"),
+ Prog,
+ mapping->upper,
+ mapping->upper + mapping->count,
+ mapping->lower,
+ mapping->lower + mapping->count);
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+
+void usage(void)
+{
+ fprintf(stderr, _("usage: %s <pid> <uid> <loweruid> <count> [ <uid> <loweruid> <count> ] ... \n"), Prog);
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * newuidmap - Set the uid_map for the specified process
+ */
+int main(int argc, char **argv)
+{
+ char proc_dir_name[32];
+ char *target_str;
+ pid_t target, parent;
+ int proc_dir_fd;
+ int ranges;
+ struct map_range *mappings;
+ struct stat st;
+ struct passwd *pw;
+ int written;
+
+ Prog = Basename (argv[0]);
+
+ /*
+ * The valid syntax are
+ * newuidmap target_pid
+ */
+ if (argc < 2)
+ usage();
+
+ /* Find the process that needs its user namespace
+ * uid mapping set.
+ */
+ target_str = argv[1];
+ if (!get_pid(target_str, &target))
+ usage();
+
+ /* max string length is 6 + 10 + 1 + 1 = 18, allocate 32 bytes */
+ written = snprintf(proc_dir_name, sizeof(proc_dir_name), "/proc/%u/",
+ target);
+ if ((written <= 0) || (written >= sizeof(proc_dir_name))) {
+ fprintf(stderr, "%s: snprintf of proc path failed: %s\n",
+ Prog, strerror(errno));
+ }
+
+ proc_dir_fd = open(proc_dir_name, O_DIRECTORY);
+ if (proc_dir_fd < 0) {
+ fprintf(stderr, _("%s: Could not open proc directory for target %u\n"),
+ Prog, target);
+ return EXIT_FAILURE;
+ }
+
+ /* Who am i? */
+ pw = get_my_pwent ();
+ if (NULL == pw) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
+ (unsigned long) getuid ()));
+ return EXIT_FAILURE;
+ }
+
+ /* Get the effective uid and effective gid of the target process */
+ if (fstat(proc_dir_fd, &st) < 0) {
+ fprintf(stderr, _("%s: Could not stat directory for target %u\n"),
+ Prog, target);
+ return EXIT_FAILURE;
+ }
+
+ /* Verify real user and real group matches the password entry
+ * and the effective user and group of the program whose
+ * mappings we have been asked to set.
+ */
+ if ((getuid() != pw->pw_uid) ||
+ (getgid() != pw->pw_gid) ||
+ (pw->pw_uid != st.st_uid) ||
+ (pw->pw_gid != st.st_gid)) {
+ fprintf(stderr, _( "%s: Target process %u is owned by a different user: uid:%lu pw_uid:%lu st_uid:%lu, gid:%lu pw_gid:%lu st_gid:%lu\n" ),
+ Prog, target,
+ (unsigned long int)getuid(), (unsigned long int)pw->pw_uid, (unsigned long int)st.st_uid,
+ (unsigned long int)getgid(), (unsigned long int)pw->pw_gid, (unsigned long int)st.st_gid);
+ return EXIT_FAILURE;
+ }
+
+ if (!sub_uid_open(O_RDONLY)) {
+ return EXIT_FAILURE;
+ }
+
+ ranges = ((argc - 2) + 2) / 3;
+ mappings = get_map_ranges(ranges, argc - 2, argv + 2);
+ if (!mappings)
+ usage();
+
+ verify_ranges(pw, ranges, mappings);
+
+ write_mapping(proc_dir_fd, ranges, mappings, "uid_map");
+ sub_uid_close();
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/newusers.c b/src/newusers.c
new file mode 100644
index 0000000..c38aec4
--- /dev/null
+++ b/src/newusers.c
@@ -0,0 +1,1251 @@
+/*
+ * Copyright (c) 1990 - 1993, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * newusers - create users from a batch file
+ *
+ * newusers creates a collection of entries in /etc/passwd
+ * and related files by reading a passwd-format file and
+ * adding entries in the related directories.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+#include "prototypes.h"
+#include "defines.h"
+#include "getdef.h"
+#include "groupio.h"
+#include "nscd.h"
+#include "pwio.h"
+#include "sgroupio.h"
+#include "shadowio.h"
+#ifdef ENABLE_SUBIDS
+#include "subordinateio.h"
+#endif /* ENABLE_SUBIDS */
+#include "chkname.h"
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static bool rflg = false; /* create a system account */
+#ifndef USE_PAM
+static /*@null@*//*@observer@*/char *crypt_method = NULL;
+#define cflg (NULL != crypt_method)
+#ifdef USE_SHA_CRYPT
+static bool sflg = false;
+static long sha_rounds = 5000;
+#endif /* USE_SHA_CRYPT */
+#endif /* !USE_PAM */
+
+static bool is_shadow;
+#ifdef SHADOWGRP
+static bool is_shadow_grp;
+static bool sgr_locked = false;
+#endif
+static bool pw_locked = false;
+static bool gr_locked = false;
+static bool spw_locked = false;
+#ifdef ENABLE_SUBIDS
+static bool is_sub_uid = false;
+static bool is_sub_gid = false;
+static bool sub_uid_locked = false;
+static bool sub_gid_locked = false;
+#endif /* ENABLE_SUBIDS */
+
+/* local function prototypes */
+static void usage (int status);
+static void fail_exit (int);
+static int add_group (const char *, const char *, gid_t *, gid_t);
+static int get_user_id (const char *, uid_t *);
+static int add_user (const char *, uid_t, gid_t);
+#ifndef USE_PAM
+static int update_passwd (struct passwd *, const char *);
+#endif /* !USE_PAM */
+static int add_passwd (struct passwd *, const char *);
+static void process_flags (int argc, char **argv);
+static void check_flags (void);
+static void check_perms (void);
+static void open_files (void);
+static void close_files (void);
+
+/*
+ * usage - display usage message and exit
+ */
+static void usage (int status)
+{
+ FILE *usageout = (EXIT_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+#ifndef USE_PAM
+ (void) fprintf (usageout,
+ _(" -c, --crypt-method METHOD the crypt method (one of %s)\n"),
+#ifndef USE_SHA_CRYPT
+ "NONE DES MD5"
+#else /* USE_SHA_CRYPT */
+ "NONE DES MD5 SHA256 SHA512"
+#endif /* USE_SHA_CRYPT */
+ );
+#endif /* !USE_PAM */
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -r, --system create system accounts\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+#ifndef USE_PAM
+#ifdef USE_SHA_CRYPT
+ (void) fputs (_(" -s, --sha-rounds number of SHA rounds for the SHA*\n"
+ " crypt algorithms\n"),
+ usageout);
+#endif /* USE_SHA_CRYPT */
+#endif /* !USE_PAM */
+ (void) fputs ("\n", usageout);
+
+ exit (status);
+}
+
+/*
+ * fail_exit - undo as much as possible
+ */
+static void fail_exit (int code)
+{
+ if (spw_locked) {
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ }
+ if (pw_locked) {
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ }
+ if (gr_locked) {
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+ }
+#ifdef SHADOWGRP
+ if (sgr_locked) {
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+ }
+#endif
+#ifdef ENABLE_SUBIDS
+ if (sub_uid_locked) {
+ if (sub_uid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ()));
+ /* continue */
+ }
+ }
+ if (sub_gid_locked) {
+ if (sub_gid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ()));
+ /* continue */
+ }
+ }
+#endif /* ENABLE_SUBIDS */
+
+ exit (code);
+}
+
+/*
+ * add_group - create a new group or add a user to an existing group
+ */
+static int add_group (const char *name, const char *gid, gid_t *ngid, uid_t uid)
+{
+ const struct group *grp;
+ struct group grent;
+ char *members[1];
+#ifdef SHADOWGRP
+ const struct sgrp *sg;
+#endif
+
+ /*
+ * Start by seeing if the named group already exists. This will be
+ * very easy to deal with if it does.
+ */
+ grp = getgrnam (gid);
+ if (NULL == grp) {
+ grp = gr_locate (gid);
+ }
+ if (NULL != grp) {
+ /* The user will use this ID for her primary group */
+ *ngid = grp->gr_gid;
+ /* Don't check gshadow */
+ return 0;
+ }
+
+ if (isdigit (gid[0])) {
+ /*
+ * The GID is a number, which means either this is a brand
+ * new group, or an existing group.
+ */
+
+ if (get_gid (gid, &grent.gr_gid) == 0) {
+ fprintf (stderr,
+ _("%s: invalid group ID '%s'\n"),
+ Prog, gid);
+ return -1;
+ }
+
+ /* Look in both the system database (getgrgid) and in the
+ * internal database (gr_locate_gid), which may contain
+ * uncommitted changes */
+ if ( (getgrgid ((gid_t) grent.gr_gid) != NULL)
+ || (gr_locate_gid ((gid_t) grent.gr_gid) != NULL)) {
+ /* The user will use this ID for her
+ * primary group */
+ *ngid = (gid_t) grent.gr_gid;
+ return 0;
+ }
+
+ /* Do not create groups with GID == (gid_t)-1 */
+ if (grent.gr_gid == (gid_t)-1) {
+ fprintf (stderr,
+ _("%s: invalid group ID '%s'\n"),
+ Prog, gid);
+ return -1;
+ }
+ } else {
+ /* The gid parameter can be "" or a name which is not
+ * already the name of an existing group.
+ * In both cases, figure out what group ID can be used.
+ */
+ if (find_new_gid(rflg, &grent.gr_gid, &uid) < 0) {
+ return -1;
+ }
+ }
+
+ /*
+ * Now I have all of the fields required to create the new group.
+ */
+ if (('\0' != gid[0]) && (!isdigit (gid[0]))) {
+ grent.gr_name = xstrdup (gid);
+ } else {
+ grent.gr_name = xstrdup (name);
+/* FIXME: check if the group exists */
+ }
+
+ /* Check if this is a valid group name */
+ if (!is_valid_group_name (grent.gr_name)) {
+ fprintf (stderr,
+ _("%s: invalid group name '%s'\n"),
+ Prog, grent.gr_name);
+ free (grent.gr_name);
+ return -1;
+ }
+
+ grent.gr_passwd = "*"; /* XXX warning: const */
+ members[0] = NULL;
+ grent.gr_mem = members;
+
+ *ngid = grent.gr_gid;
+
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ sg = sgr_locate (grent.gr_name);
+
+ if (NULL != sg) {
+ fprintf (stderr,
+ _("%s: group '%s' is a shadow group, but does not exist in /etc/group\n"),
+ Prog, grent.gr_name);
+ return -1;
+ }
+ }
+#endif
+
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ struct sgrp sgrent;
+ char *admins[1];
+ sgrent.sg_name = grent.gr_name;
+ sgrent.sg_passwd = "*"; /* XXX warning: const */
+ grent.gr_passwd = "x"; /* XXX warning: const */
+ admins[0] = NULL;
+ sgrent.sg_adm = admins;
+ sgrent.sg_mem = members;
+
+ if (sgr_update (&sgrent) == 0) {
+ return -1;
+ }
+ }
+#endif
+
+ if (gr_update (&grent) == 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int get_user_id (const char *uid, uid_t *nuid) {
+
+ /*
+ * The first guess for the UID is either the numerical UID that the
+ * caller provided, or the next available UID.
+ */
+ if (isdigit (uid[0])) {
+ if ((get_uid (uid, nuid) == 0) || (*nuid == (uid_t)-1)) {
+ fprintf (stderr,
+ _("%s: invalid user ID '%s'\n"),
+ Prog, uid);
+ return -1;
+ }
+ } else {
+ if ('\0' != uid[0]) {
+ const struct passwd *pwd;
+ /* local, no need for xgetpwnam */
+ pwd = getpwnam (uid);
+ if (NULL == pwd) {
+ pwd = pw_locate (uid);
+ }
+
+ if (NULL != pwd) {
+ *nuid = pwd->pw_uid;
+ } else {
+ fprintf (stderr,
+ _("%s: user '%s' does not exist\n"),
+ Prog, uid);
+ return -1;
+ }
+ } else {
+ if (find_new_uid (rflg, nuid, NULL) < 0) {
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * add_user - create a new user ID
+ */
+static int add_user (const char *name, uid_t uid, gid_t gid)
+{
+ struct passwd pwent;
+
+ /* Check if this is a valid user name */
+ if (!is_valid_user_name (name)) {
+ fprintf (stderr,
+ _("%s: invalid user name '%s'\n"),
+ Prog, name);
+ return -1;
+ }
+
+ /*
+ * I don't want to fill in the entire password structure members
+ * JUST YET, since there is still more data to be added. So, I fill
+ * in the parts that I have.
+ */
+ pwent.pw_name = xstrdup (name);
+ pwent.pw_uid = uid;
+ pwent.pw_passwd = "x"; /* XXX warning: const */
+ pwent.pw_gid = gid;
+ pwent.pw_gecos = ""; /* XXX warning: const */
+ pwent.pw_dir = ""; /* XXX warning: const */
+ pwent.pw_shell = ""; /* XXX warning: const */
+
+ return (pw_update (&pwent) == 0) ? -1 : 0;
+}
+
+#ifndef USE_PAM
+/*
+ * update_passwd - update the password in the passwd entry
+ *
+ * Return 0 if successful.
+ */
+static int update_passwd (struct passwd *pwd, const char *password)
+{
+ void *crypt_arg = NULL;
+ char *cp;
+ if (crypt_method != NULL) {
+#ifdef USE_SHA_CRYPT
+ if (sflg) {
+ crypt_arg = &sha_rounds;
+ }
+#endif
+ }
+
+ if ((crypt_method != NULL) && (0 == strcmp(crypt_method, "NONE"))) {
+ pwd->pw_passwd = (char *)password;
+ } else {
+ const char *salt = crypt_make_salt (crypt_method, crypt_arg);
+ cp = pw_encrypt (password, salt);
+ if (NULL == cp) {
+ fprintf (stderr,
+ _("%s: failed to crypt password with salt '%s': %s\n"),
+ Prog, salt, strerror (errno));
+ return 1;
+ }
+ pwd->pw_passwd = cp;
+ }
+
+ return 0;
+}
+#endif /* !USE_PAM */
+
+/*
+ * add_passwd - add or update the encrypted password
+ */
+static int add_passwd (struct passwd *pwd, const char *password)
+{
+ const struct spwd *sp;
+ struct spwd spent;
+ char *cp;
+
+#ifndef USE_PAM
+ void *crypt_arg = NULL;
+ if (crypt_method != NULL) {
+#ifdef USE_SHA_CRYPT
+ if (sflg) {
+ crypt_arg = &sha_rounds;
+ }
+#endif /* USE_SHA_CRYPT */
+ }
+
+ /*
+ * In the case of regular password files, this is real easy - pwd
+ * points to the entry in the password file. Shadow files are
+ * harder since there are zillions of things to do ...
+ */
+ if (!is_shadow) {
+ return update_passwd (pwd, password);
+ }
+#endif /* USE_PAM */
+
+ /*
+ * Do the first and easiest shadow file case. The user already
+ * exists in the shadow password file.
+ */
+ sp = spw_locate (pwd->pw_name);
+#ifndef USE_PAM
+ if (NULL != sp) {
+ spent = *sp;
+ if ( (NULL != crypt_method)
+ && (0 == strcmp(crypt_method, "NONE"))) {
+ spent.sp_pwdp = (char *)password;
+ } else {
+ const char *salt = crypt_make_salt (crypt_method,
+ crypt_arg);
+ cp = pw_encrypt (password, salt);
+ if (NULL == cp) {
+ fprintf (stderr,
+ _("%s: failed to crypt password with salt '%s': %s\n"),
+ Prog, salt, strerror (errno));
+ return 1;
+ }
+ spent.sp_pwdp = cp;
+ }
+ spent.sp_lstchg = (long) gettime () / SCALE;
+ if (0 == spent.sp_lstchg) {
+ /* Better disable aging than requiring a password
+ * change */
+ spent.sp_lstchg = -1;
+ }
+ return (spw_update (&spent) == 0);
+ }
+
+ /*
+ * Pick the next easiest case - the user has an encrypted password
+ * which isn't equal to "x". The password was set to "x" earlier
+ * when the entry was created, so this user would have to have had
+ * the password set someplace else.
+ */
+ if (strcmp (pwd->pw_passwd, "x") != 0) {
+ return update_passwd (pwd, password);
+ }
+#else /* USE_PAM */
+ /*
+ * If there is already a shadow entry, do not touch it.
+ * If there is already a passwd entry with a password, do not
+ * touch it.
+ * The password will be updated later for all users using PAM.
+ */
+ if ( (NULL != sp)
+ || (strcmp (pwd->pw_passwd, "x") != 0)) {
+ return 0;
+ }
+#endif /* USE_PAM */
+
+ /*
+ * Now the really hard case - I need to create an entirely new
+ * shadow password file entry.
+ */
+ spent.sp_namp = pwd->pw_name;
+#ifndef USE_PAM
+ if ((crypt_method != NULL) && (0 == strcmp(crypt_method, "NONE"))) {
+ spent.sp_pwdp = (char *)password;
+ } else {
+ const char *salt = crypt_make_salt (crypt_method, crypt_arg);
+ cp = pw_encrypt (password, salt);
+ if (NULL == cp) {
+ fprintf (stderr,
+ _("%s: failed to crypt password with salt '%s': %s\n"),
+ Prog, salt, strerror (errno));
+ return 1;
+ }
+ spent.sp_pwdp = cp;
+ }
+#else
+ /*
+ * Lock the password.
+ * The password will be updated later for all users using PAM.
+ */
+ spent.sp_pwdp = "!";
+#endif
+ spent.sp_lstchg = (long) gettime () / SCALE;
+ if (0 == spent.sp_lstchg) {
+ /* Better disable aging than requiring a password change */
+ spent.sp_lstchg = -1;
+ }
+ spent.sp_min = getdef_num ("PASS_MIN_DAYS", 0);
+ /* 10000 is infinity this week */
+ spent.sp_max = getdef_num ("PASS_MAX_DAYS", 10000);
+ spent.sp_warn = getdef_num ("PASS_WARN_AGE", -1);
+ spent.sp_inact = -1;
+ spent.sp_expire = -1;
+ spent.sp_flag = SHADOW_SP_FLAG_UNSET;
+
+ return (spw_update (&spent) == 0);
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ int c;
+ static struct option long_options[] = {
+#ifndef USE_PAM
+ {"crypt-method", required_argument, NULL, 'c'},
+#endif /* !USE_PAM */
+ {"help", no_argument, NULL, 'h'},
+ {"system", no_argument, NULL, 'r'},
+ {"root", required_argument, NULL, 'R'},
+#ifndef USE_PAM
+#ifdef USE_SHA_CRYPT
+ {"sha-rounds", required_argument, NULL, 's'},
+#endif /* USE_SHA_CRYPT */
+#endif /* !USE_PAM */
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv,
+#ifndef USE_PAM
+#ifdef USE_SHA_CRYPT
+ "c:hrs:",
+#else /* !USE_SHA_CRYPT */
+ "c:hr",
+#endif /* !USE_SHA_CRYPT */
+#else /* USE_PAM */
+ "hr",
+#endif
+ long_options, NULL)) != -1) {
+ switch (c) {
+#ifndef USE_PAM
+ case 'c':
+ crypt_method = optarg;
+ break;
+#endif /* !USE_PAM */
+ case 'h':
+ usage (EXIT_SUCCESS);
+ break;
+ case 'r':
+ rflg = true;
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+#ifndef USE_PAM
+#ifdef USE_SHA_CRYPT
+ case 's':
+ sflg = true;
+ if (getlong(optarg, &sha_rounds) == 0) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ usage (EXIT_FAILURE);
+ }
+ break;
+#endif /* USE_SHA_CRYPT */
+#endif /* !USE_PAM */
+ default:
+ usage (EXIT_FAILURE);
+ break;
+ }
+ }
+
+ if ( (optind != argc)
+ && (optind + 1 != argc)) {
+ usage (EXIT_FAILURE);
+ }
+
+ if (argv[optind] != NULL) {
+ if (freopen (argv[optind], "r", stdin) == NULL) {
+ char buf[BUFSIZ];
+ snprintf (buf, sizeof buf, "%s: %s", Prog, argv[1]);
+ perror (buf);
+ fail_exit (EXIT_FAILURE);
+ }
+ }
+
+ /* validate options */
+ check_flags ();
+}
+
+/*
+ * check_flags - check flags and parameters consistency
+ *
+ * It will not return if an error is encountered.
+ */
+static void check_flags (void)
+{
+#ifndef USE_PAM
+#ifdef USE_SHA_CRYPT
+ if (sflg && !cflg) {
+ fprintf (stderr,
+ _("%s: %s flag is only allowed with the %s flag\n"),
+ Prog, "-s", "-c");
+ usage (EXIT_FAILURE);
+ }
+#endif /* USE_SHA_CRYPT */
+
+ if (cflg) {
+ if ( (0 != strcmp (crypt_method, "DES"))
+ && (0 != strcmp (crypt_method, "MD5"))
+ && (0 != strcmp (crypt_method, "NONE"))
+#ifdef USE_SHA_CRYPT
+ && (0 != strcmp (crypt_method, "SHA256"))
+ && (0 != strcmp (crypt_method, "SHA512"))
+#endif /* USE_SHA_CRYPT */
+ ) {
+ fprintf (stderr,
+ _("%s: unsupported crypt method: %s\n"),
+ Prog, crypt_method);
+ usage (EXIT_FAILURE);
+ }
+ }
+#endif /* !USE_PAM */
+}
+
+/*
+ * check_perms - check if the caller is allowed to add a group
+ *
+ * With PAM support, the setuid bit can be set on groupadd to allow
+ * non-root users to groups.
+ * Without PAM support, only users who can write in the group databases
+ * can add groups.
+ *
+ * It will not return if the user is not allowed.
+ */
+static void check_perms (void)
+{
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ pam_handle_t *pamh = NULL;
+ int retval;
+ struct passwd *pampw;
+
+ pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+ if (NULL == pampw) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ fail_exit (EXIT_FAILURE);
+ }
+
+ retval = pam_start ("newusers", pampw->pw_name, &conv, &pamh);
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_authenticate (pamh, 0);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_acct_mgmt (pamh, 0);
+ }
+
+ if (PAM_SUCCESS != retval) {
+ fprintf (stderr, _("%s: PAM: %s\n"),
+ Prog, pam_strerror (pamh, retval));
+ SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval)));
+ if (NULL != pamh) {
+ (void) pam_end (pamh, retval);
+ }
+ fail_exit (EXIT_FAILURE);
+ }
+ (void) pam_end (pamh, retval);
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+}
+
+/*
+ * open_files - lock and open the password, group and shadow databases
+ */
+static void open_files (void)
+{
+ /*
+ * Lock the password files and open them for update. This will bring
+ * all of the entries into memory where they may be searched for an
+ * modified, or new entries added. The password file is the key - if
+ * it gets locked, assume the others can be locked right away.
+ */
+ if (pw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, pw_dbname ());
+ fail_exit (EXIT_FAILURE);
+ }
+ pw_locked = true;
+ if (is_shadow) {
+ if (spw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, spw_dbname ());
+ fail_exit (EXIT_FAILURE);
+ }
+ spw_locked = true;
+ }
+ if (gr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, gr_dbname ());
+ fail_exit (EXIT_FAILURE);
+ }
+ gr_locked = true;
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sgr_dbname ());
+ fail_exit (EXIT_FAILURE);
+ }
+ sgr_locked = true;
+ }
+#endif
+#ifdef ENABLE_SUBIDS
+ if (is_sub_uid) {
+ if (sub_uid_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sub_uid_dbname ());
+ fail_exit (EXIT_FAILURE);
+ }
+ sub_uid_locked = true;
+ }
+ if (is_sub_gid) {
+ if (sub_gid_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sub_gid_dbname ());
+ fail_exit (EXIT_FAILURE);
+ }
+ sub_gid_locked = true;
+ }
+#endif /* ENABLE_SUBIDS */
+
+ if (pw_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, pw_dbname ());
+ fail_exit (EXIT_FAILURE);
+ }
+ if (is_shadow && (spw_open (O_CREAT | O_RDWR) == 0)) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, spw_dbname ());
+ fail_exit (EXIT_FAILURE);
+ }
+ if (gr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
+ fail_exit (EXIT_FAILURE);
+ }
+#ifdef SHADOWGRP
+ if (is_shadow_grp && (sgr_open (O_CREAT | O_RDWR) == 0)) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, sgr_dbname ());
+ fail_exit (EXIT_FAILURE);
+ }
+#endif
+#ifdef ENABLE_SUBIDS
+ if (is_sub_uid) {
+ if (sub_uid_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, sub_uid_dbname ());
+ fail_exit (EXIT_FAILURE);
+ }
+ }
+ if (is_sub_gid) {
+ if (sub_gid_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, sub_gid_dbname ());
+ fail_exit (EXIT_FAILURE);
+ }
+ }
+#endif /* ENABLE_SUBIDS */
+}
+
+/*
+ * close_files - close and unlock the password, group and shadow databases
+ */
+static void close_files (void)
+{
+ if (pw_close () == 0) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
+ fail_exit (EXIT_FAILURE);
+ }
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ pw_locked = false;
+
+ if (is_shadow) {
+ if (spw_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ()));
+ fail_exit (EXIT_FAILURE);
+ }
+ if (spw_unlock () == 0) {
+ fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ spw_locked = false;
+ }
+
+ if (gr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", gr_dbname ()));
+ fail_exit (EXIT_FAILURE);
+ }
+#ifdef ENABLE_SUBIDS
+ if (is_sub_uid && (sub_uid_close () == 0)) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"), Prog, sub_uid_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", sub_uid_dbname ()));
+ fail_exit (EXIT_FAILURE);
+ }
+ if (is_sub_gid && (sub_gid_close () == 0)) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"), Prog, sub_gid_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", sub_gid_dbname ()));
+ fail_exit (EXIT_FAILURE);
+ }
+#endif /* ENABLE_SUBIDS */
+
+ if (gr_unlock () == 0) {
+ fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+ gr_locked = false;
+
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", sgr_dbname ()));
+ fail_exit (EXIT_FAILURE);
+ }
+ if (sgr_unlock () == 0) {
+ fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+ sgr_locked = false;
+ }
+#endif
+#ifdef ENABLE_SUBIDS
+ if (is_sub_uid) {
+ if (sub_uid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ()));
+ /* continue */
+ }
+ sub_uid_locked = false;
+ }
+ if (is_sub_gid) {
+ if (sub_gid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ()));
+ /* continue */
+ }
+ sub_gid_locked = false;
+ }
+#endif /* ENABLE_SUBIDS */
+}
+
+int main (int argc, char **argv)
+{
+ char buf[BUFSIZ];
+ char *fields[8];
+ int nfields;
+ char *cp;
+ const struct passwd *pw;
+ struct passwd newpw;
+ int errors = 0;
+ int line = 0;
+ uid_t uid;
+ gid_t gid;
+#ifdef USE_PAM
+ int *lines = NULL;
+ char **usernames = NULL;
+ char **passwords = NULL;
+ unsigned int nusers = 0;
+#endif /* USE_PAM */
+
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ /* FIXME: will not work with an input file */
+ process_root_flag ("-R", argc, argv);
+
+ OPENLOG ("newusers");
+
+ process_flags (argc, argv);
+
+ check_perms ();
+
+ is_shadow = spw_file_present ();
+
+#ifdef SHADOWGRP
+ is_shadow_grp = sgr_file_present ();
+#endif
+#ifdef ENABLE_SUBIDS
+ is_sub_uid = sub_uid_file_present () && !rflg;
+ is_sub_gid = sub_gid_file_present () && !rflg;
+#endif /* ENABLE_SUBIDS */
+
+ open_files ();
+
+ /*
+ * Read each line. The line has the same format as a password file
+ * entry, except that certain fields are not constrained to be
+ * numerical values. If a group ID is entered which does not already
+ * exist, an attempt is made to allocate the same group ID as the
+ * numerical user ID. Should that fail, the next available group ID
+ * over 100 is allocated. The pw_gid field will be updated with that
+ * value.
+ */
+ while (fgets (buf, (int) sizeof buf, stdin) != (char *) 0) {
+ line++;
+ cp = strrchr (buf, '\n');
+ if (NULL != cp) {
+ *cp = '\0';
+ } else {
+ if (feof (stdin) == 0) {
+ fprintf (stderr,
+ _("%s: line %d: line too long\n"),
+ Prog, line);
+ errors++;
+ continue;
+ }
+ }
+
+ /*
+ * Break the string into fields and screw around with them.
+ * There MUST be 7 colon separated fields, although the
+ * values aren't that particular.
+ */
+ for (cp = buf, nfields = 0; nfields < 7; nfields++) {
+ fields[nfields] = cp;
+ cp = strchr (cp, ':');
+ if (NULL != cp) {
+ *cp = '\0';
+ cp++;
+ } else {
+ break;
+ }
+ }
+ if (nfields != 6) {
+ fprintf (stderr, _("%s: line %d: invalid line\n"),
+ Prog, line);
+ errors++;
+ continue;
+ }
+
+ /*
+ * First check if we have to create or update an user
+ */
+ pw = pw_locate (fields[0]);
+ /* local, no need for xgetpwnam */
+ if ( (NULL == pw)
+ && (getpwnam (fields[0]) != NULL)) {
+ fprintf (stderr, _("%s: cannot update the entry of user %s (not in the passwd database)\n"), Prog, fields[0]);
+ errors++;
+ continue;
+ }
+
+ if ( (NULL == pw)
+ && (get_user_id (fields[2], &uid) != 0)) {
+ fprintf (stderr,
+ _("%s: line %d: can't create user\n"),
+ Prog, line);
+ errors++;
+ continue;
+ }
+
+ /*
+ * Processed is the group name. A new group will be
+ * created if the group name is non-numeric and does not
+ * already exist. If the group name is a number (which is not
+ * an existing GID), a group with the same name as the user
+ * will be created, with the given GID. The given or created
+ * group will be the primary group of the user. If
+ * there is no named group to be a member of, the UID will
+ * be figured out and that value will be a candidate for a
+ * new group, if that group ID exists, a whole new group ID
+ * will be made up.
+ */
+ if ( (NULL == pw)
+ && (add_group (fields[0], fields[3], &gid, uid) != 0)) {
+ fprintf (stderr,
+ _("%s: line %d: can't create group\n"),
+ Prog, line);
+ errors++;
+ continue;
+ }
+
+ /*
+ * Now we work on the user ID. It has to be specified either
+ * as a numerical value, or left blank. If it is a numerical
+ * value, that value will be used, otherwise the next
+ * available user ID is computed and used. After this there
+ * will at least be a (struct passwd) for the user.
+ */
+ if ( (NULL == pw)
+ && (add_user (fields[0], uid, gid) != 0)) {
+ fprintf (stderr,
+ _("%s: line %d: can't create user\n"),
+ Prog, line);
+ errors++;
+ continue;
+ }
+
+ /*
+ * The password, gecos field, directory, and shell fields
+ * all come next.
+ */
+ pw = pw_locate (fields[0]);
+ if (NULL == pw) {
+ fprintf (stderr,
+ _("%s: line %d: user '%s' does not exist in %s\n"),
+ Prog, line, fields[0], pw_dbname ());
+ errors++;
+ continue;
+ }
+ newpw = *pw;
+
+#ifdef USE_PAM
+ /* keep the list of user/password for later update by PAM */
+ nusers++;
+ lines = realloc (lines, sizeof (lines[0]) * nusers);
+ usernames = realloc (usernames, sizeof (usernames[0]) * nusers);
+ passwords = realloc (passwords, sizeof (passwords[0]) * nusers);
+ lines[nusers-1] = line;
+ usernames[nusers-1] = strdup (fields[0]);
+ passwords[nusers-1] = strdup (fields[1]);
+#endif /* USE_PAM */
+ if (add_passwd (&newpw, fields[1]) != 0) {
+ fprintf (stderr,
+ _("%s: line %d: can't update password\n"),
+ Prog, line);
+ errors++;
+ continue;
+ }
+ if ('\0' != fields[4][0]) {
+ newpw.pw_gecos = fields[4];
+ }
+
+ if ('\0' != fields[5][0]) {
+ newpw.pw_dir = fields[5];
+ }
+
+ if ('\0' != fields[6][0]) {
+ newpw.pw_shell = fields[6];
+ }
+
+ if ( ('\0' != fields[5][0])
+ && (access (newpw.pw_dir, F_OK) != 0)) {
+/* FIXME: should check for directory */
+ mode_t msk = 0777 & ~getdef_num ("UMASK",
+ GETDEF_DEFAULT_UMASK);
+ if (mkdir (newpw.pw_dir, msk) != 0) {
+ fprintf (stderr,
+ _("%s: line %d: mkdir %s failed: %s\n"),
+ Prog, line, newpw.pw_dir,
+ strerror (errno));
+ } else if (chown (newpw.pw_dir,
+ newpw.pw_uid,
+ newpw.pw_gid) != 0) {
+ fprintf (stderr,
+ _("%s: line %d: chown %s failed: %s\n"),
+ Prog, line, newpw.pw_dir,
+ strerror (errno));
+ }
+ }
+
+ /*
+ * Update the password entry with the new changes made.
+ */
+ if (pw_update (&newpw) == 0) {
+ fprintf (stderr,
+ _("%s: line %d: can't update entry\n"),
+ Prog, line);
+ errors++;
+ continue;
+ }
+
+#ifdef ENABLE_SUBIDS
+ /*
+ * Add subordinate uids if the user does not have them.
+ */
+ if (is_sub_uid && !sub_uid_assigned(fields[0])) {
+ uid_t sub_uid_start = 0;
+ unsigned long sub_uid_count = 0;
+ if (find_new_sub_uids(fields[0], &sub_uid_start, &sub_uid_count) == 0) {
+ if (sub_uid_add(fields[0], sub_uid_start, sub_uid_count) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare new %s entry\n"),
+ Prog, sub_uid_dbname ());
+ }
+ } else {
+ fprintf (stderr,
+ _("%s: can't find subordinate user range\n"),
+ Prog);
+ errors++;
+ }
+ }
+
+ /*
+ * Add subordinate gids if the user does not have them.
+ */
+ if (is_sub_gid && !sub_gid_assigned(fields[0])) {
+ gid_t sub_gid_start = 0;
+ unsigned long sub_gid_count = 0;
+ if (find_new_sub_gids(fields[0], &sub_gid_start, &sub_gid_count) == 0) {
+ if (sub_gid_add(fields[0], sub_gid_start, sub_gid_count) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare new %s entry\n"),
+ Prog, sub_uid_dbname ());
+ }
+ } else {
+ fprintf (stderr,
+ _("%s: can't find subordinate group range\n"),
+ Prog);
+ errors++;
+ }
+ }
+#endif /* ENABLE_SUBIDS */
+ }
+
+ /*
+ * Any detected errors will cause the entire set of changes to be
+ * aborted. Unlocking the password file will cause all of the
+ * changes to be ignored. Otherwise the file is closed, causing the
+ * changes to be written out all at once, and then unlocked
+ * afterwards.
+ */
+ if (0 != errors) {
+ fprintf (stderr,
+ _("%s: error detected, changes ignored\n"), Prog);
+ fail_exit (EXIT_FAILURE);
+ }
+
+ close_files ();
+
+ nscd_flush_cache ("passwd");
+ nscd_flush_cache ("group");
+
+#ifdef USE_PAM
+ unsigned int i;
+ /* Now update the passwords using PAM */
+ for (i = 0; i < nusers; i++) {
+ if (do_pam_passwd_non_interractive ("newusers", usernames[i], passwords[i]) != 0) {
+ fprintf (stderr,
+ _("%s: (line %d, user %s) password not changed\n"),
+ Prog, lines[i], usernames[i]);
+ errors++;
+ }
+ }
+#endif /* USE_PAM */
+
+ return ((0 == errors) ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
diff --git a/src/nologin.c b/src/nologin.c
new file mode 100644
index 0000000..7fe8a6a
--- /dev/null
+++ b/src/nologin.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2004 The FreeBSD Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <unistd.h>
+
+int main (void)
+{
+ const char *user, *tty;
+
+ tty = ttyname (0);
+ if (NULL == tty) {
+ tty = "UNKNOWN";
+ }
+ user = getlogin ();
+ if (NULL == user) {
+ user = "UNKNOWN";
+ }
+ openlog ("nologin", LOG_CONS, LOG_AUTH);
+ syslog (LOG_CRIT, "Attempted login by %s on %s", user, tty);
+ closelog ();
+
+ printf ("%s", "This account is currently not available.\n");
+
+ return EXIT_FAILURE;
+}
diff --git a/src/passwd.c b/src/passwd.c
new file mode 100644
index 0000000..3af3e65
--- /dev/null
+++ b/src/passwd.c
@@ -0,0 +1,1168 @@
+/*
+ * Copyright (c) 1989 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2001 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#include <selinux/flask.h>
+#include <selinux/av_permissions.h>
+#include <selinux/context.h>
+#endif /* WITH_SELINUX */
+#include <time.h>
+#include "defines.h"
+#include "getdef.h"
+#include "nscd.h"
+#include "prototypes.h"
+#include "pwauth.h"
+#include "pwio.h"
+#include "shadowio.h"
+
+/*
+ * exit status values
+ */
+/*@-exitarg@*/
+#define E_SUCCESS 0 /* success */
+#define E_NOPERM 1 /* permission denied */
+#define E_USAGE 2 /* invalid combination of options */
+#define E_FAILURE 3 /* unexpected failure, nothing done */
+#define E_MISSING 4 /* unexpected failure, passwd file missing */
+#define E_PWDBUSY 5 /* passwd file busy, try again later */
+#define E_BAD_ARG 6 /* invalid argument to option */
+/*
+ * Global variables
+ */
+const char *Prog; /* Program name */
+
+static char *name; /* The name of user whose password is being changed */
+static char *myname; /* The current user's name */
+static bool amroot; /* The caller's real UID was 0 */
+
+static bool
+ aflg = false, /* -a - show status for all users */
+ dflg = false, /* -d - delete password */
+ eflg = false, /* -e - force password change */
+ iflg = false, /* -i - set inactive days */
+ kflg = false, /* -k - change only if expired */
+ lflg = false, /* -l - lock the user's password */
+ nflg = false, /* -n - set minimum days */
+ qflg = false, /* -q - quiet mode */
+ Sflg = false, /* -S - show password status */
+ uflg = false, /* -u - unlock the user's password */
+ wflg = false, /* -w - set warning days */
+ xflg = false; /* -x - set maximum days */
+
+/*
+ * set to 1 if there are any flags which require root privileges,
+ * and require username to be specified
+ */
+static bool anyflag = false;
+
+static long age_min = 0; /* Minimum days before change */
+static long age_max = 0; /* Maximum days until change */
+static long warn = 0; /* Warning days before change */
+static long inact = 0; /* Days without change before locked */
+
+#ifndef USE_PAM
+static bool do_update_age = false;
+#endif /* ! USE_PAM */
+
+static bool pw_locked = false;
+static bool spw_locked = false;
+
+#ifndef USE_PAM
+/*
+ * Size of the biggest passwd:
+ * $6$ 3
+ * rounds= 7
+ * 999999999 9
+ * $ 1
+ * salt 16
+ * $ 1
+ * SHA512 123
+ * nul 1
+ *
+ * total 161
+ */
+static char crypt_passwd[256];
+static bool do_update_pwd = false;
+#endif /* !USE_PAM */
+
+/*
+ * External identifiers
+ */
+
+/* local function prototypes */
+static /*@noreturn@*/void usage (int);
+
+#ifndef USE_PAM
+static bool reuse (const char *, const struct passwd *);
+static int new_password (const struct passwd *);
+
+static void check_password (const struct passwd *, const struct spwd *);
+#endif /* !USE_PAM */
+static /*@observer@*/const char *date_to_str (time_t);
+static /*@observer@*/const char *pw_status (const char *);
+static void print_status (const struct passwd *);
+static /*@noreturn@*/void fail_exit (int);
+static /*@noreturn@*/void oom (void);
+static char *update_crypt_pw (char *);
+static void update_noshadow (void);
+
+static void update_shadow (void);
+#ifdef WITH_SELINUX
+static int check_selinux_access (const char *changed_user,
+ uid_t changed_uid,
+ access_vector_t requested_access);
+#endif /* WITH_SELINUX */
+
+/*
+ * usage - print command usage and exit
+ */
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options] [LOGIN]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -a, --all report password status on all accounts\n"), usageout);
+ (void) fputs (_(" -d, --delete delete the password for the named account\n"), usageout);
+ (void) fputs (_(" -e, --expire force expire the password for the named account\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -k, --keep-tokens change password only if expired\n"), usageout);
+ (void) fputs (_(" -i, --inactive INACTIVE set password inactive after expiration\n"
+ " to INACTIVE\n"), usageout);
+ (void) fputs (_(" -l, --lock lock the password of the named account\n"), usageout);
+ (void) fputs (_(" -n, --mindays MIN_DAYS set minimum number of days before password\n"
+ " change to MIN_DAYS\n"), usageout);
+ (void) fputs (_(" -q, --quiet quiet mode\n"), usageout);
+ (void) fputs (_(" -r, --repository REPOSITORY change password in REPOSITORY repository\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs (_(" -S, --status report password status on the named account\n"), usageout);
+ (void) fputs (_(" -u, --unlock unlock the password of the named account\n"), usageout);
+ (void) fputs (_(" -w, --warndays WARN_DAYS set expiration warning days to WARN_DAYS\n"), usageout);
+ (void) fputs (_(" -x, --maxdays MAX_DAYS set maximum number of days before password\n"
+ " change to MAX_DAYS\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+#ifndef USE_PAM
+static bool reuse (const char *pass, const struct passwd *pw)
+{
+#ifdef HAVE_LIBCRACK_HIST
+ const char *reason;
+
+#ifdef HAVE_LIBCRACK_PW
+ const char *FascistHistoryPw (const char *, const struct passwd *);
+
+ reason = FascistHistory (pass, pw);
+#else /* !HAVE_LIBCRACK_PW */
+ const char *FascistHistory (const char *, int);
+
+ reason = FascistHistory (pass, pw->pw_uid);
+#endif /* !HAVE_LIBCRACK_PW */
+ if (NULL != reason) {
+ (void) printf (_("Bad password: %s. "), reason);
+ return true;
+ }
+#endif /* HAVE_LIBCRACK_HIST */
+ return false;
+}
+
+/*
+ * new_password - validate old password and replace with new (both old and
+ * new in global "char crypt_passwd[128]")
+ */
+static int new_password (const struct passwd *pw)
+{
+ char *clear; /* Pointer to clear text */
+ char *cipher; /* Pointer to cipher text */
+ const char *salt; /* Pointer to new salt */
+ char *cp; /* Pointer to getpass() response */
+ char orig[200]; /* Original password */
+ char pass[200]; /* New password */
+ int i; /* Counter for retries */
+ bool warned;
+ int pass_max_len = -1;
+ const char *method;
+
+#ifdef HAVE_LIBCRACK_HIST
+ int HistUpdate (const char *, const char *);
+#endif /* HAVE_LIBCRACK_HIST */
+
+ /*
+ * Authenticate the user. The user will be prompted for their own
+ * password.
+ */
+
+ if (!amroot && ('\0' != crypt_passwd[0])) {
+ clear = getpass (_("Old password: "));
+ if (NULL == clear) {
+ return -1;
+ }
+
+ cipher = pw_encrypt (clear, crypt_passwd);
+
+ if (NULL == cipher) {
+ strzero (clear);
+ fprintf (stderr,
+ _("%s: failed to crypt password with previous salt: %s\n"),
+ Prog, strerror (errno));
+ SYSLOG ((LOG_INFO,
+ "Failed to crypt password with previous salt of user '%s'",
+ pw->pw_name));
+ return -1;
+ }
+
+ if (strcmp (cipher, crypt_passwd) != 0) {
+ strzero (clear);
+ strzero (cipher);
+ SYSLOG ((LOG_WARN, "incorrect password for %s",
+ pw->pw_name));
+ (void) sleep (1);
+ (void) fprintf (stderr,
+ _("Incorrect password for %s.\n"),
+ pw->pw_name);
+ return -1;
+ }
+ STRFCPY (orig, clear);
+ strzero (clear);
+ strzero (cipher);
+ } else {
+ orig[0] = '\0';
+ }
+
+ /*
+ * Get the new password. The user is prompted for the new password
+ * and has five tries to get it right. The password will be tested
+ * for strength, unless it is the root user. This provides an escape
+ * for initial login passwords.
+ */
+ method = getdef_str ("ENCRYPT_METHOD");
+ if (NULL == method) {
+ if (!getdef_bool ("MD5_CRYPT_ENAB")) {
+ pass_max_len = getdef_num ("PASS_MAX_LEN", 8);
+ }
+ } else {
+ if ( (strcmp (method, "MD5") == 0)
+#ifdef USE_SHA_CRYPT
+ || (strcmp (method, "SHA256") == 0)
+ || (strcmp (method, "SHA512") == 0)
+#endif /* USE_SHA_CRYPT */
+ ) {
+ pass_max_len = -1;
+ } else {
+ pass_max_len = getdef_num ("PASS_MAX_LEN", 8);
+ }
+ }
+ if (!qflg) {
+ if (pass_max_len == -1) {
+ (void) printf (_(
+"Enter the new password (minimum of %d characters)\n"
+"Please use a combination of upper and lower case letters and numbers.\n"),
+ getdef_num ("PASS_MIN_LEN", 5));
+ } else {
+ (void) printf (_(
+"Enter the new password (minimum of %d, maximum of %d characters)\n"
+"Please use a combination of upper and lower case letters and numbers.\n"),
+ getdef_num ("PASS_MIN_LEN", 5), pass_max_len);
+ }
+ }
+
+ warned = false;
+ for (i = getdef_num ("PASS_CHANGE_TRIES", 5); i > 0; i--) {
+ cp = getpass (_("New password: "));
+ if (NULL == cp) {
+ memzero (orig, sizeof orig);
+ return -1;
+ }
+ if (warned && (strcmp (pass, cp) != 0)) {
+ warned = false;
+ }
+ STRFCPY (pass, cp);
+ strzero (cp);
+
+ if (!amroot && (!obscure (orig, pass, pw) || reuse (pass, pw))) {
+ (void) puts (_("Try again."));
+ continue;
+ }
+
+ /*
+ * If enabled, warn about weak passwords even if you are
+ * root (enter this password again to use it anyway).
+ * --marekm
+ */
+ if (amroot && !warned && getdef_bool ("PASS_ALWAYS_WARN")
+ && (!obscure (orig, pass, pw) || reuse (pass, pw))) {
+ (void) puts (_("\nWarning: weak password (enter it again to use it anyway)."));
+ warned = true;
+ continue;
+ }
+ cp = getpass (_("Re-enter new password: "));
+ if (NULL == cp) {
+ memzero (orig, sizeof orig);
+ return -1;
+ }
+ if (strcmp (cp, pass) != 0) {
+ (void) fputs (_("They don't match; try again.\n"), stderr);
+ } else {
+ strzero (cp);
+ break;
+ }
+ }
+ memzero (orig, sizeof orig);
+
+ if (i == 0) {
+ memzero (pass, sizeof pass);
+ return -1;
+ }
+
+ /*
+ * Encrypt the password, then wipe the cleartext password.
+ */
+ salt = crypt_make_salt (NULL, NULL);
+ cp = pw_encrypt (pass, salt);
+ memzero (pass, sizeof pass);
+
+ if (NULL == cp) {
+ fprintf (stderr,
+ _("%s: failed to crypt password with salt '%s': %s\n"),
+ Prog, salt, strerror (errno));
+ return -1;
+ }
+
+#ifdef HAVE_LIBCRACK_HIST
+ HistUpdate (pw->pw_name, crypt_passwd);
+#endif /* HAVE_LIBCRACK_HIST */
+ STRFCPY (crypt_passwd, cp);
+ return 0;
+}
+
+/*
+ * check_password - test a password to see if it can be changed
+ *
+ * check_password() sees if the invoker has permission to change the
+ * password for the given user.
+ */
+static void check_password (const struct passwd *pw, const struct spwd *sp)
+{
+ time_t now;
+ int exp_status;
+
+ exp_status = isexpired (pw, sp);
+
+ /*
+ * If not expired and the "change only if expired" option (idea from
+ * PAM) was specified, do nothing. --marekm
+ */
+ if (kflg && (0 == exp_status)) {
+ exit (E_SUCCESS);
+ }
+
+ /*
+ * Root can change any password any time.
+ */
+ if (amroot) {
+ return;
+ }
+
+ (void) time (&now);
+
+ /*
+ * Expired accounts cannot be changed ever. Passwords which are
+ * locked may not be changed. Passwords where min > max may not be
+ * changed. Passwords which have been inactive too long cannot be
+ * changed.
+ */
+ if ( (sp->sp_pwdp[0] == '!')
+ || (exp_status > 1)
+ || ( (sp->sp_max >= 0)
+ && (sp->sp_min > sp->sp_max))) {
+ (void) fprintf (stderr,
+ _("The password for %s cannot be changed.\n"),
+ sp->sp_namp);
+ SYSLOG ((LOG_WARN, "password locked for '%s'", sp->sp_namp));
+ closelog ();
+ exit (E_NOPERM);
+ }
+
+ /*
+ * Passwords may only be changed after sp_min time is up.
+ */
+ if (sp->sp_lstchg > 0) {
+ time_t ok;
+ ok = (time_t) sp->sp_lstchg * SCALE;
+ if (sp->sp_min > 0) {
+ ok += (time_t) sp->sp_min * SCALE;
+ }
+
+ if (now < ok) {
+ (void) fprintf (stderr,
+ _("The password for %s cannot be changed yet.\n"),
+ pw->pw_name);
+ SYSLOG ((LOG_WARN, "now < minimum age for '%s'", pw->pw_name));
+ closelog ();
+ exit (E_NOPERM);
+ }
+ }
+}
+#endif /* !USE_PAM */
+
+static /*@observer@*/const char *date_to_str (time_t t)
+{
+ static char buf[80];
+ struct tm *tm;
+
+ tm = gmtime (&t);
+#ifdef HAVE_STRFTIME
+ (void) strftime (buf, sizeof buf, "%m/%d/%Y", tm);
+#else /* !HAVE_STRFTIME */
+ (void) snprintf (buf, sizeof buf, "%02d/%02d/%04d",
+ tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900);
+#endif /* !HAVE_STRFTIME */
+ return buf;
+}
+
+static /*@observer@*/const char *pw_status (const char *pass)
+{
+ if (*pass == '*' || *pass == '!') {
+ return "L";
+ }
+ if (*pass == '\0') {
+ return "NP";
+ }
+ return "P";
+}
+
+/*
+ * print_status - print current password status
+ */
+static void print_status (const struct passwd *pw)
+{
+ struct spwd *sp;
+
+ sp = getspnam (pw->pw_name); /* local, no need for xgetspnam */
+ if (NULL != sp) {
+ (void) printf ("%s %s %s %lld %lld %lld %lld\n",
+ pw->pw_name,
+ pw_status (sp->sp_pwdp),
+ date_to_str (sp->sp_lstchg * SCALE),
+ ((long long)sp->sp_min * SCALE) / DAY,
+ ((long long)sp->sp_max * SCALE) / DAY,
+ ((long long)sp->sp_warn * SCALE) / DAY,
+ ((long long)sp->sp_inact * SCALE) / DAY);
+ } else {
+ (void) printf ("%s %s\n",
+ pw->pw_name, pw_status (pw->pw_passwd));
+ }
+}
+
+
+static /*@noreturn@*/void fail_exit (int status)
+{
+ if (pw_locked) {
+ if (pw_unlock () == 0) {
+ (void) fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ }
+
+ if (spw_locked) {
+ if (spw_unlock () == 0) {
+ (void) fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ }
+
+ exit (status);
+}
+
+static /*@noreturn@*/void oom (void)
+{
+ (void) fprintf (stderr, _("%s: out of memory\n"), Prog);
+ fail_exit (E_FAILURE);
+}
+
+static char *update_crypt_pw (char *cp)
+{
+#ifndef USE_PAM
+ if (do_update_pwd) {
+ cp = xstrdup (crypt_passwd);
+ }
+#endif /* !USE_PAM */
+
+ if (dflg) {
+ *cp = '\0';
+ }
+
+ if (uflg && *cp == '!') {
+ if (cp[1] == '\0') {
+ (void) fprintf (stderr,
+ _("%s: unlocking the password would result in a passwordless account.\n"
+ "You should set a password with usermod -p to unlock the password of this account.\n"),
+ Prog);
+ fail_exit (E_FAILURE);
+ } else {
+ cp++;
+ }
+ }
+
+ if (lflg && *cp != '!') {
+ char *newpw = xmalloc (strlen (cp) + 2);
+
+ strcpy (newpw, "!");
+ strcat (newpw, cp);
+ cp = newpw;
+ }
+ return cp;
+}
+
+
+static void update_noshadow (void)
+{
+ const struct passwd *pw;
+ struct passwd *npw;
+
+ if (pw_lock () == 0) {
+ (void) fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, pw_dbname ());
+ exit (E_PWDBUSY);
+ }
+ pw_locked = true;
+ if (pw_open (O_CREAT | O_RDWR) == 0) {
+ (void) fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, pw_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", pw_dbname ()));
+ fail_exit (E_MISSING);
+ }
+ pw = pw_locate (name);
+ if (NULL == pw) {
+ (void) fprintf (stderr,
+ _("%s: user '%s' does not exist in %s\n"),
+ Prog, name, pw_dbname ());
+ fail_exit (E_NOPERM);
+ }
+ npw = __pw_dup (pw);
+ if (NULL == npw) {
+ oom ();
+ }
+ npw->pw_passwd = update_crypt_pw (npw->pw_passwd);
+ if (pw_update (npw) == 0) {
+ (void) fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, pw_dbname (), npw->pw_name);
+ fail_exit (E_FAILURE);
+ }
+ if (pw_close () == 0) {
+ (void) fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
+ fail_exit (E_FAILURE);
+ }
+ if (pw_unlock () == 0) {
+ (void) fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ pw_locked = false;
+}
+
+static void update_shadow (void)
+{
+ const struct spwd *sp;
+ struct spwd *nsp;
+
+ if (spw_lock () == 0) {
+ (void) fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, spw_dbname ());
+ exit (E_PWDBUSY);
+ }
+ spw_locked = true;
+ if (spw_open (O_CREAT | O_RDWR) == 0) {
+ (void) fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, spw_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", spw_dbname ()));
+ fail_exit (E_FAILURE);
+ }
+ sp = spw_locate (name);
+ if (NULL == sp) {
+ /* Try to update the password in /etc/passwd instead. */
+ (void) spw_close ();
+ update_noshadow ();
+ if (spw_unlock () == 0) {
+ (void) fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ spw_locked = false;
+ return;
+ }
+ nsp = __spw_dup (sp);
+ if (NULL == nsp) {
+ oom ();
+ }
+ nsp->sp_pwdp = update_crypt_pw (nsp->sp_pwdp);
+ if (xflg) {
+ nsp->sp_max = (age_max * DAY) / SCALE;
+ }
+ if (nflg) {
+ nsp->sp_min = (age_min * DAY) / SCALE;
+ }
+ if (wflg) {
+ nsp->sp_warn = (warn * DAY) / SCALE;
+ }
+ if (iflg) {
+ nsp->sp_inact = (inact * DAY) / SCALE;
+ }
+#ifndef USE_PAM
+ if (do_update_age) {
+ nsp->sp_lstchg = (long) gettime () / SCALE;
+ if (0 == nsp->sp_lstchg) {
+ /* Better disable aging than requiring a password
+ * change */
+ nsp->sp_lstchg = -1;
+ }
+ }
+#endif /* !USE_PAM */
+
+ /*
+ * Force change on next login, like SunOS 4.x passwd -e or Solaris
+ * 2.x passwd -f. Solaris 2.x seems to do the same thing (set
+ * sp_lstchg to 0).
+ */
+ if (eflg) {
+ nsp->sp_lstchg = 0;
+ }
+
+ if (spw_update (nsp) == 0) {
+ (void) fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, spw_dbname (), nsp->sp_namp);
+ fail_exit (E_FAILURE);
+ }
+ if (spw_close () == 0) {
+ (void) fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ()));
+ fail_exit (E_FAILURE);
+ }
+ if (spw_unlock () == 0) {
+ (void) fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ spw_locked = false;
+}
+
+#ifdef WITH_SELINUX
+static int check_selinux_access (const char *changed_user,
+ uid_t changed_uid,
+ access_vector_t requested_access)
+{
+ int status = -1;
+ security_context_t user_context;
+ context_t c;
+ const char *user;
+
+ /* if in permissive mode then allow the operation */
+ if (security_getenforce() == 0) {
+ return 0;
+ }
+
+ /* get the context of the process which executed passwd */
+ if (getprevcon(&user_context) != 0) {
+ return -1;
+ }
+
+ /* get the "user" portion of the context (the part before the first
+ colon) */
+ c = context_new(user_context);
+ user = context_user_get(c);
+
+ /* if changing a password for an account with UID==0 or for an account
+ where the identity matches then return success */
+ if (changed_uid != 0 && strcmp(changed_user, user) == 0) {
+ status = 0;
+ } else {
+ struct av_decision avd;
+ int retval;
+ retval = security_compute_av(user_context,
+ user_context,
+ SECCLASS_PASSWD,
+ requested_access,
+ &avd);
+ if ((retval == 0) &&
+ ((requested_access & avd.allowed) == requested_access)) {
+ status = 0;
+ }
+ }
+ context_free(c);
+ freecon(user_context);
+ return status;
+}
+
+#endif /* WITH_SELINUX */
+
+/*
+ * passwd - change a user's password file information
+ *
+ * This command controls the password file and commands which are used
+ * to modify it.
+ *
+ * The valid options are
+ *
+ * -d delete the password for the named account (*)
+ * -e expire the password for the named account (*)
+ * -f execute chfn command to interpret flags
+ * -g execute gpasswd command to interpret flags
+ * -i # set sp_inact to # days (*)
+ * -k change password only if expired
+ * -l lock the password of the named account (*)
+ * -n # set sp_min to # days (*)
+ * -r # change password in # repository
+ * -s execute chsh command to interpret flags
+ * -S show password status of named account
+ * -u unlock the password of the named account (*)
+ * -w # set sp_warn to # days (*)
+ * -x # set sp_max to # days (*)
+ *
+ * (*) requires root permission to execute.
+ *
+ * All of the time fields are entered in days and converted to the
+ * appropriate internal format. For finer resolute the chage
+ * command must be used.
+ */
+int main (int argc, char **argv)
+{
+ const struct passwd *pw; /* Password file entry for user */
+
+#ifndef USE_PAM
+ char *cp; /* Miscellaneous character pointing */
+
+ const struct spwd *sp; /* Shadow file entry for user */
+#endif /* !USE_PAM */
+
+ sanitize_env ();
+
+ /*
+ * Get the program name. The program name is used as a prefix to
+ * most error messages.
+ */
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ /*
+ * The program behaves differently when executed by root than when
+ * executed by a normal user.
+ */
+ amroot = (getuid () == 0);
+
+ OPENLOG ("passwd");
+
+ {
+ /*
+ * Parse the command line options.
+ */
+ int c;
+ static struct option long_options[] = {
+ {"all", no_argument, NULL, 'a'},
+ {"delete", no_argument, NULL, 'd'},
+ {"expire", no_argument, NULL, 'e'},
+ {"help", no_argument, NULL, 'h'},
+ {"inactive", required_argument, NULL, 'i'},
+ {"keep-tokens", no_argument, NULL, 'k'},
+ {"lock", no_argument, NULL, 'l'},
+ {"mindays", required_argument, NULL, 'n'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"repository", required_argument, NULL, 'r'},
+ {"root", required_argument, NULL, 'R'},
+ {"status", no_argument, NULL, 'S'},
+ {"unlock", no_argument, NULL, 'u'},
+ {"warndays", required_argument, NULL, 'w'},
+ {"maxdays", required_argument, NULL, 'x'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "adehi:kln:qr:R:Suw:x:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'a':
+ aflg = true;
+ break;
+ case 'd':
+ dflg = true;
+ anyflag = true;
+ break;
+ case 'e':
+ eflg = true;
+ anyflag = true;
+ break;
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'i':
+ if ( (getlong (optarg, &inact) == 0)
+ || (inact < -1)) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ usage (E_BAD_ARG);
+ }
+ iflg = true;
+ anyflag = true;
+ break;
+ case 'k':
+ /* change only if expired, like Linux-PAM passwd -k. */
+ kflg = true; /* ok for users */
+ break;
+ case 'l':
+ lflg = true;
+ anyflag = true;
+ break;
+ case 'n':
+ if ( (getlong (optarg, &age_min) == 0)
+ || (age_min < -1)) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ usage (E_BAD_ARG);
+ }
+ nflg = true;
+ anyflag = true;
+ break;
+ case 'q':
+ qflg = true; /* ok for users */
+ break;
+ case 'r':
+ /* -r repository (files|nis|nisplus) */
+ /* only "files" supported for now */
+ if (strcmp (optarg, "files") != 0) {
+ fprintf (stderr,
+ _("%s: repository %s not supported\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ case 'S':
+ Sflg = true; /* ok for users */
+ break;
+ case 'u':
+ uflg = true;
+ anyflag = true;
+ break;
+ case 'w':
+ if ( (getlong (optarg, &warn) == 0)
+ || (warn < -1)) {
+ (void) fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ usage (E_BAD_ARG);
+ }
+ wflg = true;
+ anyflag = true;
+ break;
+ case 'x':
+ if ( (getlong (optarg, &age_max) == 0)
+ || (age_max < -1)) {
+ (void) fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ usage (E_BAD_ARG);
+ }
+ xflg = true;
+ anyflag = true;
+ break;
+ default:
+ usage (E_BAD_ARG);
+ }
+ }
+ }
+
+ /*
+ * Now I have to get the user name. The name will be gotten from the
+ * command line if possible. Otherwise it is figured out from the
+ * environment.
+ */
+ pw = get_my_pwent ();
+ if (NULL == pw) {
+ (void) fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
+ (unsigned long) getuid ()));
+ exit (E_NOPERM);
+ }
+ myname = xstrdup (pw->pw_name);
+ if (optind < argc) {
+ name = argv[optind];
+ } else {
+ name = myname;
+ }
+
+ /*
+ * Make sure that at most one username was specified.
+ */
+ if (argc > (optind+1)) {
+ usage (E_USAGE);
+ }
+
+ /*
+ * The -a flag requires -S, no other flags, no username, and
+ * you must be root. --marekm
+ */
+ if (aflg) {
+ if (anyflag || !Sflg || (optind < argc)) {
+ usage (E_USAGE);
+ }
+ if (!amroot) {
+ (void) fprintf (stderr,
+ _("%s: Permission denied.\n"),
+ Prog);
+ exit (E_NOPERM);
+ }
+ setpwent ();
+ while ( (pw = getpwent ()) != NULL ) {
+ print_status (pw);
+ }
+ endpwent ();
+ exit (E_SUCCESS);
+ }
+#if 0
+ /*
+ * Allow certain users (administrators) to change passwords of
+ * certain users. Not implemented yet. --marekm
+ */
+ if (may_change_passwd (myname, name))
+ amroot = 1;
+#endif
+
+ /*
+ * If any of the flags were given, a user name must be supplied on
+ * the command line. Only an unadorned command line doesn't require
+ * the user's name be given. Also, -x, -n, -w, -i, -e, -d,
+ * -l, -u may appear with each other. -S, -k must appear alone.
+ */
+
+ /*
+ * -S now ok for normal users (check status of my own account), and
+ * doesn't require username. --marekm
+ */
+ if (anyflag && optind >= argc) {
+ usage (E_USAGE);
+ }
+
+ if ( (Sflg && kflg)
+ || (anyflag && (Sflg || kflg))) {
+ usage (E_USAGE);
+ }
+
+ if (anyflag && !amroot) {
+ (void) fprintf (stderr, _("%s: Permission denied.\n"), Prog);
+ exit (E_NOPERM);
+ }
+
+ pw = xgetpwnam (name);
+ if (NULL == pw) {
+ (void) fprintf (stderr,
+ _("%s: user '%s' does not exist\n"),
+ Prog, name);
+ exit (E_NOPERM);
+ }
+#ifdef WITH_SELINUX
+ /* only do this check when getuid()==0 because it's a pre-condition for
+ changing a password without entering the old one */
+ if ((is_selinux_enabled() > 0) && (getuid() == 0) &&
+ (check_selinux_access (name, pw->pw_uid, PASSWD__PASSWD) != 0)) {
+ security_context_t user_context = NULL;
+ const char *user = "Unknown user context";
+ if (getprevcon (&user_context) == 0) {
+ user = user_context; /* FIXME: use context_user_get? */
+ }
+ SYSLOG ((LOG_ALERT,
+ "%s is not authorized to change the password of %s",
+ user, name));
+ (void) fprintf(stderr,
+ _("%s: %s is not authorized to change the password of %s\n"),
+ Prog, user, name);
+ if (NULL != user_context) {
+ freecon (user_context);
+ }
+ exit (E_NOPERM);
+ }
+#endif /* WITH_SELINUX */
+
+ /*
+ * If the UID of the user does not match the current real UID,
+ * check if I'm root.
+ */
+ if (!amroot && (pw->pw_uid != getuid ())) {
+ (void) fprintf (stderr,
+ _("%s: You may not view or modify password information for %s.\n"),
+ Prog, name);
+ SYSLOG ((LOG_WARN,
+ "%s: can't view or modify password information for %s",
+ Prog, name));
+ closelog ();
+ exit (E_NOPERM);
+ }
+
+ if (Sflg) {
+ print_status (pw);
+ exit (E_SUCCESS);
+ }
+#ifndef USE_PAM
+ /*
+ * The user name is valid, so let's get the shadow file entry.
+ */
+ sp = getspnam (name); /* !USE_PAM, no need for xgetspnam */
+ if (NULL == sp) {
+ if (errno == EACCES) {
+ (void) fprintf (stderr,
+ _("%s: Permission denied.\n"),
+ Prog);
+ exit (E_NOPERM);
+ }
+ sp = pwd_to_spwd (pw);
+ }
+
+ cp = sp->sp_pwdp;
+
+ /*
+ * If there are no other flags, just change the password.
+ */
+ if (!anyflag) {
+ STRFCPY (crypt_passwd, cp);
+
+ /*
+ * See if the user is permitted to change the password.
+ * Otherwise, go ahead and set a new password.
+ */
+ check_password (pw, sp);
+
+ /*
+ * Let the user know whose password is being changed.
+ */
+ if (!qflg) {
+ (void) printf (_("Changing password for %s\n"), name);
+ }
+
+ if (new_password (pw) != 0) {
+ (void) fprintf (stderr,
+ _("The password for %s is unchanged.\n"),
+ name);
+ closelog ();
+ exit (E_NOPERM);
+ }
+ do_update_pwd = true;
+ do_update_age = true;
+ }
+#endif /* !USE_PAM */
+ /*
+ * Before going any further, raise the ulimit to prevent colliding
+ * into a lowered ulimit, and set the real UID to root to protect
+ * against unexpected signals. Any keyboard signals are set to be
+ * ignored.
+ */
+ pwd_init ();
+
+#ifdef USE_PAM
+ /*
+ * Don't set the real UID for PAM...
+ */
+ if (!anyflag) {
+ do_pam_passwd (name, qflg, kflg);
+ exit (E_SUCCESS);
+ }
+#endif /* USE_PAM */
+ if (setuid (0) != 0) {
+ (void) fputs (_("Cannot change ID to root.\n"), stderr);
+ SYSLOG ((LOG_ERR, "can't setuid(0)"));
+ closelog ();
+ exit (E_NOPERM);
+ }
+ if (spw_file_present ()) {
+ update_shadow ();
+ } else {
+ update_noshadow ();
+ }
+
+ nscd_flush_cache ("passwd");
+ nscd_flush_cache ("group");
+
+ SYSLOG ((LOG_INFO, "password for '%s' changed by '%s'", name, myname));
+ closelog ();
+ if (!qflg) {
+ if (!anyflag) {
+#ifndef USE_PAM
+ (void) printf (_("%s: password changed.\n"), Prog);
+#endif /* USE_PAM */
+ } else {
+ (void) printf (_("%s: password expiry information changed.\n"), Prog);
+ }
+ }
+
+ return E_SUCCESS;
+}
+
diff --git a/src/pwck.c b/src/pwck.c
new file mode 100644
index 0000000..523135f
--- /dev/null
+++ b/src/pwck.c
@@ -0,0 +1,893 @@
+/*
+ * Copyright (c) 1992 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2001 , Michał Moskal
+ * Copyright (c) 2001 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <getopt.h>
+#include "chkname.h"
+#include "commonio.h"
+#include "defines.h"
+#include "prototypes.h"
+#include "pwio.h"
+#include "shadowio.h"
+#include "getdef.h"
+#include "nscd.h"
+#ifdef WITH_TCB
+#include "tcbfuncs.h"
+#endif /* WITH_TCB */
+
+/*
+ * Exit codes
+ */
+/*@-exitarg@*/
+#define E_OKAY 0
+#define E_SUCCESS 0
+#define E_USAGE 1
+#define E_BADENTRY 2
+#define E_CANTOPEN 3
+#define E_CANTLOCK 4
+#define E_CANTUPDATE 5
+#define E_CANTSORT 6
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static bool use_system_pw_file = true;
+static bool use_system_spw_file = true;
+
+static bool is_shadow = false;
+
+static bool spw_opened = false;
+
+static bool pw_locked = false;
+static bool spw_locked = false;
+
+/* Options */
+static bool read_only = false;
+static bool sort_mode = false;
+static bool quiet = false; /* don't report warnings, only errors */
+
+/* local function prototypes */
+static void fail_exit (int code);
+static /*@noreturn@*/void usage (int status);
+static void process_flags (int argc, char **argv);
+static void open_files (void);
+static void close_files (bool changed);
+static void check_pw_file (int *errors, bool *changed);
+static void check_spw_file (int *errors, bool *changed);
+
+/*
+ * fail_exit - do some cleanup and exit with the given error code
+ */
+static void fail_exit (int code)
+{
+ if (spw_locked) {
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ if (use_system_spw_file) {
+ SYSLOG ((LOG_ERR, "failed to unlock %s",
+ spw_dbname ()));
+ }
+ /* continue */
+ }
+ }
+
+ if (pw_locked) {
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ if (use_system_pw_file) {
+ SYSLOG ((LOG_ERR, "failed to unlock %s",
+ pw_dbname ()));
+ }
+ /* continue */
+ }
+ }
+
+ closelog ();
+
+ exit (code);
+}
+/*
+ * usage - print syntax message and exit
+ */
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+#ifdef WITH_TCB
+ if (getdef_bool ("USE_TCB")) {
+ (void) fprintf (usageout,
+ _("Usage: %s [options] [passwd]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ } else
+#endif /* WITH_TCB */
+ {
+ (void) fprintf (usageout,
+ _("Usage: %s [options] [passwd [shadow]]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ }
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -q, --quiet report errors only\n"), usageout);
+ (void) fputs (_(" -r, --read-only display errors and warnings\n"
+ " but do not change files\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+#ifdef WITH_TCB
+ if (!getdef_bool ("USE_TCB"))
+#endif /* !WITH_TCB */
+ {
+ (void) fputs (_(" -s, --sort sort entries by UID\n"), usageout);
+ }
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ int c;
+ static struct option long_options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"read-only", no_argument, NULL, 'r'},
+ {"root", required_argument, NULL, 'R'},
+ {"sort", no_argument, NULL, 's'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ /*
+ * Parse the command line arguments
+ */
+ while ((c = getopt_long (argc, argv, "ehqrR:s",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'e': /* added for Debian shadow-961025-2 compatibility */
+ case 'q':
+ quiet = true;
+ break;
+ case 'r':
+ read_only = true;
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ case 's':
+ sort_mode = true;
+ break;
+ default:
+ usage (E_USAGE);
+ }
+ }
+
+ if (sort_mode && read_only) {
+ fprintf (stderr, _("%s: -s and -r are incompatible\n"), Prog);
+ exit (E_USAGE);
+ }
+
+ /*
+ * Make certain we have the right number of arguments
+ */
+ if (argc > (optind + 2)) {
+ usage (E_USAGE);
+ }
+
+ /*
+ * If there are two left over filenames, use those as the password
+ * and shadow password filenames.
+ */
+ if (optind != argc) {
+ pw_setdbname (argv[optind]);
+ use_system_pw_file = false;
+ }
+ if ((optind + 2) == argc) {
+#ifdef WITH_TCB
+ if (getdef_bool ("USE_TCB")) {
+ fprintf (stderr,
+ _("%s: no alternative shadow file allowed when USE_TCB is enabled.\n"),
+ Prog);
+ usage (E_USAGE);
+ }
+#endif /* WITH_TCB */
+ spw_setdbname (argv[optind + 1]);
+ is_shadow = true;
+ use_system_spw_file = false;
+ } else if (optind == argc) {
+ is_shadow = spw_file_present ();
+ }
+}
+
+/*
+ * open_files - open the shadow database
+ *
+ * In read-only mode, the databases are not locked and are opened
+ * only for reading.
+ */
+static void open_files (void)
+{
+ bool use_tcb = false;
+#ifdef WITH_TCB
+ use_tcb = getdef_bool ("USE_TCB");
+#endif /* WITH_TCB */
+
+ /*
+ * Lock the files if we aren't in "read-only" mode
+ */
+ if (!read_only) {
+ if (pw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, pw_dbname ());
+ fail_exit (E_CANTLOCK);
+ }
+ pw_locked = true;
+ if (is_shadow && !use_tcb) {
+ if (spw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, spw_dbname ());
+ fail_exit (E_CANTLOCK);
+ }
+ spw_locked = true;
+ }
+ }
+
+ /*
+ * Open the files. Use O_RDONLY if we are in read_only mode, O_RDWR
+ * otherwise.
+ */
+ if (pw_open (read_only ? O_RDONLY : O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"),
+ Prog, pw_dbname ());
+ if (use_system_pw_file) {
+ SYSLOG ((LOG_WARN, "cannot open %s", pw_dbname ()));
+ }
+ fail_exit (E_CANTOPEN);
+ }
+ if (is_shadow && !use_tcb) {
+ if (spw_open (read_only ? O_RDONLY : O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"),
+ Prog, spw_dbname ());
+ if (use_system_spw_file) {
+ SYSLOG ((LOG_WARN, "cannot open %s",
+ spw_dbname ()));
+ }
+ fail_exit (E_CANTOPEN);
+ }
+ spw_opened = true;
+ }
+}
+
+/*
+ * close_files - close and unlock the password/shadow databases
+ *
+ * If changed is not set, the databases are not closed, and no
+ * changes are committed in the databases. The databases are
+ * unlocked anyway.
+ */
+static void close_files (bool changed)
+{
+ /*
+ * All done. If there were no change we can just abandon any
+ * changes to the files.
+ */
+ if (changed) {
+ if (pw_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, pw_dbname ());
+ if (use_system_pw_file) {
+ SYSLOG ((LOG_ERR,
+ "failure while writing changes to %s",
+ pw_dbname ()));
+ }
+ fail_exit (E_CANTUPDATE);
+ }
+ if (spw_opened && (spw_close () == 0)) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, spw_dbname ());
+ if (use_system_spw_file) {
+ SYSLOG ((LOG_ERR,
+ "failure while writing changes to %s",
+ spw_dbname ()));
+ }
+ fail_exit (E_CANTUPDATE);
+ }
+ spw_opened = false;
+ }
+
+ /*
+ * Don't be anti-social - unlock the files when you're done.
+ */
+ if (spw_locked) {
+ if (spw_unlock () == 0) {
+ fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, spw_dbname ());
+ if (use_system_spw_file) {
+ SYSLOG ((LOG_ERR, "failed to unlock %s",
+ spw_dbname ()));
+ }
+ /* continue */
+ }
+ }
+ spw_locked = false;
+ if (pw_locked) {
+ if (pw_unlock () == 0) {
+ fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, pw_dbname ());
+ if (use_system_pw_file) {
+ SYSLOG ((LOG_ERR, "failed to unlock %s",
+ pw_dbname ()));
+ }
+ /* continue */
+ }
+ }
+ pw_locked = false;
+}
+
+/*
+ * check_pw_file - check the content of the passwd file
+ */
+static void check_pw_file (int *errors, bool *changed)
+{
+ struct commonio_entry *pfe, *tpfe;
+ struct passwd *pwd;
+ struct spwd *spw;
+
+ /*
+ * Loop through the entire password file.
+ */
+ for (pfe = __pw_get_head (); NULL != pfe; pfe = pfe->next) {
+ /*
+ * If this is a NIS line, skip it. You can't "know" what NIS
+ * is going to do without directly asking NIS ...
+ */
+ if (('+' == pfe->line[0]) || ('-' == pfe->line[0])) {
+ continue;
+ }
+
+ /*
+ * Start with the entries that are completely corrupt. They
+ * have no (struct passwd) entry because they couldn't be
+ * parsed properly.
+ */
+ if (NULL == pfe->eptr) {
+ /*
+ * Tell the user this entire line is bogus and ask
+ * them to delete it.
+ */
+ puts (_("invalid password file entry"));
+ printf (_("delete line '%s'? "), pfe->line);
+ *errors += 1;
+
+ /*
+ * prompt the user to delete the entry or not
+ */
+ if (!yes_or_no (read_only)) {
+ continue;
+ }
+
+ /*
+ * All password file deletions wind up here. This
+ * code removes the current entry from the linked
+ * list. When done, it skips back to the top of the
+ * loop to try out the next list element.
+ */
+ delete_pw:
+ if (use_system_pw_file) {
+ SYSLOG ((LOG_INFO, "delete passwd line '%s'",
+ pfe->line));
+ }
+ *changed = true;
+
+ __pw_del_entry (pfe);
+ continue;
+ }
+
+ /*
+ * Password structure is good, start using it.
+ */
+ pwd = pfe->eptr;
+
+ /*
+ * Make sure this entry has a unique name.
+ */
+ for (tpfe = __pw_get_head (); NULL != tpfe; tpfe = tpfe->next) {
+ const struct passwd *ent = tpfe->eptr;
+
+ /*
+ * Don't check this entry
+ */
+ if (tpfe == pfe) {
+ continue;
+ }
+
+ /*
+ * Don't check invalid entries.
+ */
+ if (NULL == ent) {
+ continue;
+ }
+
+ if (strcmp (pwd->pw_name, ent->pw_name) != 0) {
+ continue;
+ }
+
+ /*
+ * Tell the user this entry is a duplicate of
+ * another and ask them to delete it.
+ */
+ puts (_("duplicate password entry"));
+ printf (_("delete line '%s'? "), pfe->line);
+ *errors += 1;
+
+ /*
+ * prompt the user to delete the entry or not
+ */
+ if (yes_or_no (read_only)) {
+ goto delete_pw;
+ }
+ }
+
+ /*
+ * Check for invalid usernames. --marekm
+ */
+ if (!is_valid_user_name (pwd->pw_name)) {
+ printf (_("invalid user name '%s'\n"), pwd->pw_name);
+ *errors += 1;
+ }
+
+ /*
+ * Check for invalid user ID.
+ */
+ if (pwd->pw_uid == (uid_t)-1) {
+ printf (_("invalid user ID '%lu'\n"), (long unsigned int)pwd->pw_uid);
+ *errors += 1;
+ }
+
+ /*
+ * Make sure the primary group exists
+ */
+ /* local, no need for xgetgrgid */
+ if (!quiet && (NULL == getgrgid (pwd->pw_gid))) {
+
+ /*
+ * No primary group, just give a warning
+ */
+
+ printf (_("user '%s': no group %lu\n"),
+ pwd->pw_name, (unsigned long) pwd->pw_gid);
+ *errors += 1;
+ }
+
+ /*
+ * Make sure the home directory exists
+ */
+ if (!quiet && (access (pwd->pw_dir, F_OK) != 0)) {
+ /*
+ * Home directory doesn't exist, give a warning
+ */
+ printf (_("user '%s': directory '%s' does not exist\n"),
+ pwd->pw_name, pwd->pw_dir);
+ *errors += 1;
+ }
+
+ /*
+ * Make sure the login shell is executable
+ */
+ if ( !quiet
+ && ('\0' != pwd->pw_shell[0])
+ && (access (pwd->pw_shell, F_OK) != 0)) {
+
+ /*
+ * Login shell doesn't exist, give a warning
+ */
+ printf (_("user '%s': program '%s' does not exist\n"),
+ pwd->pw_name, pwd->pw_shell);
+ *errors += 1;
+ }
+
+ /*
+ * Make sure this entry exists in the /etc/shadow file.
+ */
+
+ if (is_shadow) {
+#ifdef WITH_TCB
+ if (getdef_bool ("USE_TCB")) {
+ if (shadowtcb_set_user (pwd->pw_name) == SHADOWTCB_FAILURE) {
+ printf (_("no tcb directory for %s\n"),
+ pwd->pw_name);
+ printf (_("create tcb directory for %s?"),
+ pwd->pw_name);
+ *errors += 1;
+ if (yes_or_no (read_only)) {
+ if (shadowtcb_create (pwd->pw_name, pwd->pw_uid) == SHADOWTCB_FAILURE) {
+ *errors += 1;
+ printf (_("failed to create tcb directory for %s\n"), pwd->pw_name);
+ continue;
+ }
+ } else {
+ continue;
+ }
+ }
+ if (spw_lock () == 0) {
+ *errors += 1;
+ fprintf (stderr,
+ _("%s: cannot lock %s.\n"),
+ Prog, spw_dbname ());
+ continue;
+ }
+ spw_locked = true;
+ if (spw_open (read_only ? O_RDONLY : O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, spw_dbname ());
+ *errors += 1;
+ if (spw_unlock () == 0) {
+ fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, spw_dbname ());
+ if (use_system_spw_file) {
+ SYSLOG ((LOG_ERR,
+ "failed to unlock %s",
+ spw_dbname ()));
+ }
+ }
+ continue;
+ }
+ spw_opened = true;
+ }
+#endif /* WITH_TCB */
+ spw = (struct spwd *) spw_locate (pwd->pw_name);
+ if (NULL == spw) {
+ printf (_("no matching password file entry in %s\n"),
+ spw_dbname ());
+ printf (_("add user '%s' in %s? "),
+ pwd->pw_name, spw_dbname ());
+ *errors += 1;
+ if (yes_or_no (read_only)) {
+ struct spwd sp;
+ struct passwd pw;
+
+ sp.sp_namp = pwd->pw_name;
+ sp.sp_pwdp = pwd->pw_passwd;
+ sp.sp_min =
+ getdef_num ("PASS_MIN_DAYS", -1);
+ sp.sp_max =
+ getdef_num ("PASS_MAX_DAYS", -1);
+ sp.sp_warn =
+ getdef_num ("PASS_WARN_AGE", -1);
+ sp.sp_inact = -1;
+ sp.sp_expire = -1;
+ sp.sp_flag = SHADOW_SP_FLAG_UNSET;
+ sp.sp_lstchg = (long) time ((time_t *) 0) / SCALE;
+ if (0 == sp.sp_lstchg) {
+ /* Better disable aging than
+ * requiring a password change
+ */
+ sp.sp_lstchg = -1;
+ }
+ *changed = true;
+
+ if (spw_update (&sp) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, spw_dbname (), sp.sp_namp);
+ fail_exit (E_CANTUPDATE);
+ }
+ /* remove password from /etc/passwd */
+ pw = *pwd;
+ pw.pw_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
+ if (pw_update (&pw) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, pw_dbname (), pw.pw_name);
+ fail_exit (E_CANTUPDATE);
+ }
+ }
+ } else {
+ /* The passwd entry has a shadow counterpart.
+ * Make sure no passwords are in passwd.
+ */
+ if ( !quiet
+ && (strcmp (pwd->pw_passwd,
+ SHADOW_PASSWD_STRING) != 0)) {
+ printf (_("user %s has an entry in %s, but its password field in %s is not set to 'x'\n"),
+ pwd->pw_name, spw_dbname (), pw_dbname ());
+ *errors += 1;
+ }
+ }
+ }
+#ifdef WITH_TCB
+ if (getdef_bool ("USE_TCB") && spw_locked) {
+ if (spw_opened && (spw_close () == 0)) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, spw_dbname ());
+ if (use_system_spw_file) {
+ SYSLOG ((LOG_ERR,
+ "failure while writing changes to %s",
+ spw_dbname ()));
+ }
+ } else {
+ spw_opened = false;
+ }
+ if (spw_unlock () == 0) {
+ fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, spw_dbname ());
+ if (use_system_spw_file) {
+ SYSLOG ((LOG_ERR, "failed to unlock %s",
+ spw_dbname ()));
+ }
+ } else {
+ spw_locked = false;
+ }
+ }
+#endif /* WITH_TCB */
+ }
+}
+
+/*
+ * check_spw_file - check the content of the shadowed password file (shadow)
+ */
+static void check_spw_file (int *errors, bool *changed)
+{
+ struct commonio_entry *spe, *tspe;
+ struct spwd *spw;
+
+ /*
+ * Loop through the entire shadow password file.
+ */
+ for (spe = __spw_get_head (); NULL != spe; spe = spe->next) {
+ /*
+ * Do not treat lines which were missing in shadow
+ * and were added earlier.
+ */
+ if (NULL == spe->line) {
+ continue;
+ }
+
+ /*
+ * If this is a NIS line, skip it. You can't "know" what NIS
+ * is going to do without directly asking NIS ...
+ */
+ if (('+' == spe->line[0]) || ('-' == spe->line[0])) {
+ continue;
+ }
+
+ /*
+ * Start with the entries that are completely corrupt. They
+ * have no (struct spwd) entry because they couldn't be
+ * parsed properly.
+ */
+ if (NULL == spe->eptr) {
+ /*
+ * Tell the user this entire line is bogus and ask
+ * them to delete it.
+ */
+ puts (_("invalid shadow password file entry"));
+ printf (_("delete line '%s'? "), spe->line);
+ *errors += 1;
+
+ /*
+ * prompt the user to delete the entry or not
+ */
+ if (!yes_or_no (read_only)) {
+ continue;
+ }
+
+ /*
+ * All shadow file deletions wind up here. This code
+ * removes the current entry from the linked list.
+ * When done, it skips back to the top of the loop
+ * to try out the next list element.
+ */
+ delete_spw:
+ if (use_system_spw_file) {
+ SYSLOG ((LOG_INFO, "delete shadow line '%s'",
+ spe->line));
+ }
+ *changed = true;
+
+ __spw_del_entry (spe);
+ continue;
+ }
+
+ /*
+ * Shadow password structure is good, start using it.
+ */
+ spw = spe->eptr;
+
+ /*
+ * Make sure this entry has a unique name.
+ */
+ for (tspe = __spw_get_head (); NULL != tspe; tspe = tspe->next) {
+ const struct spwd *ent = tspe->eptr;
+
+ /*
+ * Don't check this entry
+ */
+ if (tspe == spe) {
+ continue;
+ }
+
+ /*
+ * Don't check invalid entries.
+ */
+ if (NULL == ent) {
+ continue;
+ }
+
+ if (strcmp (spw->sp_namp, ent->sp_namp) != 0) {
+ continue;
+ }
+
+ /*
+ * Tell the user this entry is a duplicate of
+ * another and ask them to delete it.
+ */
+ puts (_("duplicate shadow password entry"));
+ printf (_("delete line '%s'? "), spe->line);
+ *errors += 1;
+
+ /*
+ * prompt the user to delete the entry or not
+ */
+ if (yes_or_no (read_only)) {
+ goto delete_spw;
+ }
+ }
+
+ /*
+ * Make sure this entry exists in the /etc/passwd
+ * file.
+ */
+ if (pw_locate (spw->sp_namp) == NULL) {
+ /*
+ * Tell the user this entry has no matching
+ * /etc/passwd entry and ask them to delete it.
+ */
+ printf (_("no matching password file entry in %s\n"),
+ pw_dbname ());
+ printf (_("delete line '%s'? "), spe->line);
+ *errors += 1;
+
+ /*
+ * prompt the user to delete the entry or not
+ */
+ if (yes_or_no (read_only)) {
+ goto delete_spw;
+ }
+ }
+
+ /*
+ * Warn if last password change in the future. --marekm
+ */
+ if (!quiet) {
+ time_t t = time ((time_t *) 0);
+ if ( (t != 0)
+ && (spw->sp_lstchg > (long) t / SCALE)) {
+ printf (_("user %s: last password change in the future\n"),
+ spw->sp_namp);
+ *errors += 1;
+ }
+ }
+ }
+}
+
+/*
+ * pwck - verify password file integrity
+ */
+int main (int argc, char **argv)
+{
+ int errors = 0;
+ bool changed = false;
+
+ /*
+ * Get my name so that I can use it to report errors.
+ */
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ OPENLOG ("pwck");
+
+ /* Parse the command line arguments */
+ process_flags (argc, argv);
+
+ open_files ();
+
+ if (sort_mode) {
+ if (pw_sort () != 0) {
+ fprintf (stderr,
+ _("%s: cannot sort entries in %s\n"),
+ Prog, pw_dbname ());
+ fail_exit (E_CANTSORT);
+ }
+ if (is_shadow) {
+ if (spw_sort () != 0) {
+ fprintf (stderr,
+ _("%s: cannot sort entries in %s\n"),
+ Prog, spw_dbname ());
+ fail_exit (E_CANTSORT);
+ }
+ }
+ changed = true;
+ } else {
+ check_pw_file (&errors, &changed);
+
+ if (is_shadow) {
+ check_spw_file (&errors, &changed);
+ }
+ }
+
+ close_files (changed);
+
+ nscd_flush_cache ("passwd");
+
+ /*
+ * Tell the user what we did and exit.
+ */
+ if (0 != errors) {
+ printf (changed ?
+ _("%s: the files have been updated\n") :
+ _("%s: no changes\n"), Prog);
+ }
+
+ closelog ();
+ return ((0 != errors) ? E_BADENTRY : E_OKAY);
+}
+
diff --git a/src/pwconv.c b/src/pwconv.c
new file mode 100644
index 0000000..e2d61f8
--- /dev/null
+++ b/src/pwconv.c
@@ -0,0 +1,333 @@
+/*
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2002 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2009 - 2012, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * pwconv - create or update /etc/shadow with information from
+ * /etc/passwd.
+ *
+ * It is more like SysV pwconv, slightly different from the original Shadow
+ * pwconv. Depends on "x" as password in /etc/passwd which means that the
+ * password has already been moved to /etc/shadow. There is no need to move
+ * /etc/npasswd to /etc/passwd, password files are updated using library
+ * routines with proper locking.
+ *
+ * Can be used to update /etc/shadow after adding/deleting users by editing
+ * /etc/passwd. There is no man page yet, but this program should be close
+ * to pwconv(1M) on Solaris 2.x.
+ *
+ * Warning: make sure that all users have "x" as the password in /etc/passwd
+ * before running this program for the first time on a system which already
+ * has shadow passwords. Anything else (like "*" from old versions of the
+ * shadow suite) will replace the user's encrypted password in /etc/shadow.
+ *
+ * Doesn't currently support pw_age information in /etc/passwd, and doesn't
+ * support DBM files. Add it if you need it...
+ *
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <getopt.h>
+#include "defines.h"
+#include "getdef.h"
+#include "prototypes.h"
+#include "pwio.h"
+#include "shadowio.h"
+#include "nscd.h"
+
+/*
+ * exit status values
+ */
+/*@-exitarg@*/
+#define E_SUCCESS 0 /* success */
+#define E_NOPERM 1 /* permission denied */
+#define E_USAGE 2 /* invalid command syntax */
+#define E_FAILURE 3 /* unexpected failure, nothing done */
+#define E_MISSING 4 /* unexpected failure, passwd file missing */
+#define E_PWDBUSY 5 /* passwd file(s) busy */
+#define E_BADENTRY 6 /* bad shadow entry */
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static bool spw_locked = false;
+static bool pw_locked = false;
+
+/* local function prototypes */
+static void fail_exit (int status);
+static void usage (int status);
+static void process_flags (int argc, char **argv);
+
+static void fail_exit (int status)
+{
+ if (pw_locked) {
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ }
+
+ if (spw_locked) {
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ }
+
+ exit (status);
+}
+
+static void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ /*
+ * Parse the command line options.
+ */
+ int c;
+ static struct option long_options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"root", required_argument, NULL, 'R'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "hR:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ default:
+ usage (E_USAGE);
+ }
+ }
+
+ if (optind != argc) {
+ usage (E_USAGE);
+ }
+}
+
+int main (int argc, char **argv)
+{
+ const struct passwd *pw;
+ struct passwd pwent;
+ const struct spwd *sp;
+ struct spwd spent;
+
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ OPENLOG ("pwconv");
+
+ process_flags (argc, argv);
+
+#ifdef WITH_TCB
+ if (getdef_bool("USE_TCB")) {
+ fprintf (stderr, _("%s: can't work with tcb enabled\n"), Prog);
+ exit (E_FAILURE);
+ }
+#endif /* WITH_TCB */
+
+ if (pw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, pw_dbname ());
+ fail_exit (E_PWDBUSY);
+ }
+ pw_locked = true;
+ if (pw_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"), Prog, pw_dbname ());
+ fail_exit (E_MISSING);
+ }
+
+ if (spw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, spw_dbname ());
+ fail_exit (E_PWDBUSY);
+ }
+ spw_locked = true;
+ if (spw_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"), Prog, spw_dbname ());
+ fail_exit (E_FAILURE);
+ }
+
+ /*
+ * Remove /etc/shadow entries for users not in /etc/passwd.
+ */
+ (void) spw_rewind ();
+ while ((sp = spw_next ()) != NULL) {
+ if (pw_locate (sp->sp_namp) != NULL) {
+ continue;
+ }
+
+ if (spw_remove (sp->sp_namp) == 0) {
+ /*
+ * This shouldn't happen (the entry exists) but...
+ */
+ fprintf (stderr,
+ _("%s: cannot remove entry '%s' from %s\n"),
+ Prog, sp->sp_namp, spw_dbname ());
+ fail_exit (E_FAILURE);
+ }
+ }
+
+ /*
+ * Update shadow entries which don't have "x" as pw_passwd. Add any
+ * missing shadow entries.
+ */
+ (void) pw_rewind ();
+ while ((pw = pw_next ()) != NULL) {
+ sp = spw_locate (pw->pw_name);
+ if (NULL != sp) {
+ /* do we need to update this entry? */
+ if (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) == 0) {
+ continue;
+ }
+ /* update existing shadow entry */
+ spent = *sp;
+ } else {
+ /* add new shadow entry */
+ memset (&spent, 0, sizeof spent);
+ spent.sp_namp = pw->pw_name;
+ spent.sp_min = getdef_num ("PASS_MIN_DAYS", -1);
+ spent.sp_max = getdef_num ("PASS_MAX_DAYS", -1);
+ spent.sp_warn = getdef_num ("PASS_WARN_AGE", -1);
+ spent.sp_inact = -1;
+ spent.sp_expire = -1;
+ spent.sp_flag = SHADOW_SP_FLAG_UNSET;
+ }
+ spent.sp_pwdp = pw->pw_passwd;
+ spent.sp_lstchg = (long) time ((time_t *) 0) / SCALE;
+ if (0 == spent.sp_lstchg) {
+ /* Better disable aging than requiring a password
+ * change */
+ spent.sp_lstchg = -1;
+ }
+ if (spw_update (&spent) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, spw_dbname (), spent.sp_namp);
+ fail_exit (E_FAILURE);
+ }
+
+ /* remove password from /etc/passwd */
+ pwent = *pw;
+ pwent.pw_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
+ if (pw_update (&pwent) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, pw_dbname (), pwent.pw_name);
+ fail_exit (E_FAILURE);
+ }
+ }
+
+ if (spw_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ()));
+ fail_exit (E_FAILURE);
+ }
+ if (pw_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
+ fail_exit (E_FAILURE);
+ }
+
+ /* /etc/passwd- (backup file) */
+ errno = 0;
+ if ((chmod (PASSWD_FILE "-", 0600) != 0) && (errno != ENOENT)) {
+ fprintf (stderr,
+ _("%s: failed to change the mode of %s to 0600\n"),
+ Prog, PASSWD_FILE "-");
+ SYSLOG ((LOG_ERR, "failed to change the mode of %s to 0600", PASSWD_FILE "-"));
+ /* continue */
+ }
+
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+
+ nscd_flush_cache ("passwd");
+
+ return E_SUCCESS;
+}
+
diff --git a/src/pwunconv.c b/src/pwunconv.c
new file mode 100644
index 0000000..fabf023
--- /dev/null
+++ b/src/pwunconv.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 1989 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2001 - 2005, Tomasz Kłoczko
+ * Copyright (c) 2008 - 2012, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <getopt.h>
+#include "defines.h"
+#include "nscd.h"
+#include "prototypes.h"
+#include "pwio.h"
+#include "shadowio.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static bool spw_locked = false;
+static bool pw_locked = false;
+
+/* local function prototypes */
+static void fail_exit (int status);
+static void usage (int status);
+static void process_flags (int argc, char **argv);
+
+static void fail_exit (int status)
+{
+ if (spw_locked) {
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ }
+ if (pw_locked) {
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ }
+ exit (status);
+}
+
+static void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * process_flags - parse the command line options
+ *
+ * It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+ /*
+ * Parse the command line options.
+ */
+ int c;
+ static struct option long_options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"root", required_argument, NULL, 'R'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "hR:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ default:
+ usage (E_USAGE);
+ }
+ }
+
+ if (optind != argc) {
+ usage (E_USAGE);
+ }
+}
+
+int main (int argc, char **argv)
+{
+ const struct passwd *pw;
+ struct passwd pwent;
+ const struct spwd *spwd;
+
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ OPENLOG ("pwunconv");
+
+ process_flags (argc, argv);
+
+#ifdef WITH_TCB
+ if (getdef_bool("USE_TCB")) {
+ fprintf (stderr, _("%s: can't work with tcb enabled\n"), Prog);
+ exit (1);
+ }
+#endif /* WITH_TCB */
+
+ if (!spw_file_present ()) {
+ /* shadow not installed, do nothing */
+ exit (0);
+ }
+
+ if (pw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, pw_dbname ());
+ fail_exit (5);
+ }
+ pw_locked = true;
+ if (pw_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, pw_dbname ());
+ fail_exit (1);
+ }
+
+ if (spw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, spw_dbname ());
+ fail_exit (5);
+ }
+ spw_locked = true;
+ if (spw_open (O_RDONLY) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, spw_dbname ());
+ fail_exit (1);
+ }
+
+ (void) pw_rewind ();
+ while ((pw = pw_next ()) != NULL) {
+ spwd = spw_locate (pw->pw_name);
+ if (NULL == spwd) {
+ continue;
+ }
+
+ pwent = *pw;
+
+ /*
+ * Update password if non-shadow is "x".
+ */
+ if (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) == 0) {
+ pwent.pw_passwd = spwd->sp_pwdp;
+ }
+
+ /*
+ * Password aging works differently in the two different
+ * systems. With shadow password files you apparently must
+ * have some aging information. The maxweeks or minweeks
+ * may not map exactly. In pwconv we set max == 10000,
+ * which is about 30 years. Here we have to undo that
+ * kludge. So, if maxdays == 10000, no aging information is
+ * put into the new file. Otherwise, the days are converted
+ * to weeks and so on.
+ */
+ if (pw_update (&pwent) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, pw_dbname (), pwent.pw_name);
+ fail_exit (3);
+ }
+ }
+
+ (void) spw_close (); /* was only open O_RDONLY */
+
+ if (pw_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
+ fail_exit (3);
+ }
+
+ if (unlink (SHADOW) != 0) {
+ fprintf (stderr,
+ _("%s: cannot delete %s\n"), Prog, SHADOW);
+ SYSLOG ((LOG_ERR, "cannot delete %s", SHADOW));
+ fail_exit (3);
+ }
+
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+
+ nscd_flush_cache ("passwd");
+
+ return 0;
+}
+
diff --git a/src/su.c b/src/su.c
new file mode 100644
index 0000000..974048e
--- /dev/null
+++ b/src/su.c
@@ -0,0 +1,1222 @@
+/*
+ * Copyright (c) 1989 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2013, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* Some parts substantially derived from an ancestor of:
+ su for GNU. Run a shell with substitute user and group IDs.
+
+ Copyright (C) 1992-2003 Free Software Foundation, Inc.
+
+ 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, 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., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA. */
+
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <getopt.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#ifndef USE_PAM
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#endif /* !USE_PAM */
+#include "prototypes.h"
+#include "defines.h"
+#include "pwauth.h"
+#include "getdef.h"
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif /* USE_PAM */
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+/*
+ * Global variables
+ */
+const char *Prog;
+static /*@observer@*/const char *caller_tty = NULL; /* Name of tty SU is run from */
+static bool caller_is_root = false;
+static uid_t caller_uid;
+#ifndef USE_PAM
+static bool caller_on_console = false;
+#ifdef SU_ACCESS
+static /*@only@*/char *caller_pass;
+#endif
+#endif /* !USE_PAM */
+static bool doshell = false;
+static bool fakelogin = false;
+static /*@observer@*/const char *shellstr;
+static /*@null@*/char *command = NULL;
+
+
+/* not needed by sulog.c anymore */
+static char name[BUFSIZ];
+static char caller_name[BUFSIZ];
+
+/* If nonzero, change some environment vars to indicate the user su'd to. */
+static bool change_environment = true;
+
+#ifdef USE_PAM
+static char kill_msg[256];
+static char wait_msg[256];
+static pam_handle_t *pamh = NULL;
+static int caught = 0;
+/* PID of the child, in case it needs to be killed */
+static pid_t pid_child = 0;
+#endif
+
+/*
+ * External identifiers
+ */
+
+extern char **newenvp; /* libmisc/env.c */
+extern size_t newenvc; /* libmisc/env.c */
+
+/* local function prototypes */
+
+static void execve_shell (const char *shellname,
+ char *args[],
+ char *const envp[]);
+#ifdef USE_PAM
+static RETSIGTYPE kill_child (int unused(s));
+static void prepare_pam_close_session (void);
+#else /* !USE_PAM */
+static RETSIGTYPE die (int);
+static bool iswheel (const char *);
+#endif /* !USE_PAM */
+static bool restricted_shell (const char *shellname);
+static /*@noreturn@*/void su_failure (const char *tty, bool su_to_root);
+static /*@only@*/struct passwd * check_perms (void);
+#ifdef USE_PAM
+static void check_perms_pam (const struct passwd *pw);
+#else /* !USE_PAM */
+static void check_perms_nopam (const struct passwd *pw);
+#endif /* !USE_PAM */
+static void save_caller_context (char **argv);
+static void process_flags (int argc, char **argv);
+static void set_environment (struct passwd *pw);
+
+#ifndef USE_PAM
+/*
+ * die - set or reset termio modes.
+ *
+ * die() is called before processing begins. signal() is then called
+ * with die() as the signal handler. If signal later calls die() with a
+ * signal number, the terminal modes are then reset.
+ */
+static RETSIGTYPE die (int killed)
+{
+ static TERMIO sgtty;
+
+ if (killed != 0) {
+ STTY (0, &sgtty);
+ } else {
+ GTTY (0, &sgtty);
+ }
+
+ if (killed != 0) {
+ _exit (128+killed);
+ }
+}
+
+static bool iswheel (const char *username)
+{
+ struct group *grp;
+
+ grp = getgrnam ("wheel"); /* !USE_PAM, no need for xgetgrnam */
+ if ( (NULL ==grp)
+ || (NULL == grp->gr_mem)) {
+ return false;
+ }
+ return is_on_list (grp->gr_mem, username);
+}
+#else /* USE_PAM */
+static RETSIGTYPE kill_child (int unused(s))
+{
+ if (0 != pid_child) {
+ (void) kill (-pid_child, SIGKILL);
+ (void) write (STDERR_FILENO, kill_msg, strlen (kill_msg));
+ } else {
+ (void) write (STDERR_FILENO, wait_msg, strlen (wait_msg));
+ }
+ _exit (255);
+}
+#endif /* USE_PAM */
+
+/* borrowed from GNU sh-utils' "su.c" */
+static bool restricted_shell (const char *shellname)
+{
+ /*@observer@*/const char *line;
+
+ setusershell ();
+ while ((line = getusershell ()) != NULL) {
+ if (('#' != *line) && (strcmp (line, shellname) == 0)) {
+ endusershell ();
+ return false;
+ }
+ }
+ endusershell ();
+ return true;
+}
+
+static /*@noreturn@*/void su_failure (const char *tty, bool su_to_root)
+{
+ sulog (tty, false, caller_name, name); /* log failed attempt */
+#ifdef USE_SYSLOG
+ if (getdef_bool ("SYSLOG_SU_ENAB")) {
+ SYSLOG ((su_to_root ? LOG_NOTICE : LOG_INFO,
+ "- %s %s:%s", tty,
+ ('\0' != caller_name[0]) ? caller_name : "???",
+ ('\0' != name[0]) ? name : "???"));
+ }
+ closelog ();
+#endif
+
+#ifdef WITH_AUDIT
+ audit_fd = audit_open ();
+ audit_log_acct_message (audit_fd,
+ AUDIT_USER_ROLE_CHANGE,
+ NULL, /* Prog. name */
+ "su",
+ ('\0' != caller_name[0]) ? caller_name : "???",
+ AUDIT_NO_ID,
+ "localhost",
+ NULL, /* addr */
+ tty,
+ 0); /* result */
+ close (audit_fd);
+#endif /* WITH_AUDIT */
+
+ exit (1);
+}
+
+/*
+ * execve_shell - Execute a shell with execve, or interpret it with
+ * /bin/sh
+ */
+static void execve_shell (const char *shellname,
+ char *args[],
+ char *const envp[])
+{
+ int err;
+ (void) execve (shellname, (char **) args, envp);
+ err = errno;
+
+ if (access (shellname, R_OK|X_OK) == 0) {
+ /*
+ * Assume this is a shell script (with no shebang).
+ * Interpret it with /bin/sh
+ */
+ size_t n_args = 0;
+ char **targs;
+ while (NULL != args[n_args]) {
+ n_args++;
+ }
+ targs = (char **) xmalloc ((n_args + 3) * sizeof (args[0]));
+ targs[0] = "sh";
+ targs[1] = "-";
+ targs[2] = xstrdup (shellname);
+ targs[n_args+2] = NULL;
+ while (1 != n_args) {
+ targs[n_args+1] = args[n_args - 1];
+ n_args--;
+ }
+
+ (void) execve (SHELL, targs, envp);
+ } else {
+ errno = err;
+ }
+}
+
+#ifdef USE_PAM
+/* Signal handler for parent process later */
+static void catch_signals (int sig)
+{
+ caught = sig;
+}
+
+/*
+ * prepare_pam_close_session - Fork and wait for the child to close the session
+ *
+ * Only the child returns. The parent will wait for the child to
+ * terminate and exit.
+ */
+static void prepare_pam_close_session (void)
+{
+ sigset_t ourset;
+ int status;
+ int ret;
+
+ pid_child = fork ();
+ if (pid_child == 0) { /* child shell */
+ return; /* Only the child will return from pam_create_session */
+ } else if ((pid_t)-1 == pid_child) {
+ (void) fprintf (stderr,
+ _("%s: Cannot fork user shell\n"),
+ Prog);
+ SYSLOG ((LOG_WARN, "Cannot execute %s", shellstr));
+ closelog ();
+ exit (1);
+ /* Only the child returns. See above. */
+ }
+
+ /* parent only */
+ sigfillset (&ourset);
+ if (sigprocmask (SIG_BLOCK, &ourset, NULL) != 0) {
+ (void) fprintf (stderr,
+ _("%s: signal malfunction\n"),
+ Prog);
+ caught = SIGTERM;
+ }
+ if (0 == caught) {
+ struct sigaction action;
+
+ action.sa_handler = catch_signals;
+ sigemptyset (&action.sa_mask);
+ action.sa_flags = 0;
+ sigemptyset (&ourset);
+
+ if ( (sigaddset (&ourset, SIGTERM) != 0)
+ || (sigaddset (&ourset, SIGALRM) != 0)
+ || (sigaction (SIGTERM, &action, NULL) != 0)
+ || ( !doshell /* handle SIGINT (Ctrl-C), SIGQUIT
+ * (Ctrl-\), and SIGTSTP (Ctrl-Z)
+ * since the child will not control
+ * the tty.
+ */
+ && ( (sigaddset (&ourset, SIGINT) != 0)
+ || (sigaddset (&ourset, SIGQUIT) != 0)
+ || (sigaddset (&ourset, SIGTSTP) != 0)
+ || (sigaction (SIGINT, &action, NULL) != 0)
+ || (sigaction (SIGQUIT, &action, NULL) != 0)
+ || (sigaction (SIGTSTP, &action, NULL) != 0)))
+ || (sigprocmask (SIG_UNBLOCK, &ourset, NULL) != 0)
+ ) {
+ fprintf (stderr,
+ _("%s: signal masking malfunction\n"),
+ Prog);
+ caught = SIGTERM;
+ }
+ }
+
+ if (0 == caught) {
+ bool stop = true;
+
+ do {
+ pid_t pid;
+ stop = true;
+
+ pid = waitpid (-1, &status, WUNTRACED);
+
+ /* When interrupted by signal, the signal will be
+ * forwarded to the child, and termination will be
+ * forced later.
+ */
+ if ( ((pid_t)-1 == pid)
+ && (EINTR == errno)
+ && (SIGTSTP == caught)) {
+ caught = 0;
+ /* Except for SIGTSTP, which request to
+ * stop the child.
+ * We will SIGSTOP ourself on the next
+ * waitpid round.
+ */
+ kill (pid_child, SIGSTOP);
+ stop = false;
+ } else if ( ((pid_t)-1 != pid)
+ && (0 != WIFSTOPPED (status))) {
+ /* The child (shell) was suspended.
+ * Suspend su. */
+ kill (getpid (), SIGSTOP);
+ /* wake child when resumed */
+ kill (pid, SIGCONT);
+ stop = false;
+ } else if ( (pid_t)-1 != pid) {
+ pid_child = 0;
+ }
+ } while (!stop);
+ }
+
+ if (0 != caught && 0 != pid_child) {
+ (void) fputs ("\n", stderr);
+ (void) fputs (_("Session terminated, terminating shell..."),
+ stderr);
+ (void) kill (-pid_child, caught);
+
+ snprintf (kill_msg, sizeof kill_msg, _(" ...killed.\n"));
+ snprintf (wait_msg, sizeof wait_msg, _(" ...waiting for child to terminate.\n"));
+
+ (void) signal (SIGALRM, kill_child);
+ (void) signal (SIGCHLD, catch_signals);
+ (void) alarm (2);
+
+ sigemptyset (&ourset);
+ if ((sigaddset (&ourset, SIGALRM) != 0)
+ || (sigprocmask (SIG_BLOCK, &ourset, NULL) != 0)) {
+ fprintf (stderr, _("%s: signal masking malfunction\n"), Prog);
+ kill_child (0);
+ } else {
+ while (0 == waitpid (pid_child, &status, WNOHANG)) {
+ sigsuspend (&ourset);
+ }
+ pid_child = 0;
+ (void) sigprocmask (SIG_UNBLOCK, &ourset, NULL);
+ }
+
+ (void) fputs (_(" ...terminated.\n"), stderr);
+ }
+
+ ret = pam_close_session (pamh, 0);
+ if (PAM_SUCCESS != ret) {
+ SYSLOG ((LOG_ERR, "pam_close_session: %s",
+ pam_strerror (pamh, ret)));
+ fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+ }
+
+ (void) pam_setcred (pamh, PAM_DELETE_CRED);
+ (void) pam_end (pamh, PAM_SUCCESS);
+
+ exit ((0 != WIFEXITED (status)) ? WEXITSTATUS (status)
+ : WTERMSIG (status) + 128);
+ /* Only the child returns. See above. */
+}
+#endif /* USE_PAM */
+
+/*
+ * usage - print command line syntax and exit
+ */
+static void usage (int status)
+{
+ (void)
+ fputs (_("Usage: su [options] [LOGIN]\n"
+ "\n"
+ "Options:\n"
+ " -c, --command COMMAND pass COMMAND to the invoked shell\n"
+ " -h, --help display this help message and exit\n"
+ " -, -l, --login make the shell a login shell\n"
+ " -m, -p,\n"
+ " --preserve-environment do not reset environment variables, and\n"
+ " keep the same shell\n"
+ " -s, --shell SHELL use SHELL instead of the default in passwd\n"
+ "\n"), (E_SUCCESS != status) ? stderr : stdout);
+ exit (status);
+}
+
+#ifdef USE_PAM
+static void check_perms_pam (const struct passwd *pw)
+{
+ int ret;
+ ret = pam_authenticate (pamh, 0);
+ if (PAM_SUCCESS != ret) {
+ SYSLOG (((pw->pw_uid != 0)? LOG_NOTICE : LOG_WARN, "pam_authenticate: %s",
+ pam_strerror (pamh, ret)));
+ fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+ (void) pam_end (pamh, ret);
+ su_failure (caller_tty, 0 == pw->pw_uid);
+ }
+
+ ret = pam_acct_mgmt (pamh, 0);
+ if (PAM_SUCCESS != ret) {
+ if (caller_is_root) {
+ fprintf (stderr,
+ _("%s: %s\n(Ignored)\n"),
+ Prog, pam_strerror (pamh, ret));
+ } else if (PAM_NEW_AUTHTOK_REQD == ret) {
+ ret = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+ if (PAM_SUCCESS != ret) {
+ SYSLOG ((LOG_ERR, "pam_chauthtok: %s",
+ pam_strerror (pamh, ret)));
+ fprintf (stderr,
+ _("%s: %s\n"),
+ Prog, pam_strerror (pamh, ret));
+ (void) pam_end (pamh, ret);
+ su_failure (caller_tty, 0 == pw->pw_uid);
+ }
+ } else {
+ SYSLOG ((LOG_ERR, "pam_acct_mgmt: %s",
+ pam_strerror (pamh, ret)));
+ fprintf (stderr,
+ _("%s: %s\n"),
+ Prog, pam_strerror (pamh, ret));
+ (void) pam_end (pamh, ret);
+ su_failure (caller_tty, 0 == pw->pw_uid);
+ }
+ }
+}
+#else /* !USE_PAM */
+static void check_perms_nopam (const struct passwd *pw)
+{
+ /*@observer@*/const struct spwd *spwd = NULL;
+ /*@observer@*/const char *password = pw->pw_passwd;
+ RETSIGTYPE (*oldsig) (int);
+
+ if (caller_is_root) {
+ return;
+ }
+
+ /*
+ * BSD systems only allow "wheel" to SU to root. USG systems don't,
+ * so we make this a configurable option.
+ */
+
+ /* The original Shadow 3.3.2 did this differently. Do it like BSD:
+ *
+ * - check for UID 0 instead of name "root" - there are systems with
+ * several root accounts under different names,
+ *
+ * - check the contents of /etc/group instead of the current group
+ * set (you must be listed as a member, GID 0 is not sufficient).
+ *
+ * In addition to this traditional feature, we now have complete su
+ * access control (allow, deny, no password, own password). Thanks
+ * to Chris Evans <lady0110@sable.ox.ac.uk>.
+ */
+
+ if ( (0 == pw->pw_uid)
+ && getdef_bool ("SU_WHEEL_ONLY")
+ && !iswheel (caller_name)) {
+ fprintf (stderr,
+ _("You are not authorized to su %s\n"),
+ name);
+ exit (1);
+ }
+ spwd = getspnam (name); /* !USE_PAM, no need for xgetspnam */
+#ifdef SU_ACCESS
+ if (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) == 0) {
+ if (NULL != spwd) {
+ password = spwd->sp_pwdp;
+ }
+ }
+
+ switch (check_su_auth (caller_name, name, 0 == pw->pw_uid)) {
+ case 0: /* normal su, require target user's password */
+ break;
+ case 1: /* require no password */
+ password = ""; /* XXX warning: const */
+ break;
+ case 2: /* require own password */
+ (void) puts (_("(Enter your own password)"));
+ password = caller_pass;
+ break;
+ default: /* access denied (-1) or unexpected value */
+ fprintf (stderr,
+ _("You are not authorized to su %s\n"),
+ name);
+ exit (1);
+ }
+#endif /* SU_ACCESS */
+ /*
+ * Set up a signal handler in case the user types QUIT.
+ */
+ die (0);
+ oldsig = signal (SIGQUIT, die);
+
+ /*
+ * See if the system defined authentication method is being used.
+ * The first character of an administrator defined method is an '@'
+ * character.
+ */
+ if (pw_auth (password, name, PW_SU, (char *) 0) != 0) {
+ SYSLOG (((pw->pw_uid != 0)? LOG_NOTICE : LOG_WARN,
+ "Authentication failed for %s", name));
+ fprintf(stderr, _("%s: Authentication failure\n"), Prog);
+ su_failure (caller_tty, 0 == pw->pw_uid);
+ }
+ (void) signal (SIGQUIT, oldsig);
+
+ /*
+ * Check to see if the account is expired. root gets to ignore any
+ * expired accounts, but normal users can't become a user with an
+ * expired password.
+ */
+ if (NULL != spwd) {
+ (void) expire (pw, spwd);
+ }
+
+ /*
+ * Check to see if the account permits "su". root gets to ignore any
+ * restricted accounts, but normal users can't become a user if
+ * there is a "SU" entry in the /etc/porttime file denying access to
+ * the account.
+ */
+ if (!isttytime (name, "SU", time ((time_t *) 0))) {
+ SYSLOG (((0 != pw->pw_uid) ? LOG_WARN : LOG_CRIT,
+ "SU by %s to restricted account %s",
+ caller_name, name));
+ fprintf (stderr,
+ _("%s: You are not authorized to su at that time\n"),
+ Prog);
+ su_failure (caller_tty, 0 == pw->pw_uid);
+ }
+}
+#endif /* !USE_PAM */
+
+/*
+ * check_perms - check permissions to switch to the user 'name'
+ *
+ * In case of subsystem login, the user is first authenticated in the
+ * caller's root subsystem, and then in the user's target subsystem.
+ */
+static /*@only@*/struct passwd * check_perms (void)
+{
+#ifdef USE_PAM
+ const char *tmp_name;
+ int ret;
+#endif /* !USE_PAM */
+ /*
+ * The password file entries for the user is gotten and the account
+ * validated.
+ */
+ struct passwd *pw = xgetpwnam (name);
+ if (NULL == pw) {
+ (void) fprintf (stderr,
+ _("No passwd entry for user '%s'\n"), name);
+ SYSLOG ((LOG_NOTICE, "No passwd entry for user '%s'", name));
+ su_failure (caller_tty, true);
+ }
+
+ (void) signal (SIGINT, SIG_IGN);
+ (void) signal (SIGQUIT, SIG_IGN);
+
+#ifdef USE_PAM
+ check_perms_pam (pw);
+ /* PAM authentication can request a change of account */
+ ret = pam_get_item(pamh, PAM_USER, (const void **) &tmp_name);
+ if (ret != PAM_SUCCESS) {
+ SYSLOG((LOG_ERR, "pam_get_item: internal PAM error\n"));
+ (void) fprintf (stderr,
+ "%s: Internal PAM error retrieving username\n",
+ Prog);
+ (void) pam_end (pamh, ret);
+ su_failure (caller_tty, 0 == pw->pw_uid);
+ }
+ if (strcmp (name, tmp_name) != 0) {
+ SYSLOG ((LOG_INFO,
+ "Change user from '%s' to '%s' as requested by PAM",
+ name, tmp_name));
+ strncpy (name, tmp_name, sizeof(name) - 1);
+ name[sizeof(name) - 1] = '\0';
+ pw = xgetpwnam (name);
+ if (NULL == pw) {
+ (void) fprintf (stderr,
+ _("No passwd entry for user '%s'\n"),
+ name);
+ SYSLOG ((LOG_NOTICE,
+ "No passwd entry for user '%s'", name));
+ su_failure (caller_tty, true);
+ }
+ }
+#else /* !USE_PAM */
+ check_perms_nopam (pw);
+#endif /* !USE_PAM */
+
+ (void) signal (SIGINT, SIG_DFL);
+ (void) signal (SIGQUIT, SIG_DFL);
+
+ /*
+ * Even if --shell is specified, the subsystem login test is based on
+ * the shell specified in /etc/passwd (not the one specified with
+ * --shell, which will be the one executed in the chroot later).
+ */
+ if ('*' == pw->pw_shell[0]) { /* subsystem root required */
+ subsystem (pw); /* change to the subsystem root */
+ endpwent (); /* close the old password databases */
+ endspent ();
+ pw_free (pw);
+ return check_perms (); /* authenticate in the subsystem */
+ }
+
+ return pw;
+}
+
+/*
+ * save_caller_context - save information from the call context
+ *
+ * Save the program's name (Prog), caller's UID (caller_uid /
+ * caller_is_root), name (caller_name), and password (caller_pass),
+ * the TTY (ttyp), and whether su was called from a console
+ * (is_console) for further processing and before they might change.
+ */
+static void save_caller_context (char **argv)
+{
+ struct passwd *pw = NULL;
+#ifndef USE_PAM
+#ifdef SU_ACCESS
+ const char *password = NULL;
+#endif /* SU_ACCESS */
+#endif /* !USE_PAM */
+ /*
+ * Get the program name. The program name is used as a prefix to
+ * most error messages.
+ */
+ Prog = Basename (argv[0]);
+
+ caller_uid = getuid ();
+ caller_is_root = (caller_uid == 0);
+
+ /*
+ * Get the tty name. Entries will be logged indicating that the user
+ * tried to change to the named new user from the current terminal.
+ */
+ caller_tty = ttyname (0);
+ if ((isatty (0) != 0) && (NULL != caller_tty)) {
+#ifndef USE_PAM
+ caller_on_console = console (caller_tty);
+#endif /* !USE_PAM */
+ } else {
+ /*
+ * Be more paranoid, like su from SimplePAMApps. --marekm
+ */
+ if (!caller_is_root) {
+ fprintf (stderr,
+ _("%s: must be run from a terminal\n"),
+ Prog);
+ exit (1);
+ }
+ caller_tty = "???";
+ }
+
+ /*
+ * Get the user's real name. The current UID is used to determine
+ * who has executed su. That user ID must exist.
+ */
+ pw = get_my_pwent ();
+ if (NULL == pw) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
+ (unsigned long) caller_uid));
+ su_failure (caller_tty, true); /* unknown target UID*/
+ }
+ STRFCPY (caller_name, pw->pw_name);
+
+#ifndef USE_PAM
+#ifdef SU_ACCESS
+ /*
+ * Sort out the password of user calling su, in case needed later
+ * -- chris
+ */
+ password = pw->pw_passwd;
+ if (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) == 0) {
+ const struct spwd *spwd = getspnam (caller_name);
+ if (NULL != spwd) {
+ password = spwd->sp_pwdp;
+ }
+ }
+ free (caller_pass);
+ caller_pass = xstrdup (password);
+#endif /* SU_ACCESS */
+#endif /* !USE_PAM */
+ pw_free (pw);
+}
+
+/*
+ * process_flags - Process the command line arguments
+ *
+ * process_flags() interprets the command line arguments and sets
+ * the values that the user will be created with accordingly. The
+ * values are checked for sanity.
+ */
+static void process_flags (int argc, char **argv)
+{
+ int c;
+ static struct option long_options[] = {
+ {"command", required_argument, NULL, 'c'},
+ {"help", no_argument, NULL, 'h'},
+ {"login", no_argument, NULL, 'l'},
+ {"preserve-environment", no_argument, NULL, 'p'},
+ {"shell", required_argument, NULL, 's'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "c:hlmps:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'c':
+ command = optarg;
+ break;
+ case 'h':
+ usage (E_SUCCESS);
+ break;
+ case 'l':
+ fakelogin = true;
+ break;
+ case 'm':
+ case 'p':
+ /* This will only have an effect if the target
+ * user do not have a restricted shell, or if
+ * su is called by root.
+ */
+ change_environment = false;
+ break;
+ case 's':
+ shellstr = optarg;
+ break;
+ default:
+ usage (E_USAGE); /* NOT REACHED */
+ }
+ }
+
+ if ((optind < argc) && (strcmp (argv[optind], "-") == 0)) {
+ fakelogin = true;
+ optind++;
+ if ( (optind < argc)
+ && (strcmp (argv[optind], "--") == 0)) {
+ optind++;
+ }
+ }
+
+ /*
+ * The next argument must be either a user ID, or some flag to a
+ * subshell. Pretty sticky since you can't have an argument which
+ * doesn't start with a "-" unless you specify the new user name.
+ * Any remaining arguments will be passed to the user's login shell.
+ */
+ if ((optind < argc) && ('-' != argv[optind][0])) {
+ STRFCPY (name, argv[optind++]); /* use this login id */
+ if ((optind < argc) && (strcmp (argv[optind], "--") == 0)) {
+ optind++;
+ }
+ }
+ if ('\0' == name[0]) { /* use default user */
+ struct passwd *root_pw = getpwnam ("root");
+ if ((NULL != root_pw) && (0 == root_pw->pw_uid)) {
+ (void) strcpy (name, "root");
+ } else {
+ root_pw = getpwuid (0);
+ if (NULL == root_pw) {
+ SYSLOG ((LOG_CRIT, "There is no UID 0 user."));
+ su_failure (caller_tty, true);
+ }
+ (void) strcpy (name, root_pw->pw_name);
+ }
+ }
+
+ doshell = (argc == optind); /* any arguments remaining? */
+ if (NULL != command) {
+ doshell = false;
+ }
+}
+
+static void set_environment (struct passwd *pw)
+{
+ const char *cp;
+ /*
+ * If a new login is being set up, the old environment will be
+ * ignored and a new one created later on.
+ */
+ if (change_environment && fakelogin) {
+ /*
+ * The terminal type will be left alone if it is present in
+ * the environment already.
+ */
+ cp = getenv ("TERM");
+ if (NULL != cp) {
+ addenv ("TERM", cp);
+ }
+
+ /*
+ * For some terminals COLORTERM seems to be the only way
+ * for checking for that specific terminal. For instance,
+ * gnome-terminal sets its TERM as "xterm" but its
+ * COLORTERM as "gnome-terminal". The COLORTERM variable
+ * is also of use when running GNU screen since it sets
+ * TERM to "screen" but doesn't touch COLORTERM.
+ */
+ cp = getenv ("COLORTERM");
+ if (NULL != cp) {
+ addenv ("COLORTERM", cp);
+ }
+
+#ifndef USE_PAM
+ cp = getdef_str ("ENV_TZ");
+ if (NULL != cp) {
+ addenv (('/' == *cp) ? tz (cp) : cp, NULL);
+ }
+
+ /*
+ * The clock frequency will be reset to the login value if required
+ */
+ cp = getdef_str ("ENV_HZ");
+ if (NULL != cp) {
+ addenv (cp, NULL); /* set the default $HZ, if one */
+ }
+#endif /* !USE_PAM */
+
+ /*
+ * Also leave DISPLAY and XAUTHORITY if present, else
+ * pam_xauth will not work.
+ */
+ cp = getenv ("DISPLAY");
+ if (NULL != cp) {
+ addenv ("DISPLAY", cp);
+ }
+ cp = getenv ("XAUTHORITY");
+ if (NULL != cp) {
+ addenv ("XAUTHORITY", cp);
+ }
+ } else {
+ char **envp = environ;
+ while (NULL != *envp) {
+ addenv (*envp, NULL);
+ envp++;
+ }
+ }
+
+ cp = getdef_str ((pw->pw_uid == 0) ? "ENV_SUPATH" : "ENV_PATH");
+ if (NULL == cp) {
+ addenv ((pw->pw_uid == 0) ? "PATH=/sbin:/bin:/usr/sbin:/usr/bin" : "PATH=/bin:/usr/bin", NULL);
+ } else if (strchr (cp, '=') != NULL) {
+ addenv (cp, NULL);
+ } else {
+ addenv ("PATH", cp);
+ }
+
+ if (getenv ("IFS") != NULL) { /* don't export user IFS ... */
+ addenv ("IFS= \t\n", NULL); /* ... instead, set a safe IFS */
+ }
+
+#ifdef USE_PAM
+ /* we need to setup the environment *after* pam_open_session(),
+ * else the UID is changed before stuff like pam_xauth could
+ * run, and we cannot access /etc/shadow and co
+ */
+ environ = newenvp; /* make new environment active */
+
+ if (change_environment) {
+ /* update environment with all pam set variables */
+ char **envcp = pam_getenvlist (pamh);
+ if (NULL != envcp) {
+ while (NULL != *envcp) {
+ addenv (*envcp, NULL);
+ envcp++;
+ }
+ }
+ }
+
+#else /* !USE_PAM */
+ environ = newenvp; /* make new environment active */
+#endif /* !USE_PAM */
+
+ if (change_environment) {
+ if (fakelogin) {
+ if (shellstr != pw->pw_shell) {
+ free (pw->pw_shell);
+ pw->pw_shell = xstrdup (shellstr);
+ }
+ setup_env (pw);
+ } else {
+ addenv ("HOME", pw->pw_dir);
+ addenv ("USER", pw->pw_name);
+ addenv ("LOGNAME", pw->pw_name);
+ addenv ("SHELL", shellstr);
+ }
+ }
+
+}
+
+/*
+ * su - switch user id
+ *
+ * su changes the user's ids to the values for the specified user. if
+ * no new user name is specified, "root" or UID 0 is used by default.
+ *
+ * Any additional arguments are passed to the user's shell. In
+ * particular, the argument "-c" will cause the next argument to be
+ * interpreted as a command by the common shell programs.
+ */
+int main (int argc, char **argv)
+{
+ const char *cp;
+ struct passwd *pw = NULL;
+
+#ifdef USE_PAM
+ int ret;
+#endif /* USE_PAM */
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ save_caller_context (argv);
+
+ OPENLOG ("su");
+
+ process_flags (argc, argv);
+
+ initenv ();
+
+#ifdef USE_PAM
+ ret = pam_start ("su", name, &conv, &pamh);
+ if (PAM_SUCCESS != ret) {
+ SYSLOG ((LOG_ERR, "pam_start: error %d", ret);
+ fprintf (stderr,
+ _("%s: pam_start: error %d\n"),
+ Prog, ret));
+ exit (1);
+ }
+
+ ret = pam_set_item (pamh, PAM_TTY, (const void *) caller_tty);
+ if (PAM_SUCCESS == ret) {
+ ret = pam_set_item (pamh, PAM_RUSER, (const void *) caller_name);
+ }
+ if (PAM_SUCCESS != ret) {
+ SYSLOG ((LOG_ERR, "pam_set_item: %s",
+ pam_strerror (pamh, ret)));
+ fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+ pam_end (pamh, ret);
+ exit (1);
+ }
+#endif /* USE_PAM */
+
+ pw = check_perms ();
+
+ /* If the user do not want to change the environment,
+ * use the current SHELL.
+ * (unless another shell is required by the command line)
+ */
+ if ((NULL == shellstr) && !change_environment) {
+ shellstr = getenv ("SHELL");
+ }
+
+ /* If su is not called by root, and the target user has a
+ * restricted shell, the environment must be changed and the shell
+ * must be the one specified in /etc/passwd.
+ */
+ if ( !caller_is_root
+ && restricted_shell (pw->pw_shell)) {
+ shellstr = NULL;
+ change_environment = true;
+ }
+
+ /* If the shell is not set at this time, use the shell specified
+ * in /etc/passwd.
+ */
+ if (NULL == shellstr) {
+ shellstr = pw->pw_shell;
+ }
+
+ /*
+ * Set the default shell.
+ */
+ if ((NULL == shellstr) || ('\0' == shellstr[0])) {
+ shellstr = SHELL;
+ }
+
+ sulog (caller_tty, true, caller_name, name); /* save SU information */
+#ifdef USE_SYSLOG
+ if (getdef_bool ("SYSLOG_SU_ENAB")) {
+ SYSLOG ((LOG_INFO, "+ %s %s:%s", caller_tty,
+ ('\0' != caller_name[0]) ? caller_name : "???",
+ ('\0' != name[0]) ? name : "???"));
+ }
+#endif
+
+#ifdef USE_PAM
+ /* set primary group id and supplementary groups */
+ if (setup_groups (pw) != 0) {
+ pam_end (pamh, PAM_ABORT);
+ exit (1);
+ }
+
+ /*
+ * pam_setcred() may do things like resource limits, console groups,
+ * and much more, depending on the configured modules
+ */
+ ret = pam_setcred (pamh, PAM_ESTABLISH_CRED);
+ if (PAM_SUCCESS != ret) {
+ SYSLOG ((LOG_ERR, "pam_setcred: %s", pam_strerror (pamh, ret)));
+ fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+ (void) pam_end (pamh, ret);
+ exit (1);
+ }
+
+ ret = pam_open_session (pamh, 0);
+ if (PAM_SUCCESS != ret) {
+ SYSLOG ((LOG_ERR, "pam_open_session: %s",
+ pam_strerror (pamh, ret)));
+ fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+ pam_setcred (pamh, PAM_DELETE_CRED);
+ (void) pam_end (pamh, ret);
+ exit (1);
+ }
+
+ prepare_pam_close_session ();
+
+ /* become the new user */
+ if (change_uid (pw) != 0) {
+ exit (1);
+ }
+#else /* !USE_PAM */
+ /* no limits if su from root (unless su must fake login's behavior) */
+ if (!caller_is_root || fakelogin) {
+ setup_limits (pw);
+ }
+
+ if (setup_uid_gid (pw, caller_on_console) != 0) {
+ exit (1);
+ }
+#endif /* !USE_PAM */
+
+#ifdef WITH_AUDIT
+ audit_fd = audit_open ();
+ audit_log_acct_message (audit_fd,
+ AUDIT_USER_ROLE_CHANGE,
+ NULL, /* Prog. name */
+ "su",
+ ('\0' != caller_name[0]) ? caller_name : "???",
+ AUDIT_NO_ID,
+ "localhost",
+ NULL, /* addr */
+ caller_tty,
+ 1); /* result */
+ close (audit_fd);
+#endif /* WITH_AUDIT */
+
+ set_environment (pw);
+
+ if (!doshell) {
+ /* There is no need for a controlling terminal.
+ * This avoids the callee to inject commands on
+ * the caller's tty. */
+ int err = -1;
+
+#ifdef USE_PAM
+ /* When PAM is used, we are on the child */
+ err = setsid ();
+#else
+ /* Otherwise, we cannot use setsid */
+ int fd = open ("/dev/tty", O_RDWR);
+
+ if (fd >= 0) {
+ err = ioctl (fd, TIOCNOTTY, (char *) 0);
+ (void) close (fd);
+ } else if (ENXIO == errno) {
+ /* There are no controlling terminal already */
+ err = 0;
+ }
+#endif /* USE_PAM */
+
+ if (-1 == err) {
+ (void) fprintf (stderr,
+ _("%s: Cannot drop the controlling terminal\n"),
+ Prog);
+ exit (1);
+ }
+ }
+
+ /*
+ * PAM_DATA_SILENT is not supported by some modules, and
+ * there is no strong need to clean up the process space's
+ * memory since we will either call exec or exit.
+ pam_end (pamh, PAM_SUCCESS | PAM_DATA_SILENT);
+ */
+
+ endpwent ();
+ endspent ();
+ /*
+ * This is a workaround for Linux libc bug/feature (?) - the
+ * /dev/log file descriptor is open without the close-on-exec flag
+ * and used to be passed to the new shell. There is "fcntl(LogFile,
+ * F_SETFD, 1)" in libc/misc/syslog.c, but it is commented out (at
+ * least in 5.4.33). Why? --marekm
+ */
+ closelog ();
+
+ /*
+ * See if the user has extra arguments on the command line. In that
+ * case they will be provided to the new user's shell as arguments.
+ */
+ if (fakelogin) {
+ char *arg0;
+
+ cp = getdef_str ("SU_NAME");
+ if (NULL == cp) {
+ cp = Basename (shellstr);
+ }
+
+ arg0 = xmalloc (strlen (cp) + 2);
+ arg0[0] = '-';
+ strcpy (arg0 + 1, cp);
+ cp = arg0;
+ } else {
+ cp = Basename (shellstr);
+ }
+
+ if (!doshell) {
+ int err;
+ /* Position argv to the remaining arguments */
+ argv += optind;
+ if (NULL != command) {
+ argv -= 2;
+ argv[0] = "-c";
+ argv[1] = command;
+ }
+ /*
+ * Use the shell and create an argv
+ * with the rest of the command line included.
+ */
+ argv[-1] = cp;
+ execve_shell (shellstr, &argv[-1], environ);
+ err = errno;
+ (void) fprintf (stderr,
+ _("Cannot execute %s\n"), shellstr);
+ errno = err;
+ } else {
+ (void) shell (shellstr, cp, environ);
+ }
+
+ pw_free (pw);
+
+ return (errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
+}
+
diff --git a/src/suauth.c b/src/suauth.c
new file mode 100644
index 0000000..a5bbe4c
--- /dev/null
+++ b/src/suauth.c
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 1990 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2002 - 2005, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2008, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include "defines.h"
+#include "prototypes.h"
+
+#ifndef SUAUTHFILE
+#define SUAUTHFILE "/etc/suauth"
+#endif
+
+#define NOACTION 0
+#define NOPWORD 1
+#define DENY -1
+#define OWNPWORD 2
+
+#ifdef SU_ACCESS
+
+/* Really, I could do with a few const char's here defining all the
+ * strings output to the user or the syslog. -- chris
+ */
+static int applies (const char *, char *);
+
+static int isgrp (const char *, const char *);
+
+static int lines = 0;
+
+
+int check_su_auth (const char *actual_id,
+ const char *wanted_id,
+ bool su_to_root)
+{
+ int posn, endline;
+ const char field[] = ":";
+ FILE *authfile_fd;
+ char temp[1024];
+ char *to_users;
+ char *from_users;
+ char *action;
+
+ if (!(authfile_fd = fopen (SUAUTHFILE, "r"))) {
+ int err = errno;
+ /*
+ * If the file doesn't exist - default to the standard su
+ * behaviour (no access control). If open fails for some
+ * other reason - maybe someone is trying to fool us with
+ * file descriptors limit etc., so deny access. --marekm
+ */
+ if (ENOENT == err) {
+ return NOACTION;
+ }
+ SYSLOG ((LOG_ERR,
+ "could not open/read config file '%s': %s\n",
+ SUAUTHFILE, strerror (err)));
+ return DENY;
+ }
+
+ while (fgets (temp, sizeof (temp), authfile_fd) != NULL) {
+ lines++;
+
+ if (temp[endline = strlen (temp) - 1] != '\n') {
+ SYSLOG ((LOG_ERR,
+ "%s, line %d: line too long or missing newline",
+ SUAUTHFILE, lines));
+ continue;
+ }
+
+ while (endline > 0 && (temp[endline - 1] == ' '
+ || temp[endline - 1] == '\t'
+ || temp[endline - 1] == '\n'))
+ endline--;
+ temp[endline] = '\0';
+
+ posn = 0;
+ while (temp[posn] == ' ' || temp[posn] == '\t')
+ posn++;
+
+ if (temp[posn] == '\n' || temp[posn] == '#'
+ || temp[posn] == '\0') {
+ continue;
+ }
+ if (!(to_users = strtok (temp + posn, field))
+ || !(from_users = strtok ((char *) NULL, field))
+ || !(action = strtok ((char *) NULL, field))
+ || strtok ((char *) NULL, field)) {
+ SYSLOG ((LOG_ERR,
+ "%s, line %d. Bad number of fields.\n",
+ SUAUTHFILE, lines));
+ continue;
+ }
+
+ if (!applies (wanted_id, to_users))
+ continue;
+ if (!applies (actual_id, from_users))
+ continue;
+ if (!strcmp (action, "DENY")) {
+ SYSLOG ((su_to_root ? LOG_WARN : LOG_NOTICE,
+ "DENIED su from '%s' to '%s' (%s)\n",
+ actual_id, wanted_id, SUAUTHFILE));
+ fputs (_("Access to su to that account DENIED.\n"),
+ stderr);
+ fclose (authfile_fd);
+ return DENY;
+ } else if (!strcmp (action, "NOPASS")) {
+ SYSLOG ((su_to_root ? LOG_NOTICE : LOG_INFO,
+ "NO password asked for su from '%s' to '%s' (%s)\n",
+ actual_id, wanted_id, SUAUTHFILE));
+ fputs (_("Password authentication bypassed.\n"),stderr);
+ fclose (authfile_fd);
+ return NOPWORD;
+ } else if (!strcmp (action, "OWNPASS")) {
+ SYSLOG ((su_to_root ? LOG_NOTICE : LOG_INFO,
+ "su from '%s' to '%s': asking for user's own password (%s)\n",
+ actual_id, wanted_id, SUAUTHFILE));
+ fputs (_("Please enter your OWN password as authentication.\n"),
+ stderr);
+ fclose (authfile_fd);
+ return OWNPWORD;
+ } else {
+ SYSLOG ((LOG_ERR,
+ "%s, line %d: unrecognised action!\n",
+ SUAUTHFILE, lines));
+ }
+ }
+ fclose (authfile_fd);
+ return NOACTION;
+}
+
+static int applies (const char *single, char *list)
+{
+ const char split[] = ", ";
+ char *tok;
+
+ int state = 0;
+
+ for (tok = strtok (list, split); tok != NULL;
+ tok = strtok (NULL, split)) {
+
+ if (!strcmp (tok, "ALL")) {
+ if (state != 0) {
+ SYSLOG ((LOG_ERR,
+ "%s, line %d: ALL in bad place\n",
+ SUAUTHFILE, lines));
+ return 0;
+ }
+ state = 1;
+ } else if (!strcmp (tok, "EXCEPT")) {
+ if (state != 1) {
+ SYSLOG ((LOG_ERR,
+ "%s, line %d: EXCEPT in bas place\n",
+ SUAUTHFILE, lines));
+ return 0;
+ }
+ state = 2;
+ } else if (!strcmp (tok, "GROUP")) {
+ if ((state != 0) && (state != 2)) {
+ SYSLOG ((LOG_ERR,
+ "%s, line %d: GROUP in bad place\n",
+ SUAUTHFILE, lines));
+ return 0;
+ }
+ state = (state == 0) ? 3 : 4;
+ } else {
+ switch (state) {
+ case 0: /* No control words yet */
+ if (!strcmp (tok, single))
+ return 1;
+ break;
+ case 1: /* An all */
+ SYSLOG ((LOG_ERR,
+ "%s, line %d: expect another token after ALL\n",
+ SUAUTHFILE, lines));
+ return 0;
+ case 2: /* All except */
+ if (!strcmp (tok, single))
+ return 0;
+ break;
+ case 3: /* Group */
+ if (isgrp (single, tok))
+ return 1;
+ break;
+ case 4: /* All except group */
+ if (isgrp (single, tok))
+ return 0;
+ /* FALL THRU */
+ }
+ }
+ }
+ if ((state != 0) && (state != 3))
+ return 1;
+ return 0;
+}
+
+static int isgrp (const char *name, const char *group)
+{
+ struct group *grp;
+
+ grp = getgrnam (group); /* local, no need for xgetgrnam */
+
+ if (!grp || !grp->gr_mem)
+ return 0;
+
+ return is_on_list (grp->gr_mem, name);
+}
+#endif /* SU_ACCESS */
diff --git a/src/sulogin.c b/src/sulogin.c
new file mode 100644
index 0000000..4264099
--- /dev/null
+++ b/src/sulogin.c
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 1989 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2002 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2010, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include "defines.h"
+#include "getdef.h"
+#include "prototypes.h"
+#include "pwauth.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static char name[BUFSIZ];
+static char pass[BUFSIZ];
+
+static struct passwd pwent;
+
+extern char **newenvp;
+extern size_t newenvc;
+
+extern char **environ;
+
+#ifndef ALARM
+#define ALARM 60
+#endif
+
+/* local function prototypes */
+static RETSIGTYPE catch_signals (int);
+
+static RETSIGTYPE catch_signals (unused int sig)
+{
+ _exit (1);
+}
+
+/*
+ * syslogd is usually not running at the time when sulogin is typically
+ * called, cluttering the screen with unnecessary messages. Suggested by
+ * Ivan Nejgebauer <ian@unsux.ns.ac.yu>. --marekm
+ */
+#undef USE_SYSLOG
+
+ /*ARGSUSED*/ int main (int argc, char **argv)
+{
+#ifndef USE_PAM
+ const char *env;
+#endif /* !USE_PAM */
+ char **envp = environ;
+ TERMIO termio;
+ int err = 0;
+
+#ifdef USE_TERMIO
+ ioctl (0, TCGETA, &termio);
+ termio.c_iflag |= (ICRNL | IXON);
+ termio.c_oflag |= (OPOST | ONLCR);
+ termio.c_cflag |= (CREAD);
+ termio.c_lflag |= (ISIG | ICANON | ECHO | ECHOE | ECHOK);
+ ioctl (0, TCSETAF, &termio);
+#endif
+#ifdef USE_TERMIOS
+ tcgetattr (0, &termio);
+ termio.c_iflag |= (ICRNL | IXON);
+ termio.c_oflag |= (CREAD);
+ termio.c_lflag |= (ECHO | ECHOE | ECHOK | ICANON | ISIG);
+ tcsetattr (0, TCSANOW, &termio);
+#endif
+
+ Prog = Basename (argv[0]);
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+#ifdef USE_SYSLOG
+ OPENLOG ("sulogin");
+#endif
+ initenv ();
+ if (argc > 1) {
+ close (0);
+ close (1);
+ close (2);
+
+ if (open (argv[1], O_RDWR) >= 0) {
+ dup (0);
+ dup (0);
+ } else {
+#ifdef USE_SYSLOG
+ SYSLOG (LOG_WARN, "cannot open %s\n", argv[1]);
+ closelog ();
+#endif
+ exit (1);
+ }
+ }
+ if (access (PASSWD_FILE, F_OK) == -1) { /* must be a password file! */
+ (void) puts (_("No password file"));
+#ifdef USE_SYSLOG
+ SYSLOG (LOG_WARN, "No password file\n");
+ closelog ();
+#endif
+ exit (1);
+ }
+#if !defined(DEBUG) && defined(SULOGIN_ONLY_INIT)
+ if (getppid () != 1) { /* parent must be INIT */
+#ifdef USE_SYSLOG
+ SYSLOG (LOG_WARN, "Pid == %d, not 1\n", getppid ());
+ closelog ();
+#endif
+ exit (1);
+ }
+#endif
+ if ((isatty (0) == 0) || (isatty (1) == 0) || (isatty (2) == 0)) {
+#ifdef USE_SYSLOG
+ closelog ();
+#endif
+ exit (1); /* must be a terminal */
+ }
+ /* If we were init, we need to start a new session */
+ if (getppid() == 1) {
+ setsid();
+ if (ioctl(0, TIOCSCTTY, 1) != 0) {
+ (void) fputs (_("TIOCSCTTY failed"), stderr);
+ }
+ }
+ while (NULL != *envp) { /* add inherited environment, */
+ addenv (*envp, NULL); /* some variables change later */
+ envp++;
+ }
+
+#ifndef USE_PAM
+ env = getdef_str ("ENV_TZ");
+ if (NULL != env) {
+ addenv (('/' == *env) ? tz (env) : env, NULL);
+ }
+ env = getdef_str ("ENV_HZ");
+ if (NULL != env) {
+ addenv (env, NULL); /* set the default $HZ, if one */
+ }
+#endif /* !USE_PAM */
+
+ (void) strcpy (name, "root"); /* KLUDGE!!! */
+
+ (void) signal (SIGALRM, catch_signals); /* exit if the timer expires */
+ (void) alarm (ALARM); /* only wait so long ... */
+
+ while (true) { /* repeatedly get login/password pairs */
+ char *cp;
+ pw_entry (name, &pwent); /* get entry from password file */
+ if (pwent.pw_name == (char *) 0) {
+ /*
+ * Fail secure
+ */
+ (void) puts (_("No password entry for 'root'"));
+#ifdef USE_SYSLOG
+ SYSLOG (LOG_WARN, "No password entry for 'root'\n");
+ closelog ();
+#endif
+ exit (1);
+ }
+
+ /*
+ * Here we prompt for the root password, or if no password
+ * is given we just exit.
+ */
+
+ /* get a password for root */
+ cp = getpass (_(
+"\n"
+"Type control-d to proceed with normal startup,\n"
+"(or give root password for system maintenance):"));
+ /*
+ * XXX - can't enter single user mode if root password is
+ * empty. I think this doesn't happen very often :-). But
+ * it will work with standard getpass() (no NULL on EOF).
+ * --marekm
+ */
+ if ((NULL == cp) || ('\0' == *cp)) {
+#ifdef USE_SYSLOG
+ SYSLOG (LOG_INFO, "Normal startup\n");
+ closelog ();
+#endif
+ (void) puts ("");
+#ifdef TELINIT
+ execl (PATH_TELINIT, "telinit", RUNLEVEL, (char *) 0);
+#endif
+ exit (0);
+ } else {
+ STRFCPY (pass, cp);
+ strzero (cp);
+ }
+ if (valid (pass, &pwent)) { /* check encrypted passwords ... */
+ break; /* ... encrypted passwords matched */
+ }
+
+#ifdef USE_SYSLOG
+ SYSLOG (LOG_WARN, "Incorrect root password\n");
+#endif
+ sleep (2);
+ (void) puts (_("Login incorrect"));
+ }
+ strzero (pass);
+ (void) alarm (0);
+ (void) signal (SIGALRM, SIG_DFL);
+ environ = newenvp; /* make new environment active */
+
+ (void) puts (_("Entering System Maintenance Mode"));
+#ifdef USE_SYSLOG
+ SYSLOG (LOG_INFO, "System Maintenance Mode\n");
+#endif
+
+#ifdef USE_SYSLOG
+ closelog ();
+#endif
+ /* exec the shell finally. */
+ err = shell (pwent.pw_shell, (char *) 0, environ);
+
+ return ((err == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
+}
+
diff --git a/src/useradd.c b/src/useradd.c
new file mode 100644
index 0000000..0e0fa1f
--- /dev/null
+++ b/src/useradd.c
@@ -0,0 +1,2315 @@
+/*
+ * Copyright (c) 1991 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2012, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <lastlog.h>
+#include <pwd.h>
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+#include "chkname.h"
+#include "defines.h"
+#include "faillog.h"
+#include "getdef.h"
+#include "groupio.h"
+#include "nscd.h"
+#include "prototypes.h"
+#include "pwauth.h"
+#include "pwio.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+#include "shadowio.h"
+#ifdef ENABLE_SUBIDS
+#include "subordinateio.h"
+#endif /* ENABLE_SUBIDS */
+#ifdef WITH_TCB
+#include "tcbfuncs.h"
+#endif
+
+#ifndef SKEL_DIR
+#define SKEL_DIR "/etc/skel"
+#endif
+#ifndef USER_DEFAULTS_FILE
+#define USER_DEFAULTS_FILE "/etc/default/useradd"
+#define NEW_USER_FILE "/etc/default/nuaddXXXXXX"
+#endif
+/*
+ * Needed for MkLinux DR1/2/2.1 - J.
+ */
+#ifndef LASTLOG_FILE
+#define LASTLOG_FILE "/var/log/lastlog"
+#endif
+/*
+ * Global variables
+ */
+const char *Prog;
+
+/*
+ * These defaults are used if there is no defaults file.
+ */
+static gid_t def_group = 100;
+static const char *def_gname = "other";
+static const char *def_home = "/home";
+static const char *def_shell = "";
+static const char *def_template = SKEL_DIR;
+static const char *def_create_mail_spool = "no";
+
+static long def_inactive = -1;
+static const char *def_expire = "";
+
+#define VALID(s) (strcspn (s, ":\n") == strlen (s))
+
+static const char *user_name = "";
+static const char *user_pass = "!";
+static uid_t user_id;
+static gid_t user_gid;
+static const char *user_comment = "";
+static const char *user_home = "";
+static const char *user_shell = "";
+static const char *create_mail_spool = "";
+#ifdef WITH_SELINUX
+static /*@notnull@*/const char *user_selinux = "";
+#endif /* WITH_SELINUX */
+
+static long user_expire = -1;
+static bool is_shadow_pwd;
+
+#ifdef SHADOWGRP
+static bool is_shadow_grp;
+static bool sgr_locked = false;
+#endif
+#ifdef ENABLE_SUBIDS
+static bool is_sub_uid = false;
+static bool is_sub_gid = false;
+static bool sub_uid_locked = false;
+static bool sub_gid_locked = false;
+static uid_t sub_uid_start; /* New subordinate uid range */
+static unsigned long sub_uid_count;
+static gid_t sub_gid_start; /* New subordinate gid range */
+static unsigned long sub_gid_count;
+#endif /* ENABLE_SUBIDS */
+static bool pw_locked = false;
+static bool gr_locked = false;
+static bool spw_locked = false;
+static char **user_groups; /* NULL-terminated list */
+static long sys_ngroups;
+static bool do_grp_update = false; /* group files need to be updated */
+
+static bool
+ bflg = false, /* new default root of home directory */
+ cflg = false, /* comment (GECOS) field for new account */
+ dflg = false, /* home directory for new account */
+ Dflg = false, /* set/show new user default values */
+ eflg = false, /* days since 1970-01-01 when account is locked */
+ fflg = false, /* days until account with expired password is locked */
+ gflg = false, /* primary group ID for new account */
+ Gflg = false, /* secondary group set for new account */
+ kflg = false, /* specify a directory to fill new user directory */
+ lflg = false, /* do not add user to lastlog/faillog databases */
+ mflg = false, /* create user's home directory if it doesn't exist */
+ Mflg = false, /* do not create user's home directory even if CREATE_HOME is set */
+ Nflg = false, /* do not create a group having the same name as the user, but add the user to def_group (or the group specified with -g) */
+ oflg = false, /* permit non-unique user ID to be specified with -u */
+ rflg = false, /* create a system account */
+ sflg = false, /* shell program for new account */
+ uflg = false, /* specify user ID for new account */
+ Uflg = false; /* create a group having the same name as the user */
+
+#ifdef WITH_SELINUX
+#define Zflg ('\0' != *user_selinux)
+#endif /* WITH_SELINUX */
+
+static bool home_added = false;
+
+/*
+ * exit status values
+ */
+/*@-exitarg@*/
+#define E_SUCCESS 0 /* success */
+#define E_PW_UPDATE 1 /* can't update password file */
+#define E_USAGE 2 /* invalid command syntax */
+#define E_BAD_ARG 3 /* invalid argument to option */
+#define E_UID_IN_USE 4 /* UID already in use (and no -o) */
+#define E_NOTFOUND 6 /* specified group doesn't exist */
+#define E_NAME_IN_USE 9 /* username already in use */
+#define E_GRP_UPDATE 10 /* can't update group file */
+#define E_HOMEDIR 12 /* can't create home directory */
+#define E_SE_UPDATE 14 /* can't update SELinux user mapping */
+#ifdef ENABLE_SUBIDS
+#define E_SUB_UID_UPDATE 16 /* can't update the subordinate uid file */
+#define E_SUB_GID_UPDATE 18 /* can't update the subordinate gid file */
+#endif /* ENABLE_SUBIDS */
+
+#define DGROUP "GROUP="
+#define DHOME "HOME="
+#define DSHELL "SHELL="
+#define DINACT "INACTIVE="
+#define DEXPIRE "EXPIRE="
+#define DSKEL "SKEL="
+#define DCREATE_MAIL_SPOOL "CREATE_MAIL_SPOOL="
+
+/* local function prototypes */
+static void fail_exit (int);
+static void get_defaults (void);
+static void show_defaults (void);
+static int set_defaults (void);
+static int get_groups (char *);
+static void usage (int status);
+static void new_pwent (struct passwd *);
+
+static long scale_age (long);
+static void new_spent (struct spwd *);
+static void grp_update (void);
+
+static void process_flags (int argc, char **argv);
+static void close_files (void);
+static void open_files (void);
+static void open_shadow (void);
+static void faillog_reset (uid_t);
+static void lastlog_reset (uid_t);
+static void tallylog_reset (char *);
+static void usr_update (void);
+static void create_home (void);
+static void create_mail (void);
+
+/*
+ * fail_exit - undo as much as possible
+ */
+static void fail_exit (int code)
+{
+ if (home_added) {
+ if (rmdir (user_home) != 0) {
+ fprintf (stderr,
+ _("%s: %s was created, but could not be removed\n"),
+ Prog, user_home);
+ SYSLOG ((LOG_ERR, "failed to remove %s", user_home));
+ }
+ }
+
+ if (spw_locked) {
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "unlocking shadow file",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ /* continue */
+ }
+ }
+ if (pw_locked) {
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "unlocking passwd file",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ /* continue */
+ }
+ }
+ if (gr_locked) {
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "unlocking group file",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ /* continue */
+ }
+ }
+#ifdef SHADOWGRP
+ if (sgr_locked) {
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "unlocking gshadow file",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ /* continue */
+ }
+ }
+#endif
+#ifdef ENABLE_SUBIDS
+ if (sub_uid_locked) {
+ if (sub_uid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ()));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "unlocking subordinate user file",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ /* continue */
+ }
+ }
+ if (sub_gid_locked) {
+ if (sub_gid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ()));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "unlocking subordinate group file",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ /* continue */
+ }
+ }
+#endif /* ENABLE_SUBIDS */
+
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding user",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ SYSLOG ((LOG_INFO, "failed adding user '%s', data deleted", user_name));
+ exit (code);
+}
+
+#define MATCH(x,y) (strncmp((x),(y),strlen(y)) == 0)
+
+/*
+ * get_defaults - read the defaults file
+ *
+ * get_defaults() reads the defaults file for this command. It sets the
+ * various values from the file, or uses built-in default values if the
+ * file does not exist.
+ */
+static void get_defaults (void)
+{
+ FILE *fp;
+ char buf[1024];
+ char *cp;
+
+ /*
+ * Open the defaults file for reading.
+ */
+
+ fp = fopen (USER_DEFAULTS_FILE, "r");
+ if (NULL == fp) {
+ return;
+ }
+
+ /*
+ * Read the file a line at a time. Only the lines that have relevant
+ * values are used, everything else can be ignored.
+ */
+ while (fgets (buf, (int) sizeof buf, fp) == buf) {
+ cp = strrchr (buf, '\n');
+ if (NULL != cp) {
+ *cp = '\0';
+ }
+
+ cp = strchr (buf, '=');
+ if (NULL == cp) {
+ continue;
+ }
+
+ cp++;
+
+ /*
+ * Primary GROUP identifier
+ */
+ if (MATCH (buf, DGROUP)) {
+ const struct group *grp = getgr_nam_gid (cp);
+ if (NULL == grp) {
+ fprintf (stderr,
+ _("%s: group '%s' does not exist\n"),
+ Prog, cp);
+ fprintf (stderr,
+ _("%s: the %s configuration in %s will be ignored\n"),
+ Prog, DGROUP, USER_DEFAULTS_FILE);
+ } else {
+ def_group = grp->gr_gid;
+ def_gname = xstrdup (grp->gr_name);
+ }
+ }
+
+ /*
+ * Default HOME filesystem
+ */
+ else if (MATCH (buf, DHOME)) {
+ def_home = xstrdup (cp);
+ }
+
+ /*
+ * Default Login Shell command
+ */
+ else if (MATCH (buf, DSHELL)) {
+ def_shell = xstrdup (cp);
+ }
+
+ /*
+ * Default Password Inactive value
+ */
+ else if (MATCH (buf, DINACT)) {
+ if ( (getlong (cp, &def_inactive) == 0)
+ || (def_inactive < -1)) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, cp);
+ fprintf (stderr,
+ _("%s: the %s configuration in %s will be ignored\n"),
+ Prog, DINACT, USER_DEFAULTS_FILE);
+ def_inactive = -1;
+ }
+ }
+
+ /*
+ * Default account expiration date
+ */
+ else if (MATCH (buf, DEXPIRE)) {
+ def_expire = xstrdup (cp);
+ }
+
+ /*
+ * Default Skeleton information
+ */
+ else if (MATCH (buf, DSKEL)) {
+ if ('\0' == *cp) {
+ cp = SKEL_DIR; /* XXX warning: const */
+ }
+
+ def_template = xstrdup (cp);
+ }
+
+ /*
+ * Create by default user mail spool or not ?
+ */
+ else if (MATCH (buf, DCREATE_MAIL_SPOOL)) {
+ if (*cp == '\0') {
+ cp = "no"; /* XXX warning: const */
+ }
+
+ def_create_mail_spool = xstrdup (cp);
+ }
+ }
+ (void) fclose (fp);
+}
+
+/*
+ * show_defaults - show the contents of the defaults file
+ *
+ * show_defaults() displays the values that are used from the default
+ * file and the built-in values.
+ */
+static void show_defaults (void)
+{
+ printf ("GROUP=%u\n", (unsigned int) def_group);
+ printf ("HOME=%s\n", def_home);
+ printf ("INACTIVE=%ld\n", def_inactive);
+ printf ("EXPIRE=%s\n", def_expire);
+ printf ("SHELL=%s\n", def_shell);
+ printf ("SKEL=%s\n", def_template);
+ printf ("CREATE_MAIL_SPOOL=%s\n", def_create_mail_spool);
+}
+
+/*
+ * set_defaults - write new defaults file
+ *
+ * set_defaults() re-writes the defaults file using the values that
+ * are currently set. Duplicated lines are pruned, missing lines are
+ * added, and unrecognized lines are copied as is.
+ */
+static int set_defaults (void)
+{
+ FILE *ifp;
+ FILE *ofp;
+ char buf[1024];
+ static char new_file[] = NEW_USER_FILE;
+ char *cp;
+ int ofd;
+ int wlen;
+ bool out_group = false;
+ bool out_home = false;
+ bool out_inactive = false;
+ bool out_expire = false;
+ bool out_shell = false;
+ bool out_skel = false;
+ bool out_create_mail_spool = false;
+
+ /*
+ * Create a temporary file to copy the new output to.
+ */
+ ofd = mkstemp (new_file);
+ if (-1 == ofd) {
+ fprintf (stderr,
+ _("%s: cannot create new defaults file\n"),
+ Prog);
+ return -1;
+ }
+
+ ofp = fdopen (ofd, "w");
+ if (NULL == ofp) {
+ fprintf (stderr,
+ _("%s: cannot open new defaults file\n"),
+ Prog);
+ return -1;
+ }
+
+ /*
+ * Open the existing defaults file and copy the lines to the
+ * temporary file, using any new values. Each line is checked
+ * to insure that it is not output more than once.
+ */
+ ifp = fopen (USER_DEFAULTS_FILE, "r");
+ if (NULL == ifp) {
+ fprintf (ofp, "# useradd defaults file\n");
+ goto skip;
+ }
+
+ while (fgets (buf, (int) sizeof buf, ifp) == buf) {
+ cp = strrchr (buf, '\n');
+ if (NULL != cp) {
+ *cp = '\0';
+ } else {
+ /* A line which does not end with \n is only valid
+ * at the end of the file.
+ */
+ if (feof (ifp) == 0) {
+ fprintf (stderr,
+ _("%s: line too long in %s: %s..."),
+ Prog, USER_DEFAULTS_FILE, buf);
+ (void) fclose (ifp);
+ return -1;
+ }
+ }
+
+ if (!out_group && MATCH (buf, DGROUP)) {
+ fprintf (ofp, DGROUP "%u\n", (unsigned int) def_group);
+ out_group = true;
+ } else if (!out_home && MATCH (buf, DHOME)) {
+ fprintf (ofp, DHOME "%s\n", def_home);
+ out_home = true;
+ } else if (!out_inactive && MATCH (buf, DINACT)) {
+ fprintf (ofp, DINACT "%ld\n", def_inactive);
+ out_inactive = true;
+ } else if (!out_expire && MATCH (buf, DEXPIRE)) {
+ fprintf (ofp, DEXPIRE "%s\n", def_expire);
+ out_expire = true;
+ } else if (!out_shell && MATCH (buf, DSHELL)) {
+ fprintf (ofp, DSHELL "%s\n", def_shell);
+ out_shell = true;
+ } else if (!out_skel && MATCH (buf, DSKEL)) {
+ fprintf (ofp, DSKEL "%s\n", def_template);
+ out_skel = true;
+ } else if (!out_create_mail_spool
+ && MATCH (buf, DCREATE_MAIL_SPOOL)) {
+ fprintf (ofp,
+ DCREATE_MAIL_SPOOL "%s\n",
+ def_create_mail_spool);
+ out_create_mail_spool = true;
+ } else
+ fprintf (ofp, "%s\n", buf);
+ }
+ (void) fclose (ifp);
+
+ skip:
+ /*
+ * Check each line to insure that every line was output. This
+ * causes new values to be added to a file which did not previously
+ * have an entry for that value.
+ */
+ if (!out_group)
+ fprintf (ofp, DGROUP "%u\n", (unsigned int) def_group);
+ if (!out_home)
+ fprintf (ofp, DHOME "%s\n", def_home);
+ if (!out_inactive)
+ fprintf (ofp, DINACT "%ld\n", def_inactive);
+ if (!out_expire)
+ fprintf (ofp, DEXPIRE "%s\n", def_expire);
+ if (!out_shell)
+ fprintf (ofp, DSHELL "%s\n", def_shell);
+ if (!out_skel)
+ fprintf (ofp, DSKEL "%s\n", def_template);
+
+ if (!out_create_mail_spool)
+ fprintf (ofp, DCREATE_MAIL_SPOOL "%s\n", def_create_mail_spool);
+
+ /*
+ * Flush and close the file. Check for errors to make certain
+ * the new file is intact.
+ */
+ (void) fflush (ofp);
+ if ( (ferror (ofp) != 0)
+ || (fsync (fileno (ofp)) != 0)
+ || (fclose (ofp) != 0)) {
+ unlink (new_file);
+ return -1;
+ }
+
+ /*
+ * Rename the current default file to its backup name.
+ */
+ wlen = snprintf (buf, sizeof buf, "%s-", USER_DEFAULTS_FILE);
+ assert (wlen < (int) sizeof buf);
+ unlink (buf);
+ if ((link (USER_DEFAULTS_FILE, buf) != 0) && (ENOENT != errno)) {
+ int err = errno;
+ fprintf (stderr,
+ _("%s: Cannot create backup file (%s): %s\n"),
+ Prog, buf, strerror (err));
+ unlink (new_file);
+ return -1;
+ }
+
+ /*
+ * Rename the new default file to its correct name.
+ */
+ if (rename (new_file, USER_DEFAULTS_FILE) != 0) {
+ int err = errno;
+ fprintf (stderr,
+ _("%s: rename: %s: %s\n"),
+ Prog, new_file, strerror (err));
+ return -1;
+ }
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USYS_CONFIG, Prog,
+ "changing useradd defaults",
+ NULL, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ SYSLOG ((LOG_INFO,
+ "useradd defaults: GROUP=%u, HOME=%s, SHELL=%s, INACTIVE=%ld, "
+ "EXPIRE=%s, SKEL=%s, CREATE_MAIL_SPOOL=%s",
+ (unsigned int) def_group, def_home, def_shell,
+ def_inactive, def_expire, def_template,
+ def_create_mail_spool));
+ return 0;
+}
+
+/*
+ * get_groups - convert a list of group names to an array of group IDs
+ *
+ * get_groups() takes a comma-separated list of group names and
+ * converts it to a NULL-terminated array. Any unknown group
+ * names are reported as errors.
+ */
+static int get_groups (char *list)
+{
+ char *cp;
+ const struct group *grp;
+ int errors = 0;
+ int ngroups = 0;
+
+ if ('\0' == *list) {
+ return 0;
+ }
+
+ /*
+ * So long as there is some data to be converted, strip off
+ * each name and look it up. A mix of numerical and string
+ * values for group identifiers is permitted.
+ */
+ do {
+ /*
+ * Strip off a single name from the list
+ */
+ cp = strchr (list, ',');
+ if (NULL != cp) {
+ *cp++ = '\0';
+ }
+
+ /*
+ * Names starting with digits are treated as numerical
+ * GID values, otherwise the string is looked up as is.
+ */
+ grp = getgr_nam_gid (list);
+
+ /*
+ * There must be a match, either by GID value or by
+ * string name.
+ * FIXME: It should exist according to gr_locate,
+ * otherwise, we can't change its members
+ */
+ if (NULL == grp) {
+ fprintf (stderr,
+ _("%s: group '%s' does not exist\n"),
+ Prog, list);
+ errors++;
+ }
+ list = cp;
+
+ /*
+ * If the group doesn't exist, don't dump core...
+ * Instead, try the next one. --marekm
+ */
+ if (NULL == grp) {
+ continue;
+ }
+
+#ifdef USE_NIS
+ /*
+ * Don't add this group if they are an NIS group. Tell
+ * the user to go to the server for this group.
+ */
+ if (__isgrNIS ()) {
+ fprintf (stderr,
+ _("%s: group '%s' is a NIS group.\n"),
+ Prog, grp->gr_name);
+ continue;
+ }
+#endif
+
+ if (ngroups == sys_ngroups) {
+ fprintf (stderr,
+ _("%s: too many groups specified (max %d).\n"),
+ Prog, ngroups);
+ break;
+ }
+
+ /*
+ * Add the group name to the user's list of groups.
+ */
+ user_groups[ngroups++] = xstrdup (grp->gr_name);
+ } while (NULL != list);
+
+ user_groups[ngroups] = (char *) 0;
+
+ /*
+ * Any errors in finding group names are fatal
+ */
+ if (0 != errors) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * usage - display usage message and exit
+ */
+static void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options] LOGIN\n"
+ " %s -D\n"
+ " %s -D [options]\n"
+ "\n"
+ "Options:\n"),
+ Prog, Prog, Prog);
+ (void) fputs (_(" -b, --base-dir BASE_DIR base directory for the home directory of the\n"
+ " new account\n"), usageout);
+ (void) fputs (_(" -c, --comment COMMENT GECOS field of the new account\n"), usageout);
+ (void) fputs (_(" -d, --home-dir HOME_DIR home directory of the new account\n"), usageout);
+ (void) fputs (_(" -D, --defaults print or change default useradd configuration\n"), usageout);
+ (void) fputs (_(" -e, --expiredate EXPIRE_DATE expiration date of the new account\n"), usageout);
+ (void) fputs (_(" -f, --inactive INACTIVE password inactivity period of the new account\n"), usageout);
+ (void) fputs (_(" -g, --gid GROUP name or ID of the primary group of the new\n"
+ " account\n"), usageout);
+ (void) fputs (_(" -G, --groups GROUPS list of supplementary groups of the new\n"
+ " account\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -k, --skel SKEL_DIR use this alternative skeleton directory\n"), usageout);
+ (void) fputs (_(" -K, --key KEY=VALUE override /etc/login.defs defaults\n"), usageout);
+ (void) fputs (_(" -l, --no-log-init do not add the user to the lastlog and\n"
+ " faillog databases\n"), usageout);
+ (void) fputs (_(" -m, --create-home create the user's home directory\n"), usageout);
+ (void) fputs (_(" -M, --no-create-home do not create the user's home directory\n"), usageout);
+ (void) fputs (_(" -N, --no-user-group do not create a group with the same name as\n"
+ " the user\n"), usageout);
+ (void) fputs (_(" -o, --non-unique allow to create users with duplicate\n"
+ " (non-unique) UID\n"), usageout);
+ (void) fputs (_(" -p, --password PASSWORD encrypted password of the new account\n"), usageout);
+ (void) fputs (_(" -r, --system create a system account\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs (_(" -s, --shell SHELL login shell of the new account\n"), usageout);
+ (void) fputs (_(" -u, --uid UID user ID of the new account\n"), usageout);
+ (void) fputs (_(" -U, --user-group create a group with the same name as the user\n"), usageout);
+#ifdef WITH_SELINUX
+ (void) fputs (_(" -Z, --selinux-user SEUSER use a specific SEUSER for the SELinux user mapping\n"), usageout);
+#endif /* WITH_SELINUX */
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * new_pwent - initialize the values in a password file entry
+ *
+ * new_pwent() takes all of the values that have been entered and
+ * fills in a (struct passwd) with them.
+ */
+static void new_pwent (struct passwd *pwent)
+{
+ memzero (pwent, sizeof *pwent);
+ pwent->pw_name = (char *) user_name;
+ if (is_shadow_pwd) {
+ pwent->pw_passwd = (char *) SHADOW_PASSWD_STRING;
+ } else {
+ pwent->pw_passwd = (char *) user_pass;
+ }
+
+ pwent->pw_uid = user_id;
+ pwent->pw_gid = user_gid;
+ pwent->pw_gecos = (char *) user_comment;
+ pwent->pw_dir = (char *) user_home;
+ pwent->pw_shell = (char *) user_shell;
+}
+
+static long scale_age (long x)
+{
+ if (x <= 0) {
+ return x;
+ }
+
+ return x * (DAY / SCALE);
+}
+
+/*
+ * new_spent - initialize the values in a shadow password file entry
+ *
+ * new_spent() takes all of the values that have been entered and
+ * fills in a (struct spwd) with them.
+ */
+static void new_spent (struct spwd *spent)
+{
+ memzero (spent, sizeof *spent);
+ spent->sp_namp = (char *) user_name;
+ spent->sp_pwdp = (char *) user_pass;
+ spent->sp_lstchg = (long) gettime () / SCALE;
+ if (0 == spent->sp_lstchg) {
+ /* Better disable aging than requiring a password change */
+ spent->sp_lstchg = -1;
+ }
+ if (!rflg) {
+ spent->sp_min = scale_age (getdef_num ("PASS_MIN_DAYS", -1));
+ spent->sp_max = scale_age (getdef_num ("PASS_MAX_DAYS", -1));
+ spent->sp_warn = scale_age (getdef_num ("PASS_WARN_AGE", -1));
+ spent->sp_inact = scale_age (def_inactive);
+ spent->sp_expire = scale_age (user_expire);
+ } else {
+ spent->sp_min = -1;
+ spent->sp_max = -1;
+ spent->sp_warn = -1;
+ spent->sp_inact = -1;
+ spent->sp_expire = -1;
+ }
+ spent->sp_flag = SHADOW_SP_FLAG_UNSET;
+}
+
+/*
+ * grp_update - add user to secondary group set
+ *
+ * grp_update() takes the secondary group set given in user_groups
+ * and adds the user to each group given by that set.
+ *
+ * The group files are opened and locked in open_files().
+ *
+ * close_files() should be called afterwards to commit the changes
+ * and unlocking the group files.
+ */
+static void grp_update (void)
+{
+ const struct group *grp;
+ struct group *ngrp;
+
+#ifdef SHADOWGRP
+ const struct sgrp *sgrp;
+ struct sgrp *nsgrp;
+#endif
+
+ /*
+ * Scan through the entire group file looking for the groups that
+ * the user is a member of.
+ * FIXME: we currently do not check that all groups of user_groups
+ * were completed with the new user.
+ */
+ for (gr_rewind (), grp = gr_next (); NULL != grp; grp = gr_next ()) {
+
+ /*
+ * See if the user specified this group as one of their
+ * concurrent groups.
+ */
+ if (!is_on_list (user_groups, grp->gr_name)) {
+ continue;
+ }
+
+ /*
+ * Make a copy - gr_update() will free() everything
+ * from the old entry, and we need it later.
+ */
+ ngrp = __gr_dup (grp);
+ if (NULL == ngrp) {
+ fprintf (stderr,
+ _("%s: Out of memory. Cannot update %s.\n"),
+ Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to prepare the new %s entry '%s'", gr_dbname (), user_name));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding user to group",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ fail_exit (E_GRP_UPDATE); /* XXX */
+ }
+
+ /*
+ * Add the username to the list of group members and
+ * update the group entry to reflect the change.
+ */
+ ngrp->gr_mem = add_list (ngrp->gr_mem, user_name);
+ if (gr_update (ngrp) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, gr_dbname (), ngrp->gr_name);
+ SYSLOG ((LOG_ERR, "failed to prepare the new %s entry '%s'", gr_dbname (), user_name));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding user to group",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ fail_exit (E_GRP_UPDATE);
+ }
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding user to group",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ SYSLOG ((LOG_INFO,
+ "add '%s' to group '%s'",
+ user_name, ngrp->gr_name));
+ }
+
+#ifdef SHADOWGRP
+ if (!is_shadow_grp)
+ return;
+
+ /*
+ * Scan through the entire shadow group file looking for the groups
+ * that the user is a member of. The administrative list isn't
+ * modified.
+ */
+ for (sgr_rewind (), sgrp = sgr_next (); NULL != sgrp; sgrp = sgr_next ()) {
+
+ /*
+ * See if the user specified this group as one of their
+ * concurrent groups.
+ * FIXME: is it really needed?
+ * This would be important only if the group is in
+ * user_groups. All these groups should be checked
+ * for existence with gr_locate already.
+ */
+ if (gr_locate (sgrp->sg_name) == NULL) {
+ continue;
+ }
+
+ if (!is_on_list (user_groups, sgrp->sg_name)) {
+ continue;
+ }
+
+ /*
+ * Make a copy - sgr_update() will free() everything
+ * from the old entry, and we need it later.
+ */
+ nsgrp = __sgr_dup (sgrp);
+ if (NULL == nsgrp) {
+ fprintf (stderr,
+ _("%s: Out of memory. Cannot update %s.\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to prepare the new %s entry '%s'", sgr_dbname (), user_name));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding user to shadow group",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ fail_exit (E_GRP_UPDATE); /* XXX */
+ }
+
+ /*
+ * Add the username to the list of group members and
+ * update the group entry to reflect the change.
+ */
+ nsgrp->sg_mem = add_list (nsgrp->sg_mem, user_name);
+ if (sgr_update (nsgrp) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, sgr_dbname (), nsgrp->sg_name);
+ SYSLOG ((LOG_ERR, "failed to prepare the new %s entry '%s'", sgr_dbname (), user_name));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding user to shadow group",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ fail_exit (E_GRP_UPDATE);
+ }
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding user to shadow group",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ SYSLOG ((LOG_INFO,
+ "add '%s' to shadow group '%s'",
+ user_name, nsgrp->sg_name));
+ }
+#endif /* SHADOWGRP */
+}
+
+/*
+ * process_flags - perform command line argument setting
+ *
+ * process_flags() interprets the command line arguments and sets
+ * the values that the user will be created with accordingly. The
+ * values are checked for sanity.
+ */
+static void process_flags (int argc, char **argv)
+{
+ const struct group *grp;
+ bool anyflag = false;
+ char *cp;
+
+ {
+ /*
+ * Parse the command line options.
+ */
+ int c;
+ static struct option long_options[] = {
+ {"base-dir", required_argument, NULL, 'b'},
+ {"comment", required_argument, NULL, 'c'},
+ {"home-dir", required_argument, NULL, 'd'},
+ {"defaults", no_argument, NULL, 'D'},
+ {"expiredate", required_argument, NULL, 'e'},
+ {"inactive", required_argument, NULL, 'f'},
+ {"gid", required_argument, NULL, 'g'},
+ {"groups", required_argument, NULL, 'G'},
+ {"help", no_argument, NULL, 'h'},
+ {"skel", required_argument, NULL, 'k'},
+ {"key", required_argument, NULL, 'K'},
+ {"no-log-init", no_argument, NULL, 'l'},
+ {"create-home", no_argument, NULL, 'm'},
+ {"no-create-home", no_argument, NULL, 'M'},
+ {"no-user-group", no_argument, NULL, 'N'},
+ {"non-unique", no_argument, NULL, 'o'},
+ {"password", required_argument, NULL, 'p'},
+ {"system", no_argument, NULL, 'r'},
+ {"root", required_argument, NULL, 'R'},
+ {"shell", required_argument, NULL, 's'},
+ {"uid", required_argument, NULL, 'u'},
+ {"user-group", no_argument, NULL, 'U'},
+#ifdef WITH_SELINUX
+ {"selinux-user", required_argument, NULL, 'Z'},
+#endif /* WITH_SELINUX */
+ {NULL, 0, NULL, '\0'}
+ };
+ while ((c = getopt_long (argc, argv,
+#ifdef WITH_SELINUX
+ "b:c:d:De:f:g:G:hk:K:lmMNop:rR:s:u:UZ:",
+#else /* !WITH_SELINUX */
+ "b:c:d:De:f:g:G:hk:K:lmMNop:rR:s:u:U",
+#endif /* !WITH_SELINUX */
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'b':
+ if ( ( !VALID (optarg) )
+ || ( optarg[0] != '/' )) {
+ fprintf (stderr,
+ _("%s: invalid base directory '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ def_home = optarg;
+ bflg = true;
+ break;
+ case 'c':
+ if (!VALID (optarg)) {
+ fprintf (stderr,
+ _("%s: invalid comment '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ user_comment = optarg;
+ cflg = true;
+ break;
+ case 'd':
+ if ( ( !VALID (optarg) )
+ || ( optarg[0] != '/' )) {
+ fprintf (stderr,
+ _("%s: invalid home directory '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ user_home = optarg;
+ dflg = true;
+ break;
+ case 'D':
+ if (anyflag) {
+ usage (E_USAGE);
+ }
+ Dflg = true;
+ break;
+ case 'e':
+ if ('\0' != *optarg) {
+ user_expire = strtoday (optarg);
+ if (user_expire < -1) {
+ fprintf (stderr,
+ _("%s: invalid date '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ } else {
+ user_expire = -1;
+ }
+
+ /*
+ * -e "" is allowed without /etc/shadow
+ * (it's a no-op in such case)
+ */
+ if ((-1 != user_expire) && !is_shadow_pwd) {
+ fprintf (stderr,
+ _("%s: shadow passwords required for -e\n"),
+ Prog);
+ exit (E_USAGE);
+ }
+ if (Dflg) {
+ def_expire = optarg;
+ }
+ eflg = true;
+ break;
+ case 'f':
+ if ( (getlong (optarg, &def_inactive) == 0)
+ || (def_inactive < -1)) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ /*
+ * -f -1 is allowed
+ * it's a no-op without /etc/shadow
+ */
+ if ((-1 != def_inactive) && !is_shadow_pwd) {
+ fprintf (stderr,
+ _("%s: shadow passwords required for -f\n"),
+ Prog);
+ exit (E_USAGE);
+ }
+ fflg = true;
+ break;
+ case 'g':
+ grp = getgr_nam_gid (optarg);
+ if (NULL == grp) {
+ fprintf (stderr,
+ _("%s: group '%s' does not exist\n"),
+ Prog, optarg);
+ exit (E_NOTFOUND);
+ }
+ if (Dflg) {
+ def_group = grp->gr_gid;
+ def_gname = optarg;
+ } else {
+ user_gid = grp->gr_gid;
+ }
+ gflg = true;
+ break;
+ case 'G':
+ if (get_groups (optarg) != 0) {
+ exit (E_NOTFOUND);
+ }
+ if (NULL != user_groups[0]) {
+ do_grp_update = true;
+ }
+ Gflg = true;
+ break;
+ case 'h':
+ usage (E_SUCCESS);
+ break;
+ case 'k':
+ def_template = optarg;
+ kflg = true;
+ break;
+ case 'K':
+ /*
+ * override login.defs defaults (-K name=value)
+ * example: -K UID_MIN=100 -K UID_MAX=499
+ * note: -K UID_MIN=10,UID_MAX=499 doesn't work yet
+ */
+ cp = strchr (optarg, '=');
+ if (NULL == cp) {
+ fprintf (stderr,
+ _("%s: -K requires KEY=VALUE\n"),
+ Prog);
+ exit (E_BAD_ARG);
+ }
+ /* terminate name, point to value */
+ *cp = '\0';
+ cp++;
+ if (putdef_str (optarg, cp) < 0) {
+ exit (E_BAD_ARG);
+ }
+ break;
+ case 'l':
+ lflg = true;
+ break;
+ case 'm':
+ mflg = true;
+ break;
+ case 'M':
+ Mflg = true;
+ break;
+ case 'N':
+ Nflg = true;
+ break;
+ case 'o':
+ oflg = true;
+ break;
+ case 'p': /* set encrypted password */
+ if (!VALID (optarg)) {
+ fprintf (stderr,
+ _("%s: invalid field '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ user_pass = optarg;
+ break;
+ case 'r':
+ rflg = true;
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ case 's':
+ if ( ( !VALID (optarg) )
+ || ( ('\0' != optarg[0])
+ && ('/' != optarg[0])
+ && ('*' != optarg[0]) )) {
+ fprintf (stderr,
+ _("%s: invalid shell '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ user_shell = optarg;
+ def_shell = optarg;
+ sflg = true;
+ break;
+ case 'u':
+ if ( (get_uid (optarg, &user_id) == 0)
+ || (user_id == (gid_t)-1)) {
+ fprintf (stderr,
+ _("%s: invalid user ID '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ uflg = true;
+ break;
+ case 'U':
+ Uflg = true;
+ break;
+#ifdef WITH_SELINUX
+ case 'Z':
+ if (is_selinux_enabled () > 0) {
+ user_selinux = optarg;
+ } else {
+ fprintf (stderr,
+ _("%s: -Z requires SELinux enabled kernel\n"),
+ Prog);
+
+ exit (E_BAD_ARG);
+ }
+ break;
+#endif /* WITH_SELINUX */
+ default:
+ usage (E_USAGE);
+ }
+ anyflag = true;
+ }
+ }
+
+ if (!gflg && !Nflg && !Uflg) {
+ /* Get the settings from login.defs */
+ Uflg = getdef_bool ("USERGROUPS_ENAB");
+ }
+
+ /*
+ * Certain options are only valid in combination with others.
+ * Check it here so that they can be specified in any order.
+ */
+ if (oflg && !uflg) {
+ fprintf (stderr,
+ _("%s: %s flag is only allowed with the %s flag\n"),
+ Prog, "-o", "-u");
+ usage (E_USAGE);
+ }
+ if (kflg && !mflg) {
+ fprintf (stderr,
+ _("%s: %s flag is only allowed with the %s flag\n"),
+ Prog, "-k", "-m");
+ usage (E_USAGE);
+ }
+ if (Uflg && gflg) {
+ fprintf (stderr,
+ _("%s: options %s and %s conflict\n"),
+ Prog, "-U", "-g");
+ usage (E_USAGE);
+ }
+ if (Uflg && Nflg) {
+ fprintf (stderr,
+ _("%s: options %s and %s conflict\n"),
+ Prog, "-U", "-N");
+ usage (E_USAGE);
+ }
+ if (mflg && Mflg) {
+ fprintf (stderr,
+ _("%s: options %s and %s conflict\n"),
+ Prog, "-m", "-M");
+ usage (E_USAGE);
+ }
+
+ /*
+ * Either -D or username is required. Defaults can be set with -D
+ * for the -b, -e, -f, -g, -s options only.
+ */
+ if (Dflg) {
+ if (optind != argc) {
+ usage (E_USAGE);
+ }
+
+ if (uflg || Gflg || dflg || cflg || mflg) {
+ usage (E_USAGE);
+ }
+ } else {
+ if (optind != argc - 1) {
+ usage (E_USAGE);
+ }
+
+ user_name = argv[optind];
+ if (!is_valid_user_name (user_name)) {
+ fprintf (stderr,
+ _("%s: invalid user name '%s'\n"),
+ Prog, user_name);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding user",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ exit (E_BAD_ARG);
+ }
+ if (!dflg) {
+ char *uh;
+ size_t len = strlen (def_home) + strlen (user_name) + 2;
+ int wlen;
+
+ uh = xmalloc (len);
+ wlen = snprintf (uh, len, "%s/%s", def_home, user_name);
+ assert (wlen == (int) len -1);
+
+ user_home = uh;
+ }
+ }
+
+ if (!eflg) {
+ user_expire = strtoday (def_expire);
+ }
+
+ if (!gflg) {
+ user_gid = def_group;
+ }
+
+ if (!sflg) {
+ user_shell = def_shell;
+ }
+
+ create_mail_spool = def_create_mail_spool;
+
+ if (!rflg) {
+ /* for system accounts defaults are ignored and we
+ * do not create a home dir */
+ if (getdef_bool ("CREATE_HOME")) {
+ mflg = true;
+ }
+ }
+
+ if (Mflg) {
+ /* absolutely sure that we do not create home dirs */
+ mflg = false;
+ }
+}
+
+/*
+ * close_files - close all of the files that were opened
+ *
+ * close_files() closes all of the files that were opened for this
+ * new user. This causes any modified entries to be written out.
+ */
+static void close_files (void)
+{
+ if (pw_close () == 0) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
+ fail_exit (E_PW_UPDATE);
+ }
+ if (is_shadow_pwd && (spw_close () == 0)) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ()));
+ fail_exit (E_PW_UPDATE);
+ }
+ if (do_grp_update) {
+ if (gr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", gr_dbname ()));
+ fail_exit (E_GRP_UPDATE);
+ }
+#ifdef SHADOWGRP
+ if (is_shadow_grp && (sgr_close () == 0)) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", sgr_dbname ()));
+ fail_exit (E_GRP_UPDATE);
+ }
+#endif
+ }
+#ifdef ENABLE_SUBIDS
+ if (is_sub_uid && (sub_uid_close () == 0)) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"), Prog, sub_uid_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", sub_uid_dbname ()));
+ fail_exit (E_SUB_UID_UPDATE);
+ }
+ if (is_sub_gid && (sub_gid_close () == 0)) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"), Prog, sub_gid_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", sub_gid_dbname ()));
+ fail_exit (E_SUB_GID_UPDATE);
+ }
+#endif /* ENABLE_SUBIDS */
+ if (is_shadow_pwd) {
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "unlocking shadow file",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ /* continue */
+ }
+ spw_locked = false;
+ }
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "unlocking passwd file",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ /* continue */
+ }
+ pw_locked = false;
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "unlocking group file",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ /* continue */
+ }
+ gr_locked = false;
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "unlocking gshadow file",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ /* continue */
+ }
+ sgr_locked = false;
+ }
+#endif
+#ifdef ENABLE_SUBIDS
+ if (is_sub_uid) {
+ if (sub_uid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ()));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "unlocking subordinate user file",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ /* continue */
+ }
+ sub_uid_locked = false;
+ }
+ if (is_sub_gid) {
+ if (sub_gid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ()));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "unlocking subordinate group file",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ /* continue */
+ }
+ sub_gid_locked = false;
+ }
+#endif /* ENABLE_SUBIDS */
+}
+
+/*
+ * open_files - lock and open the password files
+ *
+ * open_files() opens the two password files.
+ */
+static void open_files (void)
+{
+ if (pw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, pw_dbname ());
+ exit (E_PW_UPDATE);
+ }
+ pw_locked = true;
+ if (pw_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, pw_dbname ());
+ fail_exit (E_PW_UPDATE);
+ }
+
+ /* shadow file will be opened by open_shadow(); */
+
+ /*
+ * Lock and open the group file.
+ */
+ if (gr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, gr_dbname ());
+ fail_exit (E_GRP_UPDATE);
+ }
+ gr_locked = true;
+ if (gr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
+ fail_exit (E_GRP_UPDATE);
+ }
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sgr_dbname ());
+ fail_exit (E_GRP_UPDATE);
+ }
+ sgr_locked = true;
+ if (sgr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, sgr_dbname ());
+ fail_exit (E_GRP_UPDATE);
+ }
+ }
+#endif
+#ifdef ENABLE_SUBIDS
+ if (is_sub_uid) {
+ if (sub_uid_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sub_uid_dbname ());
+ fail_exit (E_SUB_UID_UPDATE);
+ }
+ sub_uid_locked = true;
+ if (sub_uid_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, sub_uid_dbname ());
+ fail_exit (E_SUB_UID_UPDATE);
+ }
+ }
+ if (is_sub_gid) {
+ if (sub_gid_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sub_gid_dbname ());
+ fail_exit (E_SUB_GID_UPDATE);
+ }
+ sub_gid_locked = true;
+ if (sub_gid_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, sub_gid_dbname ());
+ fail_exit (E_SUB_GID_UPDATE);
+ }
+ }
+#endif /* ENABLE_SUBIDS */
+}
+
+static void open_shadow (void)
+{
+ if (!is_shadow_pwd) {
+ return;
+ }
+ if (spw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, spw_dbname ());
+ fail_exit (E_PW_UPDATE);
+ }
+ spw_locked = true;
+ if (spw_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, spw_dbname ());
+ fail_exit (E_PW_UPDATE);
+ }
+}
+
+static char *empty_list = NULL;
+
+/*
+ * new_grent - initialize the values in a group file entry
+ *
+ * new_grent() takes all of the values that have been entered and fills
+ * in a (struct group) with them.
+ */
+
+static void new_grent (struct group *grent)
+{
+ memzero (grent, sizeof *grent);
+ grent->gr_name = (char *) user_name;
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ grent->gr_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
+ } else
+#endif /* SHADOWGRP */
+ {
+ grent->gr_passwd = "!"; /* XXX warning: const */
+ }
+ grent->gr_gid = user_gid;
+ grent->gr_mem = &empty_list;
+}
+
+#ifdef SHADOWGRP
+/*
+ * new_sgent - initialize the values in a shadow group file entry
+ *
+ * new_sgent() takes all of the values that have been entered and fills
+ * in a (struct sgrp) with them.
+ */
+
+static void new_sgent (struct sgrp *sgent)
+{
+ memzero (sgent, sizeof *sgent);
+ sgent->sg_name = (char *) user_name;
+ sgent->sg_passwd = "!"; /* XXX warning: const */
+ sgent->sg_adm = &empty_list;
+ sgent->sg_mem = &empty_list;
+}
+#endif /* SHADOWGRP */
+
+
+/*
+ * grp_add - add new group file entries
+ *
+ * grp_add() writes the new records to the group files.
+ */
+
+static void grp_add (void)
+{
+ struct group grp;
+
+#ifdef SHADOWGRP
+ struct sgrp sgrp;
+#endif /* SHADOWGRP */
+
+ /*
+ * Create the initial entries for this new group.
+ */
+ new_grent (&grp);
+#ifdef SHADOWGRP
+ new_sgent (&sgrp);
+#endif /* SHADOWGRP */
+
+ /*
+ * Write out the new group file entry.
+ */
+ if (gr_update (&grp) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, gr_dbname (), grp.gr_name);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_GROUP, Prog,
+ "adding group",
+ grp.gr_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ fail_exit (E_GRP_UPDATE);
+ }
+#ifdef SHADOWGRP
+ /*
+ * Write out the new shadow group entries as well.
+ */
+ if (is_shadow_grp && (sgr_update (&sgrp) == 0)) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, sgr_dbname (), sgrp.sg_name);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_GROUP, Prog,
+ "adding group",
+ grp.gr_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ fail_exit (E_GRP_UPDATE);
+ }
+#endif /* SHADOWGRP */
+ SYSLOG ((LOG_INFO, "new group: name=%s, GID=%u", user_name, user_gid));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_GROUP, Prog,
+ "adding group",
+ grp.gr_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ do_grp_update = true;
+}
+
+static void faillog_reset (uid_t uid)
+{
+ struct faillog fl;
+ int fd;
+ off_t offset_uid = (off_t) (sizeof fl) * uid;
+
+ if (access (FAILLOG_FILE, F_OK) != 0) {
+ return;
+ }
+
+ memzero (&fl, sizeof (fl));
+
+ fd = open (FAILLOG_FILE, O_RDWR);
+ if ( (-1 == fd)
+ || (lseek (fd, offset_uid, SEEK_SET) != offset_uid)
+ || (write (fd, &fl, sizeof (fl)) != (ssize_t) sizeof (fl))
+ || (fsync (fd) != 0)
+ || (close (fd) != 0)) {
+ fprintf (stderr,
+ _("%s: failed to reset the faillog entry of UID %lu: %s\n"),
+ Prog, (unsigned long) uid, strerror (errno));
+ SYSLOG ((LOG_WARN, "failed to reset the faillog entry of UID %lu", (unsigned long) uid));
+ /* continue */
+ }
+}
+
+static void lastlog_reset (uid_t uid)
+{
+ struct lastlog ll;
+ int fd;
+ off_t offset_uid = (off_t) (sizeof ll) * uid;
+
+ if (access (LASTLOG_FILE, F_OK) != 0) {
+ return;
+ }
+
+ memzero (&ll, sizeof (ll));
+
+ fd = open (LASTLOG_FILE, O_RDWR);
+ if ( (-1 == fd)
+ || (lseek (fd, offset_uid, SEEK_SET) != offset_uid)
+ || (write (fd, &ll, sizeof (ll)) != (ssize_t) sizeof (ll))
+ || (fsync (fd) != 0)
+ || (close (fd) != 0)) {
+ fprintf (stderr,
+ _("%s: failed to reset the lastlog entry of UID %lu: %s\n"),
+ Prog, (unsigned long) uid, strerror (errno));
+ SYSLOG ((LOG_WARN, "failed to reset the lastlog entry of UID %lu", (unsigned long) uid));
+ /* continue */
+ }
+}
+
+static void tallylog_reset (char *user_name)
+{
+ const char pam_tally2[] = "/sbin/pam_tally2";
+ const char *pname;
+ pid_t childpid;
+ int failed;
+ int status;
+
+ if (access(pam_tally2, X_OK) == -1)
+ return;
+
+ failed = 0;
+ switch (childpid = fork())
+ {
+ case -1: /* error */
+ failed = 1;
+ break;
+ case 0: /* child */
+ pname = strrchr(pam_tally2, '/');
+ if (pname == NULL)
+ pname = pam_tally2;
+ else
+ pname++; /* Skip the '/' */
+ execl(pam_tally2, pname, "--user", user_name, "--reset", "--quiet", NULL);
+ /* If we come here, something has gone terribly wrong */
+ perror(pam_tally2);
+ exit(42); /* don't continue, we now have 2 processes running! */
+ /* NOTREACHED */
+ break;
+ default: /* parent */
+ if (waitpid(childpid, &status, 0) == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ failed = 1;
+ break;
+ }
+
+ if (failed)
+ {
+ fprintf (stderr,
+ _("%s: failed to reset the tallylog entry of user \"%s\"\n"),
+ Prog, user_name);
+ SYSLOG ((LOG_WARN, "failed to reset the tallylog entry of user \"%s\"", user_name));
+ }
+
+ return;
+}
+
+/*
+ * usr_update - create the user entries
+ *
+ * usr_update() creates the password file entries for this user
+ * and will update the group entries if required.
+ */
+static void usr_update (void)
+{
+ struct passwd pwent;
+ struct spwd spent;
+
+ /*
+ * Fill in the password structure with any new fields, making
+ * copies of strings.
+ */
+ new_pwent (&pwent);
+ new_spent (&spent);
+
+ /*
+ * Create a syslog entry. We need to do this now in case anything
+ * happens so we know what we were trying to accomplish.
+ */
+ SYSLOG ((LOG_INFO,
+ "new user: name=%s, UID=%u, GID=%u, home=%s, shell=%s",
+ user_name, (unsigned int) user_id,
+ (unsigned int) user_gid, user_home, user_shell));
+
+ /*
+ * Initialize faillog and lastlog entries for this UID in case
+ * it belongs to a previously deleted user. We do it only if
+ * no user with this UID exists yet (entries for shared UIDs
+ * are left unchanged). --marekm
+ */
+ /* local, no need for xgetpwuid */
+ if ((!lflg) && (getpwuid (user_id) == NULL)) {
+ faillog_reset (user_id);
+ lastlog_reset (user_id);
+ }
+
+ /*
+ * Put the new (struct passwd) in the table.
+ */
+ if (pw_update (&pwent) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, pw_dbname (), pwent.pw_name);
+ fail_exit (E_PW_UPDATE);
+ }
+
+ /*
+ * Put the new (struct spwd) in the table.
+ */
+ if (is_shadow_pwd && (spw_update (&spent) == 0)) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, spw_dbname (), spent.sp_namp);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding shadow password",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ fail_exit (E_PW_UPDATE);
+ }
+#ifdef ENABLE_SUBIDS
+ if (is_sub_uid &&
+ (sub_uid_add(user_name, sub_uid_start, sub_uid_count) == 0)) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry\n"),
+ Prog, sub_uid_dbname ());
+ fail_exit (E_SUB_UID_UPDATE);
+ }
+ if (is_sub_gid &&
+ (sub_gid_add(user_name, sub_gid_start, sub_gid_count) == 0)) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry\n"),
+ Prog, sub_uid_dbname ());
+ fail_exit (E_SUB_GID_UPDATE);
+ }
+#endif /* ENABLE_SUBIDS */
+
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding user",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ /*
+ * Do any group file updates for this user.
+ */
+ if (do_grp_update) {
+ grp_update ();
+ }
+}
+
+/*
+ * create_home - create the user's home directory
+ *
+ * create_home() creates the user's home directory if it does not
+ * already exist. It will be created mode 755 owned by the user
+ * with the user's default group.
+ */
+static void create_home (void)
+{
+ if (access (user_home, F_OK) != 0) {
+#ifdef WITH_SELINUX
+ if (set_selinux_file_context (user_home) != 0) {
+ fprintf (stderr,
+ _("%s: cannot set SELinux context for home directory %s\n"),
+ Prog, user_home);
+ fail_exit (E_HOMEDIR);
+ }
+#endif
+ /* XXX - create missing parent directories. --marekm */
+ if (mkdir (user_home, 0) != 0) {
+ fprintf (stderr,
+ _("%s: cannot create directory %s\n"),
+ Prog, user_home);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding home directory",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ fail_exit (E_HOMEDIR);
+ }
+ chown (user_home, user_id, user_gid);
+ chmod (user_home,
+ 0777 & ~getdef_num ("UMASK", GETDEF_DEFAULT_UMASK));
+ home_added = true;
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding home directory",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+#ifdef WITH_SELINUX
+ /* Reset SELinux to create files with default contexts */
+ if (reset_selinux_file_context () != 0) {
+ fprintf (stderr,
+ _("%s: cannot reset SELinux file creation context\n"),
+ Prog);
+ fail_exit (E_HOMEDIR);
+ }
+#endif
+ }
+}
+
+/*
+ * create_mail - create the user's mail spool
+ *
+ * create_mail() creates the user's mail spool if it does not already
+ * exist. It will be created mode 660 owned by the user and group
+ * 'mail'
+ */
+static void create_mail (void)
+{
+ if (strcasecmp (create_mail_spool, "yes") == 0) {
+ const char *spool;
+ char *file;
+ int fd;
+ struct group *gr;
+ gid_t gid;
+ mode_t mode;
+
+ spool = getdef_str ("MAIL_DIR");
+ if (NULL == spool) {
+ spool = "/var/mail";
+ }
+ file = alloca (strlen (spool) + strlen (user_name) + 2);
+ sprintf (file, "%s/%s", spool, user_name);
+ fd = open (file, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0);
+ if (fd < 0) {
+ perror (_("Creating mailbox file"));
+ return;
+ }
+
+ gr = getgrnam ("mail"); /* local, no need for xgetgrnam */
+ if (NULL == gr) {
+ fputs (_("Group 'mail' not found. Creating the user mailbox file with 0600 mode.\n"),
+ stderr);
+ gid = user_gid;
+ mode = 0600;
+ } else {
+ gid = gr->gr_gid;
+ mode = 0660;
+ }
+
+ if ( (fchown (fd, user_id, gid) != 0)
+ || (fchmod (fd, mode) != 0)) {
+ perror (_("Setting mailbox file permissions"));
+ }
+
+ fsync (fd);
+ close (fd);
+ }
+}
+
+/*
+ * main - useradd command
+ */
+int main (int argc, char **argv)
+{
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ pam_handle_t *pamh = NULL;
+ int retval;
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+
+#ifdef ENABLE_SUBIDS
+ uid_t uid_min;
+ uid_t uid_max;
+#endif
+
+ /*
+ * Get my name so that I can use it to report errors.
+ */
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ OPENLOG ("useradd");
+#ifdef WITH_AUDIT
+ audit_help_open ();
+#endif
+
+ sys_ngroups = sysconf (_SC_NGROUPS_MAX);
+ user_groups = (char **) xmalloc ((1 + sys_ngroups) * sizeof (char *));
+ /*
+ * Initialize the list to be empty
+ */
+ user_groups[0] = (char *) 0;
+
+
+ is_shadow_pwd = spw_file_present ();
+#ifdef SHADOWGRP
+ is_shadow_grp = sgr_file_present ();
+#endif
+
+ get_defaults ();
+
+ process_flags (argc, argv);
+
+#ifdef ENABLE_SUBIDS
+ uid_min = (uid_t) getdef_ulong ("UID_MIN", 1000UL);
+ uid_max = (uid_t) getdef_ulong ("UID_MAX", 60000UL);
+ is_sub_uid = sub_uid_file_present () && !rflg &&
+ (!user_id || (user_id <= uid_max && user_id >= uid_min));
+ is_sub_gid = sub_gid_file_present () && !rflg &&
+ (!user_id || (user_id <= uid_max && user_id >= uid_min));
+#endif /* ENABLE_SUBIDS */
+
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ {
+ struct passwd *pampw;
+ pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+ if (pampw == NULL) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ fail_exit (1);
+ }
+
+ retval = pam_start ("useradd", pampw->pw_name, &conv, &pamh);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_authenticate (pamh, 0);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_acct_mgmt (pamh, 0);
+ }
+
+ if (PAM_SUCCESS != retval) {
+ fprintf (stderr, _("%s: PAM: %s\n"),
+ Prog, pam_strerror (pamh, retval));
+ SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval)));
+ if (NULL != pamh) {
+ (void) pam_end (pamh, retval);
+ }
+ fail_exit (1);
+ }
+ (void) pam_end (pamh, retval);
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+
+ /*
+ * See if we are messing with the defaults file, or creating
+ * a new user.
+ */
+ if (Dflg) {
+ if (gflg || bflg || fflg || eflg || sflg) {
+ exit ((set_defaults () != 0) ? 1 : 0);
+ }
+
+ show_defaults ();
+ exit (E_SUCCESS);
+ }
+
+ /*
+ * Start with a quick check to see if the user exists.
+ */
+ if (getpwnam (user_name) != NULL) { /* local, no need for xgetpwnam */
+ fprintf (stderr, _("%s: user '%s' already exists\n"), Prog, user_name);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding user",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ fail_exit (E_NAME_IN_USE);
+ }
+
+ /*
+ * Don't blindly overwrite a group when a user is added...
+ * If you already have a group username, and want to add the user
+ * to that group, use useradd -g username username.
+ * --bero
+ */
+ if (Uflg) {
+ /* local, no need for xgetgrnam */
+ if (getgrnam (user_name) != NULL) {
+ fprintf (stderr,
+ _("%s: group %s exists - if you want to add this user to that group, use -g.\n"),
+ Prog, user_name);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding group",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ fail_exit (E_NAME_IN_USE);
+ }
+ }
+
+ /*
+ * Do the hard stuff:
+ * - open the files,
+ * - create the user entries,
+ * - create the home directory,
+ * - create user mail spool,
+ * - flush nscd caches for passwd and group services,
+ * - then close and update the files.
+ */
+ open_files ();
+
+ if (!oflg) {
+ /* first, seek for a valid uid to use for this user.
+ * We do this because later we can use the uid we found as
+ * gid too ... --gafton */
+ if (!uflg) {
+ if (find_new_uid (rflg, &user_id, NULL) < 0) {
+ fprintf (stderr, _("%s: can't create user\n"), Prog);
+ fail_exit (E_UID_IN_USE);
+ }
+ } else {
+ if (getpwuid (user_id) != NULL) {
+ fprintf (stderr,
+ _("%s: UID %lu is not unique\n"),
+ Prog, (unsigned long) user_id);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding user",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif
+ fail_exit (E_UID_IN_USE);
+ }
+ }
+ }
+
+#ifdef WITH_TCB
+ if (getdef_bool ("USE_TCB")) {
+ if (shadowtcb_create (user_name, user_id) == SHADOWTCB_FAILURE) {
+ fprintf (stderr,
+ _("%s: Failed to create tcb directory for %s\n"),
+ Prog, user_name);
+ fail_exit (E_UID_IN_USE);
+ }
+ }
+#endif
+ open_shadow ();
+
+ /* do we have to add a group for that user? This is why we need to
+ * open the group files in the open_files() function --gafton */
+ if (Uflg) {
+ if (find_new_gid (rflg, &user_gid, &user_id) < 0) {
+ fprintf (stderr,
+ _("%s: can't create group\n"),
+ Prog);
+ fail_exit (4);
+ }
+ grp_add ();
+ }
+
+#ifdef ENABLE_SUBIDS
+ if (is_sub_uid) {
+ if (find_new_sub_uids(user_name, &sub_uid_start, &sub_uid_count) < 0) {
+ fprintf (stderr,
+ _("%s: can't create subordinate user IDs\n"),
+ Prog);
+ fail_exit(E_SUB_UID_UPDATE);
+ }
+ }
+ if (is_sub_gid) {
+ if (find_new_sub_gids(user_name, &sub_gid_start, &sub_gid_count) < 0) {
+ fprintf (stderr,
+ _("%s: can't create subordinate group IDs\n"),
+ Prog);
+ fail_exit(E_SUB_GID_UPDATE);
+ }
+ }
+#endif /* ENABLE_SUBIDS */
+
+ usr_update ();
+
+ if (mflg) {
+ create_home ();
+ if (home_added) {
+ copy_tree (def_template, user_home, false, false,
+ (uid_t)-1, user_id, (gid_t)-1, user_gid);
+ } else {
+ fprintf (stderr,
+ _("%s: warning: the home directory already exists.\n"
+ "Not copying any file from skel directory into it.\n"),
+ Prog);
+ }
+
+ }
+
+ /* Do not create mail directory for system accounts */
+ if (!rflg) {
+ create_mail ();
+ }
+
+ close_files ();
+
+ /*
+ * tallylog_reset needs to be able to lookup
+ * a valid existing user name,
+ * so we canot call it before close_files()
+ */
+ if (!lflg && getpwuid (user_id) != NULL) {
+ tallylog_reset (user_name);
+ }
+
+#ifdef WITH_SELINUX
+ if (Zflg) {
+ if (set_seuser (user_name, user_selinux) != 0) {
+ fprintf (stderr,
+ _("%s: warning: the user name %s to %s SELinux user mapping failed.\n"),
+ Prog, user_name, user_selinux);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "adding SELinux user mapping",
+ user_name, (unsigned int) user_id, 0);
+#endif /* WITH_AUDIT */
+ fail_exit (E_SE_UPDATE);
+ }
+ }
+#endif /* WITH_SELINUX */
+
+ nscd_flush_cache ("passwd");
+ nscd_flush_cache ("group");
+
+ return E_SUCCESS;
+}
+
diff --git a/src/userdel.c b/src/userdel.c
new file mode 100644
index 0000000..9092b5c
--- /dev/null
+++ b/src/userdel.c
@@ -0,0 +1,1283 @@
+/*
+ * Copyright (c) 1991 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2012, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/stat.h>
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+#include "defines.h"
+#include "getdef.h"
+#include "groupio.h"
+#include "nscd.h"
+#include "prototypes.h"
+#include "pwauth.h"
+#include "pwio.h"
+#include "shadowio.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif /* SHADOWGRP */
+#ifdef WITH_TCB
+#include <tcb.h>
+#include "tcbfuncs.h"
+#endif /* WITH_TCB */
+/*@-exitarg@*/
+#include "exitcodes.h"
+#ifdef ENABLE_SUBIDS
+#include "subordinateio.h"
+#endif /* ENABLE_SUBIDS */
+
+/*
+ * exit status values
+ */
+#define E_PW_UPDATE 1 /* can't update password file */
+#define E_NOTFOUND 6 /* specified user doesn't exist */
+#define E_USER_BUSY 8 /* user currently logged in */
+#define E_GRP_UPDATE 10 /* can't update group file */
+#define E_HOMEDIR 12 /* can't remove home directory */
+#define E_SE_UPDATE 14 /* can't update SELinux user mapping */
+#ifdef ENABLE_SUBIDS
+#define E_SUB_UID_UPDATE 16 /* can't update the subordinate uid file */
+#define E_SUB_GID_UPDATE 18 /* can't update the subordinate gid file */
+#endif /* ENABLE_SUBIDS */
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static char *user_name;
+static uid_t user_id;
+static gid_t user_gid;
+static char *user_home;
+
+static bool fflg = false;
+static bool rflg = false;
+static bool Zflg = false;
+
+static bool is_shadow_pwd;
+
+#ifdef SHADOWGRP
+static bool is_shadow_grp;
+static bool sgr_locked = false;
+#endif /* SHADOWGRP */
+static bool pw_locked = false;
+static bool gr_locked = false;
+static bool spw_locked = false;
+#ifdef ENABLE_SUBIDS
+static bool is_sub_uid;
+static bool is_sub_gid;
+static bool sub_uid_locked = false;
+static bool sub_gid_locked = false;
+#endif /* ENABLE_SUBIDS */
+
+/* local function prototypes */
+static void usage (int status);
+static void update_groups (void);
+static void remove_usergroup (void);
+static void close_files (void);
+static void fail_exit (int);
+static void open_files (void);
+static void update_user (void);
+static void user_cancel (const char *);
+
+#ifdef EXTRA_CHECK_HOME_DIR
+static bool path_prefix (const char *, const char *);
+#endif /* EXTRA_CHECK_HOME_DIR */
+static int is_owner (uid_t, const char *);
+static int remove_mailbox (void);
+#ifdef WITH_TCB
+static int remove_tcbdir (const char *user_name, uid_t user_id);
+#endif /* WITH_TCB */
+
+/*
+ * usage - display usage message and exit
+ */
+static void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options] LOGIN\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -f, --force force removal of files,\n"
+ " even if not owned by user\n"),
+ usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -r, --remove remove home directory and mail spool\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+#ifdef WITH_SELINUX
+ (void) fputs (_(" -Z, --selinux-user remove any SELinux user mapping for the user\n"), usageout);
+#endif /* WITH_SELINUX */
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * update_groups - delete user from secondary group set
+ *
+ * update_groups() takes the user name that was given and searches
+ * the group files for membership in any group.
+ *
+ * we also check to see if they have any groups they own (the same
+ * name is their user name) and delete them too (only if USERGROUPS_ENAB
+ * is enabled).
+ */
+static void update_groups (void)
+{
+ const struct group *grp;
+ struct group *ngrp;
+
+#ifdef SHADOWGRP
+ const struct sgrp *sgrp;
+ struct sgrp *nsgrp;
+#endif /* SHADOWGRP */
+
+ /*
+ * Scan through the entire group file looking for the groups that
+ * the user is a member of.
+ */
+ for (gr_rewind (), grp = gr_next (); NULL != grp; grp = gr_next ()) {
+
+ /*
+ * See if the user specified this group as one of their
+ * concurrent groups.
+ */
+ if (!is_on_list (grp->gr_mem, user_name)) {
+ continue;
+ }
+
+ /*
+ * Delete the username from the list of group members and
+ * update the group entry to reflect the change.
+ */
+ ngrp = __gr_dup (grp);
+ if (NULL == ngrp) {
+ fprintf (stderr,
+ _("%s: Out of memory. Cannot update %s.\n"),
+ Prog, gr_dbname ());
+ exit (13); /* XXX */
+ }
+ ngrp->gr_mem = del_list (ngrp->gr_mem, user_name);
+ if (gr_update (ngrp) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, gr_dbname (), ngrp->gr_name);
+ exit (E_GRP_UPDATE);
+ }
+
+ /*
+ * Update the DBM group file with the new entry as well.
+ */
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "deleting user from group",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_SUCCESS);
+#endif /* WITH_AUDIT */
+ SYSLOG ((LOG_INFO, "delete '%s' from group '%s'\n",
+ user_name, ngrp->gr_name));
+ }
+
+ if (getdef_bool ("USERGROUPS_ENAB")) {
+ remove_usergroup ();
+ }
+
+#ifdef SHADOWGRP
+ if (!is_shadow_grp) {
+ return;
+ }
+
+ /*
+ * Scan through the entire shadow group file looking for the groups
+ * that the user is a member of. Both the administrative list and
+ * the ordinary membership list is checked.
+ */
+ for (sgr_rewind (), sgrp = sgr_next ();
+ NULL != sgrp;
+ sgrp = sgr_next ()) {
+ bool was_member, was_admin;
+
+ /*
+ * See if the user specified this group as one of their
+ * concurrent groups.
+ */
+ was_member = is_on_list (sgrp->sg_mem, user_name);
+ was_admin = is_on_list (sgrp->sg_adm, user_name);
+
+ if (!was_member && !was_admin) {
+ continue;
+ }
+
+ nsgrp = __sgr_dup (sgrp);
+ if (NULL == nsgrp) {
+ fprintf (stderr,
+ _("%s: Out of memory. Cannot update %s.\n"),
+ Prog, sgr_dbname ());
+ exit (13); /* XXX */
+ }
+
+ if (was_member) {
+ nsgrp->sg_mem = del_list (nsgrp->sg_mem, user_name);
+ }
+
+ if (was_admin) {
+ nsgrp->sg_adm = del_list (nsgrp->sg_adm, user_name);
+ }
+
+ if (sgr_update (nsgrp) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, sgr_dbname (), nsgrp->sg_name);
+ exit (E_GRP_UPDATE);
+ }
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "deleting user from shadow group",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_SUCCESS);
+#endif /* WITH_AUDIT */
+ SYSLOG ((LOG_INFO, "delete '%s' from shadow group '%s'\n",
+ user_name, nsgrp->sg_name));
+ }
+#endif /* SHADOWGRP */
+}
+
+/*
+ * remove_usergroup - delete the user's group if it is a usergroup
+ *
+ * An usergroup is removed if
+ * + it has the same name as the user
+ * + it is the primary group of the user
+ * + it has no other members
+ * + it is not the primary group of any other user
+ */
+static void remove_usergroup (void)
+{
+ const struct group *grp;
+ const struct passwd *pwd = NULL;
+
+ grp = gr_locate (user_name);
+ if (NULL == grp) {
+ /* This user has no usergroup. */
+ return;
+ }
+
+ if (grp->gr_gid != user_gid) {
+ fprintf (stderr,
+ _("%s: group %s not removed because it is not the primary group of user %s.\n"),
+ Prog, grp->gr_name, user_name);
+ return;
+ }
+
+ if (NULL != grp->gr_mem[0]) {
+ /* The usergroup has other members. */
+ fprintf (stderr,
+ _("%s: group %s not removed because it has other members.\n"),
+ Prog, grp->gr_name);
+ return;
+ }
+
+ if (!fflg) {
+ /*
+ * Scan the passwd file to check if this group is still
+ * used as a primary group.
+ */
+ setpwent ();
+ while ((pwd = getpwent ()) != NULL) {
+ if (strcmp (pwd->pw_name, user_name) == 0) {
+ continue;
+ }
+ if (pwd->pw_gid == grp->gr_gid) {
+ fprintf (stderr,
+ _("%s: group %s is the primary group of another user and is not removed.\n"),
+ Prog, grp->gr_name);
+ break;
+ }
+ }
+ endpwent ();
+ }
+
+ if (NULL == pwd) {
+ /*
+ * We can remove this group, it is not the primary
+ * group of any remaining user.
+ */
+ if (gr_remove (user_name) == 0) {
+ fprintf (stderr,
+ _("%s: cannot remove entry '%s' from %s\n"),
+ Prog, user_name, gr_dbname ());
+ fail_exit (E_GRP_UPDATE);
+ }
+
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_GROUP, Prog,
+ "deleting group",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif /* WITH_AUDIT */
+ SYSLOG ((LOG_INFO,
+ "removed group '%s' owned by '%s'\n",
+ user_name, user_name));
+
+#ifdef SHADOWGRP
+ if (sgr_locate (user_name) != NULL) {
+ if (sgr_remove (user_name) == 0) {
+ fprintf (stderr,
+ _("%s: cannot remove entry '%s' from %s\n"),
+ Prog, user_name, sgr_dbname ());
+ fail_exit (E_GRP_UPDATE);
+ }
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_GROUP, Prog,
+ "deleting shadow group",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_SUCCESS);
+#endif /* WITH_AUDIT */
+ SYSLOG ((LOG_INFO,
+ "removed shadow group '%s' owned by '%s'\n",
+ user_name, user_name));
+
+ }
+#endif /* SHADOWGRP */
+ }
+}
+
+/*
+ * close_files - close all of the files that were opened
+ *
+ * close_files() closes all of the files that were opened for this
+ * new user. This causes any modified entries to be written out.
+ */
+static void close_files (void)
+{
+ if (pw_close () == 0) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
+ fail_exit (E_PW_UPDATE);
+ }
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ pw_locked = false;
+
+ if (is_shadow_pwd) {
+ if (spw_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ()));
+ fail_exit (E_PW_UPDATE);
+ }
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ spw_locked = false;
+ }
+
+ if (gr_close () == 0) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", gr_dbname ()));
+ fail_exit (E_GRP_UPDATE);
+ }
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+ gr_locked = false;
+
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", sgr_dbname ()));
+ fail_exit (E_GRP_UPDATE);
+ }
+
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+ sgr_locked = false;
+ }
+#endif /* SHADOWGRP */
+
+#ifdef ENABLE_SUBIDS
+ if (is_sub_uid) {
+ if (sub_uid_close () == 0) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, sub_uid_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", sub_uid_dbname ()));
+ fail_exit (E_SUB_UID_UPDATE);
+ }
+ if (sub_uid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ()));
+ /* continue */
+ }
+ sub_uid_locked = false;
+ }
+
+ if (is_sub_gid) {
+ if (sub_gid_close () == 0) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, sub_gid_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", sub_gid_dbname ()));
+ fail_exit (E_SUB_GID_UPDATE);
+ }
+ if (sub_gid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ()));
+ /* continue */
+ }
+ sub_gid_locked = false;
+ }
+#endif /* ENABLE_SUBIDS */
+}
+
+/*
+ * fail_exit - exit with a failure code after unlocking the files
+ */
+static void fail_exit (int code)
+{
+ if (pw_locked) {
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ }
+ if (gr_locked) {
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+ }
+ if (spw_locked) {
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ }
+#ifdef SHADOWGRP
+ if (sgr_locked) {
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+ }
+#endif /* SHADOWGRP */
+#ifdef ENABLE_SUBIDS
+ if (sub_uid_locked) {
+ if (sub_uid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ()));
+ /* continue */
+ }
+ }
+ if (sub_gid_locked) {
+ if (sub_gid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ()));
+ /* continue */
+ }
+ }
+#endif /* ENABLE_SUBIDS */
+
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "deleting user",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+
+ exit (code);
+}
+
+/*
+ * open_files - lock and open the password files
+ *
+ * open_files() opens the two password files.
+ */
+
+static void open_files (void)
+{
+ if (pw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, pw_dbname ());
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "locking password file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_PW_UPDATE);
+ }
+ pw_locked = true;
+ if (pw_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"), Prog, pw_dbname ());
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "opening password file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_PW_UPDATE);
+ }
+ if (is_shadow_pwd) {
+ if (spw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, spw_dbname ());
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "locking shadow password file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_PW_UPDATE);
+ }
+ spw_locked = true;
+ if (spw_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, spw_dbname ());
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "opening shadow password file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_PW_UPDATE);
+ }
+ }
+ if (gr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, gr_dbname ());
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "locking group file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_GRP_UPDATE);
+ }
+ gr_locked = true;
+ if (gr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "opening group file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_GRP_UPDATE);
+ }
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sgr_dbname ());
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "locking shadow group file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_GRP_UPDATE);
+ }
+ sgr_locked= true;
+ if (sgr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"),
+ Prog, sgr_dbname ());
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "opening shadow group file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_GRP_UPDATE);
+ }
+ }
+#endif /* SHADOWGRP */
+#ifdef ENABLE_SUBIDS
+ if (is_sub_uid) {
+ if (sub_uid_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sub_uid_dbname ());
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "locking subordinate user file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_SUB_UID_UPDATE);
+ }
+ sub_uid_locked = true;
+ if (sub_uid_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"), Prog, sub_uid_dbname ());
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "opening subordinate user file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_SUB_UID_UPDATE);
+ }
+ }
+ if (is_sub_gid) {
+ if (sub_gid_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sub_gid_dbname ());
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "locking subordinate group file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_SUB_GID_UPDATE);
+ }
+ sub_gid_locked = true;
+ if (sub_gid_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"), Prog, sub_gid_dbname ());
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "opening subordinate group file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_SUB_GID_UPDATE);
+ }
+ }
+#endif /* ENABLE_SUBIDS */
+}
+
+/*
+ * update_user - delete the user entries
+ *
+ * update_user() deletes the password file entries for this user
+ * and will update the group entries as required.
+ */
+static void update_user (void)
+{
+ if (pw_remove (user_name) == 0) {
+ fprintf (stderr,
+ _("%s: cannot remove entry '%s' from %s\n"),
+ Prog, user_name, pw_dbname ());
+ fail_exit (E_PW_UPDATE);
+ }
+ if ( is_shadow_pwd
+ && (spw_locate (user_name) != NULL)
+ && (spw_remove (user_name) == 0)) {
+ fprintf (stderr,
+ _("%s: cannot remove entry '%s' from %s\n"),
+ Prog, user_name, spw_dbname ());
+ fail_exit (E_PW_UPDATE);
+ }
+#ifdef ENABLE_SUBIDS
+ if (is_sub_uid && sub_uid_remove(user_name, 0, ULONG_MAX) == 0) {
+ fprintf (stderr,
+ _("%s: cannot remove entry %lu from %s\n"),
+ Prog, (unsigned long)user_id, sub_uid_dbname ());
+ fail_exit (E_SUB_UID_UPDATE);
+ }
+ if (is_sub_gid && sub_gid_remove(user_name, 0, ULONG_MAX) == 0) {
+ fprintf (stderr,
+ _("%s: cannot remove entry %lu from %s\n"),
+ Prog, (unsigned long)user_id, sub_gid_dbname ());
+ fail_exit (E_SUB_GID_UPDATE);
+ }
+#endif /* ENABLE_SUBIDS */
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "deleting user entries",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_SUCCESS);
+#endif /* WITH_AUDIT */
+ SYSLOG ((LOG_INFO, "delete user '%s'\n", user_name));
+}
+
+/*
+ * user_cancel - cancel cron and at jobs
+ *
+ * user_cancel calls a script for additional cleanups like removal of
+ * cron, at, or print jobs.
+ */
+
+static void user_cancel (const char *user)
+{
+ const char *cmd;
+ const char *argv[3];
+ int status;
+
+ cmd = getdef_str ("USERDEL_CMD");
+ if (NULL == cmd) {
+ return;
+ }
+ argv[0] = cmd;
+ argv[1] = user;
+ argv[2] = (char *)0;
+ (void) run_command (cmd, argv, NULL, &status);
+}
+
+#ifdef EXTRA_CHECK_HOME_DIR
+static bool path_prefix (const char *s1, const char *s2)
+{
+ return ( (strncmp (s2, s1, strlen (s1)) == 0)
+ && ( ('\0' == s2[strlen (s1)])
+ || ('/' == s2[strlen (s1)])));
+}
+#endif /* EXTRA_CHECK_HOME_DIR */
+
+/*
+ * is_owner - Check if path is owned by uid
+ *
+ * Return
+ * 1: path exists and is owned by uid
+ * 0: path is not owned by uid, or a failure occurred
+ * -1: path does not exist
+ */
+static int is_owner (uid_t uid, const char *path)
+{
+ struct stat st;
+
+ errno = 0;
+ if (stat (path, &st) != 0) {
+ if ((ENOENT == errno) || (ENOTDIR == errno)) {
+ /* The file or directory does not exist */
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ return (st.st_uid == uid) ? 1 : 0;
+}
+
+static int remove_mailbox (void)
+{
+ const char *maildir;
+ char mailfile[1024];
+ int i;
+ int errors = 0;
+
+ maildir = getdef_str ("MAIL_DIR");
+#ifdef MAIL_SPOOL_DIR
+ if ((NULL == maildir) && (getdef_str ("MAIL_FILE") == NULL)) {
+ maildir = MAIL_SPOOL_DIR;
+ }
+#endif /* MAIL_SPOOL_DIR */
+ if (NULL == maildir) {
+ return 0;
+ }
+ snprintf (mailfile, sizeof mailfile, "%s/%s", maildir, user_name);
+
+ if (access (mailfile, F_OK) != 0) {
+ if (ENOENT == errno) {
+ fprintf (stderr,
+ _("%s: %s mail spool (%s) not found\n"),
+ Prog, user_name, mailfile);
+ return 0;
+ } else {
+ fprintf (stderr,
+ _("%s: warning: can't remove %s: %s\n"),
+ Prog, mailfile, strerror (errno));
+ SYSLOG ((LOG_ERR, "Cannot remove %s: %s", mailfile, strerror (errno)));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "deleting mail file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ return -1;
+ }
+ }
+
+ if (fflg) {
+ if (unlink (mailfile) != 0) {
+ fprintf (stderr,
+ _("%s: warning: can't remove %s: %s\n"),
+ Prog, mailfile, strerror (errno));
+ SYSLOG ((LOG_ERR, "Cannot remove %s: %s", mailfile, strerror (errno)));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "deleting mail file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ errors = 1;
+ /* continue */
+ }
+#ifdef WITH_AUDIT
+ else
+ {
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "deleting mail file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_SUCCESS);
+ }
+#endif /* WITH_AUDIT */
+ return errors;
+ }
+ i = is_owner (user_id, mailfile);
+ if (i == 0) {
+ fprintf (stderr,
+ _("%s: %s not owned by %s, not removing\n"),
+ Prog, mailfile, user_name);
+ SYSLOG ((LOG_ERR,
+ "%s not owned by %s, not removed",
+ mailfile, strerror (errno)));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "deleting mail file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ return 1;
+ } else if (i == -1) {
+ return 0; /* mailbox doesn't exist */
+ }
+ if (unlink (mailfile) != 0) {
+ fprintf (stderr,
+ _("%s: warning: can't remove %s: %s\n"),
+ Prog, mailfile, strerror (errno));
+ SYSLOG ((LOG_ERR, "Cannot remove %s: %s", mailfile, strerror (errno)));
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "deleting mail file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ errors = 1;
+ /* continue */
+ }
+#ifdef WITH_AUDIT
+ else
+ {
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "deleting mail file",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_SUCCESS);
+ }
+#endif /* WITH_AUDIT */
+ return errors;
+}
+
+#ifdef WITH_TCB
+static int remove_tcbdir (const char *user_name, uid_t user_id)
+{
+ char *buf;
+ int ret = 0;
+ size_t bufsize = (sizeof TCB_DIR) + strlen (user_name) + 2;
+
+ if (!getdef_bool ("USE_TCB")) {
+ return 0;
+ }
+
+ buf = malloc (buflen);
+ if (NULL == buf) {
+ fprintf (stderr, _("%s: Can't allocate memory, "
+ "tcb entry for %s not removed.\n"),
+ Prog, user_name);
+ return 1;
+ }
+ snprintf (buf, buflen, TCB_DIR "/%s", user_name);
+ if (shadowtcb_drop_priv () == SHADOWTCB_FAILURE) {
+ fprintf (stderr, _("%s: Cannot drop privileges: %s\n"),
+ Prog, strerror (errno));
+ shadowtcb_gain_priv ();
+ free (buf);
+ return 1;
+ }
+ /* Only remove directory contents with dropped privileges.
+ * We will regain them and remove the user's tcb directory afterwards.
+ */
+ if (remove_tree (buf, false) != 0) {
+ fprintf (stderr, _("%s: Cannot remove the content of %s: %s\n"),
+ Prog, buf, strerror (errno));
+ shadowtcb_gain_priv ();
+ free (buf);
+ return 1;
+ }
+ shadowtcb_gain_priv ();
+ free (buf);
+ if (shadowtcb_remove (user_name) == SHADOWTCB_FAILURE) {
+ fprintf (stderr, _("%s: Cannot remove tcb files for %s: %s\n"),
+ Prog, user_name, strerror (errno));
+ ret = 1;
+ }
+ return ret;
+}
+#endif /* WITH_TCB */
+
+/*
+ * main - userdel command
+ */
+int main (int argc, char **argv)
+{
+ int errors = 0; /* Error in the removal of the home directory */
+
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ pam_handle_t *pamh = NULL;
+ int retval;
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+
+ /*
+ * Get my name so that I can use it to report errors.
+ */
+ Prog = Basename (argv[0]);
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ OPENLOG ("userdel");
+#ifdef WITH_AUDIT
+ audit_help_open ();
+#endif /* WITH_AUDIT */
+
+ {
+ /*
+ * Parse the command line options.
+ */
+ int c;
+ static struct option long_options[] = {
+ {"force", no_argument, NULL, 'f'},
+ {"help", no_argument, NULL, 'h'},
+ {"remove", no_argument, NULL, 'r'},
+ {"root", required_argument, NULL, 'R'},
+#ifdef WITH_SELINUX
+ {"selinux-user", no_argument, NULL, 'Z'},
+#endif /* WITH_SELINUX */
+ {NULL, 0, NULL, '\0'}
+ };
+ while ((c = getopt_long (argc, argv,
+#ifdef WITH_SELINUX
+ "fhrR:Z",
+#else /* !WITH_SELINUX */
+ "fhrR:",
+#endif /* !WITH_SELINUX */
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'f': /* force remove even if not owned by user */
+ fflg = true;
+ break;
+ case 'h':
+ usage (E_SUCCESS);
+ break;
+ case 'r': /* remove home dir and mailbox */
+ rflg = true;
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+#ifdef WITH_SELINUX
+ case 'Z':
+ if (is_selinux_enabled () > 0) {
+ Zflg = true;
+ } else {
+ fprintf (stderr,
+ _("%s: -Z requires SELinux enabled kernel\n"),
+ Prog);
+
+ exit (E_BAD_ARG);
+ }
+ break;
+#endif /* WITH_SELINUX */
+ default:
+ usage (E_USAGE);
+ }
+ }
+ }
+
+ if ((optind + 1) != argc) {
+ usage (E_USAGE);
+ }
+
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ {
+ struct passwd *pampw;
+ pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+ if (pampw == NULL) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ exit (E_PW_UPDATE);
+ }
+
+ retval = pam_start ("userdel", pampw->pw_name, &conv, &pamh);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_authenticate (pamh, 0);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_acct_mgmt (pamh, 0);
+ }
+
+ if (PAM_SUCCESS != retval) {
+ fprintf (stderr, _("%s: PAM: %s\n"),
+ Prog, pam_strerror (pamh, retval));
+ SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval)));
+ if (NULL != pamh) {
+ (void) pam_end (pamh, retval);
+ }
+ exit (E_PW_UPDATE);
+ }
+ (void) pam_end (pamh, retval);
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+
+ is_shadow_pwd = spw_file_present ();
+#ifdef SHADOWGRP
+ is_shadow_grp = sgr_file_present ();
+#endif /* SHADOWGRP */
+#ifdef ENABLE_SUBIDS
+ is_sub_uid = sub_uid_file_present ();
+ is_sub_gid = sub_gid_file_present ();
+#endif /* ENABLE_SUBIDS */
+
+ /*
+ * Start with a quick check to see if the user exists.
+ */
+ user_name = argv[argc - 1];
+ {
+ struct passwd *pwd;
+ pwd = getpwnam (user_name); /* local, no need for xgetpwnam */
+ if (NULL == pwd) {
+ fprintf (stderr, _("%s: user '%s' does not exist\n"),
+ Prog, user_name);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "deleting user not found",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ exit (E_NOTFOUND);
+ }
+ user_id = pwd->pw_uid;
+ user_gid = pwd->pw_gid;
+ user_home = xstrdup (pwd->pw_dir);
+ }
+#ifdef WITH_TCB
+ if (shadowtcb_set_user (user_name) == SHADOWTCB_FAILURE) {
+ exit (E_NOTFOUND);
+ }
+#endif /* WITH_TCB */
+#ifdef USE_NIS
+
+ /*
+ * Now make sure it isn't an NIS user.
+ */
+ if (__ispwNIS ()) {
+ char *nis_domain;
+ char *nis_master;
+
+ fprintf (stderr,
+ _("%s: user %s is a NIS user\n"), Prog, user_name);
+ if ( !yp_get_default_domain (&nis_domain)
+ && !yp_master (nis_domain, "passwd.byname", &nis_master)) {
+ fprintf (stderr,
+ _("%s: %s is the NIS master\n"),
+ Prog, nis_master);
+ }
+ exit (E_NOTFOUND);
+ }
+#endif /* USE_NIS */
+ /*
+ * Check to make certain the user isn't logged in.
+ * Note: This is a best effort basis. The user may log in between,
+ * a cron job may be started on her behalf, etc.
+ */
+ if (user_busy (user_name, user_id) != 0) {
+ if (!fflg) {
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "deleting user logged in",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ exit (E_USER_BUSY);
+ }
+ }
+
+ /*
+ * Do the hard stuff - open the files, create the user entries,
+ * create the home directory, then close and update the files.
+ */
+ open_files ();
+ update_user ();
+ update_groups ();
+
+ if (rflg) {
+ errors += remove_mailbox ();
+ }
+ if (rflg) {
+ int home_owned = is_owner (user_id, user_home);
+ if (-1 == home_owned) {
+ fprintf (stderr,
+ _("%s: %s home directory (%s) not found\n"),
+ Prog, user_name, user_home);
+ rflg = 0;
+ } else if ((0 == home_owned) && !fflg) {
+ fprintf (stderr,
+ _("%s: %s not owned by %s, not removing\n"),
+ Prog, user_home, user_name);
+ rflg = 0;
+ errors++;
+ /* continue */
+ }
+ }
+
+#ifdef EXTRA_CHECK_HOME_DIR
+ /* This may be slow, the above should be good enough. */
+ if (rflg && !fflg) {
+ struct passwd *pwd;
+ /*
+ * For safety, refuse to remove the home directory if it
+ * would result in removing some other user's home
+ * directory. Still not perfect so be careful, but should
+ * prevent accidents if someone has /home or / as home
+ * directory... --marekm
+ */
+ setpwent ();
+ while ((pwd = getpwent ())) {
+ if (strcmp (pwd->pw_name, user_name) == 0) {
+ continue;
+ }
+ if (path_prefix (user_home, pwd->pw_dir)) {
+ fprintf (stderr,
+ _("%s: not removing directory %s (would remove home of user %s)\n"),
+ Prog, user_home, pwd->pw_name);
+ rflg = false;
+ errors++;
+ /* continue */
+ break;
+ }
+ }
+ endpwent ();
+ }
+#endif /* EXTRA_CHECK_HOME_DIR */
+
+ if (rflg) {
+ if (remove_tree (user_home, true) != 0) {
+ fprintf (stderr,
+ _("%s: error removing directory %s\n"),
+ Prog, user_home);
+ errors++;
+ /* continue */
+ }
+#ifdef WITH_AUDIT
+ else
+ {
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "deleting home directory",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_SUCCESS);
+ }
+#endif /* WITH_AUDIT */
+ }
+#ifdef WITH_AUDIT
+ if (0 != errors) {
+ audit_logger (AUDIT_DEL_USER, Prog,
+ "deleting home directory",
+ user_name, AUDIT_NO_ID,
+ SHADOW_AUDIT_FAILURE);
+ }
+#endif /* WITH_AUDIT */
+
+#ifdef WITH_SELINUX
+ if (Zflg) {
+ if (del_seuser (user_name) != 0) {
+ fprintf (stderr,
+ _("%s: warning: the user name %s to SELinux user mapping removal failed.\n"),
+ Prog, user_name);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "removing SELinux user mapping",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_SE_UPDATE);
+ }
+ }
+#endif /* WITH_SELINUX */
+
+ /*
+ * Cancel any crontabs or at jobs. Have to do this before we remove
+ * the entry from /etc/passwd.
+ */
+ user_cancel (user_name);
+ close_files ();
+
+#ifdef WITH_TCB
+ errors += remove_tcbdir (user_name, user_id);
+#endif /* WITH_TCB */
+
+ nscd_flush_cache ("passwd");
+ nscd_flush_cache ("group");
+
+ return ((0 != errors) ? E_HOMEDIR : E_SUCCESS);
+}
+
diff --git a/src/usermod.c b/src/usermod.c
new file mode 100644
index 0000000..9c5e479
--- /dev/null
+++ b/src/usermod.c
@@ -0,0 +1,2285 @@
+/*
+ * Copyright (c) 1991 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <lastlog.h>
+#include <pwd.h>
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include "chkname.h"
+#include "defines.h"
+#include "faillog.h"
+#include "getdef.h"
+#include "groupio.h"
+#include "nscd.h"
+#include "prototypes.h"
+#include "pwauth.h"
+#include "pwio.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+#include "shadowio.h"
+#ifdef ENABLE_SUBIDS
+#include "subordinateio.h"
+#endif /* ENABLE_SUBIDS */
+#ifdef WITH_TCB
+#include "tcbfuncs.h"
+#endif
+
+/*
+ * exit status values
+ * for E_GRP_UPDATE and E_NOSPACE (not used yet), other update requests
+ * will be implemented (as documented in the Solaris 2.x man page).
+ */
+/*@-exitarg@*/
+#define E_SUCCESS 0 /* success */
+#define E_PW_UPDATE 1 /* can't update password file */
+#define E_USAGE 2 /* invalid command syntax */
+#define E_BAD_ARG 3 /* invalid argument to option */
+#define E_UID_IN_USE 4 /* UID already in use (and no -o) */
+/* #define E_BAD_PWFILE 5 passwd file contains errors */
+#define E_NOTFOUND 6 /* specified user/group doesn't exist */
+#define E_USER_BUSY 8 /* user to modify is logged in */
+#define E_NAME_IN_USE 9 /* username already in use */
+#define E_GRP_UPDATE 10 /* can't update group file */
+/* #define E_NOSPACE 11 insufficient space to move home dir */
+#define E_HOMEDIR 12 /* unable to complete home dir move */
+#define E_SE_UPDATE 13 /* can't update SELinux user mapping */
+#ifdef ENABLE_SUBIDS
+#define E_SUB_UID_UPDATE 16 /* can't update the subordinate uid file */
+#define E_SUB_GID_UPDATE 18 /* can't update the subordinate gid file */
+#endif /* ENABLE_SUBIDS */
+
+#define VALID(s) (strcspn (s, ":\n") == strlen (s))
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static char *user_name;
+static char *user_newname;
+static char *user_pass;
+static uid_t user_id;
+static uid_t user_newid;
+static gid_t user_gid;
+static gid_t user_newgid;
+static char *user_comment;
+static char *user_newcomment;
+static char *user_home;
+static char *user_newhome;
+static char *user_shell;
+#ifdef WITH_SELINUX
+static const char *user_selinux = "";
+#endif /* WITH_SELINUX */
+static char *user_newshell;
+static long user_expire;
+static long user_newexpire;
+static long user_inactive;
+static long user_newinactive;
+static long sys_ngroups;
+static char **user_groups; /* NULL-terminated list */
+
+static bool
+ aflg = false, /* append to existing secondary group set */
+ cflg = false, /* new comment (GECOS) field */
+ dflg = false, /* new home directory */
+ eflg = false, /* days since 1970-01-01 when account becomes expired */
+ fflg = false, /* days until account with expired password is locked */
+ gflg = false, /* new primary group ID */
+ Gflg = false, /* new secondary group set */
+ Lflg = false, /* lock the password */
+ lflg = false, /* new user name */
+ mflg = false, /* create user's home directory if it doesn't exist */
+ oflg = false, /* permit non-unique user ID to be specified with -u */
+ pflg = false, /* new encrypted password */
+ sflg = false, /* new shell program */
+#ifdef WITH_SELINUX
+ Zflg = false, /* new selinux user */
+#endif
+#ifdef ENABLE_SUBIDS
+ vflg = false, /* add subordinate uids */
+ Vflg = false, /* delete subordinate uids */
+ wflg = false, /* add subordinate gids */
+ Wflg = false, /* delete subordinate gids */
+#endif /* ENABLE_SUBIDS */
+ uflg = false, /* specify new user ID */
+ Uflg = false; /* unlock the password */
+
+static bool is_shadow_pwd;
+
+#ifdef SHADOWGRP
+static bool is_shadow_grp;
+#endif
+
+#ifdef ENABLE_SUBIDS
+static bool is_sub_uid = false;
+static bool is_sub_gid = false;
+#endif /* ENABLE_SUBIDS */
+
+static bool pw_locked = false;
+static bool spw_locked = false;
+static bool gr_locked = false;
+#ifdef SHADOWGRP
+static bool sgr_locked = false;
+#endif
+#ifdef ENABLE_SUBIDS
+static bool sub_uid_locked = false;
+static bool sub_gid_locked = false;
+#endif /* ENABLE_SUBIDS */
+
+
+/* local function prototypes */
+static void date_to_str (/*@unique@*//*@out@*/char *buf, size_t maxsize,
+ long int date);
+static int get_groups (char *);
+static /*@noreturn@*/void usage (int status);
+static void new_pwent (struct passwd *);
+static void new_spent (struct spwd *);
+static /*@noreturn@*/void fail_exit (int);
+static void update_group (void);
+
+#ifdef SHADOWGRP
+static void update_gshadow (void);
+#endif
+static void grp_update (void);
+
+static void process_flags (int, char **);
+static void close_files (void);
+static void open_files (void);
+static void usr_update (void);
+static void move_home (void);
+static void update_lastlog (void);
+static void update_faillog (void);
+
+#ifndef NO_MOVE_MAILBOX
+static void move_mailbox (void);
+#endif
+
+static void date_to_str (/*@unique@*//*@out@*/char *buf, size_t maxsize,
+ long int date)
+{
+ struct tm *tp;
+
+ if (date < 0) {
+ strncpy (buf, "never", maxsize);
+ } else {
+ time_t t = (time_t) date;
+ tp = gmtime (&t);
+#ifdef HAVE_STRFTIME
+ strftime (buf, maxsize, "%Y-%m-%d", tp);
+#else
+ (void) snprintf (buf, maxsize, "%04d-%02d-%02d",
+ tp->tm_year + 1900,
+ tp->tm_mon + 1,
+ tp->tm_mday);
+#endif /* HAVE_STRFTIME */
+ }
+ buf[maxsize - 1] = '\0';
+}
+
+/*
+ * get_groups - convert a list of group names to an array of group IDs
+ *
+ * get_groups() takes a comma-separated list of group names and
+ * converts it to a NULL-terminated array. Any unknown group names are
+ * reported as errors.
+ */
+static int get_groups (char *list)
+{
+ char *cp;
+ const struct group *grp;
+ int errors = 0;
+ int ngroups = 0;
+
+ /*
+ * Initialize the list to be empty
+ */
+ user_groups[0] = (char *) 0;
+
+ if ('\0' == *list) {
+ return 0;
+ }
+
+ /*
+ * So long as there is some data to be converted, strip off each
+ * name and look it up. A mix of numerical and string values for
+ * group identifiers is permitted.
+ */
+ do {
+ /*
+ * Strip off a single name from the list
+ */
+ cp = strchr (list, ',');
+ if (NULL != cp) {
+ *cp = '\0';
+ cp++;
+ }
+
+ /*
+ * Names starting with digits are treated as numerical GID
+ * values, otherwise the string is looked up as is.
+ */
+ grp = getgr_nam_gid (list);
+
+ /*
+ * There must be a match, either by GID value or by
+ * string name.
+ */
+ if (NULL == grp) {
+ fprintf (stderr, _("%s: group '%s' does not exist\n"),
+ Prog, list);
+ errors++;
+ }
+ list = cp;
+
+ /*
+ * If the group doesn't exist, don't dump core. Instead,
+ * try the next one. --marekm
+ */
+ if (NULL == grp) {
+ continue;
+ }
+
+#ifdef USE_NIS
+ /*
+ * Don't add this group if they are an NIS group. Tell the
+ * user to go to the server for this group.
+ */
+ if (__isgrNIS ()) {
+ fprintf (stderr,
+ _("%s: group '%s' is a NIS group.\n"),
+ Prog, grp->gr_name);
+ gr_free ((struct group *)grp);
+ continue;
+ }
+#endif
+
+ if (ngroups == sys_ngroups) {
+ fprintf (stderr,
+ _("%s: too many groups specified (max %d).\n"),
+ Prog, ngroups);
+ gr_free ((struct group *)grp);
+ break;
+ }
+
+ /*
+ * Add the group name to the user's list of groups.
+ */
+ user_groups[ngroups++] = xstrdup (grp->gr_name);
+ gr_free ((struct group *)grp);
+ } while (NULL != list);
+
+ user_groups[ngroups] = (char *) 0;
+
+ /*
+ * Any errors in finding group names are fatal
+ */
+ if (0 != errors) {
+ return -1;
+ }
+
+ return 0;
+}
+
+#ifdef ENABLE_SUBIDS
+struct ulong_range
+{
+ unsigned long first;
+ unsigned long last;
+};
+
+static struct ulong_range getulong_range(const char *str)
+{
+ struct ulong_range result = { .first = ULONG_MAX, .last = 0 };
+ long long first, last;
+ char *pos;
+
+ errno = 0;
+ first = strtoll(str, &pos, 10);
+ if (('\0' == *str) || ('-' != *pos ) || (ERANGE == errno) ||
+ (first != (unsigned long int)first))
+ goto out;
+
+ errno = 0;
+ last = strtoll(pos + 1, &pos, 10);
+ if (('\0' != *pos ) || (ERANGE == errno) ||
+ (last != (unsigned long int)last))
+ goto out;
+
+ if (first > last)
+ goto out;
+
+ result.first = (unsigned long int)first;
+ result.last = (unsigned long int)last;
+out:
+ return result;
+
+}
+
+struct ulong_range_list_entry {
+ struct ulong_range_list_entry *next;
+ struct ulong_range range;
+};
+
+static struct ulong_range_list_entry *add_sub_uids = NULL, *del_sub_uids = NULL;
+static struct ulong_range_list_entry *add_sub_gids = NULL, *del_sub_gids = NULL;
+
+static int prepend_range(const char *str, struct ulong_range_list_entry **head)
+{
+ struct ulong_range range;
+ struct ulong_range_list_entry *entry;
+ range = getulong_range(str);
+ if (range.first > range.last)
+ return 0;
+
+ entry = malloc(sizeof(*entry));
+ if (!entry) {
+ fprintf (stderr,
+ _("%s: failed to allocate memory: %s\n"),
+ Prog, strerror (errno));
+ return 0;
+ }
+ entry->next = *head;
+ entry->range = range;
+ *head = entry;
+ return 1;
+}
+#endif /* ENABLE_SUBIDS */
+
+/*
+ * usage - display usage message and exit
+ */
+static /*@noreturn@*/void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options] LOGIN\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -c, --comment COMMENT new value of the GECOS field\n"), usageout);
+ (void) fputs (_(" -d, --home HOME_DIR new home directory for the user account\n"), usageout);
+ (void) fputs (_(" -e, --expiredate EXPIRE_DATE set account expiration date to EXPIRE_DATE\n"), usageout);
+ (void) fputs (_(" -f, --inactive INACTIVE set password inactive after expiration\n"
+ " to INACTIVE\n"), usageout);
+ (void) fputs (_(" -g, --gid GROUP force use GROUP as new primary group\n"), usageout);
+ (void) fputs (_(" -G, --groups GROUPS new list of supplementary GROUPS\n"), usageout);
+ (void) fputs (_(" -a, --append append the user to the supplemental GROUPS\n"
+ " mentioned by the -G option without removing\n"
+ " him/her from other groups\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -l, --login NEW_LOGIN new value of the login name\n"), usageout);
+ (void) fputs (_(" -L, --lock lock the user account\n"), usageout);
+ (void) fputs (_(" -m, --move-home move contents of the home directory to the\n"
+ " new location (use only with -d)\n"), usageout);
+ (void) fputs (_(" -o, --non-unique allow using duplicate (non-unique) UID\n"), usageout);
+ (void) fputs (_(" -p, --password PASSWORD use encrypted password for the new password\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs (_(" -s, --shell SHELL new login shell for the user account\n"), usageout);
+ (void) fputs (_(" -u, --uid UID new UID for the user account\n"), usageout);
+ (void) fputs (_(" -U, --unlock unlock the user account\n"), usageout);
+#ifdef ENABLE_SUBIDS
+ (void) fputs (_(" -v, --add-subuids FIRST-LAST add range of subordinate uids\n"), usageout);
+ (void) fputs (_(" -V, --del-subuids FIRST-LAST remove range of subordinate uids\n"), usageout);
+ (void) fputs (_(" -w, --add-subgids FIRST-LAST add range of subordinate gids\n"), usageout);
+ (void) fputs (_(" -W, --del-subgids FIRST-LAST remove range of subordinate gids\n"), usageout);
+#endif /* ENABLE_SUBIDS */
+#ifdef WITH_SELINUX
+ (void) fputs (_(" -Z, --selinux-user SEUSER new SELinux user mapping for the user account\n"), usageout);
+#endif /* WITH_SELINUX */
+ (void) fputs ("\n", usageout);
+ exit (status);
+}
+
+/*
+ * update encrypted password string (for both shadow and non-shadow
+ * passwords)
+ */
+static char *new_pw_passwd (char *pw_pass)
+{
+ if (Lflg && ('!' != pw_pass[0])) {
+ char *buf = xmalloc (strlen (pw_pass) + 2);
+
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "updating passwd",
+ user_newname, (unsigned int) user_newid, 0);
+#endif
+ SYSLOG ((LOG_INFO, "lock user '%s' password", user_newname));
+ strcpy (buf, "!");
+ strcat (buf, pw_pass);
+ pw_pass = buf;
+ } else if (Uflg && pw_pass[0] == '!') {
+ char *s;
+
+ if (pw_pass[1] == '\0') {
+ fprintf (stderr,
+ _("%s: unlocking the user's password would result in a passwordless account.\n"
+ "You should set a password with usermod -p to unlock this user's password.\n"),
+ Prog);
+ return pw_pass;
+ }
+
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "updating password",
+ user_newname, (unsigned int) user_newid, 0);
+#endif
+ SYSLOG ((LOG_INFO, "unlock user '%s' password", user_newname));
+ s = pw_pass;
+ while ('\0' != *s) {
+ *s = *(s + 1);
+ s++;
+ }
+ } else if (pflg) {
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing password",
+ user_newname, (unsigned int) user_newid, 1);
+#endif
+ SYSLOG ((LOG_INFO, "change user '%s' password", user_newname));
+ pw_pass = xstrdup (user_pass);
+ }
+ return pw_pass;
+}
+
+/*
+ * new_pwent - initialize the values in a password file entry
+ *
+ * new_pwent() takes all of the values that have been entered and fills
+ * in a (struct passwd) with them.
+ */
+static void new_pwent (struct passwd *pwent)
+{
+ if (lflg) {
+ if (pw_locate (user_newname) != NULL) {
+ /* This should never happen.
+ * It was already checked that the user doesn't
+ * exist on the system.
+ */
+ fprintf (stderr,
+ _("%s: user '%s' already exists in %s\n"),
+ Prog, user_newname, pw_dbname ());
+ fail_exit (E_NAME_IN_USE);
+ }
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing name",
+ user_newname, (unsigned int) user_newid, 1);
+#endif
+ SYSLOG ((LOG_INFO,
+ "change user name '%s' to '%s'",
+ pwent->pw_name, user_newname));
+ pwent->pw_name = xstrdup (user_newname);
+ }
+ /* Update the password in passwd if there is no shadow file or if
+ * the password is currently in passwd (pw_passwd != "x").
+ * We do not force the usage of shadow passwords if they are not
+ * used for this account.
+ */
+ if ( (!is_shadow_pwd)
+ || (strcmp (pwent->pw_passwd, SHADOW_PASSWD_STRING) != 0)) {
+ pwent->pw_passwd = new_pw_passwd (pwent->pw_passwd);
+ }
+
+ if (uflg) {
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing uid",
+ user_newname, (unsigned int) user_newid, 1);
+#endif
+ SYSLOG ((LOG_INFO,
+ "change user '%s' UID from '%d' to '%d'",
+ pwent->pw_name, pwent->pw_uid, user_newid));
+ pwent->pw_uid = user_newid;
+ }
+ if (gflg) {
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing primary group",
+ user_newname, (unsigned int) user_newid, 1);
+#endif
+ SYSLOG ((LOG_INFO,
+ "change user '%s' GID from '%d' to '%d'",
+ pwent->pw_name, pwent->pw_gid, user_newgid));
+ pwent->pw_gid = user_newgid;
+ }
+ if (cflg) {
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing comment",
+ user_newname, (unsigned int) user_newid, 1);
+#endif
+ pwent->pw_gecos = user_newcomment;
+ }
+
+ if (dflg) {
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing home directory",
+ user_newname, (unsigned int) user_newid, 1);
+#endif
+ SYSLOG ((LOG_INFO,
+ "change user '%s' home from '%s' to '%s'",
+ pwent->pw_name, pwent->pw_dir, user_newhome));
+ pwent->pw_dir = user_newhome;
+ }
+ if (sflg) {
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing user shell",
+ user_newname, (unsigned int) user_newid, 1);
+#endif
+ SYSLOG ((LOG_INFO,
+ "change user '%s' shell from '%s' to '%s'",
+ pwent->pw_name, pwent->pw_shell, user_newshell));
+ pwent->pw_shell = user_newshell;
+ }
+}
+
+/*
+ * new_spent - initialize the values in a shadow password file entry
+ *
+ * new_spent() takes all of the values that have been entered and fills
+ * in a (struct spwd) with them.
+ */
+static void new_spent (struct spwd *spent)
+{
+ if (lflg) {
+ if (spw_locate (user_newname) != NULL) {
+ fprintf (stderr,
+ _("%s: user '%s' already exists in %s\n"),
+ Prog, user_newname, spw_dbname ());
+ fail_exit (E_NAME_IN_USE);
+ }
+ spent->sp_namp = xstrdup (user_newname);
+ }
+
+ if (fflg) {
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing inactive days",
+ user_newname, (unsigned int) user_newid, 1);
+#endif
+ SYSLOG ((LOG_INFO,
+ "change user '%s' inactive from '%ld' to '%ld'",
+ spent->sp_namp, spent->sp_inact, user_newinactive));
+ spent->sp_inact = user_newinactive;
+ }
+ if (eflg) {
+ /* log dates rather than numbers of days. */
+ char new_exp[16], old_exp[16];
+ date_to_str (new_exp, sizeof(new_exp),
+ user_newexpire * DAY);
+ date_to_str (old_exp, sizeof(old_exp),
+ user_expire * DAY);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing expiration date",
+ user_newname, (unsigned int) user_newid, 1);
+#endif
+ SYSLOG ((LOG_INFO,
+ "change user '%s' expiration from '%s' to '%s'",
+ spent->sp_namp, old_exp, new_exp));
+ spent->sp_expire = user_newexpire;
+ }
+
+ /* Always update the shadowed password if there is a shadow entry
+ * (even if shadowed passwords might not be enabled for this
+ * account (pw_passwd != "x")).
+ * It seems better to update the password in both places in case a
+ * shadow and a non shadow entry exist.
+ * This might occur if:
+ * + there were already both entries
+ * + aging has been requested
+ */
+ spent->sp_pwdp = new_pw_passwd (spent->sp_pwdp);
+
+ if (pflg) {
+ spent->sp_lstchg = (long) gettime () / SCALE;
+ if (0 == spent->sp_lstchg) {
+ /* Better disable aging than requiring a password
+ * change. */
+ spent->sp_lstchg = -1;
+ }
+ }
+}
+
+/*
+ * fail_exit - exit with an error code after unlocking files
+ */
+static /*@noreturn@*/void fail_exit (int code)
+{
+ if (gr_locked) {
+ if (gr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+ /* continue */
+ }
+ }
+#ifdef SHADOWGRP
+ if (sgr_locked) {
+ if (sgr_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+ /* continue */
+ }
+ }
+#endif
+ if (spw_locked) {
+ if (spw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
+ /* continue */
+ }
+ }
+ if (pw_locked) {
+ if (pw_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+ }
+#ifdef ENABLE_SUBIDS
+ if (sub_uid_locked) {
+ if (sub_uid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ()));
+ /* continue */
+ }
+ }
+ if (sub_gid_locked) {
+ if (sub_gid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ()));
+ /* continue */
+ }
+ }
+#endif /* ENABLE_SUBIDS */
+
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "modifying account",
+ user_name, AUDIT_NO_ID, 0);
+#endif
+ exit (code);
+}
+
+
+static void update_group (void)
+{
+ bool is_member;
+ bool was_member;
+ bool changed;
+ const struct group *grp;
+ struct group *ngrp;
+
+ changed = false;
+
+ /*
+ * Scan through the entire group file looking for the groups that
+ * the user is a member of.
+ */
+ while ((grp = gr_next ()) != NULL) {
+ /*
+ * See if the user specified this group as one of their
+ * concurrent groups.
+ */
+ was_member = is_on_list (grp->gr_mem, user_name);
+ is_member = Gflg && ( (was_member && aflg)
+ || is_on_list (user_groups, grp->gr_name));
+
+ if (!was_member && !is_member) {
+ continue;
+ }
+
+ ngrp = __gr_dup (grp);
+ if (NULL == ngrp) {
+ fprintf (stderr,
+ _("%s: Out of memory. Cannot update %s.\n"),
+ Prog, gr_dbname ());
+ fail_exit (E_GRP_UPDATE);
+ }
+
+ if (was_member) {
+ if ((!Gflg) || is_member) {
+ /* User was a member and is still a member
+ * of this group.
+ * But the user might have been renamed.
+ */
+ if (lflg) {
+ ngrp->gr_mem = del_list (ngrp->gr_mem,
+ user_name);
+ ngrp->gr_mem = add_list (ngrp->gr_mem,
+ user_newname);
+ changed = true;
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing group member",
+ user_newname, AUDIT_NO_ID, 1);
+#endif
+ SYSLOG ((LOG_INFO,
+ "change '%s' to '%s' in group '%s'",
+ user_name, user_newname,
+ ngrp->gr_name));
+ }
+ } else {
+ /* User was a member but is no more a
+ * member of this group.
+ */
+ ngrp->gr_mem = del_list (ngrp->gr_mem, user_name);
+ changed = true;
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "removing group member",
+ user_name, AUDIT_NO_ID, 1);
+#endif
+ SYSLOG ((LOG_INFO,
+ "delete '%s' from group '%s'",
+ user_name, ngrp->gr_name));
+ }
+ } else {
+ /* User was not a member but is now a member this
+ * group.
+ */
+ ngrp->gr_mem = add_list (ngrp->gr_mem, user_newname);
+ changed = true;
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "adding user to group",
+ user_name, AUDIT_NO_ID, 1);
+#endif
+ SYSLOG ((LOG_INFO, "add '%s' to group '%s'",
+ user_newname, ngrp->gr_name));
+ }
+ if (!changed) {
+ continue;
+ }
+
+ changed = false;
+ if (gr_update (ngrp) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, gr_dbname (), ngrp->gr_name);
+ SYSLOG ((LOG_WARN, "failed to prepare the new %s entry '%s'", gr_dbname (), ngrp->gr_name));
+ fail_exit (E_GRP_UPDATE);
+ }
+ }
+}
+
+#ifdef SHADOWGRP
+static void update_gshadow (void)
+{
+ bool is_member;
+ bool was_member;
+ bool was_admin;
+ bool changed;
+ const struct sgrp *sgrp;
+ struct sgrp *nsgrp;
+
+ changed = false;
+
+ /*
+ * Scan through the entire shadow group file looking for the groups
+ * that the user is a member of.
+ */
+ while ((sgrp = sgr_next ()) != NULL) {
+
+ /*
+ * See if the user was a member of this group
+ */
+ was_member = is_on_list (sgrp->sg_mem, user_name);
+
+ /*
+ * See if the user was an administrator of this group
+ */
+ was_admin = is_on_list (sgrp->sg_adm, user_name);
+
+ /*
+ * See if the user specified this group as one of their
+ * concurrent groups.
+ */
+ is_member = Gflg && ( (was_member && aflg)
+ || is_on_list (user_groups, sgrp->sg_name));
+
+ if (!was_member && !was_admin && !is_member) {
+ continue;
+ }
+
+ nsgrp = __sgr_dup (sgrp);
+ if (NULL == nsgrp) {
+ fprintf (stderr,
+ _("%s: Out of memory. Cannot update %s.\n"),
+ Prog, sgr_dbname ());
+ fail_exit (E_GRP_UPDATE);
+ }
+
+ if (was_admin && lflg) {
+ /* User was an admin of this group but the user
+ * has been renamed.
+ */
+ nsgrp->sg_adm = del_list (nsgrp->sg_adm, user_name);
+ nsgrp->sg_adm = add_list (nsgrp->sg_adm, user_newname);
+ changed = true;
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing admin name in shadow group",
+ user_name, AUDIT_NO_ID, 1);
+#endif
+ SYSLOG ((LOG_INFO,
+ "change admin '%s' to '%s' in shadow group '%s'",
+ user_name, user_newname, nsgrp->sg_name));
+ }
+
+ if (was_member) {
+ if ((!Gflg) || is_member) {
+ /* User was a member and is still a member
+ * of this group.
+ * But the user might have been renamed.
+ */
+ if (lflg) {
+ nsgrp->sg_mem = del_list (nsgrp->sg_mem,
+ user_name);
+ nsgrp->sg_mem = add_list (nsgrp->sg_mem,
+ user_newname);
+ changed = true;
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing member in shadow group",
+ user_name, AUDIT_NO_ID, 1);
+#endif
+ SYSLOG ((LOG_INFO,
+ "change '%s' to '%s' in shadow group '%s'",
+ user_name, user_newname,
+ nsgrp->sg_name));
+ }
+ } else {
+ /* User was a member but is no more a
+ * member of this group.
+ */
+ nsgrp->sg_mem = del_list (nsgrp->sg_mem, user_name);
+ changed = true;
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "removing user from shadow group",
+ user_name, AUDIT_NO_ID, 1);
+#endif
+ SYSLOG ((LOG_INFO,
+ "delete '%s' from shadow group '%s'",
+ user_name, nsgrp->sg_name));
+ }
+ } else if (is_member) {
+ /* User was not a member but is now a member this
+ * group.
+ */
+ nsgrp->sg_mem = add_list (nsgrp->sg_mem, user_newname);
+ changed = true;
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "adding user to shadow group",
+ user_newname, AUDIT_NO_ID, 1);
+#endif
+ SYSLOG ((LOG_INFO, "add '%s' to shadow group '%s'",
+ user_newname, nsgrp->sg_name));
+ }
+ if (!changed) {
+ continue;
+ }
+
+ changed = false;
+
+ /*
+ * Update the group entry to reflect the changes.
+ */
+ if (sgr_update (nsgrp) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, sgr_dbname (), nsgrp->sg_name);
+ SYSLOG ((LOG_WARN, "failed to prepare the new %s entry '%s'",
+ sgr_dbname (), nsgrp->sg_name));
+ fail_exit (E_GRP_UPDATE);
+ }
+ }
+}
+#endif /* SHADOWGRP */
+
+/*
+ * grp_update - add user to secondary group set
+ *
+ * grp_update() takes the secondary group set given in user_groups and
+ * adds the user to each group given by that set.
+ */
+static void grp_update (void)
+{
+ update_group ();
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ update_gshadow ();
+ }
+#endif
+}
+
+/*
+ * process_flags - perform command line argument setting
+ *
+ * process_flags() interprets the command line arguments and sets the
+ * values that the user will be created with accordingly. The values
+ * are checked for sanity.
+ */
+static void process_flags (int argc, char **argv)
+{
+ const struct group *grp;
+
+ bool anyflag = false;
+
+ {
+ /*
+ * Parse the command line options.
+ */
+ int c;
+ static struct option long_options[] = {
+ {"append", no_argument, NULL, 'a'},
+ {"comment", required_argument, NULL, 'c'},
+ {"home", required_argument, NULL, 'd'},
+ {"expiredate", required_argument, NULL, 'e'},
+ {"inactive", required_argument, NULL, 'f'},
+ {"gid", required_argument, NULL, 'g'},
+ {"groups", required_argument, NULL, 'G'},
+ {"help", no_argument, NULL, 'h'},
+ {"login", required_argument, NULL, 'l'},
+ {"lock", no_argument, NULL, 'L'},
+ {"move-home", no_argument, NULL, 'm'},
+ {"non-unique", no_argument, NULL, 'o'},
+ {"password", required_argument, NULL, 'p'},
+ {"root", required_argument, NULL, 'R'},
+ {"shell", required_argument, NULL, 's'},
+ {"uid", required_argument, NULL, 'u'},
+ {"unlock", no_argument, NULL, 'U'},
+#ifdef ENABLE_SUBIDS
+ {"add-subuids", required_argument, NULL, 'v'},
+ {"del-subuids", required_argument, NULL, 'V'},
+ {"add-subgids", required_argument, NULL, 'w'},
+ {"del-subgids", required_argument, NULL, 'W'},
+#endif /* ENABLE_SUBIDS */
+#ifdef WITH_SELINUX
+ {"selinux-user", required_argument, NULL, 'Z'},
+#endif /* WITH_SELINUX */
+ {NULL, 0, NULL, '\0'}
+ };
+ while ((c = getopt_long (argc, argv,
+ "ac:d:e:f:g:G:hl:Lmop:R:s:u:U"
+#ifdef ENABLE_SUBIDS
+ "v:w:V:W:"
+#endif /* ENABLE_SUBIDS */
+#ifdef WITH_SELINUX
+ "Z:"
+#endif /* WITH_SELINUX */
+ , long_options, NULL)) != -1) {
+ switch (c) {
+ case 'a':
+ aflg = true;
+ break;
+ case 'c':
+ if (!VALID (optarg)) {
+ fprintf (stderr,
+ _("%s: invalid field '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ user_newcomment = optarg;
+ cflg = true;
+ break;
+ case 'd':
+ if (!VALID (optarg)) {
+ fprintf (stderr,
+ _("%s: invalid field '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ dflg = true;
+ user_newhome = optarg;
+ break;
+ case 'e':
+ if ('\0' != *optarg) {
+ user_newexpire = strtoday (optarg);
+ if (user_newexpire < -1) {
+ fprintf (stderr,
+ _("%s: invalid date '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ user_newexpire *= DAY / SCALE;
+ } else {
+ user_newexpire = -1;
+ }
+ eflg = true;
+ break;
+ case 'f':
+ if ( (getlong (optarg, &user_newinactive) == 0)
+ || (user_newinactive < -1)) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ fflg = true;
+ break;
+ case 'g':
+ grp = getgr_nam_gid (optarg);
+ if (NULL == grp) {
+ fprintf (stderr,
+ _("%s: group '%s' does not exist\n"),
+ Prog, optarg);
+ exit (E_NOTFOUND);
+ }
+ user_newgid = grp->gr_gid;
+ gflg = true;
+ break;
+ case 'G':
+ if (get_groups (optarg) != 0) {
+ exit (E_NOTFOUND);
+ }
+ Gflg = true;
+ break;
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'l':
+ if (!is_valid_user_name (optarg)) {
+ fprintf (stderr,
+ _("%s: invalid user name '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ lflg = true;
+ user_newname = optarg;
+ break;
+ case 'L':
+ Lflg = true;
+ break;
+ case 'm':
+ mflg = true;
+ break;
+ case 'o':
+ oflg = true;
+ break;
+ case 'p':
+ user_pass = optarg;
+ pflg = true;
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ case 's':
+ if (!VALID (optarg)) {
+ fprintf (stderr,
+ _("%s: invalid field '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ user_newshell = optarg;
+ sflg = true;
+ break;
+ case 'u':
+ if ( (get_uid (optarg, &user_newid) ==0)
+ || (user_newid == (uid_t)-1)) {
+ fprintf (stderr,
+ _("%s: invalid user ID '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
+ uflg = true;
+ break;
+ case 'U':
+ Uflg = true;
+ break;
+#ifdef ENABLE_SUBIDS
+ case 'v':
+ if (prepend_range (optarg, &add_sub_uids) == 0) {
+ fprintf (stderr,
+ _("%s: invalid subordinate uid range '%s'\n"),
+ Prog, optarg);
+ exit(E_BAD_ARG);
+ }
+ vflg = true;
+ break;
+ case 'V':
+ if (prepend_range (optarg, &del_sub_uids) == 0) {
+ fprintf (stderr,
+ _("%s: invalid subordinate uid range '%s'\n"),
+ Prog, optarg);
+ exit(E_BAD_ARG);
+ }
+ Vflg = true;
+ break;
+ case 'w':
+ if (prepend_range (optarg, &add_sub_gids) == 0) {
+ fprintf (stderr,
+ _("%s: invalid subordinate gid range '%s'\n"),
+ Prog, optarg);
+ exit(E_BAD_ARG);
+ }
+ wflg = true;
+ break;
+ case 'W':
+ if (prepend_range (optarg, &del_sub_gids) == 0) {
+ fprintf (stderr,
+ _("%s: invalid subordinate gid range '%s'\n"),
+ Prog, optarg);
+ exit(E_BAD_ARG);
+ }
+ Wflg = true;
+ break;
+#endif /* ENABLE_SUBIDS */
+#ifdef WITH_SELINUX
+ case 'Z':
+ if (is_selinux_enabled () > 0) {
+ user_selinux = optarg;
+ Zflg = true;
+ } else {
+ fprintf (stderr,
+ _("%s: -Z requires SELinux enabled kernel\n"),
+ Prog);
+ exit (E_BAD_ARG);
+ }
+ break;
+#endif /* WITH_SELINUX */
+ default:
+ usage (E_USAGE);
+ }
+ anyflag = true;
+ }
+ }
+
+ if (optind != argc - 1) {
+ usage (E_USAGE);
+ }
+
+ user_name = argv[argc - 1];
+
+ {
+ const struct passwd *pwd;
+ /* local, no need for xgetpwnam */
+ pwd = getpwnam (user_name);
+ if (NULL == pwd) {
+ fprintf (stderr,
+ _("%s: user '%s' does not exist\n"),
+ Prog, user_name);
+ exit (E_NOTFOUND);
+ }
+
+ user_id = pwd->pw_uid;
+ user_gid = pwd->pw_gid;
+ user_comment = xstrdup (pwd->pw_gecos);
+ user_home = xstrdup (pwd->pw_dir);
+ user_shell = xstrdup (pwd->pw_shell);
+ }
+
+ /* user_newname, user_newid, user_newgid can be used even when the
+ * options where not specified. */
+ if (!lflg) {
+ user_newname = user_name;
+ }
+ if (!uflg) {
+ user_newid = user_id;
+ }
+ if (!gflg) {
+ user_newgid = user_gid;
+ }
+
+#ifdef USE_NIS
+ /*
+ * Now make sure it isn't an NIS user.
+ */
+ if (__ispwNIS ()) {
+ char *nis_domain;
+ char *nis_master;
+
+ fprintf (stderr,
+ _("%s: user %s is a NIS user\n"),
+ Prog, user_name);
+
+ if ( !yp_get_default_domain (&nis_domain)
+ && !yp_master (nis_domain, "passwd.byname", &nis_master)) {
+ fprintf (stderr,
+ _("%s: %s is the NIS master\n"),
+ Prog, nis_master);
+ }
+ exit (E_NOTFOUND);
+ }
+#endif
+
+ {
+ const struct spwd *spwd = NULL;
+ /* local, no need for xgetspnam */
+ if (is_shadow_pwd && ((spwd = getspnam (user_name)) != NULL)) {
+ user_expire = spwd->sp_expire;
+ user_inactive = spwd->sp_inact;
+ }
+ }
+
+ if (!anyflag) {
+ fprintf (stderr, _("%s: no options\n"), Prog);
+ usage (E_USAGE);
+ }
+
+ if (aflg && (!Gflg)) {
+ fprintf (stderr,
+ _("%s: %s flag is only allowed with the %s flag\n"),
+ Prog, "-a", "-G");
+ usage (E_USAGE);
+ }
+
+ if ((Lflg && (pflg || Uflg)) || (pflg && Uflg)) {
+ fprintf (stderr,
+ _("%s: the -L, -p, and -U flags are exclusive\n"),
+ Prog);
+ usage (E_USAGE);
+ }
+
+ if (oflg && !uflg) {
+ fprintf (stderr,
+ _("%s: %s flag is only allowed with the %s flag\n"),
+ Prog, "-o", "-u");
+ usage (E_USAGE);
+ }
+
+ if (mflg && !dflg) {
+ fprintf (stderr,
+ _("%s: %s flag is only allowed with the %s flag\n"),
+ Prog, "-m", "-d");
+ usage (E_USAGE);
+ }
+
+ if (user_newid == user_id) {
+ uflg = false;
+ oflg = false;
+ }
+ if (user_newgid == user_gid) {
+ gflg = false;
+ }
+ if ( (NULL != user_newshell)
+ && (strcmp (user_newshell, user_shell) == 0)) {
+ sflg = false;
+ }
+ if (strcmp (user_newname, user_name) == 0) {
+ lflg = false;
+ }
+ if (user_newinactive == user_inactive) {
+ fflg = false;
+ }
+ if (user_newexpire == user_expire) {
+ eflg = false;
+ }
+ if ( (NULL != user_newhome)
+ && (strcmp (user_newhome, user_home) == 0)) {
+ dflg = false;
+ mflg = false;
+ }
+ if ( (NULL != user_newcomment)
+ && (strcmp (user_newcomment, user_comment) == 0)) {
+ cflg = false;
+ }
+
+ if (!(Uflg || uflg || sflg || pflg || mflg || Lflg ||
+ lflg || Gflg || gflg || fflg || eflg || dflg || cflg
+#ifdef ENABLE_SUBIDS
+ || vflg || Vflg || wflg || Wflg
+#endif /* ENABLE_SUBIDS */
+#ifdef WITH_SELINUX
+ || Zflg
+#endif /* WITH_SELINUX */
+ )) {
+ fprintf (stderr, _("%s: no changes\n"), Prog);
+ exit (E_SUCCESS);
+ }
+
+ if (!is_shadow_pwd && (eflg || fflg)) {
+ fprintf (stderr,
+ _("%s: shadow passwords required for -e and -f\n"),
+ Prog);
+ exit (E_USAGE);
+ }
+
+ /* local, no need for xgetpwnam */
+ if (lflg && (getpwnam (user_newname) != NULL)) {
+ fprintf (stderr,
+ _("%s: user '%s' already exists\n"),
+ Prog, user_newname);
+ exit (E_NAME_IN_USE);
+ }
+
+ /* local, no need for xgetpwuid */
+ if (uflg && !oflg && (getpwuid (user_newid) != NULL)) {
+ fprintf (stderr,
+ _("%s: UID '%lu' already exists\n"),
+ Prog, (unsigned long) user_newid);
+ exit (E_UID_IN_USE);
+ }
+
+#ifdef ENABLE_SUBIDS
+ if ( (vflg || Vflg)
+ && !is_sub_uid) {
+ fprintf (stderr,
+ _("%s: %s does not exist, you cannot use the flags %s or %s\n"),
+ Prog, sub_uid_dbname (), "-v", "-V");
+ exit (E_USAGE);
+ }
+
+ if ( (wflg || Wflg)
+ && !is_sub_gid) {
+ fprintf (stderr,
+ _("%s: %s does not exist, you cannot use the flags %s or %s\n"),
+ Prog, sub_gid_dbname (), "-w", "-W");
+ exit (E_USAGE);
+ }
+#endif /* ENABLE_SUBIDS */
+}
+
+/*
+ * close_files - close all of the files that were opened
+ *
+ * close_files() closes all of the files that were opened for this new
+ * user. This causes any modified entries to be written out.
+ */
+static void close_files (void)
+{
+ if (pw_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
+ fail_exit (E_PW_UPDATE);
+ }
+ if (is_shadow_pwd && (spw_close () == 0)) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR,
+ "failure while writing changes to %s",
+ spw_dbname ()));
+ fail_exit (E_PW_UPDATE);
+ }
+
+ if (Gflg || lflg) {
+ if (gr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR,
+ "failure while writing changes to %s",
+ gr_dbname ()));
+ fail_exit (E_GRP_UPDATE);
+ }
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_close () == 0) {
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR,
+ "failure while writing changes to %s",
+ sgr_dbname ()));
+ fail_exit (E_GRP_UPDATE);
+ }
+ }
+#endif
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_unlock () == 0) {
+ fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_ERR,
+ "failed to unlock %s",
+ sgr_dbname ()));
+ /* continue */
+ }
+ }
+#endif
+ if (gr_unlock () == 0) {
+ fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, gr_dbname ());
+ SYSLOG ((LOG_ERR,
+ "failed to unlock %s",
+ gr_dbname ()));
+ /* continue */
+ }
+ }
+
+ if (is_shadow_pwd) {
+ if (spw_unlock () == 0) {
+ fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, spw_dbname ());
+ SYSLOG ((LOG_ERR,
+ "failed to unlock %s",
+ spw_dbname ()));
+ /* continue */
+ }
+ }
+ if (pw_unlock () == 0) {
+ fprintf (stderr,
+ _("%s: failed to unlock %s\n"),
+ Prog, pw_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+ /* continue */
+ }
+
+ pw_locked = false;
+ spw_locked = false;
+ gr_locked = false;
+#ifdef SHADOWGRP
+ sgr_locked = false;
+#endif
+
+#ifdef ENABLE_SUBIDS
+ if (vflg || Vflg) {
+ if (sub_uid_close () == 0) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, sub_uid_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", sub_uid_dbname ()));
+ fail_exit (E_SUB_UID_UPDATE);
+ }
+ if (sub_uid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ()));
+ /* continue */
+ }
+ sub_uid_locked = false;
+ }
+ if (wflg || Wflg) {
+ if (sub_gid_close () == 0) {
+ fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, sub_gid_dbname ());
+ SYSLOG ((LOG_ERR, "failure while writing changes to %s", sub_gid_dbname ()));
+ fail_exit (E_SUB_GID_UPDATE);
+ }
+ if (sub_gid_unlock () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname ());
+ SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ()));
+ /* continue */
+ }
+ sub_gid_locked = false;
+ }
+#endif /* ENABLE_SUBIDS */
+
+ /*
+ * Close the DBM and/or flat files
+ */
+ endpwent ();
+ endspent ();
+ endgrent ();
+#ifdef SHADOWGRP
+ endsgent ();
+#endif
+}
+
+/*
+ * open_files - lock and open the password files
+ *
+ * open_files() opens the two password files.
+ */
+static void open_files (void)
+{
+ if (pw_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, pw_dbname ());
+ fail_exit (E_PW_UPDATE);
+ }
+ pw_locked = true;
+ if (pw_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, pw_dbname ());
+ fail_exit (E_PW_UPDATE);
+ }
+ if (is_shadow_pwd && (spw_lock () == 0)) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, spw_dbname ());
+ fail_exit (E_PW_UPDATE);
+ }
+ spw_locked = true;
+ if (is_shadow_pwd && (spw_open (O_CREAT | O_RDWR) == 0)) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, spw_dbname ());
+ fail_exit (E_PW_UPDATE);
+ }
+
+ if (Gflg || lflg) {
+ /*
+ * Lock and open the group file. This will load all of the
+ * group entries.
+ */
+ if (gr_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, gr_dbname ());
+ fail_exit (E_GRP_UPDATE);
+ }
+ gr_locked = true;
+ if (gr_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, gr_dbname ());
+ fail_exit (E_GRP_UPDATE);
+ }
+#ifdef SHADOWGRP
+ if (is_shadow_grp && (sgr_lock () == 0)) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sgr_dbname ());
+ fail_exit (E_GRP_UPDATE);
+ }
+ sgr_locked = true;
+ if (is_shadow_grp && (sgr_open (O_CREAT | O_RDWR) == 0)) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, sgr_dbname ());
+ fail_exit (E_GRP_UPDATE);
+ }
+#endif
+ }
+#ifdef ENABLE_SUBIDS
+ if (vflg || Vflg) {
+ if (sub_uid_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sub_uid_dbname ());
+ fail_exit (E_SUB_UID_UPDATE);
+ }
+ sub_uid_locked = true;
+ if (sub_uid_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, sub_uid_dbname ());
+ fail_exit (E_SUB_UID_UPDATE);
+ }
+ }
+ if (wflg || Wflg) {
+ if (sub_gid_lock () == 0) {
+ fprintf (stderr,
+ _("%s: cannot lock %s; try again later.\n"),
+ Prog, sub_gid_dbname ());
+ fail_exit (E_SUB_GID_UPDATE);
+ }
+ sub_gid_locked = true;
+ if (sub_gid_open (O_CREAT | O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, sub_gid_dbname ());
+ fail_exit (E_SUB_GID_UPDATE);
+ }
+ }
+#endif /* ENABLE_SUBIDS */
+}
+
+/*
+ * usr_update - create the user entries
+ *
+ * usr_update() creates the password file entries for this user and
+ * will update the group entries if required.
+ */
+static void usr_update (void)
+{
+ struct passwd pwent;
+ const struct passwd *pwd;
+
+ struct spwd spent;
+ const struct spwd *spwd = NULL;
+
+ /*
+ * Locate the entry in /etc/passwd, which MUST exist.
+ */
+ pwd = pw_locate (user_name);
+ if (NULL == pwd) {
+ fprintf (stderr,
+ _("%s: user '%s' does not exist in %s\n"),
+ Prog, user_name, pw_dbname ());
+ fail_exit (E_NOTFOUND);
+ }
+ pwent = *pwd;
+ new_pwent (&pwent);
+
+
+ /* If the shadow file does not exist, it won't be created */
+ if (is_shadow_pwd) {
+ spwd = spw_locate (user_name);
+ if (NULL != spwd) {
+ /* Update the shadow entry if it exists */
+ spent = *spwd;
+ new_spent (&spent);
+ } else if ( ( pflg
+ && (strcmp (pwent.pw_passwd, SHADOW_PASSWD_STRING) == 0))
+ || eflg || fflg) {
+ /* In some cases, we force the creation of a
+ * shadow entry:
+ * + new password requested and passwd indicates
+ * a shadowed password
+ * + aging information is requested
+ */
+ memset (&spent, 0, sizeof spent);
+ spent.sp_namp = user_name;
+
+ /* The user explicitly asked for a shadow feature.
+ * Enable shadowed passwords for this new account.
+ */
+ spent.sp_pwdp = xstrdup (pwent.pw_passwd);
+ pwent.pw_passwd = xstrdup (SHADOW_PASSWD_STRING);
+
+ spent.sp_lstchg = (long) gettime () / SCALE;
+ if (0 == spent.sp_lstchg) {
+ /* Better disable aging than
+ * requiring a password change */
+ spent.sp_lstchg = -1;
+ }
+ spent.sp_min = getdef_num ("PASS_MIN_DAYS", -1);
+ spent.sp_max = getdef_num ("PASS_MAX_DAYS", -1);
+ spent.sp_warn = getdef_num ("PASS_WARN_AGE", -1);
+ spent.sp_inact = -1;
+ spent.sp_expire = -1;
+ spent.sp_flag = SHADOW_SP_FLAG_UNSET;
+ new_spent (&spent);
+ spwd = &spent; /* entry needs to be committed */
+ }
+ }
+
+ if (lflg || uflg || gflg || cflg || dflg || sflg || pflg
+ || Lflg || Uflg) {
+ if (pw_update (&pwent) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, pw_dbname (), pwent.pw_name);
+ fail_exit (E_PW_UPDATE);
+ }
+ if (lflg && (pw_remove (user_name) == 0)) {
+ fprintf (stderr,
+ _("%s: cannot remove entry '%s' from %s\n"),
+ Prog, user_name, pw_dbname ());
+ fail_exit (E_PW_UPDATE);
+ }
+ }
+ if ((NULL != spwd) && (lflg || eflg || fflg || pflg || Lflg || Uflg)) {
+ if (spw_update (&spent) == 0) {
+ fprintf (stderr,
+ _("%s: failed to prepare the new %s entry '%s'\n"),
+ Prog, spw_dbname (), spent.sp_namp);
+ fail_exit (E_PW_UPDATE);
+ }
+ if (lflg && (spw_remove (user_name) == 0)) {
+ fprintf (stderr,
+ _("%s: cannot remove entry '%s' from %s\n"),
+ Prog, user_name, spw_dbname ());
+ fail_exit (E_PW_UPDATE);
+ }
+ }
+}
+
+/*
+ * move_home - move the user's home directory
+ *
+ * move_home() moves the user's home directory to a new location. The
+ * files will be copied if the directory cannot simply be renamed.
+ */
+static void move_home (void)
+{
+ struct stat sb;
+
+ if (access (user_newhome, F_OK) == 0) {
+ /*
+ * If the new home directory already exist, the user
+ * should not use -m.
+ */
+ fprintf (stderr,
+ _("%s: directory %s exists\n"),
+ Prog, user_newhome);
+ fail_exit (E_HOMEDIR);
+ }
+
+ if (stat (user_home, &sb) == 0) {
+ /*
+ * Don't try to move it if it is not a directory
+ * (but /dev/null for example). --marekm
+ */
+ if (!S_ISDIR (sb.st_mode)) {
+ fprintf (stderr,
+ _("%s: The previous home directory (%s) was "
+ "not a directory. It is not removed and no "
+ "home directories are created.\n"),
+ Prog, user_home);
+ fail_exit (E_HOMEDIR);
+ }
+
+#ifdef WITH_AUDIT
+ if (uflg || gflg) {
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing home directory owner",
+ user_newname, (unsigned int) user_newid, 1);
+ }
+#endif
+
+ if (rename (user_home, user_newhome) == 0) {
+ /* FIXME: rename above may have broken symlinks
+ * pointing to the user's home directory
+ * with an absolute path. */
+ if (chown_tree (user_newhome,
+ user_id, uflg ? user_newid : (uid_t)-1,
+ user_gid, gflg ? user_newgid : (gid_t)-1) != 0) {
+ fprintf (stderr,
+ _("%s: Failed to change ownership of the home directory"),
+ Prog);
+ fail_exit (E_HOMEDIR);
+ }
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "moving home directory",
+ user_newname, (unsigned int) user_newid,
+ 1);
+#endif
+ return;
+ } else {
+ if (EXDEV == errno) {
+ if (copy_tree (user_home, user_newhome, true,
+ true,
+ user_id,
+ uflg ? user_newid : (uid_t)-1,
+ user_gid,
+ gflg ? user_newgid : (gid_t)-1) == 0) {
+ if (remove_tree (user_home, true) != 0) {
+ fprintf (stderr,
+ _("%s: warning: failed to completely remove old home directory %s"),
+ Prog, user_home);
+ }
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK,
+ Prog,
+ "moving home directory",
+ user_newname,
+ (unsigned int) user_newid,
+ 1);
+#endif
+ return;
+ }
+
+ (void) remove_tree (user_newhome, true);
+ }
+ fprintf (stderr,
+ _("%s: cannot rename directory %s to %s\n"),
+ Prog, user_home, user_newhome);
+ fail_exit (E_HOMEDIR);
+ }
+ }
+}
+
+/*
+ * update_lastlog - update the lastlog file
+ *
+ * Relocate the "lastlog" entries for the user. The old entry is
+ * left alone in case the UID was shared. It doesn't hurt anything
+ * to just leave it be.
+ */
+static void update_lastlog (void)
+{
+ struct lastlog ll;
+ int fd;
+ off_t off_uid = (off_t) user_id * sizeof ll;
+ off_t off_newuid = (off_t) user_newid * sizeof ll;
+
+ if (access (LASTLOG_FILE, F_OK) != 0) {
+ return;
+ }
+
+ fd = open (LASTLOG_FILE, O_RDWR);
+
+ if (-1 == fd) {
+ fprintf (stderr,
+ _("%s: failed to copy the lastlog entry of user %lu to user %lu: %s\n"),
+ Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
+ return;
+ }
+
+ if ( (lseek (fd, off_uid, SEEK_SET) == off_uid)
+ && (read (fd, &ll, sizeof ll) == (ssize_t) sizeof ll)) {
+ /* Copy the old entry to its new location */
+ if ( (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
+ || (write (fd, &ll, sizeof ll) != (ssize_t) sizeof ll)
+ || (fsync (fd) != 0)
+ || (close (fd) != 0)) {
+ fprintf (stderr,
+ _("%s: failed to copy the lastlog entry of user %lu to user %lu: %s\n"),
+ Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
+ }
+ } else {
+ /* Assume lseek or read failed because there is
+ * no entry for the old UID */
+
+ /* Check if the new UID already has an entry */
+ if ( (lseek (fd, off_newuid, SEEK_SET) == off_newuid)
+ && (read (fd, &ll, sizeof ll) == (ssize_t) sizeof ll)) {
+ /* Reset the new uid's lastlog entry */
+ memzero (&ll, sizeof (ll));
+ if ( (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
+ || (write (fd, &ll, sizeof ll) != (ssize_t) sizeof ll)
+ || (fsync (fd) != 0)
+ || (close (fd) != 0)) {
+ fprintf (stderr,
+ _("%s: failed to copy the lastlog entry of user %lu to user %lu: %s\n"),
+ Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
+ }
+ } else {
+ (void) close (fd);
+ }
+ }
+}
+
+/*
+ * update_faillog - update the faillog file
+ *
+ * Relocate the "faillog" entries for the user. The old entry is
+ * left alone in case the UID was shared. It doesn't hurt anything
+ * to just leave it be.
+ */
+static void update_faillog (void)
+{
+ struct faillog fl;
+ int fd;
+ off_t off_uid = (off_t) user_id * sizeof fl;
+ off_t off_newuid = (off_t) user_newid * sizeof fl;
+
+ if (access (FAILLOG_FILE, F_OK) != 0) {
+ return;
+ }
+
+ fd = open (FAILLOG_FILE, O_RDWR);
+
+ if (-1 == fd) {
+ fprintf (stderr,
+ _("%s: failed to copy the faillog entry of user %lu to user %lu: %s\n"),
+ Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
+ return;
+ }
+
+ if ( (lseek (fd, off_uid, SEEK_SET) == off_uid)
+ && (read (fd, (char *) &fl, sizeof fl) == (ssize_t) sizeof fl)) {
+ /* Copy the old entry to its new location */
+ if ( (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
+ || (write (fd, &fl, sizeof fl) != (ssize_t) sizeof fl)
+ || (fsync (fd) != 0)
+ || (close (fd) != 0)) {
+ fprintf (stderr,
+ _("%s: failed to copy the faillog entry of user %lu to user %lu: %s\n"),
+ Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
+ }
+ } else {
+ /* Assume lseek or read failed because there is
+ * no entry for the old UID */
+
+ /* Check if the new UID already has an entry */
+ if ( (lseek (fd, off_newuid, SEEK_SET) == off_newuid)
+ && (read (fd, &fl, sizeof fl) == (ssize_t) sizeof fl)) {
+ /* Reset the new uid's faillog entry */
+ memzero (&fl, sizeof (fl));
+ if ( (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
+ || (write (fd, &fl, sizeof fl) != (ssize_t) sizeof fl)
+ || (close (fd) != 0)) {
+ fprintf (stderr,
+ _("%s: failed to copy the faillog entry of user %lu to user %lu: %s\n"),
+ Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
+ }
+ } else {
+ (void) close (fd);
+ }
+ }
+}
+
+#ifndef NO_MOVE_MAILBOX
+/*
+ * This is the new and improved code to carefully chown/rename the user's
+ * mailbox. Maybe I am too paranoid but the mail spool dir sometimes
+ * happens to be mode 1777 (this makes mail user agents work without
+ * being setgid mail, but is NOT recommended; they all should be fixed
+ * to use movemail). --marekm
+ */
+static void move_mailbox (void)
+{
+ const char *maildir;
+ char mailfile[1024], newmailfile[1024];
+ int fd;
+ struct stat st;
+
+ maildir = getdef_str ("MAIL_DIR");
+#ifdef MAIL_SPOOL_DIR
+ if ((NULL == maildir) && (getdef_str ("MAIL_FILE") == NULL)) {
+ maildir = MAIL_SPOOL_DIR;
+ }
+#endif
+ if (NULL == maildir) {
+ return;
+ }
+
+ /*
+ * O_NONBLOCK is to make sure open won't hang on mandatory locks.
+ * We do fstat/fchown to make sure there are no races (someone
+ * replacing /var/spool/mail/luser with a hard link to /etc/passwd
+ * between stat and chown). --marekm
+ */
+ (void) snprintf (mailfile, sizeof mailfile, "%s/%s",
+ maildir, user_name);
+ mailfile[(sizeof mailfile) - 1] = '\0';
+ fd = open (mailfile, O_RDONLY | O_NONBLOCK, 0);
+ if (fd < 0) {
+ /* no need for warnings if the mailbox doesn't exist */
+ if (errno != ENOENT) {
+ perror (mailfile);
+ }
+ return;
+ }
+ if (fstat (fd, &st) < 0) {
+ perror ("fstat");
+ (void) close (fd);
+ return;
+ }
+ if (st.st_uid != user_id) {
+ /* better leave it alone */
+ fprintf (stderr, _("%s: warning: %s not owned by %s\n"),
+ Prog, mailfile, user_name);
+ (void) close (fd);
+ return;
+ }
+ if (uflg) {
+ if (fchown (fd, user_newid, (gid_t) -1) < 0) {
+ perror (_("failed to change mailbox owner"));
+ }
+#ifdef WITH_AUDIT
+ else {
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing mail file owner",
+ user_newname, (unsigned int) user_newid, 1);
+ }
+#endif
+ }
+
+ (void) close (fd);
+
+ if (lflg) {
+ (void) snprintf (newmailfile, sizeof newmailfile, "%s/%s",
+ maildir, user_newname);
+ newmailfile[(sizeof newmailfile) - 1] = '\0';
+ if ( (link (mailfile, newmailfile) != 0)
+ || (unlink (mailfile) != 0)) {
+ perror (_("failed to rename mailbox"));
+ }
+#ifdef WITH_AUDIT
+ else {
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing mail file name",
+ user_newname, (unsigned int) user_newid, 1);
+ }
+#endif
+ }
+}
+#endif
+
+/*
+ * main - usermod command
+ */
+int main (int argc, char **argv)
+{
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ pam_handle_t *pamh = NULL;
+ int retval;
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+
+ /*
+ * Get my name so that I can use it to report errors.
+ */
+ Prog = Basename (argv[0]);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ OPENLOG ("usermod");
+#ifdef WITH_AUDIT
+ audit_help_open ();
+#endif
+
+ sys_ngroups = sysconf (_SC_NGROUPS_MAX);
+ user_groups = (char **) malloc (sizeof (char *) * (1 + sys_ngroups));
+ user_groups[0] = (char *) 0;
+
+ is_shadow_pwd = spw_file_present ();
+#ifdef SHADOWGRP
+ is_shadow_grp = sgr_file_present ();
+#endif
+#ifdef ENABLE_SUBIDS
+ is_sub_uid = sub_uid_file_present ();
+ is_sub_gid = sub_gid_file_present ();
+#endif /* ENABLE_SUBIDS */
+
+ process_flags (argc, argv);
+
+ /*
+ * The home directory, the username and the user's UID should not
+ * be changed while the user is logged in.
+ */
+ if ( (uflg || lflg || dflg
+#ifdef ENABLE_SUBIDS
+ || Vflg || Wflg
+#endif /* ENABLE_SUBIDS */
+ )
+ && (user_busy (user_name, user_id) != 0)) {
+ exit (E_USER_BUSY);
+ }
+
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ {
+ struct passwd *pampw;
+ pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+ if (pampw == NULL) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ exit (1);
+ }
+
+ retval = pam_start ("usermod", pampw->pw_name, &conv, &pamh);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_authenticate (pamh, 0);
+ }
+
+ if (PAM_SUCCESS == retval) {
+ retval = pam_acct_mgmt (pamh, 0);
+ }
+
+ if (PAM_SUCCESS != retval) {
+ fprintf (stderr, _("%s: PAM: %s\n"),
+ Prog, pam_strerror (pamh, retval));
+ SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval)));
+ if (NULL != pamh) {
+ (void) pam_end (pamh, retval);
+ }
+ exit (1);
+ }
+ (void) pam_end (pamh, retval);
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
+
+#ifdef WITH_TCB
+ if (shadowtcb_set_user (user_name) == SHADOWTCB_FAILURE) {
+ exit (E_PW_UPDATE);
+ }
+#endif
+
+ /*
+ * Do the hard stuff - open the files, change the user entries,
+ * change the home directory, then close and update the files.
+ */
+ open_files ();
+ if ( cflg || dflg || eflg || fflg || gflg || Lflg || lflg || pflg
+ || sflg || uflg || Uflg) {
+ usr_update ();
+ }
+ if (Gflg || lflg) {
+ grp_update ();
+ }
+#ifdef ENABLE_SUBIDS
+ if (Vflg) {
+ struct ulong_range_list_entry *ptr;
+ for (ptr = del_sub_uids; ptr != NULL; ptr = ptr->next) {
+ unsigned long count = ptr->range.last - ptr->range.first + 1;
+ if (sub_uid_remove(user_name, ptr->range.first, count) == 0) {
+ fprintf (stderr,
+ _("%s: failed to remove uid range %lu-%lu from '%s'\n"),
+ Prog, ptr->range.first, ptr->range.last,
+ sub_uid_dbname ());
+ fail_exit (E_SUB_UID_UPDATE);
+ }
+ }
+ }
+ if (vflg) {
+ struct ulong_range_list_entry *ptr;
+ for (ptr = add_sub_uids; ptr != NULL; ptr = ptr->next) {
+ unsigned long count = ptr->range.last - ptr->range.first + 1;
+ if (sub_uid_add(user_name, ptr->range.first, count) == 0) {
+ fprintf (stderr,
+ _("%s: failed to add uid range %lu-%lu to '%s'\n"),
+ Prog, ptr->range.first, ptr->range.last,
+ sub_uid_dbname ());
+ fail_exit (E_SUB_UID_UPDATE);
+ }
+ }
+ }
+ if (Wflg) {
+ struct ulong_range_list_entry *ptr;
+ for (ptr = del_sub_gids; ptr != NULL; ptr = ptr->next) {
+ unsigned long count = ptr->range.last - ptr->range.first + 1;
+ if (sub_gid_remove(user_name, ptr->range.first, count) == 0) {
+ fprintf (stderr,
+ _("%s: failed to remove gid range %lu-%lu from '%s'\n"),
+ Prog, ptr->range.first, ptr->range.last,
+ sub_gid_dbname ());
+ fail_exit (E_SUB_GID_UPDATE);
+ }
+ }
+ }
+ if (wflg) {
+ struct ulong_range_list_entry *ptr;
+ for (ptr = add_sub_gids; ptr != NULL; ptr = ptr->next) {
+ unsigned long count = ptr->range.last - ptr->range.first + 1;
+ if (sub_gid_add(user_name, ptr->range.first, count) == 0) {
+ fprintf (stderr,
+ _("%s: failed to add gid range %lu-%lu to '%s'\n"),
+ Prog, ptr->range.first, ptr->range.last,
+ sub_gid_dbname ());
+ fail_exit (E_SUB_GID_UPDATE);
+ }
+ }
+ }
+#endif /* ENABLE_SUBIDS */
+ close_files ();
+
+#ifdef WITH_TCB
+ if ( (lflg || uflg)
+ && (shadowtcb_move (user_newname, user_newid) == SHADOWTCB_FAILURE) ) {
+ exit (E_PW_UPDATE);
+ }
+#endif
+
+ nscd_flush_cache ("passwd");
+ nscd_flush_cache ("group");
+
+#ifdef WITH_SELINUX
+ if (Zflg) {
+ if ('\0' != *user_selinux) {
+ if (set_seuser (user_name, user_selinux) != 0) {
+ fprintf (stderr,
+ _("%s: warning: the user name %s to %s SELinux user mapping failed.\n"),
+ Prog, user_name, user_selinux);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "modifying User mapping ",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_SE_UPDATE);
+ }
+ } else {
+ if (del_seuser (user_name) != 0) {
+ fprintf (stderr,
+ _("%s: warning: the user name %s to SELinux user mapping removal failed.\n"),
+ Prog, user_name);
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_USER, Prog,
+ "removing SELinux user mapping",
+ user_name, (unsigned int) user_id,
+ SHADOW_AUDIT_FAILURE);
+#endif /* WITH_AUDIT */
+ fail_exit (E_SE_UPDATE);
+ }
+ }
+ }
+#endif /* WITH_SELINUX */
+
+ if (mflg) {
+ move_home ();
+ }
+
+#ifndef NO_MOVE_MAILBOX
+ if (lflg || uflg) {
+ move_mailbox ();
+ }
+#endif /* NO_MOVE_MAILBOX */
+
+ if (uflg) {
+ update_lastlog ();
+ update_faillog ();
+ }
+
+ if (!mflg && (uflg || gflg)) {
+ if (access (dflg ? user_newhome : user_home, F_OK) == 0) {
+ /*
+ * Change the UID on all of the files owned by
+ * `user_id' to `user_newid' in the user's home
+ * directory.
+ *
+ * move_home() already takes care of changing the
+ * ownership.
+ *
+ */
+#ifdef WITH_AUDIT
+ if (uflg || gflg) {
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "changing home directory owner",
+ user_newname, (unsigned int) user_newid, 1);
+ }
+#endif
+ if (chown_tree (dflg ? user_newhome : user_home,
+ user_id,
+ uflg ? user_newid : (uid_t)-1,
+ user_gid,
+ gflg ? user_newgid : (gid_t)-1) != 0) {
+ fprintf (stderr,
+ _("%s: Failed to change ownership of the home directory"),
+ Prog);
+ fail_exit (E_HOMEDIR);
+ }
+ }
+ }
+
+ return E_SUCCESS;
+}
+
diff --git a/src/vipw.c b/src/vipw.c
new file mode 100644
index 0000000..6d730f6
--- /dev/null
+++ b/src/vipw.c
@@ -0,0 +1,562 @@
+/*
+ vipw, vigr edit the password or group file
+ with -s will edit shadow or gshadow file
+
+ Copyright (c) 1997 , Guy Maor <maor@ece.utexas.edu>
+ Copyright (c) 1999 - 2000, Marek Michałkiewicz
+ Copyright (c) 2002 - 2006, Tomasz Kłoczko
+ Copyright (c) 2007 - 2013, Nicolas François
+ All rights reserved.
+
+ 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., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA. */
+
+#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 "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 */
+
+#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;
+ /* 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) {
+ security_context_t passwd_context=NULL;
+ int ret = 0;
+ if (getfilecon (file, &passwd_context) < 0) {
+ vipwexit (_("Couldn't get file context"), errno, 1);
+ }
+ ret = setfscreatecon (passwd_context);
+ freecon (passwd_context);
+ 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;
+ }
+
+ 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;
+ int status;
+
+ 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);
+ }
+ }
+
+ for (;;) {
+ pid = waitpid (pid, &status, WUNTRACED);
+ if ((pid != -1) && (WIFSTOPPED (status) != 0)) {
+ /* The child (editor) was suspended.
+ * Suspend vipw. */
+ kill (getpid (), SIGSTOP);
+ /* wake child when resumed */
+ kill (pid, SIGCONT);
+ } else {
+ break;
+ }
+ }
+
+ 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 (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]);
+
+ (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");
+
+ return E_SUCCESS;
+}
+