summaryrefslogtreecommitdiff
path: root/libpamc
diff options
context:
space:
mode:
authorSteve Langasek <steve.langasek@ubuntu.com>2019-01-03 16:26:05 -0800
committerSteve Langasek <steve.langasek@ubuntu.com>2019-01-03 17:26:38 -0800
commit9c52e721044e7501c3d4567b36d222dc7326224a (patch)
tree9011790770130c60a712a6f125ad50d60e07cc74 /libpamc
parent9727ff2a3fa0e94a42b34a579027bacf4146d571 (diff)
parent186ff16e8d12ff15d518000c17f115ccab5275a4 (diff)
New upstream version 1.0.1
Diffstat (limited to 'libpamc')
-rw-r--r--libpamc/License42
-rw-r--r--libpamc/Makefile.am24
-rw-r--r--libpamc/Makefile.in676
-rw-r--r--libpamc/include/security/pam_client.h192
-rw-r--r--libpamc/libpamc.h63
-rw-r--r--libpamc/libpamc.map12
-rw-r--r--libpamc/pamc_client.c189
-rw-r--r--libpamc/pamc_converse.c211
-rw-r--r--libpamc/pamc_load.c477
-rw-r--r--libpamc/test/Makefile.am11
-rw-r--r--libpamc/test/Makefile.in392
-rwxr-xr-xlibpamc/test/agents/secret@here308
-rw-r--r--libpamc/test/modules/Makefile9
-rw-r--r--libpamc/test/modules/pam_secret.c671
-rw-r--r--libpamc/test/regress/Makefile7
-rwxr-xr-xlibpamc/test/regress/run_test.sh6
-rw-r--r--libpamc/test/regress/test.libpamc.c342
-rwxr-xr-xlibpamc/test/regress/test.secret@here152
18 files changed, 3784 insertions, 0 deletions
diff --git a/libpamc/License b/libpamc/License
new file mode 100644
index 00000000..90106954
--- /dev/null
+++ b/libpamc/License
@@ -0,0 +1,42 @@
+Unless otherwise *explicitly* stated the following text describes the
+licensed conditions under which the contents of this libpamc release
+may be distributed:
+
+-------------------------------------------------------------------------
+Redistribution and use in source and binary forms of libpamc,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions of source code must retain any existing copyright
+ notice, and this entire permission notice in its entirety,
+ including the disclaimer of warranties.
+
+2. Redistributions in binary form must reproduce all prior and current
+ copyright notices, this list of conditions, and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+3. The name of any author may not be used to endorse or promote
+ products derived from this software without their specific prior
+ written permission.
+
+ALTERNATIVELY, this product may be distributed under the terms of the
+GNU Library General Public License (LGPL), in which case the
+provisions of the GNU LGPL are required INSTEAD OF the above
+restrictions. (This clause is necessary due to a potential conflict
+between the GNU LGPL and the restrictions contained in a BSD-style
+copyright.)
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+-------------------------------------------------------------------------
+
diff --git a/libpamc/Makefile.am b/libpamc/Makefile.am
new file mode 100644
index 00000000..b57beb24
--- /dev/null
+++ b/libpamc/Makefile.am
@@ -0,0 +1,24 @@
+#
+# Copyright (c) 2005 Thorsten Kukuk <kukuk@suse.de>
+#
+
+SUBDIRS = test
+
+CLEANFILES = *~
+
+EXTRA_DIST = License libpamc.map
+
+include_HEADERS = include/security/pam_client.h
+
+noinst_HEADERS = libpamc.h
+
+AM_CFLAGS=-I$(top_srcdir)/libpam/include -I$(srcdir)/include
+
+libpamc_la_LDFLAGS = -no-undefined -version-info 81:0:81
+if HAVE_VERSIONING
+ libpamc_la_LDFLAGS += -Wl,--version-script=$(srcdir)/libpamc.map
+endif
+
+lib_LTLIBRARIES = libpamc.la
+
+libpamc_la_SOURCES = pamc_client.c pamc_converse.c pamc_load.c
diff --git a/libpamc/Makefile.in b/libpamc/Makefile.in
new file mode 100644
index 00000000..44102e22
--- /dev/null
+++ b/libpamc/Makefile.in
@@ -0,0 +1,676 @@
+# Makefile.in generated by automake 1.10.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008 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@
+
+#
+# Copyright (c) 2005 Thorsten Kukuk <kukuk@suse.de>
+#
+
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@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@
+@HAVE_VERSIONING_TRUE@am__append_1 = -Wl,--version-script=$(srcdir)/libpamc.map
+subdir = libpamc
+DIST_COMMON = $(include_HEADERS) $(noinst_HEADERS) \
+ $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/jh_path_xml_catalog.m4 \
+ $(top_srcdir)/m4/ld-O1.m4 $(top_srcdir)/m4/ld-as-needed.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libprelude.m4 \
+ $(top_srcdir)/m4/nls.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = `echo $$p | sed -e 's|^.*/||'`;
+am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(includedir)"
+libLTLIBRARIES_INSTALL = $(INSTALL)
+LTLIBRARIES = $(lib_LTLIBRARIES)
+libpamc_la_LIBADD =
+am_libpamc_la_OBJECTS = pamc_client.lo pamc_converse.lo pamc_load.lo
+libpamc_la_OBJECTS = $(am_libpamc_la_OBJECTS)
+libpamc_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(libpamc_la_LDFLAGS) $(LDFLAGS) -o $@
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SOURCES = $(libpamc_la_SOURCES)
+DIST_SOURCES = $(libpamc_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
+ html-recursive info-recursive install-data-recursive \
+ install-dvi-recursive install-exec-recursive \
+ install-html-recursive install-info-recursive \
+ install-pdf-recursive install-ps-recursive install-recursive \
+ installcheck-recursive installdirs-recursive pdf-recursive \
+ ps-recursive uninstall-recursive
+includeHEADERS_INSTALL = $(INSTALL_HEADER)
+HEADERS = $(include_HEADERS) $(noinst_HEADERS)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BROWSER = @BROWSER@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DSYMUTIL = @DSYMUTIL@
+ECHO = @ECHO@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+F77 = @F77@
+FFLAGS = @FFLAGS@
+FO2PDF = @FO2PDF@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_KEY_MANAGEMENT = @HAVE_KEY_MANAGEMENT@
+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@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBAUDIT = @LIBAUDIT@
+LIBCRACK = @LIBCRACK@
+LIBCRYPT = @LIBCRYPT@
+LIBDB = @LIBDB@
+LIBDL = @LIBDL@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBNSL = @LIBNSL@
+LIBOBJS = @LIBOBJS@
+LIBPRELUDE_CFLAGS = @LIBPRELUDE_CFLAGS@
+LIBPRELUDE_CONFIG = @LIBPRELUDE_CONFIG@
+LIBPRELUDE_CONFIG_PREFIX = @LIBPRELUDE_CONFIG_PREFIX@
+LIBPRELUDE_LDFLAGS = @LIBPRELUDE_LDFLAGS@
+LIBPRELUDE_LIBS = @LIBPRELUDE_LIBS@
+LIBPRELUDE_PREFIX = @LIBPRELUDE_PREFIX@
+LIBPRELUDE_PTHREAD_CFLAGS = @LIBPRELUDE_PTHREAD_CFLAGS@
+LIBS = @LIBS@
+LIBSELINUX = @LIBSELINUX@
+LIBTOOL = @LIBTOOL@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NMEDIT = @NMEDIT@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PAM_READ_BOTH_CONFS = @PAM_READ_BOTH_CONFS@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+POSUB = @POSUB@
+RANLIB = @RANLIB@
+SCONFIGDIR = @SCONFIGDIR@
+SECUREDIR = @SECUREDIR@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WITH_DEBUG = @WITH_DEBUG@
+WITH_PAMLOCKING = @WITH_PAMLOCKING@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XMLCATALOG = @XMLCATALOG@
+XMLLINT = @XMLLINT@
+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_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_F77 = @ac_ct_F77@
+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@
+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@
+libc_cv_fpie = @libc_cv_fpie@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pam_cv_ld_as_needed = @pam_cv_ld_as_needed@
+pam_xauth_path = @pam_xauth_path@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = test
+CLEANFILES = *~
+EXTRA_DIST = License libpamc.map
+include_HEADERS = include/security/pam_client.h
+noinst_HEADERS = libpamc.h
+AM_CFLAGS = -I$(top_srcdir)/libpam/include -I$(srcdir)/include
+libpamc_la_LDFLAGS = -no-undefined -version-info 81:0:81 \
+ $(am__append_1)
+lib_LTLIBRARIES = libpamc.la
+libpamc_la_SOURCES = pamc_client.c pamc_converse.c pamc_load.c
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu libpamc/Makefile'; \
+ cd $(top_srcdir) && \
+ $(AUTOMAKE) --gnu libpamc/Makefile
+.PRECIOUS: 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__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ test -z "$(libdir)" || $(MKDIR_P) "$(DESTDIR)$(libdir)"
+ @list='$(lib_LTLIBRARIES)'; for p in $$list; do \
+ if test -f $$p; then \
+ f=$(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) '$$p' '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) "$$p" "$(DESTDIR)$(libdir)/$$f"; \
+ else :; fi; \
+ done
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; for p in $$list; do \
+ p=$(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$p'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$p"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; for p in $$list; do \
+ dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \
+ test "$$dir" != "$$p" || dir=.; \
+ echo "rm -f \"$${dir}/so_locations\""; \
+ rm -f "$${dir}/so_locations"; \
+ done
+libpamc.la: $(libpamc_la_OBJECTS) $(libpamc_la_DEPENDENCIES)
+ $(libpamc_la_LINK) -rpath $(libdir) $(libpamc_la_OBJECTS) $(libpamc_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pamc_client.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pamc_converse.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pamc_load.Plo@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-includeHEADERS: $(include_HEADERS)
+ @$(NORMAL_INSTALL)
+ test -z "$(includedir)" || $(MKDIR_P) "$(DESTDIR)$(includedir)"
+ @list='$(include_HEADERS)'; for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ f=$(am__strip_dir) \
+ echo " $(includeHEADERS_INSTALL) '$$d$$p' '$(DESTDIR)$(includedir)/$$f'"; \
+ $(includeHEADERS_INSTALL) "$$d$$p" "$(DESTDIR)$(includedir)/$$f"; \
+ done
+
+uninstall-includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(include_HEADERS)'; for p in $$list; do \
+ f=$(am__strip_dir) \
+ echo " rm -f '$(DESTDIR)$(includedir)/$$f'"; \
+ rm -f "$(DESTDIR)$(includedir)/$$f"; \
+ done
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run `make' without going through this Makefile.
+# To change the values of `make' variables: instead of editing Makefiles,
+# (1) if the variable is set in `config.status', edit `config.status'
+# (which will cause the Makefiles to be regenerated when you run `make');
+# (2) otherwise, pass the desired values on the `make' command line.
+$(RECURSIVE_TARGETS):
+ @failcom='exit 1'; \
+ for f in x $$MAKEFLAGS; do \
+ case $$f in \
+ *=* | --[!k]*);; \
+ *k*) failcom='fail=yes';; \
+ esac; \
+ done; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+$(RECURSIVE_CLEAN_TARGETS):
+ @failcom='exit 1'; \
+ for f in x $$MAKEFLAGS; do \
+ case $$f in \
+ *=* | --[!k]*);; \
+ *k*) failcom='fail=yes';; \
+ esac; \
+ done; \
+ dot_seen=no; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ rev=''; for subdir in $$list; do \
+ if test "$$subdir" = "."; then :; else \
+ rev="$$subdir $$rev"; \
+ fi; \
+ done; \
+ rev="$$rev ."; \
+ target=`echo $@ | sed s/-recursive//`; \
+ for subdir in $$rev; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done && test -z "$$fail"
+tags-recursive:
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \
+ done
+ctags-recursive:
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \
+ done
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: tags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ tags="$$tags $$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$tags $$unique; \
+ fi
+ctags: CTAGS
+CTAGS: ctags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$tags$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$tags $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && cd $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) $$here
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(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 $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+ fi; \
+ cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
+ else \
+ test -f $(distdir)/$$file \
+ || cp -p $$d/$$file $(distdir)/$$file \
+ || exit 1; \
+ fi; \
+ done
+ list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ distdir=`$(am__cd) $(distdir) && pwd`; \
+ top_distdir=`$(am__cd) $(top_distdir) && pwd`; \
+ (cd $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$top_distdir" \
+ distdir="$$distdir/$$subdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_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-recursive
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-includeHEADERS
+
+install-dvi: install-dvi-recursive
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-recursive
+
+install-info: install-info-recursive
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-ps: install-ps-recursive
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-includeHEADERS uninstall-libLTLIBRARIES
+
+.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) install-am \
+ install-strip
+
+.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
+ all all-am check check-am clean clean-generic \
+ clean-libLTLIBRARIES clean-libtool ctags ctags-recursive \
+ distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags 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-includeHEADERS install-info install-info-am \
+ install-libLTLIBRARIES install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-recursive uninstall uninstall-am \
+ uninstall-includeHEADERS uninstall-libLTLIBRARIES
+
+# 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/libpamc/include/security/pam_client.h b/libpamc/include/security/pam_client.h
new file mode 100644
index 00000000..3ef0edd1
--- /dev/null
+++ b/libpamc/include/security/pam_client.h
@@ -0,0 +1,192 @@
+/*
+ * $Id: pam_client.h,v 1.7 2005/05/20 14:58:58 kukuk Exp $
+ *
+ * Copyright (c) 1999 Andrew G. Morgan <morgan@linux.kernel.org>
+ *
+ * This header file provides the prototypes for the PAM client API
+ */
+
+#ifndef PAM_CLIENT_H
+#define PAM_CLIENT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* def __cplusplus */
+
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+/* opaque agent handling structure */
+
+typedef struct pamc_handle_s *pamc_handle_t;
+
+/* binary prompt structure pointer */
+typedef struct { u_int32_t length; u_int8_t control; } *pamc_bp_t;
+
+/*
+ * functions provided by libpamc
+ */
+
+/*
+ * Initialize the agent abstraction library
+ */
+
+pamc_handle_t pamc_start(void);
+
+/*
+ * Terminate the authentication process
+ */
+
+int pamc_end(pamc_handle_t *pch);
+
+/*
+ * force the loading of a specified agent
+ */
+
+int pamc_load(pamc_handle_t pch, const char *agent_id);
+
+/*
+ * Single conversation interface for binary prompts
+ */
+
+int pamc_converse(pamc_handle_t pch, pamc_bp_t *prompt_p);
+
+/*
+ * disable an agent
+ */
+
+int pamc_disable(pamc_handle_t pch, const char *agent_id);
+
+/*
+ * obtain a list of available agents
+ */
+
+char **pamc_list_agents(pamc_handle_t pch);
+
+/*
+ * PAM_BP_ MACROS for creating, destroying and manipulating binary prompts
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#ifndef PAM_BP_ASSERT
+# define PAM_BP_ASSERT(x) do { printf(__FILE__ "(%d): %s\n", \
+ __LINE__, x) ; exit(1); } while (0)
+#endif /* PAM_BP_ASSERT */
+
+#ifndef PAM_BP_CALLOC
+# define PAM_BP_CALLOC calloc
+#endif /* PAM_BP_CALLOC */
+
+#ifndef PAM_BP_FREE
+# define PAM_BP_FREE free
+#endif /* PAM_BP_FREE */
+
+#define __PAM_BP_WOCTET(x,y) (*((y) + (u_int8_t *)(x)))
+#define __PAM_BP_ROCTET(x,y) (*((y) + (const u_int8_t *)(x)))
+
+#define PAM_BP_MIN_SIZE (sizeof(u_int32_t) + sizeof(u_int8_t))
+#define PAM_BP_MAX_LENGTH 0x20000 /* an advisory limit */
+#define PAM_BP_WCONTROL(x) (__PAM_BP_WOCTET(x,4))
+#define PAM_BP_RCONTROL(x) (__PAM_BP_ROCTET(x,4))
+#define PAM_BP_SIZE(x) ((__PAM_BP_ROCTET(x,0)<<24)+ \
+ (__PAM_BP_ROCTET(x,1)<<16)+ \
+ (__PAM_BP_ROCTET(x,2)<< 8)+ \
+ (__PAM_BP_ROCTET(x,3) ))
+#define PAM_BP_LENGTH(x) (PAM_BP_SIZE(x) - PAM_BP_MIN_SIZE)
+#define PAM_BP_WDATA(x) (PAM_BP_MIN_SIZE + (u_int8_t *) (x))
+#define PAM_BP_RDATA(x) (PAM_BP_MIN_SIZE + (const u_int8_t *) (x))
+
+/* Note, this macro always '\0' terminates renewed packets */
+
+#define PAM_BP_RENEW(old_p, cntrl, data_length) \
+do { \
+ if (old_p) { \
+ if (*(old_p)) { \
+ u_int32_t __size; \
+ __size = PAM_BP_SIZE(*(old_p)); \
+ memset(*(old_p), 0, __size); \
+ PAM_BP_FREE(*(old_p)); \
+ } \
+ if (cntrl) { \
+ u_int32_t __size; \
+ \
+ __size = PAM_BP_MIN_SIZE + data_length; \
+ if ((*(old_p) = PAM_BP_CALLOC(1, 1+__size))) { \
+ __PAM_BP_WOCTET(*(old_p), 3) = __size & 0xFF; \
+ __PAM_BP_WOCTET(*(old_p), 2) = (__size>>=8) & 0xFF; \
+ __PAM_BP_WOCTET(*(old_p), 1) = (__size>>=8) & 0xFF; \
+ __PAM_BP_WOCTET(*(old_p), 0) = (__size>>=8) & 0xFF; \
+ (*(old_p))->control = cntrl; \
+ } else { \
+ PAM_BP_ASSERT("out of memory for binary prompt"); \
+ } \
+ } else { \
+ *old_p = NULL; \
+ } \
+ } else { \
+ PAM_BP_ASSERT("programming error, invalid binary prompt pointer"); \
+ } \
+} while (0)
+
+#define PAM_BP_FILL(prmpt, offset, length, data) \
+do { \
+ size_t bp_length; \
+ u_int8_t *prompt = (u_int8_t *) (prmpt); \
+ bp_length = PAM_BP_LENGTH(prompt); \
+ if (bp_length < ((length)+(offset))) { \
+ PAM_BP_ASSERT("attempt to write over end of prompt"); \
+ } \
+ memcpy((offset) + PAM_BP_WDATA(prompt), (data), (length)); \
+} while (0)
+
+#define PAM_BP_EXTRACT(prmpt, offset, length, data) \
+do { \
+ size_t __bp_length; \
+ const u_int8_t *__prompt = (const u_int8_t *) (prmpt); \
+ __bp_length = PAM_BP_LENGTH(__prompt); \
+ if (((offset) < 0) || (__bp_length < ((length)+(offset))) \
+ || ((length) < 0)) { \
+ PAM_BP_ASSERT("invalid extraction from prompt"); \
+ } \
+ memcpy((data), (offset) + PAM_BP_RDATA(__prompt), (length)); \
+} while (0)
+
+
+/* Control types */
+
+#define PAM_BPC_FALSE 0
+#define PAM_BPC_TRUE 1
+
+#define PAM_BPC_OK 0x01 /* continuation packet */
+#define PAM_BPC_SELECT 0x02 /* initialization packet */
+#define PAM_BPC_DONE 0x03 /* termination packet */
+#define PAM_BPC_FAIL 0x04 /* unable to execute */
+
+/* The following control characters are only legal for echanges
+ between an agent and a client (it is the responsibility of the
+ client to enforce this rule in the face of a rogue server): */
+
+#define PAM_BPC_GETENV 0x41 /* obtain client env.var */
+#define PAM_BPC_PUTENV 0x42 /* set client env.var */
+#define PAM_BPC_TEXT 0x43 /* display message */
+#define PAM_BPC_ERROR 0x44 /* display error message */
+#define PAM_BPC_PROMPT 0x45 /* echo'd text prompt */
+#define PAM_BPC_PASS 0x46 /* non-echo'd text prompt*/
+
+/* quick check for prompts that are legal for the client (by
+ implication the server too) to send to libpamc */
+
+#define PAM_BPC_FOR_CLIENT(/* pamc_bp_t */ prompt) \
+ (((prompt)->control <= PAM_BPC_FAIL && (prompt)->control >= PAM_BPC_OK) \
+ ? PAM_BPC_TRUE:PAM_BPC_FALSE)
+
+#ifdef __cplusplus
+}
+#endif /* def __cplusplus */
+
+#endif /* PAM_CLIENT_H */
diff --git a/libpamc/libpamc.h b/libpamc/libpamc.h
new file mode 100644
index 00000000..1c9397c9
--- /dev/null
+++ b/libpamc/libpamc.h
@@ -0,0 +1,63 @@
+/*
+ * $Id: libpamc.h,v 1.2 2000/11/19 23:54:03 agmorgan Exp $
+ *
+ * Copyright (c) Andrew G. Morgan <morgan@ftp.kernel.org>
+ *
+ */
+
+#ifndef LIBPAMC_H
+#define LIBPAMC_H
+
+#include <security/pam_client.h>
+#include <security/_pam_macros.h>
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+
+#define _PAMC_DEFAULT_TOP_FD 10
+
+struct pamc_handle_s {
+ struct pamc_agent_s *current;
+ struct pamc_agent_s *chain;
+ struct pamc_blocked_s *blocked_agents;
+ int max_path;
+ char **agent_paths;
+ int combined_status;
+ int highest_fd_to_close;
+};
+
+typedef struct pamc_blocked_s {
+ char *id; /* <NUL> terminated */
+ struct pamc_blocked_s *next;
+} pamc_blocked_t;
+
+typedef struct pamc_agent_s {
+ char *id;
+ int id_length;
+ struct pamc_agent_s *next;
+ int writer; /* write to agent */
+ int reader; /* read from agent */
+ pid_t pid; /* agent process id */
+} pamc_agent_t;
+
+/* used to build a tree of unique, sorted agent ids */
+
+typedef struct pamc_id_node {
+ struct pamc_id_node *left, *right;
+ int child_count;
+ char *agent_id;
+} pamc_id_node_t;
+
+/* internal function */
+int __pamc_valid_agent_id(int id_length, const char *id);
+
+#define PAMC_SYSTEM_AGENT_PATH "/lib/pamc:/usr/lib/pamc"
+#define PAMC_SYSTEM_AGENT_SEPARATOR ':'
+
+#endif /* LIBPAMC_H */
diff --git a/libpamc/libpamc.map b/libpamc/libpamc.map
new file mode 100644
index 00000000..cc61a33b
--- /dev/null
+++ b/libpamc/libpamc.map
@@ -0,0 +1,12 @@
+LIBPAMC_1.0 {
+ global:
+ pamc_start;
+ pamc_end;
+ pamc_load;
+ pamc_converse;
+ pamc_disable;
+ pamc_list_agents;
+
+ local:
+ *;
+};
diff --git a/libpamc/pamc_client.c b/libpamc/pamc_client.c
new file mode 100644
index 00000000..19eff429
--- /dev/null
+++ b/libpamc/pamc_client.c
@@ -0,0 +1,189 @@
+/*
+ * $Id: pamc_client.c,v 1.1.1.1 2000/06/20 22:11:25 agmorgan Exp $
+ *
+ * Copyright (c) Andrew G. Morgan <morgan@ftp.kernel.org>
+ *
+ * pamc_start and pamc_end
+ */
+
+#include "libpamc.h"
+
+/*
+ * liberate path list
+ */
+
+static void __pamc_delete_path_list(pamc_handle_t pch)
+{
+ int i;
+
+ for (i=0; pch->agent_paths[i]; ++i) {
+ free(pch->agent_paths[i]);
+ pch->agent_paths[i] = NULL;
+ }
+
+ free(pch->agent_paths);
+ pch->agent_paths = NULL;
+}
+
+/*
+ * open the pamc library
+ */
+
+pamc_handle_t pamc_start(void)
+{
+ int i, count, last, this;
+ const char *default_path;
+ pamc_handle_t pch;
+
+ pch = calloc(1, sizeof(struct pamc_handle_s));
+ if (pch == NULL) {
+ D(("no memory for *pch"));
+ return NULL;
+ }
+
+ pch->highest_fd_to_close = _PAMC_DEFAULT_TOP_FD;
+
+ default_path = getenv("PAMC_AGENT_PATH");
+ if (default_path == NULL) {
+ default_path = PAMC_SYSTEM_AGENT_PATH;
+ }
+
+ /* number of individual paths */
+ for (count=1, i=0; default_path[i]; ++i) {
+ if (default_path[i] == PAMC_SYSTEM_AGENT_SEPARATOR) {
+ ++count;
+ }
+ }
+
+ pch->agent_paths = calloc(count+1, sizeof(char *));
+ if (pch->agent_paths == NULL) {
+ D(("no memory for path list"));
+ goto drop_pch;
+ }
+
+ this = last = i = 0;
+ while ( default_path[i] || (i != last) ) {
+ if ( default_path[i] == PAMC_SYSTEM_AGENT_SEPARATOR
+ || !default_path[i] ) {
+ int length;
+
+ pch->agent_paths[this] = malloc(length = 1+i-last);
+
+ if (pch->agent_paths[this] == NULL) {
+ D(("no memory for next path"));
+ goto drop_list;
+ }
+
+ memcpy(pch->agent_paths[this], default_path + last, i-last);
+ pch->agent_paths[this][i-last] = '\0';
+ if (length > pch->max_path) {
+ pch->max_path = length;
+ }
+
+ if (++this == count) {
+ break;
+ }
+
+ last = ++i;
+ } else {
+ ++i;
+ }
+ }
+
+ return pch;
+
+drop_list:
+ __pamc_delete_path_list(pch);
+
+drop_pch:
+ free(pch);
+
+ return NULL;
+}
+
+/*
+ * shutdown each of the loaded agents and
+ */
+
+static int __pamc_shutdown_agents(pamc_handle_t pch)
+{
+ int retval = PAM_BPC_TRUE;
+
+ D(("called"));
+
+ while (pch->chain) {
+ pid_t pid;
+ int status;
+ pamc_agent_t *this;
+
+ this = pch->chain;
+ D(("cleaning up agent %p", this));
+ pch->chain = pch->chain->next;
+ this->next = NULL;
+ D(("cleaning up agent: %s", this->id));
+
+ /* close off contact with agent and wait for it to shutdown */
+
+ close(this->writer);
+ this->writer = -1;
+ close(this->reader);
+ this->reader = -1;
+
+ pid = waitpid(this->pid, &status, 0);
+ if (pid == this->pid) {
+
+ D(("is exit:%d, exit val:%d",
+ WIFEXITED(status), WEXITSTATUS(status)));
+
+ if (!(WIFEXITED(status) && (WEXITSTATUS(status) == 0))) {
+ retval = PAM_BPC_FALSE;
+ }
+ } else {
+ D(("problem shutting down agent (%s): pid(%d) != waitpid(%d)!?",
+ this->id, this->pid, pid));
+ retval = PAM_BPC_FALSE;
+ }
+ pid = this->pid = 0;
+
+ memset(this->id, 0, this->id_length);
+ free(this->id);
+ this->id = NULL;
+ this->id_length = 0;
+
+ free(this);
+ this = NULL;
+ }
+
+ return retval;
+}
+
+/*
+ * close the pamc library
+ */
+
+int pamc_end(pamc_handle_t *pch_p)
+{
+ int retval;
+
+ if (pch_p == NULL) {
+ D(("called with no pch_p"));
+ return PAM_BPC_FALSE;
+ }
+
+ if (*pch_p == NULL) {
+ D(("called with no *pch_p"));
+ return PAM_BPC_FALSE;
+ }
+
+ D(("removing path_list"));
+ __pamc_delete_path_list(*pch_p);
+
+ D(("shutting down agents"));
+ retval = __pamc_shutdown_agents(*pch_p);
+
+ D(("freeing *pch_p"));
+ free(*pch_p);
+ *pch_p = NULL;
+
+ return retval;
+}
diff --git a/libpamc/pamc_converse.c b/libpamc/pamc_converse.c
new file mode 100644
index 00000000..50f25200
--- /dev/null
+++ b/libpamc/pamc_converse.c
@@ -0,0 +1,211 @@
+/*
+ * $Id: pamc_converse.c,v 1.5 2005/11/24 17:15:31 ldv Exp $
+ *
+ * Copyright (c) Andrew G. Morgan <morgan@ftp.kernel.org>
+ *
+ * pamc_converse
+ */
+
+#include "libpamc.h"
+
+/*
+ * select agent
+ */
+
+static int __pamc_select_agent(pamc_handle_t pch, char *agent_id)
+{
+ pamc_agent_t *agent;
+
+ for (agent = pch->chain; agent; agent = agent->next) {
+ if (!strcmp(agent->id, agent_id)) {
+ pch->current = agent;
+ return PAM_BPC_TRUE;
+ }
+ }
+
+ D(("failed to locate agent"));
+ pch->current = NULL;
+ return PAM_BPC_FALSE;
+}
+
+/*
+ * pass a binary prompt to the active agent and wait for a reply prompt
+ */
+
+int pamc_converse(pamc_handle_t pch, pamc_bp_t *prompt_p)
+{
+ u_int32_t size, offset=0;
+ u_int8_t control, raw[PAM_BP_MIN_SIZE];
+
+ D(("called"));
+
+ if (pch == NULL) {
+ D(("null pch"));
+ goto pamc_converse_failure;
+ }
+
+ if (prompt_p == NULL) {
+ D(("null prompt_p"));
+ goto pamc_converse_failure;
+ }
+
+ if (*prompt_p == NULL) {
+ D(("null *prompt_p"));
+ goto pamc_converse_failure;
+ }
+
+ /* from here on, failures are interoperability problems.. */
+
+ size = PAM_BP_SIZE(*prompt_p);
+ if (size < PAM_BP_MIN_SIZE) {
+ D(("problem with size being too short (%u)", size));
+ goto pamc_unknown_prompt;
+ }
+
+ if (PAM_BPC_FOR_CLIENT(*prompt_p) != PAM_BPC_TRUE) {
+ D(("*prompt_p is not legal for the client to use"));
+ goto pamc_unknown_prompt;
+ }
+
+ /* do we need to select the agent? */
+ if ((*prompt_p)->control == PAM_BPC_SELECT) {
+ char *rawh;
+ size_t i;
+ int retval;
+
+ D(("selecting a specified agent"));
+
+ rawh = (char *) *prompt_p;
+ for (i = PAM_BP_MIN_SIZE; i<size; ++i) {
+ if (rawh[i] == '/') {
+ break;
+ }
+ }
+
+ if ( (i >= size)
+ || !__pamc_valid_agent_id(i-PAM_BP_MIN_SIZE,
+ rawh + PAM_BP_MIN_SIZE) ) {
+ goto pamc_unknown_prompt;
+ }
+
+ rawh[i] = '\0';
+ retval = pamc_load(pch, PAM_BP_MIN_SIZE + rawh);
+ if (retval == PAM_BPC_TRUE) {
+ retval = __pamc_select_agent(pch, PAM_BP_MIN_SIZE + rawh);
+ }
+ rawh[i] = '/';
+
+ if (retval != PAM_BPC_TRUE) {
+ goto pamc_unknown_prompt;
+ }
+
+ D(("agent is loaded"));
+ }
+
+ if (pch->current == NULL) {
+ D(("unable to address agent"));
+ goto pamc_unknown_prompt;
+ }
+
+ /* pump all of the prompt into the agent */
+ do {
+ int rval = write(pch->current->writer,
+ offset + (const u_int8_t *) (*prompt_p),
+ size - offset);
+ if (rval == -1) {
+ switch (errno) {
+ case EINTR:
+ break;
+ default:
+ D(("problem writing to agent: %m"));
+ goto pamc_unknown_prompt;
+ }
+ } else {
+ offset += rval;
+ }
+ } while (offset < size);
+
+ D(("whole prompt sent to agent"));
+
+ /* read size and control for response prompt */
+
+ offset = 0;
+ memset(raw, 0, sizeof(raw));
+ do {
+ int rval;
+
+ rval = read(pch->current->reader, raw + offset,
+ PAM_BP_MIN_SIZE - offset);
+
+ if (rval == -1) {
+ switch (errno) {
+ case EINTR:
+ break;
+ default:
+ D(("problem reading from agent: %m"));
+ goto pamc_unknown_prompt;
+ }
+ } else if (rval) {
+ offset += rval;
+ } else {
+ D(("agent has closed its output pipe - nothing more to read"));
+ goto pamc_converse_failure;
+ }
+ } while (offset < PAM_BP_MIN_SIZE);
+
+ /* construct the whole reply prompt */
+
+ size = PAM_BP_SIZE(raw);
+ control = PAM_BP_RCONTROL(raw);
+ memset(raw, 0, sizeof(raw));
+
+ D(("agent replied with prompt of size %d and control %u",
+ size, control));
+
+ PAM_BP_RENEW(prompt_p, control, size - PAM_BP_MIN_SIZE);
+ if (*prompt_p == NULL) {
+ D(("problem making a new prompt for reply"));
+ goto pamc_unknown_prompt;
+ }
+
+ /* read the rest of the reply prompt -- note offset has the correct
+ value from the previous loop */
+
+ while (offset < size) {
+ int rval = read(pch->current->reader, offset + (u_int8_t *) *prompt_p,
+ size-offset);
+
+ if (rval == -1) {
+ switch (errno) {
+ case EINTR:
+ break;
+ default:
+ D(("problem reading from agent: %m"));
+ goto pamc_unknown_prompt;
+ }
+ } else if (rval) {
+ offset += rval;
+ } else {
+ D(("problem reading prompt (%d) with %d to go",
+ size, size-offset));
+ goto pamc_converse_failure;
+ }
+ }
+
+ D(("returning success"));
+
+ return PAM_BPC_TRUE;
+
+pamc_converse_failure:
+
+ D(("conversation failure"));
+ PAM_BP_RENEW(prompt_p, 0, 0);
+ return PAM_BPC_FALSE;
+
+pamc_unknown_prompt:
+
+ /* the server is trying something that the client does not support */
+ D(("unknown prompt"));
+ PAM_BP_RENEW(prompt_p, PAM_BPC_FAIL, 0);
+ return PAM_BPC_TRUE;
+}
diff --git a/libpamc/pamc_load.c b/libpamc/pamc_load.c
new file mode 100644
index 00000000..01304cc1
--- /dev/null
+++ b/libpamc/pamc_load.c
@@ -0,0 +1,477 @@
+/*
+ * $Id: pamc_load.c,v 1.1.1.1 2000/06/20 22:11:26 agmorgan Exp $
+ *
+ * Copyright (c) 1999 Andrew G. Morgan <morgan@ftp.kernel.org>
+ *
+ * pamc_load
+ */
+
+#include "libpamc.h"
+
+static int __pamc_exec_agent(pamc_handle_t pch, pamc_agent_t *agent)
+{
+ char *full_path;
+ int found_agent, length, reset_length, to_agent[2], from_agent[2];
+ int return_code = PAM_BPC_FAIL;
+
+ if (agent->id[agent->id_length] != '\0') {
+ PAM_BP_ASSERT("libpamc: internal error agent_id not terminated");
+ }
+
+ for (length=0; (length < agent->id_length); ++length) {
+ switch (agent->id[length]) {
+ case '/':
+ D(("ill formed agent id"));
+ return PAM_BPC_FAIL;
+ }
+ }
+
+ /* enough memory for any path + this agent */
+ reset_length = 3 + pch->max_path + agent->id_length;
+ D(("reset_length = %d (3+%d+%d)",
+ reset_length, pch->max_path, agent->id_length));
+ full_path = malloc(reset_length);
+ if (full_path == NULL) {
+ D(("no memory for agent path"));
+ return PAM_BPC_FAIL;
+ }
+
+ found_agent = 0;
+ for (length=0; pch->agent_paths[length]; ++length) {
+ struct stat buf;
+
+ D(("path: [%s]", pch->agent_paths[length]));
+ D(("agent id: [%s]", agent->id));
+
+ sprintf(full_path, "%s/%s", pch->agent_paths[length], agent->id);
+
+ D(("looking for agent here: [%s]\n", full_path));
+ if (stat(full_path, &buf) == 0) {
+ D(("file existis"));
+ found_agent = 1;
+ break;
+ }
+ }
+
+ if (! found_agent) {
+ D(("no agent was found"));
+ goto free_and_return;
+ }
+
+ if (pipe(to_agent)) {
+ D(("failed to open pipe to agent"));
+ goto free_and_return;
+ }
+
+ if (pipe(from_agent)) {
+ D(("failed to open pipe from agent"));
+ goto close_the_agent;
+ }
+
+ agent->pid = fork();
+ if (agent->pid == -1) {
+
+ D(("failed to fork for agent"));
+ goto close_both_pipes;
+
+ } else if (agent->pid == 0) {
+
+ int i;
+
+ dup2(from_agent[1], STDOUT_FILENO);
+ dup2(to_agent[0], STDIN_FILENO);
+
+ /* we close all of the files that have filedescriptors lower
+ and equal to twice the highest we have seen, The idea is
+ that we don't want to leak filedescriptors to agents from a
+ privileged client application.
+
+ XXX - this is a heuristic at this point. There is a growing
+ need for an extra 'set param' libpamc function, that could
+ be used to supply info like the highest fd to close etc..
+ */
+
+ if (from_agent[1] > pch->highest_fd_to_close) {
+ pch->highest_fd_to_close = 2*from_agent[1];
+ }
+
+ for (i=0; i <= pch->highest_fd_to_close; ++i) {
+ switch (i) {
+ case STDOUT_FILENO:
+ case STDERR_FILENO:
+ case STDIN_FILENO:
+ /* only these three remain open */
+ break;
+ default:
+ (void) close(i); /* don't care if its not open */
+ }
+ }
+
+ /* we make no attempt to drop other privileges - this library
+ has no idea how that would be done in the general case. It
+ is up to the client application (when calling
+ pamc_converse) to make sure no privilege will leak into an
+ (untrusted) agent. */
+
+ /* we propogate no environment - future versions of this
+ library may have the ability to audit all agent
+ transactions. */
+
+ D(("exec'ing agent %s", full_path));
+ execle(full_path, "pam-agent", NULL, NULL);
+
+ D(("exec failed"));
+ exit(1);
+
+ }
+
+ close(to_agent[0]);
+ close(from_agent[1]);
+
+ agent->writer = to_agent[1];
+ agent->reader = from_agent[0];
+
+ return_code = PAM_BPC_TRUE;
+ goto free_and_return;
+
+close_both_pipes:
+ close(from_agent[0]);
+ close(from_agent[1]);
+
+close_the_agent:
+ close(to_agent[0]);
+ close(to_agent[1]);
+
+free_and_return:
+ memset(full_path, 0, reset_length);
+ free(full_path);
+
+ D(("returning %d", return_code));
+
+ return return_code;
+}
+
+/*
+ * has the named agent been loaded?
+ */
+
+static int __pamc_agent_is_enabled(pamc_handle_t pch, const char *agent_id)
+{
+ pamc_agent_t *agent;
+
+ for (agent = pch->chain; agent; agent = agent->next) {
+ if (!strcmp(agent->id, agent_id)) {
+ D(("agent already loaded"));
+ return PAM_BPC_TRUE;
+ }
+ }
+
+ D(("agent is not loaded"));
+ return PAM_BPC_FALSE;
+}
+
+/*
+ * has the named agent been disabled?
+ */
+
+static int __pamc_agent_is_disabled(pamc_handle_t pch, const char *agent_id)
+{
+ pamc_blocked_t *blocked;
+
+ for (blocked=pch->blocked_agents; blocked; blocked = blocked->next) {
+ if (!strcmp(agent_id, blocked->id)) {
+ D(("agent is disabled"));
+ return PAM_BPC_TRUE;
+ }
+ }
+
+ D(("agent is not disabled"));
+ return PAM_BPC_FALSE;
+}
+
+/*
+ * disable an agent
+ */
+
+int pamc_disable(pamc_handle_t pch, const char *agent_id)
+{
+ pamc_blocked_t *block;
+
+ if (pch == NULL) {
+ D(("pch is NULL"));
+ return PAM_BPC_FALSE;
+ }
+
+ if (agent_id == NULL) {
+ D(("agent_id is NULL"));
+ return PAM_BPC_FALSE;
+ }
+
+ if (__pamc_agent_is_enabled(pch, agent_id) != PAM_BPC_FALSE) {
+ D(("agent is already loaded"));
+ return PAM_BPC_FALSE;
+ }
+
+ if (__pamc_agent_is_disabled(pch, agent_id) != PAM_BPC_FALSE) {
+ D(("agent is already disabled"));
+ return PAM_BPC_TRUE;
+ }
+
+ block = calloc(1, sizeof(pamc_blocked_t));
+ if (block == NULL) {
+ D(("no memory for new blocking structure"));
+ return PAM_BPC_FALSE;
+ }
+
+ block->id = malloc(1 + strlen(agent_id));
+ if (block->id == NULL) {
+ D(("no memory for agent id"));
+ free(block);
+ return PAM_BPC_FALSE;
+ }
+
+ strcpy(block->id, agent_id);
+ block->next = pch->blocked_agents;
+ pch->blocked_agents = block;
+
+ return PAM_BPC_TRUE;
+}
+
+/*
+ * force the loading of a particular agent
+ */
+
+int pamc_load(pamc_handle_t pch, const char *agent_id)
+{
+ pamc_agent_t *agent;
+ int length;
+
+ /* santity checking */
+
+ if (pch == NULL) {
+ D(("pch is NULL"));
+ return PAM_BPC_FALSE;
+ }
+
+ if (agent_id == NULL) {
+ D(("agent_id is NULL"));
+ return PAM_BPC_FALSE;
+ }
+
+ if (__pamc_agent_is_disabled(pch, agent_id) != PAM_BPC_FALSE) {
+ D(("sorry agent is disabled"));
+ return PAM_BPC_FALSE;
+ }
+
+ length = strlen(agent_id);
+
+ /* scan list to see if agent is loaded */
+
+ if (__pamc_agent_is_enabled(pch, agent_id) == PAM_BPC_TRUE) {
+ D(("no need to load an already loaded agent (%s)", agent_id));
+ return PAM_BPC_TRUE;
+ }
+
+ /* not in the list, so we need to load it and add it to the head
+ of the chain */
+
+ agent = calloc(1, sizeof(pamc_agent_t));
+ if (agent == NULL) {
+ D(("no memory for new agent"));
+ return PAM_BPC_FALSE;
+ }
+ agent->id = calloc(1, 1+length);
+ if (agent->id == NULL) {
+ D(("no memory for new agent's id"));
+ goto fail_free_agent;
+ }
+ memcpy(agent->id, agent_id, length);
+ agent->id[length] = '\0';
+ agent->id_length = length;
+
+ if (__pamc_exec_agent(pch, agent) != PAM_BPC_TRUE) {
+ D(("unable to exec agent"));
+ goto fail_free_agent_id;
+ }
+
+ agent->next = pch->chain;
+ pch->chain = agent;
+
+ return PAM_BPC_TRUE;
+
+fail_free_agent_id:
+
+ memset(agent->id, 0, agent->id_length);
+ free(agent->id);
+
+ memset(agent, 0, sizeof(*agent));
+
+fail_free_agent:
+
+ free(agent);
+ return PAM_BPC_FALSE;
+}
+
+/*
+ * what's a valid agent name?
+ */
+
+int __pamc_valid_agent_id(int id_length, const char *id)
+{
+ int post, i;
+
+ for (i=post=0 ; i < id_length; ++i) {
+ int ch = id[i++];
+
+ if (isalpha(ch) || isdigit(ch) || (ch == '_')) {
+ continue;
+ } else if (post && (ch == '.')) {
+ continue;
+ } else if ((i > 1) && (!post) && (ch == '@')) {
+ post = 1;
+ } else {
+ D(("id=%s contains '%c' which is illegal", id, ch));
+ return 0;
+ }
+ }
+
+ if (!i) {
+ D(("length of id is 0"));
+ return 0;
+ } else {
+ return 1; /* id is valid */
+ }
+}
+
+/*
+ * building a tree of available agent names
+ */
+
+static pamc_id_node_t *__pamc_add_node(pamc_id_node_t *root, const char *id,
+ int *counter)
+{
+ if (root) {
+
+ int cmp;
+
+ if ((cmp = strcmp(id, root->agent_id))) {
+ if (cmp > 0) {
+ root->right = __pamc_add_node(root->right, id,
+ &(root->child_count));
+ } else {
+ root->left = __pamc_add_node(root->left, id,
+ &(root->child_count));
+ }
+ }
+
+ return root;
+
+ } else {
+
+ pamc_id_node_t *node = calloc(1, sizeof(pamc_id_node_t));
+
+ if (node) {
+ node->agent_id = malloc(1+strlen(id));
+ if (node->agent_id) {
+ strcpy(node->agent_id, id);
+ } else {
+ free(node);
+ node = NULL;
+ }
+ }
+
+ (*counter)++;
+ return node;
+ }
+}
+
+/*
+ * drop all of the tree and any remaining ids
+ */
+
+static pamc_id_node_t *__pamc_liberate_nodes(pamc_id_node_t *tree)
+{
+ if (tree) {
+ if (tree->agent_id) {
+ free(tree->agent_id);
+ tree->agent_id = NULL;
+ }
+
+ tree->left = __pamc_liberate_nodes(tree->left);
+ tree->right = __pamc_liberate_nodes(tree->right);
+
+ tree->child_count = 0;
+ free(tree);
+ }
+
+ return NULL;
+}
+
+/*
+ * fill a list with the contents of the tree (in ascii order)
+ */
+
+static void __pamc_fill_list_from_tree(pamc_id_node_t *tree, char **agent_list,
+ int *counter)
+{
+ if (tree) {
+ __pamc_fill_list_from_tree(tree->left, agent_list, counter);
+ agent_list[(*counter)++] = tree->agent_id;
+ tree->agent_id = NULL;
+ __pamc_fill_list_from_tree(tree->right, agent_list, counter);
+ }
+}
+
+/*
+ * get a list of the available agents
+ */
+
+char **pamc_list_agents(pamc_handle_t pch)
+{
+ int i, total_agent_count=0;
+ pamc_id_node_t *tree = NULL;
+ char **agent_list;
+
+ /* loop over agent paths */
+
+ for (i=0; pch->agent_paths[i]; ++i) {
+ DIR *dir;
+
+ dir = opendir(pch->agent_paths[i]);
+ if (dir) {
+ struct dirent *item;
+
+ while ((item = readdir(dir))) {
+
+ /* this is a cheat on recognizing agent_ids */
+ if (!__pamc_valid_agent_id(strlen(item->d_name),
+ item->d_name)) {
+ continue;
+ }
+
+ tree = __pamc_add_node(tree, item->d_name, &total_agent_count);
+ }
+
+ closedir(dir);
+ }
+ }
+
+ /* now, we build a list of ids */
+ D(("total of %d available agents\n", total_agent_count));
+
+ agent_list = calloc(total_agent_count+1, sizeof(char *));
+ if (agent_list) {
+ int counter=0;
+
+ __pamc_fill_list_from_tree(tree, agent_list, &counter);
+ if (counter != total_agent_count) {
+ PAM_BP_ASSERT("libpamc: internal error transcribing tree");
+ }
+ } else {
+ D(("no memory for agent list"));
+ }
+
+ __pamc_liberate_nodes(tree);
+
+ return agent_list;
+}
diff --git a/libpamc/test/Makefile.am b/libpamc/test/Makefile.am
new file mode 100644
index 00000000..fc0cac46
--- /dev/null
+++ b/libpamc/test/Makefile.am
@@ -0,0 +1,11 @@
+#
+# Copyright (c) 2005 Thorsten Kukuk <kukuk@suse.de>
+#
+
+AUTOMAKE_OPTIONS = 1.6 gnits
+
+CLEANFILES = *~ */*~
+
+EXTRA_DIST = agents/secret@here modules/Makefile modules/pam_secret.c \
+ regress/Makefile regress/run_test.sh regress/test.libpamc.c \
+ regress/test.secret@here
diff --git a/libpamc/test/Makefile.in b/libpamc/test/Makefile.in
new file mode 100644
index 00000000..3f52e4e7
--- /dev/null
+++ b/libpamc/test/Makefile.in
@@ -0,0 +1,392 @@
+# Makefile.in generated by automake 1.10.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008 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@
+
+#
+# Copyright (c) 2005 Thorsten Kukuk <kukuk@suse.de>
+#
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@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 = libpamc/test
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/jh_path_xml_catalog.m4 \
+ $(top_srcdir)/m4/ld-O1.m4 $(top_srcdir)/m4/ld-as-needed.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libprelude.m4 \
+ $(top_srcdir)/m4/nls.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+SOURCES =
+DIST_SOURCES =
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BROWSER = @BROWSER@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DSYMUTIL = @DSYMUTIL@
+ECHO = @ECHO@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+F77 = @F77@
+FFLAGS = @FFLAGS@
+FO2PDF = @FO2PDF@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_KEY_MANAGEMENT = @HAVE_KEY_MANAGEMENT@
+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@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBAUDIT = @LIBAUDIT@
+LIBCRACK = @LIBCRACK@
+LIBCRYPT = @LIBCRYPT@
+LIBDB = @LIBDB@
+LIBDL = @LIBDL@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBNSL = @LIBNSL@
+LIBOBJS = @LIBOBJS@
+LIBPRELUDE_CFLAGS = @LIBPRELUDE_CFLAGS@
+LIBPRELUDE_CONFIG = @LIBPRELUDE_CONFIG@
+LIBPRELUDE_CONFIG_PREFIX = @LIBPRELUDE_CONFIG_PREFIX@
+LIBPRELUDE_LDFLAGS = @LIBPRELUDE_LDFLAGS@
+LIBPRELUDE_LIBS = @LIBPRELUDE_LIBS@
+LIBPRELUDE_PREFIX = @LIBPRELUDE_PREFIX@
+LIBPRELUDE_PTHREAD_CFLAGS = @LIBPRELUDE_PTHREAD_CFLAGS@
+LIBS = @LIBS@
+LIBSELINUX = @LIBSELINUX@
+LIBTOOL = @LIBTOOL@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NMEDIT = @NMEDIT@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PAM_READ_BOTH_CONFS = @PAM_READ_BOTH_CONFS@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+POSUB = @POSUB@
+RANLIB = @RANLIB@
+SCONFIGDIR = @SCONFIGDIR@
+SECUREDIR = @SECUREDIR@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WITH_DEBUG = @WITH_DEBUG@
+WITH_PAMLOCKING = @WITH_PAMLOCKING@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XMLCATALOG = @XMLCATALOG@
+XMLLINT = @XMLLINT@
+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_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_F77 = @ac_ct_F77@
+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@
+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@
+libc_cv_fpie = @libc_cv_fpie@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pam_cv_ld_as_needed = @pam_cv_ld_as_needed@
+pam_xauth_path = @pam_xauth_path@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AUTOMAKE_OPTIONS = 1.6 gnits
+CLEANFILES = *~ */*~
+EXTRA_DIST = agents/secret@here modules/Makefile modules/pam_secret.c \
+ regress/Makefile regress/run_test.sh regress/test.libpamc.c \
+ regress/test.secret@here
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnits libpamc/test/Makefile'; \
+ cd $(top_srcdir) && \
+ $(AUTOMAKE) --gnits libpamc/test/Makefile
+.PRECIOUS: 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__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags: TAGS
+TAGS:
+
+ctags: CTAGS
+CTAGS:
+
+
+distdir: $(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 $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+ fi; \
+ cp -pR $$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:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_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
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+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
+
+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 \
+ 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 uninstall uninstall-am
+
+# 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/libpamc/test/agents/secret@here b/libpamc/test/agents/secret@here
new file mode 100755
index 00000000..afdcbaa8
--- /dev/null
+++ b/libpamc/test/agents/secret@here
@@ -0,0 +1,308 @@
+#!/usr/bin/perl
+#
+# This is a simple example PAM authentication agent, it implements a
+# simple shared secret authentication scheme. The PAM module pam_secret.so
+# is its counter part. Both the agent and the remote server are able to
+# authenticate one another, but the server is given the opportunity to
+# ignore a failed authentication.
+#
+
+$^W = 1;
+use strict;
+use IPC::Open2;
+$| = 1;
+
+# display extra information to STDERR
+my $debug = 0;
+if (scalar @ARGV) {
+ $debug = 1;
+}
+
+# Globals
+
+my %state;
+my $default_key;
+
+my $next_key = $$;
+
+# loop over binary prompts
+for (;;) {
+ my ($control, $data) = ReadBinaryPrompt();
+ my ($reply_control, $reply_data);
+
+ if ($control == 0) {
+ if ($debug) {
+ print STDERR "agent: no packet to read\n";
+ }
+ last;
+ } elsif ($control == 0x02) {
+ ($reply_control, $reply_data) = HandleAgentSelection($data);
+ } elsif ($control == 0x01) {
+ ($reply_control, $reply_data) = HandleContinuation($data);
+ } else {
+ if ($debug) {
+ print STDERR
+ "agent: unrecognized packet $control {$data} to read\n";
+ }
+ ($reply_control, $reply_data) = (0x04, "");
+ }
+
+ WriteBinaryPrompt($reply_control, $reply_data);
+}
+
+# Only willing to exit well if we've completed our authentication exchange
+
+if (scalar keys %state) {
+ if ($debug) {
+ print STDERR "The following sessions are still active:\n ";
+ print STDERR join ', ', keys %state;
+ print STDERR "\n";
+ }
+ exit 1;
+} else {
+ exit 0;
+}
+
+sub HandleAgentSelection ($) {
+ my ($data) = @_;
+
+ unless ( $data =~ /^([a-zA-Z0-9_]+\@?[a-zA-Z0-9_.]*)\/(.*)$/ ) {
+ return (0x04, "");
+ }
+
+ my ($agent_name, $payload) = ($1, $2);
+ if ($debug) {
+ print STDERR "agent: ". "agent=$agent_name, payload=$payload\n";
+ }
+
+ # this agent has a defined name
+ if ($agent_name ne "secret\@here") {
+ if ($debug) {
+ print STDERR "bad agent name: [$agent_name]\n";
+ }
+ return (0x04, "");
+ }
+
+ # the selection request is acompanied with a hexadecimal cookie
+ my @tokens = split '\|', $payload;
+
+ unless ((scalar @tokens) == 2) {
+ if ($debug) {
+ print STDERR "bad payload\n";
+ }
+ return (0x04, "");
+ }
+
+ unless ($tokens[1] =~ /^[a-z0-9]+$/) {
+ if ($debug) {
+ print STDERR "bad server cookie\n";
+ }
+ return (0x04, "");
+ }
+
+ my $shared_secret = IdentifyLocalSecret($tokens[0]);
+
+ unless (defined $shared_secret) {
+ # make a secret up
+ if ($debug) {
+ print STDERR "agent: cannot authenticate user\n";
+ }
+ $shared_secret = GetRandom();
+ }
+
+ my $local_cookie = GetRandom();
+ $default_key = $next_key++;
+
+ $state{$default_key} = $local_cookie ."|". $tokens[1] ."|". $shared_secret;
+
+ if ($debug) {
+ print STDERR "agent: \$state{$default_key} = $state{$default_key}\n";
+ }
+
+ return (0x01, $default_key ."|". $local_cookie);
+}
+
+sub HandleContinuation ($) {
+ my ($data) = @_;
+
+ my ($key, $server_digest) = split '\|', $data;
+
+ unless (defined $state{$key}) {
+ # retries and out of sequence prompts are not permitted
+ return (0x04, "");
+ }
+
+ my $expected_digest = CreateDigest($state{$key});
+ my ($local_cookie, $remote_cookie, $shared_secret)
+ = split '\|', $state{$key};
+ delete $state{$key};
+
+ unless ($expected_digest eq $server_digest) {
+ if ($debug) {
+ print STDERR "agent: don't trust server - faking reply\n";
+ print STDERR "agent: got ($server_digest)\n";
+ print STDERR "agent: expected ($expected_digest)\n";
+ }
+
+ ## FIXME: Agent should exchange a prompt with the client warning
+ ## that the server is faking us out.
+
+ return (0x03, CreateDigest($expected_digest . $data . GetRandom()));
+ }
+
+ if ($debug) {
+ print STDERR "agent: server appears to know the secret\n";
+ }
+
+ my $session_authenticated_ticket =
+ CreateDigest($remote_cookie."|".$shared_secret."|".$local_cookie);
+
+ # FIXME: Agent should set a derived session key environment
+ # variable (available for the client (and its children) to sign
+ # future data exchanges.
+
+ if ($debug) {
+ print STDERR "agent: should putenv("
+ ."\"AUTH_SESSION_TICKET=$session_authenticated_ticket\")\n";
+ }
+
+ # return agent's authenticating digest
+ return (0x03, CreateDigest($shared_secret."|".$remote_cookie
+ ."|".$local_cookie));
+}
+
+sub ReadBinaryPrompt {
+ my $buffer = " ";
+ my $count = read(STDIN, $buffer, 5);
+ if ($count == 0) {
+ # no more packets to read
+ return (0, "");
+ }
+
+ if ($count != 5) {
+ # broken packet header
+ return (-1, "");
+ }
+
+ my ($length, $control) = unpack("N C", $buffer);
+ if ($length < 5) {
+ # broken packet length
+ return (-1, "");
+ }
+
+ my $data = "";
+ $length -= 5;
+ while ($count = read(STDIN, $buffer, $length)) {
+ $data .= $buffer;
+ if ($count != $length) {
+ $length -= $count;
+ next;
+ }
+
+ if ($debug) {
+ print STDERR "agent: ". "data is [$data]\n";
+ }
+
+ return ($control, $data);
+ }
+
+ # broken packet data
+ return (-1, "");
+}
+
+sub WriteBinaryPrompt ($$) {
+ my ($control, $data) = @_;
+
+ my $length = 5 + length($data);
+ if ($debug) {
+ printf STDERR "agent: ". "{%d|0x%.2x|%s}\n", $length, $control, $data;
+ }
+ my $bp = pack("N C a*", $length, $control, $data);
+ print STDOUT $bp;
+ if ($debug) {
+ printf STDERR "agent: ". "agent has replied\n";
+ }
+}
+
+##
+## Here is where we parse the simple secret file
+## The format of this file is a list of lines of the following form:
+##
+## user@client0.host.name secret_string1
+## user@client1.host.name secret_string2
+## user@client2.host.name secret_string3
+##
+
+sub IdentifyLocalSecret ($) {
+ my ($identifier) = @_;
+ my $secret;
+
+ if (open SECRETS, "< ". (getpwuid($<))[7] ."/.secret\@here") {
+ my $line;
+ while (defined ($line = <SECRETS>)) {
+ my ($id, $sec) = split /[\s]+/, $line;
+ if ((defined $id) && ($id eq $identifier)) {
+ $secret = $sec;
+ last;
+ }
+ }
+ close SECRETS;
+ }
+
+ return $secret;
+}
+
+## Here is where we generate a message digest
+
+sub CreateDigest ($) {
+ my ($data) = @_;
+
+ my $pid = open2(\*MD5out, \*MD5in, "/usr/bin/md5sum -")
+ or die "you'll need /usr/bin/md5sum installed";
+
+ my $oldfd = select MD5in; $|=1; select $oldfd;
+ if ($debug) {
+ print STDERR "agent: ". "telling md5: <$data>\n";
+ }
+ print MD5in "$data";
+ close MD5in;
+ my $reply = <MD5out>;
+ ($reply) = split /\s/, $reply;
+ if ($debug) {
+ print STDERR "agent: ". "md5 said: <$reply>\n";
+ }
+ close MD5out;
+
+ return $reply;
+}
+
+## get a random number
+
+sub GetRandom {
+
+ if ( -r "/dev/urandom" ) {
+ open RANDOM, "< /dev/urandom" or die "crazy";
+
+ my $i;
+ my $reply = "";
+
+ for ($i=0; $i<4; ++$i) {
+ my $buffer = " ";
+ while (read(RANDOM, $buffer, 4) != 4) {
+ ;
+ }
+ $reply .= sprintf "%.8x", unpack("N", $buffer);
+ if ($debug) {
+ print STDERR "growing reply: [$reply]\n";
+ }
+ }
+ close RANDOM;
+
+ return $reply;
+ } else {
+ print STDERR "agent: ". "[got linux?]\n";
+ return "%.8x%.8x%.8x%.8x", time, time, time, time;
+ }
+
+}
+
diff --git a/libpamc/test/modules/Makefile b/libpamc/test/modules/Makefile
new file mode 100644
index 00000000..48065462
--- /dev/null
+++ b/libpamc/test/modules/Makefile
@@ -0,0 +1,9 @@
+CFLAGS = -g -fPIC -I"../../include"
+
+pam_secret.so: pam_secret.o
+ ld -x --shared -o pam_secret.so pam_secret.o -lc
+
+.o.c:
+
+clean:
+ rm -f *.so *.o
diff --git a/libpamc/test/modules/pam_secret.c b/libpamc/test/modules/pam_secret.c
new file mode 100644
index 00000000..95f26014
--- /dev/null
+++ b/libpamc/test/modules/pam_secret.c
@@ -0,0 +1,671 @@
+/*
+ * $Id: pam_secret.c,v 1.3 2004/09/14 14:22:39 kukuk Exp $
+ *
+ * Copyright (c) 1999 Andrew G. Morgan <morgan@linux.kernel.org>
+ */
+
+/*
+ * WARNING: AS WRITTEN THIS CODE IS NOT SECURE. THE MD5 IMPLEMENTATION
+ * NEEDS TO BE INTEGRATED MORE NATIVELY.
+ */
+
+/* #define DEBUG */
+
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <security/pam_modules.h>
+#include <security/pam_client.h>
+#include <security/_pam_macros.h>
+
+/*
+ * This is a sample module that demonstrates the use of binary prompts
+ * and how they can be used to implement sophisticated authentication
+ * schemes.
+ */
+
+struct ps_state_s {
+ int retval; /* last retval returned by the authentication fn */
+ int state; /* what state the module was in when it
+ returned incomplete */
+
+ char *username; /* the name of the local user */
+
+ char server_cookie[33]; /* storage for 32 bytes of server cookie */
+ char client_cookie[33]; /* storage for 32 bytes of client cookie */
+
+ char *secret_data; /* pointer to <NUL> terminated secret_data */
+ int invalid_secret; /* indication of whether the secret is valid */
+
+ pamc_bp_t current_prompt; /* place to store the current prompt */
+ pamc_bp_t current_reply; /* place to receive the reply prompt */
+};
+
+#define PS_STATE_ID "PAM_SECRET__STATE"
+#define PS_AGENT_ID "secret@here"
+#define PS_STATE_DEAD 0
+#define PS_STATE_INIT 1
+#define PS_STATE_PROMPT1 2
+#define PS_STATE_PROMPT2 3
+
+#define MAX_LEN_HOSTNAME 512
+#define MAX_FILE_LINE_LEN 1024
+
+/*
+ * Routine for generating 16*8 bits of random data represented in ASCII hex
+ */
+
+static int generate_cookie(unsigned char *buffer_33)
+{
+ static const char hexarray[] = "0123456789abcdef";
+ int i, fd;
+
+ /* fill buffer_33 with 32 hex characters (lower case) + '\0' */
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd < 0) {
+ D(("failed to open /dev/urandom"));
+ return 0;
+ }
+ read(fd, buffer_33 + 16, 16);
+ close(fd);
+
+ /* expand top 16 bytes into 32 nibbles */
+ for (i=0; i<16; ++i) {
+ buffer_33[2*i ] = hexarray[(buffer_33[16+i] & 0xf0)>>4];
+ buffer_33[2*i+1] = hexarray[(buffer_33[16+i] & 0x0f)];
+ }
+
+ buffer_33[32] = '\0';
+
+ return 1;
+}
+
+/*
+ * XXX - This is a hack, and is fundamentally insecure. Its subject to
+ * all sorts of attacks not to mention the fact that all our secrets
+ * will be displayed on the command line for someone doing 'ps' to
+ * see. This is just for programming convenience in this instance, it
+ * needs to be replaced with the md5 code. Although I am loath to
+ * add yet another instance of md5 code to the Linux-PAM source code.
+ * [Need to think of a cleaner way to do this for the distribution as
+ * a whole...]
+ */
+
+#define COMMAND_FORMAT "/bin/echo -n '%s|%s|%s'|/usr/bin/md5sum -"
+
+int create_digest(const char *d1, const char *d2, const char *d3,
+ char *buffer_33)
+{
+ int length;
+ char *buffer;
+ FILE *pipe;
+
+ length = strlen(d1)+strlen(d2)+strlen(d3)+sizeof(COMMAND_FORMAT);
+ buffer = malloc(length);
+ if (buffer == NULL) {
+ D(("out of memory"));
+ return 0;
+ }
+
+ sprintf(buffer, COMMAND_FORMAT, d1,d2,d3);
+
+ D(("executing pipe [%s]", buffer));
+ pipe = popen(buffer, "r");
+ memset(buffer, 0, length);
+ free(buffer);
+
+ if (pipe == NULL) {
+ D(("failed to launch pipe"));
+ return 0;
+ }
+
+ if (fgets(buffer_33, 33, pipe) == NULL) {
+ D(("failed to read digest"));
+ return 0;
+ }
+
+ if (strlen(buffer_33) != 32) {
+ D(("digest was not 32 chars"));
+ return 0;
+ }
+
+ fclose(pipe);
+
+ D(("done [%s]", buffer_33));
+
+ return 1;
+}
+
+/*
+ * method to attempt to instruct the application's conversation function
+ */
+
+static int converse(pam_handle_t *pamh, struct ps_state_s *new)
+{
+ int retval;
+ struct pam_conv *conv;
+
+ D(("called"));
+
+ retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
+ if (retval == PAM_SUCCESS) {
+ struct pam_message msg;
+ struct pam_response *single_reply;
+ const struct pam_message *msg_ptr;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_style = PAM_BINARY_PROMPT;
+ msg.msg = (const char *) new->current_prompt;
+ msg_ptr = &msg;
+
+ single_reply = NULL;
+ retval = conv->conv(1, &msg_ptr, &single_reply, conv->appdata_ptr);
+ if (retval == PAM_SUCCESS) {
+ if ((single_reply == NULL) || (single_reply->resp == NULL)) {
+ retval == PAM_CONV_ERR;
+ } else {
+ new->current_reply = (pamc_bp_t) single_reply->resp;
+ single_reply->resp = NULL;
+ }
+ }
+
+ if (single_reply) {
+ free(single_reply);
+ }
+ }
+
+#ifdef DEBUG
+ if (retval == PAM_SUCCESS) {
+ D(("reply has length=%d and control=%u",
+ PAM_BP_LENGTH(new->current_reply),
+ PAM_BP_CONTROL(new->current_reply)));
+ }
+ D(("returning %s", pam_strerror(pamh, retval)));
+#endif
+
+ return retval;
+}
+
+/*
+ * identify the secret in question
+ */
+
+#define SECRET_FILE_FORMAT "%s/.secret@here"
+
+char *identify_secret(char *identity, const char *user)
+{
+ struct passwd *pwd;
+ char *temp;
+ FILE *secrets;
+ int length_id;
+
+ pwd = getpwnam(user);
+ if ((pwd == NULL) || (pwd->pw_dir == NULL)) {
+ D(("user [%s] is not known", user));
+ return NULL;
+ }
+
+ length_id = strlen(pwd->pw_dir) + sizeof(SECRET_FILE_FORMAT);
+ temp = malloc(length_id);
+ if (temp == NULL) {
+ D(("out of memory"));
+ pwd = NULL;
+ return NULL;
+ }
+
+ sprintf(temp, SECRET_FILE_FORMAT, pwd->pw_dir);
+ pwd = NULL;
+
+ D(("opening key file [%s]", temp));
+ secrets = fopen(temp, "r");
+ memset(temp, 0, length_id);
+
+ if (secrets == NULL) {
+ D(("failed to open key file"));
+ return NULL;
+ }
+
+ length_id = strlen(identity);
+ temp = malloc(MAX_FILE_LINE_LEN);
+
+ for (;;) {
+ char *secret = NULL;
+
+ if (fgets(temp, MAX_FILE_LINE_LEN, secrets) == NULL) {
+ fclose(secrets);
+ return NULL;
+ }
+
+ D(("cf[%s][%s]", identity, temp));
+ if (memcmp(temp, identity, length_id)) {
+ continue;
+ }
+
+ D(("found entry"));
+ fclose(secrets);
+
+ for (secret=temp+length_id; *secret; ++secret) {
+ if (!(*secret == ' ' || *secret == '\n' || *secret == '\t')) {
+ break;
+ }
+ }
+
+ memmove(temp, secret, MAX_FILE_LINE_LEN-(secret-(temp+length_id)));
+ secret = temp;
+
+ for (; *secret; ++secret) {
+ if (*secret == ' ' || *secret == '\n' || *secret == '\t') {
+ break;
+ }
+ }
+
+ if (*secret) {
+ *secret = '\0';
+ }
+
+ D(("secret found [%s]", temp));
+
+ return temp;
+ }
+
+ /* NOT REACHED */
+}
+
+/*
+ * function to perform the two message authentication process
+ * (with support for event driven conversation functions)
+ */
+
+static int auth_sequence(pam_handle_t *pamh,
+ const struct ps_state_s *old, struct ps_state_s *new)
+{
+ const char *rhostname;
+ const char *rusername;
+ int retval;
+
+ retval = pam_get_item(pamh, PAM_RUSER, (const void **) &rusername);
+ if ((retval != PAM_SUCCESS) || (rusername == NULL)) {
+ D(("failed to obtain an rusername"));
+ new->state = PS_STATE_DEAD;
+ return PAM_AUTH_ERR;
+ }
+
+ retval = pam_get_item(pamh, PAM_RHOST, (const void **) &rhostname);
+ if ((retval != PAM_SUCCESS) || (rhostname == NULL)) {
+ D(("failed to identify local hostname: ", pam_strerror(pamh, retval)));
+ new->state = PS_STATE_DEAD;
+ return PAM_AUTH_ERR;
+ }
+
+ D(("switch on new->state=%d [%s@%s]", new->state, rusername, rhostname));
+ switch (new->state) {
+
+ case PS_STATE_INIT:
+ {
+ const char *user = NULL;
+
+ retval = pam_get_user(pamh, &user, NULL);
+
+ if ((retval == PAM_SUCCESS) && (user == NULL)) {
+ D(("success but no username?"));
+ new->state = PS_STATE_DEAD;
+ retval = PAM_USER_UNKNOWN;
+ }
+
+ if (retval != PAM_SUCCESS) {
+ if (retval == PAM_CONV_AGAIN) {
+ retval = PAM_INCOMPLETE;
+ } else {
+ new->state = PS_STATE_DEAD;
+ }
+ D(("state init failed: %s", pam_strerror(pamh, retval)));
+ return retval;
+ }
+
+ /* nothing else in this 'case' can be retried */
+
+ new->username = strdup(user);
+ if (new->username == NULL) {
+ D(("out of memory"));
+ new->state = PS_STATE_DEAD;
+ return PAM_BUF_ERR;
+ }
+
+ if (! generate_cookie(new->server_cookie)) {
+ D(("problem generating server cookie"));
+ new->state = PS_STATE_DEAD;
+ return PAM_ABORT;
+ }
+
+ new->current_prompt = NULL;
+ PAM_BP_RENEW(&new->current_prompt, PAM_BPC_SELECT,
+ sizeof(PS_AGENT_ID) + strlen(rusername) + 1
+ + strlen(rhostname) + 1 + 32);
+ sprintf(PAM_BP_WDATA(new->current_prompt),
+ PS_AGENT_ID "/%s@%s|%.32s", rusername, rhostname,
+ new->server_cookie);
+
+ /* note, the BP is guaranteed by the spec to be <NUL> terminated */
+ D(("initialization packet [%s]", PAM_BP_DATA(new->current_prompt)));
+
+ /* fall through */
+ new->state = PS_STATE_PROMPT1;
+
+ D(("fall through to state_prompt1"));
+ }
+
+ case PS_STATE_PROMPT1:
+ {
+ int i, length;
+
+ /* send {secret@here/jdoe@client.host|<s_cookie>} */
+ retval = converse(pamh, new);
+ if (retval != PAM_SUCCESS) {
+ if (retval == PAM_CONV_AGAIN) {
+ D(("conversation failed to complete"));
+ return PAM_INCOMPLETE;
+ } else {
+ new->state = PS_STATE_DEAD;
+ return retval;
+ }
+ }
+
+ if (retval != PAM_SUCCESS) {
+ D(("failed to read ruser@rhost"));
+ new->state = PS_STATE_DEAD;
+ return PAM_AUTH_ERR;
+ }
+
+ /* expect to receive the following {<seqid>|<a_cookie>} */
+ if (new->current_reply == NULL) {
+ D(("converstation returned [%s] but gave no reply",
+ pam_strerror(pamh, retval)));
+ new->state = PS_STATE_DEAD;
+ return PAM_CONV_ERR;
+ }
+
+ /* find | */
+ length = PAM_BP_LENGTH(new->current_reply);
+ for (i=0; i<length; ++i) {
+ if (PAM_BP_RDATA(new->current_reply)[i] == '|') {
+ break;
+ }
+ }
+ if (i >= length) {
+ D(("malformed response (no |) of length %d", length));
+ new->state = PS_STATE_DEAD;
+ return PAM_CONV_ERR;
+ }
+ if ((length - ++i) != 32) {
+ D(("cookie is incorrect length (%d,%d) %d != 32",
+ length, i, length-i));
+ new->state = PS_STATE_DEAD;
+ return PAM_CONV_ERR;
+ }
+
+ /* copy client cookie */
+ memcpy(new->client_cookie, PAM_BP_RDATA(new->current_reply)+i, 32);
+
+ /* generate a prompt that is length(seqid) + length(|) + 32 long */
+ PAM_BP_RENEW(&new->current_prompt, PAM_BPC_OK, i+32);
+ /* copy the head of the response prompt */
+ memcpy(PAM_BP_WDATA(new->current_prompt),
+ PAM_BP_RDATA(new->current_reply), i);
+ PAM_BP_RENEW(&new->current_reply, 0, 0);
+
+ /* look up the secret */
+ new->invalid_secret = 0;
+
+ if (new->secret_data == NULL) {
+ char *ruser_rhost;
+
+ ruser_rhost = malloc(strlen(rusername)+2+strlen(rhostname));
+ if (ruser_rhost == NULL) {
+ D(("out of memory"));
+ new->state = PS_STATE_DEAD;
+ return PAM_BUF_ERR;
+ }
+ sprintf(ruser_rhost, "%s@%s", rusername, rhostname);
+ new->secret_data = identify_secret(ruser_rhost, new->username);
+
+ memset(ruser_rhost, 0, strlen(ruser_rhost));
+ free(ruser_rhost);
+ }
+
+ if (new->secret_data == NULL) {
+ D(("secret not found for user"));
+ new->invalid_secret = 1;
+
+ /* need to make up a secret */
+ new->secret_data = malloc(32 + 1);
+ if (new->secret_data == NULL) {
+ D(("out of memory"));
+ new->state = PS_STATE_DEAD;
+ return PAM_BUF_ERR;
+ }
+ if (! generate_cookie(new->secret_data)) {
+ D(("what's up - no fake cookie generated?"));
+ new->state = PS_STATE_DEAD;
+ return PAM_ABORT;
+ }
+ }
+
+ /* construct md5[<client_cookie>|<server_cookie>|<secret_data>] */
+ if (! create_digest(new->client_cookie, new->server_cookie,
+ new->secret_data,
+ PAM_BP_WDATA(new->current_prompt)+i)) {
+ D(("md5 digesting failed"));
+ new->state = PS_STATE_DEAD;
+ return PAM_ABORT;
+ }
+
+ /* prompt2 is now constructed - fall through to send it */
+ }
+
+ case PS_STATE_PROMPT2:
+ {
+ /* send {<seqid>|md5[<client_cookie>|<server_cookie>|<secret_data>]} */
+ retval = converse(pamh, new);
+ if (retval != PAM_SUCCESS) {
+ if (retval == PAM_CONV_AGAIN) {
+ D(("conversation failed to complete"));
+ return PAM_INCOMPLETE;
+ } else {
+ new->state = PS_STATE_DEAD;
+ return retval;
+ }
+ }
+
+ /* After we complete this section, we should not be able to
+ recall this authentication function. So, we force all
+ future calls into the weeds. */
+
+ new->state = PS_STATE_DEAD;
+
+ /* expect reply:{md5[<secret_data>|<server_cookie>|<client_cookie>]} */
+
+ {
+ int cf;
+ char expectation[33];
+
+ if (!create_digest(new->secret_data, new->server_cookie,
+ new->client_cookie, expectation)) {
+ new->state = PS_STATE_DEAD;
+ return PAM_ABORT;
+ }
+
+ cf = strcmp(expectation, PAM_BP_RDATA(new->current_reply));
+ memset(expectation, 0, sizeof(expectation));
+ if (cf || new->invalid_secret) {
+ D(("failed to authenticate"));
+ return PAM_AUTH_ERR;
+ }
+ }
+
+ D(("correctly authenticated :)"));
+ return PAM_SUCCESS;
+ }
+
+ default:
+ new->state = PS_STATE_DEAD;
+
+ case PS_STATE_DEAD:
+
+ D(("state is currently dead/unknown"));
+ return PAM_AUTH_ERR;
+ }
+
+ fprintf(stderr, "pam_secret: this should not be reached\n");
+ return PAM_ABORT;
+}
+
+static void clean_data(pam_handle_t *pamh, void *datum, int error_status)
+{
+ struct ps_state_s *data = datum;
+
+ D(("liberating datum=%p", datum));
+
+ if (data) {
+ D(("renew prompt"));
+ PAM_BP_RENEW(&data->current_prompt, 0, 0);
+ D(("renew reply"));
+ PAM_BP_RENEW(&data->current_reply, 0, 0);
+ D(("overwrite datum"));
+ memset(data, 0, sizeof(struct ps_state_s));
+ D(("liberate datum"));
+ free(data);
+ }
+
+ D(("done."));
+}
+
+/*
+ * front end for the authentication function
+ */
+
+int pam_sm_authenticate(pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
+{
+ int retval;
+ struct ps_state_s *new_data;
+ const struct ps_state_s *old_data;
+
+ D(("called"));
+
+ new_data = calloc(1, sizeof(struct ps_state_s));
+ if (new_data == NULL) {
+ D(("out of memory"));
+ return PAM_BUF_ERR;
+ }
+ new_data->retval = PAM_SUCCESS;
+
+ retval = pam_get_data(pamh, PS_STATE_ID, (const void **) &old_data);
+ if (retval == PAM_SUCCESS) {
+ new_data->state = old_data->state;
+ memcpy(new_data->server_cookie, old_data->server_cookie, 32);
+ memcpy(new_data->client_cookie, old_data->client_cookie, 32);
+ if (old_data->username) {
+ new_data->username = strdup(old_data->username);
+ }
+ if (old_data->secret_data) {
+ new_data->secret_data = strdup(old_data->secret_data);
+ }
+ if (old_data->current_prompt) {
+ int length;
+
+ length = PAM_BP_LENGTH(old_data->current_prompt);
+ PAM_BP_RENEW(&new_data->current_prompt,
+ PAM_BP_CONTROL(old_data->current_prompt), length);
+ PAM_BP_FILL(new_data->current_prompt, 0, length,
+ PAM_BP_RDATA(old_data->current_prompt));
+ }
+ /* don't need to duplicate current_reply */
+ } else {
+ old_data = NULL;
+ new_data->state = PS_STATE_INIT;
+ }
+
+ D(("call auth_sequence"));
+ new_data->retval = auth_sequence(pamh, old_data, new_data);
+ D(("returned from auth_sequence"));
+
+ retval = pam_set_data(pamh, PS_STATE_ID, new_data, clean_data);
+ if (retval != PAM_SUCCESS) {
+ D(("unable to store new_data"));
+ } else {
+ retval = new_data->retval;
+ }
+
+ old_data = new_data = NULL;
+
+ D(("done (%d)", retval));
+ return retval;
+}
+
+/*
+ * front end for the credential setting function
+ */
+
+#define AUTH_SESSION_TICKET_ENV_FORMAT "AUTH_SESSION_TICKET="
+
+int pam_sm_setcred(pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
+{
+ int retval;
+ const struct ps_state_s *old_data;
+
+ D(("called"));
+
+ /* XXX - need to pay attention to the various flavors of call */
+
+ /* XXX - need provide an option to turn this feature on/off: if
+ other modules want to supply an AUTH_SESSION_TICKET, we should
+ leave it up to the admin which module dominiates. */
+
+ retval = pam_get_data(pamh, PS_STATE_ID, (const void **) &old_data);
+ if (retval != PAM_SUCCESS) {
+ D(("no data to base decision on"));
+ return PAM_AUTH_ERR;
+ }
+
+ /*
+ * If ok, export a derived shared secret session ticket to the
+ * client's PAM environment - the ticket has the form
+ *
+ * AUTH_SESSION_TICKET =
+ * md5[<server_cookie>|<secret_data>|<client_cookie>]
+ *
+ * This is a precursor to supporting a spoof resistant trusted
+ * path mechanism. This shared secret ticket can be used to add
+ * a hard-to-guess checksum to further authentication data.
+ */
+
+ retval = old_data->retval;
+ if (retval == PAM_SUCCESS) {
+ char envticket[sizeof(AUTH_SESSION_TICKET_ENV_FORMAT)+32];
+
+ memcpy(envticket, AUTH_SESSION_TICKET_ENV_FORMAT,
+ sizeof(AUTH_SESSION_TICKET_ENV_FORMAT));
+
+ if (! create_digest(old_data->server_cookie, old_data->secret_data,
+ old_data->client_cookie,
+ envticket+sizeof(AUTH_SESSION_TICKET_ENV_FORMAT)-1
+ )) {
+ D(("unable to generate a digest for session ticket"));
+ return PAM_ABORT;
+ }
+
+ D(("putenv[%s]", envticket));
+ retval = pam_putenv(pamh, envticket);
+ memset(envticket, 0, sizeof(envticket));
+ }
+
+ old_data = NULL;
+ D(("done (%d)", retval));
+
+ return retval;
+}
diff --git a/libpamc/test/regress/Makefile b/libpamc/test/regress/Makefile
new file mode 100644
index 00000000..cba474f9
--- /dev/null
+++ b/libpamc/test/regress/Makefile
@@ -0,0 +1,7 @@
+CFLAGS = -g -I ../../include
+
+test.libpamc: test.libpamc.o
+ $(CC) -o $@ $(CFLAGS) $< -L ../.. -lpamc
+
+clean:
+ rm -f test.libpamc test.libpamc.o
diff --git a/libpamc/test/regress/run_test.sh b/libpamc/test/regress/run_test.sh
new file mode 100755
index 00000000..6922f03d
--- /dev/null
+++ b/libpamc/test/regress/run_test.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+LD_LIBRARY_PATH=../.. ; export LD_LIBRARY_PATH
+PAMC_AGENT_PATH="../agents" ; export PAMC_AGENT_PATH
+
+./test.libpamc
diff --git a/libpamc/test/regress/test.libpamc.c b/libpamc/test/regress/test.libpamc.c
new file mode 100644
index 00000000..b7bc4e4b
--- /dev/null
+++ b/libpamc/test/regress/test.libpamc.c
@@ -0,0 +1,342 @@
+/*
+ * This is a small test program for testing libpamc against the
+ * secret@here agent. It does the same as the test.secret@here perl
+ * script in this directory, but via the libpamc API.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <security/pam_client.h>
+#include <ctype.h>
+
+struct internal_packet {
+ int length;
+ int at;
+ char *buffer;
+};
+
+
+void append_data(struct internal_packet *packet, int extra, const char *data)
+{
+ if ((extra + packet->at) >= packet->length) {
+ if (packet->length == 0) {
+ packet->length = 1000;
+ }
+ /* make sure we have at least a char extra space available */
+ while (packet->length <= (extra + packet->at)) {
+ packet->length <<= 1;
+ }
+ packet->buffer = realloc(packet->buffer, packet->length);
+ if (packet->buffer == NULL) {
+ fprintf(stderr, "out of memory\n");
+ exit(1);
+ }
+ }
+
+ if (data != NULL) {
+ memcpy(packet->at + packet->buffer, data, extra);
+ }
+ packet->at += extra;
+
+ /* assisting string manipulation */
+ packet->buffer[packet->at] = '\0';
+}
+
+void append_string(struct internal_packet *packet, const char *string,
+ int with_nul)
+{
+ append_data(packet, strlen(string) + (with_nul ? 1:0), string);
+}
+
+char *identify_secret(char *identity)
+{
+ struct internal_packet temp_packet;
+ FILE *secrets;
+ int length_id;
+
+ temp_packet.length = temp_packet.at = 0;
+ temp_packet.buffer = NULL;
+
+ append_string(&temp_packet, "/home/", 0);
+ append_string(&temp_packet, getlogin(), 0);
+ append_string(&temp_packet, "/.secret@here", 1);
+
+ secrets = fopen(temp_packet.buffer, "r");
+ if (secrets == NULL) {
+ fprintf(stderr, "server: failed to open\n [%s]\n",
+ temp_packet.buffer);
+ exit(1);
+ }
+
+ length_id = strlen(identity);
+ for (;;) {
+ char *secret = NULL;
+ temp_packet.at = 0;
+
+ if (fgets(temp_packet.buffer, temp_packet.length, secrets) == NULL) {
+ fclose(secrets);
+ return NULL;
+ }
+
+ if (memcmp(temp_packet.buffer, identity, length_id)) {
+ continue;
+ }
+
+ fclose(secrets);
+ for (secret=temp_packet.buffer; *secret; ++secret) {
+ if (*secret == ' ' || *secret == '\n' || *secret == '\t') {
+ break;
+ }
+ }
+ for (; *secret; ++secret) {
+ if (!(*secret == ' ' || *secret == '\n' || *secret == '\t')) {
+ break;
+ }
+ }
+
+ for (temp_packet.buffer=secret; *temp_packet.buffer;
+ ++temp_packet.buffer) {
+ if (*temp_packet.buffer == ' ' || *temp_packet.buffer == '\n'
+ || *temp_packet.buffer == '\t') {
+ break;
+ }
+ }
+ if (*temp_packet.buffer) {
+ *temp_packet.buffer = '\0';
+ }
+
+ return secret;
+ }
+
+ /* NOT REACHED */
+}
+
+/*
+ * This is a hack, and is fundamentally insecure. All our secrets will be
+ * displayed on the command line for someone doing 'ps' to see. This
+ * is just for programming convenience in this instance, since this
+ * program is simply a regression test. The pam_secret module should
+ * not do this, but make use of md5 routines directly.
+ */
+
+char *create_digest(int length, const char *raw)
+{
+ struct internal_packet temp_packet;
+ FILE *pipe;
+
+ temp_packet.length = temp_packet.at = 0;
+ temp_packet.buffer = NULL;
+
+ append_string(&temp_packet, "echo -n '", 0);
+ append_string(&temp_packet, raw, 0);
+ append_string(&temp_packet, "'|/usr/bin/md5sum -", 1);
+
+ fprintf(stderr, "am attempting to run [%s]\n", temp_packet.buffer);
+
+ pipe = popen(temp_packet.buffer, "r");
+ if (pipe == NULL) {
+ fprintf(stderr, "server: failed to run\n [%s]\n", temp_packet.buffer);
+ exit(1);
+ }
+
+ temp_packet.at = 0;
+ append_data(&temp_packet, 32, NULL);
+
+ if (fgets(temp_packet.buffer, 33, pipe) == NULL) {
+ fprintf(stderr, "server: failed to read digest\n");
+ exit(1);
+ }
+ if (strlen(temp_packet.buffer) != 32) {
+ fprintf(stderr, "server: digest was not 32 chars?? [%s]\n",
+ temp_packet.buffer);
+ exit(1);
+ }
+
+ fclose(pipe);
+
+ return temp_packet.buffer;
+}
+
+void packet_to_prompt(pamc_bp_t *prompt_p, __u8 control,
+ struct internal_packet *packet)
+{
+ PAM_BP_RENEW(prompt_p, control, packet->at);
+ PAM_BP_FILL(*prompt_p, 0, packet->at, packet->buffer);
+ packet->at = 0;
+}
+
+void prompt_to_packet(pamc_bp_t prompt, struct internal_packet *packet)
+{
+ int data_length;
+
+ data_length = PAM_BP_LENGTH(prompt);
+ packet->at = 0;
+ append_data(packet, data_length, NULL);
+
+ PAM_BP_EXTRACT(prompt, 0, data_length, packet->buffer);
+
+ fprintf(stderr, "server received[%d]: {%d|0x%.2x|%s}\n",
+ data_length,
+ PAM_BP_SIZE(prompt), PAM_BP_RCONTROL(prompt),
+ PAM_BP_RDATA(prompt));
+}
+
+int main(int argc, char **argv)
+{
+ pamc_handle_t pch;
+ pamc_bp_t prompt = NULL;
+ struct internal_packet packet_data, *packet;
+ char *temp_string, *secret, *user, *a_cookie, *seqid, *digest;
+ const char *cookie = "123451234512345";
+ int retval;
+
+ packet = &packet_data;
+ packet->length = 0;
+ packet->at = 0;
+ packet->buffer = NULL;
+
+ pch = pamc_start();
+ if (pch == NULL) {
+ fprintf(stderr, "server: unable to get a handle from libpamc\n");
+ exit(1);
+ }
+
+ temp_string = getlogin();
+ if (temp_string == NULL) {
+ fprintf(stderr, "server: who are you?\n");
+ exit(1);
+ }
+#define DOMAIN "@local.host"
+ user = malloc(1+strlen(temp_string)+strlen(DOMAIN));
+ if (user == NULL) {
+ fprintf(stderr, "server: out of memory for user id\n");
+ exit(1);
+ }
+ sprintf(user, "%s%s", temp_string, DOMAIN);
+
+ append_string(packet, "secret@here/", 0);
+ append_string(packet, user, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, cookie, 0);
+ packet_to_prompt(&prompt, PAM_BPC_SELECT, packet);
+
+ /* get the library to accept the first packet (which should load
+ the secret@here agent) */
+
+ retval = pamc_converse(pch, &prompt);
+ fprintf(stderr, "server: after conversation\n");
+ if (PAM_BP_RCONTROL(prompt) != PAM_BPC_OK) {
+ fprintf(stderr, "server: prompt had unexpected control type: %u\n",
+ PAM_BP_RCONTROL(prompt));
+ exit(1);
+ }
+
+ fprintf(stderr, "server: got a prompt back\n");
+
+ prompt_to_packet(prompt, packet);
+
+ temp_string = strtok(packet->buffer, "|");
+ if (temp_string == NULL) {
+ fprintf(stderr, "server: prompt does not contain anything");
+ exit(1);
+ }
+ seqid = strdup(temp_string);
+ if (seqid == NULL) {
+ fprintf(stderr, "server: unable to store sequence id\n");
+ }
+
+ temp_string = strtok(NULL, "|");
+ if (temp_string == NULL) {
+ fprintf(stderr, "server: no cookie from agent\n");
+ exit(1);
+ }
+ a_cookie = strdup(temp_string);
+ if (a_cookie == NULL) {
+ fprintf(stderr, "server: no memory to store agent cookie\n");
+ exit(1);
+ }
+
+ fprintf(stderr, "server: agent responded with {%s|%s}\n", seqid, a_cookie);
+ secret = identify_secret(user);
+ fprintf(stderr, "server: secret=%s\n", secret);
+
+ /* now, we construct the response */
+ packet->at = 0;
+ append_string(packet, a_cookie, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, cookie, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, secret, 0);
+
+ fprintf(stderr, "server: get digest of %s\n", packet->buffer);
+
+ digest = create_digest(packet->at, packet->buffer);
+
+ fprintf(stderr, "server: secret=%s, digest=%s\n", secret, digest);
+
+ packet->at = 0;
+ append_string(packet, seqid, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, digest, 0);
+ packet_to_prompt(&prompt, PAM_BPC_OK, packet);
+
+ retval = pamc_converse(pch, &prompt);
+ fprintf(stderr, "server: after 2nd conversation\n");
+ if (PAM_BP_RCONTROL(prompt) != PAM_BPC_DONE) {
+ fprintf(stderr, "server: 2nd prompt had unexpected control type: %u\n",
+ PAM_BP_RCONTROL(prompt));
+ exit(1);
+ }
+
+ prompt_to_packet(prompt, packet);
+ PAM_BP_RENEW(&prompt, 0, 0);
+
+ temp_string = strtok(packet->buffer, "|");
+ if (temp_string == NULL) {
+ fprintf(stderr, "no digest from agent\n");
+ exit(1);
+ }
+ temp_string = strdup(temp_string);
+
+ packet->at = 0;
+ append_string(packet, secret, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, cookie, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, a_cookie, 0);
+
+ fprintf(stderr, "server: get digest of %s\n", packet->buffer);
+
+ digest = create_digest(packet->at, packet->buffer);
+
+ fprintf(stderr, "server: digest=%s\n", digest);
+
+ if (strcmp(digest, temp_string)) {
+ fprintf(stderr, "server: agent doesn't know the secret\n");
+ fprintf(stderr, "server: agent says: [%s]\n"
+ "server: server says: [%s]\n", temp_string, digest);
+ exit(1);
+ } else {
+ fprintf(stderr, "server: agent seems to know the secret\n");
+
+ packet->at = 0;
+ append_string(packet, cookie, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, secret, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, a_cookie, 0);
+
+ digest = create_digest(packet->at, packet->buffer);
+
+ fprintf(stderr, "server: putenv(\"AUTH_SESSION_TICKET=%s\")\n",
+ digest);
+ }
+
+
+ retval = pamc_end(&pch);
+
+ fprintf(stderr, "server: agent(s) were %shappy to terminate\n",
+ retval == PAM_BPC_TRUE ? "":"un");
+
+ exit(!retval);
+}
diff --git a/libpamc/test/regress/test.secret@here b/libpamc/test/regress/test.secret@here
new file mode 100755
index 00000000..2e0b9b94
--- /dev/null
+++ b/libpamc/test/regress/test.secret@here
@@ -0,0 +1,152 @@
+#!/usr/bin/perl
+
+##
+## this is a test script for regressing changes to the secret@here PAM
+## agent
+##
+
+$^W = 1;
+use strict;
+use IPC::Open2;
+
+$| = 1;
+
+my $whoami = `/usr/bin/whoami`; chomp $whoami;
+my $cookie = "12345";
+my $user_domain = "$whoami\@local.host";
+
+my $pid = open2(\*Reader, \*Writer, "../agents/secret\@here blah")
+ or die "failed to load secret\@here agent";
+
+unless (-f (getpwuid($<))[7]."/.secret\@here") {
+ print STDERR "server: ". "no " .(getpwuid($<))[7]. "/.secret\@here file\n";
+ die "no config file";
+}
+
+WriteBinaryPrompt(\*Writer, 0x02, "secret\@here/$user_domain|$cookie");
+
+my ($control, $data) = ReadBinaryPrompt(\*Reader);
+
+print STDERR "server: ". "reply: control=$control, data=$data\n";
+if ($control != 1) {
+ die "expected 1 (OK) for the first agent reply; got $control";
+}
+my ($seqid, $a_cookie) = split '\|', $data;
+
+# server needs to convince agent that it knows the secret before
+# agent will give a valid response
+my $secret = IdentifyLocalSecret($user_domain);
+my $digest = CreateDigest($a_cookie."|".$cookie."|".$secret);
+
+print STDERR "server: ". "digest = $digest\n";
+WriteBinaryPrompt(\*Writer, 0x01, "$seqid|$digest");
+
+# The agent will authenticate us and then reply with its
+# authenticating digest. we check that before we're done.
+
+($control, $data) = ReadBinaryPrompt(\*Reader);
+if ($control != 0x03) {
+ die "server: agent did not reply with a 'done' prompt ($control)\n";
+}
+
+unless ($data eq CreateDigest($secret."|".$cookie."|".$a_cookie)) {
+ die "server: agent is not authenticated\n";
+}
+
+print STDERR "server: agent appears to know secret\n";
+
+my $session_authenticated_ticket
+ = CreateDigest($cookie."|".$secret."|".$a_cookie);
+
+print STDERR "server: should putenv("
+ ."\"AUTH_SESSION_TICKET=$session_authenticated_ticket\")\n";
+
+exit 0;
+
+sub CreateDigest ($) {
+ my ($data) = @_;
+
+ my $pid = open2(\*MD5out, \*MD5in, "/usr/bin/md5sum -")
+ or die "you'll need /usr/bin/md5sum installed";
+
+ my $oldfd = select MD5in; $|=1; select $oldfd;
+ print MD5in "$data";
+ close MD5in;
+ my $reply = <MD5out>;
+ ($reply) = split /\s/, $reply;
+ print STDERR "server: ". "md5 said: <$reply>\n";
+ close MD5out;
+
+ return $reply;
+}
+
+sub ReadBinaryPrompt ($) {
+ my ($fd) = @_;
+
+ my $buffer = " ";
+ my $count = read($fd, $buffer, 5);
+ if ($count == 0) {
+ # no more packets to read
+ return (0, "");
+ }
+
+ if ($count != 5) {
+ # broken packet header
+ return (-1, "");
+ }
+
+ my ($length, $control) = unpack("N C", $buffer);
+ if ($length < 5) {
+ # broken packet length
+ return (-1, "");
+ }
+
+ my $data = "";
+ $length -= 5;
+ while ($count = read($fd, $buffer, $length)) {
+ $data .= $buffer;
+ if ($count != $length) {
+ $length -= $count;
+ next;
+ }
+
+ print STDERR "server: ". "data is [$data]\n";
+
+ return ($control, $data);
+ }
+
+ # broken packet data
+ return (-1, "");
+}
+
+sub WriteBinaryPrompt ($$$) {
+ my ($fd, $control, $data) = @_;
+
+ my $length = 5 + length($data);
+ printf STDERR "server: ". "{%d|0x%.2x|%s}\n", $length, $control, $data;
+ my $bp = pack("N C a*", $length, $control, $data);
+ print $fd $bp;
+
+ print STDERR "server: ". "control passed to agent\@here\n";
+}
+
+sub IdentifyLocalSecret ($) {
+ my ($identifier) = @_;
+ my $secret;
+
+ my $whoami = `/usr/bin/whoami` ; chomp $whoami;
+ if (open SECRETS, "< " .(getpwuid($<))[7]. "/.secret\@here") {
+ my $line;
+ while (defined ($line = <SECRETS>)) {
+ my ($id, $sec) = split /[\s]/, $line;
+ if ((defined $id) && ($id eq $identifier)) {
+ $secret = $sec;
+ last;
+ }
+ }
+ close SECRETS;
+ }
+
+ return $secret;
+}
+