diff options
Diffstat (limited to 'libpamc/test')
-rw-r--r-- | libpamc/test/Makefile.am | 11 | ||||
-rw-r--r-- | libpamc/test/Makefile.in | 504 | ||||
-rwxr-xr-x | libpamc/test/agents/secret@here | 307 | ||||
-rw-r--r-- | libpamc/test/modules/Makefile | 9 | ||||
-rw-r--r-- | libpamc/test/modules/pam_secret.c | 669 | ||||
-rw-r--r-- | libpamc/test/regress/Makefile | 7 | ||||
-rwxr-xr-x | libpamc/test/regress/run_test.sh | 6 | ||||
-rw-r--r-- | libpamc/test/regress/test.libpamc.c | 342 | ||||
-rwxr-xr-x | libpamc/test/regress/test.secret@here | 151 |
9 files changed, 2006 insertions, 0 deletions
diff --git a/libpamc/test/Makefile.am b/libpamc/test/Makefile.am new file mode 100644 index 0000000..fc0cac4 --- /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 0000000..827c574 --- /dev/null +++ b/libpamc/test/Makefile.in @@ -0,0 +1,504 @@ +# Makefile.in generated by automake 1.13.4 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 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@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = libpamc/test +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \ + $(top_srcdir)/m4/japhar_grep_cflags.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/ld-no-undefined.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/libtool.m4 \ + $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ + $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/m4/nls.m4 $(top_srcdir)/m4/po.m4 \ + $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BROWSER = @BROWSER@ +BUILD_CFLAGS = @BUILD_CFLAGS@ +BUILD_CPPFLAGS = @BUILD_CPPFLAGS@ +BUILD_LDFLAGS = @BUILD_LDFLAGS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FO2PDF = @FO2PDF@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +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@ +LD = @LD@ +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@ +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@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NIS_CFLAGS = @NIS_CFLAGS@ +NIS_LIBS = @NIS_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NSL_CFLAGS = @NSL_CFLAGS@ +NSL_LIBS = @NSL_LIBS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POSUB = @POSUB@ +RANLIB = @RANLIB@ +SCONFIGDIR = @SCONFIGDIR@ +SECUREDIR = @SECUREDIR@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +TIRPC_CFLAGS = @TIRPC_CFLAGS@ +TIRPC_LIBS = @TIRPC_LIBS@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +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_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +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_O1 = @pam_cv_ld_O1@ +pam_cv_ld_as_needed = @pam_cv_ld_as_needed@ +pam_cv_ld_no_undefined = @pam_cv_ld_no_undefined@ +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_build_prefix = @top_build_prefix@ +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 ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnits libpamc/test/Makefile'; \ + $(am__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 +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +tags TAGS: + +ctags CTAGS: + +cscope cscopelist: + + +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 "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: all all-am check check-am clean clean-generic clean-libtool \ + cscopelist-am ctags-am distclean distclean-generic \ + distclean-libtool distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags-am uninstall uninstall-am + + +# 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 0000000..8d82c01 --- /dev/null +++ b/libpamc/test/agents/secret@here @@ -0,0 +1,307 @@ +#!/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 0000000..4806546 --- /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 0000000..f1c74c6 --- /dev/null +++ b/libpamc/test/modules/pam_secret.c @@ -0,0 +1,669 @@ +/* + * $Id$ + * + * 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. + */ + +#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 PAM_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 0000000..cba474f --- /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 0000000..6922f03 --- /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 0000000..b0fff9c --- /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, u_int8_t 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 0000000..67fe22e --- /dev/null +++ b/libpamc/test/regress/test.secret@here @@ -0,0 +1,151 @@ +#!/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; +} |