summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/.indent.pro5
-rw-r--r--src/Makefile.am235
-rw-r--r--src/Makefile.in1638
-rw-r--r--src/chage.c904
-rw-r--r--src/check_subid_range.c51
-rw-r--r--src/chfn.c735
-rw-r--r--src/chgpasswd.c623
-rw-r--r--src/chpasswd.c685
-rw-r--r--src/chsh.c541
-rw-r--r--src/expiry.c190
-rw-r--r--src/faillog.c720
-rw-r--r--src/free_subid_range.c55
-rw-r--r--src/get_subid_owners.c45
-rw-r--r--src/getsubids.c49
-rw-r--r--src/gpasswd.c1196
-rw-r--r--src/groupadd.c632
-rw-r--r--src/groupdel.c481
-rw-r--r--src/groupmems.c632
-rw-r--r--src/groupmod.c905
-rw-r--r--src/groups.c179
-rw-r--r--src/grpck.c875
-rw-r--r--src/grpconv.c269
-rw-r--r--src/grpunconv.c232
-rw-r--r--src/id.c172
-rw-r--r--src/lastlog.c440
-rw-r--r--src/login.c1342
-rw-r--r--src/login_nopam.c341
-rw-r--r--src/logoutd.c279
-rw-r--r--src/new_subid_range.c62
-rw-r--r--src/newgidmap.c239
-rw-r--r--src/newgrp.c859
-rw-r--r--src/newuidmap.c167
-rw-r--r--src/newusers.c1344
-rw-r--r--src/nologin.c41
-rw-r--r--src/passwd.c1084
-rw-r--r--src/pwck.c897
-rw-r--r--src/pwconv.c316
-rw-r--r--src/pwunconv.c238
-rw-r--r--src/su.c1216
-rw-r--r--src/suauth.c216
-rw-r--r--src/sulogin.c237
-rw-r--r--src/useradd.c2744
-rw-r--r--src/userdel.c1346
-rw-r--r--src/usermod.c2388
-rw-r--r--src/vipw.c599
45 files changed, 28444 insertions, 0 deletions
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..a1a2e4e
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,235 @@
+
+EXTRA_DIST = \
+ .indent.pro
+
+ubindir = ${prefix}/bin
+usbindir = ${prefix}/sbin
+suidperms = 4755
+sgidperms = 2755
+
+AM_CPPFLAGS = \
+ -I${top_srcdir}/lib \
+ -I$(top_srcdir)/libmisc \
+ -I$(top_srcdir) \
+ -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
+sbin_PROGRAMS = nologin
+ubin_PROGRAMS = faillog lastlog chage chfn chsh expiry gpasswd newgrp passwd
+if ENABLE_SUBIDS
+ubin_PROGRAMS += newgidmap newuidmap
+endif
+if WITH_SU
+bin_PROGRAMS += su
+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
+
+suidusbins =
+suidbins =
+suidubins = chage chfn chsh expiry gpasswd newgrp
+if WITH_SU
+suidbins += su
+endif
+if !WITH_TCB
+suidubins += passwd
+endif
+if ACCT_TOOLS_SETUID
+suidusbins += chgpasswd chpasswd groupadd groupdel groupmod newusers useradd userdel usermod
+endif
+if ENABLE_SUBIDS
+if !FCAPS
+suidubins += newgidmap newuidmap
+endif
+endif
+
+if WITH_TCB
+shadowsgidubins = passwd
+endif
+
+LDADD = $(INTLLIBS) \
+ $(top_builddir)/libmisc/libmisc.la \
+ $(top_builddir)/lib/libshadow.la \
+ $(LIBTCB)
+
+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) $(LIBECONF)
+newuidmap_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBCAP) $(LIBECONF) -ldl
+newgidmap_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBCAP) $(LIBECONF) -ldl
+chfn_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD) $(LIBECONF)
+chgpasswd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT) $(LIBECONF)
+chsh_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD) $(LIBECONF)
+chpasswd_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT) $(LIBECONF)
+expiry_LDADD = $(LDADD) $(LIBECONF)
+gpasswd_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT) $(LIBECONF)
+groupadd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF) -ldl
+groupdel_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF) -ldl
+groupmems_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+groupmod_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF) -ldl
+grpck_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+grpconv_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+grpunconv_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+lastlog_LDADD = $(LDADD) $(LIBAUDIT) $(LIBECONF)
+login_SOURCES = \
+ login.c \
+ login_nopam.c
+login_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD) $(LIBECONF)
+newgrp_LDADD = $(LDADD) $(LIBAUDIT) $(LIBCRYPT) $(LIBECONF)
+newusers_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT) $(LIBECONF) -ldl
+nologin_LDADD =
+passwd_LDADD = $(LDADD) $(LIBPAM) $(LIBCRACK) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBECONF)
+pwck_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+pwconv_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+pwunconv_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+su_SOURCES = \
+ su.c \
+ suauth.c
+su_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD) $(LIBECONF)
+sulogin_LDADD = $(LDADD) $(LIBCRYPT) $(LIBECONF)
+useradd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBSEMANAGE) $(LIBACL) $(LIBATTR) $(LIBECONF) -ldl
+userdel_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBSEMANAGE) $(LIBECONF) -ldl
+usermod_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBSEMANAGE) $(LIBACL) $(LIBATTR) $(LIBECONF) -ldl
+vipw_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+
+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
+ set -e; for i in $(suidbins); do \
+ chmod $(suidperms) $(DESTDIR)$(bindir)/$$i; \
+ done
+ set -e; for i in $(suidubins); do \
+ chmod $(suidperms) $(DESTDIR)$(ubindir)/$$i; \
+ done
+ set -e; for i in $(suidusbins); do \
+ chmod $(suidperms) $(DESTDIR)$(usbindir)/$$i; \
+ done
+if WITH_TCB
+ set -e; for i in $(shadowsgidubins); do \
+ chown root:shadow $(DESTDIR)$(ubindir)/$$i; \
+ chmod $(sgidperms) $(DESTDIR)$(ubindir)/$$i; \
+ done
+endif
+if ENABLE_SUBIDS
+if FCAPS
+ setcap cap_setuid+ep $(DESTDIR)$(ubindir)/newuidmap
+ setcap cap_setgid+ep $(DESTDIR)$(ubindir)/newgidmap
+endif
+
+bin_PROGRAMS += getsubids
+noinst_PROGRAMS += get_subid_owners \
+ new_subid_range \
+ free_subid_range \
+ check_subid_range
+
+MISCLIBS = \
+ $(LIBAUDIT) \
+ $(LIBSELINUX) \
+ $(LIBSEMANAGE) \
+ $(LIBCRYPT_NOPAM) \
+ $(LIBSKEY) \
+ $(LIBMD) \
+ $(LIBECONF) \
+ $(LIBCRYPT) \
+ $(LIBTCB)
+
+getsubids_LDADD = \
+ $(top_builddir)/lib/libshadow.la \
+ $(top_builddir)/libmisc/libmisc.la \
+ $(top_builddir)/libsubid/libsubid.la \
+ $(MISCLIBS) -ldl
+
+getsubids_CPPFLAGS = \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/libmisc \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/libsubid
+
+get_subid_owners_LDADD = \
+ $(top_builddir)/lib/libshadow.la \
+ $(top_builddir)/libmisc/libmisc.la \
+ $(top_builddir)/libsubid/libsubid.la \
+ $(MISCLIBS) -ldl
+
+get_subid_owners_CPPFLAGS = \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/libmisc \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/libsubid
+
+new_subid_range_CPPFLAGS = \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/libmisc \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/libsubid
+
+new_subid_range_LDADD = \
+ $(top_builddir)/lib/libshadow.la \
+ $(top_builddir)/libmisc/libmisc.la \
+ $(top_builddir)/libsubid/libsubid.la \
+ $(MISCLIBS) -ldl
+
+free_subid_range_CPPFLAGS = \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/libmisc \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/libsubid
+
+free_subid_range_LDADD = \
+ $(top_builddir)/lib/libshadow.la \
+ $(top_builddir)/libmisc/libmisc.la \
+ $(top_builddir)/libsubid/libsubid.la \
+ $(MISCLIBS) -ldl
+
+check_subid_range_CPPFLAGS = \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/libmisc
+
+check_subid_range_LDADD = \
+ $(top_builddir)/lib/libshadow.la \
+ $(top_builddir)/libmisc/libmisc.la \
+ $(MISCLIBS) -ldl
+endif
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..da31572
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,1638 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+bin_PROGRAMS = groups$(EXEEXT) login$(EXEEXT) $(am__EXEEXT_1) \
+ $(am__EXEEXT_2)
+sbin_PROGRAMS = nologin$(EXEEXT)
+ubin_PROGRAMS = faillog$(EXEEXT) lastlog$(EXEEXT) chage$(EXEEXT) \
+ chfn$(EXEEXT) chsh$(EXEEXT) expiry$(EXEEXT) gpasswd$(EXEEXT) \
+ newgrp$(EXEEXT) passwd$(EXEEXT) $(am__EXEEXT_4)
+@ENABLE_SUBIDS_TRUE@am__append_1 = newgidmap newuidmap
+@WITH_SU_TRUE@am__append_2 = su
+usbin_PROGRAMS = chgpasswd$(EXEEXT) chpasswd$(EXEEXT) \
+ groupadd$(EXEEXT) groupdel$(EXEEXT) groupmems$(EXEEXT) \
+ groupmod$(EXEEXT) grpck$(EXEEXT) grpconv$(EXEEXT) \
+ grpunconv$(EXEEXT) logoutd$(EXEEXT) newusers$(EXEEXT) \
+ pwck$(EXEEXT) pwconv$(EXEEXT) pwunconv$(EXEEXT) \
+ useradd$(EXEEXT) userdel$(EXEEXT) usermod$(EXEEXT) \
+ vipw$(EXEEXT)
+noinst_PROGRAMS = id$(EXEEXT) sulogin$(EXEEXT) $(am__EXEEXT_3)
+@WITH_SU_TRUE@am__append_3 = su
+@WITH_TCB_FALSE@am__append_4 = passwd
+@ACCT_TOOLS_SETUID_TRUE@am__append_5 = chgpasswd chpasswd groupadd groupdel groupmod newusers useradd userdel usermod
+@ENABLE_SUBIDS_TRUE@@FCAPS_FALSE@am__append_6 = newgidmap newuidmap
+@ENABLE_SUBIDS_TRUE@am__append_7 = getsubids
+@ENABLE_SUBIDS_TRUE@am__append_8 = get_subid_owners \
+@ENABLE_SUBIDS_TRUE@ new_subid_range \
+@ENABLE_SUBIDS_TRUE@ free_subid_range \
+@ENABLE_SUBIDS_TRUE@ check_subid_range
+
+subdir = src
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/m4/nls.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@WITH_SU_TRUE@am__EXEEXT_1 = su$(EXEEXT)
+@ENABLE_SUBIDS_TRUE@am__EXEEXT_2 = getsubids$(EXEEXT)
+am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(sbindir)" \
+ "$(DESTDIR)$(ubindir)" "$(DESTDIR)$(usbindir)"
+@ENABLE_SUBIDS_TRUE@am__EXEEXT_3 = get_subid_owners$(EXEEXT) \
+@ENABLE_SUBIDS_TRUE@ new_subid_range$(EXEEXT) \
+@ENABLE_SUBIDS_TRUE@ free_subid_range$(EXEEXT) \
+@ENABLE_SUBIDS_TRUE@ check_subid_range$(EXEEXT)
+@ENABLE_SUBIDS_TRUE@am__EXEEXT_4 = newgidmap$(EXEEXT) \
+@ENABLE_SUBIDS_TRUE@ newuidmap$(EXEEXT)
+PROGRAMS = $(bin_PROGRAMS) $(noinst_PROGRAMS) $(sbin_PROGRAMS) \
+ $(ubin_PROGRAMS) $(usbin_PROGRAMS)
+chage_SOURCES = chage.c
+chage_OBJECTS = chage.$(OBJEXT)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) \
+ $(top_builddir)/libmisc/libmisc.la \
+ $(top_builddir)/lib/libshadow.la $(am__DEPENDENCIES_1)
+@ACCT_TOOLS_SETUID_TRUE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1)
+chage_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_3) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+check_subid_range_SOURCES = check_subid_range.c
+check_subid_range_OBJECTS = \
+ check_subid_range-check_subid_range.$(OBJEXT)
+@USE_PAM_FALSE@am__DEPENDENCIES_4 = $(am__DEPENDENCIES_1)
+@ENABLE_SUBIDS_TRUE@am__DEPENDENCIES_5 = $(am__DEPENDENCIES_1) \
+@ENABLE_SUBIDS_TRUE@ $(am__DEPENDENCIES_1) \
+@ENABLE_SUBIDS_TRUE@ $(am__DEPENDENCIES_1) \
+@ENABLE_SUBIDS_TRUE@ $(am__DEPENDENCIES_4) \
+@ENABLE_SUBIDS_TRUE@ $(am__DEPENDENCIES_1) \
+@ENABLE_SUBIDS_TRUE@ $(am__DEPENDENCIES_1) \
+@ENABLE_SUBIDS_TRUE@ $(am__DEPENDENCIES_1) \
+@ENABLE_SUBIDS_TRUE@ $(am__DEPENDENCIES_1) \
+@ENABLE_SUBIDS_TRUE@ $(am__DEPENDENCIES_1)
+@ENABLE_SUBIDS_TRUE@check_subid_range_DEPENDENCIES = \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/lib/libshadow.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libmisc/libmisc.la \
+@ENABLE_SUBIDS_TRUE@ $(am__DEPENDENCIES_5)
+chfn_SOURCES = chfn.c
+chfn_OBJECTS = chfn.$(OBJEXT)
+chfn_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_4) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+chgpasswd_SOURCES = chgpasswd.c
+chgpasswd_OBJECTS = chgpasswd.$(OBJEXT)
+chgpasswd_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_3) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+chpasswd_SOURCES = chpasswd.c
+chpasswd_OBJECTS = chpasswd.$(OBJEXT)
+chpasswd_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+chsh_SOURCES = chsh.c
+chsh_OBJECTS = chsh.$(OBJEXT)
+chsh_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_4) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+expiry_SOURCES = expiry.c
+expiry_OBJECTS = expiry.$(OBJEXT)
+expiry_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1)
+faillog_SOURCES = faillog.c
+faillog_OBJECTS = faillog.$(OBJEXT)
+faillog_LDADD = $(LDADD)
+faillog_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+ $(top_builddir)/libmisc/libmisc.la \
+ $(top_builddir)/lib/libshadow.la $(am__DEPENDENCIES_1)
+free_subid_range_SOURCES = free_subid_range.c
+free_subid_range_OBJECTS = \
+ free_subid_range-free_subid_range.$(OBJEXT)
+@ENABLE_SUBIDS_TRUE@free_subid_range_DEPENDENCIES = \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/lib/libshadow.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libmisc/libmisc.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libsubid/libsubid.la \
+@ENABLE_SUBIDS_TRUE@ $(am__DEPENDENCIES_5)
+get_subid_owners_SOURCES = get_subid_owners.c
+get_subid_owners_OBJECTS = \
+ get_subid_owners-get_subid_owners.$(OBJEXT)
+@ENABLE_SUBIDS_TRUE@get_subid_owners_DEPENDENCIES = \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/lib/libshadow.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libmisc/libmisc.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libsubid/libsubid.la \
+@ENABLE_SUBIDS_TRUE@ $(am__DEPENDENCIES_5)
+getsubids_SOURCES = getsubids.c
+getsubids_OBJECTS = getsubids-getsubids.$(OBJEXT)
+@ENABLE_SUBIDS_TRUE@getsubids_DEPENDENCIES = \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/lib/libshadow.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libmisc/libmisc.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libsubid/libsubid.la \
+@ENABLE_SUBIDS_TRUE@ $(am__DEPENDENCIES_5)
+gpasswd_SOURCES = gpasswd.c
+gpasswd_OBJECTS = gpasswd.$(OBJEXT)
+gpasswd_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+groupadd_SOURCES = groupadd.c
+groupadd_OBJECTS = groupadd.$(OBJEXT)
+groupadd_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_3) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+groupdel_SOURCES = groupdel.c
+groupdel_OBJECTS = groupdel.$(OBJEXT)
+groupdel_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_3) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+groupmems_SOURCES = groupmems.c
+groupmems_OBJECTS = groupmems.$(OBJEXT)
+groupmems_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+groupmod_SOURCES = groupmod.c
+groupmod_OBJECTS = groupmod.$(OBJEXT)
+groupmod_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_3) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+groups_SOURCES = groups.c
+groups_OBJECTS = groups.$(OBJEXT)
+groups_LDADD = $(LDADD)
+groups_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+ $(top_builddir)/libmisc/libmisc.la \
+ $(top_builddir)/lib/libshadow.la $(am__DEPENDENCIES_1)
+grpck_SOURCES = grpck.c
+grpck_OBJECTS = grpck.$(OBJEXT)
+grpck_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+grpconv_SOURCES = grpconv.c
+grpconv_OBJECTS = grpconv.$(OBJEXT)
+grpconv_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+grpunconv_SOURCES = grpunconv.c
+grpunconv_OBJECTS = grpunconv.$(OBJEXT)
+grpunconv_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+id_SOURCES = id.c
+id_OBJECTS = id.$(OBJEXT)
+id_LDADD = $(LDADD)
+id_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+ $(top_builddir)/libmisc/libmisc.la \
+ $(top_builddir)/lib/libshadow.la $(am__DEPENDENCIES_1)
+lastlog_SOURCES = lastlog.c
+lastlog_OBJECTS = lastlog.$(OBJEXT)
+lastlog_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am_login_OBJECTS = login.$(OBJEXT) login_nopam.$(OBJEXT)
+login_OBJECTS = $(am_login_OBJECTS)
+login_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_4) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+logoutd_SOURCES = logoutd.c
+logoutd_OBJECTS = logoutd.$(OBJEXT)
+logoutd_LDADD = $(LDADD)
+logoutd_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+ $(top_builddir)/libmisc/libmisc.la \
+ $(top_builddir)/lib/libshadow.la $(am__DEPENDENCIES_1)
+new_subid_range_SOURCES = new_subid_range.c
+new_subid_range_OBJECTS = new_subid_range-new_subid_range.$(OBJEXT)
+@ENABLE_SUBIDS_TRUE@new_subid_range_DEPENDENCIES = \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/lib/libshadow.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libmisc/libmisc.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libsubid/libsubid.la \
+@ENABLE_SUBIDS_TRUE@ $(am__DEPENDENCIES_5)
+newgidmap_SOURCES = newgidmap.c
+newgidmap_OBJECTS = newgidmap.$(OBJEXT)
+newgidmap_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+newgrp_SOURCES = newgrp.c
+newgrp_OBJECTS = newgrp.$(OBJEXT)
+newgrp_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+newuidmap_SOURCES = newuidmap.c
+newuidmap_OBJECTS = newuidmap.$(OBJEXT)
+newuidmap_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+newusers_SOURCES = newusers.c
+newusers_OBJECTS = newusers.$(OBJEXT)
+newusers_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+nologin_SOURCES = nologin.c
+nologin_OBJECTS = nologin.$(OBJEXT)
+nologin_DEPENDENCIES =
+passwd_SOURCES = passwd.c
+passwd_OBJECTS = passwd.$(OBJEXT)
+passwd_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_4) \
+ $(am__DEPENDENCIES_1)
+pwck_SOURCES = pwck.c
+pwck_OBJECTS = pwck.$(OBJEXT)
+pwck_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+pwconv_SOURCES = pwconv.c
+pwconv_OBJECTS = pwconv.$(OBJEXT)
+pwconv_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+pwunconv_SOURCES = pwunconv.c
+pwunconv_OBJECTS = pwunconv.$(OBJEXT)
+pwunconv_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_su_OBJECTS = su.$(OBJEXT) suauth.$(OBJEXT)
+su_OBJECTS = $(am_su_OBJECTS)
+su_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_4) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+sulogin_SOURCES = sulogin.c
+sulogin_OBJECTS = sulogin.$(OBJEXT)
+sulogin_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+useradd_SOURCES = useradd.c
+useradd_OBJECTS = useradd.$(OBJEXT)
+useradd_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_3) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+userdel_SOURCES = userdel.c
+userdel_OBJECTS = userdel.$(OBJEXT)
+userdel_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_3) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+usermod_SOURCES = usermod.c
+usermod_OBJECTS = usermod.$(OBJEXT)
+usermod_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_3) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+vipw_SOURCES = vipw.c
+vipw_OBJECTS = vipw.$(OBJEXT)
+vipw_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/chage.Po \
+ ./$(DEPDIR)/check_subid_range-check_subid_range.Po \
+ ./$(DEPDIR)/chfn.Po ./$(DEPDIR)/chgpasswd.Po \
+ ./$(DEPDIR)/chpasswd.Po ./$(DEPDIR)/chsh.Po \
+ ./$(DEPDIR)/expiry.Po ./$(DEPDIR)/faillog.Po \
+ ./$(DEPDIR)/free_subid_range-free_subid_range.Po \
+ ./$(DEPDIR)/get_subid_owners-get_subid_owners.Po \
+ ./$(DEPDIR)/getsubids-getsubids.Po ./$(DEPDIR)/gpasswd.Po \
+ ./$(DEPDIR)/groupadd.Po ./$(DEPDIR)/groupdel.Po \
+ ./$(DEPDIR)/groupmems.Po ./$(DEPDIR)/groupmod.Po \
+ ./$(DEPDIR)/groups.Po ./$(DEPDIR)/grpck.Po \
+ ./$(DEPDIR)/grpconv.Po ./$(DEPDIR)/grpunconv.Po \
+ ./$(DEPDIR)/id.Po ./$(DEPDIR)/lastlog.Po ./$(DEPDIR)/login.Po \
+ ./$(DEPDIR)/login_nopam.Po ./$(DEPDIR)/logoutd.Po \
+ ./$(DEPDIR)/new_subid_range-new_subid_range.Po \
+ ./$(DEPDIR)/newgidmap.Po ./$(DEPDIR)/newgrp.Po \
+ ./$(DEPDIR)/newuidmap.Po ./$(DEPDIR)/newusers.Po \
+ ./$(DEPDIR)/nologin.Po ./$(DEPDIR)/passwd.Po \
+ ./$(DEPDIR)/pwck.Po ./$(DEPDIR)/pwconv.Po \
+ ./$(DEPDIR)/pwunconv.Po ./$(DEPDIR)/su.Po \
+ ./$(DEPDIR)/suauth.Po ./$(DEPDIR)/sulogin.Po \
+ ./$(DEPDIR)/useradd.Po ./$(DEPDIR)/userdel.Po \
+ ./$(DEPDIR)/usermod.Po ./$(DEPDIR)/vipw.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = chage.c check_subid_range.c chfn.c chgpasswd.c chpasswd.c \
+ chsh.c expiry.c faillog.c free_subid_range.c \
+ get_subid_owners.c getsubids.c gpasswd.c groupadd.c groupdel.c \
+ groupmems.c groupmod.c groups.c grpck.c grpconv.c grpunconv.c \
+ id.c lastlog.c $(login_SOURCES) logoutd.c new_subid_range.c \
+ newgidmap.c newgrp.c newuidmap.c newusers.c nologin.c passwd.c \
+ pwck.c pwconv.c pwunconv.c $(su_SOURCES) sulogin.c useradd.c \
+ userdel.c usermod.c vipw.c
+DIST_SOURCES = chage.c check_subid_range.c chfn.c chgpasswd.c \
+ chpasswd.c chsh.c expiry.c faillog.c free_subid_range.c \
+ get_subid_owners.c getsubids.c gpasswd.c groupadd.c groupdel.c \
+ groupmems.c groupmod.c groups.c grpck.c grpconv.c grpunconv.c \
+ id.c lastlog.c $(login_SOURCES) logoutd.c new_subid_range.c \
+ newgidmap.c newgrp.c newuidmap.c newusers.c nologin.c passwd.c \
+ pwck.c pwconv.c pwunconv.c $(su_SOURCES) sulogin.c useradd.c \
+ userdel.c usermod.c vipw.c
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+ECONF_CPPFLAGS = @ECONF_CPPFLAGS@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+GROUP_NAME_MAX_LENGTH = @GROUP_NAME_MAX_LENGTH@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBACL = @LIBACL@
+LIBATTR = @LIBATTR@
+LIBAUDIT = @LIBAUDIT@
+LIBCRACK = @LIBCRACK@
+LIBCRYPT = @LIBCRYPT@
+LIBECONF = @LIBECONF@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMD = @LIBMD@
+LIBOBJS = @LIBOBJS@
+LIBPAM = @LIBPAM@
+LIBS = @LIBS@
+LIBSELINUX = @LIBSELINUX@
+LIBSEMANAGE = @LIBSEMANAGE@
+LIBSKEY = @LIBSKEY@
+LIBSUBID_ABI = @LIBSUBID_ABI@
+LIBSUBID_ABI_MAJOR = @LIBSUBID_ABI_MAJOR@
+LIBSUBID_ABI_MICRO = @LIBSUBID_ABI_MICRO@
+LIBSUBID_ABI_MINOR = @LIBSUBID_ABI_MINOR@
+LIBTCB = @LIBTCB@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LIYESCRYPT = @LIYESCRYPT@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+POSUB = @POSUB@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VENDORDIR = @VENDORDIR@
+VERSION = @VERSION@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMLCATALOG = @XMLCATALOG@
+XML_CATALOG_FILE = @XML_CATALOG_FILE@
+XSLTPROC = @XSLTPROC@
+YACC = @YACC@
+YFLAGS = @YFLAGS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+capcmd = @capcmd@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+EXTRA_DIST = \
+ .indent.pro
+
+ubindir = ${prefix}/bin
+usbindir = ${prefix}/sbin
+suidperms = 4755
+sgidperms = 2755
+AM_CPPFLAGS = \
+ -I${top_srcdir}/lib \
+ -I$(top_srcdir)/libmisc \
+ -I$(top_srcdir) \
+ -DLOCALEDIR=\"$(datadir)/locale\"
+
+suidusbins = $(am__append_5)
+suidbins = $(am__append_3)
+suidubins = chage chfn chsh expiry gpasswd newgrp $(am__append_4) \
+ $(am__append_6)
+@WITH_TCB_TRUE@shadowsgidubins = passwd
+LDADD = $(INTLLIBS) \
+ $(top_builddir)/libmisc/libmisc.la \
+ $(top_builddir)/lib/libshadow.la \
+ $(LIBTCB)
+
+@ACCT_TOOLS_SETUID_FALSE@LIBPAM_SUID =
+@ACCT_TOOLS_SETUID_TRUE@LIBPAM_SUID = $(LIBPAM)
+@USE_PAM_FALSE@LIBCRYPT_NOPAM = $(LIBCRYPT)
+@USE_PAM_TRUE@LIBCRYPT_NOPAM =
+chage_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+newuidmap_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBCAP) $(LIBECONF) -ldl
+newgidmap_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBCAP) $(LIBECONF) -ldl
+chfn_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD) $(LIBECONF)
+chgpasswd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT) $(LIBECONF)
+chsh_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD) $(LIBECONF)
+chpasswd_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT) $(LIBECONF)
+expiry_LDADD = $(LDADD) $(LIBECONF)
+gpasswd_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT) $(LIBECONF)
+groupadd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF) -ldl
+groupdel_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF) -ldl
+groupmems_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+groupmod_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF) -ldl
+grpck_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+grpconv_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+grpunconv_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+lastlog_LDADD = $(LDADD) $(LIBAUDIT) $(LIBECONF)
+login_SOURCES = \
+ login.c \
+ login_nopam.c
+
+login_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD) $(LIBECONF)
+newgrp_LDADD = $(LDADD) $(LIBAUDIT) $(LIBCRYPT) $(LIBECONF)
+newusers_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT) $(LIBECONF) -ldl
+nologin_LDADD =
+passwd_LDADD = $(LDADD) $(LIBPAM) $(LIBCRACK) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBECONF)
+pwck_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+pwconv_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+pwunconv_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+su_SOURCES = \
+ su.c \
+ suauth.c
+
+su_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD) $(LIBECONF)
+sulogin_LDADD = $(LDADD) $(LIBCRYPT) $(LIBECONF)
+useradd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBSEMANAGE) $(LIBACL) $(LIBATTR) $(LIBECONF) -ldl
+userdel_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBSEMANAGE) $(LIBECONF) -ldl
+usermod_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBSEMANAGE) $(LIBACL) $(LIBATTR) $(LIBECONF) -ldl
+vipw_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBECONF)
+@ENABLE_SUBIDS_TRUE@MISCLIBS = \
+@ENABLE_SUBIDS_TRUE@ $(LIBAUDIT) \
+@ENABLE_SUBIDS_TRUE@ $(LIBSELINUX) \
+@ENABLE_SUBIDS_TRUE@ $(LIBSEMANAGE) \
+@ENABLE_SUBIDS_TRUE@ $(LIBCRYPT_NOPAM) \
+@ENABLE_SUBIDS_TRUE@ $(LIBSKEY) \
+@ENABLE_SUBIDS_TRUE@ $(LIBMD) \
+@ENABLE_SUBIDS_TRUE@ $(LIBECONF) \
+@ENABLE_SUBIDS_TRUE@ $(LIBCRYPT) \
+@ENABLE_SUBIDS_TRUE@ $(LIBTCB)
+
+@ENABLE_SUBIDS_TRUE@getsubids_LDADD = \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/lib/libshadow.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libmisc/libmisc.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libsubid/libsubid.la \
+@ENABLE_SUBIDS_TRUE@ $(MISCLIBS) -ldl
+
+@ENABLE_SUBIDS_TRUE@getsubids_CPPFLAGS = \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir)/lib \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir)/libmisc \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir) \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir)/libsubid
+
+@ENABLE_SUBIDS_TRUE@get_subid_owners_LDADD = \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/lib/libshadow.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libmisc/libmisc.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libsubid/libsubid.la \
+@ENABLE_SUBIDS_TRUE@ $(MISCLIBS) -ldl
+
+@ENABLE_SUBIDS_TRUE@get_subid_owners_CPPFLAGS = \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir)/lib \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir)/libmisc \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir) \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir)/libsubid
+
+@ENABLE_SUBIDS_TRUE@new_subid_range_CPPFLAGS = \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir)/lib \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir)/libmisc \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir) \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir)/libsubid
+
+@ENABLE_SUBIDS_TRUE@new_subid_range_LDADD = \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/lib/libshadow.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libmisc/libmisc.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libsubid/libsubid.la \
+@ENABLE_SUBIDS_TRUE@ $(MISCLIBS) -ldl
+
+@ENABLE_SUBIDS_TRUE@free_subid_range_CPPFLAGS = \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir)/lib \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir)/libmisc \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir) \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir)/libsubid
+
+@ENABLE_SUBIDS_TRUE@free_subid_range_LDADD = \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/lib/libshadow.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libmisc/libmisc.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libsubid/libsubid.la \
+@ENABLE_SUBIDS_TRUE@ $(MISCLIBS) -ldl
+
+@ENABLE_SUBIDS_TRUE@check_subid_range_CPPFLAGS = \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir)/lib \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir) \
+@ENABLE_SUBIDS_TRUE@ -I$(top_srcdir)/libmisc
+
+@ENABLE_SUBIDS_TRUE@check_subid_range_LDADD = \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/lib/libshadow.la \
+@ENABLE_SUBIDS_TRUE@ $(top_builddir)/libmisc/libmisc.la \
+@ENABLE_SUBIDS_TRUE@ $(MISCLIBS) -ldl
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+install-sbinPROGRAMS: $(sbin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-sbinPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(sbindir)" && rm -f $$files
+
+clean-sbinPROGRAMS:
+ @list='$(sbin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+install-ubinPROGRAMS: $(ubin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(ubin_PROGRAMS)'; test -n "$(ubindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(ubindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(ubindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(ubindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(ubindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-ubinPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(ubin_PROGRAMS)'; test -n "$(ubindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(ubindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(ubindir)" && rm -f $$files
+
+clean-ubinPROGRAMS:
+ @list='$(ubin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+install-usbinPROGRAMS: $(usbin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(usbin_PROGRAMS)'; test -n "$(usbindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(usbindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(usbindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(usbindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(usbindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-usbinPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(usbin_PROGRAMS)'; test -n "$(usbindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(usbindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(usbindir)" && rm -f $$files
+
+clean-usbinPROGRAMS:
+ @list='$(usbin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+chage$(EXEEXT): $(chage_OBJECTS) $(chage_DEPENDENCIES) $(EXTRA_chage_DEPENDENCIES)
+ @rm -f chage$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(chage_OBJECTS) $(chage_LDADD) $(LIBS)
+
+check_subid_range$(EXEEXT): $(check_subid_range_OBJECTS) $(check_subid_range_DEPENDENCIES) $(EXTRA_check_subid_range_DEPENDENCIES)
+ @rm -f check_subid_range$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(check_subid_range_OBJECTS) $(check_subid_range_LDADD) $(LIBS)
+
+chfn$(EXEEXT): $(chfn_OBJECTS) $(chfn_DEPENDENCIES) $(EXTRA_chfn_DEPENDENCIES)
+ @rm -f chfn$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(chfn_OBJECTS) $(chfn_LDADD) $(LIBS)
+
+chgpasswd$(EXEEXT): $(chgpasswd_OBJECTS) $(chgpasswd_DEPENDENCIES) $(EXTRA_chgpasswd_DEPENDENCIES)
+ @rm -f chgpasswd$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(chgpasswd_OBJECTS) $(chgpasswd_LDADD) $(LIBS)
+
+chpasswd$(EXEEXT): $(chpasswd_OBJECTS) $(chpasswd_DEPENDENCIES) $(EXTRA_chpasswd_DEPENDENCIES)
+ @rm -f chpasswd$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(chpasswd_OBJECTS) $(chpasswd_LDADD) $(LIBS)
+
+chsh$(EXEEXT): $(chsh_OBJECTS) $(chsh_DEPENDENCIES) $(EXTRA_chsh_DEPENDENCIES)
+ @rm -f chsh$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(chsh_OBJECTS) $(chsh_LDADD) $(LIBS)
+
+expiry$(EXEEXT): $(expiry_OBJECTS) $(expiry_DEPENDENCIES) $(EXTRA_expiry_DEPENDENCIES)
+ @rm -f expiry$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(expiry_OBJECTS) $(expiry_LDADD) $(LIBS)
+
+faillog$(EXEEXT): $(faillog_OBJECTS) $(faillog_DEPENDENCIES) $(EXTRA_faillog_DEPENDENCIES)
+ @rm -f faillog$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(faillog_OBJECTS) $(faillog_LDADD) $(LIBS)
+
+free_subid_range$(EXEEXT): $(free_subid_range_OBJECTS) $(free_subid_range_DEPENDENCIES) $(EXTRA_free_subid_range_DEPENDENCIES)
+ @rm -f free_subid_range$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(free_subid_range_OBJECTS) $(free_subid_range_LDADD) $(LIBS)
+
+get_subid_owners$(EXEEXT): $(get_subid_owners_OBJECTS) $(get_subid_owners_DEPENDENCIES) $(EXTRA_get_subid_owners_DEPENDENCIES)
+ @rm -f get_subid_owners$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(get_subid_owners_OBJECTS) $(get_subid_owners_LDADD) $(LIBS)
+
+getsubids$(EXEEXT): $(getsubids_OBJECTS) $(getsubids_DEPENDENCIES) $(EXTRA_getsubids_DEPENDENCIES)
+ @rm -f getsubids$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(getsubids_OBJECTS) $(getsubids_LDADD) $(LIBS)
+
+gpasswd$(EXEEXT): $(gpasswd_OBJECTS) $(gpasswd_DEPENDENCIES) $(EXTRA_gpasswd_DEPENDENCIES)
+ @rm -f gpasswd$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(gpasswd_OBJECTS) $(gpasswd_LDADD) $(LIBS)
+
+groupadd$(EXEEXT): $(groupadd_OBJECTS) $(groupadd_DEPENDENCIES) $(EXTRA_groupadd_DEPENDENCIES)
+ @rm -f groupadd$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(groupadd_OBJECTS) $(groupadd_LDADD) $(LIBS)
+
+groupdel$(EXEEXT): $(groupdel_OBJECTS) $(groupdel_DEPENDENCIES) $(EXTRA_groupdel_DEPENDENCIES)
+ @rm -f groupdel$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(groupdel_OBJECTS) $(groupdel_LDADD) $(LIBS)
+
+groupmems$(EXEEXT): $(groupmems_OBJECTS) $(groupmems_DEPENDENCIES) $(EXTRA_groupmems_DEPENDENCIES)
+ @rm -f groupmems$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(groupmems_OBJECTS) $(groupmems_LDADD) $(LIBS)
+
+groupmod$(EXEEXT): $(groupmod_OBJECTS) $(groupmod_DEPENDENCIES) $(EXTRA_groupmod_DEPENDENCIES)
+ @rm -f groupmod$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(groupmod_OBJECTS) $(groupmod_LDADD) $(LIBS)
+
+groups$(EXEEXT): $(groups_OBJECTS) $(groups_DEPENDENCIES) $(EXTRA_groups_DEPENDENCIES)
+ @rm -f groups$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(groups_OBJECTS) $(groups_LDADD) $(LIBS)
+
+grpck$(EXEEXT): $(grpck_OBJECTS) $(grpck_DEPENDENCIES) $(EXTRA_grpck_DEPENDENCIES)
+ @rm -f grpck$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(grpck_OBJECTS) $(grpck_LDADD) $(LIBS)
+
+grpconv$(EXEEXT): $(grpconv_OBJECTS) $(grpconv_DEPENDENCIES) $(EXTRA_grpconv_DEPENDENCIES)
+ @rm -f grpconv$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(grpconv_OBJECTS) $(grpconv_LDADD) $(LIBS)
+
+grpunconv$(EXEEXT): $(grpunconv_OBJECTS) $(grpunconv_DEPENDENCIES) $(EXTRA_grpunconv_DEPENDENCIES)
+ @rm -f grpunconv$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(grpunconv_OBJECTS) $(grpunconv_LDADD) $(LIBS)
+
+id$(EXEEXT): $(id_OBJECTS) $(id_DEPENDENCIES) $(EXTRA_id_DEPENDENCIES)
+ @rm -f id$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(id_OBJECTS) $(id_LDADD) $(LIBS)
+
+lastlog$(EXEEXT): $(lastlog_OBJECTS) $(lastlog_DEPENDENCIES) $(EXTRA_lastlog_DEPENDENCIES)
+ @rm -f lastlog$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(lastlog_OBJECTS) $(lastlog_LDADD) $(LIBS)
+
+login$(EXEEXT): $(login_OBJECTS) $(login_DEPENDENCIES) $(EXTRA_login_DEPENDENCIES)
+ @rm -f login$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(login_OBJECTS) $(login_LDADD) $(LIBS)
+
+logoutd$(EXEEXT): $(logoutd_OBJECTS) $(logoutd_DEPENDENCIES) $(EXTRA_logoutd_DEPENDENCIES)
+ @rm -f logoutd$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(logoutd_OBJECTS) $(logoutd_LDADD) $(LIBS)
+
+new_subid_range$(EXEEXT): $(new_subid_range_OBJECTS) $(new_subid_range_DEPENDENCIES) $(EXTRA_new_subid_range_DEPENDENCIES)
+ @rm -f new_subid_range$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(new_subid_range_OBJECTS) $(new_subid_range_LDADD) $(LIBS)
+
+newgidmap$(EXEEXT): $(newgidmap_OBJECTS) $(newgidmap_DEPENDENCIES) $(EXTRA_newgidmap_DEPENDENCIES)
+ @rm -f newgidmap$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(newgidmap_OBJECTS) $(newgidmap_LDADD) $(LIBS)
+
+newgrp$(EXEEXT): $(newgrp_OBJECTS) $(newgrp_DEPENDENCIES) $(EXTRA_newgrp_DEPENDENCIES)
+ @rm -f newgrp$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(newgrp_OBJECTS) $(newgrp_LDADD) $(LIBS)
+
+newuidmap$(EXEEXT): $(newuidmap_OBJECTS) $(newuidmap_DEPENDENCIES) $(EXTRA_newuidmap_DEPENDENCIES)
+ @rm -f newuidmap$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(newuidmap_OBJECTS) $(newuidmap_LDADD) $(LIBS)
+
+newusers$(EXEEXT): $(newusers_OBJECTS) $(newusers_DEPENDENCIES) $(EXTRA_newusers_DEPENDENCIES)
+ @rm -f newusers$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(newusers_OBJECTS) $(newusers_LDADD) $(LIBS)
+
+nologin$(EXEEXT): $(nologin_OBJECTS) $(nologin_DEPENDENCIES) $(EXTRA_nologin_DEPENDENCIES)
+ @rm -f nologin$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(nologin_OBJECTS) $(nologin_LDADD) $(LIBS)
+
+passwd$(EXEEXT): $(passwd_OBJECTS) $(passwd_DEPENDENCIES) $(EXTRA_passwd_DEPENDENCIES)
+ @rm -f passwd$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(passwd_OBJECTS) $(passwd_LDADD) $(LIBS)
+
+pwck$(EXEEXT): $(pwck_OBJECTS) $(pwck_DEPENDENCIES) $(EXTRA_pwck_DEPENDENCIES)
+ @rm -f pwck$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(pwck_OBJECTS) $(pwck_LDADD) $(LIBS)
+
+pwconv$(EXEEXT): $(pwconv_OBJECTS) $(pwconv_DEPENDENCIES) $(EXTRA_pwconv_DEPENDENCIES)
+ @rm -f pwconv$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(pwconv_OBJECTS) $(pwconv_LDADD) $(LIBS)
+
+pwunconv$(EXEEXT): $(pwunconv_OBJECTS) $(pwunconv_DEPENDENCIES) $(EXTRA_pwunconv_DEPENDENCIES)
+ @rm -f pwunconv$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(pwunconv_OBJECTS) $(pwunconv_LDADD) $(LIBS)
+
+su$(EXEEXT): $(su_OBJECTS) $(su_DEPENDENCIES) $(EXTRA_su_DEPENDENCIES)
+ @rm -f su$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(su_OBJECTS) $(su_LDADD) $(LIBS)
+
+sulogin$(EXEEXT): $(sulogin_OBJECTS) $(sulogin_DEPENDENCIES) $(EXTRA_sulogin_DEPENDENCIES)
+ @rm -f sulogin$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sulogin_OBJECTS) $(sulogin_LDADD) $(LIBS)
+
+useradd$(EXEEXT): $(useradd_OBJECTS) $(useradd_DEPENDENCIES) $(EXTRA_useradd_DEPENDENCIES)
+ @rm -f useradd$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(useradd_OBJECTS) $(useradd_LDADD) $(LIBS)
+
+userdel$(EXEEXT): $(userdel_OBJECTS) $(userdel_DEPENDENCIES) $(EXTRA_userdel_DEPENDENCIES)
+ @rm -f userdel$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(userdel_OBJECTS) $(userdel_LDADD) $(LIBS)
+
+usermod$(EXEEXT): $(usermod_OBJECTS) $(usermod_DEPENDENCIES) $(EXTRA_usermod_DEPENDENCIES)
+ @rm -f usermod$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(usermod_OBJECTS) $(usermod_LDADD) $(LIBS)
+
+vipw$(EXEEXT): $(vipw_OBJECTS) $(vipw_DEPENDENCIES) $(EXTRA_vipw_DEPENDENCIES)
+ @rm -f vipw$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(vipw_OBJECTS) $(vipw_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chage.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/check_subid_range-check_subid_range.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chfn.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chgpasswd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chpasswd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chsh.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/expiry.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/faillog.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/free_subid_range-free_subid_range.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/get_subid_owners-get_subid_owners.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getsubids-getsubids.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpasswd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/groupadd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/groupdel.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/groupmems.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/groupmod.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/groups.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/grpck.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/grpconv.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/grpunconv.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/id.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lastlog.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/login.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/login_nopam.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/logoutd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/new_subid_range-new_subid_range.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/newgidmap.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/newgrp.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/newuidmap.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/newusers.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nologin.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passwd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pwck.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pwconv.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pwunconv.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/su.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/suauth.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sulogin.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/useradd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdel.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/usermod.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vipw.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+check_subid_range-check_subid_range.o: check_subid_range.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(check_subid_range_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT check_subid_range-check_subid_range.o -MD -MP -MF $(DEPDIR)/check_subid_range-check_subid_range.Tpo -c -o check_subid_range-check_subid_range.o `test -f 'check_subid_range.c' || echo '$(srcdir)/'`check_subid_range.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/check_subid_range-check_subid_range.Tpo $(DEPDIR)/check_subid_range-check_subid_range.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='check_subid_range.c' object='check_subid_range-check_subid_range.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(check_subid_range_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o check_subid_range-check_subid_range.o `test -f 'check_subid_range.c' || echo '$(srcdir)/'`check_subid_range.c
+
+check_subid_range-check_subid_range.obj: check_subid_range.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(check_subid_range_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT check_subid_range-check_subid_range.obj -MD -MP -MF $(DEPDIR)/check_subid_range-check_subid_range.Tpo -c -o check_subid_range-check_subid_range.obj `if test -f 'check_subid_range.c'; then $(CYGPATH_W) 'check_subid_range.c'; else $(CYGPATH_W) '$(srcdir)/check_subid_range.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/check_subid_range-check_subid_range.Tpo $(DEPDIR)/check_subid_range-check_subid_range.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='check_subid_range.c' object='check_subid_range-check_subid_range.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(check_subid_range_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o check_subid_range-check_subid_range.obj `if test -f 'check_subid_range.c'; then $(CYGPATH_W) 'check_subid_range.c'; else $(CYGPATH_W) '$(srcdir)/check_subid_range.c'; fi`
+
+free_subid_range-free_subid_range.o: free_subid_range.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(free_subid_range_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT free_subid_range-free_subid_range.o -MD -MP -MF $(DEPDIR)/free_subid_range-free_subid_range.Tpo -c -o free_subid_range-free_subid_range.o `test -f 'free_subid_range.c' || echo '$(srcdir)/'`free_subid_range.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/free_subid_range-free_subid_range.Tpo $(DEPDIR)/free_subid_range-free_subid_range.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='free_subid_range.c' object='free_subid_range-free_subid_range.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(free_subid_range_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o free_subid_range-free_subid_range.o `test -f 'free_subid_range.c' || echo '$(srcdir)/'`free_subid_range.c
+
+free_subid_range-free_subid_range.obj: free_subid_range.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(free_subid_range_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT free_subid_range-free_subid_range.obj -MD -MP -MF $(DEPDIR)/free_subid_range-free_subid_range.Tpo -c -o free_subid_range-free_subid_range.obj `if test -f 'free_subid_range.c'; then $(CYGPATH_W) 'free_subid_range.c'; else $(CYGPATH_W) '$(srcdir)/free_subid_range.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/free_subid_range-free_subid_range.Tpo $(DEPDIR)/free_subid_range-free_subid_range.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='free_subid_range.c' object='free_subid_range-free_subid_range.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(free_subid_range_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o free_subid_range-free_subid_range.obj `if test -f 'free_subid_range.c'; then $(CYGPATH_W) 'free_subid_range.c'; else $(CYGPATH_W) '$(srcdir)/free_subid_range.c'; fi`
+
+get_subid_owners-get_subid_owners.o: get_subid_owners.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(get_subid_owners_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT get_subid_owners-get_subid_owners.o -MD -MP -MF $(DEPDIR)/get_subid_owners-get_subid_owners.Tpo -c -o get_subid_owners-get_subid_owners.o `test -f 'get_subid_owners.c' || echo '$(srcdir)/'`get_subid_owners.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/get_subid_owners-get_subid_owners.Tpo $(DEPDIR)/get_subid_owners-get_subid_owners.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='get_subid_owners.c' object='get_subid_owners-get_subid_owners.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(get_subid_owners_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o get_subid_owners-get_subid_owners.o `test -f 'get_subid_owners.c' || echo '$(srcdir)/'`get_subid_owners.c
+
+get_subid_owners-get_subid_owners.obj: get_subid_owners.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(get_subid_owners_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT get_subid_owners-get_subid_owners.obj -MD -MP -MF $(DEPDIR)/get_subid_owners-get_subid_owners.Tpo -c -o get_subid_owners-get_subid_owners.obj `if test -f 'get_subid_owners.c'; then $(CYGPATH_W) 'get_subid_owners.c'; else $(CYGPATH_W) '$(srcdir)/get_subid_owners.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/get_subid_owners-get_subid_owners.Tpo $(DEPDIR)/get_subid_owners-get_subid_owners.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='get_subid_owners.c' object='get_subid_owners-get_subid_owners.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(get_subid_owners_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o get_subid_owners-get_subid_owners.obj `if test -f 'get_subid_owners.c'; then $(CYGPATH_W) 'get_subid_owners.c'; else $(CYGPATH_W) '$(srcdir)/get_subid_owners.c'; fi`
+
+getsubids-getsubids.o: getsubids.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(getsubids_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT getsubids-getsubids.o -MD -MP -MF $(DEPDIR)/getsubids-getsubids.Tpo -c -o getsubids-getsubids.o `test -f 'getsubids.c' || echo '$(srcdir)/'`getsubids.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/getsubids-getsubids.Tpo $(DEPDIR)/getsubids-getsubids.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='getsubids.c' object='getsubids-getsubids.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(getsubids_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o getsubids-getsubids.o `test -f 'getsubids.c' || echo '$(srcdir)/'`getsubids.c
+
+getsubids-getsubids.obj: getsubids.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(getsubids_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT getsubids-getsubids.obj -MD -MP -MF $(DEPDIR)/getsubids-getsubids.Tpo -c -o getsubids-getsubids.obj `if test -f 'getsubids.c'; then $(CYGPATH_W) 'getsubids.c'; else $(CYGPATH_W) '$(srcdir)/getsubids.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/getsubids-getsubids.Tpo $(DEPDIR)/getsubids-getsubids.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='getsubids.c' object='getsubids-getsubids.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(getsubids_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o getsubids-getsubids.obj `if test -f 'getsubids.c'; then $(CYGPATH_W) 'getsubids.c'; else $(CYGPATH_W) '$(srcdir)/getsubids.c'; fi`
+
+new_subid_range-new_subid_range.o: new_subid_range.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(new_subid_range_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT new_subid_range-new_subid_range.o -MD -MP -MF $(DEPDIR)/new_subid_range-new_subid_range.Tpo -c -o new_subid_range-new_subid_range.o `test -f 'new_subid_range.c' || echo '$(srcdir)/'`new_subid_range.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/new_subid_range-new_subid_range.Tpo $(DEPDIR)/new_subid_range-new_subid_range.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='new_subid_range.c' object='new_subid_range-new_subid_range.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(new_subid_range_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o new_subid_range-new_subid_range.o `test -f 'new_subid_range.c' || echo '$(srcdir)/'`new_subid_range.c
+
+new_subid_range-new_subid_range.obj: new_subid_range.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(new_subid_range_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT new_subid_range-new_subid_range.obj -MD -MP -MF $(DEPDIR)/new_subid_range-new_subid_range.Tpo -c -o new_subid_range-new_subid_range.obj `if test -f 'new_subid_range.c'; then $(CYGPATH_W) 'new_subid_range.c'; else $(CYGPATH_W) '$(srcdir)/new_subid_range.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/new_subid_range-new_subid_range.Tpo $(DEPDIR)/new_subid_range-new_subid_range.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='new_subid_range.c' object='new_subid_range-new_subid_range.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(new_subid_range_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o new_subid_range-new_subid_range.obj `if test -f 'new_subid_range.c'; then $(CYGPATH_W) 'new_subid_range.c'; else $(CYGPATH_W) '$(srcdir)/new_subid_range.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(ubindir)" "$(DESTDIR)$(usbindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool \
+ clean-noinstPROGRAMS clean-sbinPROGRAMS clean-ubinPROGRAMS \
+ clean-usbinPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/chage.Po
+ -rm -f ./$(DEPDIR)/check_subid_range-check_subid_range.Po
+ -rm -f ./$(DEPDIR)/chfn.Po
+ -rm -f ./$(DEPDIR)/chgpasswd.Po
+ -rm -f ./$(DEPDIR)/chpasswd.Po
+ -rm -f ./$(DEPDIR)/chsh.Po
+ -rm -f ./$(DEPDIR)/expiry.Po
+ -rm -f ./$(DEPDIR)/faillog.Po
+ -rm -f ./$(DEPDIR)/free_subid_range-free_subid_range.Po
+ -rm -f ./$(DEPDIR)/get_subid_owners-get_subid_owners.Po
+ -rm -f ./$(DEPDIR)/getsubids-getsubids.Po
+ -rm -f ./$(DEPDIR)/gpasswd.Po
+ -rm -f ./$(DEPDIR)/groupadd.Po
+ -rm -f ./$(DEPDIR)/groupdel.Po
+ -rm -f ./$(DEPDIR)/groupmems.Po
+ -rm -f ./$(DEPDIR)/groupmod.Po
+ -rm -f ./$(DEPDIR)/groups.Po
+ -rm -f ./$(DEPDIR)/grpck.Po
+ -rm -f ./$(DEPDIR)/grpconv.Po
+ -rm -f ./$(DEPDIR)/grpunconv.Po
+ -rm -f ./$(DEPDIR)/id.Po
+ -rm -f ./$(DEPDIR)/lastlog.Po
+ -rm -f ./$(DEPDIR)/login.Po
+ -rm -f ./$(DEPDIR)/login_nopam.Po
+ -rm -f ./$(DEPDIR)/logoutd.Po
+ -rm -f ./$(DEPDIR)/new_subid_range-new_subid_range.Po
+ -rm -f ./$(DEPDIR)/newgidmap.Po
+ -rm -f ./$(DEPDIR)/newgrp.Po
+ -rm -f ./$(DEPDIR)/newuidmap.Po
+ -rm -f ./$(DEPDIR)/newusers.Po
+ -rm -f ./$(DEPDIR)/nologin.Po
+ -rm -f ./$(DEPDIR)/passwd.Po
+ -rm -f ./$(DEPDIR)/pwck.Po
+ -rm -f ./$(DEPDIR)/pwconv.Po
+ -rm -f ./$(DEPDIR)/pwunconv.Po
+ -rm -f ./$(DEPDIR)/su.Po
+ -rm -f ./$(DEPDIR)/suauth.Po
+ -rm -f ./$(DEPDIR)/sulogin.Po
+ -rm -f ./$(DEPDIR)/useradd.Po
+ -rm -f ./$(DEPDIR)/userdel.Po
+ -rm -f ./$(DEPDIR)/usermod.Po
+ -rm -f ./$(DEPDIR)/vipw.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-ubinPROGRAMS install-usbinPROGRAMS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS install-sbinPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/chage.Po
+ -rm -f ./$(DEPDIR)/check_subid_range-check_subid_range.Po
+ -rm -f ./$(DEPDIR)/chfn.Po
+ -rm -f ./$(DEPDIR)/chgpasswd.Po
+ -rm -f ./$(DEPDIR)/chpasswd.Po
+ -rm -f ./$(DEPDIR)/chsh.Po
+ -rm -f ./$(DEPDIR)/expiry.Po
+ -rm -f ./$(DEPDIR)/faillog.Po
+ -rm -f ./$(DEPDIR)/free_subid_range-free_subid_range.Po
+ -rm -f ./$(DEPDIR)/get_subid_owners-get_subid_owners.Po
+ -rm -f ./$(DEPDIR)/getsubids-getsubids.Po
+ -rm -f ./$(DEPDIR)/gpasswd.Po
+ -rm -f ./$(DEPDIR)/groupadd.Po
+ -rm -f ./$(DEPDIR)/groupdel.Po
+ -rm -f ./$(DEPDIR)/groupmems.Po
+ -rm -f ./$(DEPDIR)/groupmod.Po
+ -rm -f ./$(DEPDIR)/groups.Po
+ -rm -f ./$(DEPDIR)/grpck.Po
+ -rm -f ./$(DEPDIR)/grpconv.Po
+ -rm -f ./$(DEPDIR)/grpunconv.Po
+ -rm -f ./$(DEPDIR)/id.Po
+ -rm -f ./$(DEPDIR)/lastlog.Po
+ -rm -f ./$(DEPDIR)/login.Po
+ -rm -f ./$(DEPDIR)/login_nopam.Po
+ -rm -f ./$(DEPDIR)/logoutd.Po
+ -rm -f ./$(DEPDIR)/new_subid_range-new_subid_range.Po
+ -rm -f ./$(DEPDIR)/newgidmap.Po
+ -rm -f ./$(DEPDIR)/newgrp.Po
+ -rm -f ./$(DEPDIR)/newuidmap.Po
+ -rm -f ./$(DEPDIR)/newusers.Po
+ -rm -f ./$(DEPDIR)/nologin.Po
+ -rm -f ./$(DEPDIR)/passwd.Po
+ -rm -f ./$(DEPDIR)/pwck.Po
+ -rm -f ./$(DEPDIR)/pwconv.Po
+ -rm -f ./$(DEPDIR)/pwunconv.Po
+ -rm -f ./$(DEPDIR)/su.Po
+ -rm -f ./$(DEPDIR)/suauth.Po
+ -rm -f ./$(DEPDIR)/sulogin.Po
+ -rm -f ./$(DEPDIR)/useradd.Po
+ -rm -f ./$(DEPDIR)/userdel.Po
+ -rm -f ./$(DEPDIR)/usermod.Po
+ -rm -f ./$(DEPDIR)/vipw.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS uninstall-sbinPROGRAMS \
+ uninstall-ubinPROGRAMS uninstall-usbinPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-binPROGRAMS clean-generic clean-libtool \
+ clean-noinstPROGRAMS clean-sbinPROGRAMS clean-ubinPROGRAMS \
+ clean-usbinPROGRAMS cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-binPROGRAMS install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-sbinPROGRAMS install-strip \
+ install-ubinPROGRAMS install-usbinPROGRAMS installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-binPROGRAMS \
+ uninstall-sbinPROGRAMS uninstall-ubinPROGRAMS \
+ uninstall-usbinPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+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
+ set -e; for i in $(suidbins); do \
+ chmod $(suidperms) $(DESTDIR)$(bindir)/$$i; \
+ done
+ set -e; for i in $(suidubins); do \
+ chmod $(suidperms) $(DESTDIR)$(ubindir)/$$i; \
+ done
+ set -e; for i in $(suidusbins); do \
+ chmod $(suidperms) $(DESTDIR)$(usbindir)/$$i; \
+ done
+@WITH_TCB_TRUE@ set -e; for i in $(shadowsgidubins); do \
+@WITH_TCB_TRUE@ chown root:shadow $(DESTDIR)$(ubindir)/$$i; \
+@WITH_TCB_TRUE@ chmod $(sgidperms) $(DESTDIR)$(ubindir)/$$i; \
+@WITH_TCB_TRUE@ done
+@ENABLE_SUBIDS_TRUE@@FCAPS_TRUE@ setcap cap_setuid+ep $(DESTDIR)$(ubindir)/newuidmap
+@ENABLE_SUBIDS_TRUE@@FCAPS_TRUE@ setcap cap_setgid+ep $(DESTDIR)$(ubindir)/newgidmap
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/chage.c b/src/chage.c
new file mode 100644
index 0000000..01570d7
--- /dev/null
+++ b/src/chage.c
@@ -0,0 +1,904 @@
+/*
+ * SPDX-FileCopyrightText: 1989 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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>
+#include "prototypes.h"
+#include "defines.h"
+#include "pwio.h"
+#include "shadowio.h"
+#include "shadowlog.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 iso8601 date formatting */
+ 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 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, --iso8601 use YYYY-MM-DD when printing dates\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 maximum 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);
+}
+
+/*
+ * 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 || lstchgdate > LONG_MAX / SCALE) {
+ strcpy (buf, "-1");
+ } else {
+ date_to_str (sizeof(buf), buf, 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 || LONG_MAX / SCALE < expdate) {
+ strcpy (buf, "-1");
+ } else {
+ date_to_str (sizeof(buf), buf, 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)
+{
+ 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, iflg ? "%Y-%m-%d" : "%b %d, %Y", tp);
+ (void) puts (buf);
+ }
+}
+
+/*
+ * 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 || lstchgdate > LONG_MAX / SCALE) {
+ (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)
+ || ((LONG_MAX - changed) / SCALE < maxdays)) {
+ (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)
+ || (maxdays > LONG_MAX - inactdays)
+ || ((LONG_MAX - changed) / SCALE < maxdays + inactdays)) {
+ (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 || LONG_MAX / SCALE < expdate) {
+ (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 changed, 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'},
+ {"iso8601", no_argument, NULL, 'i'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "d:E:hiI: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;
+ 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 maximum 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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ 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) {
+ amroot = (check_selinux_permit ("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/check_subid_range.c b/src/check_subid_range.c
new file mode 100644
index 0000000..38703b6
--- /dev/null
+++ b/src/check_subid_range.c
@@ -0,0 +1,51 @@
+// This program is for testing purposes only.
+// usage is "[program] owner [u|g] start count
+// Exits 0 if owner has subid range starting start, of size count
+// Exits 1 otherwise.
+
+#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"
+#include "shadowlog.h"
+
+const char *Prog;
+
+int main(int argc, char **argv)
+{
+ char *owner;
+ unsigned long start, count;
+ bool check_uids;
+ Prog = Basename (argv[0]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ if (argc != 5)
+ exit(1);
+
+ owner = argv[1];
+ check_uids = argv[2][0] == 'u';
+ start = strtoul(argv[3], NULL, 10);
+ if (start == ULONG_MAX && errno == ERANGE)
+ exit(1);
+ count = strtoul(argv[4], NULL, 10);
+ if (count == ULONG_MAX && errno == ERANGE)
+ exit(1);
+ if (check_uids) {
+ if (have_sub_uids(owner, start, count))
+ exit(0);
+ exit(1);
+ }
+ if (have_sub_gids(owner, start, count))
+ exit(0);
+ exit(1);
+}
diff --git a/src/chfn.c b/src/chfn.c
new file mode 100644
index 0000000..1c2f1cc
--- /dev/null
+++ b/src/chfn.c
@@ -0,0 +1,735 @@
+/*
+ * SPDX-FileCopyrightText: 1989 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+#include "defines.h"
+#include "getdef.h"
+#include "nscd.h"
+#include "sssd.h"
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif
+#include "prototypes.h"
+#include "pwauth.h"
+#include "pwio.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+#include "shadowlog.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 + 1 + 80];
+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;
+ if (strlen (optarg) > (unsigned int) 80) {
+ fprintf (stderr,
+ _("%s: fields too long\n"), Prog);
+ exit (E_NOPERM);
+ }
+ 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 ())
+ && (check_selinux_permit ("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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ 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");
+ sssd_flush_cache (SSSD_DB_PASSWD);
+
+ closelog ();
+ exit (E_SUCCESS);
+}
+
diff --git a/src/chgpasswd.c b/src/chgpasswd.c
new file mode 100644
index 0000000..d17acb6
--- /dev/null
+++ b/src/chgpasswd.c
@@ -0,0 +1,623 @@
+/*
+ * SPDX-FileCopyrightText: 1990 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 2006 , Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2006 , Jonas Meurer
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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 "sssd.h"
+#include "prototypes.h"
+#include "groupio.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+/*@-exitarg@*/
+#include "exitcodes.h"
+#include "shadowlog.h"
+
+/*
+ * Global variables
+ */
+const char *Prog;
+static bool eflg = false;
+static bool md5flg = false;
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+static bool sflg = false;
+#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
+
+static /*@null@*//*@observer@*/const char *crypt_method = NULL;
+#define cflg (NULL != crypt_method)
+#ifdef USE_SHA_CRYPT
+static long sha_rounds = 5000;
+#endif
+#ifdef USE_BCRYPT
+static long bcrypt_rounds = 13;
+#endif
+#ifdef USE_YESCRYPT
+static long yescrypt_cost = 5;
+#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"),
+ "NONE DES MD5"
+#if defined(USE_SHA_CRYPT)
+ " SHA256 SHA512"
+#endif
+#if defined(USE_BCRYPT)
+ " BCRYPT"
+#endif
+#if defined(USE_YESCRYPT)
+ " YESCRYPT"
+#endif
+ );
+ (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);
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ (void) fputs (_(" -s, --sha-rounds number of rounds for the SHA, BCRYPT\n"
+ " or YESCRYPT crypt algorithms\n"),
+ usageout);
+#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
+ (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;
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ int bad_s;
+#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
+ 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'},
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ {"sha-rounds", required_argument, NULL, 's'},
+#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
+ {NULL, 0, NULL, '\0'}
+ };
+ while ((c = getopt_long (argc, argv,
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ "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;
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ case 's':
+ sflg = true;
+ bad_s = 0;
+#if defined(USE_SHA_CRYPT)
+ if ( ( ((0 == strcmp (crypt_method, "SHA256")) || (0 == strcmp (crypt_method, "SHA512")))
+ && (0 == getlong(optarg, &sha_rounds)))) {
+ bad_s = 1;
+ }
+#endif /* USE_SHA_CRYPT */
+#if defined(USE_BCRYPT)
+ if (( (0 == strcmp (crypt_method, "BCRYPT"))
+ && (0 == getlong(optarg, &bcrypt_rounds)))) {
+ bad_s = 1;
+ }
+#endif /* USE_BCRYPT */
+#if defined(USE_YESCRYPT)
+ if (( (0 == strcmp (crypt_method, "YESCRYPT"))
+ && (0 == getlong(optarg, &yescrypt_cost)))) {
+ bad_s = 1;
+ }
+#endif /* USE_YESCRYPT */
+ if (bad_s != 0) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ usage (E_USAGE);
+ }
+ break;
+#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
+
+ 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)
+{
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ 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 */
+#ifdef USE_BCRYPT
+ && (0 != strcmp (crypt_method, "BCRYPT"))
+#endif /* USE_BCRYPT */
+#ifdef USE_YESCRYPT
+ && (0 != strcmp (crypt_method, "YESCRYPT"))
+#endif /* USE_YESCRYPT */
+ ) {
+ 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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ 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";
+ }
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ if (sflg) {
+#if defined(USE_SHA_CRYPT)
+ if ( (0 == strcmp (crypt_method, "SHA256"))
+ || (0 == strcmp (crypt_method, "SHA512"))) {
+ arg = &sha_rounds;
+ }
+#endif /* USE_SHA_CRYPT */
+#if defined(USE_BCRYPT)
+ if (0 == strcmp (crypt_method, "BCRYPT")) {
+ arg = &bcrypt_rounds;
+ }
+#endif /* USE_BCRYPT */
+#if defined(USE_YESCRYPT)
+ if (0 == strcmp (crypt_method, "YESCRYPT")) {
+ arg = &yescrypt_cost;
+ }
+#endif /* USE_YESCRYPT */
+ }
+#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");
+ sssd_flush_cache (SSSD_DB_GROUP);
+
+ return (0);
+}
+
diff --git a/src/chpasswd.c b/src/chpasswd.c
new file mode 100644
index 0000000..48d5178
--- /dev/null
+++ b/src/chpasswd.c
@@ -0,0 +1,685 @@
+/*
+ * SPDX-FileCopyrightText: 1990 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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 "sssd.h"
+#include "getdef.h"
+#include "prototypes.h"
+#include "pwio.h"
+#include "shadowio.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+#include "shadowlog.h"
+
+#define IS_CRYPT_METHOD(str) ((crypt_method != NULL && strcmp(crypt_method, str) == 0) ? true : false)
+
+/*
+ * Global variables
+ */
+const char *Prog;
+static bool eflg = false;
+static bool md5flg = false;
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+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 USE_BCRYPT
+static long bcrypt_rounds = 13;
+#endif
+#ifdef USE_YESCRYPT
+static long yescrypt_cost = 5;
+#endif
+
+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"),
+ "NONE DES MD5"
+#if defined(USE_SHA_CRYPT)
+ " SHA256 SHA512"
+#endif
+#if defined(USE_BCRYPT)
+ " BCRYPT"
+#endif
+#if defined(USE_YESCRYPT)
+ " YESCRYPT"
+#endif
+ );
+ (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);
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ (void) fputs (_(" -s, --sha-rounds number of rounds for the SHA, BCRYPT\n"
+ " or YESCRYPT crypt algorithms\n"),
+ usageout);
+#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
+ (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;
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ int bad_s;
+#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
+ 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'},
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ {"sha-rounds", required_argument, NULL, 's'},
+#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv,
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ "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;
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ case 's':
+ sflg = true;
+ bad_s = 0;
+#if defined(USE_SHA_CRYPT)
+ if ((IS_CRYPT_METHOD("SHA256") || IS_CRYPT_METHOD("SHA512"))
+ && (0 == getlong(optarg, &sha_rounds))) {
+ bad_s = 1;
+ }
+#endif /* USE_SHA_CRYPT */
+#if defined(USE_BCRYPT)
+ if (IS_CRYPT_METHOD("BCRYPT")
+ && (0 == getlong(optarg, &bcrypt_rounds))) {
+ bad_s = 1;
+ }
+#endif /* USE_BCRYPT */
+#if defined(USE_YESCRYPT)
+ if (IS_CRYPT_METHOD("YESCRYPT")
+ && (0 == getlong(optarg, &yescrypt_cost))) {
+ bad_s = 1;
+ }
+#endif /* USE_YESCRYPT */
+ if (bad_s != 0) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ usage (E_USAGE);
+ }
+ break;
+#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
+
+ 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)
+{
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ 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 ((!IS_CRYPT_METHOD("DES"))
+ &&(!IS_CRYPT_METHOD("MD5"))
+ &&(!IS_CRYPT_METHOD("NONE"))
+#ifdef USE_SHA_CRYPT
+ &&(!IS_CRYPT_METHOD("SHA256"))
+ &&(!IS_CRYPT_METHOD("SHA512"))
+#endif /* USE_SHA_CRYPT */
+#ifdef USE_BCRYPT
+ &&(!IS_CRYPT_METHOD("BCRYPT"))
+#endif /* USE_BCRYPT */
+#ifdef USE_YESCRYPT
+ &&(!IS_CRYPT_METHOD("YESCRYPT"))
+#endif /* USE_YESCRYPT */
+ ) {
+ 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;
+}
+
+static const char *get_salt(void)
+{
+ void *arg = NULL;
+
+ if (eflg || IS_CRYPT_METHOD("NONE")) {
+ return NULL;
+ }
+
+ if (md5flg) {
+ crypt_method = "MD5";
+ }
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ if (sflg) {
+#if defined(USE_SHA_CRYPT)
+ if (IS_CRYPT_METHOD("SHA256") || IS_CRYPT_METHOD("SHA512")) {
+ arg = &sha_rounds;
+ }
+#endif /* USE_SHA_CRYPT */
+#if defined(USE_BCRYPT)
+ if (IS_CRYPT_METHOD("BCRYPT")) {
+ arg = &bcrypt_rounds;
+ }
+#endif /* USE_BCRYPT */
+#if defined(USE_YESCRYPT)
+ if (IS_CRYPT_METHOD("YESCRYPT")) {
+ arg = &yescrypt_cost;
+ }
+#endif /* USE_YESCRYPT */
+ }
+#endif
+ return crypt_make_salt (crypt_method, arg);
+}
+
+int main (int argc, char **argv)
+{
+ char buf[BUFSIZ];
+ char *name;
+ char *newpwd;
+ char *cp;
+ const char *salt;
+
+#ifdef USE_PAM
+ bool use_pam = true;
+#endif /* USE_PAM */
+
+ int errors = 0;
+ int line = 0;
+
+ Prog = Basename (argv[0]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_flags (argc, argv);
+
+ salt = get_salt();
+ process_root_flag ("-R", 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) {
+
+ // Drop all remaining characters on this line.
+ while (fgets (buf, (int) sizeof buf, stdin) != (char *) 0) {
+ cp = strchr (buf, '\n');
+ if (cp != NULL) {
+ break;
+ }
+ }
+
+ 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_interactive ("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 (salt) {
+ 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");
+ sssd_flush_cache (SSSD_DB_PASSWD);
+
+ return (0);
+}
+
diff --git a/src/chsh.c b/src/chsh.c
new file mode 100644
index 0000000..21d1c3e
--- /dev/null
+++ b/src/chsh.c
@@ -0,0 +1,541 @@
+/*
+ * SPDX-FileCopyrightText: 1989 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include "defines.h"
+#include "getdef.h"
+#include "nscd.h"
+#include "sssd.h"
+#include "prototypes.h"
+#include "pwauth.h"
+#include "pwio.h"
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif
+/*@-exitarg@*/
+#include "exitcodes.h"
+#include "shadowlog.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 ())
+ && (check_selinux_permit("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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ /*
+ * 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");
+ sssd_flush_cache (SSSD_DB_PASSWD);
+
+ closelog ();
+ exit (E_SUCCESS);
+}
+
diff --git a/src/expiry.c b/src/expiry.c
new file mode 100644
index 0000000..dc20b90
--- /dev/null
+++ b/src/expiry.c
@@ -0,0 +1,190 @@
+/*
+ * SPDX-FileCopyrightText: 1994 , Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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"
+#include "shadowlog.h"
+
+/* Global variables */
+const char *Prog;
+static bool cflg = false;
+
+/* local function prototypes */
+static void catch_signals (unused int sig);
+static /*@noreturn@*/void usage (int status);
+static void process_flags (int argc, char **argv);
+
+/*
+ * catch_signals - signal catcher
+ */
+static void 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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ 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..0f94836
--- /dev/null
+++ b/src/faillog.c
@@ -0,0 +1,720 @@
+/*
+ * SPDX-FileCopyrightText: 1989 - 1993, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2002 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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"
+#include "shadowlog.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;
+ char *cp;
+ char ptime[80];
+
+ 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);
+ if (!tm) {
+ fprintf (stderr, "Cannot read time from faillog.\n");
+ return;
+ }
+ strftime (ptime, sizeof (ptime), "%D %H:%M:%S %z", tm);
+ cp = ptime;
+
+ printf ("%-9s %5d %5d ",
+ pw->pw_name, fl.fail_cnt, fl.fail_max);
+ 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 = 0;
+ short fail_max = 0; // initialize to silence compiler warning
+ long days = 0;
+
+ /*
+ * Get the program name. The program name is used as a prefix to
+ * most error messages.
+ */
+ Prog = Basename (argv[0]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ {
+ 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/free_subid_range.c b/src/free_subid_range.c
new file mode 100644
index 0000000..d9a2cd8
--- /dev/null
+++ b/src/free_subid_range.c
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+
+#include <stdio.h>
+#include <unistd.h>
+#include "subid.h"
+#include "stdlib.h"
+#include "prototypes.h"
+#include "shadowlog.h"
+
+/* Test program for the subid freeing routine */
+
+const char *Prog;
+
+static void usage(void)
+{
+ fprintf(stderr, "Usage: %s [-g] user start count\n", Prog);
+ fprintf(stderr, " Release a user's subuid (or with -g, subgid) range\n");
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ bool ok;
+ struct subordinate_range range;
+ bool group = false; // get subuids by default
+
+ Prog = Basename (argv[0]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+ while ((c = getopt(argc, argv, "g")) != EOF) {
+ switch(c) {
+ case 'g': group = true; break;
+ default: usage();
+ }
+ }
+ argv = &argv[optind];
+ argc = argc - optind;
+ if (argc < 3)
+ usage();
+ range.owner = argv[0];
+ range.start = atoi(argv[1]);
+ range.count = atoi(argv[2]);
+ if (group)
+ ok = subid_ungrant_gid_range(&range);
+ else
+ ok = subid_ungrant_uid_range(&range);
+
+ if (!ok) {
+ fprintf(stderr, "Failed freeing id range\n");
+ exit(EXIT_FAILURE);
+ }
+
+ return 0;
+}
diff --git a/src/get_subid_owners.c b/src/get_subid_owners.c
new file mode 100644
index 0000000..36974b8
--- /dev/null
+++ b/src/get_subid_owners.c
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+
+#include <stdio.h>
+#include "subid.h"
+#include "stdlib.h"
+#include "prototypes.h"
+#include "shadowlog.h"
+
+const char *Prog;
+
+static void usage(void)
+{
+ fprintf(stderr, "Usage: [-g] %s subuid\n", Prog);
+ fprintf(stderr, " list uids who own the given subuid\n");
+ fprintf(stderr, " pass -g to query a subgid\n");
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char *argv[])
+{
+ int i, n;
+ uid_t *uids;
+
+ Prog = Basename (argv[0]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+ if (argc < 2) {
+ usage();
+ }
+ if (argc == 3 && strcmp(argv[1], "-g") == 0)
+ n = subid_get_gid_owners(atoi(argv[2]), &uids);
+ else if (argc == 2 && strcmp(argv[1], "-h") == 0)
+ usage();
+ else
+ n = subid_get_uid_owners(atoi(argv[1]), &uids);
+ if (n < 0) {
+ fprintf(stderr, "No owners found\n");
+ exit(1);
+ }
+ for (i = 0; i < n; i++) {
+ printf("%d\n", uids[i]);
+ }
+ free(uids);
+ return 0;
+}
diff --git a/src/getsubids.c b/src/getsubids.c
new file mode 100644
index 0000000..c91ae39
--- /dev/null
+++ b/src/getsubids.c
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "subid.h"
+#include "prototypes.h"
+#include "shadowlog.h"
+
+const char *Prog;
+
+static void usage(void)
+{
+ fprintf(stderr, "Usage: %s [-g] user\n", Prog);
+ fprintf(stderr, " list subuid ranges for user\n");
+ fprintf(stderr, " pass -g to list subgid ranges\n");
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char *argv[])
+{
+ int i, count=0;
+ struct subid_range *ranges;
+ const char *owner;
+
+ Prog = Basename (argv[0]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+ if (argc < 2)
+ usage();
+ owner = argv[1];
+ if (argc == 3 && strcmp(argv[1], "-g") == 0) {
+ owner = argv[2];
+ count = subid_get_gid_ranges(owner, &ranges);
+ } else if (argc == 2 && strcmp(argv[1], "-h") == 0) {
+ usage();
+ } else {
+ count = subid_get_uid_ranges(owner, &ranges);
+ }
+ if (!ranges) {
+ fprintf(stderr, "Error fetching ranges\n");
+ exit(1);
+ }
+ for (i = 0; i < count; i++) {
+ printf("%d: %s %lu %lu\n", i, owner,
+ ranges[i].start, ranges[i].count);
+ }
+ return 0;
+}
diff --git a/src/gpasswd.c b/src/gpasswd.c
new file mode 100644
index 0000000..5983f78
--- /dev/null
+++ b/src/gpasswd.c
@@ -0,0 +1,1196 @@
+/*
+ * SPDX-FileCopyrightText: 1990 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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 "sssd.h"
+#include "prototypes.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+#include "shadowlog.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 void 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 void 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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ 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");
+ sssd_flush_cache (SSSD_DB_GROUP);
+
+#ifdef SHADOWGRP
+ if (is_shadowgrp) {
+ free(sgent.sg_adm);
+ free(sgent.sg_mem);
+ }
+#endif
+ free(grent.gr_mem);
+ exit (E_SUCCESS);
+}
+
diff --git a/src/groupadd.c b/src/groupadd.c
new file mode 100644
index 0000000..66ccb53
--- /dev/null
+++ b/src/groupadd.c
@@ -0,0 +1,632 @@
+/*
+ * SPDX-FileCopyrightText: 1991 - 1993, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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 "sssd.h"
+#include "prototypes.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+#include "shadowlog.h"
+
+/*
+ * 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 const char *prefix = "";
+static char *user_list;
+
+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 (_(" -P, --prefix PREFIX_DI directory prefix\n"), usageout);
+ (void) fputs (_(" -U, --users USERS list of user members of this group\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 */
+
+ if (user_list) {
+ char *token;
+ token = strtok(user_list, ",");
+ while (token) {
+ if (prefix_getpwnam (token) == NULL) {
+ fprintf (stderr, _("Invalid member username %s\n"), token);
+ exit (E_GRP_UPDATE);
+ }
+ grp.gr_mem = add_list(grp.gr_mem, token);
+ token = strtok(NULL, ",");
+ }
+ }
+
+ /*
+ * 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'},
+ {"prefix", required_argument, NULL, 'P'},
+ {"users", required_argument, NULL, 'U'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "fg:hK:op:rR:P:U:",
+ 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;
+ case 'P': /* no-op, handled in process_prefix_flag () */
+ break;
+ case 'U':
+ user_list = optarg;
+ 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 (prefix_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 && (prefix_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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+ prefix = process_prefix_flag ("-P", 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");
+ sssd_flush_cache (SSSD_DB_GROUP);
+
+ return E_SUCCESS;
+}
+
diff --git a/src/groupdel.c b/src/groupdel.c
new file mode 100644
index 0000000..c84faa7
--- /dev/null
+++ b/src/groupdel.c
@@ -0,0 +1,481 @@
+/*
+ * SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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 "sssd.h"
+#include "prototypes.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+#include "shadowlog.h"
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static char *group_name;
+static gid_t group_id = -1;
+static bool check_group_busy = true;
+
+static const char* prefix = "";
+
+#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 (_(" -P, --prefix PREFIX_DIR prefix directory where are located the /etc/* files\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.
+ */
+
+ prefix_setpwent ();
+
+ while ( ((pwd = prefix_getpwent ()) != NULL) && (pwd->pw_gid != gid) );
+
+ prefix_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'},
+ {"force", no_argument, NULL, 'f'},
+ {"root", required_argument, NULL, 'R'},
+ {"prefix", required_argument, NULL, 'P'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv, "hfR:P:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ usage (E_SUCCESS);
+ /*@notreached@*/break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ case 'P': /* no-op, handled in process_prefix_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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+ prefix = process_prefix_flag ("-P", 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 = prefix_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");
+ sssd_flush_cache (SSSD_DB_GROUP);
+
+ return E_SUCCESS;
+}
+
diff --git a/src/groupmems.c b/src/groupmems.c
new file mode 100644
index 0000000..a0e3266
--- /dev/null
+++ b/src/groupmems.c
@@ -0,0 +1,632 @@
+/*
+ * SPDX-FileCopyrightText: 2000 , International Business Machines
+ * SPDX-FileCopyrightText: 2000 , George Kraft IV, gk4@us.ibm.com, 03/23/2000
+ * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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
+#include "shadowlog.h"
+
+/* 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 - Remove 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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ 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..006eca1
--- /dev/null
+++ b/src/groupmod.c
@@ -0,0 +1,905 @@
+/*
+ * SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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 "sssd.h"
+#include "prototypes.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+#include "shadowlog.h"
+/*
+ * 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 */
+#define E_CLEANUP_SERVICE 11 /* can't setup cleanup service */
+#define E_PAM_USERNAME 12 /* can't determine your username for use with pam */
+#define E_PAM_ERROR 13 /* pam returned an error, see Syslog facility id groupmod */
+
+
+/*
+ * 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 const char* prefix = "";
+static char *user_list;
+
+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
+ aflg = false, /* append -U members rather than replace them */
+ 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 (_(" -a, --append append the users mentioned by -U option to the group \n"
+ " without removing existing user members\n"), usageout);
+ (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 (_(" -P, --prefix PREFIX_DIR prefix directory where are located the /etc/* files\n"), usageout);
+ (void) fputs (_(" -U, --users USERS list of user members of this group\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);
+ }
+
+ if (user_list) {
+ char *token;
+
+ if (!aflg) {
+ // requested to replace the existing groups
+ if (NULL != grp.gr_mem[0])
+ gr_free_members(&grp);
+ grp.gr_mem = (char **)xmalloc(sizeof(char *));
+ grp.gr_mem[0] = (char *)0;
+ } else {
+ // append to existing groups
+ if (NULL != grp.gr_mem[0])
+ grp.gr_mem = dup_list (grp.gr_mem);
+ }
+
+ token = strtok(user_list, ",");
+ while (token) {
+ if (prefix_getpwnam (token) == NULL) {
+ fprintf (stderr, _("Invalid member username %s\n"), token);
+ exit (E_GRP_UPDATE);
+ }
+ grp.gr_mem = add_list(grp.gr_mem, token);
+ token = strtok(NULL, ",");
+ }
+ }
+
+ /*
+ * 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 (prefix_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[] = {
+ {"append", no_argument, NULL, 'a'},
+ {"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'},
+ {"prefix", required_argument, NULL, 'P'},
+ {"users", required_argument, NULL, 'U'},
+ {NULL, 0, NULL, '\0'}
+ };
+ while ((c = getopt_long (argc, argv, "ag:hn:op:R:P:U:",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'a':
+ aflg = true;
+ break;
+ 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;
+ case 'P': /* no-op, handled in process_prefix_flag () */
+ break;
+ case 'U':
+ user_list = optarg;
+ 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;
+
+ prefix_setpwent ();
+ while ((pwd = prefix_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);
+ }
+ }
+ }
+ }
+ prefix_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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+ prefix = process_prefix_flag ("-P", 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 (E_CLEANUP_SERVICE);
+ }
+
+ 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 (E_PAM_USERNAME);
+ }
+
+ 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 (E_PAM_ERROR);
+ }
+ (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 = prefix_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");
+ sssd_flush_cache (SSSD_DB_GROUP);
+
+ return E_SUCCESS;
+}
+
diff --git a/src/groups.c b/src/groups.c
new file mode 100644
index 0000000..12bd224
--- /dev/null
+++ b/src/groups.c
@@ -0,0 +1,179 @@
+/*
+ * SPDX-FileCopyrightText: 1991 - 1993, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2008, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include "defines.h"
+#include "prototypes.h"
+#include "shadowlog.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)
+{
+ long sys_ngroups;
+ GETGROUPS_T *groups;
+
+ sys_ngroups = sysconf (_SC_NGROUPS_MAX);
+ groups = (GETGROUPS_T *) malloc (sizeof (GETGROUPS_T) * sys_ngroups);
+
+ (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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ if (argc == 1) {
+
+ /*
+ * Called with no arguments - give the group set for the
+ * current user.
+ */
+
+ 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 {
+
+ /*
+ * 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..881fb4d
--- /dev/null
+++ b/src/grpck.c
@@ -0,0 +1,875 @@
+/*
+ * SPDX-FileCopyrightText: 1992 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2001 , Michał Moskal
+ * SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <config.h>
+
+#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 "sssd.h"
+#include "prototypes.h"
+#include "shadowlog.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;
+static bool silence_warnings = 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 (_(" -S, --silence-warnings silence controversial/paranoid warnings\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'},
+ {"silence-warnings", no_argument, NULL, 'S'},
+ {"sort", no_argument, NULL, 's'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+ /*
+ * Parse the command line arguments
+ */
+ while ((c = getopt_long (argc, argv, "hqrR:sS",
+ 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;
+ case 'S':
+ silence_warnings = 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 (!silence_warnings && *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
+ const 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 = 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)
+{
+ const 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 = 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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ 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);
+
+ if (!read_only) {
+ nscd_flush_cache ("group");
+ sssd_flush_cache (SSSD_DB_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..57d8d58
--- /dev/null
+++ b/src/grpconv.c
@@ -0,0 +1,269 @@
+/*
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2002 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2011 , Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+/*
+ * 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 "sssd.h"
+#include "prototypes.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+#ifdef SHADOWGRP
+#include "groupio.h"
+#include "sgroupio.h"
+#include "shadowlog.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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ 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);
+ }
+ (void) sgr_rewind ();
+ }
+
+ /*
+ * 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");
+ sssd_flush_cache (SSSD_DB_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..fc6cecf
--- /dev/null
+++ b/src/grpunconv.c
@@ -0,0 +1,232 @@
+/*
+ * SPDX-FileCopyrightText: 1996 , Michael Meskes
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2002 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2008 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+/*
+ * 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 "sssd.h"
+#include "prototypes.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+#ifdef SHADOWGRP
+#include "groupio.h"
+#include "sgroupio.h"
+#include "shadowlog.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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ 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");
+ sssd_flush_cache (SSSD_DB_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..4952109
--- /dev/null
+++ b/src/id.c
@@ -0,0 +1,172 @@
+/*
+ * SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2008, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+/*
+ * 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)
+{
+ (void) fputs (_("Usage: id [-a]\n"), stderr);
+ 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.
+ */
+ GETGROUPS_T *groups;
+ int ngroups;
+ bool aflg = 0;
+ 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);
+ 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;
+ }
+ }
+
+ 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);
+ }
+ }
+
+ /*
+ * 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);
+
+ /*
+ * 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..f5c0a5c
--- /dev/null
+++ b/src/lastlog.c
@@ -0,0 +1,440 @@
+/*
+ * SPDX-FileCopyrightText: 1989 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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>
+#ifdef HAVE_LL_HOST
+#include <net/if.h>
+#endif
+#include "defines.h"
+#include "prototypes.h"
+#include "getdef.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+#include "shadowlog.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;
+ char ptime[80];
+
+#ifdef HAVE_LL_HOST
+ /*
+ * ll_host is in minimized form, thus the maximum IPv6 address possible is
+ * 8*4+7 = 39 characters.
+ * RFC 4291 2.5.6 states that for LL-addresses fe80+only the interface ID is set,
+ * thus having a maximum size of 25+1+IFNAMSIZ.
+ * POSIX says IFNAMSIZ should be 16 characters long including the null byte, thus
+ * 25+1+IFNAMSIZ >= 42 > 39
+ */
+ /* Link-Local address + % + Interfacename */
+ const int maxIPv6Addrlen = 25+1+IFNAMSIZ;
+#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
+ printf (_("Username Port From%*sLatest\n"), maxIPv6Addrlen-3, " ");
+#else
+ puts (_("Username Port Latest"));
+#endif
+ once = true;
+ }
+
+ ll_time = ll.ll_time;
+ tm = localtime (&ll_time);
+ if (tm == NULL) {
+ cp = "(unknown)";
+ } else {
+ strftime (ptime, sizeof (ptime), "%a %b %e %H:%M:%S %z %Y", tm);
+ cp = ptime;
+ }
+ if (ll.ll_time == (time_t) 0) {
+ cp = _("**Never logged in**\0");
+ }
+
+#ifdef HAVE_LL_HOST
+ printf ("%-16s %-8.8s %*s%s\n",
+ pw->pw_name, ll.ll_line, -maxIPv6Addrlen, 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;
+ unsigned long lastlog_uid_max;
+
+ lastlog_uid_max = getdef_ulong ("LASTLOG_UID_MAX", 0xFFFFFFFFUL);
+ if ( (has_umin && umin > lastlog_uid_max)
+ || (has_umax && umax > lastlog_uid_max)) {
+ fprintf (stderr, _("%s: Selected uid(s) are higher than LASTLOG_UID_MAX (%lu),\n"
+ "\tthe output might be incorrect.\n"), Prog, lastlog_uid_max);
+ }
+
+ 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;
+ } else if ( !uflg && pwent->pw_uid > (uid_t) lastlog_uid_max) {
+ 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;
+ unsigned long lastlog_uid_max;
+
+ if (!uflg) /* safety measure */
+ return;
+
+ lastlog_uid_max = getdef_ulong ("LASTLOG_UID_MAX", 0xFFFFFFFFUL);
+ if ( (has_umin && umin > lastlog_uid_max)
+ || (has_umax && umax > lastlog_uid_max)) {
+ fprintf (stderr, _("%s: Selected uid(s) are higher than LASTLOG_UID_MAX (%lu),\n"
+ "\tthey will not be updated.\n"), Prog, lastlog_uid_max);
+ 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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+#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..0048281
--- /dev/null
+++ b/src/login.c
@@ -0,0 +1,1342 @@
+/*
+ * SPDX-FileCopyrightText: 1989 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2001, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2012, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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"
+#include "shadowlog.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,
+#ifdef USE_UTMPX
+ /*@null@*/const struct utmpx *utent
+#else
+ /*@null@*/const struct utmp *utent
+#endif
+ );
+
+#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 void 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)
+{
+ const 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
+ const 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 void 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;
+
+ 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,
+#ifdef USE_UTMPX
+ /*@null@*/const struct utmpx *utent
+#else
+ /*@null@*/const struct utmp *utent
+#endif
+ )
+{
+#ifdef USE_UTMPX
+ struct utmpx *utx = prepare_utmpx (user, tty, host, utent);
+#else
+ struct utmp *ut = prepare_utmp (user, tty, host, utent);
+#endif /* USE_UTMPX */
+
+#ifndef USE_UTMPX
+ (void) setutmp (ut); /* make entry in the utmp & wtmp files */
+ free (ut);
+#else
+ (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(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;
+#ifdef USE_UTMPX
+ /*@null@*/struct utmpx *utent;
+#else
+ /*@null@*/struct utmp *utent;
+#endif
+
+#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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ 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;
+#if defined(HAVE_STRUCT_UTMP_UT_HOST) || defined(USE_UTMPX)
+ } 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);
+ 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, "") == 0) {
+ char *prevent_no_auth = getdef_str("PREVENT_NO_AUTH");
+ if (prevent_no_auth == NULL) {
+ prevent_no_auth = "superuser";
+ }
+ if (strcmp(prevent_no_auth, "yes") == 0) {
+ failed = true;
+ } else if ((pwd->pw_uid == 0)
+ && (strcmp(prevent_no_auth, "superuser") == 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")
+ && pwd->pw_uid <= (uid_t) getdef_ulong ("LASTLOG_UID_MAX", 0xFFFFFFFFUL)) {
+ /* 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
+ * getxxyyy 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++;
+ }
+ }
+ (void) pam_end (pamh, PAM_SUCCESS | PAM_DATA_SILENT);
+#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")
+ && pwd->pw_uid <= (uid_t) getdef_ulong ("LASTLOG_UID_MAX", 0xFFFFFFFFUL)
+ && (ll.ll_time != 0)) {
+ time_t ll_time = ll.ll_time;
+
+ (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);
+#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..df6ba88
--- /dev/null
+++ b/src/login_nopam.c
@@ -0,0 +1,341 @@
+/* Taken from logdaemon-5.0, only minimal changes. --marekm */
+/*
+ * SPDX-FileCopyrightText: 1990 - 1995, Wietse Venema.
+ *
+ * SPDX-License-Identifier: Unlicense
+ */
+
+/************************************************************************
+* 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..03680f3
--- /dev/null
+++ b/src/logoutd.c
@@ -0,0 +1,279 @@
+/*
+ * SPDX-FileCopyrightText: 1991 - 1993, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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"
+#include "shadowlog.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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ 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';
+ }
+
+ strncat (tty_name, ut->ut_line, UT_LINESIZE);
+#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/new_subid_range.c b/src/new_subid_range.c
new file mode 100644
index 0000000..523d480
--- /dev/null
+++ b/src/new_subid_range.c
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+
+#include <stdio.h>
+#include <unistd.h>
+#include "subid.h"
+#include "stdlib.h"
+#include "prototypes.h"
+#include "shadowlog.h"
+
+/* Test program for the subid creation routine */
+
+const char *Prog;
+
+static void usage(void)
+{
+ fprintf(stderr, "Usage: %s [-g] [-n] user count\n", Prog);
+ fprintf(stderr, " Find a subuid (or with -g, subgid) range for user\n");
+ fprintf(stderr, " If -n is given, a new range will be created even if one exists\n");
+ fprintf(stderr, " count defaults to 65536\n");
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ struct subordinate_range range;
+ bool makenew = false; // reuse existing by default
+ bool group = false; // get subuids by default
+ bool ok;
+
+ Prog = Basename (argv[0]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+ while ((c = getopt(argc, argv, "gn")) != EOF) {
+ switch(c) {
+ case 'n': makenew = true; break;
+ case 'g': group = true; break;
+ default: usage();
+ }
+ }
+ argv = &argv[optind];
+ argc = argc - optind;
+ if (argc == 0)
+ usage();
+ range.owner = argv[0];
+ range.start = 0;
+ range.count = 65536;
+ if (argc > 1)
+ range.count = atoi(argv[1]);
+ if (group)
+ ok = subid_grant_gid_range(&range, !makenew);
+ else
+ ok = subid_grant_uid_range(&range, !makenew);
+
+ if (!ok) {
+ fprintf(stderr, "Failed creating new id range\n");
+ exit(EXIT_FAILURE);
+ }
+ printf("Subuid range %lu:%lu\n", range.start, range.count);
+
+ return 0;
+}
diff --git a/src/newgidmap.c b/src/newgidmap.c
new file mode 100644
index 0000000..5b42431
--- /dev/null
+++ b/src/newgidmap.c
@@ -0,0 +1,239 @@
+/*
+ * SPDX-FileCopyrightText: 2013 Eric Biederman
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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 "getdef.h"
+#include "idmapping.h"
+#include "shadowlog.h"
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+
+static bool verify_range(struct passwd *pw, struct map_range *range, bool *allow_setgroups)
+{
+ /* An empty range is invalid */
+ if (range->count == 0)
+ return false;
+
+ /* Test /etc/subgid. If the mapping is valid then we allow setgroups. */
+ if (have_sub_gids(pw->pw_name, range->lower, range->count)) {
+ *allow_setgroups = true;
+ return true;
+ }
+
+ /* Allow a process to map its own gid. */
+ if ((range->count == 1) && (getgid() == range->lower)) {
+ /* noop -- if setgroups is enabled already we won't disable it. */
+ return true;
+ }
+
+ return false;
+}
+
+static void verify_ranges(struct passwd *pw, int ranges,
+ struct map_range *mappings, bool *allow_setgroups)
+{
+ struct map_range *mapping;
+ int idx;
+
+ mapping = mappings;
+ for (idx = 0; idx < ranges; idx++, mapping++) {
+ if (!verify_range(pw, mapping, allow_setgroups)) {
+ 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);
+}
+
+static void write_setgroups(int proc_dir_fd, bool allow_setgroups)
+{
+ int setgroups_fd;
+ const char *policy;
+ char policy_buffer[4096];
+
+ /*
+ * Default is "deny", and any "allow" will out-rank a "deny". We don't
+ * forcefully write an "allow" here because the process we are writing
+ * mappings for may have already set themselves to "deny" (and "allow"
+ * is the default anyway). So allow_setgroups == true is a noop.
+ */
+ policy = "deny\n";
+ if (allow_setgroups)
+ return;
+
+ setgroups_fd = openat(proc_dir_fd, "setgroups", O_RDWR|O_CLOEXEC);
+ if (setgroups_fd < 0) {
+ /*
+ * If it's an ENOENT then we are on too old a kernel for the setgroups
+ * code to exist. Emit a warning and bail on this.
+ */
+ if (ENOENT == errno) {
+ fprintf(stderr, _("%s: kernel doesn't support setgroups restrictions\n"), Prog);
+ goto out;
+ }
+ fprintf(stderr, _("%s: couldn't open process setgroups: %s\n"),
+ Prog,
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * Check whether the policy is already what we want. /proc/self/setgroups
+ * is write-once, so attempting to write after it's already written to will
+ * fail.
+ */
+ if (read(setgroups_fd, policy_buffer, sizeof(policy_buffer)) < 0) {
+ fprintf(stderr, _("%s: failed to read setgroups: %s\n"),
+ Prog,
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ if (!strncmp(policy_buffer, policy, strlen(policy)))
+ goto out;
+
+ /* Write the policy. */
+ if (lseek(setgroups_fd, 0, SEEK_SET) < 0) {
+ fprintf(stderr, _("%s: failed to seek setgroups: %s\n"),
+ Prog,
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ if (dprintf(setgroups_fd, "%s", policy) < 0) {
+ fprintf(stderr, _("%s: failed to setgroups %s policy: %s\n"),
+ Prog,
+ policy,
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+out:
+ close(setgroups_fd);
+}
+
+/*
+ * 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;
+ int proc_dir_fd;
+ int ranges;
+ struct map_range *mappings;
+ struct stat st;
+ struct passwd *pw;
+ int written;
+ bool allow_setgroups = false;
+
+ Prog = Basename (argv[0]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ /*
+ * 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) ||
+ (!getdef_bool("GRANT_AUX_GROUP_SUBIDS") && (getgid() != pw->pw_gid)) ||
+ (pw->pw_uid != st.st_uid) ||
+ (getgid() != 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, &allow_setgroups);
+
+ write_setgroups(proc_dir_fd, allow_setgroups);
+ write_mapping(proc_dir_fd, ranges, mappings, "gid_map", pw->pw_uid);
+ sub_gid_close();
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/newgrp.c b/src/newgrp.c
new file mode 100644
index 0000000..9982083
--- /dev/null
+++ b/src/newgrp.c
@@ -0,0 +1,859 @@
+/*
+ * SPDX-FileCopyrightText: 1990 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2008, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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"
+#include "shadowlog.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);
+ }
+}
+
+static bool ingroup(const char *name, struct group *gr)
+{
+ char **look;
+ bool notfound = true;
+
+ look = gr->gr_mem;
+ while (*look && notfound)
+ notfound = strcmp (*look++, name);
+
+ return !notfound;
+}
+
+/*
+ * find_matching_group - search all groups of a gr's group id for
+ * membership of a given username
+ * but check gr itself first
+ */
+static /*@null@*/struct group *find_matching_group (const char *name, struct group *gr)
+{
+ gid_t gid = gr->gr_gid;
+
+ if (ingroup(name, gr))
+ return gr;
+
+ setgrent ();
+ while ((gr = getgrent ()) != NULL) {
+ if (gr->gr_gid != gid) {
+ continue;
+ }
+
+ /*
+ * A group with matching GID was found.
+ * Test for membership of 'name'.
+ */
+ if (ingroup(name, gr))
+ 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 = xstrdup (spwd->sp_pwdp);
+ spw_free (spwd);
+ }
+
+ 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 logout 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 is_member = false;
+ bool cflag = false;
+ int err = 0;
+ gid_t gid;
+ char *cp;
+ const char *progbase;
+ 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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+ is_newgrp = (strcmp (Prog, "newgrp") == 0);
+ OPENLOG (is_newgrp ? "newgrp" : "sg");
+ 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;
+ }
+
+#ifdef HAVE_SETGROUPS
+ /* when using pam_group, she will not be listed in the groups
+ * database. However getgroups() will return the group. So
+ * if she is listed there already it is ok to grant membership.
+ */
+ for (i = 0; i < ngroups; i++) {
+ if (grp->gr_gid == grouplist[i]) {
+ is_member = true;
+ break;
+ }
+ }
+#endif /* HAVE_SETGROUPS */
+ /*
+ * 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.
+ */
+ if (!is_member) {
+ grp = find_matching_group (name, grp);
+ 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.
+ */
+ if (!is_member) {
+ 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.
+ */
+ progbase = Basename (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 : progbase, 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..546856a
--- /dev/null
+++ b/src/newuidmap.c
@@ -0,0 +1,167 @@
+/*
+ * SPDX-FileCopyrightText: 2013 Eric Biederman
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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 "getdef.h"
+#include "idmapping.h"
+#include "shadowlog.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);
+ }
+ }
+}
+
+static 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;
+ int proc_dir_fd;
+ int ranges;
+ struct map_range *mappings;
+ struct stat st;
+ struct passwd *pw;
+ int written;
+
+ Prog = Basename (argv[0]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ /*
+ * 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) ||
+ (!getdef_bool("GRANT_AUX_GROUP_SUBIDS") && (getgid() != pw->pw_gid)) ||
+ (pw->pw_uid != st.st_uid) ||
+ (getgid() != 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", pw->pw_uid);
+ sub_uid_close();
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/newusers.c b/src/newusers.c
new file mode 100644
index 0000000..deeb361
--- /dev/null
+++ b/src/newusers.c
@@ -0,0 +1,1344 @@
+/*
+ * SPDX-FileCopyrightText: 1990 - 1993, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+/*
+ * 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 "sssd.h"
+#include "pwio.h"
+#include "sgroupio.h"
+#include "shadowio.h"
+#ifdef ENABLE_SUBIDS
+#include "subordinateio.h"
+#endif /* ENABLE_SUBIDS */
+#include "chkname.h"
+#include "shadowlog.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)
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT)
+static bool sflg = false;
+#endif
+#ifdef USE_SHA_CRYPT
+static long sha_rounds = 5000;
+#endif /* USE_SHA_CRYPT */
+#ifdef USE_BCRYPT
+static long bcrypt_rounds = 13;
+#endif /* USE_BCRYPT */
+#ifdef USE_YESCRYPT
+static long yescrypt_cost = 5;
+#endif /* USE_YESCRYPT */
+#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);
+
+extern int allow_bad_names;
+
+/*
+ * 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);
+ (void) fputs (_(" -b, --badname allow bad names\n"), usageout);
+#ifndef USE_PAM
+ (void) fprintf (usageout,
+ _(" -c, --crypt-method METHOD the crypt method (one of %s)\n"),
+ "NONE DES MD5"
+#if defined(USE_SHA_CRYPT)
+ " SHA256 SHA512"
+#endif
+#if defined(USE_BCRYPT)
+ " BCRYPT"
+#endif
+#if defined(USE_YESCRYPT)
+ " YESCRYPT"
+#endif
+ );
+#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
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ (void) fputs (_(" -s, --sha-rounds number of rounds for the SHA, BCRYPT\n"
+ " or YESCRYPT crypt algorithms\n"),
+ usageout);
+#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
+#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': use --badname to ignore\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 (NULL != crypt_method) {
+#if defined(USE_SHA_CRYPT)
+ if (sflg) {
+ if ( (0 == strcmp (crypt_method, "SHA256"))
+ || (0 == strcmp (crypt_method, "SHA512"))) {
+ crypt_arg = &sha_rounds;
+ }
+ }
+#endif /* USE_SHA_CRYPT */
+#if defined(USE_BCRYPT)
+ if (sflg) {
+ if (0 == strcmp (crypt_method, "BCRYPT")) {
+ crypt_arg = &bcrypt_rounds;
+ }
+ }
+#endif /* USE_BCRYPT */
+#if defined(USE_YESCRYPT)
+ if (sflg) {
+ if (0 == strcmp (crypt_method, "YESCRYPT")) {
+ crypt_arg = &yescrypt_cost;
+ }
+ }
+#endif /* USE_YESCRYPT */
+ }
+
+ if ((NULL != crypt_method) && (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;
+#ifndef USE_PAM
+ char *cp;
+#endif /* !USE_PAM */
+
+#ifndef USE_PAM
+ void *crypt_arg = NULL;
+ if (NULL != crypt_method) {
+#if defined(USE_SHA_CRYPT)
+ if (sflg) {
+ if ( (0 == strcmp (crypt_method, "SHA256"))
+ || (0 == strcmp (crypt_method, "SHA512"))) {
+ crypt_arg = &sha_rounds;
+ }
+ }
+#endif /* USE_SHA_CRYPT */
+#if defined(USE_BCRYPT)
+ if (sflg) {
+ if (0 == strcmp (crypt_method, "BCRYPT")) {
+ crypt_arg = &bcrypt_rounds;
+ }
+ }
+#endif /* USE_BCRYPT */
+#if defined(USE_YESCRYPT)
+ if (sflg) {
+ if (0 == strcmp (crypt_method, "YESCRYPT")) {
+ crypt_arg = &yescrypt_cost;
+ }
+ }
+#endif /* USE_PAM */
+ }
+
+ /*
+ * 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;
+#ifndef USE_PAM
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ int bad_s;
+#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
+#endif /* !USE_PAM */
+ static struct option long_options[] = {
+ {"badname", no_argument, NULL, 'b'},
+#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
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ {"sha-rounds", required_argument, NULL, 's'},
+#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
+#endif /* !USE_PAM */
+ {NULL, 0, NULL, '\0'}
+ };
+
+ while ((c = getopt_long (argc, argv,
+#ifndef USE_PAM
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ "c:bhrs:",
+#else /* !USE_SHA_CRYPT && !USE_BCRYPT && !USE_YESCRYPT */
+ "c:bhr",
+#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
+#else /* USE_PAM */
+ "bhr",
+#endif
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'b':
+ allow_bad_names = true;
+ break;
+#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
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ case 's':
+ sflg = true;
+ bad_s = 0;
+#if defined(USE_SHA_CRYPT)
+ if ( ( ((0 == strcmp (crypt_method, "SHA256")) || (0 == strcmp (crypt_method, "SHA512")))
+ && (0 == getlong(optarg, &sha_rounds)))) {
+ bad_s = 1;
+ }
+#endif /* USE_SHA_CRYPT */
+#if defined(USE_BCRYPT)
+ if (( (0 == strcmp (crypt_method, "BCRYPT"))
+ && (0 == getlong(optarg, &bcrypt_rounds)))) {
+ bad_s = 1;
+ }
+#endif /* USE_BCRYPT */
+#if defined(USE_YESCRYPT)
+ if (( (0 == strcmp (crypt_method, "YESCRYPT"))
+ && (0 == getlong(optarg, &yescrypt_cost)))) {
+ bad_s = 1;
+ }
+#endif /* USE_YESCRYPT */
+ if (bad_s != 0) {
+ fprintf (stderr,
+ _("%s: invalid numeric argument '%s'\n"),
+ Prog, optarg);
+ usage (EXIT_FAILURE);
+ }
+ break;
+#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
+#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
+#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
+ 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 || USE_BCRYPT || USE_YESCRYPT */
+
+ 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 */
+#ifdef USE_BCRYPT
+ && (0 != strcmp (crypt_method, "BCRYPT"))
+#endif /* USE_BCRYPT */
+#ifdef USE_YESCRYPT
+ && (0 != strcmp (crypt_method, "YESCRYPT"))
+#endif /* USE_YESCRYPT */
+ ) {
+ 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 */
+}
+
+static bool want_subuids(void)
+{
+ if (get_subid_nss_handle() != NULL)
+ return false;
+ if (getdef_ulong ("SUB_UID_COUNT", 65536) == 0)
+ return false;
+ return true;
+}
+
+static bool want_subgids(void)
+{
+ if (get_subid_nss_handle() != NULL)
+ return false;
+ if (getdef_ulong ("SUB_GID_COUNT", 65536) == 0)
+ return false;
+ return true;
+}
+
+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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (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 mode = getdef_num ("HOME_MODE",
+ 0777 & ~getdef_num ("UMASK", GETDEF_DEFAULT_UMASK));
+ if (newpw.pw_dir[0] != '/') {
+ fprintf(stderr,
+ _("%s: line %d: homedir must be an absolute path\n"),
+ Prog, line);
+ errors++;
+ continue;
+ };
+ if (mkdir (newpw.pw_dir, mode) != 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 && want_subuids() && !local_sub_uid_assigned(fields[0])) {
+ uid_t sub_uid_start = 0;
+ unsigned long sub_uid_count = 0;
+ if (find_new_sub_uids(&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 && want_subgids() && !local_sub_gid_assigned(fields[0])) {
+ gid_t sub_gid_start = 0;
+ unsigned long sub_gid_count = 0;
+ if (find_new_sub_gids(&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");
+ sssd_flush_cache (SSSD_DB_PASSWD | SSSD_DB_GROUP);
+
+#ifdef USE_PAM
+ unsigned int i;
+ /* Now update the passwords using PAM */
+ for (i = 0; i < nusers; i++) {
+ if (do_pam_passwd_non_interactive ("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..a744281
--- /dev/null
+++ b/src/nologin.c
@@ -0,0 +1,41 @@
+/*
+ * SPDX-FileCopyrightText: 2004 The FreeBSD Project.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+
+#ident "$Id$"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <unistd.h>
+
+int main (void)
+{
+ const char *user, *tty;
+ uid_t uid;
+
+ tty = ttyname (0);
+ if (NULL == tty) {
+ tty = "UNKNOWN";
+ }
+ user = getlogin ();
+ if (NULL == user) {
+ user = "UNKNOWN";
+ }
+
+ char *ssh_origcmd = getenv("SSH_ORIGINAL_COMMAND");
+ uid = getuid (); /* getuid() is always successful */
+ openlog ("nologin", LOG_CONS, LOG_AUTH);
+ syslog (LOG_CRIT, "Attempted login by %s (UID: %d) on %s%s%s",
+ user, uid, tty,
+ (ssh_origcmd ? " SSH_ORIGINAL_COMMAND=" : ""),
+ (ssh_origcmd ? ssh_origcmd : ""));
+ 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..8c6f81a
--- /dev/null
+++ b/src/passwd.c
@@ -0,0 +1,1084 @@
+/*
+ * SPDX-FileCopyrightText: 1989 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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>
+#include <time.h>
+#include "defines.h"
+#include "getdef.h"
+#include "nscd.h"
+#include "sssd.h"
+#include "prototypes.h"
+#include "pwauth.h"
+#include "pwio.h"
+#include "shadowio.h"
+#include "shadowlog.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 *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);
+
+/*
+ * 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 */
+#ifdef USE_BCRYPT
+ || (strcmp (method, "BCRYPT") == 0)
+#endif /* USE_BCRYPT*/
+#ifdef USE_YESCRYPT
+ || (strcmp (method, "YESCRYPT") == 0)
+#endif /* USE_YESCRYPT*/
+
+ ) {
+ 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);
+ memzero (pass, sizeof pass);
+ 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);
+ memzero (pass, sizeof pass);
+ 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 *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)
+{
+ char date[80];
+ struct spwd *sp;
+
+ sp = getspnam (pw->pw_name); /* local, no need for xgetspnam */
+ if (NULL != sp) {
+ date_to_str (sizeof(date), date, sp->sp_lstchg * SCALE),
+ (void) printf ("%s %s %s %lld %lld %lld %lld\n",
+ pw->pw_name,
+ pw_status (sp->sp_pwdp),
+ date,
+ ((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 if (NULL != pw->pw_passwd) {
+ (void) printf ("%s %s\n",
+ pw->pw_name, pw_status (pw->pw_passwd));
+ } else {
+ (void) fprintf(stderr, _("%s: malformed password data obtained for user %s\n"),
+ Prog, pw->pw_name);
+ }
+}
+
+
+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);
+#ifndef USE_PAM
+ if (do_update_pwd) {
+ free (cp);
+ }
+#endif /* USE_PAM */
+ 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;
+}
+
+/*
+ * 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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ /*
+ * 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 (amroot && (check_selinux_permit ("passwd") != 0)) {
+ SYSLOG ((LOG_ALERT,
+ "root is not authorized by SELinux to change the password of %s",
+ name));
+ (void) fprintf(stderr,
+ _("%s: root is not authorized by SELinux to change the password of %s\n"),
+ Prog, name);
+ 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");
+ sssd_flush_cache (SSSD_DB_PASSWD | SSSD_DB_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 changed.\n"), Prog);
+ }
+ }
+
+ return E_SUCCESS;
+}
+
diff --git a/src/pwck.c b/src/pwck.c
new file mode 100644
index 0000000..eaa4163
--- /dev/null
+++ b/src/pwck.c
@@ -0,0 +1,897 @@
+/*
+ * SPDX-FileCopyrightText: 1992 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2001 , Michał Moskal
+ * SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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"
+#include "sssd.h"
+#ifdef WITH_TCB
+#include "tcbfuncs.h"
+#endif /* WITH_TCB */
+#include "shadowlog.h"
+
+/*
+ * 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);
+
+extern int allow_bad_names;
+
+/*
+ * 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 (_(" -b, --badname allow bad names\n"), usageout);
+ (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[] = {
+ {"badname", no_argument, NULL, 'b'},
+ {"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, "behqrR:s",
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'b':
+ allow_bad_names = true;
+ break;
+ 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_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_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;
+ const struct spwd *spw;
+ uid_t min_sys_id = (uid_t) getdef_ulong ("SYS_UID_MIN", 101UL);
+ uid_t max_sys_id = (uid_t) getdef_ulong ("SYS_UID_MAX", 999UL);
+
+ /*
+ * 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': use --badname to ignore\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;
+ }
+
+ /*
+ * If uid is not system and has a home directory, then check
+ */
+ if (!(pwd->pw_uid >= min_sys_id && pwd->pw_uid <= max_sys_id ) && pwd->pw_dir && pwd->pw_dir[0]) {
+ /*
+ * Make sure the home directory exists
+ */
+ if (!quiet && (access (pwd->pw_dir, F_OK) != 0)) {
+ const char *nonexistent = getdef_str("NONEXISTENT");
+
+ /*
+ * Home directory does not exist, give a warning (unless intentional)
+ */
+ if (NULL == nonexistent || strcmp (pwd->pw_dir, nonexistent) != 0) {
+ 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_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 = 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) gettime () / 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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ 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);
+
+ if (!read_only) {
+ nscd_flush_cache ("passwd");
+ sssd_flush_cache (SSSD_DB_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..21d36e7
--- /dev/null
+++ b/src/pwconv.c
@@ -0,0 +1,316 @@
+/*
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2002 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2009 - 2012, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+/*
+ * 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"
+#include "sssd.h"
+#include "shadowlog.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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ 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);
+ }
+ (void) spw_rewind();
+ }
+
+ /*
+ * 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) gettime () / 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");
+ sssd_flush_cache (SSSD_DB_PASSWD);
+
+ return E_SUCCESS;
+}
+
diff --git a/src/pwunconv.c b/src/pwunconv.c
new file mode 100644
index 0000000..b862435
--- /dev/null
+++ b/src/pwunconv.c
@@ -0,0 +1,238 @@
+/*
+ * SPDX-FileCopyrightText: 1989 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2001 - 2005, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2008 - 2012, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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 "sssd.h"
+#include "prototypes.h"
+#include "pwio.h"
+#include "shadowio.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+#include "shadowlog.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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ 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");
+ sssd_flush_cache (SSSD_DB_PASSWD);
+
+ return 0;
+}
+
diff --git a/src/su.c b/src/su.c
new file mode 100644
index 0000000..6cd82fc
--- /dev/null
+++ b/src/su.c
@@ -0,0 +1,1216 @@
+/*
+ * SPDX-FileCopyrightText: 1989 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2013, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+/* 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"
+#include "shadowlog.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 void kill_child (int unused(s));
+static void prepare_pam_close_session (void);
+#else /* !USE_PAM */
+static void 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 void 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 void 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;
+ struct sigaction action;
+
+ /* reset SIGCHLD handling to default */
+ action.sa_handler = SIG_DFL;
+ sigemptyset (&action.sa_mask);
+ action.sa_flags = 0;
+ if (0 == caught && sigaction (SIGCHLD, &action, NULL) != 0) {
+ fprintf (stderr,
+ _("%s: signal masking malfunction\n"),
+ Prog);
+ SYSLOG ((LOG_WARN, "Will not execute %s", shellstr));
+ closelog ();
+ exit (1);
+ /* Only the child returns. See above. */
+ }
+
+ 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) {
+ action.sa_handler = catch_signals;
+ 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;
+
+ do {
+ pid = waitpid (-1, &status, WUNTRACED);
+ } while (pid != -1 && pid != pid_child);
+
+ /* 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"));
+
+ /* Any signals other than SIGCHLD and SIGALRM will no longer have any effect,
+ * so it's time to block all of them. */
+ sigfillset (&ourset);
+ if (sigprocmask (SIG_BLOCK, &ourset, NULL) != 0) {
+ fprintf (stderr, _("%s: signal masking malfunction\n"), Prog);
+ kill_child (0);
+ /* Never reach (_exit called). */
+ }
+
+ /* Send SIGKILL to the child if it doesn't
+ * exit within 2 seconds (after SIGTERM) */
+ (void) signal (SIGALRM, kill_child);
+ (void) signal (SIGCHLD, catch_signals);
+ (void) alarm (2);
+
+ (void) sigdelset (&ourset, SIGALRM);
+ (void) sigdelset (&ourset, SIGCHLD);
+
+ while (0 == waitpid (pid_child, &status, WNOHANG)) {
+ sigsuspend (&ourset);
+ }
+ pid_child = 0;
+
+ (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] [-] [username [args]]\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"
+ "If no username is given, assume root.\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;
+ sighandler_t oldsig;
+
+ if (caller_is_root) {
+ return;
+ }
+
+ if (strcmp (pw->pw_passwd, "") == 0) {
+ char *prevent_no_auth = getdef_str("PREVENT_NO_AUTH");
+ if (prevent_no_auth == NULL) {
+ prevent_no_auth = "superuser";
+ }
+ if (strcmp(prevent_no_auth, "yes") == 0) {
+ fprintf(stderr, _("Password field is empty, this is forbidden for all accounts.\n"));
+ exit(1);
+ } else if ((pw->pw_uid == 0)
+ && (strcmp(prevent_no_auth, "superuser") == 0)) {
+ fprintf(stderr, _("Password field is empty, this is forbidden for super-user.\n"));
+ exit(1);
+ }
+ }
+
+ /*
+ * 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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ 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) {
+ STRFCPY (name, argv[optind++]); /* use this login id */
+ }
+ 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 */
+ }
+
+ environ = newenvp; /* make new environment active */
+
+ 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);
+ }
+
+#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
+ */
+ /* update environment with all pam set variables */
+ char **envcp = pam_getenvlist (pamh);
+ if (NULL != envcp) {
+ while (NULL != *envcp) {
+ addenv (*envcp, NULL);
+ envcp++;
+ }
+ }
+#endif /* !USE_PAM */
+ }
+
+}
+
+/*
+ * 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);
+ }
+ }
+
+#ifdef USE_PAM
+ (void) pam_end (pamh, PAM_SUCCESS | PAM_DATA_SILENT);
+#endif
+
+ 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..2641d33
--- /dev/null
+++ b/src/suauth.c
@@ -0,0 +1,216 @@
+/*
+ * SPDX-FileCopyrightText: 1990 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2002 - 2005, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2008, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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: unrecognized 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..2c5e094
--- /dev/null
+++ b/src/sulogin.c
@@ -0,0 +1,237 @@
+/*
+ * SPDX-FileCopyrightText: 1989 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2002 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2010, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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"
+#include "shadowlog.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 void catch_signals (int);
+
+static void 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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+ (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"));
+ }
+ memzero (pass, sizeof 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..7ea0a9c
--- /dev/null
+++ b/src/useradd.c
@@ -0,0 +1,2744 @@
+/*
+ * SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2012, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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 <libgen.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 "sssd.h"
+#include "prototypes.h"
+#include "pwauth.h"
+#include "pwio.h"
+#include "run_part.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif
+#include "shadowio.h"
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#endif /* WITH_SELINUX */
+#ifdef ENABLE_SUBIDS
+#include "subordinateio.h"
+#endif /* ENABLE_SUBIDS */
+#ifdef WITH_TCB
+#include "tcbfuncs.h"
+#endif
+#include "shadowlog.h"
+
+#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 = 1000;
+static const char *def_gname = "other";
+static const char *def_home = "/home";
+static const char *def_shell = "/bin/bash";
+static const char *def_template = SKEL_DIR;
+static const char *def_create_mail_spool = "yes";
+static const char *def_log_init = "yes";
+
+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 = "";
+
+static const char *prefix = "";
+static const char *prefix_user_home = NULL;
+
+#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 gid_t sub_gid_start; /* New subordinate gid range */
+#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 */
+
+extern int allow_bad_names;
+
+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 */
+#ifdef ENABLE_SUBIDS
+ Fflg = false, /* update /etc/subuid and /etc/subgid even if -r option is given */
+#endif
+ 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 */
+ subvolflg = false, /* create subvolume home on BTRFS */
+ 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 or group name already in use */
+#define E_GRP_UPDATE 10 /* can't update group file */
+#define E_HOMEDIR 12 /* can't create home directory */
+#define E_MAILBOXFILE 13 /* can't create mailbox file */
+#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="
+#define DLOG_INIT "LOG_INIT="
+
+/* 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 struct group * get_local_group (char * grp_name);
+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 close_group_files (void);
+static void unlock_group_files (void);
+static void open_files (void);
+static void open_group_files (void);
+static void open_shadow (void);
+static void faillog_reset (uid_t);
+static void lastlog_reset (uid_t);
+static void tallylog_reset (const char *);
+static void usr_update (unsigned long subuid_count, unsigned long subgid_count);
+static void create_home (void);
+static void create_mail (void);
+static void check_uid_range(int rflg, uid_t user_id);
+
+/*
+ * fail_exit - undo as much as possible
+ */
+static void fail_exit (int code)
+{
+ if (home_added) {
+ if (rmdir (prefix_user_home) != 0) {
+ fprintf (stderr,
+ _("%s: %s was created, but could not be removed\n"),
+ Prog, prefix_user_home);
+ SYSLOG ((LOG_ERR, "failed to remove %s", prefix_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', exit code: %d", user_name, code));
+ 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 *default_file = USER_DEFAULTS_FILE;
+ char buf[1024];
+ char *cp;
+
+ if (prefix[0]) {
+ size_t len;
+ int wlen;
+
+ len = strlen(prefix) + strlen(USER_DEFAULTS_FILE) + 2;
+ default_file = malloc(len);
+ if (default_file == NULL)
+ return;
+ wlen = snprintf(default_file, len, "%s/%s", prefix, USER_DEFAULTS_FILE);
+ assert (wlen == (int) len -1);
+ }
+
+ /*
+ * Open the defaults file for reading.
+ */
+
+ fp = fopen (default_file, "r");
+ if (NULL == fp) {
+ goto getdef_err;
+ }
+
+ /*
+ * 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 = prefix_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, default_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, default_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 */
+ }
+
+ if (prefix[0]) {
+ size_t len;
+ int wlen;
+ char* _def_template; /* avoid const warning */
+
+ len = strlen(prefix) + strlen(cp) + 2;
+ _def_template = xmalloc(len);
+ wlen = snprintf(_def_template, len, "%s/%s", prefix, cp);
+ assert (wlen == (int) len -1);
+ def_template = _def_template;
+ }
+ else {
+ 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);
+ }
+
+ /*
+ * By default do we add the user to the lastlog and faillog databases ?
+ */
+ else if (MATCH (buf, DLOG_INIT)) {
+ if (*cp == '\0') {
+ cp = def_log_init; /* XXX warning: const */
+ }
+ def_log_init = xstrdup (cp);
+ }
+ }
+ (void) fclose (fp);
+ getdef_err:
+ if (prefix[0]) {
+ free(default_file);
+ }
+}
+
+/*
+ * 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);
+ printf ("LOG_INIT=%s\n", def_log_init);
+}
+
+/*
+ * 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];
+ char *new_file = NULL;
+ char *new_file_dup = NULL;
+ char *default_file = USER_DEFAULTS_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;
+ bool out_log_init = false;
+ size_t len;
+ int ret = -1;
+
+
+ len = strlen(prefix) + strlen(NEW_USER_FILE) + 2;
+ new_file = malloc(len);
+ if (new_file == NULL) {
+ fprintf (stderr,
+ _("%s: cannot create new defaults file: %s\n"),
+ Prog, strerror(errno));
+ return -1;
+ }
+ wlen = snprintf(new_file, len, "%s%s%s", prefix, prefix[0]?"/":"", NEW_USER_FILE);
+ assert (wlen <= (int) len -1);
+
+ if (prefix[0]) {
+ len = strlen(prefix) + strlen(USER_DEFAULTS_FILE) + 2;
+ default_file = malloc(len);
+ if (default_file == NULL) {
+ fprintf (stderr,
+ _("%s: cannot create new defaults file: %s\n"),
+ Prog, strerror(errno));
+ goto setdef_err;
+ }
+ wlen = snprintf(default_file, len, "%s/%s", prefix, USER_DEFAULTS_FILE);
+ assert (wlen == (int) len -1);
+ }
+
+ new_file_dup = strdup(new_file);
+ if (new_file_dup == NULL) {
+ fprintf (stderr,
+ _("%s: cannot create directory for defaults file\n"),
+ Prog);
+ goto setdef_err;
+ }
+
+ ret = mkdir(dirname(new_file_dup), 0755);
+ if (-1 == ret && EEXIST != errno) {
+ fprintf (stderr,
+ _("%s: cannot create directory for defaults file\n"),
+ Prog);
+ free(new_file_dup);
+ goto setdef_err;
+ }
+ free(new_file_dup);
+
+ /*
+ * 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);
+ goto setdef_err;
+ }
+
+ ofp = fdopen (ofd, "w");
+ if (NULL == ofp) {
+ fprintf (stderr,
+ _("%s: cannot open new defaults file\n"),
+ Prog);
+ goto setdef_err;
+ }
+
+ /*
+ * 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 (default_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, default_file, buf);
+ (void) fclose (ifp);
+ goto setdef_err;
+ }
+ }
+
+ 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 if (!out_log_init
+ && MATCH (buf, DLOG_INIT)) {
+ fprintf (ofp,
+ DLOG_INIT "%s\n",
+ def_log_init);
+ out_log_init = 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);
+ if (!out_log_init)
+ fprintf (ofp, DLOG_INIT "%s\n", def_log_init);
+ /*
+ * 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);
+ goto setdef_err;
+ }
+
+ /*
+ * Rename the current default file to its backup name.
+ */
+ wlen = snprintf (buf, sizeof buf, "%s-", default_file);
+ assert (wlen < (int) sizeof buf);
+ unlink (buf);
+ if ((link (default_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);
+ goto setdef_err;
+ }
+
+ /*
+ * Rename the new default file to its correct name.
+ */
+ if (rename (new_file, default_file) != 0) {
+ int err = errno;
+ fprintf (stderr,
+ _("%s: rename: %s: %s\n"),
+ Prog, new_file, strerror (err));
+ goto setdef_err;
+ }
+#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, LOG_INIT=%s",
+ (unsigned int) def_group, def_home, def_shell,
+ def_inactive, def_expire, def_template,
+ def_create_mail_spool, def_log_init));
+ ret = 0;
+ setdef_err:
+ free(new_file);
+ if (prefix[0]) {
+ free(default_file);
+ }
+
+ return ret;
+}
+
+/*
+ * 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;
+ struct group *grp;
+ int errors = 0;
+ int ngroups = 0;
+
+ if ('\0' == *list) {
+ return 0;
+ }
+
+ /*
+ * Open the group files
+ */
+ open_group_files ();
+
+ /*
+ * 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 = get_local_group (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);
+ gr_free(grp);
+ continue;
+ }
+#endif
+
+ if (ngroups == sys_ngroups) {
+ fprintf (stderr,
+ _("%s: too many groups specified (max %d).\n"),
+ Prog, ngroups);
+ gr_free(grp);
+ break;
+ }
+
+ /*
+ * Add the group name to the user's list of groups.
+ */
+ user_groups[ngroups++] = xstrdup (grp->gr_name);
+ gr_free (grp);
+ } while (NULL != list);
+
+ close_group_files ();
+ unlock_group_files ();
+
+ user_groups[ngroups] = (char *) 0;
+
+ /*
+ * Any errors in finding group names are fatal
+ */
+ if (0 != errors) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * get_local_group - checks if a given group name exists locally
+ *
+ * get_local_group() checks if a given group name exists locally.
+ * If the name exists the group information is returned, otherwise NULL is
+ * returned.
+ */
+static struct group * get_local_group(char * grp_name)
+{
+ const struct group *grp;
+ struct group *result_grp = NULL;
+ long long int gid;
+ char *endptr;
+
+ gid = strtoll (grp_name, &endptr, 10);
+ if ( ('\0' != *grp_name)
+ && ('\0' == *endptr)
+ && (ERANGE != errno)
+ && (gid == (gid_t)gid)) {
+ grp = gr_locate_gid ((gid_t) gid);
+ }
+ else {
+ grp = gr_locate(grp_name);
+ }
+
+ if (grp != NULL) {
+ result_grp = __gr_dup (grp);
+ if (NULL == result_grp) {
+ fprintf (stderr,
+ _("%s: Out of memory. Cannot find group '%s'.\n"),
+ Prog, grp_name);
+ fail_exit (E_GRP_UPDATE);
+ }
+ }
+
+ return result_grp;
+}
+
+/*
+ * 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 (_(" --badname do not check for bad names\n"), usageout);
+ (void) fputs (_(" -b, --base-dir BASE_DIR base directory for the home directory of the\n"
+ " new account\n"), usageout);
+#ifdef WITH_BTRFS
+ (void) fputs (_(" --btrfs-subvolume-home use BTRFS subvolume for home directory\n"), usageout);
+#endif
+ (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);
+#ifdef ENABLE_SUBIDS
+ (void) fputs (_(" -F, --add-subids-for-system add entries to sub[ud]id even when adding a system user\n"), usageout);
+#endif
+ (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 (_(" -P, --prefix PREFIX_DIR prefix directory where are located the /etc/* files\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;
+ struct stat st;
+
+ {
+ /*
+ * Parse the command line options.
+ */
+ int c;
+ static struct option long_options[] = {
+ {"base-dir", required_argument, NULL, 'b'},
+#ifdef WITH_BTRFS
+ {"btrfs-subvolume-home", no_argument, NULL, 200},
+#endif
+ {"badname", no_argument, NULL, 201},
+ {"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'},
+#ifdef ENABLE_SUBIDS
+ {"add-subids-for-system", no_argument,NULL, 'F'},
+#endif
+ {"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'},
+ {"prefix", required_argument, NULL, 'P'},
+ {"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,
+ "b:c:d:De:f:g:G:hk:K:lmMNop:rR:P:s:u:U"
+#ifdef WITH_SELINUX
+ "Z:"
+#endif /* WITH_SELINUX */
+#ifdef ENABLE_SUBIDS
+ "F"
+#endif /* ENABLE_SUBIDS */
+ "",
+ 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 200:
+ subvolflg = true;
+ break;
+ case 201:
+ allow_bad_names = 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;
+#ifdef ENABLE_SUBIDS
+ case 'F':
+ Fflg = true;
+ break;
+#endif
+ case 'g':
+ grp = prefix_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 'P': /* no-op, handled in process_prefix_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);
+ }
+ if ( '\0' != optarg[0]
+ && '*' != optarg[0]
+ && strcmp(optarg, "/sbin/nologin") != 0
+ && ( stat(optarg, &st) != 0
+ || S_ISDIR(st.st_mode)
+ || access(optarg, X_OK) != 0)) {
+ fprintf (stderr,
+ _("%s: Warning: missing or non-executable shell '%s'\n"),
+ Prog, optarg);
+ }
+ 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 (prefix[0]) {
+ fprintf (stderr,
+ _("%s: -Z cannot be used with --prefix\n"),
+ Prog);
+ exit (E_BAD_ARG);
+ }
+ 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': use --badname to ignore\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 (prefix[0]) {
+ size_t len = strlen(prefix) + strlen(user_home) + 2;
+ int wlen;
+ char* _prefix_user_home; /* to avoid const warning */
+ _prefix_user_home = xmalloc(len);
+ wlen = snprintf(_prefix_user_home, len, "%s/%s", prefix, user_home);
+ assert (wlen == (int) len -1);
+ prefix_user_home = _prefix_user_home;
+ }
+ else {
+ prefix_user_home = user_home;
+ }
+ }
+
+ 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 (!lflg) {
+ /* If we are missing the flag lflg aka -l, check the defaults
+ * file to see if we need to disable it as a default*/
+ if (strcmp (def_log_init, "no") == 0) {
+ lflg = true;
+ }
+ }
+
+ 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);
+ }
+
+ close_group_files ();
+
+#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;
+
+ unlock_group_files ();
+
+#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 */
+}
+
+/*
+ * close_group_files - close all of the files that were opened
+ *
+ * close_group_files() closes all of the files that were opened related
+ * with groups. This causes any modified entries to be written out.
+ */
+static void close_group_files (void)
+{
+ 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 /* SHADOWGRP */
+ }
+}
+
+/*
+ * unlock_group_files - unlock all of the files that were locked
+ *
+ * unlock_group_files() unlocks all of the files that were locked related
+ * with groups. This causes any modified entries to be written out.
+ */
+static void unlock_group_files (void)
+{
+ 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 /* WITH_AUDIT */
+ /* 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 /* WITH_AUDIT */
+ /* continue */
+ }
+ sgr_locked = false;
+ }
+#endif /* SHADOWGRP */
+}
+
+/*
+ * 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(); */
+
+ open_group_files ();
+
+#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_group_files (void)
+{
+ 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 /* SHADOWGRP */
+}
+
+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;
+ struct stat st;
+
+ if (stat (FAILLOG_FILE, &st) != 0 || st.st_size <= offset_uid) {
+ return;
+ }
+
+ memzero (&fl, sizeof (fl));
+
+ fd = open (FAILLOG_FILE, O_RDWR);
+ if (-1 == fd) {
+ fprintf (stderr,
+ _("%s: failed to open the faillog file for UID %lu: %s\n"),
+ Prog, (unsigned long) uid, strerror (errno));
+ SYSLOG ((LOG_WARN, "failed to open the faillog file for UID %lu", (unsigned long) uid));
+ return;
+ }
+ if ( (lseek (fd, offset_uid, SEEK_SET) != offset_uid)
+ || (write (fd, &fl, sizeof (fl)) != (ssize_t) sizeof (fl))
+ || (fsync (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));
+ }
+ if (close (fd) != 0) {
+ fprintf (stderr,
+ _("%s: failed to close the faillog file for UID %lu: %s\n"),
+ Prog, (unsigned long) uid, strerror (errno));
+ SYSLOG ((LOG_WARN, "failed to close the faillog file for UID %lu", (unsigned long) uid));
+ }
+}
+
+static void lastlog_reset (uid_t uid)
+{
+ struct lastlog ll;
+ int fd;
+ off_t offset_uid = (off_t) (sizeof ll) * uid;
+ uid_t max_uid;
+ struct stat st;
+
+ if (stat (LASTLOG_FILE, &st) != 0 || st.st_size <= offset_uid) {
+ return;
+ }
+
+ max_uid = (uid_t) getdef_ulong ("LASTLOG_UID_MAX", 0xFFFFFFFFUL);
+ if (uid > max_uid) {
+ /* do not touch lastlog for large uids */
+ return;
+ }
+
+ memzero (&ll, sizeof (ll));
+
+ fd = open (LASTLOG_FILE, O_RDWR);
+ if (-1 == fd) {
+ fprintf (stderr,
+ _("%s: failed to open the lastlog file for UID %lu: %s\n"),
+ Prog, (unsigned long) uid, strerror (errno));
+ SYSLOG ((LOG_WARN, "failed to open the lastlog file for UID %lu", (unsigned long) uid));
+ return;
+ }
+ if ( (lseek (fd, offset_uid, SEEK_SET) != offset_uid)
+ || (write (fd, &ll, sizeof (ll)) != (ssize_t) sizeof (ll))
+ || (fsync (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 */
+ }
+ if (close (fd) != 0) {
+ fprintf (stderr,
+ _("%s: failed to close the lastlog file for UID %lu: %s\n"),
+ Prog, (unsigned long) uid, strerror (errno));
+ SYSLOG ((LOG_WARN, "failed to close the lastlog file for UID %lu", (unsigned long) uid));
+ /* continue */
+ }
+}
+
+static void tallylog_reset (const 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 (unsigned long subuid_count, unsigned long subgid_count)
+{
+ struct passwd pwent;
+ struct spwd spent;
+ char *tty;
+
+ /*
+ * 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.
+ */
+ tty=ttyname (STDIN_FILENO);
+ SYSLOG ((LOG_INFO,
+ "new user: name=%s, UID=%u, GID=%u, home=%s, shell=%s, from=%s",
+ user_name, (unsigned int) user_id,
+ (unsigned int) user_gid, user_home, user_shell,
+ tty ? tty : "none" ));
+
+ /*
+ * 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) && (prefix_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, subuid_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, subgid_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 (prefix_user_home, F_OK) != 0) {
+ char path[strlen (prefix_user_home) + 2];
+ char *bhome, *cp;
+
+ path[0] = '\0';
+ bhome = strdup (prefix_user_home);
+ if (!bhome) {
+ fprintf (stderr,
+ _("%s: error while duplicating string %s\n"),
+ Prog, user_home);
+ fail_exit (E_HOMEDIR);
+ }
+
+#ifdef WITH_SELINUX
+ if (set_selinux_file_context (prefix_user_home, S_IFDIR) != 0) {
+ fprintf (stderr,
+ _("%s: cannot set SELinux context for home directory %s\n"),
+ Prog, user_home);
+ fail_exit (E_HOMEDIR);
+ }
+#endif
+
+ /* Check for every part of the path, if the directory
+ exists. If not, create it with permissions 755 and
+ owner root:root.
+ */
+ cp = strtok (bhome, "/");
+ while (cp) {
+ /* Avoid turning a relative path into an absolute path.
+ */
+ if (bhome[0] == '/' || strlen (path) != 0) {
+ strcat (path, "/");
+ }
+ strcat (path, cp);
+ if (access (path, F_OK) != 0) {
+ /* Check if parent directory is BTRFS, fail if requesting
+ subvolume but no BTRFS. The paths cound be different by the
+ trailing slash
+ */
+#if WITH_BTRFS
+ if (subvolflg && (strlen(prefix_user_home) - (int)strlen(path)) <= 1) {
+ char *btrfs_check = strdup(path);
+
+ if (!btrfs_check) {
+ fprintf (stderr,
+ _("%s: error while duplicating string in BTRFS check %s\n"),
+ Prog, path);
+ fail_exit (E_HOMEDIR);
+ }
+ btrfs_check[strlen(path) - strlen(cp) - 1] = '\0';
+ if (is_btrfs(btrfs_check) <= 0) {
+ fprintf (stderr,
+ _("%s: home directory \"%s\" must be mounted on BTRFS\n"),
+ Prog, path);
+ fail_exit (E_HOMEDIR);
+ }
+ // make subvolume to mount for user instead of directory
+ if (btrfs_create_subvolume(path)) {
+ fprintf (stderr,
+ _("%s: failed to create BTRFS subvolume: %s\n"),
+ Prog, path);
+ fail_exit (E_HOMEDIR);
+ }
+ }
+ else
+#endif
+ if (mkdir (path, 0) != 0) {
+ fprintf (stderr,
+ _("%s: cannot create directory %s\n"),
+ Prog, path);
+#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);
+ }
+ if (chown (path, 0, 0) < 0) {
+ fprintf (stderr,
+ _("%s: warning: chown on `%s' failed: %m\n"),
+ Prog, path);
+ }
+ if (chmod (path, 0755) < 0) {
+ fprintf (stderr,
+ _("%s: warning: chmod on `%s' failed: %m\n"),
+ Prog, path);
+ }
+ }
+ cp = strtok (NULL, "/");
+ }
+ free (bhome);
+
+ (void) chown (prefix_user_home, user_id, user_gid);
+ mode_t mode = getdef_num ("HOME_MODE",
+ 0777 & ~getdef_num ("UMASK", GETDEF_DEFAULT_UMASK));
+ if (chmod (prefix_user_home, mode)) {
+ fprintf (stderr, _("%s: warning: chown on '%s' failed: %m\n"),
+ Prog, path);
+ }
+ 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");
+#ifdef MAIL_SPOOL_DIR
+ if ((NULL == spool) && (getdef_str ("MAIL_FILE") == NULL)) {
+ spool = MAIL_SPOOL_DIR;
+ }
+#endif /* MAIL_SPOOL_DIR */
+ if (NULL == spool) {
+ return;
+ }
+ file = alloca (strlen (prefix) + strlen (spool) + strlen (user_name) + 3);
+ if (prefix[0])
+ sprintf (file, "%s/%s/%s", prefix, spool, user_name);
+ else
+ sprintf (file, "%s/%s", spool, user_name);
+
+#ifdef WITH_SELINUX
+ if (set_selinux_file_context (file, S_IFREG) != 0) {
+ fprintf (stderr,
+ _("%s: cannot set SELinux context for mailbox file %s\n"),
+ Prog, file);
+ fail_exit (E_MAILBOXFILE);
+ }
+#endif
+
+ fd = open (file, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0);
+ if (fd < 0) {
+ perror (_("Creating mailbox file"));
+ return;
+ }
+
+ gr = prefix_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);
+#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_MAILBOXFILE);
+ }
+#endif
+ }
+}
+
+static void check_uid_range(int rflg, uid_t user_id)
+{
+ uid_t uid_min ;
+ uid_t uid_max ;
+ if (rflg) {
+ uid_max = (uid_t)getdef_ulong("SYS_UID_MAX",getdef_ulong("UID_MIN",1000UL)-1);
+ if (user_id > uid_max) {
+ fprintf(stderr, _("%s warning: %s's uid %d is greater than SYS_UID_MAX %d\n"), Prog, user_name, user_id, uid_max);
+ }
+ }else{
+ uid_min = (uid_t)getdef_ulong("UID_MIN", 1000UL);
+ uid_max = (uid_t)getdef_ulong("UID_MAX", 6000UL);
+ if (uid_min <= uid_max) {
+ if (user_id < uid_min || user_id >uid_max)
+ fprintf(stderr, _("%s warning: %s's uid %d outside of the UID_MIN %d and UID_MAX %d range.\n"), Prog, user_name, user_id, uid_min, uid_max);
+ }
+ }
+
+}
+/*
+ * 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
+ unsigned long subuid_count = 0;
+ unsigned long subgid_count = 0;
+
+ /*
+ * Get my name so that I can use it to report errors.
+ */
+ Prog = Basename (argv[0]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ prefix = process_prefix_flag("-P", 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);
+ subuid_count = getdef_ulong ("SUB_UID_COUNT", 65536);
+ subgid_count = getdef_ulong ("SUB_GID_COUNT", 65536);
+ is_sub_uid = subuid_count > 0 && sub_uid_file_present () &&
+ (!rflg || Fflg) &&
+ (!user_id || (user_id <= uid_max && user_id >= uid_min));
+ is_sub_gid = subgid_count > 0 && sub_gid_file_present () &&
+ (!rflg || Fflg) &&
+ (!user_id || (user_id <= uid_max && user_id >= uid_min));
+#endif /* ENABLE_SUBIDS */
+
+ if (run_parts ("/etc/shadow-maint/useradd-pre.d", user_name,
+ "useradd")) {
+ exit(1);
+ }
+
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
+ {
+ struct passwd *pampw;
+ pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+ if (pampw == NULL && getuid ()) {
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ fail_exit (1);
+ }
+
+ retval = pam_start ("useradd", pampw?pampw->pw_name:"root", &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 (prefix_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 (prefix_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 (prefix_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);
+ }
+ }
+ }
+
+ if (uflg)
+ check_uid_range(rflg,user_id);
+#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 && subuid_count != 0) {
+ if (find_new_sub_uids(&sub_uid_start, &subuid_count) < 0) {
+ fprintf (stderr,
+ _("%s: can't create subordinate user IDs\n"),
+ Prog);
+ fail_exit(E_SUB_UID_UPDATE);
+ }
+ }
+ if (is_sub_gid && subgid_count != 0) {
+ if (find_new_sub_gids(&sub_gid_start, &subgid_count) < 0) {
+ fprintf (stderr,
+ _("%s: can't create subordinate group IDs\n"),
+ Prog);
+ fail_exit(E_SUB_GID_UPDATE);
+ }
+ }
+#endif /* ENABLE_SUBIDS */
+
+ usr_update (subuid_count, subgid_count);
+
+ close_files ();
+
+ nscd_flush_cache ("passwd");
+ nscd_flush_cache ("group");
+ sssd_flush_cache (SSSD_DB_PASSWD | SSSD_DB_GROUP);
+
+ /*
+ * tallylog_reset needs to be able to lookup
+ * a valid existing user name,
+ * so we cannot 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 */
+
+ if (mflg) {
+ create_home ();
+ if (home_added) {
+ copy_tree (def_template, prefix_user_home, false, true,
+ (uid_t)-1, user_id, (gid_t)-1, user_gid);
+ } else {
+ fprintf (stderr,
+ _("%s: warning: the home directory %s already exists.\n"
+ "%s: Not copying any file from skel directory into it.\n"),
+ Prog, user_home, Prog);
+ }
+
+ }
+
+ /* Do not create mail directory for system accounts */
+ if (!rflg) {
+ create_mail ();
+ }
+
+ if (run_parts ("/etc/shadow-maint/useradd-post.d", user_name,
+ "useradd")) {
+ exit(1);
+ }
+
+ return E_SUCCESS;
+}
+
diff --git a/src/userdel.c b/src/userdel.c
new file mode 100644
index 0000000..7012b0e
--- /dev/null
+++ b/src/userdel.c
@@ -0,0 +1,1346 @@
+/*
+ * SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2012, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <config.h>
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.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 "sssd.h"
+#include "prototypes.h"
+#include "pwauth.h"
+#include "pwio.h"
+#include "shadowio.h"
+#ifdef SHADOWGRP
+#include "sgroupio.h"
+#endif /* SHADOWGRP */
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#endif /* WITH_SELINUX */
+#ifdef WITH_TCB
+#include <tcb.h>
+#include "tcbfuncs.h"
+#endif /* WITH_TCB */
+#include "run_part.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+#ifdef ENABLE_SUBIDS
+#include "subordinateio.h"
+#endif /* ENABLE_SUBIDS */
+#include "shadowlog.h"
+
+/*
+ * 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;
+#ifdef WITH_SELINUX
+static bool Zflg = false;
+#endif
+static bool Rflg = 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 */
+
+static const char* prefix = "";
+
+/* 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 some actions that would fail otherwise\n"
+ " e.g. removal of user still logged in\n"
+ " or files, even if not owned by the 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);
+ (void) fputs (_(" -P, --prefix PREFIX_DIR prefix directory where are located the /etc/* files\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.
+ */
+ prefix_setpwent ();
+ while ((pwd = prefix_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;
+ }
+ }
+ prefix_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;
+ int i;
+ int errors = 0;
+ size_t len;
+
+ 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;
+ }
+
+ len = strlen (prefix) + strlen (maildir) + strlen (user_name) + 2;
+ mailfile = xmalloc (len);
+
+ if (prefix[0]) {
+ (void) snprintf (mailfile, len, "%s/%s/%s",
+ prefix, maildir, user_name);
+ }
+ else {
+ (void) snprintf (mailfile, len, "%s/%s",
+ maildir, user_name);
+ }
+ mailfile[len-1] = '\0';
+
+ if (access (mailfile, F_OK) != 0) {
+ if (ENOENT == errno) {
+ fprintf (stderr,
+ _("%s: %s mail spool (%s) not found\n"),
+ Prog, user_name, mailfile);
+ free(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 */
+ free(mailfile);
+ 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 */
+ free(mailfile);
+ 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 */
+ free(mailfile);
+ return 1;
+ } else if (i == -1) {
+ free(mailfile);
+ 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 */
+ free(mailfile);
+ return errors;
+}
+
+#ifdef WITH_TCB
+static int remove_tcbdir (const char *user_name, uid_t user_id)
+{
+ char *buf;
+ int ret = 0;
+ size_t buflen = (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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+ prefix = process_prefix_flag ("-P", 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'},
+ {"prefix", required_argument, NULL, 'P'},
+#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:P:Z",
+#else /* !WITH_SELINUX */
+ "fhrR:P:",
+#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 () */
+ Rflg = true;
+ break;
+ case 'P': /* no-op, handled in process_prefix_flag () */
+ break;
+#ifdef WITH_SELINUX
+ case 'Z':
+ if (prefix[0]) {
+ fprintf (stderr,
+ _("%s: -Z cannot be used with --prefix\n"),
+ Prog);
+ exit (E_BAD_ARG);
+ }
+ 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];
+ {
+ const struct passwd *pwd;
+
+ if (run_parts ("/etc/shadow-maint/userdel-pre.d", user_name,
+ "userdel")) {
+ exit(1);
+ }
+ pw_open(O_RDONLY);
+ pwd = pw_locate (user_name); /* we care only about local users */
+ if (NULL == pwd) {
+ pw_close();
+ 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;
+
+ if (prefix[0]) {
+
+ size_t len = strlen(prefix) + strlen(pwd->pw_dir) + 2;
+ int wlen;
+ user_home = xmalloc(len);
+ wlen = snprintf(user_home, len, "%s/%s", prefix, pwd->pw_dir);
+ assert (wlen == (int) len -1);
+ }
+ else {
+ user_home = xstrdup (pwd->pw_dir);
+ }
+ pw_close();
+ }
+#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 ((prefix[0] == '\0') && !Rflg && 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
+ */
+ prefix_setpwent ();
+ while ((pwd = prefix_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;
+ }
+ }
+ prefix_endpwent ();
+ }
+#endif /* EXTRA_CHECK_HOME_DIR */
+
+ if (rflg) {
+#ifdef WITH_BTRFS
+ int is_subvolume = btrfs_is_subvolume (user_home);
+ if (is_subvolume < 0) {
+ errors++;
+ /* continue */
+ }
+ else if (is_subvolume > 0) {
+ if (btrfs_remove_subvolume (user_home)) {
+ fprintf (stderr,
+ _("%s: error removing subvolume %s\n"),
+ Prog, user_home);
+ errors++;
+ /* continue */
+ }
+ }
+ else
+#endif
+ 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.
+ */
+ if (prefix[0] == '\0')
+ user_cancel (user_name);
+ close_files ();
+
+ if (run_parts ("/etc/shadow-maint/userdel-post.d", user_name, "userdel")) {
+ exit(1);
+ }
+
+#ifdef WITH_TCB
+ errors += remove_tcbdir (user_name, user_id);
+#endif /* WITH_TCB */
+
+ nscd_flush_cache ("passwd");
+ nscd_flush_cache ("group");
+ sssd_flush_cache (SSSD_DB_PASSWD | SSSD_DB_GROUP);
+
+ return ((0 != errors) ? E_HOMEDIR : E_SUCCESS);
+}
+
diff --git a/src/usermod.c b/src/usermod.c
new file mode 100644
index 0000000..c1a5b2c
--- /dev/null
+++ b/src/usermod.c
@@ -0,0 +1,2388 @@
+/*
+ * SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#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 <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 "sssd.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_SELINUX
+#include <selinux/selinux.h>
+#endif /* WITH_SELINUX */
+#ifdef WITH_TCB
+#include "tcbfuncs.h"
+#endif
+#include "shadowlog.h"
+
+/*
+ * 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 or group name 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 const char* prefix = "";
+static char* prefix_user_home = NULL;
+static char* prefix_user_newhome = NULL;
+
+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 */
+ rflg = false, /* remove a user from a single group */
+ 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 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
+
+extern int allow_bad_names;
+
+/*
+ * 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 = prefix_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 (_(" -a, --append append the user to the supplemental GROUPS\n"
+ " mentioned by the -G option without removing\n"
+ " the user from other groups\n"), usageout);
+ (void) fputs (_(" -b, --badname allow bad names\n"), usageout);
+ (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 (_(" -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 (_(" -P, --prefix PREFIX_DIR prefix directory where are located the /etc/* files\n"), usageout);
+ (void) fputs (_(" -r, --remove remove the user from only the supplemental GROUPS\n"
+ " mentioned by the -G option without removing\n"
+ " the user from other groups\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));
+
+ if (strlen(user_newhome) > 1
+ && '/' == user_newhome[strlen(user_newhome)-1]) {
+ user_newhome[strlen(user_newhome)-1]='\0';
+ }
+
+ 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 (sizeof(new_exp), new_exp, user_newexpire * DAY);
+ date_to_str (sizeof(old_exp), 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;
+ }
+
+ /*
+ * If rflg+Gflg is passed in AKA -rG invert is_member flag, which removes
+ * mentioned groups while leaving the others.
+ */
+ if (Gflg && rflg) {
+ is_member = !is_member;
+ }
+
+ 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 if (is_member) {
+ /* 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);
+ }
+
+ gr_free(ngrp);
+ }
+}
+
+#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;
+ }
+
+ /*
+ * If rflg+Gflg is passed in AKA -rG invert is_member, to remove targeted
+ * groups while leaving the user apart of groups not mentioned
+ */
+ if (Gflg && rflg) {
+ is_member = !is_member;
+ }
+
+ 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);
+ }
+
+ free (nsgrp);
+ }
+}
+#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;
+ struct stat st;
+ bool anyflag = false;
+
+ {
+ /*
+ * Parse the command line options.
+ */
+ int c;
+ static struct option long_options[] = {
+ {"append", no_argument, NULL, 'a'},
+ {"badnames", no_argument, NULL, 'b'},
+ {"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'},
+ {"remove", no_argument, NULL, 'r'},
+ {"root", required_argument, NULL, 'R'},
+ {"prefix", required_argument, NULL, 'P'},
+ {"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,
+ "abc:d:e:f:g:G:hl:Lmop:rR:s:u:UP:"
+#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 'b':
+ allow_bad_names = 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;
+ if (user_newhome[0] != '/') {
+ fprintf (stderr,
+ _("%s: homedir must be an absolute path\n"),
+ Prog);
+ exit (E_BAD_ARG);
+ }
+ break;
+ case 'e':
+ 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;
+ 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;
+ gr_free (grp);
+ 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': use --badname to ignore\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':
+ rflg = true;
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ case 'P': /* no-op, handled in process_prefix_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);
+ }
+ if ( '\0' != optarg[0]
+ && '*' != optarg[0]
+ && strcmp(optarg, "/sbin/nologin") != 0
+ && ( stat(optarg, &st) != 0
+ || S_ISDIR(st.st_mode)
+ || access(optarg, X_OK) != 0)) {
+ fprintf (stderr,
+ _("%s: Warning: missing or non-executable shell '%s'\n"),
+ Prog, optarg);
+ }
+ 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 (prefix[0]) {
+ fprintf (stderr,
+ _("%s: -Z cannot be used with --prefix\n"),
+ Prog);
+ exit (E_BAD_ARG);
+ }
+ 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 = prefix_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;
+ }
+ if (prefix[0]) {
+ size_t len = strlen(prefix) + strlen(user_home) + 2;
+ int wlen;
+ prefix_user_home = xmalloc(len);
+ wlen = snprintf(prefix_user_home, len, "%s/%s", prefix, user_home);
+ assert (wlen == (int) len -1);
+ if (user_newhome) {
+ len = strlen(prefix) + strlen(user_newhome) + 2;
+ prefix_user_newhome = xmalloc(len);
+ wlen = snprintf(prefix_user_newhome, len, "%s/%s", prefix, user_newhome);
+ assert (wlen == (int) len -1);
+ }
+
+ }
+ else {
+ prefix_user_home = user_home;
+ prefix_user_newhome = user_newhome;
+ }
+
+#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 = prefix_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 (rflg && (!Gflg)) {
+ fprintf (stderr,
+ _("%s: %s flag is only allowed with the %s flag\n"),
+ Prog, "-r", "-G");
+ usage (E_USAGE);
+ }
+
+ if (rflg && aflg) {
+ fprintf (stderr,
+ _("%s: %s and %s are mutually exclusive flags\n"),
+ Prog, "-r", "-a");
+ 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 (stdout, _("%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 && (prefix_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 && (prefix_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 (prefix_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 (prefix_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 (prefix_user_home, prefix_user_newhome) == 0) {
+ /* FIXME: rename above may have broken symlinks
+ * pointing to the user's home directory
+ * with an absolute path. */
+ if (chown_tree (prefix_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) {
+#ifdef WITH_BTRFS
+ if (btrfs_is_subvolume (prefix_user_home) > 0) {
+ fprintf (stderr,
+ _("%s: error: cannot move subvolume from %s to %s - different device\n"),
+ Prog, prefix_user_home, prefix_user_newhome);
+ fail_exit (E_HOMEDIR);
+ }
+#endif
+
+ if (copy_tree (prefix_user_home, prefix_user_newhome, true,
+ true,
+ user_id,
+ uflg ? user_newid : (uid_t)-1,
+ user_gid,
+ gflg ? user_newgid : (gid_t)-1) == 0) {
+ if (remove_tree (prefix_user_home, true) != 0) {
+ fprintf (stderr,
+ _("%s: warning: failed to completely remove old home directory %s"),
+ Prog, prefix_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 (prefix_user_newhome, true);
+ }
+ fprintf (stderr,
+ _("%s: cannot rename directory %s to %s\n"),
+ Prog, prefix_user_home, prefix_user_newhome);
+ fail_exit (E_HOMEDIR);
+ }
+ } else {
+ fprintf (stderr,
+ _("%s: The previous home directory (%s) does not "
+ "exist or is inaccessible. Move cannot be completed.\n"),
+ Prog, prefix_user_home);
+ }
+}
+
+/*
+ * 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;
+ uid_t max_uid;
+
+ if (access (LASTLOG_FILE, F_OK) != 0) {
+ return;
+ }
+
+ max_uid = (uid_t) getdef_ulong ("LASTLOG_UID_MAX", 0xFFFFFFFFUL);
+ if (user_newid > max_uid) {
+ /* do not touch lastlog for large uids */
+ 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)) {
+ 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)) {
+ 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));
+ }
+ }
+ }
+
+ (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)) {
+ 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)) {
+ 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));
+ }
+ }
+ }
+
+ (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;
+ char* newmailfile;
+ int fd;
+ struct stat st;
+ size_t len;
+
+ 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;
+ }
+ len = strlen (prefix) + strlen (maildir) + strlen (user_name) + 2;
+ mailfile = alloca (len);
+
+ /*
+ * 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
+ */
+ if (prefix[0]) {
+ (void) snprintf (mailfile, len, "%s/%s/%s",
+ prefix, maildir, user_name);
+ }
+ else {
+ (void) snprintf (mailfile, len, "%s/%s",
+ maildir, user_name);
+ }
+ mailfile[len-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) {
+ len = strlen (prefix) + strlen (maildir) + strlen (user_newname) + 2;
+ newmailfile = alloca(len);
+ if (prefix[0]) {
+ (void) snprintf (newmailfile, len, "%s/%s/%s",
+ prefix, maildir, user_newname);
+ }
+ else {
+ (void) snprintf (newmailfile, len, "%s/%s",
+ maildir, user_newname);
+ }
+ newmailfile[len - 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]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+ prefix = process_prefix_flag ("-P", 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.
+ * Note: no need to check if a prefix is specified...
+ */
+ if ( (prefix[0] == '\0') && (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");
+ sssd_flush_cache (SSSD_DB_PASSWD | SSSD_DB_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)) {
+ struct stat sb;
+
+ if (stat (dflg ? prefix_user_newhome : prefix_user_home, &sb) == 0 &&
+ ((uflg && sb.st_uid == user_newid) || sb.st_uid == user_id)) {
+ /*
+ * 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 ? prefix_user_newhome : prefix_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..488a97d
--- /dev/null
+++ b/src/vipw.c
@@ -0,0 +1,599 @@
+/*
+ vipw, vigr edit the password or group file
+ with -s will edit shadow or gshadow file
+ *
+ * SPDX-FileCopyrightText: 1997 , Guy Maor <maor@ece.utexas.edu>
+ * SPDX-FileCopyrightText: 1999 - 2000, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2002 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2013, Nicolas François
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <errno.h>
+#include <getopt.h>
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#endif /* WITH_SELINUX */
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utime.h>
+#include "defines.h"
+#include "groupio.h"
+#include "nscd.h"
+#include "sssd.h"
+#include "prototypes.h"
+#include "pwio.h"
+#include "sgroupio.h"
+#include "shadowio.h"
+/*@-exitarg@*/
+#include "exitcodes.h"
+#ifdef WITH_TCB
+#include <tcb.h>
+#include "tcbfuncs.h"
+#endif /* WITH_TCB */
+#include "shadowlog.h"
+
+#define MSG_WARN_EDIT_OTHER_FILE _( \
+ "You have modified %s.\n"\
+ "You may need to modify %s for consistency.\n"\
+ "Please use the command '%s' to do so.\n")
+
+/*
+ * Global variables
+ */
+const char *Prog;
+
+static const char *filename, *fileeditname;
+static bool filelocked = false;
+static bool createedit = false;
+static int (*unlock) (void);
+static bool quiet = false;
+#ifdef WITH_TCB
+static const char *user = NULL;
+static bool tcb_mode = false;
+#define SHADOWTCB_SCRATCHDIR ":tmp"
+#endif /* WITH_TCB */
+
+/* local function prototypes */
+static void usage (int status);
+static int create_backup_file (FILE *, const char *, struct stat *);
+static void vipwexit (const char *msg, int syserr, int ret);
+static void vipwedit (const char *, int (*)(void), int (*)(void));
+
+/*
+ * usage - display usage message and exit
+ */
+static void usage (int status)
+{
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (stderr,
+ _("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -g, --group edit group database\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -p, --passwd edit passwd database\n"), usageout);
+ (void) fputs (_(" -q, --quiet quiet mode\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+ (void) fputs (_(" -s, --shadow edit shadow or gshadow database\n"), usageout);
+#ifdef WITH_TCB
+ (void) fputs (_(" -u, --user which user's tcb shadow file to edit\n"), usageout);
+#endif /* WITH_TCB */
+ (void) fputs (_("\n"), usageout);
+ exit (status);
+}
+
+/*
+ *
+ */
+static int create_backup_file (FILE * fp, const char *backup, struct stat *sb)
+{
+ struct utimbuf ub;
+ FILE *bkfp;
+ int c;
+ mode_t mask;
+
+ mask = umask (077);
+ bkfp = fopen (backup, "w");
+ (void) umask (mask);
+ if (NULL == bkfp) {
+ return -1;
+ }
+
+ c = 0;
+ if (fseeko (fp, 0, SEEK_SET) == 0)
+ while ((c = getc (fp)) != EOF) {
+ if (putc (c, bkfp) == EOF) {
+ break;
+ }
+ }
+ if ((EOF != c) || (ferror (fp) != 0) || (fflush (bkfp) != 0)) {
+ fclose (bkfp);
+ unlink (backup);
+ return -1;
+ }
+ if (fsync (fileno (bkfp)) != 0) {
+ (void) fclose (bkfp);
+ unlink (backup);
+ return -1;
+ }
+ if (fclose (bkfp) != 0) {
+ unlink (backup);
+ return -1;
+ }
+
+ ub.actime = sb->st_atime;
+ ub.modtime = sb->st_mtime;
+ if ( (utime (backup, &ub) != 0)
+ || (chmod (backup, sb->st_mode) != 0)
+ || (chown (backup, sb->st_uid, sb->st_gid) != 0)) {
+ unlink (backup);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ *
+ */
+static void vipwexit (const char *msg, int syserr, int ret)
+{
+ int err = errno;
+
+ if (createedit) {
+ if (unlink (fileeditname) != 0) {
+ fprintf (stderr, _("%s: failed to remove %s\n"), Prog, fileeditname);
+ /* continue */
+ }
+ }
+ if (filelocked) {
+ if ((*unlock) () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, fileeditname);
+ SYSLOG ((LOG_ERR, "failed to unlock %s", fileeditname));
+ /* continue */
+ }
+ }
+ if (NULL != msg) {
+ fprintf (stderr, "%s: %s", Prog, msg);
+ }
+ if (0 != syserr) {
+ fprintf (stderr, ": %s", strerror (err));
+ }
+ if ( (NULL != msg)
+ || (0 != syserr)) {
+ (void) fputs ("\n", stderr);
+ }
+ if (!quiet) {
+ fprintf (stdout, _("%s: %s is unchanged\n"), Prog,
+ filename);
+ }
+ exit (ret);
+}
+
+#ifndef DEFAULT_EDITOR
+#define DEFAULT_EDITOR "vi"
+#endif
+
+/*
+ *
+ */
+static void
+vipwedit (const char *file, int (*file_lock) (void), int (*file_unlock) (void))
+{
+ const char *editor;
+ pid_t pid;
+ struct stat st1, st2;
+ int status;
+ FILE *f;
+ pid_t orig_pgrp, editor_pgrp = -1;
+ sigset_t mask, omask;
+ /* FIXME: the following should have variable sizes */
+ char filebackup[1024], fileedit[1024];
+ char *to_rename;
+
+ snprintf (filebackup, sizeof filebackup, "%s-", file);
+#ifdef WITH_TCB
+ if (tcb_mode) {
+ if ( (mkdir (TCB_DIR "/" SHADOWTCB_SCRATCHDIR, 0700) != 0)
+ && (errno != EEXIST)) {
+ vipwexit (_("failed to create scratch directory"), errno, 1);
+ }
+ if (shadowtcb_drop_priv () == SHADOWTCB_FAILURE) {
+ vipwexit (_("failed to drop privileges"), errno, 1);
+ }
+ snprintf (fileedit, sizeof fileedit,
+ TCB_DIR "/" SHADOWTCB_SCRATCHDIR "/.vipw.shadow.%s",
+ user);
+ } else {
+#endif /* WITH_TCB */
+ snprintf (fileedit, sizeof fileedit, "%s.edit", file);
+#ifdef WITH_TCB
+ }
+#endif /* WITH_TCB */
+ unlock = file_unlock;
+ filename = file;
+ fileeditname = fileedit;
+
+ if (access (file, F_OK) != 0) {
+ vipwexit (file, 1, 1);
+ }
+#ifdef WITH_SELINUX
+ /* if SE Linux is enabled then set the context of all new files
+ to be the context of the file we are editing */
+ if (is_selinux_enabled () != 0) {
+ char *passwd_context_raw = NULL;
+ int ret = 0;
+ if (getfilecon_raw (file, &passwd_context_raw) < 0) {
+ vipwexit (_("Couldn't get file context"), errno, 1);
+ }
+ ret = setfscreatecon_raw (passwd_context_raw);
+ freecon (passwd_context_raw);
+ if (0 != ret) {
+ vipwexit (_("setfscreatecon () failed"), errno, 1);
+ }
+ }
+#endif /* WITH_SELINUX */
+#ifdef WITH_TCB
+ if (tcb_mode && (shadowtcb_gain_priv () == SHADOWTCB_FAILURE)) {
+ vipwexit (_("failed to gain privileges"), errno, 1);
+ }
+#endif /* WITH_TCB */
+ if (file_lock () == 0) {
+ vipwexit (_("Couldn't lock file"), errno, 5);
+ }
+ filelocked = true;
+#ifdef WITH_TCB
+ if (tcb_mode && (shadowtcb_drop_priv () == SHADOWTCB_FAILURE)) {
+ vipwexit (_("failed to drop privileges"), errno, 1);
+ }
+#endif /* WITH_TCB */
+
+ /* edited copy has same owners, perm */
+ if (stat (file, &st1) != 0) {
+ vipwexit (file, 1, 1);
+ }
+ f = fopen (file, "r");
+ if (NULL == f) {
+ vipwexit (file, 1, 1);
+ }
+#ifdef WITH_TCB
+ if (tcb_mode && (shadowtcb_gain_priv () == SHADOWTCB_FAILURE))
+ vipwexit (_("failed to gain privileges"), errno, 1);
+#endif /* WITH_TCB */
+ if (create_backup_file (f, fileedit, &st1) != 0) {
+ vipwexit (_("Couldn't make backup"), errno, 1);
+ }
+ (void) fclose (f);
+ createedit = true;
+
+ editor = getenv ("VISUAL");
+ if (NULL == editor) {
+ editor = getenv ("EDITOR");
+ }
+ if (NULL == editor) {
+ editor = DEFAULT_EDITOR;
+ }
+
+ orig_pgrp = tcgetpgrp(STDIN_FILENO);
+
+ pid = fork ();
+ if (-1 == pid) {
+ vipwexit ("fork", 1, 1);
+ } else if (0 == pid) {
+ /* use the system() call to invoke the editor so that it accepts
+ command line args in the EDITOR and VISUAL environment vars */
+ char *buf;
+
+ /* Wait for parent to make us the foreground pgrp. */
+ if (orig_pgrp != -1) {
+ pid = getpid();
+ setpgid(0, 0);
+ while (tcgetpgrp(STDIN_FILENO) != pid)
+ continue;
+ }
+
+ buf = (char *) malloc (strlen (editor) + strlen (fileedit) + 2);
+ snprintf (buf, strlen (editor) + strlen (fileedit) + 2,
+ "%s %s", editor, fileedit);
+ status = system (buf);
+ if (-1 == status) {
+ fprintf (stderr, _("%s: %s: %s\n"), Prog, editor,
+ strerror (errno));
+ exit (1);
+ } else if ( WIFEXITED (status)
+ && (WEXITSTATUS (status) != 0)) {
+ fprintf (stderr, _("%s: %s returned with status %d\n"),
+ Prog, editor, WEXITSTATUS (status));
+ exit (WEXITSTATUS (status));
+ } else if (WIFSIGNALED (status)) {
+ fprintf (stderr, _("%s: %s killed by signal %d\n"),
+ Prog, editor, WTERMSIG (status));
+ exit (1);
+ } else {
+ exit (0);
+ }
+ }
+
+ /* Run child in a new pgrp and make it the foreground pgrp. */
+ if (orig_pgrp != -1) {
+ setpgid(pid, pid);
+ tcsetpgrp(STDIN_FILENO, pid);
+
+ /* Avoid SIGTTOU when changing foreground pgrp below. */
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGTTOU);
+ sigprocmask(SIG_BLOCK, &mask, &omask);
+ }
+
+ /* set SIGCHLD to default for waitpid */
+ signal(SIGCHLD, SIG_DFL);
+
+ for (;;) {
+ pid = waitpid (pid, &status, WUNTRACED);
+ if ((pid != -1) && (WIFSTOPPED (status) != 0)) {
+ /* The child (editor) was suspended.
+ * Restore terminal pgrp and suspend vipw. */
+ if (orig_pgrp != -1) {
+ editor_pgrp = tcgetpgrp(STDIN_FILENO);
+ if (editor_pgrp == -1) {
+ fprintf (stderr, "%s: %s: %s", Prog,
+ "tcgetpgrp", strerror (errno));
+ }
+ if (tcsetpgrp(STDIN_FILENO, orig_pgrp) == -1) {
+ fprintf (stderr, "%s: %s: %s", Prog,
+ "tcsetpgrp", strerror (errno));
+ }
+ }
+ kill (getpid (), SIGSTOP);
+ /* wake child when resumed */
+ if (editor_pgrp != -1) {
+ if (tcsetpgrp(STDIN_FILENO, editor_pgrp) == -1) {
+ fprintf (stderr, "%s: %s: %s", Prog,
+ "tcsetpgrp", strerror (errno));
+ }
+ }
+ killpg (pid, SIGCONT);
+ } else {
+ break;
+ }
+ }
+
+ if (orig_pgrp != -1)
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+
+ if (-1 == pid) {
+ vipwexit (editor, 1, 1);
+ } else if ( WIFEXITED (status)
+ && (WEXITSTATUS (status) != 0)) {
+ vipwexit (NULL, 0, WEXITSTATUS (status));
+ } else if (WIFSIGNALED (status)) {
+ fprintf (stderr, _("%s: %s killed by signal %d\n"),
+ Prog, editor, WTERMSIG(status));
+ vipwexit (NULL, 0, 1);
+ }
+
+ if (stat (fileedit, &st2) != 0) {
+ vipwexit (fileedit, 1, 1);
+ }
+ if (st1.st_mtime == st2.st_mtime) {
+ vipwexit (0, 0, 0);
+ }
+#ifdef WITH_SELINUX
+ /* unset the fscreatecon */
+ if (is_selinux_enabled () != 0) {
+ if (setfscreatecon_raw (NULL) != 0) {
+ vipwexit (_("setfscreatecon () failed"), errno, 1);
+ }
+ }
+#endif /* WITH_SELINUX */
+
+ /*
+ * XXX - here we should check fileedit for errors; if there are any,
+ * ask the user what to do (edit again, save changes anyway, or quit
+ * without saving). Use pwck or grpck to do the check. --marekm
+ */
+ createedit = false;
+#ifdef WITH_TCB
+ if (tcb_mode) {
+ f = fopen (fileedit, "r");
+ if (NULL == f) {
+ vipwexit (_("failed to open scratch file"), errno, 1);
+ }
+ if (unlink (fileedit) != 0) {
+ vipwexit (_("failed to unlink scratch file"), errno, 1);
+ }
+ if (shadowtcb_drop_priv () == SHADOWTCB_FAILURE) {
+ vipwexit (_("failed to drop privileges"), errno, 1);
+ }
+ if (stat (file, &st1) != 0) {
+ vipwexit (_("failed to stat edited file"), errno, 1);
+ }
+ to_rename = malloc (strlen (file) + 2);
+ if (NULL == to_rename) {
+ vipwexit (_("failed to allocate memory"), errno, 1);
+ }
+ snprintf (to_rename, strlen (file) + 2, "%s+", file);
+ if (create_backup_file (f, to_rename, &st1) != 0) {
+ free (to_rename);
+ vipwexit (_("failed to create backup file"), errno, 1);
+ }
+ (void) fclose (f);
+ } else {
+#endif /* WITH_TCB */
+ to_rename = fileedit;
+#ifdef WITH_TCB
+ }
+#endif /* WITH_TCB */
+ unlink (filebackup);
+ link (file, filebackup);
+ if (rename (to_rename, file) == -1) {
+ fprintf (stderr,
+ _("%s: can't restore %s: %s (your changes are in %s)\n"),
+ Prog, file, strerror (errno), to_rename);
+#ifdef WITH_TCB
+ if (tcb_mode) {
+ free (to_rename);
+ }
+#endif /* WITH_TCB */
+ vipwexit (0, 0, 1);
+ }
+
+#ifdef WITH_TCB
+ if (tcb_mode) {
+ free (to_rename);
+ if (shadowtcb_gain_priv () == SHADOWTCB_FAILURE) {
+ vipwexit (_("failed to gain privileges"), errno, 1);
+ }
+ }
+#endif /* WITH_TCB */
+
+ if ((*file_unlock) () == 0) {
+ fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, fileeditname);
+ SYSLOG ((LOG_ERR, "failed to unlock %s", fileeditname));
+ /* continue */
+ }
+ SYSLOG ((LOG_INFO, "file %s edited", fileeditname));
+}
+
+int main (int argc, char **argv)
+{
+ bool editshadow = false;
+ bool do_vipw;
+
+ Prog = Basename (argv[0]);
+ log_set_progname(Prog);
+ log_set_logfd(stderr);
+
+ (void) setlocale (LC_ALL, "");
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ process_root_flag ("-R", argc, argv);
+
+ do_vipw = (strcmp (Prog, "vigr") != 0);
+
+ OPENLOG (do_vipw ? "vipw" : "vigr");
+
+ {
+ /*
+ * Parse the command line options.
+ */
+ int c;
+ static struct option long_options[] = {
+ {"group", no_argument, NULL, 'g'},
+ {"help", no_argument, NULL, 'h'},
+ {"passwd", no_argument, NULL, 'p'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"root", required_argument, NULL, 'R'},
+ {"shadow", no_argument, NULL, 's'},
+#ifdef WITH_TCB
+ {"user", required_argument, NULL, 'u'},
+#endif /* WITH_TCB */
+ {NULL, 0, NULL, '\0'}
+ };
+ while ((c = getopt_long (argc, argv,
+#ifdef WITH_TCB
+ "ghpqR:su:",
+#else /* !WITH_TCB */
+ "ghpqR:s",
+#endif /* !WITH_TCB */
+ long_options, NULL)) != -1) {
+ switch (c) {
+ case 'g':
+ do_vipw = false;
+ break;
+ case 'h':
+ usage (E_SUCCESS);
+ break;
+ case 'p':
+ do_vipw = true;
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
+ case 's':
+ editshadow = true;
+ break;
+#ifdef WITH_TCB
+ case 'u':
+ user = optarg;
+ break;
+#endif /* WITH_TCB */
+ default:
+ usage (E_USAGE);
+ }
+ }
+
+ if (optind != argc) {
+ usage (E_USAGE);
+ }
+ }
+
+ if (do_vipw) {
+ if (editshadow) {
+#ifdef WITH_TCB
+ if (getdef_bool ("USE_TCB") && (NULL != user)) {
+ if (shadowtcb_set_user (user) == SHADOWTCB_FAILURE) {
+ fprintf (stderr,
+ _("%s: failed to find tcb directory for %s\n"),
+ Prog, user);
+ return E_SHADOW_NOTFOUND;
+ }
+ tcb_mode = true;
+ }
+#endif /* WITH_TCB */
+ vipwedit (spw_dbname (), spw_lock, spw_unlock);
+ printf (MSG_WARN_EDIT_OTHER_FILE,
+ spw_dbname (),
+ pw_dbname (),
+ "vipw");
+ } else {
+ vipwedit (pw_dbname (), pw_lock, pw_unlock);
+ if (spw_file_present ()) {
+ printf (MSG_WARN_EDIT_OTHER_FILE,
+ pw_dbname (),
+ spw_dbname (),
+ "vipw -s");
+ }
+ }
+ } else {
+#ifdef SHADOWGRP
+ if (editshadow) {
+ vipwedit (sgr_dbname (), sgr_lock, sgr_unlock);
+ printf (MSG_WARN_EDIT_OTHER_FILE,
+ sgr_dbname (),
+ gr_dbname (),
+ "vigr");
+ } else {
+#endif /* SHADOWGRP */
+ vipwedit (gr_dbname (), gr_lock, gr_unlock);
+#ifdef SHADOWGRP
+ if (sgr_file_present ()) {
+ printf (MSG_WARN_EDIT_OTHER_FILE,
+ gr_dbname (),
+ sgr_dbname (),
+ "vigr -s");
+ }
+ }
+#endif /* SHADOWGRP */
+ }
+
+ nscd_flush_cache ("passwd");
+ nscd_flush_cache ("group");
+ sssd_flush_cache (SSSD_DB_PASSWD | SSSD_DB_GROUP);
+
+ return E_SUCCESS;
+}
+