about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2018-02-04 23:22:53 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2018-02-04 23:22:53 +0000
commit03012f54b1bcd31e0b817fc0222a9a47709c4018 (patch)
treed53c568d1e14bae1e1e0efadab8b0bf3e66982aa
downloadpamela-03012f54b1bcd31e0b817fc0222a9a47709c4018.tar.gz
pamela-03012f54b1bcd31e0b817fc0222a9a47709c4018.tar.xz
pamela-03012f54b1bcd31e0b817fc0222a9a47709c4018.zip
Initial commit
-rw-r--r--.gitignore7
-rw-r--r--AUTHORS5
-rw-r--r--COPYING13
-rw-r--r--INSTALL187
-rw-r--r--Makefile151
-rw-r--r--NEWS6
-rw-r--r--README28
-rw-r--r--README.macosx4
-rw-r--r--README.solaris16
-rwxr-xr-xconfigure461
-rw-r--r--doc/building.html103
-rw-r--r--doc/index.html138
-rw-r--r--doc/pamelad.html68
-rw-r--r--doc/upgrade.html28
-rw-r--r--package/deps-build1
-rw-r--r--package/deps.mak45
-rw-r--r--package/info4
-rw-r--r--package/modes1
-rw-r--r--package/targets.mak14
-rwxr-xr-xpatch-for-solaris21
-rw-r--r--src/include/pamela/common.h101
-rw-r--r--src/include/pamela/pam.h161
-rw-r--r--src/include/pamela/pamela.h80
-rw-r--r--src/pamela/deps-exe/pamelad2
-rw-r--r--src/pamela/deps-lib/pamela27
-rw-r--r--src/pamela/pam_acct_mgmt.c11
-rw-r--r--src/pamela/pam_authenticate.c11
-rw-r--r--src/pamela/pam_chauthtok.c11
-rw-r--r--src/pamela/pam_close_session.c11
-rw-r--r--src/pamela/pam_end.c17
-rw-r--r--src/pamela/pam_fail_delay.c11
-rw-r--r--src/pamela/pam_get_item.c52
-rw-r--r--src/pamela/pam_getenv.c32
-rw-r--r--src/pamela/pam_getenvlist.c42
-rw-r--r--src/pamela/pam_open_session.c11
-rw-r--r--src/pamela/pam_putenv.c14
-rw-r--r--src/pamela/pam_set_item.c50
-rw-r--r--src/pamela/pam_setcred.c11
-rw-r--r--src/pamela/pam_start.c34
-rw-r--r--src/pamela/pam_strerror.c17
-rw-r--r--src/pamela/pamela-internal.h13
-rw-r--r--src/pamela/pamela_end.c16
-rw-r--r--src/pamela/pamela_get_item.c11
-rw-r--r--src/pamela/pamela_getenvlist.c9
-rw-r--r--src/pamela/pamela_op.c123
-rw-r--r--src/pamela/pamela_pam_response_free.c11
-rw-r--r--src/pamela/pamela_query_string.c20
-rw-r--r--src/pamela/pamela_set_item.c16
-rw-r--r--src/pamela/pamela_set_item_internal.c14
-rw-r--r--src/pamela/pamela_set_itemv.c14
-rw-r--r--src/pamela/pamela_startf.c40
-rw-r--r--src/pamela/pamela_strerror.c11
-rw-r--r--src/pamela/pamela_zero.c5
-rw-r--r--src/pamela/pamelad.c385
-rwxr-xr-xtools/gen-deps.sh93
-rwxr-xr-xtools/install.sh64
56 files changed, 2852 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3d0b823
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+*.o
+*.lo
+/config.mak
+/src/include/pamela/config.h
+/pamelad
+/libpamela.a.xyzzy
+/libpamela.so.xyzzy
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..6b17ed5
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,5 @@
+Main author:
+  Laurent Bercot <ska-skaware@skarnet.org>
+
+Contributors:
+  A. Wilcox <AWilcox@Wilcox-Tech.com>
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..361dcb6
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,13 @@
+Copyright (c) 2018 Laurent Bercot <ska-skaware@skarnet.org>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..0283d17
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,187 @@
+Build Instructions
+------------------
+
+* Requirements
+  ------------
+
+  - A POSIX-compliant C development environment
+  - GNU make version 3.81 or later
+  - skalibs version 2.6.3.2 or later: http://skarnet.org/software/skalibs/
+  - Linux-PAM version 1.3.0 or later: http://www.linux-pam.org/library/
+
+ This software will run on any operating system that implements
+POSIX.1-2008, available at:
+  http://pubs.opengroup.org/onlinepubs/9699919799/
+as well as Linux-PAM. As of February 2018, this means only Linux.
+
+
+* Standard usage
+  --------------
+
+  ./configure && make && sudo make install
+
+ will work for most users.
+ It will install the binaries in /bin and the static libraries in
+/usr/lib/pamela.
+
+ Please note that static libraries in /usr/lib/pamela *will not*
+be found by a default linker invocation: you need -L/usr/lib/pamela.
+Other skarnet.org software automatically handles that case if the
+default configuration is used, but if you change the configuration,
+remember to use the appropriate --with-lib configure option.
+
+ You can strip the binaries and libraries of their extra symbols via
+"make strip" before the "make install" phase. It will shave a few bytes
+off them.
+
+ The PAM declarations for applications are available in the pamela/pam.h
+header. They are not made available in security/pam_appl.h by default
+in order to avoid conflicting with a Linux-PAM installation. If you
+want to make pamela your default PAM implementation, run
+
+  sudo make install-symlink
+
+which will make security/pam_appl.h a symlink to pamela/pam.h,
+possibly overwriting any previous security/pam_appl.h file.
+Please note that if you try and build pamela again *after* doing
+this, the pamelad binary (which expects Linux-PAM, not pamela,
+in security/pam_appl.h) may not be built correctly.
+
+
+* Customization
+  -------------
+
+ You can customize paths via flags given to configure.
+ See ./configure --help for a list of all available configure options.
+
+
+* Environment variables
+  ---------------------
+
+ Controlling a build process via environment variables is a big and
+dangerous hammer. You should try and pass flags to configure instead;
+nevertheless, a few standard environment variables are recognized.
+
+ If the CC environment variable is set, its value will override compiler
+detection by configure. The --host=HOST option will still add a HOST-
+prefix to the value of CC.
+
+ The values of CFLAGS, CPPFLAGS and LDFLAGS will be appended to flags
+auto-detected by configure. To entirely override the flags set by
+configure instead, use make variables.
+
+
+* Make variables
+  --------------
+
+ You can invoke make with a few variables for more configuration.
+
+ CC, CFLAGS, CPPFLAGS, LDFLAGS, LDLIBS, AR, RANLIB, STRIP, INSTALL and
+CROSS_COMPILE can all be overridden on the make command line. This is
+an even bigger hammer than running ./configure with environment
+variables, so it is advised to only do this when it is the only way of
+obtaining the behaviour you want.
+
+ DESTDIR can be given on the "make install" command line in order to
+install to a staging directory.
+
+
+* Shared libraries
+  ----------------
+
+ Software from skarnet.org is small enough that shared libraries are
+generally not worth using. Static linking is simpler and incurs less
+runtime overhead and less points of failure: so by default, shared
+libraries are not built and binaries are linked against the static
+versions of the skarnet.org libraries. Nevertheless, you can:
+  * build shared libraries: --enable-shared
+  * link binaries against shared libraries: --disable-allstatic
+
+
+* Static binaries
+  ---------------
+
+ By default, binaries are linked against static versions of all the
+libraries they depend on, except for the libc. You can enforce
+linking against the static libc with --enable-static-libc.
+
+ Note that the pamelad binary needs to be able to load the PAM
+modules, so it requires dynamic linking. Attempting to statically
+link the pamela package is strongly discouraged.
+
+
+* Cross-compilation
+  -----------------
+
+ skarnet.org packages centralize all the difficulty of
+cross-compilation in one place: skalibs. Once you have
+cross-compiled skalibs, the rest is easy.
+
+ * Use the --host=HOST option to configure, HOST being the triplet
+for your target.
+ * Make sure your cross-toolchain binaries (i.e. prefixed with HOST-)
+are accessible via your PATH environment variable.
+ * Make sure to use the correct version of skalibs for your target,
+and the correct sysdeps directory, making use of the
+--with-include, --with-lib, --with-dynlib and --with-sysdeps
+options as necessary.
+
+
+* The slashpackage convention
+  ---------------------------
+
+ The slashpackage convention (http://cr.yp.to/slashpackage.html)
+is a package installation scheme that provides a few guarantees
+over other conventions such as the FHS, for instance fixed
+absolute pathnames. skarnet.org packages support it: use the
+--enable-slashpackage option to configure, or
+--enable-slashpackage=DIR for a prefixed DIR/package tree.
+This option will activate slashpackage support during the build
+and set slashpackage-compatible installation directories.
+If $package_home is the home of the package, defined as
+DIR/package/$category/$package-$version with the variables
+read from the package/info file, then:
+
+  --dynlibdir is set to $package_home/library.so
+  --bindir is set to $package_home/command
+  --sbindir is also set to $package_home/command (slashpackage
+differentiates root-only binaries by their Unix rights, not their
+location in the filesystem)
+  --libexecdir is also set to $package_home/command (slashpackage
+does not need a specific directory for internal binaries)
+  --libdir is set to $package_home/library
+  --includedir is set to $package_home/include
+
+ --prefix is pretty much ignored when you use --enable-slashpackage.
+You should probably not use both --enable-slashpackage and --prefix.
+
+ When using slashpackage, two additional Makefile targets are
+available after "make install":
+ - "make update" changes the default version of the software to the
+freshly installed one. (This is useful when you have several installed
+versions of the same software, which slashpackage supports.)
+ - "make -L global-links" adds links from /command and /library.so to the
+default version of the binaries and shared libraries. The "-L" option to
+make is necessary because targets are symbolic links, and the default make
+behaviour is to check the pointed file's timestamp and not the symlink's
+timestamp.
+
+
+* Absolute pathnames
+  ------------------
+
+ You may want to use fixed absolute pathnames even if you're not
+following the slashpackage convention: for instance, the Nix packaging
+system prefers calling binaries with immutable paths rather than rely on
+PATH resolution. If you are in that case, use the --enable-absolute-paths
+option to configure. This will ensure that programs calling binaries from
+this package will call them with their full installation path (in bindir)
+without relying on a PATH search.
+
+
+* Out-of-tree builds
+  ------------------
+
+ skarnet.org packages do not support out-of-tree builds. They
+are small, so it does not cost much to duplicate the entire
+source tree if parallel builds are needed.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..610dbb6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,151 @@
+#
+# This Makefile requires GNU make.
+#
+# Do not make changes here.
+# Use the included .mak files.
+#
+
+it: all
+
+make_need := 3.81
+ifeq "" "$(strip $(filter $(make_need), $(firstword $(sort $(make_need) $(MAKE_VERSION)))))"
+fail := $(error Your make ($(MAKE_VERSION)) is too old. You need $(make_need) or newer)
+endif
+
+CC = $(error Please use ./configure first)
+
+STATIC_LIBS :=
+SHARED_LIBS :=
+INTERNAL_LIBS :=
+EXTRA_TARGETS :=
+LIB_DEFS :=
+
+define library_definition
+LIB$(firstword $(subst =, ,$(1))) := lib$(lastword $(subst =, ,$(1))).$(if $(DO_ALLSTATIC),a,so).xyzzy
+ifdef DO_SHARED
+SHARED_LIBS += lib$(lastword $(subst =, ,$(1))).so.xyzzy
+endif
+ifdef DO_STATIC
+STATIC_LIBS += lib$(lastword $(subst =, ,$(1))).a.xyzzy
+endif
+endef
+
+-include config.mak
+include package/targets.mak
+
+$(foreach var,$(LIB_DEFS),$(eval $(call library_definition,$(var))))
+
+include package/deps.mak
+
+version_m := $(basename $(version))
+version_M := $(basename $(version_m))
+version_l := $(basename $(version_M))
+CPPFLAGS_ALL := $(CPPFLAGS_AUTO) $(CPPFLAGS)
+CFLAGS_ALL := $(CFLAGS_AUTO) $(CFLAGS)
+ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)
+CFLAGS_SHARED := -fPIC
+else
+CFLAGS_SHARED :=
+endif
+LDFLAGS_ALL := $(LDFLAGS_AUTO) $(LDFLAGS)
+REALCC = $(CROSS_COMPILE)$(CC)
+AR := $(CROSS_COMPILE)ar
+RANLIB := $(CROSS_COMPILE)ranlib
+STRIP := $(CROSS_COMPILE)strip
+INSTALL := ./tools/install.sh
+
+ALL_BINS := $(LIBEXEC_TARGETS) $(BIN_TARGETS)
+ALL_LIBS := $(SHARED_LIBS) $(STATIC_LIBS) $(INTERNAL_LIBS)
+ALL_INCLUDES := $(wildcard src/include/$(package)/*.h)
+
+all: $(ALL_LIBS) $(ALL_BINS) $(ALL_INCLUDES)
+
+clean:
+	@exec rm -f $(ALL_LIBS) $(ALL_BINS) $(wildcard src/*/*.o src/*/*.lo) $(EXTRA_TARGETS)
+
+distclean: clean
+	@exec rm -f config.mak src/include/$(package)/config.h
+
+tgz: distclean
+	@. package/info && \
+	rm -rf /tmp/$$package-$$version && \
+	cp -a . /tmp/$$package-$$version && \
+	cd /tmp && \
+	tar -zpcv --owner=0 --group=0 --numeric-owner --exclude=.git* -f /tmp/$$package-$$version.tar.gz $$package-$$version && \
+	exec rm -rf /tmp/$$package-$$version
+
+strip: $(ALL_LIBS) $(ALL_BINS)
+ifneq ($(strip $(STATIC_LIBS)),)
+	exec $(STRIP) -x -R .note -R .comment -R .note.GNU-stack $(STATIC_LIBS)
+endif
+ifneq ($(strip $(ALL_BINS)$(SHARED_LIBS)),)
+	exec $(STRIP) -R .note -R .comment -R .note.GNU-stack $(ALL_BINS) $(SHARED_LIBS)
+endif
+
+install: install-dynlib install-libexec install-bin install-lib install-include
+install-dynlib: $(SHARED_LIBS:lib%.so.xyzzy=$(DESTDIR)$(dynlibdir)/lib%.so)
+install-libexec: $(LIBEXEC_TARGETS:%=$(DESTDIR)$(libexecdir)/%)
+install-bin: $(BIN_TARGETS:%=$(DESTDIR)$(bindir)/%)
+install-lib: $(STATIC_LIBS:lib%.a.xyzzy=$(DESTDIR)$(libdir)/lib%.a)
+install-include: $(ALL_INCLUDES:src/include/$(package)/%.h=$(DESTDIR)$(includedir)/$(package)/%.h)
+install-data: $(ALL_DATA:src/etc/%=$(DESTDIR)$(datadir)/%)
+
+ifneq ($(exthome),)
+
+$(DESTDIR)$(exthome): $(DESTDIR)$(home)
+	exec $(INSTALL) -l $(notdir $(home)) $(DESTDIR)$(exthome)
+
+update: $(DESTDIR)$(exthome)
+
+global-links: $(DESTDIR)$(exthome) $(SHARED_LIBS:lib%.so.xyzzy=$(DESTDIR)$(sproot)/library.so/lib%.so.$(version_M)) $(BIN_TARGETS:%=$(DESTDIR)$(sproot)/command/%)
+
+$(DESTDIR)$(sproot)/command/%: $(DESTDIR)$(home)/command/%
+	exec $(INSTALL) -D -l ..$(subst $(sproot),,$(exthome))/command/$(<F) $@
+
+$(DESTDIR)$(sproot)/library.so/lib%.so.$(version_M): $(DESTDIR)$(dynlibdir)/lib%.so.$(version_M)
+	exec $(INSTALL) -D -l ..$(subst $(sproot),,$(exthome))/library.so/$(<F) $@
+
+.PHONY: update global-links
+
+endif
+
+$(DESTDIR)$(datadir)/%: src/etc/%
+	exec $(INSTALL) -D -m 644 $< $@
+
+$(DESTDIR)$(dynlibdir)/lib%.so: lib%.so.xyzzy
+	$(INSTALL) -D -m 755 $< $@.$(version) && \
+	$(INSTALL) -l $(@F).$(version) $@.$(version_m) && \
+	$(INSTALL) -l $(@F).$(version_m) $@.$(version_M) && \
+	exec $(INSTALL) -l $(@F).$(version_M) $@
+
+$(DESTDIR)$(libexecdir)/% $(DESTDIR)$(bindir)/%: % package/modes
+	exec $(INSTALL) -D -m 600 $< $@
+	grep -- ^$(@F) < package/modes | { read name mode owner && \
+	if [ x$$owner != x ] ; then chown -- $$owner $@ ; fi && \
+	chmod $$mode $@ ; }
+
+$(DESTDIR)$(libdir)/lib%.a: lib%.a.xyzzy
+	exec $(INSTALL) -D -m 644 $< $@
+
+$(DESTDIR)$(includedir)/$(package)/%.h: src/include/$(package)/%.h
+	exec $(INSTALL) -D -m 644 $< $@
+
+%.o: %.c
+	exec $(REALCC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) -c -o $@ $<
+
+%.lo: %.c
+	exec $(REALCC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) $(CFLAGS_SHARED) -c -o $@ $<
+
+$(ALL_BINS):
+	exec $(REALCC) -o $@ $(CFLAGS_ALL) $(LDFLAGS_ALL) $(LDFLAGS_NOSHARED) $^ $(EXTRA_LIBS) $(LDLIBS)
+
+lib%.a.xyzzy:
+	exec $(AR) rc $@ $^
+	exec $(RANLIB) $@
+
+lib%.so.xyzzy:
+	exec $(REALCC) -o $@ $(CFLAGS_ALL) $(CFLAGS_SHARED) $(LDFLAGS_ALL) $(LDFLAGS_SHARED) -Wl,-soname,$(patsubst lib%.so.xyzzy,lib%.so.$(version_M),$@) $^ $(EXTRA_LIBS) $(LDLIBS)
+
+.PHONY: it all clean distclean tgz strip install install-dynlib install-bin install-lib install-include install-data
+
+.DELETE_ON_ERROR:
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..9e4f611
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,6 @@
+Changelog for pamela.
+
+In 0.0.1.0
+----------
+
+ - Initial release.
diff --git a/README b/README
new file mode 100644
index 0000000..4a17ae0
--- /dev/null
+++ b/README
@@ -0,0 +1,28 @@
+pamela - a PAM wrapper for secure execution
+-------------------------------------------
+
+ pamela is a library implementing the PAM API.
+
+ Traditional PAM implementations such as Linux-PAM
+load PAM modules dynamically into the application's address
+space. This is unsafe and exposes a lot of attack surface.
+Instead, pamela encapsulates the inner workings of PAM
+in a binary which is itself linked against Linux-PAM and
+performs all the module loading in its own address space;
+pamela provides an implementation of the PAM user API that
+communicates with that binary, keeping the application's
+address space free of external modules. 
+
+ See http://skarnet.org/software/pamela/ for details.
+
+
+* Installation
+  ------------
+
+ See the INSTALL file.
+
+
+* Contact information
+  -------------------
+
+ Laurent Bercot <ska-skaware at skarnet.org>
diff --git a/README.macosx b/README.macosx
new file mode 100644
index 0000000..e8a44d0
--- /dev/null
+++ b/README.macosx
@@ -0,0 +1,4 @@
+
+ This package will compile and run on Darwin (MacOS), if there is an implementation
+of Linux-PAM there; but the building of shared libraries is not supported.
+ Make sure you use the --disable-shared option to configure.
diff --git a/README.solaris b/README.solaris
new file mode 100644
index 0000000..380c8d8
--- /dev/null
+++ b/README.solaris
@@ -0,0 +1,16 @@
+
+ This package assumes the existence of a POSIX shell in /bin/sh.
+ On Solaris, /bin/sh is not POSIX. Most versions of Solaris provide
+a POSIX shell in /usr/xpg4/bin/sh.
+
+ To compile this package on Solaris, you will need to run
+
+     ./patch-for-solaris
+
+ before you run ./configure. This script will change the #! invocation
+of the configure script and various tools so that a POSIX shell is used
+for the compilation process.
+
+ Please note that the Solaris implementation of PAM is slightly
+incompatible with Linux-PAM. If you're using pamela on Solaris, please
+report your experience to ska-skaware@skarnet.org.
diff --git a/configure b/configure
new file mode 100755
index 0000000..de87b55
--- /dev/null
+++ b/configure
@@ -0,0 +1,461 @@
+#!/bin/sh
+
+. package/info
+
+usage () {
+cat <<EOF
+Usage: $0 [OPTION]... [TARGET]
+
+Defaults for the options are specified in brackets.
+
+System types:
+  --target=TARGET               configure to run on target TARGET [detected]
+  --host=TARGET                 same as --target
+
+Installation directories:
+  --prefix=PREFIX               main installation prefix [/]
+  --exec-prefix=EPREFIX         installation prefix for executable files [PREFIX]
+
+Fine tuning of the installation directories:
+  --dynlibdir=DIR               shared library files [PREFIX/lib]
+  --bindir=BINDIR               user executables [EPREFIX/bin]
+  --libexecdir=DIR              package-scoped executables [EPREFIX/libexec]
+  --libdir=DIR                  static library files [PREFIX/lib/$package]
+  --includedir=DIR              C header files [PREFIX/include]
+
+ If no --prefix option is given, by default libdir (but not dynlibdir) will be
+ /usr/lib/$package, and includedir will be /usr/include.
+
+Dependencies:
+  --with-sysdeps=DIR            use sysdeps in DIR [PREFIX/lib/skalibs/sysdeps]
+  --with-include=DIR            add DIR to the list of searched directories for headers
+  --with-lib=DIR                add DIR to the list of searched directories for static libraries
+  --with-dynlib=DIR             add DIR to the list of searched directories for shared libraries
+
+ If no --prefix option is given, by default sysdeps will be fetched from
+ /usr/lib/skalibs/sysdeps.
+
+Optional features:
+  --enable-shared               build shared libraries [disabled]
+  --disable-static              do not build static libraries [enabled]
+  --disable-allstatic           do not prefer linking against static libraries [enabled]
+  --enable-static-libc          make entirely static binaries [disabled]
+  --enable-all-pic              build everything as PIC [enabled iff toolchain builds PIE]
+  --enable-slashpackage[=ROOT]  assume /package installation at ROOT [disabled]
+  --enable-absolute-paths       do not rely on PATH to access this package's binaries,
+                                  hardcode absolute BINDIR/foobar paths instead [disabled]
+
+EOF
+exit 0
+}
+
+# Helper functions
+
+# If your system does not have printf, you can comment this, but it is
+# generally not a good idea to use echo.
+# See http://www.etalabs.net/sh_tricks.html
+echo () {
+  IFS=" "
+  printf %s\\n "$*"
+}
+
+quote () {
+  tr '\n' ' ' <<EOF | grep '^[-[:alnum:]_=,./:]* $' >/dev/null 2>&1 && { echo "$1" ; return 0 ; }
+$1
+EOF
+  echo "$1" | sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/" -e "s#^'\([-[:alnum:]_,./:]*\)=\(.*\)\$#\1='\2#" -e "s|\*/|* /|g"
+}
+
+fail () {
+  echo "$*"
+  exit 1
+}
+
+fnmatch () {
+  eval "case \"\$2\" in $1) return 0 ;; *) return 1 ;; esac"
+}
+
+cmdexists () {
+  type "$1" >/dev/null 2>&1
+}
+
+trycc () {
+  test -z "$CC_AUTO" && cmdexists "$1" && CC_AUTO="$*"
+}
+
+stripdir () {
+  while eval "fnmatch '*/' \"\${$1}\"" ; do
+    eval "$1=\${$1%/}"
+  done
+}
+
+tryflag () {
+  echo "checking whether compiler accepts $2 ..."
+  echo "typedef int x;" > "$tmpc"
+  if $CC_AUTO $CPPFLAGS_AUTO $CPPFLAGS $CPPFLAGS_POST $CFLAGS_AUTO $CFLAGS $CFLAGS_POST "$2" -c -o /dev/null "$tmpc" >/dev/null 2>&1 ; then
+    echo "  ... yes"
+    eval "$1=\"\${$1} \$2\""
+    eval "$1=\${$1# }"
+    return 0
+  else
+    echo "  ... no"
+    return 1
+  fi
+}
+
+tryldflag () {
+  echo "checking whether linker accepts $2 ..."
+  echo "typedef int x;" > "$tmpc"
+  if $CC_AUTO $CFLAGS_AUTO $CFLAGS $CFLAGS_POST $LDFLAGS_AUTO $LDFLAGS $LDFLAGS_POST -nostdlib "$2" -o /dev/null "$tmpc" >/dev/null 2>&1 ; then
+    echo "  ... yes"
+    eval "$1=\"\${$1} \$2\""
+    eval "$1=\${$1# }"
+    return 0
+  else
+    echo "  ... no"
+    return 1
+  fi
+}
+
+
+# Actual script
+
+CC_AUTO=
+CPPFLAGS_AUTO="-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 -iquote src/include-local -Isrc/include"
+CPPFLAGS_POST="$CPPFLAGS"
+CPPFLAGS=
+CFLAGS_AUTO="-pipe -Wall"
+CFLAGS_POST="$CFLAGS"
+CFLAGS=-O2
+LDFLAGS_AUTO=
+LDFLAGS_POST="$LDFLAGS"
+LDFLAGS=
+LDFLAGS_NOSHARED=
+LDFLAGS_SHARED=-shared
+prefix=
+exec_prefix='$prefix'
+dynlibdir='$prefix/lib'
+libexecdir='$exec_prefix/libexec'
+bindir='$exec_prefix/bin'
+libdir='$prefix/lib/$package'
+includedir='$prefix/include'
+sysdeps='$prefix/lib/skalibs/sysdeps'
+manualsysdeps=false
+shared=false
+static=true
+allpic=detect
+slashpackage=false
+abspath=false
+sproot=
+home=
+exthome=
+allstatic=true
+evenmorestatic=false
+addincpath=''
+addlibspath=''
+addlibdpath=''
+vpaths=''
+vpathd=''
+build=
+
+for arg ; do
+  case "$arg" in
+    --help) usage ;;
+    --prefix=*) prefix=${arg#*=} ;;
+    --exec-prefix=*) exec_prefix=${arg#*=} ;;
+    --dynlibdir=*) dynlibdir=${arg#*=} ;;
+    --libexecdir=*) libexecdir=${arg#*=} ;;
+    --bindir=*) bindir=${arg#*=} ;;
+    --libdir=*) libdir=${arg#*=} ;;
+    --includedir=*) includedir=${arg#*=} ;;
+    --with-sysdeps=*) sysdeps=${arg#*=} manualsysdeps=true ;;
+    --with-include=*) var=${arg#*=} ; stripdir var ; addincpath="$addincpath -I$var" ;;
+    --with-lib=*) var=${arg#*=} ; stripdir var ; addlibspath="$addlibspath -L$var" ; vpaths="$vpaths $var" ;;
+    --with-dynlib=*) var=${arg#*=} ; stripdir var ; addlibdpath="$addlibdpath -L$var" ; vpathd="$vpathd $var" ;;
+    --enable-shared|--enable-shared=yes) shared=true ;;
+    --disable-shared|--enable-shared=no) shared=false ;;
+    --enable-static|--enable-static=yes) static=true ;;
+    --disable-static|--enable-static=no) static=false ;;
+    --enable-allstatic|--enable-allstatic=yes) allstatic=true ;;
+    --disable-allstatic|--enable-allstatic=no) allstatic=false ; evenmorestatic=false ;;
+    --enable-static-libc|--enable-static-libc=yes) allstatic=true ; evenmorestatic=true ;;
+    --disable-static-libc|--enable-static-libc=no) evenmorestatic=false ;;
+    --enable-all-pic|--enable-all-pic=yes) allpic=true ;;
+    --disable-all-pic|--enable-all-pic=no) allpic=false ;;
+    --enable-slashpackage=*) sproot=${arg#*=} ; slashpackage=true ; ;;
+    --enable-slashpackage) sproot= ; slashpackage=true ;;
+    --disable-slashpackage) sproot= ; slashpackage=false ;;
+    --enable-absolute-paths|--enable-absolute-paths=yes) abspath=true ;;
+    --disable-absolute-paths|--enable-absolute-paths=no) abspath=false ;;
+    --enable-*|--disable-*|--with-*|--without-*|--*dir=*) ;;
+    --host=*|--target=*) target=${arg#*=} ;;
+    --build=*) build=${arg#*=} ;;
+    -* ) echo "$0: unknown option $arg" ;;
+    *=*) ;;
+    *) target=$arg ;;
+  esac
+done
+
+# Add /usr in the default default case
+if test -z "$prefix" ; then
+  if test "$libdir" = '$prefix/lib/$package' ; then
+    libdir=/usr/lib/$package
+  fi
+  if test "$includedir" = '$prefix/include' ; then
+    includedir=/usr/include
+  fi
+  if test "$sysdeps" = '$prefix/lib/skalibs/sysdeps' ; then
+    sysdeps=/usr/lib/skalibs/sysdeps
+  fi
+fi
+
+# Expand installation directories
+stripdir prefix
+for i in exec_prefix dynlibdir libexecdir bindir libdir includedir sysdeps sproot ; do
+  eval tmp=\${$i}
+  eval $i=$tmp
+  stripdir $i
+done
+
+# Get usable temp filenames
+i=0
+set -C
+while : ; do
+  i=$(($i+1))
+  tmpc="./tmp-configure-$$-$PPID-$i.c"
+  tmpe="./tmp-configure-$$-$PPID-$i.tmp"
+  2>|/dev/null > "$tmpc" && break
+  2>|/dev/null > "$tmpe" && break
+  test "$i" -gt 50 && fail "$0: cannot create temporary files"
+done
+set +C
+trap 'rm -f "$tmpc" "$tmpe"' EXIT ABRT INT QUIT TERM HUP
+
+# Set slashpackage values
+if $slashpackage ; then
+  home=${sproot}/package/${category}/${package}-${version}
+  exthome=${sproot}/package/${category}/${package}
+  if $manualsysdeps ; then
+    :
+  else
+    sysdeps=${sproot}/package/prog/skalibs/sysdeps
+  fi
+  extbinprefix=${exthome}/command
+  dynlibdir=${home}/library.so
+  bindir=${home}/command
+  libdir=${home}/library
+  libexecdir=$bindir
+  includedir=${home}/include
+  while read dep ; do
+    addincpath="$addincpath -I${sproot}${dep}/include"
+    vpaths="$vpaths ${sproot}${dep}/library"
+    addlibspath="$addlibspath -L${sproot}${dep}/library"
+    vpathd="$vpathd ${sproot}${dep}/library.so"
+    addlibdpath="$addlibdpath -L${sproot}${dep}/library.so"
+  done < package/deps-build
+fi
+
+# Find a C compiler to use
+if test -n "$target" && test x${build} != x${target} ; then
+  cross=${target}-
+else
+  cross=
+fi
+echo "checking for C compiler..."
+trycc ${cross}${CC}
+trycc ${cross}gcc
+trycc ${cross}clang
+trycc ${cross}cc
+test -n "$CC_AUTO" || { echo "$0: cannot find a C compiler" ; exit 1 ; }
+echo "  ... $CC_AUTO"
+echo "checking whether C compiler works... "
+echo "typedef int x;" > "$tmpc"
+if $CC_AUTO $CPPFLAGS_AUTO $CPPFLAGS $CPPFLAGS_POST $CFLAGS_AUTO $CFLAGS $CFLAGS_POST -c -o /dev/null "$tmpc" 2>"$tmpe" ; then
+  echo "  ... yes"
+else
+  echo "  ... no. Compiler output follows:"
+  cat < "$tmpe"
+  exit 1
+fi
+
+echo "checking target system type..."
+if test -z "$target" ; then
+  if test -n "$build" ; then
+    target=$build ;
+  else
+    target=$($CC_AUTO -dumpmachine 2>/dev/null) || target=unknown
+  fi
+fi
+echo "  ... $target"
+if test ! -d $sysdeps || test ! -f $sysdeps/target ; then
+  echo "$0: error: $sysdeps is not a valid sysdeps directory"
+  exit 1
+fi
+if [ "x$target" != "x$(cat $sysdeps/target)" ] ; then
+  echo "$0: error: target $target does not match the contents of $sysdeps/target"
+  exit 1
+fi
+
+if test $allpic = detect ; then
+  echo "Checking whether we need to build everything as PIC..."
+  if $CC_AUTO $CPPFLAGS_AUTO $CPPFLAGS $CPPFLAGS_POST $CFLAGS_AUTO $CFLAGS $CFLAGS_POST -dM -E - < /dev/null | grep -qF __PIE__ ; then
+    allpic=true
+    echo "  ... yes"
+  else
+    allpic=false
+    echo "  ... no"
+  fi
+fi
+if $allpic ; then
+  tryflag CFLAGS_AUTO -fPIC
+fi
+
+spawn_lib=$(cat $sysdeps/spawn.lib)
+socket_lib=$(cat $sysdeps/socket.lib)
+sysclock_lib=$(cat $sysdeps/sysclock.lib)
+tainnow_lib=$(cat $sysdeps/tainnow.lib)
+timer_lib=$(cat $sysdeps/timer.lib)
+util_lib=$(cat $sysdeps/util.lib)
+
+tryflag CFLAGS_AUTO -std=c99
+tryflag CFLAGS -fomit-frame-pointer
+tryflag CFLAGS_AUTO -fno-exceptions
+tryflag CFLAGS_AUTO -fno-unwind-tables
+tryflag CFLAGS_AUTO -fno-asynchronous-unwind-tables
+tryflag CFLAGS_AUTO -Wa,--noexecstack
+tryflag CFLAGS -fno-stack-protector
+tryflag CPPFLAGS_AUTO -Werror=implicit-function-declaration
+tryflag CPPFLAGS_AUTO -Werror=implicit-int
+tryflag CPPFLAGS_AUTO -Werror=pointer-sign
+tryflag CPPFLAGS_AUTO -Werror=pointer-arith
+tryflag CFLAGS_AUTO -ffunction-sections
+tryflag CFLAGS_AUTO -fdata-sections
+
+tryldflag LDFLAGS_AUTO -Wl,--sort-section=alignment
+tryldflag LDFLAGS_AUTO -Wl,--sort-common
+
+CPPFLAGS_AUTO="${CPPFLAGS_AUTO}${addincpath}"
+
+if $evenmorestatic ; then
+  LDFLAGS_NOSHARED=-static
+fi
+
+if $shared ; then
+  tryldflag LDFLAGS -Wl,--hash-style=both
+fi
+
+LDFLAGS_SHARED="${LDFLAGS_SHARED}${addlibdpath}"
+
+if $allstatic ; then
+  LDFLAGS_NOSHARED="${LDFLAGS_NOSHARED}${addlibspath}"
+  tryldflag LDFLAGS_NOSHARED -Wl,--gc-sections
+else
+  LDFLAGS_NOSHARED="${LDFLAGS_NOSHARED}${addlibdpath}"
+fi
+
+if test -z "$vpaths" ; then
+  while read dep ; do
+    base=$(basename $dep) ;
+    vpaths="$vpaths /usr/lib/$base"
+    addlibspath="$addlibspath -L/usr/lib/$base"
+  done < package/deps-build  
+fi
+
+echo "creating config.mak..."
+cmdline=$(quote "$0")
+for i ; do cmdline="$cmdline $(quote "$i")" ; done
+exec 3>&1 1>config.mak
+cat << EOF
+# This file was generated by:
+# $cmdline
+# Any changes made here will be lost if configure is re-run.
+
+target := $target
+package := $package
+prefix := $prefix
+exec_prefix := $exec_prefix
+dynlibdir := $dynlibdir
+libexecdir := $libexecdir
+bindir := $bindir
+libdir := $libdir
+includedir := $includedir
+sysdeps := $sysdeps
+slashpackage := $slashpackage
+sproot := $sproot
+version := $version
+home := $home
+exthome := $exthome
+SPAWN_LIB := ${spawn_lib}
+SOCKET_LIB := ${socket_lib}
+SYSCLOCK_LIB := ${sysclock_lib}
+TAINNOW_LIB := ${tainnow_lib}
+TIMER_LIB := ${timer_lib}
+UTIL_LIB := ${util_lib}
+
+CC := ${CC_AUTO##${cross}}
+CPPFLAGS_AUTO := $CPPFLAGS_AUTO
+CPPFLAGS := $CPPFLAGS $CPPFLAGS_POST
+CFLAGS_AUTO := $CFLAGS_AUTO
+CFLAGS := $CFLAGS $CFLAGS_POST
+LDFLAGS_AUTO := $LDFLAGS_AUTO
+LDFLAGS := $LDFLAGS $LDFLAGS_POST
+LDFLAGS_SHARED := $LDFLAGS_SHARED
+LDFLAGS_NOSHARED := $LDFLAGS_NOSHARED
+CROSS_COMPILE := $cross
+
+vpath lib%.a$vpaths
+vpath lib%.so$vpathd
+EOF
+if $allstatic ; then
+  echo ".LIBPATTERNS := lib%.a"
+  echo "DO_ALLSTATIC := 1"
+else
+  echo ".LIBPATTERNS := lib%.so"
+fi
+if $static ; then
+  echo "DO_STATIC := 1"
+else
+  echo "DO_STATIC :="
+fi
+if $shared ; then
+  echo "DO_SHARED := 1"
+else
+  echo "DO_SHARED :="
+fi
+if $allpic ; then
+  echo "STATIC_LIBS_ARE_PIC := 1"
+else
+  echo "STATIC_LIBS_ARE_PIC :="
+fi
+
+exec 1>&3 3>&-
+echo "  ... done."
+
+echo "creating src/include/${package}/config.h..."
+mkdir -p -m 0755 src/include/${package}
+exec 3>&1 1> src/include/${package}/config.h
+cat <<EOF
+/* ISC license. */
+
+/* Generated by: $cmdline */
+
+#ifndef ${package_macro_name}_CONFIG_H
+#define ${package_macro_name}_CONFIG_H
+
+#define ${package_macro_name}_VERSION "$version"
+EOF
+if $slashpackage ; then
+  echo "#define ${package_macro_name}_BINPREFIX \"$bindir/\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"$extbinprefix/\""
+elif $abspath ; then
+  echo "#define ${package_macro_name}_BINPREFIX \"$bindir/\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"$bindir/\""
+else
+  echo "#define ${package_macro_name}_BINPREFIX \"\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"\""
+fi
+echo "#define ${package_macro_name}_LIBEXECPREFIX \"$libexecdir/\""
+echo
+echo "#endif"
+exec 1>&3 3>&-
+echo "  ... done."
diff --git a/doc/building.html b/doc/building.html
new file mode 100644
index 0000000..649dc43
--- /dev/null
+++ b/doc/building.html
@@ -0,0 +1,103 @@
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>pamela: building an application</title>
+    <meta name="Description" content="pamela: building an application" />
+    <meta name="Keywords" content="pamela PAM Linux-PAM library" />
+    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">pamela</a><br />
+<a href="//skarnet.org/software/">Software</a><br />
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> Building an application with pamela instead of Linux-PAM </h1>
+
+<h2> Prerequisites </h2>
+
+<ul>
+ <li> The pamela package must have been properly built and installed;
+in particular, the <a href="pamelad.html">pamelad</a> binary must
+have been properly linked against Linux-PAM's <tt>libpam.so</tt>. </li>
+ <li> The <tt>security/pam_appl.h</tt> header, usually installed in
+<tt>/usr/include</tt>, must be a symlink to pamela's
+<tt>pamela/pam.h</tt> header. This can be achieved by running
+<tt>make install-symlink</tt> after <tt>make install</tt> when
+building the pamela package. </li>
+ <li> The application must strictly follow the
+<a href="http://www.linux-pam.org/Linux-PAM-html/adg-interface-by-app-expected.html">Linux-PAM
+specification</a>. Note that the page claims that the
+<tt>pam_set_item()</tt> function is declared in <tt>security/pam_modules.h</tt>,
+but it is a mistake: like every PAM function used by applications, it
+is declared in <tt>security/pam_appl.h</tt>. </li>
+ <li> The pamela headers and library must be installed, as well as the
+<a href="//skarnet.org/software/skalibs/">skalibs</a> headers and library. </li>
+</ul>
+
+<h2> Compiling </h2>
+
+<ul>
+ <li> Make sure that the pamela headers and the skalibs headers
+are visible in your header search path, and that the
+Linux-PAM headers <em>are not</em>. </li>
+ <li> If the compilation fails, please report the issue to the
+skaware mailing-list. pamela is a work in progress, and there
+may be compatibility issues that still need to be fixed. </li>
+</ul>
+
+<h2> Linking </h2>
+
+<ul>
+ <li> Make sure the pamela library, as well as the skalibs
+library, are visible in your library search path. </li>
+ <li> Do not add <tt>-lpam</tt> to your linking command line.
+Instead, add <tt>-lpamela -lskarnet</tt>. Depending on the
+libc you're using, you may have to add <tt>-lrt</tt> too. </li>
+ <li> It is possible to statically link a binary using pamela:
+the pamela and skalibs libraries do not use any dynamic loading,
+and are suitable for static linking. Only the
+<a href="pamelad.html">pamelad</a> binary uses dynamic module
+loading and needs to be dynamically linked, and that is decided
+at pamela build time, not at your application's build time. </li>
+ <li> Check your application binary's dynamic library dependencies
+after it has been built. If your binary depends on <tt>libpam</tt>,
+it has been incorrectly made! Your binary should depend on
+<tt>libpamela</tt> and <tt>libskarnet</tt>, but not <tt>libpam</tt>.
+If you have chosen to link against the static version of pamela
+and skalibs, you may not even see the <tt>libpamela</tt> and
+<tt>libskarnet</tt> dependencies. </li>
+</ul>
+
+<h2> Programming </h2>
+
+<ul>
+ <li> pamela strictly implements the
+<a href="http://www.linux-pam.org/Linux-PAM-html/adg-interface-by-app-expected.html">Linux-PAM
+API</a> </li>
+ <li> The <tt>pam_start()</tt> function will spawn a
+<a href="pamelad.html">pamelad</a> binary running as a child of
+the application, until <tt>pam_end()</tt> is called. At that point
+the zombie is reaped. </li>
+ <li> If the <a href="pamelad.html">pamelad</a> binary is killed
+during the PAM session, all PAM calls will return PAM_ABORT.
+The application should then just exit, or call <tt>pam_end()</tt>
+to free resources: nothing more can be done with the session. </li>
+</ul>
+
+<h2> Running </h2>
+
+<ul>
+ <li> If your application runs as root, you can set the
+PAMELA_UID and PAMELA_GID environment variables to a non-zero
+numeric uid and a nonzero numeric gid prior to running it.
+The <a href="pamelad.html">pamelad</a> binary will then drop
+its privileges and run under this uid/gid. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/index.html b/doc/index.html
new file mode 100644
index 0000000..3c2f58c
--- /dev/null
+++ b/doc/index.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>pamela - a secure PAM implementation</title>
+    <meta name="Description" content="pamela - a secure PAM implementation" />
+    <meta name="Keywords" content="pamela PAM linux pluggable authentication modules unix login laurent bercot skarnet" />
+    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="//skarnet.org/software/">Software</a><br />
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> pamela </h1>
+
+<p style="text-align:right;"><small><em>don't crack this root of mine</em></small></p>
+
+<h2> What is it&nbsp;? </h2>
+
+<p>
+ pamela stands for PAM Encapsulated Loading Architecture.
+</p>
+
+<p>
+ It is a library implementing Linux-PAM's
+<a href="http://www.linux-pam.org/Linux-PAM-html/adg-interface-by-app-expected.html"><tt>security/pam_appl.h</tt></a>
+header, for applications to use instead of the default Linux-PAM
+<tt>security/pam_appl.h</tt> header. pamela wraps all PAM calls and
+deports them to a <a href="pamelad.html">pamelad</a> binary running
+as a child of the application, which performs the real calls to
+Linux-PAM.
+</p>
+
+<p>
+ The advantage of this setup is that it reduces the application's
+attack surface considerably. Instead of loading modules into the
+application's address space, PAM loads modules into the small,
+dedicated pamelad binary, whose main source code is less than 400
+lines long. Also, if the application runs as root, the pamelad
+binary can run as an unprivileged user, so modules cannot be used
+for privilege elevation.
+</p>
+
+<hr />
+
+<h2> Installation </h2>
+
+<h3> Requirements </h3>
+
+<ul>
+ <li> A POSIX-compliant system with a standard C development environment,
+that supports Linux-PAM - (so, probably a Linux system) </li>
+ <li> GNU make, version 3.81 or later </li>
+ <li> <a href="//skarnet.org/software/skalibs/">skalibs</a> version
+2.6.3.2 or later. It's a build-time requirement and a run-time
+requirement. </li>
+ <li> <a href="http://www.linux-pam.org/">Linux-PAM</a> version 1.3.0
+or later. It's a build-time requirement and a run-time requirement. </li>
+</ul>
+
+<h3> Licensing </h3>
+
+<p>
+ pamela is free software. It is available under the
+<a href="http://opensource.org/licenses/ISC">ISC license</a>.
+</p>
+
+<h3> Download </h3>
+
+<ul>
+ <li> <!-- The current released version of pamela is <a href="pamela-0.0.1.0.tar.gz">0.0.1.0</a>. -->
+ pamela is a work in progress: there is no numbered version yet. </li>
+ <li> You can checkout a copy of the
+<a href="//git.skarnet.org/cgi-bin/cgit.cgi/pamela/">pamela
+git repository</a>:
+<pre> git clone git://git.skarnet.org/pamela </pre> </li>
+ <li> There's also a
+<a href="https://github.com/skarnet/pamela">GitHub mirror</a>
+of the utmps git repository. </li>
+</ul>
+
+<h3> Compilation </h3>
+
+<ul>
+ <li> See the enclosed INSTALL file for installation details. </li>
+ <li> Please note that the build process expects <tt>security/pam_appl.h</tt>
+to be a Linux-PAM header. Because of this, pamela's <tt>make install</tt>
+does not overwrite <tt>security/pam_appl.h</tt>; instead, the header
+exposing PAM functionality is named <tt>pamela/pam.h</tt>. When you are
+positive you can replace the <tt>security/pam_appl.h</tt> header,
+the <tt>make install-symlink</tt> command will do it for you. </li>
+</ul>
+
+<h3> Upgrade notes </h3>
+
+<ul>
+ <li> <a href="upgrade.html">This page</a> lists the differences to be aware of between
+the previous versions of pamela and the current one. </li>
+</ul>
+
+<hr />
+
+<h2> Reference </h2>
+
+<h3> Commands </h3>
+
+<ul>
+<li><a href="pamelad.html">The <tt>pamelad</tt> internal program</a></li>
+</ul>
+
+<h3> Libraries </h3>
+
+<ul>
+<li> <a href="building.html">How to build an application with pamela</a> </li>
+<li> <a href="http://www.linux-pam.org/Linux-PAM-html/adg-interface-by-app-expected.html">The
+public interface to Linux-PAM</a> is implemented by the pamela library. </li>
+</ul>
+
+<hr />
+
+<a name="related">
+<h2> Related resources </h2>
+</a>
+
+<h3> pamela discussion </h3>
+
+<ul>
+ <li> <tt>pamela</tt> is discussed on the
+<a href="//skarnet.org/lists.html#skaware">skaware</a> mailing-list. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/pamelad.html b/doc/pamelad.html
new file mode 100644
index 0000000..5d93c27
--- /dev/null
+++ b/doc/pamelad.html
@@ -0,0 +1,68 @@
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>pamela: the pamelad internal program</title>
+    <meta name="Description" content="pamela: the pamelad internal program" />
+    <meta name="Keywords" content="pamela command pamelad program internal libexec PAM module loader" />
+    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">pamela</a><br />
+<a href="//skarnet.org/software/">Software</a><br />
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The pamelad program </h1>
+
+<p>
+pamelad is a helper program spawned by the pamela library, at <tt>pam_start()</tt> time.
+It communicates with the main application process, reading requests from it,
+performing PAM calls, and returning results to the application process.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ pamelad is not meant to be called directly. It is an <em>unexported</em>
+program, i.e. a program that the package's library is supposed to find, but
+that users should not run. Ideally it's kept outside of users' PATH variable.
+</p>
+
+<p>
+ pamelad is declared as a <em>libexec</em> binary, so it will probably be
+installed in <tt>/usr/libexec/pamelad</tt> or <tt>/usr/lib/pamela/pamelad</tt>,
+depending on the admin/distribution's preferences.
+</p>
+
+<p>
+ pamelad is run with two arguments, which are the <em>service_name</em> and
+<em>user</em> arguments given to the <tt>pam_start()</tt> call. This will
+display in a <tt>ps</tt> output, which is fine since this information is
+not confidential.
+</p>
+
+<p>
+ pamelad runs as the same uid and gid as the application. However, to
+increase security of applications running as root:
+</p>
+
+<ul>
+ <li> If the application runs as gid 0 and the PAMELA_GID environment
+variable is set, then pamelad will run with a gid set to <tt>$PAMELA_GID</tt>. </li>
+ <li> If the application runs as uid 0 and the PAMELA_UID environment
+variable is set, then pamelad will run with a uid set to <tt>$PAMELA_UID</tt>. </li>
+</ul>
+
+<p>
+ pamelad makes the real calls to Linux-PAM. Keep that in mind when
+configuring your PAM authorizations: if you're using the PAMELA_UID and
+PAMELA_GID variables, make sure the uid/gid are not used anywhere else,
+and give the proper PAM authorizations to that uid/gid pair instead of root.
+</p>
+
+</body>
+</html>
diff --git a/doc/upgrade.html b/doc/upgrade.html
new file mode 100644
index 0000000..2d4ba44
--- /dev/null
+++ b/doc/upgrade.html
@@ -0,0 +1,28 @@
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>How to upgrade pamela</title>
+    <meta name="Description" content="How to upgrade pamela" />
+    <meta name="Keywords" content="pamela installation upgrade" />
+    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">pamela</a><br />
+<a href="//skarnet.org/software/">Software</a><br />
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> What has changed in pamela </h1>
+
+<h2> in 0.0.1.0 </h2>
+
+<p>
+ Initial release.
+</p>
+
+</body>
+</html>
diff --git a/package/deps-build b/package/deps-build
new file mode 100644
index 0000000..05d5af4
--- /dev/null
+++ b/package/deps-build
@@ -0,0 +1 @@
+/package/prog/skalibs
diff --git a/package/deps.mak b/package/deps.mak
new file mode 100644
index 0000000..59c6f2a
--- /dev/null
+++ b/package/deps.mak
@@ -0,0 +1,45 @@
+#
+# This file has been generated by tools/gen-deps.sh
+#
+
+src/include/pamela/pam.h: src/include/pamela/pamela.h
+src/include/pamela/pamela.h: src/include/pamela/common.h
+src/pamela/pamela-internal.h: src/include/pamela/pamela.h
+src/pamela/pam_acct_mgmt.o src/pamela/pam_acct_mgmt.lo: src/pamela/pam_acct_mgmt.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pam_authenticate.o src/pamela/pam_authenticate.lo: src/pamela/pam_authenticate.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pam_chauthtok.o src/pamela/pam_chauthtok.lo: src/pamela/pam_chauthtok.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pam_close_session.o src/pamela/pam_close_session.lo: src/pamela/pam_close_session.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pam_end.o src/pamela/pam_end.lo: src/pamela/pam_end.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pam_fail_delay.o src/pamela/pam_fail_delay.lo: src/pamela/pam_fail_delay.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pam_get_item.o src/pamela/pam_get_item.lo: src/pamela/pam_get_item.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pam_getenv.o src/pamela/pam_getenv.lo: src/pamela/pam_getenv.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pam_getenvlist.o src/pamela/pam_getenvlist.lo: src/pamela/pam_getenvlist.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pam_open_session.o src/pamela/pam_open_session.lo: src/pamela/pam_open_session.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pam_putenv.o src/pamela/pam_putenv.lo: src/pamela/pam_putenv.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pam_set_item.o src/pamela/pam_set_item.lo: src/pamela/pam_set_item.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pam_setcred.o src/pamela/pam_setcred.lo: src/pamela/pam_setcred.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pam_start.o src/pamela/pam_start.lo: src/pamela/pam_start.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pam_strerror.o src/pamela/pam_strerror.lo: src/pamela/pam_strerror.c src/include/pamela/pam.h src/include/pamela/pamela.h
+src/pamela/pamela_end.o src/pamela/pamela_end.lo: src/pamela/pamela_end.c src/include/pamela/pamela.h
+src/pamela/pamela_get_item.o src/pamela/pamela_get_item.lo: src/pamela/pamela_get_item.c src/pamela/pamela-internal.h src/include/pamela/pamela.h
+src/pamela/pamela_getenvlist.o src/pamela/pamela_getenvlist.lo: src/pamela/pamela_getenvlist.c src/pamela/pamela-internal.h src/include/pamela/pamela.h
+src/pamela/pamela_op.o src/pamela/pamela_op.lo: src/pamela/pamela_op.c src/include/pamela/pamela.h
+src/pamela/pamela_pam_response_free.o src/pamela/pamela_pam_response_free.lo: src/pamela/pamela_pam_response_free.c src/include/pamela/pamela.h
+src/pamela/pamela_query_string.o src/pamela/pamela_query_string.lo: src/pamela/pamela_query_string.c src/include/pamela/pamela.h
+src/pamela/pamela_set_item.o src/pamela/pamela_set_item.lo: src/pamela/pamela_set_item.c src/pamela/pamela-internal.h src/include/pamela/pamela.h
+src/pamela/pamela_set_item_internal.o src/pamela/pamela_set_item_internal.lo: src/pamela/pamela_set_item_internal.c src/include/pamela/pamela.h
+src/pamela/pamela_set_itemv.o src/pamela/pamela_set_itemv.lo: src/pamela/pamela_set_itemv.c src/pamela/pamela-internal.h src/include/pamela/pamela.h
+src/pamela/pamela_startf.o src/pamela/pamela_startf.lo: src/pamela/pamela_startf.c src/include/pamela/config.h src/include/pamela/pamela.h
+src/pamela/pamela_strerror.o src/pamela/pamela_strerror.lo: src/pamela/pamela_strerror.c src/pamela/pamela-internal.h src/include/pamela/pamela.h
+src/pamela/pamela_zero.o src/pamela/pamela_zero.lo: src/pamela/pamela_zero.c src/include/pamela/pamela.h
+src/pamela/pamelad.o src/pamela/pamelad.lo: src/pamela/pamelad.c src/include/pamela/common.h
+
+ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)
+libpamela.a.xyzzy: src/pamela/pam_acct_mgmt.o src/pamela/pam_authenticate.o src/pamela/pam_chauthtok.o src/pamela/pam_close_session.o src/pamela/pam_end.o src/pamela/pam_fail_delay.o src/pamela/pam_get_item.o src/pamela/pam_getenv.o src/pamela/pam_getenvlist.o src/pamela/pam_open_session.o src/pamela/pam_putenv.o src/pamela/pam_set_item.o src/pamela/pam_setcred.o src/pamela/pam_start.o src/pamela/pam_strerror.o src/pamela/pamela_end.o src/pamela/pamela_get_item.o src/pamela/pamela_getenvlist.o src/pamela/pamela_op.o src/pamela/pamela_pam_response_free.o src/pamela/pamela_query_string.o src/pamela/pamela_set_item.o src/pamela/pamela_set_item_internal.o src/pamela/pamela_set_itemv.o src/pamela/pamela_startf.o src/pamela/pamela_strerror.o src/pamela/pamela_zero.o
+else
+libpamela.a.xyzzy: src/pamela/pam_acct_mgmt.lo src/pamela/pam_authenticate.lo src/pamela/pam_chauthtok.lo src/pamela/pam_close_session.lo src/pamela/pam_end.lo src/pamela/pam_fail_delay.lo src/pamela/pam_get_item.lo src/pamela/pam_getenv.lo src/pamela/pam_getenvlist.lo src/pamela/pam_open_session.lo src/pamela/pam_putenv.lo src/pamela/pam_set_item.lo src/pamela/pam_setcred.lo src/pamela/pam_start.lo src/pamela/pam_strerror.lo src/pamela/pamela_end.lo src/pamela/pamela_get_item.lo src/pamela/pamela_getenvlist.lo src/pamela/pamela_op.lo src/pamela/pamela_pam_response_free.lo src/pamela/pamela_query_string.lo src/pamela/pamela_set_item.lo src/pamela/pamela_set_item_internal.lo src/pamela/pamela_set_itemv.lo src/pamela/pamela_startf.lo src/pamela/pamela_strerror.lo src/pamela/pamela_zero.lo
+endif
+libpamela.so.xyzzy: EXTRA_LIBS :=
+libpamela.so.xyzzy: src/pamela/pam_acct_mgmt.lo src/pamela/pam_authenticate.lo src/pamela/pam_chauthtok.lo src/pamela/pam_close_session.lo src/pamela/pam_end.lo src/pamela/pam_fail_delay.lo src/pamela/pam_get_item.lo src/pamela/pam_getenv.lo src/pamela/pam_getenvlist.lo src/pamela/pam_open_session.lo src/pamela/pam_putenv.lo src/pamela/pam_set_item.lo src/pamela/pam_setcred.lo src/pamela/pam_start.lo src/pamela/pam_strerror.lo src/pamela/pamela_end.lo src/pamela/pamela_get_item.lo src/pamela/pamela_getenvlist.lo src/pamela/pamela_op.lo src/pamela/pamela_pam_response_free.lo src/pamela/pamela_query_string.lo src/pamela/pamela_set_item.lo src/pamela/pamela_set_item_internal.lo src/pamela/pamela_set_itemv.lo src/pamela/pamela_startf.lo src/pamela/pamela_strerror.lo src/pamela/pamela_zero.lo
+pamelad: EXTRA_LIBS := ${PAM_LIB}
+pamelad: src/pamela/pamelad.o -lskarnet
diff --git a/package/info b/package/info
new file mode 100644
index 0000000..2b5379b
--- /dev/null
+++ b/package/info
@@ -0,0 +1,4 @@
+package=pamela
+version=0.0.1.0
+category=admin
+package_macro_name=PAMELA
diff --git a/package/modes b/package/modes
new file mode 100644
index 0000000..e3b7c4e
--- /dev/null
+++ b/package/modes
@@ -0,0 +1 @@
+pamelad		0755
diff --git a/package/targets.mak b/package/targets.mak
new file mode 100644
index 0000000..6d1a06b
--- /dev/null
+++ b/package/targets.mak
@@ -0,0 +1,14 @@
+BIN_TARGETS :=
+
+LIBEXEC_TARGETS := pamelad
+
+LIB_DEFS := PAMELA=pamela
+
+PAM_LIB := -lpam
+
+$(DESTDIR)$(includedir)/security/pam_appl.h: $(DESTDIR)$(includedir)/$(package)/pam.h
+	exec $(INSTALL) -D -l ../$(package)/pam.h $@
+
+install-symlink: $(DESTDIR)$(includedir)/security/pam_appl.h
+
+.PHONY: install-symlink
diff --git a/patch-for-solaris b/patch-for-solaris
new file mode 100755
index 0000000..2d1296b
--- /dev/null
+++ b/patch-for-solaris
@@ -0,0 +1,21 @@
+#!/usr/xpg4/bin/sh
+
+patchit () {
+  echo '#!/usr/xpg4/bin/sh' > $1.tmp
+  tail -n +2 $1 >> $1.tmp
+  mv -f $1.tmp $1
+  chmod 755 $1
+}
+
+# Solaris doesn't understand POSIX.1-2008 either.
+sed -e 's/XOPEN_SOURCE=700/XOPEN_SOURCE=600/' < configure > configure.tmp
+mv -f configure.tmp configure
+
+patchit ./configure
+patchit ./tools/install.sh
+patchit ./tools/gen-deps.sh
+
+echo 'SHELL := /usr/xpg4/bin/sh' > Makefile.tmp
+echo >> Makefile.tmp
+cat Makefile >> Makefile.tmp
+mv -f Makefile.tmp Makefile
diff --git a/src/include/pamela/common.h b/src/include/pamela/common.h
new file mode 100644
index 0000000..5ae689e
--- /dev/null
+++ b/src/include/pamela/common.h
@@ -0,0 +1,101 @@
+/* ISC license. */
+
+#ifndef PAMELA_COMMON_H
+#define PAMELA_COMMON_H
+
+#define PAMELA_BUFSIZE 4096
+
+enum pamela_retcode_e
+{
+  PAMELA_PAM_SUCCESS = 0,
+  PAMELA_PAM_OPEN_ERR,
+  PAMELA_PAM_SYMBOL_ERR,
+  PAMELA_PAM_SERVICE_ERR,
+  PAMELA_PAM_SYSTEM_ERR,
+  PAMELA_PAM_BUF_ERR,
+  PAMELA_PAM_PERM_DENIED,
+  PAMELA_PAM_AUTH_ERR,
+  PAMELA_PAM_CRED_INSUFFICIENT,
+  PAMELA_PAM_AUTHINFO_UNAVAIL,
+  PAMELA_PAM_USER_UNKNOWN,
+  PAMELA_PAM_MAXTRIES,
+  PAMELA_PAM_NEW_AUTHTOK_REQD,
+  PAMELA_PAM_ACCT_EXPIRED,
+  PAMELA_PAM_SESSION_ERR,
+  PAMELA_PAM_CRED_UNAVAIL,
+  PAMELA_PAM_CRED_EXPIRED,
+  PAMELA_PAM_CRED_ERR,
+  PAMELA_PAM_NO_MODULE_DATA,
+  PAMELA_PAM_CONV_ERR,
+  PAMELA_PAM_AUTHTOK_ERR,
+  PAMELA_PAM_AUTHTOK_RECOVERY_ERR,
+  PAMELA_PAM_AUTHTOK_LOCK_BUSY,
+  PAMELA_PAM_AUTHTOK_DISABLE_AGING,
+  PAMELA_PAM_TRY_AGAIN,
+  PAMELA_PAM_IGNORE,
+  PAMELA_PAM_ABORT,
+  PAMELA_PAM_AUTHTOK_EXPIRED,
+  PAMELA_PAM_MODULE_UNKNOWN,
+  PAMELA_PAM_BAD_ITEM,
+  PAMELA_PAM_CONV_AGAIN,
+  PAMELA_PAM_INCOMPLETE
+} ;
+
+
+enum pamela_item_e
+{
+  PAMELA_ENV = 0,
+  PAMELA_PAM_SERVICE,
+  PAMELA_PAM_USER,
+  PAMELA_PAM_TTY,
+  PAMELA_PAM_RHOST,
+  PAMELA_PAM_CONV,
+  PAMELA_PAM_AUTHTOK,
+  PAMELA_PAM_OLDAUTHTOK,
+  PAMELA_PAM_RUSER,
+  PAMELA_PAM_USER_PROMPT,
+  PAMELA_PAM_FAIL_DELAY,
+  PAMELA_PAM_XDISPLAY,
+  PAMELA_PAM_XAUTHDATA,
+  PAMELA_PAM_AUTHTOK_TYPE
+} ;
+
+
+enum pamela_convmsgtype_e
+{
+  PAMELA_PAM_PROMPT_ECHO_OFF = 1,
+  PAMELA_PAM_PROMPT_ECHO_ON,
+  PAMELA_PAM_ERROR_MSG,
+  PAMELA_PAM_TEXT_INFO,
+  PAMELA_PAM_RADIO_TYPE,
+  PAMELA_PAM_BINARY_PROMPT = 7
+} ;
+
+#define PAMELA_PAM_CONV_MAX_MESSAGES 1024
+
+
+enum pamela_op_e
+{
+  PAMELA_OP_ACCT_MGMT = 1,
+  PAMELA_OP_AUTHENTICATE,
+  PAMELA_OP_CHAUTHTOK,
+  PAMELA_OP_CLOSE_SESSION,
+  PAMELA_OP_FAIL_DELAY,
+  PAMELA_OP_OPEN_SESSION,
+  PAMELA_OP_SETCRED,
+  PAMELA_OP_SETFAILDELAY,
+  PAMELA_OP_END
+} ;
+
+
+ /* flags */
+
+#define PAMELA_PAM_SILENT 0x8000U
+#define PAMELA_PAM_DISALLOW_NULL_AUTHTOK 0x0001U
+#define PAMELA_PAM_ESTABLISH_CRED 0x0002U
+#define PAMELA_PAM_DELETE_CRED 0x0004U
+#define PAMELA_PAM_REINITIALIZE_CRED 0x0008U
+#define PAMELA_PAM_REFRESH_CRED 0x0010U
+#define PAMELA_PAM_CHANGE_EXPIRED_AUTHTOK 0x0020U
+
+#endif
diff --git a/src/include/pamela/pam.h b/src/include/pamela/pam.h
new file mode 100644
index 0000000..aab7d7d
--- /dev/null
+++ b/src/include/pamela/pam.h
@@ -0,0 +1,161 @@
+/* ISC license. */
+
+#ifndef PAMELA_PAM_H
+#define PAMELA_PAM_H
+
+ /*
+    This is pamela's client-side PAM library.
+    
+ */
+
+#include <skalibs/uint64.h>
+#include <skalibs/stralloc.h>
+#include <pamela/pamela.h>
+
+#define __LINUX_PAM__ 1
+#define __LINUX_PAM_MINOR__ 0
+
+
+ /* Return codes */
+
+#define _PAM_RETURN_VALUES 32
+
+#define PAM_SUCCESS PAMELA_PAM_SUCCESS
+#define PAM_OPEN_ERR PAMELA_PAM_OPEN_ERR
+#define PAM_SYMBOL_ERR PAMELA_PAM_SYMBOL_ERR
+#define PAM_SERVICE_ERR PAMELA_PAM_SERVICE_ERR
+#define PAM_SYSTEM_ERR PAMELA_PAM_SYSTEM_ERR
+#define PAM_BUF_ERR PAMELA_PAM_BUF_ERR
+#define PAM_PERM_DENIED PAMELA_PAM_PERM_DENIED
+#define PAM_AUTH_ERR PAMELA_PAM_AUTH_ERR
+#define PAM_CRED_INSUFFICIENT PAMELA_PAM_CRED_INSUFFICIENT
+#define PAM_AUTHINFO_UNAVAIL PAMELA_PAM_AUTHINFO_UNAVAIL
+#define PAM_USER_UNKNOWN PAMELA_PAM_USER_UNKNOWN
+#define PAM_MAXTRIES PAMELA_PAM_MAXTRIES
+#define PAM_NEW_AUTHTOK_REQD PAMELA_PAM_NEW_AUTHTOK_REQD
+#define PAM_ACCT_EXPIRED PAMELA_PAM_ACCT_EXPIRED
+#define PAM_SESSION_ERR PAMELA_PAM_SESSION_ERR
+#define PAM_CRED_UNAVAIL PAMELA_PAM_CRED_UNAVAIL
+#define PAM_CRED_EXPIRED PAMELA_PAM_CRED_EXPIRED
+#define PAM_CRED_ERR PAMELA_PAM_CRED_ERR
+#define PAM_NO_MODULE_DATA PAMELA_PAM_NO_MODULE_DATA
+#define PAM_CONV_ERR PAMELA_PAM_CONV_ERR
+#define PAM_AUTHTOK_ERR PAMELA_PAM_AUTHTOK_ERR
+#define PAM_AUTHTOK_RECOVERY_ERR PAMELA_PAM_AUTHTOK_RECOVERY_ERR
+#define PAM_AUTHTOK_LOCK_BUSY PAMELA_PAM_AUTHTOK_LOCK_BUSY
+#define PAM_AUTHTOK_DISABLE_AGING PAMELA_PAM_AUTHTOK_DISABLE_AGING
+#define PAM_TRY_AGAIN PAMELA_PAM_TRY_AGAIN
+#define PAM_IGNORE PAMELA_PAM_IGNORE
+#define PAM_ABORT PAMELA_PAM_ABORT
+#define PAM_AUTHTOK_EXPIRED PAMELA_PAM_AUTHTOK_EXPIRED
+#define PAM_MODULE_UNKNOWN PAMELA_PAM_MODULE_UNKNOWN
+#define PAM_BAD_ITEM PAMELA_PAM_BAD_ITEM
+#define PAM_CONV_AGAIN PAMELA_PAM_CONV_AGAIN
+#define PAM_INCOMPLETE PAMELA_PAM_INCOMPLETE
+
+
+ /* Items */
+
+#define PAM_ITEM_MAX 14  /* 13 items + environment */
+
+#define PAM_SERVICE PAMELA_PAM_SERVICE
+#define PAM_USER PAMELA_PAM_USER
+#define PAM_TTY PAMELA_PAM_TTY
+#define PAM_RHOST PAMELA_PAM_RHOST
+#define PAM_CONV PAMELA_PAM_CONV
+#define PAM_AUTHTOK PAMELA_PAM_AUTHTOK
+#define PAM_OLDAUTHTOK PAMELA_PAM_OLDAUTHTOK
+#define PAM_RUSER PAMELA_PAM_RUSER
+#define PAM_USER_PROMPT PAMELA_PAM_USER_PROMPT
+#define PAM_FAIL_DELAY PAMELA_PAM_FAIL_DELAY
+#define PAM_XDISPLAY PAMELA_PAM_XDISPLAY
+#define PAM_XAUTHDATA PAMELA_PAM_XAUTHDATA
+#define PAM_AUTHTOK_TYPE PAMELA_PAM_AUTHTOK_TYPE
+
+
+ /* Flags */
+
+#define PAM_SILENT PAMELA_PAM_SILENT
+#define PAM_DISALLOW_NULL_AUTHTOK PAMELA_PAM_DISALLOW_NULL_AUTHTOK
+#define PAM_ESTABLISH_CRED PAMELA_PAM_ESTABLISH_CRED
+#define PAM_DELETE_CRED PAMELA_PAM_DELETE_CRED
+#define PAM_REINITIALIZE_CRED PAMELA_PAM_REINITIALIZE_CRED
+#define PAM_REFRESH_CRED PAMELA_PAM_REFRESH_CRED
+#define PAM_CHANGE_EXPIRED_AUTHTOK PAMELA_PAM_CHANGE_EXPIRED_AUTHTOK
+
+
+ /* Conversation types */
+
+#define PAM_PROMPT_ECHO_OFF PAMELA_PAM_PROMPT_ECHO_OFF
+#define PAM_PROMPT_ECHO_ON PAMELA_PAM_PROMPT_ECHO_ON
+#define PAM_ERROR_MSG PAMELA_PAM_ERROR_MSG
+#define PAM_TEXT_INFO PAMELA_PAM_TEXT_INFO
+#define PAM_RADIO_TYPE PAMELA_PAM_RADIO_TYPE
+#define PAM_BINARY_PROMPT PAMELA_PAM_BINARY_PROMPT
+
+
+ /* Misc Linux-PAM stuff */
+
+#define PAM_MAX_NUM_MSG 32
+#define PAM_MAX_MSG_SIZE 512
+#define PAM_MAX_RESP_SIZE 512
+
+#define PAM_DATA_SILENT PAMELA_PAM_DATA_SILENT
+
+
+ /* Data structures and functions */
+
+struct pam_message
+{
+  int msg_style ;
+  char const *msg ;
+} ;
+
+struct pam_response
+{
+  char *resp ;
+  int resp_retcode ;
+} ;
+
+struct pam_conv
+{
+  int (*conv) (int, struct pam_message const **, struct pam_response **, void *) ;
+  void *appdata_ptr ;
+} ;
+
+struct pam_xauth_data
+{
+  int namelen ;
+  char *name ;
+  int datalen ;
+  char *data ;
+} ;
+
+typedef struct pam_handle_s pam_handle_t ;
+struct pam_handle_s
+{
+  pamela_t handle ;
+  struct pam_xauth_data xauthdata ;
+  stralloc err[_PAM_RETURN_VALUES] ;
+  stralloc item[PAM_ITEM_MAX] ;
+  uint64_t flagerrcached : _PAM_RETURN_VALUES ;
+  uint64_t flagenvcached : 1 ;
+} ;
+
+extern int pam_start (char const *, char const *, struct pam_conv const *, pam_handle_t **) ;
+extern int pam_end (pam_handle_t *, int) ;
+extern int pam_set_item (pam_handle_t *, int, void const *) ;
+extern int pam_get_item (pam_handle_t *, int, void const **) ;
+extern char const *pam_strerror (pam_handle_t *, int) ;
+extern int pam_fail_delay (pam_handle_t *, unsigned int) ;
+extern int pam_authenticate (pam_handle_t *, int) ;
+extern int pam_setcred (pam_handle_t *, int) ;
+extern int pam_acct_mgmt (pam_handle_t *, int) ;
+extern int pam_chauthtok (pam_handle_t *, int) ;
+extern int pam_open_session (pam_handle_t *, int) ;
+extern int pam_close_session (pam_handle_t *, int) ;
+extern int pam_putenv (pam_handle_t *, char const *) ;
+extern char const *pam_getenv (pam_handle_t *, char const *) ;
+extern char **pam_getenvlist (pam_handle_t *) ;
+
+#endif
diff --git a/src/include/pamela/pamela.h b/src/include/pamela/pamela.h
new file mode 100644
index 0000000..ca45124
--- /dev/null
+++ b/src/include/pamela/pamela.h
@@ -0,0 +1,80 @@
+/* ISC license. */
+
+#ifndef PAMELA_H
+#define PAMELA_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <sys/uio.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/textmessage.h>
+#include <pamela/common.h>
+
+
+ /* pam_fail_delay */
+
+typedef void pamela_pam_delay_func_t (int, unsigned int, void *) ;
+typedef pamela_pam_delay_func_t *pamela_pam_delay_func_t_ref ;
+
+
+ /* Conversations */
+
+typedef struct pamela_pam_message_s pamela_pam_message_t, *pamela_pam_message_t_ref ;
+struct pamela_pam_message_s
+{
+  int msg_style ;
+  char const *msg ;
+} ;
+
+typedef struct pamela_pam_response_s pamela_pam_response_t, *pamela_pam_response_t_ref ;
+struct pamela_pam_response_s
+{
+  char *resp ;
+  int *resp_retcode ;
+} ;
+
+extern void pamela_pam_response_free (pamela_pam_response_t *, uint32_t) ;
+
+typedef int pamela_pam_conv_func_t (int, pamela_pam_message_t const **, pamela_pam_response_t **, void *) ;
+typedef pamela_pam_conv_func_t *pamela_pam_conv_func_t_ref ;
+
+
+ /* Client handle */
+
+typedef struct pamela_s pamela_t, *pamela_t_ref ;
+struct pamela_s
+{
+  textmessage_receiver_t in ;
+  textmessage_sender_t out ;
+  pid_t pid ;
+  pamela_pam_delay_func_t_ref delayfn ;
+  pamela_pam_conv_func_t_ref convfn ;
+  void *aux ;
+  char inbuf[PAMELA_BUFSIZE] ;
+} ;
+#define PAMELA_ZERO { TEXTMESSAGE_RECEIVER_ZERO, TEXTMESSAGE_SENDER_ZERO, 0, 0, 0, 0, "" }
+
+extern pamela_t const pamela_zero ;
+
+
+ /* User-facing functions */
+
+extern int pamela_startf (pamela_t *, char const *, char const *, pamela_pam_conv_func_t_ref, void *) ;
+extern void pamela_end (pamela_t *) ;
+extern int pamela_strerror (pamela_t *, unsigned char, stralloc *) ;
+extern int pamela_getenvlist (pamela_t *, stralloc *) ;
+extern int pamela_get_item (pamela_t *, unsigned char, stralloc *) ;
+extern int pamela_set_item (pamela_t *, unsigned char, char const *) ;
+extern int pamela_set_itemv (pamela_t *, unsigned char, struct iovec const *, unsigned int) ;
+extern int pamela_op (pamela_t *, unsigned char, int) ;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/pamela/deps-exe/pamelad b/src/pamela/deps-exe/pamelad
new file mode 100644
index 0000000..3294b7e
--- /dev/null
+++ b/src/pamela/deps-exe/pamelad
@@ -0,0 +1,2 @@
+-lskarnet
+${PAM_LIB}
diff --git a/src/pamela/deps-lib/pamela b/src/pamela/deps-lib/pamela
new file mode 100644
index 0000000..0b749c3
--- /dev/null
+++ b/src/pamela/deps-lib/pamela
@@ -0,0 +1,27 @@
+pam_acct_mgmt.o
+pam_authenticate.o
+pam_chauthtok.o
+pam_close_session.o
+pam_end.o
+pam_fail_delay.o
+pam_get_item.o
+pam_getenv.o
+pam_getenvlist.o
+pam_open_session.o
+pam_putenv.o
+pam_set_item.o
+pam_setcred.o
+pam_start.o
+pam_strerror.o
+pamela_end.o
+pamela_get_item.o
+pamela_getenvlist.o
+pamela_op.o
+pamela_pam_response_free.o
+pamela_query_string.o
+pamela_set_item.o
+pamela_set_item_internal.o
+pamela_set_itemv.o
+pamela_startf.o
+pamela_strerror.o
+pamela_zero.o
diff --git a/src/pamela/pam_acct_mgmt.c b/src/pamela/pam_acct_mgmt.c
new file mode 100644
index 0000000..bd65b2a
--- /dev/null
+++ b/src/pamela/pam_acct_mgmt.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+int pam_acct_mgmt (pam_handle_t *pamh, int flags)
+{
+  if (!pamh) return PAM_SYSTEM_ERR ;
+  return pamela_op(&pamh->handle, PAMELA_OP_ACCT_MGMT, flags) ;
+}
diff --git a/src/pamela/pam_authenticate.c b/src/pamela/pam_authenticate.c
new file mode 100644
index 0000000..5e31b0b
--- /dev/null
+++ b/src/pamela/pam_authenticate.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+int pam_authenticate (pam_handle_t *pamh, int flags)
+{
+  if (!pamh) return PAM_SYSTEM_ERR ;
+  return pamela_op(&pamh->handle, PAMELA_OP_AUTHENTICATE, flags) ;
+}
diff --git a/src/pamela/pam_chauthtok.c b/src/pamela/pam_chauthtok.c
new file mode 100644
index 0000000..53f639f
--- /dev/null
+++ b/src/pamela/pam_chauthtok.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+int pam_chauthtok (pam_handle_t *pamh, int flags)
+{
+  if (!pamh) return PAM_SYSTEM_ERR ;
+  return pamela_op(&pamh->handle, PAMELA_OP_CHAUTHTOK, flags) ;
+}
diff --git a/src/pamela/pam_close_session.c b/src/pamela/pam_close_session.c
new file mode 100644
index 0000000..989bddf
--- /dev/null
+++ b/src/pamela/pam_close_session.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+int pam_close_session (pam_handle_t *pamh, int flags)
+{
+  if (!pamh) return PAM_SYSTEM_ERR ;
+  return pamela_op(&pamh->handle, PAMELA_OP_CLOSE_SESSION, flags) ;
+}
diff --git a/src/pamela/pam_end.c b/src/pamela/pam_end.c
new file mode 100644
index 0000000..8da0dbd
--- /dev/null
+++ b/src/pamela/pam_end.c
@@ -0,0 +1,17 @@
+/* ISC license. */
+
+#include <stdlib.h>
+#include <skalibs/stralloc.h>
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+int pam_end (pam_handle_t *pamh, int pam_status)
+{
+  if (!pamh) return PAM_SYSTEM_ERR ;
+  pamela_op(&pamh->handle, PAMELA_OP_END, pam_status) ;
+  pamela_end(&pamh->handle) ;
+  for (unsigned int i = 0 ; i < _PAM_RETURN_VALUES ; i++) stralloc_free(&pamh->err[i]) ;
+  for (unsigned int i = 0 ; i < PAM_ITEM_MAX ; i++) stralloc_free(&pamh->item[i]) ;
+  free(pamh) ;
+  return PAM_SUCCESS ;
+}
diff --git a/src/pamela/pam_fail_delay.c b/src/pamela/pam_fail_delay.c
new file mode 100644
index 0000000..ca5ffb4
--- /dev/null
+++ b/src/pamela/pam_fail_delay.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+int pam_fail_delay (pam_handle_t *pamh, unsigned int usec)
+{
+  if (!pamh) return PAM_SYSTEM_ERR ;
+  return pamela_op(&pamh->handle, PAMELA_OP_FAIL_DELAY, (int)usec) ;
+}
diff --git a/src/pamela/pam_get_item.c b/src/pamela/pam_get_item.c
new file mode 100644
index 0000000..e9499fd
--- /dev/null
+++ b/src/pamela/pam_get_item.c
@@ -0,0 +1,52 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <skalibs/uint32.h>
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+static int xauthdata_unpack (pam_handle_t *pamh)
+{
+  uint32_t namelen, datalen ;
+  uint32_unpack_big(pamh->item[PAM_XAUTHDATA].s, &namelen) ;
+  uint32_unpack_big(pamh->item[PAM_XAUTHDATA].s + 4, &datalen) ;
+  if (namelen + datalen + 10 != pamh->item[PAM_XAUTHDATA].len) return 0 ;
+  pamh->xauthdata.namelen = (int)namelen ;
+  pamh->xauthdata.datalen = (int)namelen ;
+  pamh->xauthdata.name = pamh->item[PAM_XAUTHDATA].s + 8 ;
+  pamh->xauthdata.data = pamh->item[PAM_XAUTHDATA].s + 9 + namelen ;
+  return 1 ;
+}
+
+int pam_get_item (pam_handle_t *pamh, int item_type, void const **item)
+{
+  if (!pamh) return PAM_SYSTEM_ERR ;
+  if (item_type < 1 || item_type >= PAM_ITEM_MAX) return PAM_BAD_ITEM ;
+  switch (item_type)
+  {
+    case PAM_FAIL_DELAY :
+      *item = (void const *)pamh->handle.delayfn ;
+      return PAM_SUCCESS ;
+    case PAM_CONV :
+      *item = (void const *)pamh->handle.convfn ;
+      return PAM_SUCCESS ;
+    default : break ;
+  }
+  pamh->item[item_type].len = 0 ;
+  {
+    int e = pamela_get_item(&pamh->handle, (unsigned char)item_type, &pamh->item[item_type]) ;
+    if (e) return e ;
+  }
+  switch (item_type)
+  {
+    case PAM_XAUTHDATA :
+      if (!xauthdata_unpack(pamh)) return PAM_ABORT ;
+      *item = (void const *)&pamh->xauthdata ;
+      break ;
+    default :
+      *item = (void const *)pamh->item[item_type].s ;
+      break ;
+  }
+  return PAM_SUCCESS ;
+}
diff --git a/src/pamela/pam_getenv.c b/src/pamela/pam_getenv.c
new file mode 100644
index 0000000..f743b48
--- /dev/null
+++ b/src/pamela/pam_getenv.c
@@ -0,0 +1,32 @@
+/* ISC license. */
+
+#include <string.h>
+#include <skalibs/stralloc.h>
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+static char const *getvar (char const *s, size_t len, char const *var)
+{
+  size_t varlen = strlen(var) ;
+  size_t i = 0 ;
+  while (i < len)
+  {
+    if (!strncmp(var, s + i, varlen) && s[i + varlen] == '=') break ;
+    i += strlen(s + i) + 1 ;
+  }
+  return i < len ? s + i + varlen + 1 : 0 ;
+}
+
+char const *pam_getenv (pam_handle_t *pamh, char const *name)
+{
+  stralloc *sa ;
+  if (!pamh) return 0 ;
+  sa = &pamh->item[PAMELA_ENV] ;
+  if (!pamh->flagenvcached)
+  {
+    sa->len = 0 ;
+    if (!pamela_getenvlist(&pamh->handle, sa)) return 0 ;
+    pamh->flagenvcached = 1 ;
+  }
+  return getvar(sa->s, sa->len, name) ;
+}
diff --git a/src/pamela/pam_getenvlist.c b/src/pamela/pam_getenvlist.c
new file mode 100644
index 0000000..18b0da3
--- /dev/null
+++ b/src/pamela/pam_getenvlist.c
@@ -0,0 +1,42 @@
+/* ISC license. */
+
+#include <string.h>
+#include <stdlib.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/stralloc.h>
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+char **pam_getenvlist (pam_handle_t *pamh)
+{
+  stralloc *sa ;
+  char **arr ;
+  char *p ;
+  size_t n ;
+  size_t i = 0 ;
+  if (!pamh) return 0 ;
+  sa = &pamh->item[PAMELA_ENV] ;
+  if (!pamh->flagenvcached)
+  {
+    sa->len = 0 ;
+    if (!pamela_getenvlist(&pamh->handle, sa)) return 0 ;
+    pamh->flagenvcached = 1 ;
+  }
+  n = byte_count(sa->s, sa->len, 0) ;
+  arr = malloc((n+1) * sizeof(char *)) ;
+  if (!arr) return 0 ;
+  p = sa->s ;
+  for (; i < n ; i++)
+  {
+    arr[i] = strdup(p) ;
+    if (!arr[i]) goto err ;
+    p += strlen(p) + 1 ;
+  }
+  arr[n] = 0 ;
+  return arr ;
+
+ err:
+  while (i--) free(arr[i]) ;
+  free(arr) ;
+  return 0 ;
+}
diff --git a/src/pamela/pam_open_session.c b/src/pamela/pam_open_session.c
new file mode 100644
index 0000000..25881b7
--- /dev/null
+++ b/src/pamela/pam_open_session.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+int pam_open_session (pam_handle_t *pamh, int flags)
+{
+  if (!pamh) return PAM_SYSTEM_ERR ;
+  return pamela_op(&pamh->handle, PAMELA_OP_OPEN_SESSION, flags) ;
+}
diff --git a/src/pamela/pam_putenv.c b/src/pamela/pam_putenv.c
new file mode 100644
index 0000000..066d447
--- /dev/null
+++ b/src/pamela/pam_putenv.c
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+int pam_putenv (pam_handle_t *pamh, char const *name_value)
+{
+  int e ;
+  if (!pamh) return PAM_SYSTEM_ERR ;
+  e = pamela_set_item(&pamh->handle, PAMELA_ENV, name_value) ;
+  if (e) return e ;
+  pamh->flagenvcached = 0 ;
+  return PAM_SUCCESS ;
+}
diff --git a/src/pamela/pam_set_item.c b/src/pamela/pam_set_item.c
new file mode 100644
index 0000000..03e08c0
--- /dev/null
+++ b/src/pamela/pam_set_item.c
@@ -0,0 +1,50 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <skalibs/uint32.h>
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+static int xauthdata_pack_and_set (pamela_t *a, struct pam_xauth_data const *d)
+{
+  if (d->namelen < 0 || d->datalen < 0) return PAM_SYSTEM_ERR ;
+  {
+    char buf[8] ;
+    struct iovec v[3] =
+    {
+      { .iov_base = buf, .iov_len = 8 },
+      { .iov_base = d->name, .iov_len = d->namelen + 1 },
+      { .iov_base = d->data, .iov_len = d->datalen + 1 }
+    } ;
+    uint32_pack_big(buf, (uint32_t)d->namelen) ;
+    uint32_pack_big(buf + 4, (uint32_t)d->datalen) ;
+    return pamela_set_itemv(a, PAM_XAUTHDATA, v, 3) ;
+  }
+}
+
+int pam_set_item (pam_handle_t *pamh, int item_type, void const *item)
+{
+  if (!pamh) return PAM_SYSTEM_ERR ;
+  if (item_type < 1 || item_type >= PAM_ITEM_MAX) return PAM_BAD_ITEM ;
+  switch (item_type)
+  {
+    case PAM_FAIL_DELAY :
+    {
+      int e ;
+      pamh->handle.delayfn = (pamela_pam_delay_func_t_ref)item ;
+      e = pamela_op(&pamh->handle, PAMELA_OP_SETFAILDELAY, 0) ;
+      if (e != PAM_SUCCESS) return e ;
+      break ;
+    }
+    case PAM_CONV :
+      pamh->handle.convfn = (pamela_pam_conv_func_t_ref)item ;
+      break ;
+    case PAM_XAUTHDATA :
+      return xauthdata_pack_and_set(&pamh->handle, (struct pam_xauth_data const *)item) ;
+    default :
+      return pamela_set_item(&pamh->handle, (uint32_t)item_type, (char const *)item) ;
+  }
+  return PAM_SUCCESS ;
+}
diff --git a/src/pamela/pam_setcred.c b/src/pamela/pam_setcred.c
new file mode 100644
index 0000000..eddd2f9
--- /dev/null
+++ b/src/pamela/pam_setcred.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+int pam_setcred (pam_handle_t *pamh, int flags)
+{
+  if (!pamh) return PAM_SYSTEM_ERR ;
+  return pamela_op(&pamh->handle, PAMELA_OP_SETCRED, flags) ;
+}
diff --git a/src/pamela/pam_start.c b/src/pamela/pam_start.c
new file mode 100644
index 0000000..3847080
--- /dev/null
+++ b/src/pamela/pam_start.c
@@ -0,0 +1,34 @@
+/* ISC license. */
+
+#include <stdlib.h>
+#include <skalibs/stralloc.h>
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+static int pamela_dummy_conv (int num_msg, pamela_pam_message_t const **msg, pamela_pam_response_t **resp, void *aux)
+{
+  (void)num_msg ;
+  (void)msg ;
+  (void)resp ;
+  (void)aux ;
+  return PAMELA_PAM_CONV_ERR ;
+}
+
+int pam_start (char const *service_name, char const *user, struct pam_conv const *pam_conversation, pam_handle_t **pamh)
+{
+  int e ;
+  pam_handle_t *a = malloc(sizeof(pam_handle_t)) ;
+  if (!a) return PAM_BUF_ERR ;
+  a->handle = pamela_zero ;
+  a->flagerrcached = a->flagenvcached = 0 ;
+  for (unsigned int i = 0 ; i < _PAM_RETURN_VALUES ; i++) a->err[i] = stralloc_zero ;
+  for (unsigned int i = 0 ; i < PAM_ITEM_MAX ; i++) a->item[i] = stralloc_zero ;
+  e = pamela_startf(&a->handle, service_name, user, pam_conversation && pam_conversation->conv ? (pamela_pam_conv_func_t_ref)pam_conversation->conv : &pamela_dummy_conv, pam_conversation ? pam_conversation->appdata_ptr : 0) ;
+  if (e)
+  {
+    free(a) ;
+    return e ;
+  }
+  *pamh = a ;
+  return PAM_SUCCESS ;
+}
diff --git a/src/pamela/pam_strerror.c b/src/pamela/pam_strerror.c
new file mode 100644
index 0000000..fee63df
--- /dev/null
+++ b/src/pamela/pam_strerror.c
@@ -0,0 +1,17 @@
+/* ISC license. */
+
+#include <pamela/pamela.h>
+#include <pamela/pam.h>
+
+char const *pam_strerror (pam_handle_t *pamh, int errnum)
+{
+  if (errnum < 0 || errnum >= _PAM_RETURN_VALUES) return 0 ;
+  if (!(pamh->flagerrcached & (1ULL << errnum)))
+  {
+    pamh->err[errnum].len = 0 ;
+    if (!pamela_strerror(&pamh->handle, (unsigned char)errnum, &pamh->err[errnum])) return 0 ;
+    if (!stralloc_0(&pamh->err[errnum])) return 0 ;
+    pamh->flagerrcached |= (1ULL << errnum) ;
+  }
+  return (char const *)pamh->err[errnum].s ;
+}
diff --git a/src/pamela/pamela-internal.h b/src/pamela/pamela-internal.h
new file mode 100644
index 0000000..8de3a20
--- /dev/null
+++ b/src/pamela/pamela-internal.h
@@ -0,0 +1,13 @@
+/* ISC license. */
+
+#ifndef PAMELA_INTERNAL_H
+#define PAMELA_INTERNAL_H
+
+#include <sys/uio.h>
+#include <skalibs/stralloc.h>
+#include <pamela/pamela.h>
+
+extern int pamela_query_string (pamela_t *, char const *, size_t, stralloc *) ;
+extern int pamela_set_item_internal (pamela_t *, struct iovec const *, unsigned int) ;
+
+#endif
diff --git a/src/pamela/pamela_end.c b/src/pamela/pamela_end.c
new file mode 100644
index 0000000..09bb186
--- /dev/null
+++ b/src/pamela/pamela_end.c
@@ -0,0 +1,16 @@
+/* ISC license. */
+
+#include <skalibs/djbunix.h>
+#include <skalibs/textmessage.h>
+#include <pamela/pamela.h>
+
+void pamela_end (pamela_t *a)
+{
+  int wstat ;
+  fd_close(textmessage_sender_fd(&a->out)) ;
+  textmessage_sender_free(&a->out) ;
+  fd_close(textmessage_receiver_fd(&a->in)) ;
+  textmessage_receiver_free(&a->in) ;
+  waitpid_nointr(a->pid, &wstat, 0) ;
+  *a = pamela_zero ;
+}
diff --git a/src/pamela/pamela_get_item.c b/src/pamela/pamela_get_item.c
new file mode 100644
index 0000000..14fa56e
--- /dev/null
+++ b/src/pamela/pamela_get_item.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <pamela/pamela.h>
+#include "pamela-internal.h"
+
+int pamela_get_item (pamela_t *a, unsigned char what, stralloc *sa)
+{
+  char s[2] = "G" ;
+  s[1] = what ;
+  return pamela_query_string(a, s, 2, sa) ;
+}
diff --git a/src/pamela/pamela_getenvlist.c b/src/pamela/pamela_getenvlist.c
new file mode 100644
index 0000000..ecc5013
--- /dev/null
+++ b/src/pamela/pamela_getenvlist.c
@@ -0,0 +1,9 @@
+/* ISC license. */
+
+#include <pamela/pamela.h>
+#include "pamela-internal.h"
+
+int pamela_getenvlist (pamela_t *a, stralloc *sa)
+{
+  return pamela_query_string(a, "V", 1, sa) ;
+}
diff --git a/src/pamela/pamela_op.c b/src/pamela/pamela_op.c
new file mode 100644
index 0000000..2d8ff76
--- /dev/null
+++ b/src/pamela/pamela_op.c
@@ -0,0 +1,123 @@
+/* ISC license. */
+
+#include <string.h>
+#include <stdint.h>
+#include <sys/uio.h>
+#include <skalibs/posixplz.h>
+#include <skalibs/uint32.h>
+#include <skalibs/types.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/textmessage.h>
+#include <pamela/pamela.h>
+
+static inline int pamela_fail_delay (pamela_t *a, int r, unsigned int usec)
+{
+  (*a->delayfn)(r, usec, a->aux) ;
+  if (!textmessage_timed_send(&a->out, "D", 1, 0, 0)) return PAMELA_PAM_ABORT ;
+  return PAMELA_PAM_SUCCESS ;
+}
+
+static inline int pamela_converse_and_answer (pamela_t *a, char const *s, size_t len)
+{
+  pamela_pam_response_t *res ;
+  uint32_t n ;
+  int e ;
+  char ans[2] = "C" ;
+  if (len < 4) return PAMELA_PAM_ABORT ;
+  uint32_unpack_big(s, &n) ;
+  s += 4 ; len -= 4 ;
+  if (n > PAMELA_PAM_CONV_MAX_MESSAGES) return PAMELA_PAM_ABORT ;
+  if (len < n * 5) return PAMELA_PAM_ABORT ;
+  {
+    pamela_pam_message_t messages[n] ;
+    pamela_pam_message_t const *arr[n] ;
+    for (uint32_t i = 0 ; i < n ; i++)
+    {
+      uint32_t u ;
+      uint32_unpack_big(s + 4 * i, &u) ;
+      messages[i].msg_style = u ;
+      arr[i] = &messages[i] ;
+    }
+    s += n * 4 ; len -= n * 4 ;
+    for (uint32_t i = 0 ; i < n ; i++)
+    {
+      size_t pos = strnlen(s, len) ;
+      if (pos == len) return PAMELA_PAM_ABORT ;
+      messages[i].msg = s ;
+      s += pos + 1 ; len -= pos + 1 ;
+    }
+    if (len) return PAMELA_PAM_ABORT ;
+    e = (*a->convfn)((int)n, arr, &res, a->aux) ;
+  }
+  if (e != PAMELA_PAM_SUCCESS)
+  {
+    ans[1] = (unsigned char)e ;
+    if (!textmessage_timed_send(&a->out, ans, 2, 0, 0)) return PAMELA_PAM_ABORT ;
+  }
+  else
+  {
+    struct iovec v[n+1] ;
+    v[0].iov_base = ans ;
+    v[0].iov_len = 2 ;
+    for (uint32_t i = 0 ; i < n ; i++)
+    {
+      v[i+1].iov_base = res[i].resp ;
+      v[i+1].iov_len = strlen(res[i].resp) + 1 ;
+    }
+    if (!textmessage_timed_sendv(&a->out, v, n+1, 0, 0))
+    {
+      pamela_pam_response_free(res, n) ;
+      return PAMELA_PAM_ABORT ;
+    }
+    pamela_pam_response_free(res, n) ;
+  }
+  return PAMELA_PAM_SUCCESS ;
+}
+
+int pamela_op (pamela_t *a, unsigned char type, int num)
+{
+  {
+    char pack[sizeof(int)] ;
+    struct iovec v[3] =
+    {
+      { .iov_base = "O", .iov_len = 1 },
+      { .iov_base = &type, .iov_len = 1 },
+      { .iov_base = pack, .iov_len = sizeof(int) }
+    } ;
+    int_pack_big(pack, num) ;
+    if (!textmessage_timed_sendv(&a->out, v, 3, 0, 0)) return PAMELA_PAM_SYSTEM_ERR ;
+  }
+  for (;;)
+  {
+    struct iovec v ;
+    char const *s ;
+    if (textmessage_timed_receive(&a->in, &v, 0, 0) <= 0) return PAMELA_PAM_ABORT ;
+    if (v.iov_len < 2) return PAMELA_PAM_ABORT ;
+    s = v.iov_base ;
+    switch (s[0])
+    {
+      case 'o' : /* operation returns */
+        if (v.iov_len != 2) return PAMELA_PAM_ABORT ;
+        return s[1] ;
+      case 'c' : /* conversation request */
+      {
+        int e = pamela_converse_and_answer(a, s + 1, v.iov_len - 1) ;
+        if (e != PAMELA_PAM_SUCCESS) return e ;
+        break ;
+      }
+      case 'd' : /* fail_delay request */
+      {
+        uint32_t u ;
+        int r ;
+        unsigned int usec ;
+        if (v.iov_len != 9) return PAMELA_PAM_ABORT ;
+        uint32_unpack_big(s + 1, &u) ; r = u ;
+        uint32_unpack_big(s + 5, &u) ; usec = u ;
+        r = pamela_fail_delay(a, r, usec) ;
+        if (r != PAMELA_PAM_SUCCESS) return r ;
+        break ;
+      }
+      default : return PAMELA_PAM_ABORT ;
+    }
+  }
+}
diff --git a/src/pamela/pamela_pam_response_free.c b/src/pamela/pamela_pam_response_free.c
new file mode 100644
index 0000000..496928b
--- /dev/null
+++ b/src/pamela/pamela_pam_response_free.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <pamela/pamela.h>
+
+void pamela_pam_response_free (pamela_pam_response_t *res, uint32_t n)
+{
+  for (uint32_t i = 0 ; i < n ; i++) free(res[i].resp) ;
+  free(res) ;
+}
diff --git a/src/pamela/pamela_query_string.c b/src/pamela/pamela_query_string.c
new file mode 100644
index 0000000..27c131f
--- /dev/null
+++ b/src/pamela/pamela_query_string.c
@@ -0,0 +1,20 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <sys/uio.h>
+#include <skalibs/error.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/textmessage.h>
+#include <pamela/pamela.h>
+
+int pamela_query_string (pamela_t *a, char const *s, size_t len, stralloc *sa)
+{
+  struct iovec v ;
+  if (!textmessage_timed_send(&a->out, s, len, 0, 0)) return 0 ;
+  if (textmessage_timed_receive(&a->in, &v, 0, 0) <= 0) return 0 ;
+  if (!v.iov_len) return (errno = EPROTO, 0) ;
+  s = v.iov_base ;
+  if (s[0]) return (errno = s[0], 0) ;
+  if (!stralloc_catb(sa, s + 1, v.iov_len - 1)) return 0 ;
+  return 1 ;
+}
diff --git a/src/pamela/pamela_set_item.c b/src/pamela/pamela_set_item.c
new file mode 100644
index 0000000..faa3ee1
--- /dev/null
+++ b/src/pamela/pamela_set_item.c
@@ -0,0 +1,16 @@
+/* ISC license. */
+
+#include <string.h>
+#include <pamela/pamela.h>
+#include "pamela-internal.h"
+
+int pamela_set_item (pamela_t *a, unsigned char type, char const *s)
+{
+  struct iovec const v[3] =
+  {
+    { .iov_base = "S", .iov_len = 1 },
+    { .iov_base = &type, .iov_len = 1 },
+    { .iov_base = (char *)s, .iov_len = strlen(s) + 1 }
+  } ;
+  return pamela_set_item_internal(a, v, 3) ;
+}
diff --git a/src/pamela/pamela_set_item_internal.c b/src/pamela/pamela_set_item_internal.c
new file mode 100644
index 0000000..ca92903
--- /dev/null
+++ b/src/pamela/pamela_set_item_internal.c
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#include <sys/uio.h>
+#include <skalibs/textmessage.h>
+#include <pamela/pamela.h>
+
+int pamela_set_item_internal (pamela_t *a, struct iovec const *v, unsigned int n)
+{
+  struct iovec r ;
+  if (!textmessage_timed_sendv(&a->out, v, n, 0, 0)) return PAMELA_PAM_SYSTEM_ERR ;
+  if (textmessage_timed_receive(&a->in, &r, 0, 0) <= 0) return PAMELA_PAM_ABORT ;
+  if (r.iov_len != 1) return PAMELA_PAM_ABORT ;
+  return ((unsigned char const *)r.iov_base)[0] ;
+}
diff --git a/src/pamela/pamela_set_itemv.c b/src/pamela/pamela_set_itemv.c
new file mode 100644
index 0000000..7f64d5a
--- /dev/null
+++ b/src/pamela/pamela_set_itemv.c
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#include <sys/uio.h>
+#include <pamela/pamela.h>
+#include "pamela-internal.h"
+
+int pamela_set_itemv (pamela_t *a, unsigned char type, struct iovec const *v, unsigned int n)
+{
+  struct iovec vv[n+2] ;
+  vv[0].iov_base = "S" ; vv[0].iov_len = 1 ;
+  vv[1].iov_base = &type ; vv[1].iov_len = 1 ;
+  for (unsigned int i = 0 ; i < n ; i++) vv[i+2] = v[i] ;
+  return pamela_set_item_internal(a, vv, n+2) ;
+}
diff --git a/src/pamela/pamela_startf.c b/src/pamela/pamela_startf.c
new file mode 100644
index 0000000..4d7ff46
--- /dev/null
+++ b/src/pamela/pamela_startf.c
@@ -0,0 +1,40 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <skalibs/environ.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/textmessage.h>
+#include <pamela/config.h>
+#include <pamela/pamela.h>
+
+int pamela_startf (pamela_t *a, char const *service_name, char const *user, pamela_pam_conv_func_t_ref convfn, void *aux)
+{
+  char const *argv[4] = { PAMELA_LIBEXECPREFIX "pamelad", service_name, user, 0 } ;
+  int fd[2] ;
+  pid_t pid = child_spawn2(argv[0], argv, (char const *const *)environ, fd) ;
+  int e = PAMELA_PAM_ABORT ;
+  struct iovec v ;
+  if (!pid) return 0 ;
+  textmessage_receiver_init(&a->in, fd[0], a->inbuf, PAMELA_BUFSIZE, TEXTMESSAGE_MAXLEN) ;
+  if (textmessage_timed_receive(&a->in, &v, 0, 0) <= 0) goto err ;
+  if (v.iov_len != 1) goto ferr ;
+  e = ((unsigned char const *)v.iov_base)[0] ; if (e) goto ferr ;
+  a->pid = pid ;
+  a->delayfn = 0 ;
+  a->convfn = convfn ;
+  a->aux = aux ;
+  textmessage_sender_init(&a->out, fd[1]) ;
+  return e ;
+
+ ferr:
+  textmessage_receiver_free(&a->in) ;
+ err:
+  fd_close(fd[1]) ;
+  fd_close(fd[0]) ;
+  {
+    int wstat ;
+    waitpid_nointr(pid, &wstat, 0) ;
+  }
+  return e ;
+}
diff --git a/src/pamela/pamela_strerror.c b/src/pamela/pamela_strerror.c
new file mode 100644
index 0000000..2e72a01
--- /dev/null
+++ b/src/pamela/pamela_strerror.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <pamela/pamela.h>
+#include "pamela-internal.h"
+
+int pamela_strerror (pamela_t *a, unsigned char e, stralloc *sa)
+{
+  char s[2] = "E" ;
+  s[1] = e ;
+  return pamela_query_string(a, s, 2, sa) ;
+}
diff --git a/src/pamela/pamela_zero.c b/src/pamela/pamela_zero.c
new file mode 100644
index 0000000..70b3682
--- /dev/null
+++ b/src/pamela/pamela_zero.c
@@ -0,0 +1,5 @@
+/* ISC license. */
+
+#include <pamela/pamela.h>
+
+pamela_t const pamela_zero = PAMELA_ZERO ;
diff --git a/src/pamela/pamelad.c b/src/pamela/pamelad.c
new file mode 100644
index 0000000..0a55f66
--- /dev/null
+++ b/src/pamela/pamelad.c
@@ -0,0 +1,385 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/uio.h>
+#include <skalibs/posixplz.h>
+#include <skalibs/uint32.h>
+#include <skalibs/types.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/env.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/sig.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/textmessage.h>
+#include <pamela/common.h>
+#include <security/pam_appl.h>
+
+#define USAGE "pamelad service_name user"
+
+static int cont = 1 ;
+static pam_handle_t *pamh ;
+
+
+ /* Utility */
+
+static void get (struct iovec *v)
+{
+  ssize_t r = textmessage_timed_receive(textmessage_receiver_0, v, 0, 0) ;
+  if (r < 0) _exit(1) ;
+  if (!r) _exit(0) ;
+}
+
+static void put (char const *s, size_t len)
+{
+  if (!textmessage_timed_send(textmessage_sender_1, s, len, 0, 0))
+    _exit(1) ;
+}
+
+static void putv (struct iovec const *v, unsigned int n)
+{
+  if (!textmessage_timed_sendv(textmessage_sender_1, v, n, 0, 0))
+    _exit(1) ;
+}
+
+static inline void env_free (char **envp)
+{
+  char **p = envp ;
+  while (*p) free(*p++) ;
+  free(envp) ;
+}
+
+
+ /* If the app customizes fail_delay */
+
+static void custom_delay (int retval, unsigned int usec, void *aux)
+{
+  struct iovec v ;
+  char pack[9] = "d" ;
+  uint32_pack_big(pack+1, (uint32_t)retval) ;
+  uint32_pack_big(pack+5, (uint32_t)usec) ;
+  put(pack, 9) ;
+  get(&v) ;
+  if (v.iov_len != 1 || ((char const *)v.iov_base)[0] != 'D') _exit(1) ;
+  (void)aux ;
+}
+
+
+ /* Conversation */
+
+static void freeres (struct pam_response *res, unsigned int i)
+{
+  while (i--) free(res[i].resp) ;
+  free(res) ;
+}
+
+static int converse (int n, struct pam_message const **msg, struct pam_response **resp, void *aux)
+{
+  if (n < 0 || n > PAMELA_PAM_CONV_MAX_MESSAGES) return PAM_SYSTEM_ERR ;
+  {
+    char pack[n * 4] ;
+    struct iovec v[n+2] ;
+    v[0].iov_base = "c" ;
+    v[0].iov_len = 1 ;
+    v[1].iov_base = pack ;
+    v[1].iov_len = n << 2 ;
+    for (uint32_t i = 0 ; i < n ; i++) uint32_pack_big(pack + 4 * i, (uint32_t)msg[i]->msg_style) ;
+    for (uint32_t i = 0 ; i < n ; i++)
+    {
+      v[i+2].iov_base = (char *)msg[i]->msg ;
+      v[i+2].iov_len = strlen(msg[i]->msg) + 1 ;
+    }
+    putv(v, n+2) ;
+  }
+  {
+    struct pam_response *res ;
+    struct iovec v ;
+    char const *s ;
+    size_t len ;
+    get(&v) ;
+    if (v.iov_len < 2) return PAM_ABORT ;
+    s = v.iov_base ;
+    len = v.iov_len - 2 ;
+    if (s[0] != 'C') return PAM_ABORT ;
+    if (s[1]) return s[1] ;
+    res = malloc(n * sizeof(struct pam_response)) ;
+    if (!res) return PAM_BUF_ERR ;
+    for (uint32_t i = 0 ; i < n ; i++)
+    {
+      size_t pos = strnlen(s, len) ;
+      if (pos == len) return PAM_ABORT ;
+      res[i].resp_retcode = 0 ;
+      res[i].resp = strdup(s) ;
+      if (!res[i].resp)
+      {
+        freeres(res, i) ;
+        return PAM_BUF_ERR ;
+      }
+      s += pos + 1 ; len -= pos + 1 ;
+    }
+    if (len)
+    {
+      freeres(res, n) ;
+      return PAM_ABORT ;
+    }
+    *resp = res ;
+  }
+  (void)aux ;
+  return PAM_SUCCESS ;
+}
+
+
+ /* Protocol and actions */
+
+static void do_strerror (int num)
+{
+  char const *x = pam_strerror(pamh, num) ;
+  if (!x)
+  {
+    char c = errno ;
+    put(&c, 1) ;
+  }
+  else
+  {
+    struct iovec v[2] =
+    {
+      { .iov_base = "", .iov_len = 1 },
+      { .iov_base = (char *)x, .iov_len = strlen(x) + 1 }
+    } ;
+    putv(v, 2) ;
+  }
+}
+
+static void do_getenvlist (void)
+{
+  char **envp = pam_getenvlist(pamh) ;
+  if (!envp)
+  {
+    char c = errno ;
+    put(&c, 1) ;
+  }
+  else
+  {
+    size_t n = env_len((char const *const *)envp) ;
+    struct iovec v[n+1] ;
+    v[0].iov_base = "" ;
+    v[0].iov_len = 1 ;
+    for (size_t i = 0 ; i < n ; i++)
+    {
+      v[i+1].iov_base = envp[i] ;
+      v[i+1].iov_len = strlen(envp[i]) + 1 ;
+    }
+    putv(v, n+1) ;
+    env_free(envp) ;
+  }
+}
+
+static void do_getitem (int num)
+{
+  void const *item ;
+  int e = pam_get_item(pamh, num, &item) ;
+  if (e != PAM_SUCCESS)
+  {
+    char c = e ;
+    put(&c, 1) ;
+    return ;
+  }
+  switch (num)
+  {
+    case PAMELA_PAM_FAIL_DELAY :
+    case PAMELA_PAM_CONV :
+    {
+      char c = PAMELA_PAM_BAD_ITEM ;
+      put(&c, 1) ;
+      break ;
+    }
+    case PAMELA_PAM_XAUTHDATA :
+    {
+      struct pam_xauth_data const *p = item ;
+      char pack[8] ;
+      struct iovec v[4] =
+      {
+        { .iov_base = "", .iov_len = 1 },
+        { .iov_base = pack, .iov_len = 8 },
+        { .iov_base = p->name, .iov_len = p->namelen + 1 },
+        { .iov_base = p->data, .iov_len = p->datalen + 1 }
+      } ;
+      uint32_pack_big(pack, (uint32_t)p->namelen) ;
+      uint32_pack_big(pack + 4, (uint32_t)p->datalen) ;
+      putv(v, 4) ;
+      break ;
+    }
+    default :
+    {
+      struct iovec v[2] =
+      {
+        { .iov_base = "", .iov_len = 1 },
+        { .iov_base = (char *)item, .iov_len = strlen((char const *)item) + 1 }
+      } ;
+      putv(v, 2) ;
+      break ;
+    }
+  }
+}
+
+static void do_setitem (int num, char const *s, size_t len)
+{
+  char c ;
+  switch (num)
+  {
+    case PAMELA_PAM_FAIL_DELAY :
+    case PAMELA_PAM_CONV :
+    baditem:
+      c = PAMELA_PAM_BAD_ITEM ;
+      break ;
+    case PAMELA_ENV :
+      if (s[len-1]) goto baditem ;
+      c = pam_putenv(pamh, s) ;
+      break ;
+    case PAMELA_PAM_XAUTHDATA :
+    {
+      struct pam_xauth_data xd ;
+      uint32_t u ;
+      if (len < 10) goto baditem ;
+      uint32_unpack_big(s, &u) ;
+      xd.namelen = u ;
+      uint32_unpack_big(s + 4, &u) ;
+      xd.datalen = u ;
+      if (len != 10 + xd.namelen + xd.datalen) goto baditem ;
+      xd.name = (char *)s + 8 ;
+      xd.data = (char *)s + 9 + xd.namelen ;
+      c = pam_set_item(pamh, PAM_XAUTHDATA, &xd) ;
+      break ;
+    }
+    default :
+      if (s[len-1]) goto baditem ;
+      c = pam_set_item(pamh, num, s) ;
+      break ;
+  }
+  put(&c, 1) ;
+}
+
+static void do_op (char type, int num)
+{
+  char s[2] = "o" ;
+  switch (type)
+  {
+    case PAMELA_OP_ACCT_MGMT :
+      s[1] = pam_acct_mgmt(pamh, num) ;
+      break ;
+    case PAMELA_OP_AUTHENTICATE :
+      s[1] = pam_authenticate(pamh, num) ;
+      break ;
+    case PAMELA_OP_CHAUTHTOK :
+      s[1] = pam_chauthtok(pamh, num) ;
+      break ;
+    case PAMELA_OP_CLOSE_SESSION :
+      s[1] = pam_close_session(pamh, num) ;
+      break ;
+    case PAMELA_OP_FAIL_DELAY :
+      s[1] = pam_fail_delay(pamh, (unsigned int)num) ;
+      break ;
+    case PAMELA_OP_OPEN_SESSION :
+      s[1] = pam_open_session(pamh, num) ;
+      break ;
+    case PAMELA_OP_SETCRED :
+      s[1] = pam_setcred(pamh, num) ;
+      break ;
+    case PAMELA_OP_SETFAILDELAY :
+      s[1] = pam_set_item(pamh, PAM_FAIL_DELAY, &custom_delay) ;
+      break ;
+    case PAMELA_OP_END :
+      s[1] = pam_end(pamh, num) ;
+      break ;
+    default :
+      s[1] = PAM_ABORT ;
+      break ;
+  }
+  put(s, 2) ;
+  if (type == PAMELA_OP_END) _exit(0) ;
+}
+
+
+ /* Main */
+
+int main (int argc, char const *const *argv)
+{
+  PROG = "pamelad" ;
+  if (argc < 3) strerr_dieusage(100, USAGE) ;
+  if (ndelay_on(0) < 0) strerr_diefu2sys(111, "ndelay_on ", "0") ;
+  if (ndelay_on(1) < 0) strerr_diefu2sys(111, "ndelay_on ", "1") ;
+  if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ;
+
+  if (!getgid())
+  {
+    char const *x = getenv("PAMELA_GID") ;
+    if (x)
+    {
+      gid_t g ;
+      if (!gid0_scan(x, &g))
+        strerr_warnw1x("invalid PAMELA_GID, keeping gid 0") ;
+      else setgid(g) ;
+    }
+  }
+  if (!getuid())
+  {
+    char const *x = getenv("PAMELA_UID") ;
+    if (x)
+    {
+      uid_t u ;
+      if (!uid0_scan(x, &u))
+        strerr_warnw1x("invalid PAMELA_UID, keeping uid 0") ;
+      else setuid(u) ;
+    }
+  }
+
+  {
+    struct pam_conv conv = { .conv = &converse, .appdata_ptr = 0 } ;
+    char c = pam_start(argv[1], argv[2], &conv, &pamh) ;
+    put(&c, 1) ;
+    if (c != PAM_SUCCESS) return 2 ;
+  }
+
+  while (cont)
+  {
+    struct iovec v ;
+    char const *s ;
+    size_t len ;
+    get(&v) ;
+    len = v.iov_len ;
+    if (!len) return 1 ;
+    s = v.iov_base ;
+    switch (s[0])
+    {
+      case 'E' :
+        if (len != 2) return 1 ;
+        do_strerror(s[1]) ;
+        break ;
+      case 'V' :
+        if (len != 1) return 1 ;
+        do_getenvlist() ;
+        break ;
+      case 'G' :
+        if (len != 2) return 1 ;
+        do_getitem(s[1]) ;
+        break ;
+      case 'S' :
+        if (len < 3) return 1 ;
+        do_setitem(s[1], s + 2, len - 2) ;
+        break ;
+      case 'O' :
+      {
+        unsigned int arg ;
+        if (len != 2 + sizeof(int)) return 1 ;
+        uint_unpack(s + 2, &arg) ;
+        do_op(s[1], (int)arg) ;
+        break ;
+      }
+      default : return 1 ;
+    }
+  }
+  return 0 ;
+}
diff --git a/tools/gen-deps.sh b/tools/gen-deps.sh
new file mode 100755
index 0000000..6383ac2
--- /dev/null
+++ b/tools/gen-deps.sh
@@ -0,0 +1,93 @@
+#!/bin/sh -e
+
+. package/info
+
+echo '#'
+echo '# This file has been generated by tools/gen-deps.sh'
+echo '#'
+echo
+
+for dir in src/include/${package} src/* ; do
+  for file in $(ls -1 $dir | grep -- \\.h$) ; do
+    {
+      grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ;
+      grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2
+    } | sort -u | {
+      deps=
+      while read dep ; do
+        if echo $dep | grep -q "^${package}/" ; then
+          deps="$deps src/include/$dep"
+        elif test -f "${dir}/$dep" ; then
+          deps="$deps ${dir}/$dep"
+        else
+          deps="$deps src/include-local/$dep"
+        fi
+      done
+      if test -n "$deps" ; then
+        echo "${dir}/${file}:${deps}"
+      fi
+    }
+  done
+done
+
+for dir in src/* ; do
+  for file in $(ls -1 $dir | grep -- \\.c$) ; do
+    {
+      grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ;
+      grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2
+    } | sort -u | {
+      deps=" ${dir}/$file"
+      while read dep ; do
+        if echo $dep | grep -q "^${package}/" ; then
+          deps="$deps src/include/$dep"
+        elif test -f "${dir}/$dep" ; then
+          deps="$deps ${dir}/$dep"
+        else
+          deps="$deps src/include-local/$dep"
+        fi
+      done
+      o=$(echo $file | sed s/\\.c$/.o/)
+      lo=$(echo $file | sed s/\\.c$/.lo/)
+      echo "${dir}/${o} ${dir}/${lo}:${deps}"
+    }
+  done
+done
+echo
+
+for dir in $(ls -1 src | grep -v ^include) ; do
+  for file in $(ls -1 src/$dir/deps-lib) ; do
+    deps=
+    libs=
+    while read dep ; do
+      if echo $dep | grep -q -e ^-l -e '^\${.*_LIB}' ; then
+        libs="$libs $dep"
+      else
+        deps="$deps src/$dir/$dep"
+      fi
+    done < src/$dir/deps-lib/$file
+    echo 'ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)'
+    echo "lib${file}.a.xyzzy:$deps"
+    echo else
+    echo "lib${file}.a.xyzzy:$(echo "$deps" | sed 's/\.o/.lo/g')"
+    echo endif
+    echo "lib${file}.so.xyzzy: EXTRA_LIBS :=$libs"
+    echo "lib${file}.so.xyzzy:$(echo "$deps" | sed 's/\.o/.lo/g')"
+  done
+
+  for file in $(ls -1 src/$dir/deps-exe) ; do
+    deps=
+    libs=
+    while read dep ; do
+      if echo $dep | grep -q -- \\.o$ ; then
+        dep="src/$dir/$dep"
+      fi
+      if echo $dep | grep -q -- '^\${.*_LIB}' ; then
+        libs="$libs $dep"
+      else
+        deps="$deps $dep"
+      fi
+    done < src/$dir/deps-exe/$file
+    echo "$file: EXTRA_LIBS :=$libs"
+    echo "$file: src/$dir/$file.o$deps"
+  done
+done
diff --git a/tools/install.sh b/tools/install.sh
new file mode 100755
index 0000000..89f9428
--- /dev/null
+++ b/tools/install.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+usage() {
+  echo "usage: $0 [-D] [-l] [-m mode] src dst" 1>&2
+  exit 1
+}
+
+mkdirp=false
+symlink=false
+mode=0755
+
+while getopts Dlm: name ; do
+  case "$name" in
+    D) mkdirp=true ;;
+    l) symlink=true ;;
+    m) mode=$OPTARG ;;
+    ?) usage ;;
+  esac
+done
+shift $(($OPTIND - 1))
+
+test "$#" -eq 2 || usage
+src=$1
+dst=$2
+tmp="$dst.tmp.$$"
+
+case "$dst" in
+  */) echo "$0: $dst ends in /" 1>&2 ; exit 1 ;;
+esac
+
+set -C
+set -e
+
+if $mkdirp ; then
+  umask 022
+  case "$2" in
+    */*) mkdir -p "${dst%/*}" ;;
+  esac
+fi
+
+trap 'rm -f "$tmp"' EXIT INT QUIT TERM HUP
+
+umask 077
+
+if $symlink ; then
+  ln -s "$src" "$tmp"
+else
+  cat < "$1" > "$tmp"
+  chmod "$mode" "$tmp"
+fi
+
+mv -f "$tmp" "$dst"
+if test -d "$dst" ; then
+  rm -f "$dst/$(basename $tmp)"
+  if $symlink ; then
+    mkdir "$tmp"
+    ln -s "$src" "$tmp/$(basename $dst)"
+    mv -f "$tmp/$(basename $dst)" "${dst%/*}"
+    rmdir "$tmp"
+  else
+    echo "$0: $dst is a directory" 1>&2
+    exit 1
+  fi
+fi