summaryrefslogtreecommitdiffstats
path: root/contrib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:54:37 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:54:37 +0000
commit97c26c1924b076ef23ebe4381558e8aa025712b2 (patch)
tree109724175f07436696f51b14b5abbd3f4d704d6d /contrib
parentInitial commit. (diff)
downloadshadow-upstream/1%4.13+dfsg1.tar.xz
shadow-upstream/1%4.13+dfsg1.zip
Adding upstream version 1:4.13+dfsg1.upstream/1%4.13+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'contrib')
-rw-r--r--contrib/Makefile.am6
-rw-r--r--contrib/Makefile.in496
-rw-r--r--contrib/README10
-rw-r--r--contrib/adduser.c502
-rwxr-xr-xcontrib/adduser.sh90
-rwxr-xr-xcontrib/adduser2.sh743
-rw-r--r--contrib/groupmems.shar465
-rw-r--r--contrib/pwdauth.c308
-rw-r--r--contrib/shadow-anonftp.patch147
-rw-r--r--contrib/udbachk.tgzbin0 -> 17571 bytes
10 files changed, 2767 insertions, 0 deletions
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
new file mode 100644
index 0000000..5c45cb7
--- /dev/null
+++ b/contrib/Makefile.am
@@ -0,0 +1,6 @@
+# This is a dummy Makefile.am to get automake work flawlessly,
+# and also cooperate to make a distribution for `make dist'
+
+EXTRA_DIST = README adduser.c adduser.sh adduser2.sh \
+ atudel groupmems.shar pwdauth.c shadow-anonftp.patch \
+ udbachk.tgz
diff --git a/contrib/Makefile.in b/contrib/Makefile.in
new file mode 100644
index 0000000..84f3e30
--- /dev/null
+++ b/contrib/Makefile.in
@@ -0,0 +1,496 @@
+# 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@
+
+# This is a dummy Makefile.am to get automake work flawlessly,
+# and also cooperate to make a distribution for `make dist'
+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@
+subdir = contrib
+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 =
+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 =
+SOURCES =
+DIST_SOURCES =
+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)
+am__DIST_COMMON = $(srcdir)/Makefile.in README
+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 = README adduser.c adduser.sh adduser2.sh \
+ atudel groupmems.shar pwdauth.c shadow-anonftp.patch \
+ udbachk.tgz
+
+all: all-am
+
+.SUFFIXES:
+$(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 contrib/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign contrib/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):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+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
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-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-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+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 Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags-am distclean distclean-generic \
+ distclean-libtool distdir dvi dvi-am html html-am info info-am \
+ install install-am 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-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# 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/contrib/README b/contrib/README
new file mode 100644
index 0000000..c4d1bc0
--- /dev/null
+++ b/contrib/README
@@ -0,0 +1,10 @@
+People keep sending various adduser programs and scripts... They are
+all in this directory. I haven't tested them, use at your own risk.
+Anyway, the best one I've seen so far is adduser-3.x from Debian.
+
+atudel is a perl script to remove at jobs owned by the specified user
+(atrm in at-2.9 for Linux can't do that).
+
+udbachk.tgz is a passwd/group/shadow file integrity checker.
+
+--marekm
diff --git a/contrib/adduser.c b/contrib/adduser.c
new file mode 100644
index 0000000..deebd4c
--- /dev/null
+++ b/contrib/adduser.c
@@ -0,0 +1,502 @@
+/****
+** 04/21/96
+** hacked even more, replaced gets() with something slightly harder to buffer
+** overflow. Added support for setting a default quota on new account, with
+** edquota -p. Other cleanups for security, I let some users run adduser suid
+** root to add new accounts. (overflow checks, clobber environment, valid
+** shell checks, restrictions on gid + home dir settings).
+
+** Added max. username length. Used syslog() a bit for important events.
+** Support to immediately expire account with passwd -e.
+
+** Called it version 2.0! Because I felt like it!
+
+** -- Chris, chris@ferret.lmh.ox.ac.uk
+
+** 03/17/96
+** hacked a bit more, removed unused code, cleaned up for gcc -Wall.
+** --marekm
+**
+** 02/26/96
+** modified to call shadow utils (useradd,chage,passwd) on shadowed
+** systems - Cristian Gafton, gafton@sorosis.ro
+**
+** 6/27/95
+** shadow-adduser 1.4:
+**
+** now it copies the /etc/skel dir into the person's dir,
+** makes the mail folders, changed some defaults and made a 'make
+** install' just for the hell of it.
+**
+** Greg Gallagher
+** CIN.Net
+**
+** 1/28/95
+** shadow-adduser 1.3:
+**
+** Basically a bug-fix on my additions in 1.2. Thanks to Terry Stewart
+** (stew@texas.net) for pointing out one of the many idiotic bugs I introduced.
+** It was such a stupid bug that I would have never seen it myself.
+**
+** Brandon
+*****
+** 01/27/95
+**
+** shadow-adduser 1.2:
+** I took the C source from adduser-shadow (credits are below) and made
+** it a little more worthwhile. Many small changes... Here's
+** the ones I can remember:
+**
+** Removed support for non-shadowed systems (if you don't have shadow,
+** use the original adduser, don't get this shadow version!)
+** Added support for the correct /etc/shadow fields (Min days before
+** password change, max days before password change, Warning days,
+** and how many days from expiry date does the account go invalid)
+** The previous version just left all of those fields blank.
+** There is still one field left (expiry date for the account, period)
+** which I have left blank because I do not use it and didn't want to
+** spend any more time on this. I'm sure someone will put it in and
+** tack another plethora of credits on here. :)
+** Added in the password date field, which should always reflect the last
+** date the password was changed, for expiry purposes. "passwd" always
+** updates this field, so the adduser program should set it up right
+** initially (or a user could keep thier initial password forever ;)
+** The number is in days since Jan 1st, 1970.
+**
+** Have fun with it, and someone please make
+** a real version(this is still just a hack)
+** for us all to use (and Email it to me???)
+**
+** Brandon
+** photon@usis.com
+**
+*****
+** adduser 1.0: add a new user account (For systems not using shadow)
+** With a nice little interface and a will to do all the work for you.
+**
+** Craig Hagan
+** hagan@opine.cs.umass.edu
+**
+** Modified to really work, look clean, and find unused uid by Chris Cappuccio
+** chris@slinky.cs.umass.edu
+**
+*****
+**
+** 01/19/95
+**
+** FURTHER modifications to enable shadow passwd support (kludged, but
+** no more so than the original) by Dan Crowson - dcrowson@mo.net
+**
+** Search on DAN for all changes...
+**
+*****
+**
+** cc -O -o adduser adduser.c
+** Use gcc if you have it... (political reasons beyond my control) (chris)
+**
+** I've gotten this program to work with success under Linux (without
+** shadow) and SunOS 4.1.3. I would assume it should work pretty well
+** on any system that uses no shadow. (chris)
+**
+** If you have no crypt() then try
+** cc -DNO_CRYPT -O -o adduser adduser.c xfdes.c
+** I'm not sure how login operates with no crypt()... I guess
+** the same way we're doing it here.
+*/
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <grp.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/timeb.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <syslog.h>
+
+#define IMMEDIATE_CHANGE /* Expire newly created password, must be changed
+ * immediately upon next login */
+#define HAVE_QUOTAS /* Obvious */
+#define EXPIRE_VALS_SET /* If defined, 'normal' users can't change
+ * password expiry values (if running suid root) */
+
+#define HAVE_GETUSERSHELL /* FIXME: Isn't this defined in config.h too? */
+#define LOGGING /* If we want to log various things to syslog */
+#define MAX_USRNAME 8 /* Longer usernames seem to work on my system....
+ * But they're probably a poor idea */
+
+
+#define DEFAULT_SHELL "/bin/bash" /* because BASH is your friend */
+#define DEFAULT_HOME "/home"
+#define USERADD_PATH "/usr/sbin/useradd"
+#define CHAGE_PATH "/usr/bin/chage"
+#define PASSWD_PATH "/usr/bin/passwd"
+#define EDQUOTA_PATH "/usr/sbin/edquota"
+#define QUOTA_DEFAULT "defuser"
+#define DEFAULT_GROUP 100
+
+#define DEFAULT_MIN_PASS 0
+#define DEFAULT_MAX_PASS 100
+#define DEFAULT_WARN_PASS 14
+#define DEFAULT_USER_DIE 366
+
+void safeget (char *, int);
+
+void
+main (void)
+{
+ char foo[32];
+ char usrname[32], person[32], dir[32], shell[32];
+ unsigned int group, min_pass, max_pass, warn_pass, user_die;
+ /* the group and uid of the new user */
+ int bad = 0, done = 0, correct = 0, olduid;
+ char cmd[255];
+ struct group *grp;
+
+ /* flags, in order:
+ * bad to see if the username is in /etc/passwd, or if strange stuff has
+ * been typed if the user might be put in group 0
+ * done allows the program to exit when a user has been added
+ * correct loops until a username is found that isn't in /etc/passwd
+ */
+
+ /* The real program starts HERE! */
+
+ if (geteuid () != 0)
+ {
+ printf ("It seems you don't have access to add a new user. Try\n");
+ printf ("logging in as root or su root to gain superuser access.\n");
+ exit (1);
+ }
+
+ /* Sanity checks
+ */
+
+#ifdef LOGGING
+ openlog ("adduser", LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH);
+ syslog (LOG_INFO, "invoked by user %s\n", getpwuid (getuid ())->pw_name);
+#endif
+
+ if (!(grp = getgrgid (DEFAULT_GROUP)))
+ {
+ printf ("Error: the default group %d does not exist on this system!\n",
+ DEFAULT_GROUP);
+ printf ("adduser must be recompiled.\n");
+#ifdef LOGGING
+ syslog (LOG_ERR, "warning: failed. no such default group\n");
+ closelog ();
+#endif
+ exit (1);
+ };
+
+ while (!correct)
+ { /* loop until a "good" usrname is chosen */
+ while (!done)
+ {
+ printf ("\nLogin to add (^C to quit): ");
+ fflush (stdout);
+
+ safeget (usrname, sizeof (usrname));
+
+ if (!strlen (usrname))
+ {
+ printf ("Empty input.\n");
+ done = 0;
+ continue;
+ };
+
+ /* what I saw here before made me think maybe I was running DOS */
+ /* might this be a solution? (chris) */
+ if (strlen (usrname) > MAX_USRNAME)
+ {
+ printf ("That name is longer than the maximum of %d characters. Choose another.\n", MAX_USRNAME);
+ done = 0;
+ }
+ else if (getpwnam (usrname) != NULL)
+ {
+ printf ("That name is in use, choose another.\n");
+ done = 0;
+ }
+ else if (strchr (usrname, ' ') != NULL)
+ {
+ printf ("No spaces in username!!\n");
+ done = 0;
+ }
+ else
+ done = 1;
+ }; /* done, we have a valid new user name */
+
+ /* all set, get the rest of the stuff */
+ printf ("\nEditing information for new user [%s]\n", usrname);
+
+ printf ("\nFull Name [%s]: ", usrname);
+ fflush (stdout);
+ safeget (person, sizeof (person));
+ if (!strlen (person))
+ {
+ bzero (person, sizeof (person));
+ strcpy (person, usrname);
+ };
+
+ if (getuid () == 0)
+ {
+ do
+ {
+ bad = 0;
+ printf ("GID [%d]: ", DEFAULT_GROUP);
+ fflush (stdout);
+ safeget (foo, sizeof (foo));
+ if (!strlen (foo))
+ group = DEFAULT_GROUP;
+ else if (isdigit (*foo))
+ {
+ group = atoi (foo);
+ if (!(grp = getgrgid (group)))
+ {
+ printf ("unknown gid %s\n", foo);
+ group = DEFAULT_GROUP;
+ bad = 1;
+ };
+ }
+ else if ((grp = getgrnam (foo)))
+ group = grp->gr_gid;
+ else
+ {
+ printf ("unknown group %s\n", foo);
+ group = DEFAULT_GROUP;
+ bad = 1;
+ }
+ if (group == 0)
+ { /* You're not allowed to make root group users! */
+ printf ("Creation of root group users not allowed (must be done by hand)\n");
+ group = DEFAULT_GROUP;
+ bad = 1;
+ };
+ }
+ while (bad);
+ }
+ else
+ {
+ printf ("Group will be default of: %d\n", DEFAULT_GROUP);
+ group = DEFAULT_GROUP;
+ }
+
+ if (getuid () == 0)
+ {
+ printf ("\nIf home dir ends with a / then '%s' will be appended to it\n", usrname);
+ printf ("Home Directory [%s/%s]: ", DEFAULT_HOME, usrname);
+ fflush (stdout);
+ safeget (dir, sizeof (dir));
+ if (!strlen (dir))
+ { /* hit return */
+ sprintf (dir, "%s/%s", DEFAULT_HOME, usrname);
+ }
+ else if (dir[strlen (dir) - 1] == '/')
+ sprintf (dir+strlen(dir), "%s", usrname);
+ }
+ else
+ {
+ printf ("\nHome directory will be %s/%s\n", DEFAULT_HOME, usrname);
+ sprintf (dir, "%s/%s", DEFAULT_HOME, usrname);
+ }
+
+ printf ("\nShell [%s]: ", DEFAULT_SHELL);
+ fflush (stdout);
+ safeget (shell, sizeof (shell));
+ if (!strlen (shell))
+ sprintf (shell, "%s", DEFAULT_SHELL);
+ else
+ {
+ char *sh;
+ int ok = 0;
+#ifdef HAVE_GETUSERSHELL
+ setusershell ();
+ while ((sh = getusershell ()) != NULL)
+ if (!strcmp (shell, sh))
+ ok = 1;
+ endusershell ();
+#endif
+ if (!ok)
+ {
+ if (getuid () == 0)
+ printf ("Warning: root allowed non standard shell\n");
+ else
+ {
+ printf ("Shell NOT in /etc/shells, DEFAULT used\n");
+ sprintf (shell, "%s", DEFAULT_SHELL);
+ }
+ }
+ }
+
+#ifdef EXPIRE_VALS_SET
+ if (getuid () == 0)
+ {
+#endif
+ printf ("\nMin. Password Change Days [%d]: ", DEFAULT_MIN_PASS);
+ fflush (stdout);
+ safeget (foo, sizeof (foo));
+ if (strlen (foo) > 1)
+ min_pass = DEFAULT_MIN_PASS;
+ else
+ min_pass = atoi (foo);
+
+ printf ("Max. Password Change Days [%d]: ", DEFAULT_MAX_PASS);
+ fflush (stdout);
+ safeget (foo, sizeof (foo));
+ if (strlen (foo) > 1)
+ max_pass = atoi (foo);
+ else
+ max_pass = DEFAULT_MAX_PASS;
+
+ printf ("Password Warning Days [%d]: ", DEFAULT_WARN_PASS);
+ fflush (stdout);
+ safeget (foo, sizeof (foo));
+ warn_pass = atoi (foo);
+ if (warn_pass == 0)
+
+ warn_pass = DEFAULT_WARN_PASS;
+
+ printf ("Days after Password Expiry for Account Locking [%d]: ", DEFAULT_USER_DIE);
+ fflush (stdout);
+ safeget (foo, sizeof (foo));
+ user_die = atoi (foo);
+ if (user_die == 0)
+ user_die = DEFAULT_USER_DIE;
+
+#ifdef EXPIRE_VALS_SET
+ }
+ else
+ {
+ printf ("\nSorry, account expiry values are set.\n");
+ user_die = DEFAULT_USER_DIE;
+ warn_pass = DEFAULT_WARN_PASS;
+ max_pass = DEFAULT_MAX_PASS;
+ min_pass = DEFAULT_MIN_PASS;
+ }
+#endif
+
+ printf ("\nInformation for new user [%s] [%s]:\n", usrname, person);
+ printf ("Home directory: [%s] Shell: [%s]\n", dir, shell);
+ printf ("GID: [%d]\n", group);
+ printf ("MinPass: [%d] MaxPass: [%d] WarnPass: [%d] UserExpire: [%d]\n",
+ min_pass, max_pass, warn_pass, user_die);
+ printf ("\nIs this correct? [y/N]: ");
+ fflush (stdout);
+ safeget (foo, sizeof (foo));
+
+ done = bad = correct = (foo[0] == 'y' || foo[0] == 'Y');
+
+ if (bad != 1)
+ printf ("\nUser [%s] not added\n", usrname);
+ }
+
+ /* Clobber the environment, I run this suid root sometimes to let
+ * non root privileged accounts add users --chris */
+
+ *environ = NULL;
+
+ bzero (cmd, sizeof (cmd));
+ sprintf (cmd, "%s -g %d -d %s -s %s -c \"%s\" -m -k /etc/skel %s",
+ USERADD_PATH, group, dir, shell, person, usrname);
+ printf ("Calling useradd to add new user:\n%s\n", cmd);
+ if (system (cmd))
+ {
+ printf ("User add failed!\n");
+#ifdef LOGGING
+ syslog (LOG_ERR, "could not add new user\n");
+ closelog ();
+#endif
+ exit (errno);
+ };
+
+ olduid = getuid (); /* chage, passwd, edquota etc. require ruid = root
+ */
+ setuid (0);
+
+ bzero (cmd, sizeof (cmd));
+
+ /* Chage runs suid root. => we need ruid root to run it with
+ * anything other than chage -l
+ */
+
+ sprintf (cmd, "%s -m %d -M %d -W %d -I %d %s", CHAGE_PATH,
+ min_pass, max_pass, warn_pass, user_die, usrname);
+ printf ("%s\n", cmd);
+ if (system (cmd))
+ {
+ printf ("There was an error setting password expire values\n");
+#ifdef LOGGING
+ syslog (LOG_ERR, "password expire values could not be set\n");
+#endif
+ };
+
+ /* I want to add a user completely with one easy command --chris */
+
+#ifdef HAVE_QUOTAS
+ bzero (cmd, sizeof (cmd));
+ sprintf (cmd, "%s -p %s -u %s", EDQUOTA_PATH, QUOTA_DEFAULT, usrname);
+ printf ("%s\n", cmd);
+ if (system (cmd))
+ {
+ printf ("\nWarning: error setting quota\n");
+#ifdef LOGGING
+ syslog (LOG_ERR, "warning: account created but NO quotas set!\n");
+#endif /* LOGGING */
+ }
+ else
+ printf ("\nDefault quota set.\n");
+#endif /* HAVE_QUOTAS */
+
+ bzero (cmd, sizeof (cmd));
+ sprintf (cmd, "%s %s", PASSWD_PATH, usrname);
+ if (system (cmd))
+ {
+ printf ("\nWarning: error setting password\n");
+#ifdef LOGGING
+ syslog (LOG_ERR, "warning: password set failed!\n");
+#endif
+ }
+#ifdef IMMEDIATE_CHANGE
+ bzero (cmd, sizeof (cmd));
+ sprintf (cmd, "%s -e %s", PASSWD_PATH, usrname);
+ if (system (cmd))
+ {
+ printf ("\nWarning: error expiring password\n");
+#ifdef LOGGING
+ syslog (LOG_ERR, "warning: password expire failed!\n");
+#endif /* LOGGING */
+ }
+#endif /* IMMEDIATE_CHANGE */
+
+ setuid (olduid);
+
+#ifdef LOGGING
+ closelog ();
+#endif
+
+ printf ("\nDone.\n");
+}
+
+void
+safeget (char *buf, int maxlen)
+{
+ int c, i = 0, bad = 0;
+ char *bstart = buf;
+ while ((c = getc (stdin)) != EOF && (c != '\n') && (++i < maxlen))
+ {
+ bad = (!isalnum (c) && (c != '_') && (c != ' '));
+ *(buf++) = (char) c;
+ }
+ *buf = '\0';
+
+ if (bad)
+ {
+ printf ("\nString contained banned character. Please stick to alphanumerics.\n");
+ *bstart = '\0';
+ }
+}
+
diff --git a/contrib/adduser.sh b/contrib/adduser.sh
new file mode 100755
index 0000000..0efb27a
--- /dev/null
+++ b/contrib/adduser.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+# adduser script for use with shadow passwords and useradd command.
+# by Hrvoje Dogan <hdogan@student.math.hr>, Dec 1995.
+
+echo -n "Login name for new user []:"
+read LOGIN
+if [ -z $LOGIN ]
+then echo "Come on, man, you can't leave the login field empty...";exit
+fi
+echo
+echo -n "User id for $LOGIN [ defaults to next available]:"
+read ID
+GUID="-u $ID"
+if [ -z $ID ]
+then GUID=""
+fi
+
+echo
+echo -n "Initial group for $LOGIN [users]:"
+read GID
+GGID="-g $GID"
+if [ -z $GID ]
+then GGID=""
+fi
+
+echo
+echo -n "Additional groups for $LOGIN []:"
+read AGID
+GAGID="-G $AGID"
+if [ -z $AGID ]
+then GAGID=""
+fi
+
+echo
+echo -n "$LOGIN's home directory [/home/$LOGIN]:"
+read HME
+GHME="-d $HME"
+if [ -z $HME ]
+then GHME=""
+fi
+
+echo
+echo -n "$LOGIN's shell [/bin/bash]:"
+read SHL
+GSHL="-s $SHL"
+if [ -z $SHL ]
+then GSHL=""
+fi
+
+echo
+echo -n "$LOGIN's account expiry date (MM/DD/YY) []:"
+read EXP
+GEXP="-e $EXP"
+if [ -z $EXP ]
+then GEXP=""
+fi
+echo
+echo OK, I'm about to make a new account. Here's what you entered so far:
+echo New login name: $LOGIN
+if [ -z $GUID ]
+then echo New UID: [Next available]
+else echo New UID: $UID
+fi
+if [ -z $GGID ]
+then echo Initial group: users
+else echo Initial group: $GID
+fi
+if [ -z $GAGID ]
+then echo Additional groups: [none]
+else echo Additional groups: $AGID
+fi
+if [ -z $GHME ]
+then echo Home directory: /home/$LOGIN
+else echo Home directory: $HME
+fi
+if [ -z $GSHL ]
+then echo Shell: /bin/bash
+else echo Shell: $SHL
+fi
+if [ -z $GEXP ]
+then echo Expiry date: [no expiration]
+else echo Expiry date: $EXP
+fi
+echo "This is it... if you want to bail out, you'd better do it now."
+read FOO
+echo Making new account...
+/usr/sbin/useradd $GHME -m $GEXP $GGID $GAGID $GSHL $GUID $LOGIN
+/usr/bin/chfn $LOGIN
+/usr/bin/passwd $LOGIN
+echo "Done..."
diff --git a/contrib/adduser2.sh b/contrib/adduser2.sh
new file mode 100755
index 0000000..7656d02
--- /dev/null
+++ b/contrib/adduser2.sh
@@ -0,0 +1,743 @@
+#!/bin/bash
+#
+# adduser Interactive user adding program.
+#
+# Copyright (C) 1996 Petri Mattila, Prihateam Networks
+# petri@prihateam.fi
+#
+# 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.
+#
+# Changes:
+# 220496 v0.01 Initial version
+# 230496 v0.02 More checks, embolden summary
+# 240496 Even more checks
+# 250496 Help with ?
+# 040596 v0.03 Cleanups
+# 050596 v0.04 Bug fixes, expire date checks
+# 070596 v0.05 Iso-latin-1 names
+#
+
+## Defaults
+
+# default groups
+def_group="users"
+def_other_groups=""
+
+# default home directory
+def_home_dir=/home/users
+
+# default shell
+def_shell=/bin/tcsh
+
+# Default expiration date (mm/dd/yy)
+def_expire=""
+
+# default dates
+def_pwd_min=0
+def_pwd_max=90
+def_pwd_warn=14
+def_pwd_iact=14
+
+
+# possible UIDs
+uid_low=1000
+uid_high=64000
+
+# skel directory
+skel=/etc/skel
+
+# default mode for home directory
+def_mode=711
+
+# Regex, that the login name must meet, only ANSI characters
+login_regex='^[0-9a-zA-Z_-]*$'
+
+# Regex, that the user name must meet
+# ANSI version
+##name_regex='^[0-9a-zA-Z_-\ ]*$'
+# ISO-LATIN-1 version
+name_regex='^[0-9a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöùúûüýþÿ_-\ ]*$'
+
+# set PATH
+export PATH="/bin:/sbin:/usr/bin:/usr/sbin"
+
+# Some special characters
+case "$TERM" in
+ vt*|ansi*|con*|xterm*|linux*)
+ S='' # start embolden
+ E='' # end embolden
+ ;;
+ *)
+ S=''
+ E=''
+ ;;
+esac
+
+
+## Functions
+
+check_root() {
+ if test "$EUID" -ne 0
+ then
+ echo "You must be root to run this program."
+ exit 1
+ fi
+}
+
+check_user() {
+ local usr pwd uid gid name home sh
+
+ cat /etc/passwd | (
+ while IFS=":" read usr pwd uid gid name home sh
+ do
+ if test "$1" = "${usr}"
+ then
+ return 1
+ fi
+ done
+ return 0
+ )
+}
+
+check_group() {
+ local read grp pwd gid members
+
+ cat /etc/group | (
+ while IFS=":" read grp pwd gid members
+ do
+ if test "$1" = "${grp}"
+ then
+ return 1
+ fi
+ done
+ return 0
+ )
+}
+
+check_other_groups() {
+ local grp check IFS
+
+ check="$1"
+ IFS=","
+
+ set ${check}
+ for grp
+ do
+ if check_group "${grp}"
+ then
+ echo "Group ${grp} does not exist."
+ return 1
+ fi
+ done
+ return 0
+}
+
+check_uid() {
+ local usr pwd uid gid name home sh
+
+ cat /etc/passwd | (
+ while IFS=":" read usr pwd uid gid name home sh
+ do
+ if test "$1" = "${uid}"
+ then
+ return 1
+ fi
+ done
+ return 0
+ )
+}
+
+read_yn() {
+ local ans ynd
+
+ ynd="$1"
+
+ while :
+ do
+ read ans
+ case "${ans}" in
+ "") return ${ynd} ;;
+ [nN]) return 1 ;;
+ [yY]) return 0 ;;
+ *) echo -n "Y or N, please ? " ;;
+ esac
+ done
+}
+
+read_login() {
+ echo
+ while :
+ do
+ echo -n "Login: ${def_login:+[${def_login}] }"
+ read login
+
+ if test "${login}" = '?'
+ then
+ less /etc/passwd
+ echo
+ continue
+ fi
+
+ if test -z "${login}" -a -n "${def_login}"
+ then
+ login="${def_login}"
+ echo "Using ${login}"
+ return
+ fi
+
+ if test "${#login}" -gt 8
+ then
+ echo "Login must be at most 8 characters long"
+ continue
+ fi
+
+ if test "${#login}" -lt 2
+ then
+ echo "Login must be at least 2 characters long"
+ continue
+ fi
+
+ if ! expr "${login}" : "${login_regex}" &> /dev/null
+ then
+ echo "Please use letters, numbers and special characters _-,."
+ continue
+ fi
+
+ if ! check_user "${login}"
+ then
+ echo "Username ${login} is already in use"
+ continue
+ fi
+
+ def_login="${login}"
+ return
+ done
+}
+
+read_name () {
+ echo
+ while :
+ do
+ echo -n "Real name: ${def_name:+[${def_name}] }"
+ read name
+
+ if test "${name}" = '?'
+ then
+ less /etc/passwd
+ echo
+ continue
+ fi
+
+ if test -z "${name}" -a -n "${def_name}"
+ then
+ name="${def_name}"
+ echo "Using ${name}"
+ fi
+
+ if test "${#name}" -gt 32
+ then
+ echo "Name should be at most 32 characters long"
+ continue
+ fi
+
+ if ! expr "${name}" : "${name_regex}" &> /dev/null
+ then
+ echo "Please use letters, numbers, spaces and special characters ,._-"
+ continue
+ fi
+
+ def_name="${name}"
+ return
+ done
+}
+
+read_home() {
+ local x
+
+ echo
+ while :
+ do
+ echo -n "Home Directory: [${def_home_dir}/${login}] "
+ read home
+
+ if test -z "${home}"
+ then
+ home="${def_home_dir}/${login}"
+ echo "Using ${home}"
+ fi
+
+ if ! expr "${home}" : '^[0-9a-zA-Z,._-\/]*$' &> /dev/null
+ then
+ echo "Please use letters, numbers, spaces and special characters ,._-/"
+ continue
+ fi
+
+ x="$(basename ${home})"
+ if test "${x}" != "${login}"
+ then
+ echo "Warning: you are about to use different login name and home directory."
+ fi
+
+ x="$(dirname ${home})"
+ if ! test -d "${x}"
+ then
+ echo "Directory ${x} does not exist."
+ echo "If you still want to use it, please make it manually."
+ continue
+ fi
+
+ def_home_dir="${x}"
+ return
+ done
+}
+
+read_shell () {
+ local x
+
+ echo
+ while :
+ do
+ echo -n "Shell: [${def_shell}] "
+ read shell
+
+ if test -z "${shell}"
+ then
+ shell="${def_shell}"
+ echo "Using ${shell}"
+ fi
+
+ for x in $(cat /etc/shells)
+ do
+ if test "${x}" = "${shell}"
+ then
+ def_shell="${shell}"
+ return
+ fi
+ done
+
+ echo "Possible shells are:"
+ cat /etc/shells
+ done
+}
+
+read_group () {
+ echo
+ while :
+ do
+ echo -n "Group: [${def_group}] "
+ read group
+
+ if test -z "${group}"
+ then
+ group="${def_group}"
+ echo "Using ${group}"
+ fi
+
+ if test "${group}" = '?'
+ then
+ less /etc/group
+ echo
+ continue
+ fi
+
+ if check_group "${group}"
+ then
+ echo "Group ${group} does not exist."
+ continue
+ fi
+
+ def_group="${group}"
+ return
+ done
+}
+
+read_other_groups () {
+ echo
+ while :
+ do
+ echo -n "Other groups: [${def_og:-none}] "
+ read other_groups
+
+ if test "${other_groups}" = '?'
+ then
+ less /etc/group
+ echo
+ continue
+ fi
+
+ if test -z "${other_groups}"
+ then
+ if test -n "${def_og}"
+ then
+ other_groups="${def_og}"
+ echo "Using ${other_groups}"
+ else
+ echo "No other groups"
+ return
+ fi
+ fi
+
+
+ if ! check_other_groups "${other_groups}"
+ then
+ continue
+ fi
+
+ def_og="${other_groups}"
+ return
+ done
+}
+
+read_uid () {
+ echo
+ while :
+ do
+ echo -n "uid: [first free] "
+ read uid
+
+ if test -z "${uid}"
+ then
+ echo "Using first free UID."
+ return
+ fi
+
+ if test "${uid}" = '?'
+ then
+ less /etc/passwd
+ echo
+ continue
+ fi
+
+ if ! expr "${uid}" : '^[0-9]+$' &> /dev/null
+ then
+ echo "Please use numbers only."
+ continue
+ fi
+ if test "${uid}" -lt "${uid_low}"
+ then
+ echo "UID must be greater than ${uid_low}"
+ continue
+ fi
+ if test "${uid}" -gt "${uid_high}"
+ then
+ echo "UID must be smaller than ${uid_high}"
+ continue
+ fi
+ if ! check_uid "${uid}"
+ then
+ echo "UID ${uid} is already in use"
+ continue
+ fi
+
+ return
+ done
+}
+
+read_max_valid_days() {
+ echo
+ while :
+ do
+ echo -en "Maximum days between password changes: [${def_pwd_max}] "
+ read max_days
+
+ if test -z "${max_days}"
+ then
+ max_days="${def_pwd_max}"
+ echo "Using ${max_days}"
+ return
+ fi
+
+ if ! expr "${max_days}" : '^[0-9]+$' &> /dev/null
+ then
+ echo "Please use numbers only."
+ continue
+ fi
+ if test "${max_days}" -lt 7
+ then
+ echo "Warning: you are using a value shorter than a week."
+ fi
+
+ def_pwd_max="${max_days}"
+ return
+ done
+}
+
+read_min_valid_days() {
+ echo
+ while :
+ do
+ echo -en "Minimum days between password changes: [${def_pwd_min}] "
+ read min_days
+
+ if test -z "${min_days}"
+ then
+ min_days="${def_pwd_min}"
+ echo "Using ${min_days}"
+ return
+ fi
+
+ if ! expr "${min_days}" : '^[0-9]+$' &> /dev/null
+ then
+ echo "Please use numbers only."
+ continue
+ fi
+ if test "${min_days}" -gt 7
+ then
+ echo "Warning: you are using a value longer than a week."
+ fi
+
+ def_pwd_min="${min_days}"
+ return
+ done
+}
+
+read_warning_days() {
+ echo
+ while :
+ do
+ echo -en "Number of warning days before password expires: [${def_pwd_warn}] "
+ read warn_days
+
+ if test -z "${warn_days}"
+ then
+ warn_days="${def_pwd_warn}"
+ echo "Using ${warn_days}"
+ fi
+
+ if ! expr "${warn_days}" : '^[0-9]+$' &> /dev/null
+ then
+ echo "Please use numbers only."
+ continue
+ fi
+ if test "${warn_days}" -gt 14
+ then
+ echo "Warning: you are using a value longer than two week."
+ fi
+
+ def_pwd_warn="${warn_days}"
+ return
+ done
+}
+
+
+read_inactive_days() {
+ echo
+ while :
+ do
+ echo -en "Number of usable days after expiration: [${def_pwd_iact}] "
+ read iact_days
+
+ if test -z "${iact_days}"
+ then
+ iact_days="${def_pwd_iact}"
+ echo "Using ${iact_days}"
+ return
+ fi
+ if ! expr "${iact_days}" : '^[0-9]+$' &> /dev/null
+ then
+ echo "Please use numbers only."
+ continue
+ fi
+ if test "${iact_days}" -gt 14
+ then
+ echo "Warning: you are using a value that is more than two weeks."
+ fi
+
+ def_pwd_iact="${iact_days}"
+ return
+ done
+}
+
+read_expire_date() {
+ local ans
+
+ echo
+ while :
+ do
+ echo -en "Expire date of this account (mm/dd/yy): [${def_expire:-never}] "
+ read ans
+
+ if test -z "${ans}"
+ then
+ if test -z "${def_expire}"
+ then
+ ans="never"
+ else
+ ans="${def_expire}"
+ echo "Using ${def_expire}"
+ fi
+ fi
+
+ if test "${ans}" = "never"
+ then
+ echo "Account will never expire."
+ def_expire=""
+ expire=""
+ return
+ fi
+
+ if ! expr "${ans}" : '^[0-9][0-9]/[0-9][0-9]/[0-9][0-9]$' &> /dev/null
+ then
+ echo "Please use format mm/dd/yy"
+ continue
+ fi
+
+ if ! expire_date="$(date -d ${ans} '+%A, %B %d %Y')"
+ then
+ continue
+ fi
+
+ def_expire="${expire}"
+ return
+ done
+}
+
+read_passwd_yn() {
+ echo -en "\nDo you want to set password [Y/n] ? "
+ if read_yn 0
+ then
+ set_pwd="YES"
+ else
+ set_pwd=""
+ fi
+}
+
+
+print_values() {
+
+clear
+cat << EOM
+
+Login: ${S}${login}${E}
+Group: ${S}${group}${E}
+Other groups: ${S}${other_groups:-[none]}${E}
+
+Real Name: ${S}${name}${E}
+
+uid: ${S}${uid:-[first free]}${E}
+home: ${S}${home}${E}
+shell: ${S}${shell}${E}
+
+Account expiration date: ${S}${expire_date:-never}${E}
+Minimum days between password changes: ${S}${min_days}${E}
+Maximum days between password changes: ${S}${max_days}${E}
+Number of usable days after expiration: ${S}${iact_days}${E}
+Number of warning days before expiration: ${S}${warn_days}${E}
+
+${S}${set_pwd:+Set password for this account.}${E}
+
+EOM
+}
+
+set_user() {
+ if ! useradd \
+ -c "${name}" \
+ -d "${home}" \
+ -g "${group}" \
+ -s "${shell}" \
+ ${expire:+-e ${expire}} \
+ ${uid:+-u ${uid}} \
+ ${other_groups:+-G ${other_groups}} \
+ ${login}
+ then
+ echo "Error ($?) in useradd...exiting..."
+ exit 1
+ fi
+}
+
+set_aging() {
+ if ! passwd \
+ -x ${max_days} \
+ -n ${min_days} \
+ -w ${warn_days} \
+ -i ${iact_days} \
+ ${login}
+ then
+ echo "Error ($?) in setting password aging...exiting..."
+ exit 1
+ fi
+}
+
+set_password() {
+ if test -n "${set_pwd}"
+ then
+ echo
+ passwd ${login}
+ echo
+ fi
+}
+
+set_system() {
+ if test -d "${home}"
+ then
+ echo "Directory ${home} already exists."
+ echo "Skeleton files not copied."
+ return
+ fi
+
+ echo -n "Copying skeleton files..."
+ (
+ mkdir ${home}
+ cd ${skel} && cp -af . ${home}
+ chmod ${def_mode} ${home}
+ chown -R ${login}:${group} ${home}
+ )
+ echo "done."
+
+ ## Add your own stuff here:
+ echo -n "Setting up other files..."
+ (
+ mailbox="/var/spool/mail/${login}"
+ touch ${mailbox}
+ chown "${login}:mail" ${mailbox}
+ chmod 600 ${mailbox}
+ )
+ echo "done."
+}
+
+
+read_values() {
+ clear
+ echo -e "\nPlease answer the following questions about the new user to be added."
+
+ while :
+ do
+ read_login
+ read_name
+ read_group
+ read_other_groups
+ read_home
+ read_shell
+ read_uid
+ read_expire_date
+ read_max_valid_days
+ read_min_valid_days
+ read_warning_days
+ read_inactive_days
+ read_passwd_yn
+
+ print_values
+
+ echo -n "Is this correct [N/y] ? "
+ read_yn 1 && return
+ done
+}
+
+
+main() {
+ check_root
+ read_values
+ set_user
+ set_aging
+ set_system
+ set_password
+}
+
+
+## Run it 8-)
+main
+
+# End.
diff --git a/contrib/groupmems.shar b/contrib/groupmems.shar
new file mode 100644
index 0000000..62e9b48
--- /dev/null
+++ b/contrib/groupmems.shar
@@ -0,0 +1,465 @@
+#!/bin/sh
+# This is a shell archive (produced by GNU sharutils 4.2.1).
+# To extract the files from this archive, save it to some FILE, remove
+# everything before the `!/bin/sh' line above, then type `sh FILE'.
+#
+# Made on 2000-05-25 14:41 CDT by <gk4@gnu.austin.ibm.com>.
+# Source directory was `/home/gk4/src/groupmem'.
+#
+# Existing files will *not* be overwritten unless `-c' is specified.
+#
+# This shar contains:
+# length mode name
+# ------ ---------- ------------------------------------------
+# 1960 -rw-r--r-- Makefile
+# 6348 -rw-r--r-- groupmems.c
+# 3372 -rw------- groupmems.8
+#
+save_IFS="${IFS}"
+IFS="${IFS}:"
+gettext_dir=FAILED
+locale_dir=FAILED
+first_param="$1"
+for dir in $PATH
+do
+ if test "$gettext_dir" = FAILED && test -f $dir/gettext \
+ && ($dir/gettext --version >/dev/null 2>&1)
+ then
+ set `$dir/gettext --version 2>&1`
+ if test "$3" = GNU
+ then
+ gettext_dir=$dir
+ fi
+ fi
+ if test "$locale_dir" = FAILED && test -f $dir/shar \
+ && ($dir/shar --print-text-domain-dir >/dev/null 2>&1)
+ then
+ locale_dir=`$dir/shar --print-text-domain-dir`
+ fi
+done
+IFS="$save_IFS"
+if test "$locale_dir" = FAILED || test "$gettext_dir" = FAILED
+then
+ echo=echo
+else
+ TEXTDOMAINDIR=$locale_dir
+ export TEXTDOMAINDIR
+ TEXTDOMAIN=sharutils
+ export TEXTDOMAIN
+ echo="$gettext_dir/gettext -s"
+fi
+if touch -am -t 200112312359.59 $$.touch >/dev/null 2>&1 && test ! -f 200112312359.59 -a -f $$.touch; then
+ shar_touch='touch -am -t $1$2$3$4$5$6.$7 "$8"'
+elif touch -am 123123592001.59 $$.touch >/dev/null 2>&1 && test ! -f 123123592001.59 -a ! -f 123123592001.5 -a -f $$.touch; then
+ shar_touch='touch -am $3$4$5$6$1$2.$7 "$8"'
+elif touch -am 1231235901 $$.touch >/dev/null 2>&1 && test ! -f 1231235901 -a -f $$.touch; then
+ shar_touch='touch -am $3$4$5$6$2 "$8"'
+else
+ shar_touch=:
+ echo
+ $echo 'WARNING: not restoring timestamps. Consider getting and'
+ $echo "installing GNU \`touch', distributed in GNU File Utilities..."
+ echo
+fi
+rm -f 200112312359.59 123123592001.59 123123592001.5 1231235901 $$.touch
+#
+if mkdir _sh10937; then
+ $echo 'x -' 'creating lock directory'
+else
+ $echo 'failed to create lock directory'
+ exit 1
+fi
+# ============= Makefile ==============
+if test -f 'Makefile' && test "$first_param" != -c; then
+ $echo 'x -' SKIPPING 'Makefile' '(file already exists)'
+else
+ $echo 'x -' extracting 'Makefile' '(text)'
+ sed 's/^X//' << 'SHAR_EOF' > 'Makefile' &&
+/*
+# SPDX-FileCopyrightText: 2000, International Business Machines, Inc.
+# SPDX-FileCopyrightText: 2000, George Kraft IV, gk4@us.ibm.com
+# SPDX-License-Identifier: BSD-3-Clause
+#
+X
+all: groupmems
+X
+groupmems: groupmems.c
+X cc -g -o groupmems groupmems.c -L. -lshadow
+X
+install: groupmems
+X -/usr/sbin/groupadd groups
+X install -o root -g groups -m 4770 groupmems /usr/bin
+X
+install.man: groupmems.8
+X install -o root -g root -m 644 groupmems.8 /usr/man/man8
+X
+SHAR_EOF
+ (set 20 00 05 25 14 40 28 'Makefile'; eval "$shar_touch") &&
+ chmod 0644 'Makefile' ||
+ $echo 'restore of' 'Makefile' 'failed'
+ if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \
+ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then
+ md5sum -c << SHAR_EOF >/dev/null 2>&1 \
+ || $echo 'Makefile:' 'MD5 check failed'
+b46cf7ef8d59149093c011ced3f3103c Makefile
+SHAR_EOF
+ else
+ shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'Makefile'`"
+ test 1960 -eq "$shar_count" ||
+ $echo 'Makefile:' 'original size' '1960,' 'current size' "$shar_count!"
+ fi
+fi
+# ============= groupmems.c ==============
+if test -f 'groupmems.c' && test "$first_param" != -c; then
+ $echo 'x -' SKIPPING 'groupmems.c' '(file already exists)'
+else
+ $echo 'x -' extracting 'groupmems.c' '(text)'
+ sed 's/^X//' << 'SHAR_EOF' > 'groupmems.c' &&
+/*
+X * SPDX-FileCopyrightText: 2000, International Business Machines, Inc.
+X * SPDX-FileCopyrightText: 2000, George Kraft IV, gk4@us.ibm.com
+X * SPDX-License-Identifier: BSD-3-Clause
+X */
+/*
+**
+** Utility "groupmem" adds and deletes members from a user's group.
+**
+** Setup (as "root"):
+**
+** groupadd -r groups
+** chmod 2770 groupmems
+** chown root.groups groupmems
+** groupmems -g groups -a gk4
+**
+** Usage (as "gk4"):
+**
+** groupmems -a olive
+** groupmems -a jordan
+** groupmems -a meghan
+** groupmems -a morgan
+** groupmems -a jake
+** groupmems -l
+** groupmems -d jake
+** groupmems -l
+*/
+X
+#include <stdio.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "defines.h"
+#include "groupio.h"
+X
+/* Exit Status Values */
+X
+#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 */
+X
+#define TRUE 1
+#define FALSE 0
+X
+/* Globals */
+X
+extern int optind;
+extern char *optarg;
+static char *adduser = NULL;
+static char *deluser = NULL;
+static char *thisgroup = NULL;
+static int purge = FALSE;
+static int list = FALSE;
+static int exclusive = 0;
+X
+static int isroot(void) {
+X return getuid() ? FALSE : TRUE;
+}
+X
+static int isgroup(void) {
+X gid_t g = getgid();
+X struct group *grp = getgrgid(g);
+X
+X return TRUE;
+}
+X
+static char *whoami(void) {
+X struct group *grp = getgrgid(getgid());
+X struct passwd *usr = getpwuid(getuid());
+X
+X if (0 == strcmp(usr->pw_name, grp->gr_name)) {
+X return (char *)strdup(usr->pw_name);
+X } else {
+X return NULL;
+X }
+}
+X
+static void
+addtogroup(char *user, char **members) {
+X int i;
+X char **pmembers;
+X
+X for (i = 0; NULL != members[i]; i++ ) {
+X if (0 == strcmp(user, members[i])) {
+X fprintf(stderr, "Member already exists\n");
+X exit(EXIT_MEMBER_EXISTS);
+X }
+X }
+X
+X if (0 == i) {
+X pmembers = (char **)calloc(2, sizeof(char *));
+X } else {
+X pmembers = (char **)realloc(members, sizeof(char *)*(i+1));
+X }
+X
+X *members = *pmembers;
+X members[i] = user;
+X members[i+1] = NULL;
+}
+X
+static void
+rmfromgroup(char *user, char **members) {
+X int i;
+X int found = FALSE;
+X
+X i = 0;
+X while (!found && NULL != members[i]) {
+X if (0 == strcmp(user, members[i])) {
+X found = TRUE;
+X } else {
+X i++;
+X }
+X }
+X
+X while (found && NULL != members[i]) {
+X members[i] = members[++i];
+X }
+X
+X if (!found) {
+X fprintf(stderr, "Member to remove could not be found\n");
+X exit(EXIT_NOT_MEMBER);
+X }
+}
+X
+static void
+nomembers(char **members) {
+X int i;
+X
+X for (i = 0; NULL != members[i]; i++ ) {
+X members[i] = NULL;
+X }
+}
+X
+static void
+members(char **members) {
+X int i;
+X
+X for (i = 0; NULL != members[i]; i++ ) {
+X printf("%s ", members[i]);
+X
+X if (NULL == members[i+1]) {
+X printf("\n");
+X } else {
+X printf(" ");
+X }
+X }
+}
+X
+static void usage(void) {
+X fprintf(stderr, "usage: groupmems -a username | -d username | -D | -l [-g groupname]\n");
+X exit(EXIT_USAGE);
+}
+X
+main(int argc, char **argv) {
+X int arg, i;
+X char *name;
+X struct group *grp;
+X
+X while ((arg = getopt(argc, argv, "a:d:g:Dl")) != EOF) {
+X switch (arg) {
+X case 'a':
+X adduser = strdup(optarg);
+X ++exclusive;
+X break;
+X case 'd':
+X deluser = strdup(optarg);
+X ++exclusive;
+X break;
+X case 'g':
+X thisgroup = strdup(optarg);
+X break;
+X case 'D':
+X purge = TRUE;
+X ++exclusive;
+X break;
+X case 'l':
+X list = TRUE;
+X ++exclusive;
+X break;
+X default:
+X usage();
+X }
+X }
+X
+X if (exclusive > 1 || optind < argc) {
+X usage();
+X }
+X
+X if (!isroot() && NULL != thisgroup) {
+X fprintf(stderr, "Only root can add members to different groups\n");
+X exit(EXIT_NOT_ROOT);
+X } else if (isroot() && NULL != thisgroup) {
+X name = thisgroup;
+X } else if (!isgroup()) {
+X fprintf(stderr, "Group access is required\n");
+X exit(EXIT_NOT_EROOT);
+X } else if (NULL == (name = whoami())) {
+X fprintf(stderr, "Not primary owner of current group\n");
+X exit(EXIT_NOT_PRIMARY);
+X }
+X
+X if (!gr_lock()) {
+X fprintf(stderr, "Unable to lock group file\n");
+X exit(EXIT_GROUP_FILE);
+X }
+X
+X if (!gr_open(O_RDWR)) {
+X fprintf(stderr, "Unable to open group file\n");
+X exit(EXIT_GROUP_FILE);
+X }
+X
+X grp = (struct group *)gr_locate(name);
+X
+X if (NULL != adduser) {
+X addtogroup(adduser, grp->gr_mem);
+X gr_update(grp);
+X } else if (NULL != deluser) {
+X rmfromgroup(deluser, grp->gr_mem);
+X gr_update(grp);
+X } else if (purge) {
+X nomembers(grp->gr_mem);
+X gr_update(grp);
+X } else if (list) {
+X members(grp->gr_mem);
+X }
+X
+X if (!gr_close()) {
+X fprintf(stderr, "Cannot close group file\n");
+X exit(EXIT_GROUP_FILE);
+X }
+X
+X gr_unlock();
+X
+X exit(EXIT_SUCCESS);
+}
+X
+/* EOF */
+SHAR_EOF
+ (set 20 00 05 25 14 36 38 'groupmems.c'; eval "$shar_touch") &&
+ chmod 0644 'groupmems.c' ||
+ $echo 'restore of' 'groupmems.c' 'failed'
+ if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \
+ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then
+ md5sum -c << SHAR_EOF >/dev/null 2>&1 \
+ || $echo 'groupmems.c:' 'MD5 check failed'
+f0dd68f8d762d89d24d3ce1f4141f981 groupmems.c
+SHAR_EOF
+ else
+ shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'groupmems.c'`"
+ test 6348 -eq "$shar_count" ||
+ $echo 'groupmems.c:' 'original size' '6348,' 'current size' "$shar_count!"
+ fi
+fi
+# ============= groupmems.8 ==============
+if test -f 'groupmems.8' && test "$first_param" != -c; then
+ $echo 'x -' SKIPPING 'groupmems.8' '(file already exists)'
+else
+ $echo 'x -' extracting 'groupmems.8' '(text)'
+ sed 's/^X//' << 'SHAR_EOF' > 'groupmems.8' &&
+X.\"
+X.\" SPDX-FileCopyrightText: 2000, International Business Machines, Inc.
+X.\" SPDX-FileCopyrightText: 2000, George Kraft IV, gk4@us.ibm.com
+X.\" SPDX-License-Identifier: BSD-3-Clause
+X.\"
+X.\" $Id$
+X.\"
+X.TH GROUPMEMS 8
+X.SH NAME
+groupmems \- Administer members of a user's primary group
+X.SH SYNOPSIS
+X.B groupmems
+\fB-a\fI user_name \fR |
+\fB-d\fI user_name \fR |
+\fB-l\fR |
+\fB-D\fR |
+[\fB-g\fI group_name \fR]
+X.SH DESCRIPTION
+The \fBgroupmems\fR utility allows a user to administer their own
+group membership list without the requirement of superuser privileges.
+The \fBgroupmems\fR utility is for systems that configure its users to
+be in their own name sake primary group (i.e., guest / guest).
+X.P
+Only the superuser, as administrator, can use \fBgroupmems\fR to alter
+the memberships of other groups.
+X.IP "\fB-a \fIuser_name\fR"
+Add a new user to the group membership list.
+X.IP "\fB-d \fIuser_name\fR"
+Delete a user from the group membership list.
+X.IP "\fB-l\fR"
+List the group membership list.
+X.IP "\fB-D\fR"
+Delete all users from the group membership list.
+X.IP "\fB-g \fIgroup_name\fR"
+The superuser can specify which group membership list to modify.
+X.SH SETUP
+The \fBgroupmems\fR executable should be in mode \fB2770\fR as user \fBroot\fR
+and in group \fBgroups\fR. The system administrator can add users to
+group groups to allow or disallow them using the \fBgroupmems\fR utility
+to manager their own group membership list.
+X.P
+X $ groupadd -r groups
+X.br
+X $ chmod 2770 groupmems
+X.br
+X $ chown root.groups groupmems
+X.br
+X $ groupmems -g groups -a gk4
+X.SH FILES
+/etc/group
+X.br
+/etc/gshadow
+X.SH SEE ALSO
+X.BR chfn (1),
+X.BR chsh (1),
+X.BR useradd (8),
+X.BR userdel (8),
+X.BR usermod (8),
+X.BR passwd (1),
+X.BR groupadd (8),
+X.BR groupdel (8)
+X.SH AUTHOR
+George Kraft IV (gk4@us.ibm.com)
+X.\" EOF
+SHAR_EOF
+ (set 20 00 05 25 14 38 23 'groupmems.8'; eval "$shar_touch") &&
+ chmod 0600 'groupmems.8' ||
+ $echo 'restore of' 'groupmems.8' 'failed'
+ if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \
+ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then
+ md5sum -c << SHAR_EOF >/dev/null 2>&1 \
+ || $echo 'groupmems.8:' 'MD5 check failed'
+181e6cd3a3c9d3df320197fa2cde2b4a groupmems.8
+SHAR_EOF
+ else
+ shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'groupmems.8'`"
+ test 3372 -eq "$shar_count" ||
+ $echo 'groupmems.8:' 'original size' '3372,' 'current size' "$shar_count!"
+ fi
+fi
+rm -fr _sh10937
+exit 0
diff --git a/contrib/pwdauth.c b/contrib/pwdauth.c
new file mode 100644
index 0000000..ca15495
--- /dev/null
+++ b/contrib/pwdauth.c
@@ -0,0 +1,308 @@
+/*
+ * pwdauth.c - program to verify a given username/password pair.
+ *
+ * Run it with username in argv[1] (may be omitted - default is the
+ * current user), and send it the password over a pipe on stdin.
+ * Exit status: 0 - correct password, 1 - wrong password, >1 - other
+ * errors. For use with shadow passwords, this program should be
+ * installed setuid root.
+ *
+ * This can be used, for example, by xlock - you don't have to install
+ * this large and complex (== possibly insecure) program setuid root,
+ * just modify it to run this simple program to do the authentication.
+ *
+ * Recent versions (xlockmore-3.9) are cleaner, and drop privileges as
+ * soon as possible after getting the user's encrypted password.
+ * Using this program probably doesn't make it more secure, and has one
+ * disadvantage: since we don't get the encrypted user's password at
+ * startup (but at the time the user is authenticated), it is not clear
+ * how we should handle errors (like getpwnam() returning NULL).
+ * - fail the authentication? Problem: no way to unlock (other than kill
+ * the process from somewhere else) if the NIS server stops responding.
+ * - succeed and unlock? Problem: it's too easy to unlock by unplugging
+ * the box from the network and waiting until NIS times out...
+ *
+ * This program is Copyright (C) 1996 Marek Michalkiewicz
+ * <marekm@i17linuxb.ists.pwr.wroc.pl>.
+ *
+ * It may be used and distributed freely for any purposes. There is no
+ * warranty - use at your own risk. I am not liable for any damages etc.
+ * If you improve it, please send me your changes.
+ */
+
+static char rcsid[] = "$Id$";
+
+/*
+ * Define USE_SYSLOG to use syslog() to log successful and failed
+ * authentication. This should be safe even if your system has
+ * the infamous syslog buffer overrun security problem...
+ */
+#define USE_SYSLOG
+
+/*
+ * Define HAVE_GETSPNAM to get shadow passwords using getspnam().
+ * Some systems don't have getspnam(), but getpwnam() returns
+ * encrypted passwords only if running as root.
+ *
+ * According to the xlock source (not tested, except Linux) -
+ * define: Linux, Solaris 2.x, SVR4, ...
+ * undef: HP-UX with Secured Passwords, FreeBSD, NetBSD, QNX.
+ * Known not supported (yet): Ultrix, OSF/1, SCO.
+ */
+#define HAVE_GETSPNAM
+
+/*
+ * Define HAVE_PW_ENCRYPT to use pw_encrypt() instead of crypt().
+ * pw_encrypt() is like the standard crypt(), except that it may
+ * support better password hashing algorithms.
+ *
+ * Define if linking with libshadow.a from the shadow password
+ * suite (Linux, SunOS 4.x?).
+ */
+#undef HAVE_PW_ENCRYPT
+
+/*
+ * Define HAVE_AUTH_METHODS to support the shadow suite specific
+ * extension: the encrypted password field contains a list of
+ * administrator defined authentication methods, separated by
+ * semicolons. This program only supports the standard password
+ * authentication method (a string that doesn't start with '@').
+ */
+#undef HAVE_AUTH_METHODS
+
+/*
+ * FAIL_DELAY - number of seconds to sleep before exiting if the
+ * password was wrong, to slow down password guessing attempts.
+ */
+#define FAIL_DELAY 2
+
+/* No user-serviceable parts below :-). */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <pwd.h>
+
+#ifdef USE_SYSLOG
+#include <syslog.h>
+#ifndef LOG_AUTHPRIV
+#define LOG_AUTHPRIV LOG_AUTH
+#endif
+#endif
+
+#ifdef HAVE_GETSPNAM
+#include <shadow.h>
+#endif
+
+#ifdef HAVE_PW_ENCRYPT
+extern char *pw_encrypt();
+#define crypt pw_encrypt
+#endif
+
+/*
+ * Read the password (one line) from fp. We don't turn off echo
+ * because we expect input from a pipe.
+ */
+static char *
+get_line(fp)
+ FILE *fp;
+{
+ static char buf[128];
+ char *cp;
+ int ch;
+
+ cp = buf;
+ while ((ch = getc(fp)) != EOF && ch != '\0' && ch != '\n') {
+ if (cp >= buf + sizeof buf - 1)
+ break;
+ *cp++ = ch;
+ }
+ *cp = '\0';
+ return buf;
+}
+
+/*
+ * Get the password file entry for the current user. If the name
+ * returned by getlogin() is correct (matches the current real uid),
+ * return the entry for that user. Otherwise, return the entry (if
+ * any) matching the current real uid. Return NULL on failure.
+ */
+static struct passwd *
+get_my_pwent()
+{
+ uid_t uid = getuid();
+ char *name = getlogin();
+
+ if (name && *name) {
+ struct passwd *pw = getpwnam(name);
+
+ if (pw && pw->pw_uid == uid)
+ return pw;
+ }
+ return getpwuid(uid);
+}
+
+/*
+ * Verify the password. The system-dependent shadow support is here.
+ */
+static int
+password_auth_ok(pw, pass)
+ const struct passwd *pw;
+ const char *pass;
+{
+ int result;
+ char *cp;
+#ifdef HAVE_AUTH_METHODS
+ char *buf;
+#endif
+#ifdef HAVE_GETSPNAM
+ struct spwd *sp;
+#endif
+
+ if (pw) {
+#ifdef HAVE_GETSPNAM
+ sp = getspnam(pw->pw_name);
+ if (sp)
+ cp = sp->sp_pwdp;
+ else
+#endif
+ cp = pw->pw_passwd;
+ } else
+ cp = "xx";
+
+#ifdef HAVE_AUTH_METHODS
+ buf = strdup(cp); /* will be modified by strtok() */
+ if (!buf) {
+ fprintf(stderr, "Out of memory.\n");
+ exit(13);
+ }
+ cp = strtok(buf, ";");
+ while (cp && *cp == '@')
+ cp = strtok(NULL, ";");
+
+ /* fail if no password authentication for this user */
+ if (!cp)
+ cp = "xx";
+#endif
+
+ if (*pass || *cp)
+ result = (strcmp(crypt(pass, cp), cp) == 0);
+ else
+ result = 1; /* user with no password */
+
+#ifdef HAVE_AUTH_METHODS
+ free(buf);
+#endif
+ return result;
+}
+
+/*
+ * Main program.
+ */
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ struct passwd *pw;
+ char *pass, *name;
+ char myname[32];
+
+#ifdef USE_SYSLOG
+ openlog("pwdauth", LOG_PID | LOG_CONS, LOG_AUTHPRIV);
+#endif
+ pw = get_my_pwent();
+ if (!pw) {
+#ifdef USE_SYSLOG
+ syslog(LOG_ERR, "can't get login name for uid %d.\n",
+ (int) getuid());
+#endif
+ fprintf(stderr, "Who are you?\n");
+ exit(2);
+ }
+ strncpy(myname, pw->pw_name, sizeof myname - 1);
+ myname[sizeof myname - 1] = '\0';
+ name = myname;
+
+ if (argc > 1) {
+ name = argv[1];
+ pw = getpwnam(name);
+ }
+
+ pass = get_line(stdin);
+ if (password_auth_ok(pw, pass)) {
+#ifdef USE_SYSLOG
+ syslog(pw->pw_uid ? LOG_INFO : LOG_NOTICE,
+ "user `%s' entered correct password for `%.32s'.\n",
+ myname, name);
+#endif
+ exit(0);
+ }
+#ifdef USE_SYSLOG
+ /* be careful not to overrun the syslog buffer */
+ syslog((!pw || pw->pw_uid) ? LOG_NOTICE : LOG_WARNING,
+ "user `%s' entered incorrect password for `%.32s'.\n",
+ myname, name);
+#endif
+#ifdef FAIL_DELAY
+ sleep(FAIL_DELAY);
+#endif
+ fprintf(stderr, "Wrong password.\n");
+ exit(1);
+}
+
+#if 0
+/*
+ * You can use code similar to the following to run this program.
+ * Return values: >=0 - program exit status (use the <sys/wait.h>
+ * macros to get the exit code, it is shifted left by 8 bits),
+ * -1 - check errno.
+ */
+int
+verify_password(const char *username, const char *password)
+{
+ int pipe_fd[2];
+ int pid, wpid, status;
+
+ if (pipe(pipe_fd))
+ return -1;
+
+ if ((pid = fork()) == 0) {
+ char *arg[3];
+ char *env[1];
+
+ /* child */
+ close(pipe_fd[1]);
+ if (pipe_fd[0] != 0) {
+ if (dup2(pipe_fd[0], 0) != 0)
+ _exit(127);
+ close(pipe_fd[0]);
+ }
+ arg[0] = "/usr/bin/pwdauth";
+ arg[1] = username;
+ arg[2] = NULL;
+ env[0] = NULL;
+ execve(arg[0], arg, env);
+ _exit(127);
+ } else if (pid == -1) {
+ /* error */
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ return -1;
+ }
+ /* parent */
+ close(pipe_fd[0]);
+ write(pipe_fd[1], password, strlen(password));
+ write(pipe_fd[1], "\n", 1);
+ close(pipe_fd[1]);
+
+ while ((wpid = wait(&status)) != pid) {
+ if (wpid == -1)
+ return -1;
+ }
+ return status;
+}
+#endif
diff --git a/contrib/shadow-anonftp.patch b/contrib/shadow-anonftp.patch
new file mode 100644
index 0000000..e09647d
--- /dev/null
+++ b/contrib/shadow-anonftp.patch
@@ -0,0 +1,147 @@
+Hello Marek,
+
+I have created a diffile against the 980403 release that adds
+functionality to newusers for automatic handling of users with only
+anonymous ftp login (using the guestgroup feature in ftpaccess, which
+means that the users home directory looks like '/home/user/./'). It also
+adds a commandline argument to specify an initial directory structure
+for such users, with a tarball normally containing the bin,lib,etc
+directories used in the chrooted environment.
+
+I am using it to automatically create chunks of users with only ftp
+access for a webserver.
+
+I have tried to follow your coding standards and I believe it is bug
+free but.. well, who knows. :) It's not much code however.
+
+I hope you find it useful. Do what you like with it, feel free to ask if
+anything is unclear.
+
+Best rgds,
+ Calle Karlsson
+ ckn@kash.se
+
+diff -uNr shadow-980403.orig/src/newusers.c shadow-980403/src/newusers.c
+--- shadow-980403.orig/src/newusers.c Fri Jan 30 00:22:43 1998
++++ shadow-980403/src/newusers.c Fri Apr 17 16:55:33 1998
+@@ -76,11 +76,35 @@
+ static void
+ usage(void)
+ {
+- fprintf(stderr, "Usage: %s [ input ]\n", Prog);
++ fprintf (stderr, "Usage: %s [-p prototype tarfile] [ input ]\n", Prog);
++ fprintf (stderr, "The prototype tarfile is only used for users\n");
++ fprintf (stderr, "marked as anonymous ftp users. It must be a full pathname.\n");
+ exit(1);
+ }
+
+ /*
++ * createuserdir - create a directory and chmod it
++ */
++
++static int
++createuserdir (char * dir, int uid, int gid, int line)
++{
++ if (mkdir (dir, 0777 & ~getdef_num("UMASK", 077))) {
++ fprintf (stderr, "%s: line %d: mkdir %s failed\n",
++ Prog, line, dir);
++ return -1;
++ }
++
++ if (chown (dir, uid, gid)) {
++ fprintf (stderr, "%s: line %d: chown %s failed\n",
++ Prog, line, dir);
++ return -1;
++ }
++
++ return 0;
++}
++
++/*
+ * add_group - create a new group or add a user to an existing group
+ */
+
+@@ -328,6 +352,8 @@
+ main(int argc, char **argv)
+ {
+ char buf[BUFSIZ];
++ char anonproto[BUFSIZ];
++ int flag;
+ char *fields[8];
+ int nfields;
+ char *cp;
+@@ -340,12 +366,23 @@
+
+ Prog = Basename(argv[0]);
+
+- if (argc > 1 && argv[1][0] == '-')
+- usage ();
++ * anonproto = '\0';
++
++ while ((flag = getopt (argc, argv, "p:h")) != EOF) {
++ switch (flag) {
++ case 'p':
++ STRFCPY(anonproto, optarg);
++ break;
++ case 'h':
++ default:
++ usage ();
++ break;
++ }
++ }
+
+- if (argc == 2) {
+- if (! freopen (argv[1], "r", stdin)) {
+- snprintf(buf, sizeof buf, "%s: %s", Prog, argv[1]);
++ if (optind < argc) {
++ if (! freopen (argv[optind], "r", stdin)) {
++ snprintf(buf, sizeof buf, "%s: %s", Prog, argv[optind]);
+ perror (buf);
+ exit (1);
+ }
+@@ -499,15 +536,36 @@
+ if (fields[6][0])
+ newpw.pw_shell = fields[6];
+
+- if (newpw.pw_dir[0] && access(newpw.pw_dir, F_OK)) {
+- if (mkdir (newpw.pw_dir,
+- 0777 & ~getdef_num("UMASK", 077)))
+- fprintf (stderr, "%s: line %d: mkdir failed\n",
+- Prog, line);
+- else if (chown (newpw.pw_dir,
+- newpw.pw_uid, newpw.pw_gid))
+- fprintf (stderr, "%s: line %d: chown failed\n",
+- Prog, line);
++ if (newpw.pw_dir[0]) {
++ char * userdir = strdup (newpw.pw_dir);
++ char * anonpart;
++ int rc;
++
++ if ((anonpart = strstr (userdir, "/./"))) {
++ * anonpart = '\0';
++ anonpart += 2;
++ }
++
++ if (access(userdir, F_OK))
++ rc = createuserdir (userdir, newpw.pw_uid, newpw.pw_gid, line);
++ else
++ rc = 0;
++
++ if (rc == 0 && anonpart) {
++ if (* anonproto) {
++ char cmdbuf [BUFSIZ];
++ snprintf(cmdbuf, sizeof cmdbuf,
++ "cd %s; tar xf %s",
++ userdir, anonproto);
++ system (cmdbuf);
++ }
++ if (strlen (anonpart) > 1) {
++ strcat (userdir, anonpart);
++ if (access (userdir, F_OK))
++ createuserdir (userdir, newpw.pw_uid, newpw.pw_gid, line);
++ }
++ }
++ free (userdir);
+ }
+
+ /*
diff --git a/contrib/udbachk.tgz b/contrib/udbachk.tgz
new file mode 100644
index 0000000..605ad63
--- /dev/null
+++ b/contrib/udbachk.tgz
Binary files differ