about summary refs log tree commit diff
path: root/src
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 /src
downloadpamela-03012f54b1bcd31e0b817fc0222a9a47709c4018.tar.gz
pamela-03012f54b1bcd31e0b817fc0222a9a47709c4018.tar.xz
pamela-03012f54b1bcd31e0b817fc0222a9a47709c4018.zip
Initial commit
Diffstat (limited to 'src')
-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
34 files changed, 1394 insertions, 0 deletions
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 ;
+}