about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2017-11-20 14:51:36 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2017-11-20 14:51:36 +0000
commit4ba5ae5776c2e9ba4f297115c19923a928cf3e87 (patch)
tree5a67f39a64dc5df84fc20307aa95d7536890b8b5 /src
downloadutmps-4ba5ae5776c2e9ba4f297115c19923a928cf3e87.tar.gz
utmps-4ba5ae5776c2e9ba4f297115c19923a928cf3e87.tar.xz
utmps-4ba5ae5776c2e9ba4f297115c19923a928cf3e87.zip
Initial release / rename to utmps
Diffstat (limited to 'src')
-rw-r--r--src/include/utmps/utmps.h35
-rw-r--r--src/include/utmps/utmpx.h75
-rw-r--r--src/include/utmpx.h13
-rw-r--r--src/utmps/deps-exe/utmps-utmpd2
-rw-r--r--src/utmps/deps-exe/utmps-wtmpd2
-rw-r--r--src/utmps/deps-lib/utmps21
-rw-r--r--src/utmps/endutxent.c10
-rw-r--r--src/utmps/getutxent.c12
-rw-r--r--src/utmps/getutxid.c12
-rw-r--r--src/utmps/getutxline.c12
-rw-r--r--src/utmps/logwtmp.c22
-rw-r--r--src/utmps/pututxline.c14
-rw-r--r--src/utmps/setutxent.c11
-rw-r--r--src/utmps/updwtmpx.c10
-rw-r--r--src/utmps/utmps-internal.h16
-rw-r--r--src/utmps/utmps-utmpd.c216
-rw-r--r--src/utmps/utmps-wtmpd.c107
-rw-r--r--src/utmps/utmps_end.c10
-rw-r--r--src/utmps/utmps_getent.c21
-rw-r--r--src/utmps/utmps_getid.c27
-rw-r--r--src/utmps/utmps_getline.c25
-rw-r--r--src/utmps/utmps_here.c8
-rw-r--r--src/utmps/utmps_here_maybe_init.c11
-rw-r--r--src/utmps/utmps_putline.c22
-rw-r--r--src/utmps/utmps_rewind.c19
-rw-r--r--src/utmps/utmps_start.c18
-rw-r--r--src/utmps/utmps_updwtmpx.c28
-rw-r--r--src/utmps/utmps_utmpx_pack.c9
-rw-r--r--src/utmps/utmps_utmpx_unpack.c13
29 files changed, 801 insertions, 0 deletions
diff --git a/src/include/utmps/utmps.h b/src/include/utmps/utmps.h
new file mode 100644
index 0000000..e4760f9
--- /dev/null
+++ b/src/include/utmps/utmps.h
@@ -0,0 +1,35 @@
+/* ISC license. */
+
+#ifndef UTMPS_H
+#define UTMPS_H
+
+#include <skalibs/tai.h>
+#include <utmps/utmpx.h>
+
+typedef struct utmps_s utmps, *utmps_ref ;
+struct utmps_s
+{
+  int fd ;
+} ;
+#define UTMPS_ZERO { .fd = -1 }
+
+extern int utmps_start (utmps *, char const *, tain_t const *, tain_t *) ;
+#define utmps_start_g(a, s, deadline) utmps_start(a, s, (deadline), &STAMP)
+
+extern void utmps_end (utmps *) ;
+
+extern int utmps_rewind (utmps *, tain_t const *, tain_t *) ;
+#define utmps_rewind_g (a, deadline) utmps_rewind(a, (deadline), &STAMP)
+extern int utmps_getent (utmps *, struct utmpx *, tain_t const *, tain_t *) ;
+#define utmps_getent_g (a, b, deadline) utmps_getent(a, b, (deadline), &STAMP)
+extern int utmps_getid (utmps *, unsigned short, char const *, struct utmpx *, tain_t const *, tain_t *) ;
+#define utmps_getid_g(a, type, id, b, deadline) utmps_getid(a, type, id, b, (deadline), &STAMP)
+extern int utmps_getline (utmps *, char const *, struct utmpx *, tain_t const *, tain_t *) ;
+#define utmps_getline_g(a, line, b, deadline) utmps_getline(a, line, b, (deadline), &STAMP)
+extern int utmps_putline (utmps *, struct utmpx const *, tain_t const *, tain_t *) ;
+#define utmps_putline_g(a, entry, deadline) utmps_putline(a, entry, (deadine), &STAMP)
+
+extern int utmps_updwtmpx (char const *, struct utmpx const *, tain_t const *, tain_t *) ;
+#define utmps_updwtmpx_g(file, b, deadline) utmps_updwtmpx(file, b, (deadline), &STAMP)
+
+#endif
diff --git a/src/include/utmps/utmpx.h b/src/include/utmps/utmpx.h
new file mode 100644
index 0000000..4752964
--- /dev/null
+++ b/src/include/utmps/utmpx.h
@@ -0,0 +1,75 @@
+/* ISC license. */
+
+#ifndef UTMPS_UTMPX_H
+#define UTMPS_UTMPX_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <sys/time.h>
+
+#define UTMPS_UT_LINESIZE 32
+#define UTMPS_UT_NAMESIZE 32
+#define UTMPS_UT_HOSTSIZE 256
+#define UTMPS_UT_IDSIZE 4
+
+struct exit_status
+{
+  short e_termination ;
+  short e_exit ;
+} ;
+
+struct utmpx
+{
+  short ut_type ;
+  pid_t ut_pid ;
+  char ut_line[UTMPS_UT_LINESIZE] ;
+  char ut_id[UTMPS_UT_IDSIZE] ;
+  char ut_user[UTMPS_UT_NAMESIZE] ;
+
+  char ut_host[UTMPS_UT_HOSTSIZE] ;
+  struct exit_status ut_exit ;
+  pid_t ut_session ;
+
+  struct timeval ut_tv ;
+
+  uint32_t ut_addr_v6[4] ;
+  char __unused[20] ;
+} ;
+
+#define ut_name ut_user
+
+#define EMPTY 0
+#define BOOT_TIME 2
+#define OLD_TIME 4
+#define NEW_TIME 3
+#define USER_PROCESS 7
+#define INIT_PROCESS 5
+#define LOGIN_PROCESS 6
+#define DEAD_PROCESS 8
+
+#define RUN_LVL 1
+#define ACCOUNTING 9
+
+extern void endutxent (void) ;
+extern void setutxent (void) ;
+extern struct utmpx *getutxent (void) ;
+extern struct utmpx *getutxid (struct utmpx const *) ;
+extern struct utmpx *getutxline (struct utmpx const *) ;
+extern struct utmpx *pututxline (struct utmpx const *) ;
+
+extern void updwtmpx (char const *, struct utmpx const *) ;
+extern void logwtmp (char const *, char const *, char const *) ;
+
+#define UT_LINESIZE UTMPS_UT_LINESIZE
+#define UT_NAMESIZE UTMPS_UT_NAMESIZE
+#define UT_HOSTSIZE UTMPS_UT_HOSTSIZE
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/include/utmpx.h b/src/include/utmpx.h
new file mode 100644
index 0000000..fc61b8b
--- /dev/null
+++ b/src/include/utmpx.h
@@ -0,0 +1,13 @@
+/* ISC license. */
+
+/*
+  This file is part of the utmps package.
+  See https://skarnet.org/software/utmps/
+*/
+
+#ifndef UTMPX_H
+#define UTMPX_H
+
+#include <utmps/utmpx.h>
+
+#endif
diff --git a/src/utmps/deps-exe/utmps-utmpd b/src/utmps/deps-exe/utmps-utmpd
new file mode 100644
index 0000000..60d8fd7
--- /dev/null
+++ b/src/utmps/deps-exe/utmps-utmpd
@@ -0,0 +1,2 @@
+libutmps.a.xyzzy
+-lskarnet
diff --git a/src/utmps/deps-exe/utmps-wtmpd b/src/utmps/deps-exe/utmps-wtmpd
new file mode 100644
index 0000000..60d8fd7
--- /dev/null
+++ b/src/utmps/deps-exe/utmps-wtmpd
@@ -0,0 +1,2 @@
+libutmps.a.xyzzy
+-lskarnet
diff --git a/src/utmps/deps-lib/utmps b/src/utmps/deps-lib/utmps
new file mode 100644
index 0000000..430b7fb
--- /dev/null
+++ b/src/utmps/deps-lib/utmps
@@ -0,0 +1,21 @@
+endutxent.o
+getutxent.o
+getutxid.o
+getutxline.o
+logwtmp.o
+pututxline.o
+setutxent.o
+updwtmpx.o
+utmps_end.o
+utmps_getent.o
+utmps_getid.o
+utmps_getline.o
+utmps_here.o
+utmps_here_maybe_init.o
+utmps_putline.o
+utmps_rewind.o
+utmps_start.o
+utmps_updwtmpx.o
+utmps_utmpx_pack.o
+utmps_utmpx_unpack.o
+-lskarnet
diff --git a/src/utmps/endutxent.c b/src/utmps/endutxent.c
new file mode 100644
index 0000000..bc93778
--- /dev/null
+++ b/src/utmps/endutxent.c
@@ -0,0 +1,10 @@
+/* ISC license. */
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+void endutxent (void)
+{
+  utmps_end(&utmps_here) ;
+}
diff --git a/src/utmps/getutxent.c b/src/utmps/getutxent.c
new file mode 100644
index 0000000..97a5917
--- /dev/null
+++ b/src/utmps/getutxent.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+struct utmpx *getutxent (void)
+{
+  utmps_here_maybe_init() ;
+  if (!utmps_getent(&utmps_here, &utmps_utmpx_here, 0, 0)) return 0 ;
+  return &utmps_utmpx_here ;
+}
diff --git a/src/utmps/getutxid.c b/src/utmps/getutxid.c
new file mode 100644
index 0000000..262f35c
--- /dev/null
+++ b/src/utmps/getutxid.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+struct utmpx *getutxid (struct utmpx const *b)
+{
+  utmps_here_maybe_init() ;
+  if (!utmps_getid(&utmps_here, (unsigned short)b->ut_type, b->ut_id, &utmps_utmpx_here, 0, 0)) return 0 ;
+  return &utmps_utmpx_here ;
+}
diff --git a/src/utmps/getutxline.c b/src/utmps/getutxline.c
new file mode 100644
index 0000000..e950816
--- /dev/null
+++ b/src/utmps/getutxline.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+struct utmpx *getutxline (struct utmpx const *b)
+{
+  utmps_here_maybe_init() ;
+  if (!utmps_getline(&utmps_here, b->ut_line, &utmps_utmpx_here, 0, 0)) return 0 ;
+  return &utmps_utmpx_here ;
+}
diff --git a/src/utmps/logwtmp.c b/src/utmps/logwtmp.c
new file mode 100644
index 0000000..e50b68d
--- /dev/null
+++ b/src/utmps/logwtmp.c
@@ -0,0 +1,22 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <string.h>
+#include <skalibs/tai.h>
+#include <utmps/utmpx.h>
+
+void logwtmp (char const *line, char const *name, char const *host)
+{
+  struct utmpx b ;
+  memset(&b, 0, sizeof(struct utmpx)) ;
+  strncpy(b.ut_line, line, UTMPS_UT_LINESIZE - 1) ;
+  strncpy(b.ut_user, name, UTMPS_UT_NAMESIZE - 1) ;
+  strncpy(b.ut_host, host, UTMPS_UT_HOSTSIZE - 1) ;
+  b.ut_pid = getpid() ;
+  {
+    tain_t now ;
+    tain_now(&now) ;
+    timeval_from_tain(&b.ut_tv, &now) ;
+  }
+  updwtmpx("", &b) ;
+}
diff --git a/src/utmps/pututxline.c b/src/utmps/pututxline.c
new file mode 100644
index 0000000..4e149d8
--- /dev/null
+++ b/src/utmps/pututxline.c
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+struct utmpx *pututxline (struct utmpx const *b)
+{
+  static struct utmpx here ; /* POSIX says we can't use utmps_utmpx_here */
+  utmps_here_maybe_init() ;
+  if (!utmps_putline(&utmps_here, b, 0, 0)) return 0 ;
+  here = *b ;
+  return &here ;
+}
diff --git a/src/utmps/setutxent.c b/src/utmps/setutxent.c
new file mode 100644
index 0000000..b8b8199
--- /dev/null
+++ b/src/utmps/setutxent.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+void setutxent (void)
+{
+  utmps_here_maybe_init() ;
+  utmps_rewind(&utmps_here, 0, 0) ;
+}
diff --git a/src/utmps/updwtmpx.c b/src/utmps/updwtmpx.c
new file mode 100644
index 0000000..0166448
--- /dev/null
+++ b/src/utmps/updwtmpx.c
@@ -0,0 +1,10 @@
+/* ISC license. */
+
+#include <utmps/config.h>
+#include <utmps/utmps.h>
+
+void updwtmpx (char const *file, struct utmpx const *b)
+{
+  (void)file ;
+  utmps_updwtmpx(UTMPS_WTMPD_PATH, b, 0, 0) ;
+}
diff --git a/src/utmps/utmps-internal.h b/src/utmps/utmps-internal.h
new file mode 100644
index 0000000..476e100
--- /dev/null
+++ b/src/utmps/utmps-internal.h
@@ -0,0 +1,16 @@
+/* ISC license. */
+
+#ifndef UTMPS_INTERNAL_H
+#define UTMPS_INTERNAL_H
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+
+extern struct utmpx utmps_utmpx_here ;
+extern utmps utmps_here ;
+extern void utmps_here_maybe_init (void) ;
+
+extern void utmps_utmpx_pack (char *, struct utmpx const *) ;
+extern void utmps_utmpx_unpack (char const *, struct utmpx *) ;
+
+#endif
diff --git a/src/utmps/utmps-utmpd.c b/src/utmps/utmps-utmpd.c
new file mode 100644
index 0000000..e460368
--- /dev/null
+++ b/src/utmps/utmps-utmpd.c
@@ -0,0 +1,216 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <skalibs/types.h>
+#include <skalibs/env.h>
+#include <skalibs/error.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmpx.h>
+#include "utmps-internal.h"
+
+static int fd = -1 ;
+
+static void get0 (char *s, size_t n)
+{
+  tain_t deadline ;
+  tain_ulong(&deadline, 2) ;
+  tain_add_g(&deadline, &deadline) ;
+  if (buffer_timed_get_g(buffer_0small, s, n, &deadline) < n)
+    strerr_diefu1sys(111, "read from stdin") ;
+}
+
+static void flush1 (void)
+{
+  tain_t deadline ;
+  tain_ulong(&deadline, 2) ;
+  tain_add_g(&deadline, &deadline) ;
+  if (!buffer_timed_flush_g(buffer_1small, &deadline))
+    strerr_diefu1sys(111, "write to stdout") ;
+}
+
+static void answer (int e)
+{
+  char c = e ;
+  buffer_putnoflush(buffer_1small, &c, 1) ;
+  flush1() ;
+}
+
+static void maybe_open (void)
+{
+  if (fd < 0)
+  {
+    fd = open("utmp", O_RDWR | O_CREAT) ;
+    if (fd < 0)
+    {
+      int e = errno ;
+      answer(e) ;
+      errno = e ;
+      strerr_diefu1sys(111, "open utmp file") ;
+    }
+  }
+}
+
+static int read_utmp_entry (char *s)
+{
+  ssize_t r ;
+  int e ;
+  if (lock_sh(fd) < 0) { e = errno ; goto err ; }
+  r = read(fd, s, sizeof(struct utmpx)) ;
+  lock_unx(fd) ;
+  if (r < 0) { e = errno ; goto err ; }
+  if (!r) return 0 ;
+  if (r < sizeof(struct utmpx)) { e = EPIPE ; goto err ; }
+  return 1 ;
+ err:
+  unlink("utmp") ;
+  answer(e) ;
+  errno = e ;
+  strerr_diefu1sys(111, "read utmp file") ;
+}
+
+static int idmatch (unsigned short type, char const *id, struct utmpx const *b)
+{
+  if (type == BOOT_TIME || type == OLD_TIME || type == NEW_TIME)
+  {
+    if (type == (unsigned short)b->ut_type) return 1 ;
+  }
+  else if (type == INIT_PROCESS || type == LOGIN_PROCESS || type == USER_PROCESS || type == DEAD_PROCESS)
+  {
+    if ((b->ut_type == INIT_PROCESS || b->ut_type == LOGIN_PROCESS || b->ut_type == USER_PROCESS || b->ut_type == DEAD_PROCESS)
+      && !strncmp(id, b->ut_id, UTMPS_UT_IDSIZE - 1)) return 1 ;
+  }
+  return 0 ;
+}
+
+static void do_getent (void)
+{
+  struct utmpx b ;
+  char buf[1 + sizeof(struct utmpx)] = "" ;
+  maybe_open() ;
+  if (!read_utmp_entry(buf+1)) { answer(ESRCH) ; return ; }
+  utmps_utmpx_unpack(buf+1, &b) ;
+  utmps_utmpx_pack(buf+1, &b) ;
+  buffer_putnoflush(buffer_1small, buf, 1 + sizeof(struct utmpx)) ;
+  flush1() ;
+}
+
+static void do_getid (void)
+{
+  unsigned short type ;
+  char rbuf[USHORT_PACK + UTMPS_UT_IDSIZE] ;
+  char sbuf[1 + sizeof(struct utmpx)] = "" ;
+  get0(rbuf, USHORT_PACK + UTMPS_UT_IDSIZE) ;
+  ushort_unpack_big(rbuf, &type) ;
+  rbuf[USHORT_PACK + UTMPS_UT_IDSIZE - 1] = 0 ;
+  maybe_open() ;
+  for (;;)
+  {
+    struct utmpx b ;
+    if (!read_utmp_entry(sbuf+1)) { answer(ESRCH) ; return ; }
+    utmps_utmpx_unpack(sbuf+1, &b) ;
+    if (idmatch(type, rbuf + USHORT_PACK, &b)) break ;
+  }
+  buffer_putnoflush(buffer_1small, sbuf, 1 + sizeof(struct utmpx)) ;
+  flush1() ;
+}
+
+static void do_getline (void)
+{
+  char rbuf[UTMPS_UT_LINESIZE] ;
+  char sbuf[1 + sizeof(struct utmpx)] = "" ;
+  get0(rbuf, UTMPS_UT_LINESIZE) ;
+  rbuf[UTMPS_UT_LINESIZE - 1] = 0 ;
+  maybe_open() ;
+  for (;;)
+  {
+    struct utmpx b ;
+    if (!read_utmp_entry(sbuf+1)) { answer(ESRCH) ; return ; }
+    utmps_utmpx_unpack(sbuf+1, &b) ;
+    if ((b.ut_type == LOGIN_PROCESS || b.ut_type == USER_PROCESS)
+      && !strncmp(rbuf, b.ut_line, UTMPS_UT_LINESIZE - 1)) break ;
+  }
+  buffer_putnoflush(buffer_1small, sbuf, 1 + sizeof(struct utmpx)) ;
+  flush1() ;
+}
+
+static void do_putline (uid_t uid)
+{
+  struct utmpx u ;
+  char buf[sizeof(struct utmpx)] ;
+  get0(buf, sizeof(struct utmpx)) ;
+  if (uid) { answer(EPERM) ; return ; }
+  utmps_utmpx_unpack(buf, &u) ;
+  maybe_open() ;
+  for (;;)
+  {
+    struct utmpx b ;
+    char tmp[sizeof(struct utmpx)] ;
+    if (!read_utmp_entry(tmp)) goto writeit ;
+    utmps_utmpx_unpack(tmp, &b) ;
+    if (idmatch(u.ut_type, u.ut_id, &b)) break ;
+  }
+  if (lseek(fd, -sizeof(struct utmpx), SEEK_CUR) < 0)
+  {
+    answer(errno) ;
+    return ;
+  }
+ writeit:
+  utmps_utmpx_pack(buf, &u) ;
+  if (lock_ex(fd) < 0) { answer(errno) ; return ; }
+  if (allwrite(fd, buf, sizeof(struct utmpx)) < sizeof(struct utmpx))
+  {
+    int e = errno ;
+    answer(e) ;
+    errno = e ;
+    strerr_diefu1sys(111, "write to utmp") ;
+  }
+  fsync(fd) ;
+  lock_unx(fd) ;
+  answer(0) ;
+}
+
+static void do_rewind (void)
+{
+  maybe_open() ;
+  if (lseek(fd, 0, SEEK_SET) < 0) { answer(errno) ; return ; }
+  answer(0) ;
+}
+
+int main (void)
+{
+  uid_t uid ;
+  char const *x = ucspi_get("REMOTEEUID") ;
+  if (!x) strerr_diefu1x(100, "get $IPCREMOTEEUID from environment") ;
+  if (!uid0_scan(x, &uid)) strerr_dieinvalid(100, "IPCREMOTEEUID") ;
+  if (ndelay_on(0) < 0) strerr_diefu1sys(111, "set stdin non-blocking") ;
+  tain_now_g() ;
+
+  for (;;)
+  {
+    tain_t deadline ;
+    char c ;
+    tain_add_g(&deadline, &tain_infinite_relative) ;
+    if (!buffer_timed_get_g(buffer_0small, &c, 1, &deadline)) break ;
+    switch (c)
+    {
+      case 'e' : do_getent() ; break ;
+      case 'i' : do_getid() ; break ;
+      case 'l' : do_getline() ; break ;
+      case 'E' : do_putline(uid) ; break ;
+      case 'r' : do_rewind() ; break ;
+      default :
+        errno = EPROTO ;
+        strerr_diefu1sys(1, "interpret stdin") ;
+    }
+  }
+  return 0 ;
+}
diff --git a/src/utmps/utmps-wtmpd.c b/src/utmps/utmps-wtmpd.c
new file mode 100644
index 0000000..b8ff5c3
--- /dev/null
+++ b/src/utmps/utmps-wtmpd.c
@@ -0,0 +1,107 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <skalibs/types.h>
+#include <skalibs/error.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/env.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmpx.h>
+#include "utmps-internal.h"
+
+static void answer (int e)
+{
+  char c = e ;
+  write(1, &c, 1) ;
+}
+
+int main (void)
+{
+  struct utmpx b ;
+  char const *x ;
+  tain_t deadline ;
+  size_t w ;
+  uid_t uid ;
+  int fd ;
+  char buf[sizeof(struct utmpx)] ;
+  PROG = "utmps-wtmpd" ;
+
+  x = ucspi_get("REMOTEEUID") ;
+  if (!x) strerr_diefu1x(100, "get $IPCREMOTEEUID from environment") ;
+  if (!uid0_scan(x, &uid)) strerr_dieinvalid(100, "IPCREMOTEEUID") ;
+  if (ndelay_on(0) < 0) strerr_diefu1sys(111, "set stdin non-blocking") ;
+  tain_now_g() ;
+  tain_ulong(&deadline, 2) ;
+  tain_add_g(&deadline, &deadline) ;
+
+  w = buffer_timed_get_g(buffer_0small, buf, 1, &deadline) ;
+  if (!w) strerr_diefu1sys(111, "read from stdin") ;
+  if (buf[0] != '+') { errno = EPROTO ; strerr_diefu1sys(111, "read command") ; }
+  w = buffer_timed_get_g(buffer_0small, buf, sizeof(struct utmpx), &deadline) ; 
+  if (w < sizeof(struct utmpx)) strerr_diefu1sys(111, "read from stdin") ;
+  utmps_utmpx_unpack(buf, &b) ;
+  if (uid)
+  {
+    struct passwd *pw ;
+    errno = 0 ;
+    pw = getpwnam(b.ut_user) ;
+    if (!pw)
+    {
+      if (errno)
+      {
+        answer(errno) ;
+        strerr_diefu1sys(111, "read user database") ;
+      }
+      else
+      {
+        answer(EPERM) ;
+        strerr_diefu2x(1, "verify ut_user identity", ": no such user") ;
+      }
+    }
+    if (pw->pw_uid != uid)
+    {
+      answer(EPERM) ;
+      strerr_diefu2x(1, "verify ut_user identity", ": uid mismatch") ;
+    }
+  }
+  
+  fd = open_append("wtmp") ;
+  if (fd < 0)
+  {
+    answer(errno) ;
+    strerr_diefu1sys(111, "open wtmp") ;
+  }
+  if (lock_ex(fd) < 0)
+  {
+    answer(errno) ;
+    strerr_diefu1sys(111, "open wtmp") ;
+  }
+  if (lseek(fd, 0, SEEK_END) < 0)
+  {
+    answer(errno) ;
+    strerr_diefu1sys(111, "lseek on wtmp") ;
+  }
+  w = allwrite(fd, buf + 1, sizeof(struct utmpx)) ;
+  if (w < sizeof(struct utmpx))
+  {
+    int e = errno ;
+    if (w)
+    {
+      struct stat st ;
+      if (!fstat(fd, &st)) ftruncate(fd, st.st_size - w) ;
+    }
+    answer(e) ;
+    strerr_diefu1sys(111, "append to wtmp") ;
+  }
+  fsync(fd) ;
+  answer(0) ;
+  return 0 ;
+}
diff --git a/src/utmps/utmps_end.c b/src/utmps/utmps_end.c
new file mode 100644
index 0000000..141dfb7
--- /dev/null
+++ b/src/utmps/utmps_end.c
@@ -0,0 +1,10 @@
+/* ISC license. */
+
+#include <skalibs/djbunix.h>
+#include <utmps/utmps.h>
+
+void utmps_end (utmps *a)
+{
+  fd_close(a->fd) ;
+  a->fd = -1 ;
+}
diff --git a/src/utmps/utmps_getent.c b/src/utmps/utmps_getent.c
new file mode 100644
index 0000000..2b21b04
--- /dev/null
+++ b/src/utmps/utmps_getent.c
@@ -0,0 +1,21 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+int utmps_getent (utmps *a, struct utmpx *b, tain_t const *deadline, tain_t *stamp)
+{
+  ssize_t r ;
+  char buf[1 + sizeof(struct utmpx)] ;
+  if (!ipc_timed_send(a->fd, "e", 1, deadline, stamp)) return 0 ;
+  r = ipc_timed_recv(a->fd, buf, sizeof(buf), 0, deadline, stamp) ;
+  if (r < 0) return 0 ;
+  if (!r) return (errno = EPIPE, 0) ;
+  if (buf[0]) return (errno = buf[0], 0) ;
+  utmps_utmpx_unpack(buf + 1, b) ;
+  return 1 ;
+}
diff --git a/src/utmps/utmps_getid.c b/src/utmps/utmps_getid.c
new file mode 100644
index 0000000..622fec8
--- /dev/null
+++ b/src/utmps/utmps_getid.c
@@ -0,0 +1,27 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <string.h>
+#include <errno.h>
+#include <skalibs/types.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+int utmps_getid (utmps *a, unsigned short type, char const *id, struct utmpx *b, tain_t const *deadline, tain_t *stamp)
+{
+  ssize_t r ;
+  char sbuf[1 + USHORT_PACK + UTMPS_UT_IDSIZE] = "i" ;
+  char rbuf[1 + sizeof(struct utmpx)] ;
+  ushort_pack_big(sbuf + 1, type) ;
+  memset(sbuf + 1 + USHORT_PACK, 0, UTMPS_UT_IDSIZE) ;
+  strncpy(sbuf + 1 + USHORT_PACK, id, UTMPS_UT_IDSIZE - 1) ;
+  if (!ipc_timed_send(a->fd, sbuf, sizeof(sbuf), deadline, stamp)) return 0 ;
+  r = ipc_timed_recv(a->fd, rbuf, sizeof(rbuf), 0, deadline, stamp) ;
+  if (r < 0) return 0 ;
+  if (!r) return (errno = EPIPE, 0) ;
+  if (rbuf[0]) return (errno = rbuf[0], 0) ;
+  utmps_utmpx_unpack(rbuf + 1, b) ;
+  return 1 ;
+}
diff --git a/src/utmps/utmps_getline.c b/src/utmps/utmps_getline.c
new file mode 100644
index 0000000..4612082
--- /dev/null
+++ b/src/utmps/utmps_getline.c
@@ -0,0 +1,25 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <string.h>
+#include <errno.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+int utmps_getline (utmps *a, char const *line, struct utmpx *b, tain_t const *deadline, tain_t *stamp)
+{
+  ssize_t r ;
+  char sbuf[1 + UTMPS_UT_LINESIZE] = "l" ;
+  char rbuf[1 + sizeof(struct utmpx)] ;
+  memset(sbuf + 1, 0, UTMPS_UT_LINESIZE) ;
+  strncpy(sbuf + 1, line, UTMPS_UT_LINESIZE - 1) ;
+  if (!ipc_timed_send(a->fd, sbuf, sizeof(sbuf), deadline, stamp)) return 0 ;
+  r = ipc_timed_recv(a->fd, rbuf, sizeof(rbuf), 0, deadline, stamp) ;
+  if (r < 0) return 0 ;
+  if (!r) return (errno = EPIPE, 0) ;
+  if (rbuf[0]) return (errno = rbuf[0], 0) ;
+  utmps_utmpx_unpack(rbuf + 1, b) ;
+  return 1 ;
+}
diff --git a/src/utmps/utmps_here.c b/src/utmps/utmps_here.c
new file mode 100644
index 0000000..2844c1e
--- /dev/null
+++ b/src/utmps/utmps_here.c
@@ -0,0 +1,8 @@
+/* ISC license. */
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+struct utmpx utmps_utmpx_here ;
+utmps utmps_here = UTMPS_ZERO ;
diff --git a/src/utmps/utmps_here_maybe_init.c b/src/utmps/utmps_here_maybe_init.c
new file mode 100644
index 0000000..4f3e207
--- /dev/null
+++ b/src/utmps/utmps_here_maybe_init.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <utmps/config.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+void utmps_here_maybe_init (void)
+{
+  if (utmps_here.fd < 0)
+    utmps_start(&utmps_here, UTMPS_UTMPD_PATH, 0, 0) ;
+}
diff --git a/src/utmps/utmps_putline.c b/src/utmps/utmps_putline.c
new file mode 100644
index 0000000..306fa1b
--- /dev/null
+++ b/src/utmps/utmps_putline.c
@@ -0,0 +1,22 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <string.h>
+#include <errno.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+int utmps_putline (utmps *a, struct utmpx const *b, tain_t const *deadline, tain_t *stamp)
+{
+  ssize_t r ;
+  char buf[1 + sizeof(struct utmpx)] = "E" ;
+  utmps_utmpx_pack(buf + 1, b) ;
+  if (!ipc_timed_send(a->fd, buf, sizeof(buf), deadline, stamp)) return 0 ;
+  r = ipc_timed_recv(a->fd, buf, 1, 0, deadline, stamp) ;
+  if (r < 0) return 0 ;
+  if (!r) return (errno = EPIPE, 0) ;
+  if (buf[0]) return (errno = buf[0], 0) ;
+  return 1 ;
+}
diff --git a/src/utmps/utmps_rewind.c b/src/utmps/utmps_rewind.c
new file mode 100644
index 0000000..371cb0d
--- /dev/null
+++ b/src/utmps/utmps_rewind.c
@@ -0,0 +1,19 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+int utmps_rewind (utmps *a, tain_t const *deadline, tain_t *stamp)
+{
+  ssize_t r ;
+  char c ;
+  if (!ipc_timed_send(a->fd, "r", 1, deadline, stamp)) return 0 ;
+  r = ipc_timed_recv(a->fd, &c, 1, 0, deadline, stamp) ;
+  if (r < 0) return 0 ;
+  if (!r) return (errno = EPIPE, 0) ;
+  if (c) return (errno = c, 0) ;
+  return 1 ;
+}
diff --git a/src/utmps/utmps_start.c b/src/utmps/utmps_start.c
new file mode 100644
index 0000000..095fd2c
--- /dev/null
+++ b/src/utmps/utmps_start.c
@@ -0,0 +1,18 @@
+/* ISC license. */
+
+#include <skalibs/djbunix.h>
+#include <skalibs/webipc.h>
+#include <utmps/utmps.h>
+
+int utmps_start (utmps *a, char const *path, tain_t const *deadline, tain_t *stamp)
+{
+  int fd = ipc_stream_nbcoe() ;
+  if (fd < 0) return 0 ;
+  if (!ipc_timed_connect(fd, path, deadline, stamp))
+  {
+    fd_close(fd) ;
+    return 0 ;
+  }
+  a->fd = fd ;
+  return 1 ;
+}
diff --git a/src/utmps/utmps_updwtmpx.c b/src/utmps/utmps_updwtmpx.c
new file mode 100644
index 0000000..55811c4
--- /dev/null
+++ b/src/utmps/utmps_updwtmpx.c
@@ -0,0 +1,28 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+int utmps_updwtmpx (char const *path, struct utmpx const *b, tain_t const *deadline, tain_t *stamp)
+{
+  utmps a = UTMPS_ZERO ;
+  ssize_t r ;
+  char buf[1 + sizeof(struct utmpx)] = "+" ;
+  if (!utmps_start(&a, path, deadline, stamp)) return 0 ;
+  utmps_utmpx_pack(buf + 1, b) ;
+  if (!ipc_timed_send(a.fd, buf, 1 + sizeof(struct utmpx), deadline, stamp)) goto err ;
+  r = ipc_timed_recv(a.fd, buf, 1, 0, deadline, stamp) ;
+  if (r < 0) goto err ;
+  if (!r) { errno = EPIPE ; goto err ; }
+  if (buf[0]) { errno = buf[0] ; goto err ; }
+  utmps_end(&a) ;
+  return 1 ;
+
+ err :
+  utmps_end(&a) ;
+  return 0 ;
+}
diff --git a/src/utmps/utmps_utmpx_pack.c b/src/utmps/utmps_utmpx_pack.c
new file mode 100644
index 0000000..c5efecb
--- /dev/null
+++ b/src/utmps/utmps_utmpx_pack.c
@@ -0,0 +1,9 @@
+/* ISC license. */
+
+#include <string.h>
+#include <utmps/utmpx.h>
+
+void utmps_utmpx_pack (char *s, struct utmpx const *u)
+{
+  memcpy(s, u, sizeof(struct utmpx)) ;
+}
diff --git a/src/utmps/utmps_utmpx_unpack.c b/src/utmps/utmps_utmpx_unpack.c
new file mode 100644
index 0000000..a774356
--- /dev/null
+++ b/src/utmps/utmps_utmpx_unpack.c
@@ -0,0 +1,13 @@
+/* ISC license. */
+
+#include <string.h>
+#include <utmps/utmpx.h>
+
+void utmps_utmpx_unpack (char const *s, struct utmpx *b)
+{
+  memcpy(b, s, sizeof(struct utmpx)) ;
+  b->ut_user[UTMPS_UT_NAMESIZE - 1] = 0 ;
+  b->ut_id[UTMPS_UT_IDSIZE - 1] = 0 ;
+  b->ut_line[UTMPS_UT_LINESIZE - 1] = 0 ;
+  b->ut_host[UTMPS_UT_HOSTSIZE - 1] = 0 ;
+}