From c4f2d992e4a0fc068281376d89ec38de56dc2f58 Mon Sep 17 00:00:00 2001
From: mdw <mdw>
Date: Mon, 21 Jul 1997 13:47:55 +0000
Subject: [PATCH] Initial revision

---
 Makefile.in         | 263 ++++++++++++++++
 config.h.in         | 132 ++++++++
 configure.in        | 103 ++++++
 manual/become.tex   | 888 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/become.c        | 480 ++++++++++++++++++++++++++++
 src/blowfish-sbox.h | 320 +++++++++++++++++++
 src/blowfish.c      | 608 +++++++++++++++++++++++++++++++++++
 src/blowfish.h      | 110 +++++++
 src/check.c         | 286 +++++++++++++++++
 src/check.h         |  70 +++++
 src/class.c         | 325 +++++++++++++++++++
 src/class.h         | 175 +++++++++++
 src/crypt.c         | 566 +++++++++++++++++++++++++++++++++
 src/crypt.h         | 181 +++++++++++
 src/daemon.c        | 419 +++++++++++++++++++++++++
 src/daemon.h        |  86 +++++
 src/dbutils.h       |  69 ++++
 src/icrypt.c        | 317 +++++++++++++++++++
 src/icrypt.h        | 150 +++++++++
 src/idea.c          | 501 +++++++++++++++++++++++++++++
 src/idea.h          | 126 ++++++++
 src/keygen.c        | 577 ++++++++++++++++++++++++++++++++++
 src/lexer.h         |  83 +++++
 src/lexer.l         | 173 ++++++++++
 src/md5.c           | 496 +++++++++++++++++++++++++++++
 src/md5.h           | 146 +++++++++
 src/mdwopt.c        | 766 ++++++++++++++++++++++++++++++++++++++++++++
 src/mdwopt.h        | 421 +++++++++++++++++++++++++
 src/name.c          | 248 +++++++++++++++
 src/name.h          | 123 ++++++++
 src/parser.h        |  79 +++++
 src/parser.y        | 507 ++++++++++++++++++++++++++++++
 src/rule.c          | 158 ++++++++++
 src/rule.h          | 115 +++++++
 src/set.c           | 161 ++++++++++
 src/set.h           | 114 +++++++
 src/sym.c           | 681 ++++++++++++++++++++++++++++++++++++++++
 src/sym.h           | 190 +++++++++++
 src/tx.c            | 178 +++++++++++
 src/tx.h            |  84 +++++
 src/userdb.c        | 784 ++++++++++++++++++++++++++++++++++++++++++++++
 src/userdb.h        | 235 ++++++++++++++
 src/utils.c         | 199 ++++++++++++
 src/utils.h         | 176 +++++++++++
 44 files changed, 12869 insertions(+)
 create mode 100644 Makefile.in
 create mode 100644 config.h.in
 create mode 100644 configure.in
 create mode 100644 manual/become.tex
 create mode 100644 src/become.c
 create mode 100644 src/blowfish-sbox.h
 create mode 100644 src/blowfish.c
 create mode 100644 src/blowfish.h
 create mode 100644 src/check.c
 create mode 100644 src/check.h
 create mode 100644 src/class.c
 create mode 100644 src/class.h
 create mode 100644 src/crypt.c
 create mode 100644 src/crypt.h
 create mode 100644 src/daemon.c
 create mode 100644 src/daemon.h
 create mode 100644 src/dbutils.h
 create mode 100644 src/icrypt.c
 create mode 100644 src/icrypt.h
 create mode 100644 src/idea.c
 create mode 100644 src/idea.h
 create mode 100644 src/keygen.c
 create mode 100644 src/lexer.h
 create mode 100644 src/lexer.l
 create mode 100644 src/md5.c
 create mode 100644 src/md5.h
 create mode 100644 src/mdwopt.c
 create mode 100644 src/mdwopt.h
 create mode 100644 src/name.c
 create mode 100644 src/name.h
 create mode 100644 src/parser.h
 create mode 100644 src/parser.y
 create mode 100644 src/rule.c
 create mode 100644 src/rule.h
 create mode 100644 src/set.c
 create mode 100644 src/set.h
 create mode 100644 src/sym.c
 create mode 100644 src/sym.h
 create mode 100644 src/tx.c
 create mode 100644 src/tx.h
 create mode 100644 src/userdb.c
 create mode 100644 src/userdb.h
 create mode 100644 src/utils.c
 create mode 100644 src/utils.h

diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..36534f2
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,263 @@
+# -*-makefile-*-
+#
+# $Id: Makefile.in,v 1.1 1997/07/21 13:47:55 mdw Exp $
+#
+# Makefile for `become'
+#
+# (c) 1997 EBI
+#
+
+#----- Licencing notice -----------------------------------------------------
+#
+# This file is part of `become'
+#
+# `Become' is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# `Become' is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with `become'; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+#----- Revision history -----------------------------------------------------
+#
+# $Log: Makefile.in,v $
+# Revision 1.1  1997/07/21 13:47:55  mdw
+# Initial revision
+#
+
+#----- Configuration section ------------------------------------------------
+
+# --- Miscellaneous stuff ---
+
+SHELL=		/bin/sh
+VERSION=	@VERSION@
+
+# --- Finding source code ---
+
+srcdir=		@srcdir@
+VPATH=		@srcdir@
+
+# --- Where to put things ---
+
+prefix=		@prefix@
+exec_prefix=	@exec_prefix@
+bindir=		@bindir@
+libdir=		@libdir@
+etcdir=		@sysconfdir@
+
+# --- Compiling ---
+
+CC=		@CC@
+CFLAGS=		@CFLAGS@
+CPPFLAGS=	@CPPFLAGS@ -I$(srcdir) -I.
+TEST_DEFINES=	-DTEST_RIG
+
+YACC=		@YACC@
+YFLAGS=		-d -v
+
+LEX=		@LEX@
+LEXFLAGS=
+
+DEPEND=		gcc -MM -MG -x c
+
+# --- Linking ---
+
+LD=		@CC@
+LIBS=		@LIBS@ @LEXLIB@
+LDFLAGS=	@LDFLAGS@
+AR=		@AR@
+RANLIB=		@RANLIB@
+
+# --- Installation ---
+
+INSTALL=	@INSTALL@
+INSTALL_PROG=	@INSTALL_PROGRAM@
+INSTALL_DATA=	@INSTALL_DATA@
+
+# --- Other useful things ---
+
+SED=		sed
+MV=		mv -f
+RM=		rm -f
+ETAGS=		etags
+
+#----- Interesting files ----------------------------------------------------
+
+# --- Library of common things ---
+
+BCMLIB=		libbecome.a
+BCMLIBSRCF=	idea.c md5.c icrypt.c crypt.c tx.c \
+		class.c name.c rule.c set.c sym.c userdb.c \
+		check.c daemon.c \
+		parser.y lexer.l \
+		mdwopt.c utils.c
+BCMLIBSRC=	$(addprefix $(srcdir)/, $(BCMLIBSRCF))
+BCMLIBOBJ=	$(addsuffix .o, $(basename $(BCMLIBSRCF)))
+
+# --- The `become' program ---
+
+BECOME=		become
+BECOMESRCF=	become.c
+BECOMESRC=	$(addprefix $(srcdir)/, $(BECOMESRCF))
+BECOMEOBJ=	$(addsuffix .o, $(basename $(BECOMESRCF)))
+
+# --- The `keygen' program ---
+
+KEYGEN=		keygen
+KEYGENSRCF=	keygen.c
+KEYGENSRC=	$(addprefix $(srcdir)/, $(KEYGENSRCF))
+KEYGENOBJ=	$(addsuffix .o, $(basename $(KEYGENSRCF)))
+
+# --- Various tests for some important modules ---
+
+TESTS=		crypt-test icrypt-test idea-test md5-test \
+		name-test sym-test userdb-test \
+		become-test
+TESTOBJ=	$(addsuffix .o, $(TESTS))
+
+# --- Everything ---
+
+ALLSRC=		$(BCMLIBSRC) $(BECOMESRC) $(KEYGENSRC)
+ALLOBJ=		$(BCMLIBOBJ) $(BCMLIB) $(BECOMEOBJ) $(KEYGENOBJ) $(TESTOBJ)
+
+# --- Generated source files ---
+
+GENSRCF=	parser.c parser.tab.h lexer.c
+GENSRC=		$(addprefix $(srcdir)/, $(GENSRCF))
+
+# --- Installed files ---
+
+INSTALLABLES=	$(bindir)/$(BECOME) $(bindir)/$(KEYGEN)
+
+#----- Parser tokens header file --------------------------------------------
+
+%.tab.h:	%.c
+		$(MV) y.tab.h $@
+
+#----- Main target ----------------------------------------------------------
+
+all:		$(BECOME) $(KEYGEN)
+
+#----- Compiling the code ---------------------------------------------------
+
+$(BCMLIB):	$(BCMLIBOBJ)
+		$(AR) rc $(BCMLIB) $?
+		$(RANLIB) $(BCMLIB)
+
+$(BECOME):	$(BECOMEOBJ) $(BCMLIB)
+		$(LD) $(LDFLAGS) $^ $(LIBS) -o $(BECOME)
+
+$(KEYGEN):	$(KEYGENOBJ) $(BCMLIB)
+		$(LD) $(LDFLAGS) $^ $(LIBS) -o $(KEYGEN)
+
+#----- Installation ---------------------------------------------------------
+
+# --- Installing ---
+
+install:	$(INSTALLABLES)
+
+$(bindir):;	$(srcdir)/mkinstalldirs $(bindir)
+
+$(bindir)/%:	% $(bindir)
+		$(INSTALL_PROG) $< $(bindir)
+
+# --- Uninstalling ---
+
+uninstall:;	$(RM) $(INSTALLABLES)
+
+#----- Environmental luxuries -----------------------------------------------
+
+tags:		$(srcdir)/TAGS
+$(srcdir)/TAGS:	$(ALLSRC)
+		$(ETAGS) $(ALLSRC) -o $(srcdir)/TAGS
+
+depend:		$(srcdir)/depend
+$(srcdir)/depend:	$(ALLSRC)
+		$(SED) \
+		  -e '/^# \[Generated dependencies]/ q' \
+		  $(srcdir)/Makefile.in >$(srcdir)/new-Makefile.in
+		$(DEPEND) $(ALLSRC) | \
+		  sed -e 's/'$(srcdir)'\///g' \
+		  >>$(srcdir)/new-Makefile.in
+		$(MV) $(srcdir)/new-Makefile.in $(srcdir)/Makefile.in
+		touch $(srcdir)/depend
+
+#----- Distribution building ------------------------------------------------
+
+dist:		$(GENSRC)
+		$(srcdir)/mkdist -p $(BECOME) -v $(VERSION) \
+			-m $(srcdir)/MANIFEST -i $(srcdir)
+
+#----- Cleanliness ----------------------------------------------------------
+
+mostlyclean:;	$(RM) \
+		  $(BECOME) $(KEYGEN) $(TESTS) $(ALLOBJ) \
+		  y.output \
+		  $(srcdir)/core $(srcdir)/\#* $(srcdir)/*~ core \#* ~*
+
+clean:		mostlyclean
+
+distclean:	clean
+		$(RM) Makefile \
+		  config.status config.cache config.h config.log
+
+maintainer-clean:	distclean
+		$(RM) $(srcdir)/TAGS $(srcdir)/depend $(GENSRC) \
+
+#----- Test rigs for various pieces of code ---------------------------------
+
+test:		$(TESTS)
+
+%-test.o:	%.c config.h
+		$(CC) $(CFLAGS) $(CPPFLAGS) $(TEST_DEFINES) -c $< -o $@
+
+%-test:		%-test.o $(BCMLIB)
+		$(LD) $(LDFLAGS) $^ $(LIBS) -o $@
+
+#----- Administrivia --------------------------------------------------------
+
+.PHONY:		all tags install instdirs uninstall tests depend \
+		mostlyclean clean distclean maintainer-clean
+
+#----- Automatic dependencies -----------------------------------------------
+#
+# [Generated dependencies]
+idea.o: idea.c config.h idea.h config.h utils.h
+md5.o: md5.c config.h md5.h config.h utils.h
+icrypt.o: icrypt.c config.h icrypt.h config.h idea.h config.h
+crypt.o: crypt.c config.h crypt.h become.h config.h config.h \
+ icrypt.h config.h idea.h config.h md5.h config.h tx.h \
+ utils.h
+tx.o: tx.c config.h tx.h
+class.o: class.c class.h sym.h set.h utils.h
+name.o: name.c class.h sym.h name.h userdb.h utils.h
+rule.o: rule.c become.h config.h class.h sym.h rule.h \
+ utils.h
+set.o: set.c set.h sym.h utils.h
+sym.o: sym.c sym.h utils.h
+userdb.o: userdb.c config.h sym.h userdb.h utils.h
+check.o: check.c become.h config.h config.h crypt.h config.h \
+ idea.h config.h lexer.h name.h class.h sym.h rule.h \
+ parser.h parser.tab.h tx.h utils.h
+daemon.o: daemon.c become.h config.h config.h crypt.h \
+ config.h daemon.h idea.h config.h lexer.h name.h \
+ class.h sym.h parser.h parser.tab.h rule.h tx.h \
+ userdb.h utils.h
+parser.y.o: parser.y class.h sym.h daemon.h lexer.h \
+ name.h rule.h become.h config.h set.h userdb.h \
+ utils.h
+lexer.l.o: lexer.l become.h config.h lexer.h parser.h \
+ class.h sym.h name.h parser.tab.h utils.h
+mdwopt.o: mdwopt.c mdwopt.h
+utils.o: utils.c config.h utils.h
+become.o: become.c become.h config.h config.h check.h \
+ daemon.h lexer.h mdwopt.h name.h class.h sym.h \
+ parser.h parser.tab.h rule.h utils.h
+keygen.o: keygen.c config.h tx.h mdwopt.h utils.h
diff --git a/config.h.in b/config.h.in
new file mode 100644
index 0000000..e5f2d23
--- /dev/null
+++ b/config.h.in
@@ -0,0 +1,132 @@
+/* -*-c-*-
+ *
+ * $Id: config.h.in,v 1.1 1997/07/21 13:47:52 mdw Exp $
+ *
+ * Default settings for `become' config.h
+ *
+ * (c) 1997 Mark Wooding
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: config.h.in,v $
+ * Revision 1.1  1997/07/21 13:47:52  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+/*----- Configuration time ------------------------------------------------*/
+
+/* --- My version number --- */
+#define VERSION ???
+
+/* --- Where to find things --- */
+
+/* The `etcdir' contains configuration and state information */
+#define ETCDIR "/usr/local/etc"
+
+/* Other filenames based on the above */
+#define file_KEY (ETCDIR "/become.key")
+#define file_PID (ETCDIR "/become.pid")
+#define file_RANDSEED (ETCDIR "/become.random")
+#define file_RULES (ETCDIR "/become.conf")
+#define file_SERVER (ETCDIR "/become.server")
+
+/* --- Endianness (for transfer and cryptography) --- */
+
+/* If defined, we're big-endian */
+#undef WORDS_BIGENDIAN
+
+/* --- Swap endianness if necessary (don't alter this lot) --- */
+
+#define END_SWAP(n)                       \
+  do {                                    \
+    register unsigned long x;             \
+    x = n;                                \
+    x = ((x<<24) & 0xFF000000ul) |        \
+        ((x<< 8) & 0x00FF0000ul) |        \
+        ((x>> 8) & 0x0000FF00ul) |        \
+        ((x>>24) & 0x000000FFul);         \
+    n = x;                                \
+  } while (0);
+
+#ifdef WORDS_BIGENDIAN
+#  define TO_BIG_END(x) ((void)0)
+#  define FROM_BIG_END(x) ((void)0)
+#  define TO_LITTLE_END(x) END_SWAP(x)
+#  define FROM_LITTLE_END(x) END_SWAP(x)
+#else
+#  define TO_BIG_END(x) END_SWAP(x)
+#  define FROM_BIG_END(x) END_SWAP(x)
+#  define TO_LITTLE_END(x) ((void)0)
+#  define FROM_LITTLE_END(x) ((void)0)
+#endif
+
+/* --- Find a suitable 32-bit type --- */
+
+/* Define to be the size of an int */
+#define SIZEOF_INT 4
+
+#if SIZEOF_INT < 4
+  typedef unsigned long uint_32;
+#else
+  typedef unsigned int uint_32;
+#endif
+
+/* --- Path separator character --- */
+
+/* This is replaced by `/' by `configure' -- leave alone for DOSness */
+#define PATHSEP '\\'
+
+/* --- Whether to do debugging --- */
+
+#ifndef TEST_RIG
+#undef NDEBUG
+#endif
+
+#ifdef NDEBUG
+#  define D(x) /* empty */
+#else
+#  define D(x) x
+#endif
+
+/* --- YP support --- */
+
+#undef HAVE_YP
+
+/* --- Useful types --- */
+
+/* Type representing a user id */
+#undef uid_t
+
+/* Type representing a group id */
+#undef gid_t
+
+/* Type representing a process id */
+#undef pid_t
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#endif
diff --git a/configure.in b/configure.in
new file mode 100644
index 0000000..7dc5995
--- /dev/null
+++ b/configure.in
@@ -0,0 +1,103 @@
+dnl -*-fundamental-*-
+dnl
+dnl $Id: configure.in,v 1.1 1997/07/21 13:47:51 mdw Exp $
+dnl
+dnl Source for auto configuration for `become'
+dnl
+dnl (c) 1997 Mark Wooding
+dnl
+
+dnl----- Licencing notice ---------------------------------------------------
+dnl 
+dnl This file is part of `become'
+dnl 
+dnl `Become' is free software; you can redistribute it and/or modify
+dnl it under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation; either version 2 of the License, or
+dnl (at your option) any later version.
+dnl 
+dnl `Become' is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+dnl GNU General Public License for more details.
+dnl 
+dnl You should have received a copy of the GNU General Public License
+dnl along with `become'; if not, write to the Free Software
+dnl Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+dnl----- Revision history ---------------------------------------------------
+dnl
+dnl $Log: configure.in,v $
+dnl Revision 1.1  1997/07/21 13:47:51  mdw
+dnl Initial revision
+dnl
+
+AC_INIT(icrypt.c)
+AC_CONFIG_HEADER(config.h)
+VERSION=1.1 AC_SUBST(VERSION)
+AC_DEFINE(VERSION, "1.1 (22 February 1997)")
+
+dnl --- Check for compilers and things ---
+
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LEX
+AC_CHECK_PROG(AR, ar, ar)
+AC_PROG_RANLIB
+AC_PROG_YACC
+if test "$ac_cv_prog_gcc" = "yes"; then
+  CFLAGS="$CFLAGS -pedantic -Wall"
+fi
+
+dnl --- Libraries ---
+
+AC_CHECK_LIB(socket, socket)
+MDW_LIB_RESOLVER
+
+dnl --- Types ---
+
+AC_TYPE_PID_T
+AC_TYPE_UID_T
+
+dnl --- Check on endianness ---
+
+AC_C_BIGENDIAN(yes)
+
+dnl --- Check on type sizes ---
+
+AC_CHECK_SIZEOF(int)
+
+dnl --- Set the path separator ---
+
+AC_DEFINE(PATHSEP, '/')
+
+dnl --- Debugging stuff ---
+
+AC_ARG_ENABLE(debugging,
+  [--enable-debugging	  spews vast swathes of useless information],
+  [if test "$enableval" = "no"; then
+    AC_DEFINE(NDEBUG, 1)
+  fi],
+  AC_DEFINE(NDEBUG, 1))
+
+dnl --- Yell^H^H^H^HNetwork Information System ---
+
+AC_ARG_ENABLE(yp,
+  [--enable-yp		  read user names using ypcat],
+  [if test "$enableval" != "no"; then
+    AC_DEFINE(HAVE_YP, 1)
+  fi],
+  MDW_CHECK_YP)
+
+dnl --- Define where things get put ---
+
+mdw_save_prefix="$prefix"
+test "x$prefix" = "xNONE" && prefix="$ac_default_prefix"
+AC_DEFINE_UNQUOTED(ETCDIR, "`eval echo ${sysconfdir}`")
+prefix="$mdw_save_prefix"
+
+dnl --- Done ---
+
+AC_OUTPUT(Makefile)
+
+dnl----- That's all, folks --------------------------------------------------
diff --git a/manual/become.tex b/manual/become.tex
new file mode 100644
index 0000000..b3cc061
--- /dev/null
+++ b/manual/become.tex
@@ -0,0 +1,888 @@
+%%% -*-LaTeX-*-
+%%%
+%%% $Id: become.tex,v 1.1 1997/07/21 13:47:54 mdw Exp $
+%%%
+%%% Documentation for `become'
+%%%
+%%% (c) 1997 EBI
+%%%
+
+%%%----- Licencing notice ---------------------------------------------------
+%%% 
+%%% This file is part of `become'
+%%% 
+%%% `Become' is free software; you can redistribute it and/or modify
+%%% it under the terms of the GNU General Public License as published by
+%%% the Free Software Foundation; either version 2 of the License, or
+%%% (at your option) any later version.
+%%% 
+%%% `Become' is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+%%% GNU General Public License for more details.
+%%% 
+%%% You should have received a copy of the GNU General Public License
+%%% along with `become'; if not, write to the Free Software
+%%% Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+%%%----- Revision history ---------------------------------------------------
+%%%
+%%% $Log: become.tex,v $
+%%% Revision 1.1  1997/07/21 13:47:54  mdw
+%%% Initial revision
+%%%
+
+%%%----- Document preamble --------------------------------------------------
+
+
+%% --- Document class and packages ---
+
+\documentclass[a4paper, 10pt]{article}
+\usepackage{array, tabularx}
+\usepackage[rounded]{syntax}
+
+\newif\ifxypic
+% \IfFileExists{xy.sty}{\usepackage[all]{xy}\xypictrue}{\xypicfalse}
+
+\IfFileExists{mdwfonts.sty}{\usepackage{mdwfonts}}{}
+
+
+%% --- Macros and things ---
+
+\newcommand{\become}{\textsf{become}}
+\newcommand{\path}[1]{\texttt{#1}}
+\def\<#1>{\synt{#1}}
+\newcommand{\xor}{\oplus}
+\newcommand{\ror}{\mathbin{>\mskip-6mu>\mskip-6mu>}}
+
+
+%% --- eqalign, from Plain TeX, LaTeXified ---
+
+\makeatletter
+\def\eqalign{%
+  \null\,\vcenter\bgroup\openup\jot\m@th%
+  \ialign\bgroup%
+    \strut\hfil$\displaystyle{##}$&$\displaystyle{{}##}$\hfil\crcr%
+}
+\def\endeqalign{
+    \crcr%
+  \egroup\egroup%
+  \,%
+}
+\makeatother
+
+
+%% --- Other layout preferences ---
+
+\setlength{\grammarindent}{1in}
+\renewcommand{\arraystretch}{1.2}
+\addtolength{\textwidth}{0.6in}
+\addtolength{\oddsidemargin}{-0.3in}
+\sloppy
+
+\begin{document}
+
+
+%%%----- Introductory matter ------------------------------------------------
+
+
+%% --- Title and some `brief' acknowledgements ---
+
+\title{The \become\ program}
+\author{Mark Wooding\thanks{
+    The program contains nontrivial pieces of code owned by the European
+    Bioinformatics Institute, Mark Wooding and Straylight (even though I
+    actually wrote it all).  Thanks also to the Free Software Foundation for
+    Autoconf; to Ron Rivest for the MD5 message digest algorithm; to Xuekia
+    Lai and James Massey for the IDEA cipher; and to Bruce Schneier, for
+    writing \emph{Applied Cryptography}, which explained why I'd written the
+    first version of all this code wrong.} \\
+  \texttt{mdw@ebi.ac.uk}}
+
+\maketitle
+
+
+%% --- Abstract ---
+
+\begin{abstract}
+  This document describes a system for allowing users to `become' other users
+  in a secure and controlled way under Unix.  The idea is to allow users to
+  maintain programs and other resources which require their own accounts
+  while removing the need for such accounts to have passwords (which can be
+  disclosed, forgotten or otherwise abused in ways that passwords for user
+  accounts don't tend to be).
+
+  The \become\ program will look up the user's identity, the identity of the
+  user he or she wishes to `become', the name of the program which is to be
+  executed, and the identity of the current host, consult a configuration
+  file, and decide whether the request is permitted before granting it.  The
+  novel idea is that the table doesn't need to be on the local machine --
+  \become\ will send a request to a server, asking it for permission,
+  allowing the information to be held centrally, and making maintenance more
+  convenient.  Cryptographic protocols are used to ensure the authenticity of
+  the server's responses.
+\end{abstract}
+
+
+%% --- Contents ---
+
+\tableofcontents
+
+
+%%%--------------------------------------------------------------------------
+\section{User guide}
+
+
+\subsection{Introduction}
+
+Running \become\ lets you `become' another user.  What this really means is
+that it lets you execute a process with the permissions of another user.
+Which users you're allowed to `become', and exactly what processes you're
+allowed to execute as those users is determined by the people that installed
+\become.
+
+
+\subsection{Invoking \become}
+
+Invoking the \become\ program is impressively simple.  There are essentially
+two forms of invocation:
+\begin{syntdiag}
+  `become' <user-name> \[ <program> \[ \< <argument> \> \] \]
+\end{syntdiag}
+and
+\begin{syntdiag}
+  `become' `-c' <shell-command> <user-name>
+\end{syntdiag}
+The first variant allows you to execute any \<program> as user \<user-name>,
+as limited by your site's configuration.  The second variant simply passes
+the \<shell-command> to \path{/bin/sh}, so you must have permission to
+execute the shell as \<user-name>.  The latter form doesn't actually allow
+you to do anything the former doesn't: it is in fact entirely equivalent to
+saying `\syntax{"become" <user-name> "/bin/sh -c" <shell-command>}', but
+slightly more compact.
+
+
+%%%--------------------------------------------------------------------------
+\section{Installation and configuration}
+
+
+\subsection{Unpacking and compiling}
+
+Before you begin, there are some prerequisites you should check:
+\begin{itemize}
+\item You need the Free Software Foundation's gzip program to unpack the
+  distribution archive.
+\item You need an ANSI-conformant C~compiler and library to correctly compile
+  the source code.  No effort at all has been made to support nonstandard
+  C~implementations.\footnote{
+    The ANSI~C standard was ratified in 1989.  It is now 1997.  If your
+    system vendor hasn't bothered to comply with an eight year old standard,
+    you ought to firstly complain to your vendor, and secondly install the
+    GNU~C compiler.}
+\item You need a copy of GNU make to build the software.  This can be
+  obtained from any GNU software archive.
+\item You'll probably need GNU flex and bison to rebuild the scanner and
+  parser.  However, prebuilt C~source for these components is provided, and
+  will probably work.
+\item A recent version of \LaTeX\ is required for formatting the manual.  For
+  those without \LaTeX, a formatted version of the manual is supplied, in
+  PostScript form.
+\end{itemize}
+
+The \become\ software is distributed as a gzipped tape archive: saying
+\begin{verbatim}
+    $ gunzip -c become-1.1.tar.gz | tar xvf -
+\end{verbatim}
+will decompress and unpack the source code into a directory
+\path{become-1.1}.
+
+The software must be configured prior to compilation.  The author has used
+the Free Software Foundation's Autoconf system which will (with luck)
+configure \become\ correctly for the host platform.  The simplest way to do
+this is to change into the unpacked source directory and say
+\begin{verbatim}
+    $ ./configure
+\end{verbatim}
+If you're compiling for several architectures, you can keep the object files
+for each in a separate directory.  To do this, create a directory for each
+one, e.g., by saying something like
+\begin{verbatim}
+    $ mkdir linux solaris
+\end{verbatim}
+Then make the appropriate directory current, and run the configure script
+from the parent directory:
+\begin{verbatim}
+    $ cd linux
+    $ ../configure
+\end{verbatim}
+
+Without any arguments, the configure script will attempt to deduce all it
+needs to know about your platform, and it will choose default places to
+install files.  You can change the configure script's ideas about where to
+put the files by passing it command line arguments.  By default, all of
+\become's files are placed relative to a \emph{prefix} directory (so binaries
+go in \emph{prefix}\path{/bin} and so on).  The prefix directory is usually
+\path{/usr/local}, although you can change this by using the
+\texttt{--prefix} option, e.g.,
+\begin{verbatim}
+    $ ./configure --prefix=/usr/local/become-1.1
+\end{verbatim}
+(This will keep all of \become's files in a subdirectory of
+\path{/usr/local}, which you may find makes maintenance easier.)
+
+You can also choose different locations for various types of file.  Most
+importantly, \become's configuration files are put into a `system
+configuration' directory, which by default is \emph{prefix}\path{/etc}.  You
+can change it using the \texttt{--sysconfdir} option, e.g.,
+\begin{verbatim}
+    $ ./configure --sysconfdir=/etc/become
+\end{verbatim}
+If you're planning to use \become\ in a centralised installation (see
+section~\ref{sec:become.inst-type} on page~\pageref{sec:become.inst-type})
+then the system configuration directory \emph{must not} be on a remote
+filesystem because cryptographic keys are stored in this directory and
+putting them on a remote filesystem will make them visible on the network.
+
+A complete list of options accepted by the configure script may be displayed
+by passing the \texttt{--help} option:
+\begin{verbatim}
+    $ ./configure --help
+\end{verbatim}
+
+You can now build the programs by invoking GNU make.  Whether simply typing
+\texttt{make} is sufficient to run GNU make depends on your site: ask around
+if you don't know how to invoke GNU make.
+
+Finally, you can install the various files to their correct directories by
+saying
+\begin{verbatim}
+    $ make install
+\end{verbatim}
+(again, using GNU make, so maybe it's not called `\texttt{make}' at your
+site).
+
+Congratulations: \become\ is now compiled.  The easy part is now done.
+
+
+\subsection{Different installation types}
+\label{sec:become.inst-type}
+
+There are two types of installation for \become, and which one you choose
+depends on how you want to maintain the configuration file, which contains
+the rules describing who is allowed to become whom:
+\begin{itemize}
+\item a \emph{standalone} installation, where the configuration is stored
+  locally, and
+\item a \emph{centralised} installation, where the configuration is stored on
+  a central (and trusted) server.
+\end{itemize}
+The difference is basically how you want to maintain the configuration.  In
+the standalone case, you have to ensure that the configuration file is copied
+to each participating host each time it gets changed.  In the centralised
+case, you only have one copy of the configuration file, and have a different
+problem concerning key distribution.
+
+
+\subsection{The configuration file}
+
+The configuration file for \become is called \path{become.conf}, and it's
+stored in the system configuration directory you set up when compiling the
+program.  It defines a set of records, each containing four fields:
+\begin{itemize}
+\item a \emph{from} field, identifying a class of users;
+\item a \emph{to} field, identifying a (possibly different) class of users;
+\item a \emph{commands} field, describing a class of commands; and
+\item a \emph{hosts} field, describing a class of hosts.
+\end{itemize}
+Such a record permits any user in the \emph{from} class to run a command
+contained in the \emph{commands} class as any user in the \emph{to} class, on
+any host in the \emph{hosts} class.  If any class fails to match, permission
+is denied.
+
+The configuration file can contain comments, which start with a \lit{\#}
+character and extend to the end of the line; this is the only time when
+newlines are significant in the configuration file.
+
+\subsubsection{Allow records}
+
+A record like the one described above is represented in the configuration
+file by an \emph{allow record}.  It has the following syntax:
+\begin{grammar}
+
+<allow>         ::= \[[
+  `allow'
+    \[ `[' <host-class> `]' \]
+    <user-class> `->' \[ <user-class> \]
+    \[ `:' <command-class> \]
+  `;'
+\]]
+
+\end{grammar}
+The items \<host-class>, \<user-class> and \<command-class> are all
+\emph{class expressions}.  If you omit one of the classes, then it will match
+all requests.  So saying
+\begin{verbatim}
+    allow EVILHACKER -> ;
+\end{verbatim}
+allows anyone in the `EVILHACKER' class to become anyone they like,
+everywhere, and do anything.
+
+\subsubsection{Class expressions}
+
+Class expressions allow you to define classes of users, hosts and commands
+conveniently.
+
+All class expressions have the same high-level syntax, and it's fairly easy
+to understand.  It looks a little bit like set notation: you can obtain the
+union of two classes using the \lit{|} character (or), take intersections
+using \lit{\&} (and), and subtract classes using \lit{-}.  Finally, you can
+list several classes by separating them with commas \lit{,}.  The order of
+precedence, from lowest to highest, is \lit{,}, \lit{-}, \lit{|} and
+\lit{\&}.\footnote{
+  Actually, the \lit{,} and \lit{|} operators do exactly the same thing.  The
+  only difference is their relative precedence.  It probably helps if you
+  think of them as being conceptually different, though.}
+You can override the precedence rules by using parentheses.
+
+The whole syntax looks like this:
+\begin{grammar}
+
+<class-expr>    ::= \[[
+  \[ \< <class-minus-expr> \\ `,' \> \] <class-minus-expr>
+ \]]
+
+<class-minus-expr> ::= \[[
+  \[ \< <class-or-expr> \\ `-' \> \] <class-or-expr>
+\]]
+
+<class-or-expr> ::= \[[
+  \[ \< <class-and-expr> \\ `|' \> \] <class-and-expr>
+\]]
+
+<class-and-expr> ::= \[[
+  \[ \< <class-primary> \\ `&' \> \] <class-primary>
+\]]
+
+<class-primary> ::= \[[
+  \( `(' <class-expr> `)' \\ <class-name> \\ <explicit-item> \)
+\]]
+
+\end{grammar}
+
+\subsubsection{Naming classes}
+
+To save repetition, you can give names to classes, using one of the three
+assignment statements:
+\begin{grammar}
+
+<user-assign>   ::= \[[ `user' <name> `=' <user-class> `;' \]]
+
+<host-assign>   ::= \[[ `host' <name> `=' <host-class> `;' \]]
+
+<command-assign> ::= \[[ `command' <name> `=' <command-class> `;' \]]
+
+\end{grammar}
+
+Classes can be defined in terms of themselves: saying
+\begin{verbatim}
+    user HACKERS = HACKERS | "mdw";
+\end{verbatim}
+says to add `mdw' to the class of hackers, for example.  The configuration
+file is read strictly top-to-bottom, and an allow record already given
+doesn't change its meaning just because you later redefine of the classes it
+refers to.
+
+\subsubsection{Naming users, hosts and commands}
+
+Right: you now know how to define classes in terms of other classes, but
+you've got to start somewhere.  Each type of class has its own way of
+identifying members.
+\begin{itemize}
+\item A user may be identified either by writing the user's name in double
+  quotes (e.g, \texttt{"mdw"}) or by giving the integer user id (e.g.,
+  \texttt{272}).
+\item A host may be specified by giving, in quotes, either the host's
+  \emph{fully qualified} name (e.g., \texttt{"excessus.hacker.org"}), or its
+  IP~address, (e.g., \texttt{"158.152.170.219"}).  Note that the IP~address
+  must be quoted too: this is slightly unusual.  Either form may contain
+  wildcards: \lit{?} matches any character, and \lit{*} matches zero or more
+  characters.  For example, I can name all hosts at hacker.org by saying
+  \texttt{"*.hacker.org"}.  \emergencystretch=10pt
+\item A command may be specified by giving its \emph{full pathname} in quotes
+  (e.g., \texttt{"/sbin/shutdown"}).  Again, wildcards can be used to specify
+  lots of commands at the same time.
+\end{itemize}
+
+Also, note that lots of user classes come \emph{predefined}.  A class is
+defined for every user, named with the user's name, and containing that user.
+Also, for every group, there is a class, named with the group's name, which
+contains all users who are members of that group.  You can redefine these
+classes if you like: they're meant to be a convenience, but if you don't like
+them, you don't have to use them.
+
+A warning is in order: some systems have group names which are also user
+names: in this case, a class is defined containing the named user \emph{and}
+all the members of the group.  That's probably not desirable.
+
+
+\subsection{Configuring standalone installations}
+
+That's it, really.  Make sure that \become\ can find the configuration file
+on each host.  If \become\ can't find a server to talk to (which it can't
+because you haven't configured one) it will parse the local configuration
+file and decide for itself whether to grant the user's request.
+
+If you're only interested in setting up a standalone installation, then
+you're finished, and can get on with doing something interesting.
+Alternatively, read on, and see all the work you don't have to do.
+
+
+\subsection{Keys and random numbers}
+
+Because \become\ uses cryptographic methods for communicating with its
+server, you must set up some encryption keys for it to use.  You need to set
+up two files, both in \become's system configuration directory:
+\begin{itemize}
+\item The file \path{become.key} contains \become's `master' key.  Someone
+  who knows the master key can fake responses from the server, and grant
+  themselves any privileges they like.
+\item The file \path{become.random} contains a `random number seed' which is
+  used (together with the master key) to generate random numbers (e.g.,
+  session keys).  Someone who knows the random numbers can fake responses
+  from the server, and grant themselves any privileges they like.  It's
+  difficult to actually predict random numbers given the random number seed
+  file, although it's not a good idea to leave the seed lying around.
+\end{itemize}
+Both of these files should be stored on a local filesystem, and they should
+be readable only by the super-user.
+
+Each of the two files, the key and the random number file, contain a 128-bit
+number, written in hexadecimal.  To make the thing more readable, you may
+insert dashes in the number between each chunk of eight digits.\footnote{
+  Actually, you can insert dashes wherever you like in the number, but
+  this is only because the parser is rather primitive.  The author recommends
+  that you stick with every eight digits.}
+Here's an example of a possible key file:
+\begin{verbatim}
+    4fda99b0-fcbd8bcb-d1bcf951-e1ed04c9
+\end{verbatim}
+
+You should generate 128 genuinely random bits for each file.  It is
+\emph{not} good enough to use a computer random number generator.  A program
+will be supplied later which will examine key timings as a source for random
+numbers.  Also, don't use the number printed above.  That would be really
+silly.
+
+To help you do this, a program `keygen' is provided.  It uses timings of
+keypresses to generate random numbers.  To use the program to generate (for
+example) the random number seed file, type
+\begin{verbatim}
+    $ keygen -o become.random
+\end{verbatim}
+Keygen will report the number of bits which still need to be generated.  Keep
+typing until it says `done'.  The program automatically ensures that its
+output file, if it doesn't already exist, is readable only by its owner.  The
+command line arguments to keygen are simple:
+\begin{syntdiag}
+`keygen' \< \[ `--bits' <number> \\ `--output' <file-name> \] \>
+\end{syntdiag}
+If you don't specify a \<number> of bits, a default of 128 random bits are
+generated, which is correct for IDEA keys.  If you omit the \<file-name>, the
+random key is written to standard output.
+
+
+\subsection{Setting up the server and clients}
+
+You don't need a separate program to run as a \become\ server: the normal
+\become\ is quite capable of behaving as a server all by itself.  However,
+before you start the server up, you need to decide on a port to which it will
+listen.  The author uses port 9876 for testing purposes, and there's not much
+reason why you couldn't do the same.
+
+There are three ways you can inform \become\ of your choice of port:
+\begin{itemize}
+\item You can pass the port number on the command line, using the
+  \texttt{--port} option.
+\item You can add a line saying `\syntax{"port" <number> ";"}' to the
+  configuration file.
+\item You can add an entry to your \path{/etc/services} file (or NIS map),
+  binding your chosen port number to the name `become'.\footnote{
+    Actually, \become\ searches for the port using the filename with which
+    it was invoked, so if you call the \become\ binary \path{splat}, then
+    \become will look for a service labelled `splat'.}
+\end{itemize}
+If \become\ still has no idea which port to use, it refuses to start up as a
+server and reports an error message to you.
+
+You can also choose a different key file to use, by writing a line of the
+form `\syntax{"key" <filename> ";"}' in \path{become.conf}.  The client won't
+listen to this -- only the server does that.
+
+To make \become\ run as a server, say
+\begin{verbatim}
+    $ become --daemon
+\end{verbatim}
+(or to use an explicit port number, say something like
+\begin{verbatim}
+    $ become --daemon --port=9876
+\end{verbatim}
+replacing \texttt{9876} in the example with your chosen port).  You can also
+run the daemon with a different configuration file, by using the
+\texttt{--config-file} option, e.g.,
+\begin{verbatim}
+    $ become --daemon --config-file=/etc/become/server.conf
+\end{verbatim}
+
+Now to configure the clients.  All they need is a file saying where to find
+the server.  All this contains is a single line of the form
+\begin{syntdiag}
+  <host-name> \[ `:' <port-number> \]
+\end{syntdiag}
+If you omit the port number (or it's otherwise incomprehensible) then
+\become\ looks at the services table (again using the name under which it was
+invoked) to find a port.  If it still can't find a port to use, then \become\
+complains.
+
+The server wakes up every five minutes to rescan its configuration and
+encryption key.  Thus, it should react fairly quickly to changes to the user
+database or to its configuration.  However, you can always force the server
+to refetch its configuration files by sending it a SIGHUP signal.  To help
+you do this, the server stores its process id in a file \path{become.pid}
+within its system configuration directory.
+
+
+\subsection{Maintaining \become}
+
+There's not much to it really, apart from updating the configuration file
+when your requirements change.
+
+The only other thing you really ought to do is to periodically change the
+master key.  This should be done about once a week, I'd suspect.  The
+difficult part is distributing the keys over the network: you don't really
+want to trust the old keys.  I'd recommend that you investigate `ssh' for key
+distribution.
+
+
+\subsection{Summary of \become\ configuration}
+
+\subsubsection{Table of the configuration files}
+
+\begin{tabularx}{\textwidth}{@{} >{\ttfamily}l X @{}}
+  \multicolumn{1}{@{}l}{\textbf{File name}} &
+  \multicolumn{1}{l@{}}{\textbf{Contents}} \\
+  become.conf	& Main configuration file.  See the syntax below for the
+		  complete reference \\
+  become.key	& Master encryption key.  Should contain a 128-bit random
+		  number. \\
+  become.pid	& Server's process id (so that you can kill it).  The server
+		  creates this file all by itself. \\
+  become.random & Random number seed for generating session keys.  Should
+		  also contain a 128-bit random number.  Don't be surprised
+		  if the number keeps changing -- it's meant to. \\
+  become.server	& Tells the \become\ client where to find the server. \\
+\end{tabularx}
+
+\subsubsection{Definitive syntax for \path{become.conf}}
+
+The syntax for \path{become.conf} files is shown below.  This mainly reprises
+the syntax shown earlier, but in a different order, and without all the
+explanatory text getting in the way.
+
+Firstly, the lexical grammar is as follows:
+
+\begin{grammar}
+
+<comment>	::= \[[
+  `#' \< \tok{any character other than <new-line>} \> <new-line>
+\]]
+
+<name>		::= \[[
+  \tok{letter or `_'} \< \( \tok{letter or `_'} \\ \tok{digit} \) \>
+\]]
+
+<integer>	::= \[[ \< \tok{digit} \> \]]
+
+<string>	::= \[[
+  `"' \< \( \tok{any character other than `"', <new-line> or `\\'} \\
+            `\\' \tok{any character other than <new-line>} \) \> `"'
+\]]
+
+\end{grammar}
+
+All \<comment>s and whitespace are ignored entirely.  What's left is parsed
+as follows:
+
+\begin{grammar}
+
+<become-conf>	::= \[[ \< <statement> \> \]]
+
+<statement>	::= \[[
+  \( \( `user' \\ `command' \\ `host' \) <name> `=' <class-expr> `;' \\
+     `allow' <allow-spec> `;' \\
+     `port' <integer> `;' \\
+     `key' <string> `;' \)
+\]]
+
+<allow-spec>	::= \[[
+   \[ `[' <host-class> `]' \]
+  <user-class> `->' \[ <user-class> \]
+  \[ `:' <command-class> \]
+\]]
+
+<class-expr>    ::= \[[
+  \[ \< <class-minus-expr> \\ `,' \> \] <class-minus-expr>
+ \]]
+
+<class-minus-expr> ::= \[[
+  \[ \< <class-or-expr> \\ `-' \> \] <class-or-expr>
+\]]
+
+<class-or-expr> ::= \[[
+  \[ \< <class-and-expr> \\ `|' \> \] <class-and-expr>
+\]]
+
+<class-and-expr> ::= \[[
+  \[ \< <class-primary> \\ `&' \> \] <class-primary>
+\]]
+
+<class-primary> ::= \[[
+  \( `(' <class-expr> `)' \\ <name> \\ <integer> \\ <string> \)
+\]]
+
+\end{grammar}
+
+
+%%%--------------------------------------------------------------------------
+\section{Cryptographic trivia}
+
+
+\subsection{Design requirements}
+
+The way the system works is that the \become\ client program builds a
+\emph{request block} containing all the information needed to decide whether
+the user's request is valid.  It then sends this to a server, asking it
+whether this request should be granted.  If the server replies `yes', then
+\become\ changes its uid, and runs the user's program.
+
+The really important point is that the client must be able to trust the
+responses it gets from the server: the final decision over whether to grant
+the request lies only with the client.  The server doesn't really need to
+worry too much about whether it trusts a request -- it's not going to do
+anything with them anyway except send a reply back.
+
+
+\subsection{Notation}
+
+Some slightly weird mathematical notation is used in the following sections.
+\begin{description}
+\item [$a \xor b$] denotes the exclusive-or (XOR) operation (bitwise addition
+  mod 2).
+\item [$(a, b, c)$] denotes concatenation of the quantities $a$, $b$ and $c$.
+\item [$a[x : y]$] denotes bits $x$ up to $y$ of $a$, including bit~$x$ but
+  \emph{not} bit~$y$.  For example, $a[32:64]$ is a 32-bit quantity.  The
+  bits are labelled starting from the left at zero, and increasing to the
+  right.
+\item [$E_{k, IV}(a)$] denotes encryption of $a$, using the key $k$ and
+  initialisation vector $IV$.
+\item [$D_{k, IV}(a)$] denotes decryption of $a$, using the key $k$ and
+  initialisation vector $IV$.
+\end{description}
+Encryption is performed using the IDEA algorithm, in 64-bit ciphertext
+feedback mode.
+
+
+\subsection{The actual protocol}
+
+The protocol \become\ uses to communicate with the server is as follows:
+\begin{enumerate}
+
+\item The client and server share a secret key~$k$.
+
+\item The client calculates the following:
+  \begin{description}
+  \item [$F$] is the `from' user id;
+  \item [$T$] is the `to' user id;
+  \item [$C$] is the command the user wishes to execute;
+  \item [$t$] is the current time, as returned from \texttt{time}(2); and
+  \item [$p$] is the client's process id.
+  \end{description}
+  The fields $t$ and~$p$ are to ensure that the client doesn't get confused
+  by replies to the wrong requests.
+
+\item The client generates a session key~$s$ and initialisation vector~$IV$.
+  It then calculates a checksum
+  \[ X = MD5(F, T, C, t, p)[0:32] \]
+  and sends the server a message
+  \[ \bigl(IV, E_{k, IV}(s), E_{s, IV'}(F, T, C, t, p, X)\bigr) \]
+  where $IV'$ is $E_{k, IV}(s)[64:128]$ (i.e., the last block of ciphertext
+  after encrypting the session key, so the whole message is encrypted as one
+  ciphertext feedback job, with a key change part-way).
+
+\item The server decrypts the message, and checks it to make sure it's valid:
+  \begin{itemize}
+  \item It checks that $X$ is the correct checksum.
+  \item It ensures that the difference between $t$ and the true time is
+    acceptable.  (The current implementation allows $t$ to be 15 seconds
+    out.)
+  \end{itemize}
+  If either of these checks fails, the request is rejected without
+  acknowledgement.
+
+\item The server decides whether to grant the request.  If it gives its
+  permission, it sets $a = 1$; otherwise it sets $a = 0$.  It calculates a
+  checksum
+  \[ Y = MD5(t, p, a)[0:32] \]
+  and sends the client a message
+  \[ \bigl(IV'', E_{s, IV''}(t, p, a, Y)\bigr) \]
+  where $IV''$ is the last 64~bits of ciphertext received from the client,
+  continuing the ciphertext feedback again.  (Later versions of \become\
+  might use a different method for deciding on the initialisation vector.)
+
+\item The client decrypts the reply, and verifies it:
+  \begin{itemize}
+  \item It checks that $Y$ is a valid checksum.
+  \item It checks that the $t$ and $p$ values received match the ones in the
+    original request.
+  \end{itemize}
+  If either fail to match, the reply is discarded, and the client continues
+  to wait for a valid reply (possibly timing out).
+
+\item The client accepts the reply.  If $a = 1$ it changes uid and executes
+  the named process~$C$.
+
+\end{enumerate}
+
+The encryption makes it hard for an attacker to alter the data being
+transmitted in any meaningful way; the 32-bit checksum means that an altered
+message has a $2^{-32}$ probability of not being noticed.
+
+The use of ciphertext feedback mode attempts to prevent chosen-plaintext
+attacks, even though the user can make the client send arbitrary messages.
+
+
+\subsection{The random number generator}
+
+The random number generator is used to generate initialisation vectors and
+session keys for the cryptographic protocol above.  The random number
+generator might well change in later versions of \become.
+
+The current implementation maintains an $n$-bit random number seed~$R_i$.  It
+generates a 128-bit session key~$s$, an initialisation vector~$IV$ and a new
+seed~$R_{i+1}$.
+\[
+   \begin{eqalign}
+   s         &= MD5(R_i, t, p, k) \cr
+   IV        &= MD5(R_i, t, p, s)[0:64] \cr
+   R_{i + 1} &= (R_i[n - 128 : n] \xor s, R_i[0 : n - 128]) \cr
+   \end{eqalign}
+\]
+\ifxypic
+  See figure~\ref{fig:become.randgen} for a diagrammatic representation of
+  the generator.  It's
+\else
+  This is
+\fi
+really just a weird sort of feedback shift register, generating 128~bits of
+data at a time.
+
+\ifxypic
+\begin{figure}
+
+  \xymatrix{
+    
+  }
+
+  \caption{The \become\ random number generator}
+  \label{fig:become.randgen}
+\end{figure}
+\fi
+
+
+The use of the secret key~$k$ helps to ensure that even if the random number
+seed is compromised, an attacker still needs to know $k$ before he can
+predict session keys.  Of course, if the attacker knows $k$, he has no need
+to predict session keys: he can just decrypt them from the messages.  The use
+of the values $t$ and $p$ attempts to add a small quantity of randomness to
+the seed in each iteration.
+
+
+\subsection{The `keygen' program}
+
+The `keygen' program attempts to take advantage of the variations in time
+between your keystrokes to generate random numbers.  It's not perfect.  It
+may help a little if you know exactly how it works.
+
+Keygen keeps track of the interval between keypresses.  It exclusive-ors
+adjacent interval times together, and strips off leading and trailing
+sequences of one- or zero-bits.  What's left is shifted into the accumulator.
+The aim of all this complexity is to measure the variation in key timings,
+and then discard any uninteresting bits from the result.
+
+This method works best on machines with very high-resolution clocks
+(preferably with microsecond granularity), although even on the author's
+Linux machine, which uses a clock with centisecond granularity, the number of
+keystrokes required is acceptable.
+
+
+\subsection{How to break \become's security}
+
+The author can't see any obvious weaknesses in the protocol used.  Here are
+some possibilities which might occur to an attacker, though:
+\begin{itemize}
+
+\item Forge a server reply packet and send it to the client.  Intercept the
+  request packet and discard it before it reaches the real server.  The
+  required contents of the reply packet can be guessed.  However, encrypting
+  it requires knowledge of the session key sent by the client.  Obtaining
+  this means you need to break the IDEA cipher, which (to the author's
+  knowledge) isn't practical.
+
+\item Send another packet to the server at the same time, altering the sender
+  address so that the server replies to the wrong host or port.  This won't
+  work, because the client will attempt to decrypt the fake reply with the
+  wrong session key and will reject the packet when it finds that the
+  checksum is incorrect.
+
+\item Find some other back door into the client host, to become root.  Read
+  the secret key file, and use that to decrypt requests and send back
+  replies.  If you can already become the super-user, why bother cracking
+  \become?
+
+\item Feed the client program bad input to overflow a fixed-size buffer.  The
+  bad input contains executable code which gives the attacker a privileged
+  shell.  The author isn't aware of any buffers which might overflow as a
+  result of user-supplied data.
+
+\end{itemize}
+
+The above assumes that \become\ has been set up correctly.  The following
+attacks rely on misconfiguration:
+\begin{itemize}
+
+\item Watch new secret keys being transmitted over the network when the
+  administrator replaces them.  Now you can decrypt request packets and send
+  back replies.  Make sure that the original server's responses are
+  corrupted so that the client rejects them.
+
+\item Watch the client or server reading the secret key from a remote
+  filesystem.
+
+\item Clobber the configuration file when the server re-reads it from a
+  remote filesystem, so that it gives your user account permission to become
+  anyone.
+
+\end{itemize}
+
+
+%%%----- Licencing conditions -----------------------------------------------
+
+\input{gpl}
+
+%%%----- That's all, folks --------------------------------------------------
+
+\end{document}
+
+%%% Local Variables: 
+%%% mode: latex
+%%% TeX-master: t
+%%% End: 
diff --git a/src/become.c b/src/become.c
new file mode 100644
index 0000000..c2d8bcf
--- /dev/null
+++ b/src/become.c
@@ -0,0 +1,480 @@
+/* -*-c-*-
+ *
+ * $Id: become.c,v 1.1 1997/07/21 13:47:54 mdw Exp $
+ *
+ * Main code for `become'
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: become.c,v $
+ * Revision 1.1  1997/07/21 13:47:54  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Unix headers --- */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+
+#include <netdb.h>
+#include <pwd.h>
+#include <syslog.h>
+#include <unistd.h>
+
+/* --- Local headers --- */
+
+#include "become.h"
+#include "config.h"
+#include "check.h"
+#include "daemon.h"
+#include "lexer.h"
+#include "mdwopt.h"
+#include "name.h"
+#include "parser.h"
+#include "rule.h"
+#include "utils.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @bc__write@ --- *
+ *
+ * Arguments:	@FILE *fp@ = pointer to a stream to write on
+ *		@const char *p@ = pointer to a string
+ *
+ * Returns:	---
+ *
+ * Use:		Writes the string to the stream, substituting the program
+ *		name (as returned by @quis@) for each occurrence of the
+ *		character `$'.
+ */
+
+static void bc__write(FILE *fp, const char *p)
+{
+  const char *n = quis();
+  size_t l;
+  size_t nl = strlen(n);
+
+  /* --- Try to be a little efficient --- *
+   *
+   * Gather up non-`$' characters using @strcpn@ and spew them out really
+   * quickly.
+   */
+
+  for (;;) {
+    l = strcspn(p, "$");
+    if (l)
+      fwrite(p, l, 1, fp);
+    p += l;
+    if (!*p)
+      break;
+    fwrite(n, nl, 1, fp);
+    p++;
+  }
+}
+
+/* --- @bc__banner@ --- *
+ *
+ * Arguments:	@FILE *fp@ = stream to write on
+ *
+ * Returns:	---
+ *
+ * Use:		Writes a banner containing copyright information.
+ */
+
+static void bc__banner(FILE *fp)
+{
+  bc__write(fp, "$ version " VERSION "\n");
+}
+
+/* --- @bc__usage@ --- *
+ *
+ * Arguments:	@FILE *fp@ = stream to write on
+ *
+ * Returns:	---
+ *
+ * Use:		Writes a terse reminder of command line syntax.
+ */
+
+static void bc__usage(FILE *fp)
+{
+  bc__write(fp,
+	    "Usage: \n"
+	    "	$ -c <shell-command> <user>\n"
+	    "	$ <user> [<command> [<arguments>]...]\n"
+	    "	$ -d [-p <port>] [-f <config-file>]\n");
+}
+
+/* --- @bc__help@ --- *
+ *
+ * Arguments:	@FILE *fp@ = stream to write on
+ *
+ * Returns:	---
+ *
+ * Use:		Displays a help message for this excellent piece of software.
+ */
+
+static void bc__help(FILE *fp)
+{
+  bc__banner(fp);
+  putc('\n', fp);
+  bc__usage(fp);
+  putc('\n', fp);
+  bc__write(fp,
+"The `$' program allows you to run a process as another user.\n"
+"If a command name is given, this is the process executed.  If the `-c'\n"
+"option is used, the process is assumed to be `/bin/sh'.  If no command is\n"
+"given, your default login shell is used.\n"
+"\n"
+"Your user id, the user id you wish to become, the name of the process\n"
+"you wish to run, and the identity of the current host are looked up to\n"
+"ensure that you have permission to do this.\n"
+"\n"
+"Note that logs are kept of all uses of this program.\n"
+"\n"
+"Options available are:\n"
+"\n"
+"-h, --help		Display this help text\n"
+"-v, --version		Display the version number of this copy of $\n"
+"-c, --command=CMD	Run the (Bourne) shell command CMD\n"
+"-d, --daemon		Start up a daemon, to accept requests from clients\n"
+"-p, --port=PORT		In daemon mode, listen on PORT\n"
+"-f, --config-file=FILE	In daemon mode, read config from FILE\n"
+"--yacc-debug		Dump lots of parser diagnostics (boring)\n");
+}
+
+/* --- @main@ --- *
+ *
+ * Arguments:	@int argc@ = number of command line arguments
+ *		@char *argv[]@ = pointer to the various arguments
+ *
+ * Returns:	Zero if successful.
+ *
+ * Use:		Allows a user to change UID.
+ */
+
+int main(int argc, char *argv[])
+{
+  char *cmd = 0;
+  static char *shell[] = { "/bin/sh", "-c", 0, 0 };
+  char **todo;
+  request rq;
+  char buff[CMDLEN_MAX];
+  char *conffile = file_RULES;
+  int port = -1;
+
+  enum {
+    f_daemon = 1,
+    f_duff = 2
+  };
+
+  unsigned flags = 0;
+
+  /* --- Set up the program name --- */
+
+  ego(argv[0]);
+
+  /* --- Parse some command line arguments --- */
+
+  for (;;) {
+    int i;
+    struct option opts[] = {
+      { "help",		0,		0,	'h' },
+      { "version",	0,		0,	'v' },
+      { "command",	gFlag_argReq,	0,	'c' },
+      { "daemon",	0,		0,	'd' },
+      { "port",		gFlag_argReq,	0,	'p' },
+      { "config-file",	gFlag_argReq,	0,	'f' },
+      { "yacc-debug",	0,		0,	'Y' },
+      { 0,		0,		0,	0 }
+    };
+
+    i = mdwopt(argc, argv, "hvc:p:df:", opts, 0, 0, 0);
+    if (i < 0)
+      break;
+
+    switch (i) {
+      case 'h':
+	bc__help(stdout);
+	exit(0);
+	break;
+      case 'v':
+	bc__banner(stdout);
+	exit(0);
+	break;
+      case 'c':
+	cmd = optarg;
+	break;
+      case 'p':
+	port = atoi(optarg);
+	break;
+      case 'd':
+	flags |= f_daemon;
+	break;
+      case 'f':
+	conffile = optarg;
+	break;
+      case 'Y':
+	if (getuid() == geteuid())
+	  yydebug = 1;
+	else
+	  moan("won't set debugging mode when running setuid");
+	break;
+      case '?':
+	flags |= f_duff;
+	break;
+    }
+  }
+  if (flags & f_duff) {
+    bc__usage(stderr);
+    exit(1);
+  }
+
+  /* --- Switch to daemon mode if requested --- */
+
+  if (flags & f_daemon) {
+    daemon_init(conffile, port);
+    exit(0);
+  }
+
+  /* --- Open a syslog --- */
+
+  openlog(quis(), 0, LOG_AUTH);
+
+  /* --- Pick out the uid --- */
+
+  {
+    const char *u;
+    struct passwd *pw;
+    if (optind >= argc) {
+      bc__usage(stderr);
+      exit(1);
+    }
+    u = argv[optind++];
+    pw = getpwnam(u);
+    if (!pw && isdigit(u[0]))
+      pw = getpwuid(atoi(u));
+    if (!pw)
+      die("unknown user `%s'", u);
+    rq.to = pw->pw_uid;
+  }
+
+  /* --- Fill in the easy bits of the request --- */
+
+  rq.from = getuid();
+
+  /* --- Find the local host address --- */
+
+  {
+    struct utsname u;
+    struct hostent *he;
+    uname(&u);
+    if ((he = gethostbyname(u.nodename)) == 0)
+      die("who am I? (can't resolve `%s')", u.nodename);
+    memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
+  }
+
+  /* --- Figure out what command to use --- */
+
+  if (cmd) {
+    shell[2] = cmd;
+    todo = shell;
+  } else if (optind < argc) {
+    todo = argv + optind;
+  } else {
+    struct passwd *pw = getpwuid(rq.from);
+    if (!pw)
+      die("who are you? (can't find uid %li in database)", (long)rq.from);
+    shell[0] = pw->pw_shell;
+    shell[1] = 0;
+    todo = shell;
+  }
+
+  /* --- If necessary, resolve the path to the command --- */
+
+  if (!strchr(todo[0], '/')) {
+    char *path;
+    char *p;
+    struct stat st;
+    size_t sz;
+
+    if ((p = getenv("PATH")) == 0)
+      p = "/bin:/usr/bin";
+    sz = strlen(p) + 1;
+    memcpy(path = xmalloc(sz), p, sz);
+
+    for (p = strtok(path, ":"); (p = strtok(0, ":")) != 0; ) {
+
+      /* --- SECURITY: check length of string before copying --- */
+
+      if (strlen(p) + strlen(todo[0]) + 2 > sizeof(buff))
+	continue;
+
+      /* --- Now build the pathname and check it --- */
+
+      sprintf(buff, "%s/%s", p, todo[0]);
+      if (stat(buff, &st) == 0 &&	/* Check it exists */
+	  st.st_mode & 0111 &&		/* Check it's executable */
+	  (st.st_mode & S_IFMT) == S_IFREG) /* Check it's a file */
+	break;
+    }
+
+    if (!p)
+      die("couldn't find `%s' in path", todo[0]);
+    todo[0] = buff;
+    free(path);
+  }
+
+  /* --- Canonicalise the path string, if necessary --- */
+
+  {
+    char b[CMDLEN_MAX];
+    char *p;
+    const char *q;
+
+    /* --- Insert current directory name if path not absolute --- */
+
+    p = b;
+    q = todo[0];
+    if (*q != '/') {
+      if (!getcwd(b, sizeof(b)))
+	die("couldn't read current directory: %s", strerror(errno));
+      p += strlen(p);
+      *p++ = '/';
+    }
+
+    /* --- Now copy over characters from the path string --- */
+
+    while (*q) {
+
+      /* --- SECURITY: check for buffer overflows here --- *
+       *
+       * I write at most one byte per iteration so this is OK.  Remember to
+       * allow one for the null byte.
+       */
+
+      if (p >= b + sizeof(b) - 1)
+	die("buffer overflow -- bad things happened");
+
+      /* --- Reduce multiple slashes to just one --- */
+
+      if (*q == '/') {
+	while (*q == '/')
+	  q++;
+	*p++ = '/';
+      }
+
+      /* --- Handle dots in filenames --- *
+       *
+       * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
+       * we've just stuck the current directory on the end of the buffer,
+       * or we've just put something else on the end.
+       */
+
+      else if (*q == '.' && p[-1] == '/') {
+
+	/* --- A simple `./' just gets removed --- */
+
+	if (q[1] == 0 || q[1] == '/') {
+	  q++;
+	  p--;
+	  continue;
+	}
+
+	/* --- A `../' needs to be peeled back to the previous `/' --- */
+
+	if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
+	  q += 2;
+	  p--;
+	  while (p > b && p[-1] != '/')
+	    p--;
+	  if (p > b)
+	    p--;
+	  continue;
+	}
+      } else
+	*p++ = *q++;
+    }
+
+    *p++ = 0;
+    strcpy(rq.cmd, b);
+  }
+
+  /* --- Run the check --- */
+
+  {
+    int a = check(&rq);
+    char from[16], to[16];
+    struct passwd *pw;
+
+    if ((pw = getpwuid(rq.from)) != 0)
+      sprintf(from, "%.15s", pw->pw_name);
+    else
+      sprintf(from, "user %lu", (unsigned long)rq.from);
+
+    if ((pw = getpwuid(rq.to)) != 0)
+      sprintf(to, "%.15s", pw->pw_name);
+    else
+      sprintf(to, "user %lu", (unsigned long)rq.to);
+
+    syslog(LOG_INFO,
+	   "permission %s for %s to become %s to run `%s'",
+	   a ? "granted" : "denied", from, to, rq.cmd);
+
+    if (!a)
+      die("permission denied");
+  }
+
+  /* --- Now do the job --- */
+
+#ifdef TEST_RIG
+  printf("ok\n");
+  return (0);
+#else
+  if (setuid(rq.to) == -1 || seteuid(rq.to) == -1)
+    die("couldn't set uid: %s", strerror(errno));
+  execv(rq.cmd, todo);
+  die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
+  return (127);
+#endif
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/blowfish-sbox.h b/src/blowfish-sbox.h
new file mode 100644
index 0000000..0baf11c
--- /dev/null
+++ b/src/blowfish-sbox.h
@@ -0,0 +1,320 @@
+/* -*-c-*-
+ *
+ * $Id: blowfish-sbox.h,v 1.1 1997/07/21 13:47:54 mdw Exp $
+ *
+ * Blowfish encryption routines
+ *
+ * (c) 1997 Mark Wooding
+ */
+ 
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+ 
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: blowfish-sbox.h,v $
+ * Revision 1.1  1997/07/21 13:47:54  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Blowfish initialisation tables ------------------------------------*/
+
+/* --- These are just digits of %$\pi$% --- */
+
+static const blowfish_key blowfish__init = {
+
+  /* --- P-array of round-specific subkeys --- */
+
+  { 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
+    0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
+    0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
+    0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
+    0x9216d5d9, 0x8979fb1b },
+
+  /* --- First S-box --- */
+
+  { 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
+    0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
+    0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
+    0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
+    0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
+    0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
+    0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
+    0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
+    0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
+    0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
+    0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
+    0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
+    0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
+    0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
+    0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
+    0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
+    0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
+    0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
+    0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
+    0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
+    0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
+    0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
+    0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
+    0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
+    0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
+    0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
+    0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
+    0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
+    0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
+    0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
+    0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
+    0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
+    0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
+    0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
+    0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
+    0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
+    0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
+    0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
+    0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
+    0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
+    0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
+    0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
+    0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
+    0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
+    0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
+    0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
+    0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
+    0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
+    0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
+    0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
+    0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
+    0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
+    0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
+    0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
+    0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
+    0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
+    0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
+    0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
+    0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
+    0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
+    0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
+    0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
+    0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
+    0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a },
+
+  /* --- Second S-box --- */
+
+  { 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
+    0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
+    0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
+    0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
+    0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
+    0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
+    0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
+    0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
+    0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
+    0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
+    0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
+    0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
+    0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
+    0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
+    0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
+    0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
+    0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
+    0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
+    0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
+    0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
+    0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
+    0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
+    0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
+    0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
+    0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
+    0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
+    0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
+    0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
+    0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
+    0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
+    0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
+    0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
+    0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
+    0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
+    0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
+    0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
+    0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
+    0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
+    0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
+    0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
+    0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
+    0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
+    0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
+    0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
+    0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
+    0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
+    0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
+    0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
+    0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
+    0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
+    0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
+    0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
+    0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
+    0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
+    0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
+    0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
+    0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
+    0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
+    0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
+    0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
+    0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
+    0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
+    0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
+    0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 },
+
+  /* --- Third S-box --- */
+
+  { 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
+    0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
+    0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
+    0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
+    0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
+    0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
+    0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
+    0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
+    0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
+    0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
+    0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
+    0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
+    0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
+    0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
+    0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
+    0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
+    0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
+    0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
+    0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
+    0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
+    0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
+    0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
+    0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
+    0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
+    0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
+    0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
+    0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
+    0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
+    0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
+    0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
+    0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
+    0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
+    0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
+    0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
+    0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
+    0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
+    0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
+    0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
+    0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
+    0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
+    0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
+    0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
+    0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
+    0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
+    0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
+    0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
+    0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
+    0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
+    0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
+    0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
+    0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
+    0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
+    0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
+    0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
+    0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
+    0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
+    0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
+    0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
+    0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
+    0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
+    0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
+    0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
+    0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
+    0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 },
+
+  /* --- Fourth and final S-box --- */
+  
+  { 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
+    0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
+    0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
+    0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
+    0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
+    0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
+    0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
+    0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
+    0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
+    0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
+    0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
+    0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
+    0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
+    0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
+    0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
+    0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
+    0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
+    0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
+    0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
+    0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
+    0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
+    0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
+    0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
+    0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
+    0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
+    0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
+    0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
+    0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
+    0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
+    0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
+    0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
+    0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
+    0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
+    0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
+    0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
+    0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
+    0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
+    0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
+    0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
+    0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
+    0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
+    0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
+    0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
+    0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
+    0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
+    0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
+    0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
+    0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
+    0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
+    0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
+    0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
+    0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
+    0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
+    0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
+    0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
+    0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
+    0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
+    0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
+    0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
+    0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
+    0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
+    0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
+    0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
+    0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 }
+};
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/blowfish.c b/src/blowfish.c
new file mode 100644
index 0000000..ddf7b26
--- /dev/null
+++ b/src/blowfish.c
@@ -0,0 +1,608 @@
+/* -*-c-*-
+ *
+ * $Id: blowfish.c,v 1.1 1997/07/21 13:47:53 mdw Exp $
+ *
+ * Blowfish encryption routines
+ *
+ * (c) 1997 Mark Wooding
+ */
+ 
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+ 
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: blowfish.c,v $
+ * Revision 1.1  1997/07/21 13:47:53  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <stdio.h>
+
+/* --- Local headers --- */
+
+#include "config.h"
+#include "blowfish.h"
+#include "utils.h"
+
+/*----- Define the initial S-box values -----------------------------------*/
+
+#include "blowfish-sbox.h"
+
+/*----- Useful macros -----------------------------------------------------*/
+
+/* --- The Blowfish round function --- *
+ *
+ * This is why I like this cipher.  The round function is microscopic.  And
+ * very fast.
+ */
+
+#define ROUND(L, R, K)							\
+  ((L) ^= k->p[K],							\
+   (R) ^= ((((k->s0[((L) >> 24) & 0xFF]) +				\
+	      k->s1[((L) >> 16) & 0xFF]) ^				\
+	      k->s2[((L) >>  8) & 0xFF]) +				\
+	      k->s3[((L) >>  0) & 0xFF]))
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @blowfish_encrypt@ --- *
+ *
+ * Arguments:	@const blowfish_key *k@ = pointer to key block
+ *		@const void *from@ = block to encrypt from
+ *		@void *to@ = block to encrypt to
+ *
+ * Returns:	---
+ *
+ * Use:		Encrypts a block using the Blowfish algorithm.
+ */
+
+void blowfish_encrypt(const blowfish_key *k, const void *from, void *to)
+{
+  uint_32 l, r;
+  const unsigned char *f = from;
+  unsigned char *t = to;
+
+  /* --- Extract left and right block halves --- */
+
+  l = load32(f + 0);
+  r = load32(f + 4);
+
+  /* --- Now run the round function on these values --- */
+
+  ROUND(l, r,  0);
+  ROUND(r, l,  1);
+  ROUND(l, r,  2);
+  ROUND(r, l,  3);
+  ROUND(l, r,  4);
+  ROUND(r, l,  5);
+  ROUND(l, r,  6);
+  ROUND(r, l,  7);
+  ROUND(l, r,  8);
+  ROUND(r, l,  9);
+  ROUND(l, r, 10);
+  ROUND(r, l, 11);
+  ROUND(l, r, 12);
+  ROUND(r, l, 13);
+  ROUND(l, r, 14);
+  ROUND(r, l, 15);
+
+  /* --- Final transformation --- */
+
+  l ^= k->p[16];
+  r ^= k->p[17];
+
+  /* --- Store the encrypted value --- */
+
+  store32(t + 0, r);
+  store32(t + 4, l);  
+}
+
+/* --- @blowfish_decrypt@ --- *
+ *
+ * Arguments:	@const blowfish_key *k@ = pointer to key block
+ *		@const void *from@ = block to decrypt from
+ *		@void *to@ = block to decrypt to
+ *
+ * Returns:	---
+ *
+ * Use:		Decrypts a block using the Blowfish algorithm.
+ */
+
+void blowfish_decrypt(const blowfish_key *k, const void *from, void *to)
+{
+  uint_32 l, r;
+  const unsigned char *f = from;
+  unsigned char *t = to;
+
+  /* --- Extract left and right block halves --- */
+
+  l = load32(f + 0);
+  r = load32(f + 4);
+
+  /* --- Now run the round function on these values --- */
+
+  ROUND(l, r, 17);
+  ROUND(r, l, 16);
+  ROUND(l, r, 15);
+  ROUND(r, l, 14);
+  ROUND(l, r, 13);
+  ROUND(r, l, 12);
+  ROUND(l, r, 11);
+  ROUND(r, l, 10);
+  ROUND(l, r,  9);
+  ROUND(r, l,  8);
+  ROUND(l, r,  7);
+  ROUND(r, l,  6);
+  ROUND(l, r,  5);
+  ROUND(r, l,  4);
+  ROUND(l, r,  3);
+  ROUND(r, l,  2);
+
+  /* --- Final transformation --- */
+
+  l ^= k->p[1];
+  r ^= k->p[0];
+
+  /* --- Store the decrypted value --- */
+
+  store32(t + 0, r);
+  store32(t + 4, l);  
+}
+
+/* --- @blowfish__qcrypt@ --- *
+ *
+ * Arguments:	@const blowfish_key *k@ = pointer to a key block
+ *		@uint_32 *p@ = pointer to block to mangle
+ *
+ * Returns:	---
+ *
+ * Use:		Mangles a block using the Blowfish algorithm.
+ */
+
+static void blowfish__qcrypt(blowfish_key *k, uint_32 *p)
+{
+  uint_32 l = p[0], r = p[1];
+
+  /* --- Run the round function --- */
+
+  ROUND(l, r,  0);
+  ROUND(r, l,  1);
+  ROUND(l, r,  2);
+  ROUND(r, l,  3);
+  ROUND(l, r,  4);
+  ROUND(r, l,  5);
+  ROUND(l, r,  6);
+  ROUND(r, l,  7);
+  ROUND(l, r,  8);
+  ROUND(r, l,  9);
+  ROUND(l, r, 10);
+  ROUND(r, l, 11);
+  ROUND(l, r, 12);
+  ROUND(r, l, 13);
+  ROUND(l, r, 14);
+  ROUND(r, l, 15);
+
+  /* --- Output transformation --- */
+
+  l ^= k->p[16];
+  r ^= k->p[17];
+
+  /* --- Store the new values --- */
+
+  p[0] = r;
+  p[1] = l;
+}
+
+/* --- @blowfish__buildKey@ --- *
+ *
+ * Arguments:	@blowfish_key *k@ = pointer to a key block to set up
+ *
+ * Returns:	---
+ *
+ * Use:		Sets up the P-array and S-boxes once a key has been mixed
+ *		into the P-array.  Use a local copy of the Blowfish
+ *		encryption routine, to avoid penalising the main code too
+ *		much with having to veneer onto a general args-in-words
+ *		function, and to avoid me messing about with transforming
+ *		values backwards and forwards between char arrays and
+ *		integers.
+ */
+
+static void blowfish__buildKey(blowfish_key *k)
+{
+  uint_32 b[2] = { 0, 0 };
+  int i;
+
+  /* --- First, run through the P-array --- */
+
+  for (i = 0; i < 18; i += 2) {
+    blowfish__qcrypt(k, b);
+    k->p[i] = b[0];
+    k->p[i + 1] = b[1];
+  }
+
+  /* --- Now do the S-boxes --- */
+
+  for (i = 0; i < 256; i += 2) {
+    blowfish__qcrypt(k, b);
+    k->s0[i] = b[0];
+    k->s0[i + 1] = b[1];
+  }
+
+  for (i = 0; i < 256; i += 2) {
+    blowfish__qcrypt(k, b);
+    k->s1[i] = b[0];
+    k->s1[i + 1] = b[1];
+  }
+
+  for (i = 0; i < 256; i += 2) {
+    blowfish__qcrypt(k, b);
+    k->s2[i] = b[0];
+    k->s2[i + 1] = b[1];
+  }
+
+  for (i = 0; i < 256; i += 2) {
+    blowfish__qcrypt(k, b);
+    k->s3[i] = b[0];
+    k->s3[i + 1] = b[1];
+  }
+}
+
+/* --- @blowfish_setKey@ --- *
+ *
+ * Arguments:	@blowfish_key *kb@ = pointer to key block to fill
+ *		@void *k@ = pointer to key data
+ *		@size_t sz@ = length of data in bytes
+ *
+ * Returns:	---
+ *
+ * Use:		Expands a key which isn't represented as a number of whole
+ *		words.  This is a nonstandard extension, although it can be
+ *		used to support 40-bit keys, which some governments might
+ *		find more palatable than 160-bit (or 448-bit!) keys.
+ */
+
+void blowfish_setKey(blowfish_key *kb, const void *k, size_t sz)
+{
+  int i, j, l;
+  const unsigned char *p = k;
+  uint_32 a;
+
+  memcpy(kb, &blowfish__init, sizeof(blowfish__init));
+
+  j = 0;
+  for (i = 0; i < 18; i++) {
+    a = 0;
+    for (l = 0; l < 4; l++) {
+      a = (a << 8) | p[j];
+      j++;
+      if (j >= sz)
+	j = 0;
+    }
+    kb->p[i] ^= a;
+  }
+
+  blowfish__buildKey(kb);
+}
+
+/*----- Test rig ----------------------------------------------------------*/
+
+#ifdef TEST_RIG
+
+int main(void)
+{
+  /* --- Stage one: ECB tests --- */
+
+  {
+    static struct {
+      uint_32 k[2];
+      uint_32 p[2];
+      uint_32 c[2];
+    } table[] = {
+      { { 0x00000000u, 0x00000000u },
+        { 0x00000000u, 0x00000000u },
+        { 0x4EF99745u, 0x6198DD78u } },
+
+      { { 0xFFFFFFFFu, 0xFFFFFFFFu },
+        { 0xFFFFFFFFu, 0xFFFFFFFFu },
+        { 0x51866FD5u, 0xB85ECB8Au } },
+
+      { { 0x30000000u, 0x00000000u },
+        { 0x10000000u, 0x00000001u },
+        { 0x7D856F9Au, 0x613063F2u } },
+
+      { { 0x11111111u, 0x11111111u },
+        { 0x11111111u, 0x11111111u },
+        { 0x2466DD87u, 0x8B963C9Du } },
+
+      { { 0x01234567u, 0x89ABCDEFu },
+        { 0x11111111u, 0x11111111u },
+        { 0x61F9C380u, 0x2281B096u } },
+
+      { { 0x11111111u, 0x11111111u },
+        { 0x01234567u, 0x89ABCDEFu },
+        { 0x7D0CC630u, 0xAFDA1EC7u } },
+
+      { { 0x00000000u, 0x00000000u },
+        { 0x00000000u, 0x00000000u },
+        { 0x4EF99745u, 0x6198DD78u } },
+
+      { { 0xFEDCBA98u, 0x76543210u },
+        { 0x01234567u, 0x89ABCDEFu },
+        { 0x0ACEAB0Fu, 0xC6A0A28Du } },
+
+      { { 0x7CA11045u, 0x4A1A6E57u },
+        { 0x01A1D6D0u, 0x39776742u },
+        { 0x59C68245u, 0xEB05282Bu } },
+
+      { { 0x0131D961u, 0x9DC1376Eu },
+        { 0x5CD54CA8u, 0x3DEF57DAu },
+        { 0xB1B8CC0Bu, 0x250F09A0u } },
+
+      { { 0x07A1133Eu, 0x4A0B2686u },
+        { 0x0248D438u, 0x06F67172u },
+        { 0x1730E577u, 0x8BEA1DA4u } },
+
+      { { 0x3849674Cu, 0x2602319Eu },
+        { 0x51454B58u, 0x2DDF440Au },
+        { 0xA25E7856u, 0xCF2651EBu } },
+
+      { { 0x04B915BAu, 0x43FEB5B6u },
+        { 0x42FD4430u, 0x59577FA2u },
+        { 0x353882B1u, 0x09CE8F1Au } },
+
+      { { 0x0113B970u, 0xFD34F2CEu },
+        { 0x059B5E08u, 0x51CF143Au },
+        { 0x48F4D088u, 0x4C379918u } },
+
+      { { 0x0170F175u, 0x468FB5E6u },
+        { 0x0756D8E0u, 0x774761D2u },
+        { 0x432193B7u, 0x8951FC98u } },
+
+      { { 0x43297FADu, 0x38E373FEu },
+        { 0x762514B8u, 0x29BF486Au },
+        { 0x13F04154u, 0xD69D1AE5u } },
+
+      { { 0x07A71370u, 0x45DA2A16u },
+        { 0x3BDD1190u, 0x49372802u },
+        { 0x2EEDDA93u, 0xFFD39C79u } },
+
+      { { 0x04689104u, 0xC2FD3B2Fu },
+        { 0x26955F68u, 0x35AF609Au },
+        { 0xD887E039u, 0x3C2DA6E3u } },
+
+      { { 0x37D06BB5u, 0x16CB7546u },
+        { 0x164D5E40u, 0x4F275232u },
+        { 0x5F99D04Fu, 0x5B163969u } },
+
+      { { 0x1F08260Du, 0x1AC2465Eu },
+        { 0x6B056E18u, 0x759F5CCAu },
+        { 0x4A057A3Bu, 0x24D3977Bu } },
+
+      { { 0x58402364u, 0x1ABA6176u },
+        { 0x004BD6EFu, 0x09176062u },
+        { 0x452031C1u, 0xE4FADA8Eu } },
+
+      { { 0x02581616u, 0x4629B007u },
+        { 0x480D3900u, 0x6EE762F2u },
+        { 0x7555AE39u, 0xF59B87BDu } },
+
+      { { 0x49793EBCu, 0x79B3258Fu },
+        { 0x437540C8u, 0x698F3CFAu },
+        { 0x53C55F9Cu, 0xB49FC019u } },
+
+      { { 0x4FB05E15u, 0x15AB73A7u },
+        { 0x072D43A0u, 0x77075292u },
+        { 0x7A8E7BFAu, 0x937E89A3u } },
+
+      { { 0x49E95D6Du, 0x4CA229BFu },
+        { 0x02FE5577u, 0x8117F12Au },
+        { 0xCF9C5D7Au, 0x4986ADB5u } },
+
+      { { 0x018310DCu, 0x409B26D6u },
+        { 0x1D9D5C50u, 0x18F728C2u },
+        { 0xD1ABB290u, 0x658BC778u } },
+
+      { { 0x1C587F1Cu, 0x13924FEFu },
+        { 0x30553228u, 0x6D6F295Au },
+        { 0x55CB3774u, 0xD13EF201u } },
+
+      { { 0x01010101u, 0x01010101u },
+        { 0x01234567u, 0x89ABCDEFu },
+        { 0xFA34EC48u, 0x47B268B2u } },
+
+      { { 0x1F1F1F1Fu, 0x0E0E0E0Eu },
+        { 0x01234567u, 0x89ABCDEFu },
+        { 0xA7907951u, 0x08EA3CAEu } },
+
+      { { 0xE0FEE0FEu, 0xF1FEF1FEu },
+        { 0x01234567u, 0x89ABCDEFu },
+        { 0xC39E072Du, 0x9FAC631Du } },
+
+      { { 0x00000000u, 0x00000000u },
+        { 0xFFFFFFFFu, 0xFFFFFFFFu },
+        { 0x014933E0u, 0xCDAFF6E4u } },
+
+      { { 0xFFFFFFFFu, 0xFFFFFFFFu },
+        { 0x00000000u, 0x00000000u },
+        { 0xF21E9A77u, 0xB71C49BCu } },
+
+      { { 0x01234567u, 0x89ABCDEFu },
+        { 0x00000000u, 0x00000000u },
+        { 0x24594688u, 0x5754369Au } },
+
+      { { 0xFEDCBA98u, 0x76543210u },
+        { 0xFFFFFFFFu, 0xFFFFFFFFu },
+        { 0x6B5C5A9Cu, 0x5D9E0A5Au } }
+    };
+
+    int f = 1;
+    int i;
+
+    printf("*** stage one: ");
+    fflush(stdout);
+
+    for (i = 0; i < sizeof(table) / sizeof(table[0]); i++) {
+      char kb[8], p[8], c[8];
+      blowfish_key k;
+
+      store32(kb + 0, table[i].k[0]);
+      store32(kb + 4, table[i].k[1]);
+      blowfish_setKey(&k, kb, 8);
+
+      store32(p + 0, table[i].p[0]);
+      store32(p + 4, table[i].p[1]);
+      blowfish_encrypt(&k, p, c);
+
+      if (load32(c + 0) != table[i].c[0] ||
+	  load32(c + 4) != table[i].c[1]) {
+	printf("\n"
+	       "!!! bad encryption\n"
+	       "                    key = %08lx-%08lx\n"
+	       "              plaintext = %08lx-%08lx\n"
+	       "    expected ciphertext = %08lx-%08lx\n"
+	       "  calculated ciphertext = %08lx-%08lx\n",
+	       (unsigned long)table[i].k[0],
+	       (unsigned long)table[i].k[1],
+	       (unsigned long)table[i].p[0],
+	       (unsigned long)table[i].p[1],
+	       (unsigned long)table[i].c[0],
+	       (unsigned long)table[i].c[1],
+	       (unsigned long)load32(c + 0),
+	       (unsigned long)load32(c + 4));
+	f = 0;
+      }
+
+      blowfish_decrypt(&k, c, p);
+      if (load32(p + 0) != table[i].p[0] ||
+	  load32(p + 4) != table[i].p[1]) {
+	printf("\n"
+	       "!!! bad decryption\n"
+	       "                    key = %08lx-%08lx\n"
+	       "             ciphertext = %08lx-%08lx\n"
+	       "     expected plaintext = %08lx-%08lx\n"
+	       "   calculated plaintext = %08lx-%08lx\n",
+	       (unsigned long)table[i].k[0],
+	       (unsigned long)table[i].k[1],
+	       (unsigned long)table[i].c[0],
+	       (unsigned long)table[i].c[1],
+	       (unsigned long)table[i].p[0],
+	       (unsigned long)table[i].p[1],
+	       (unsigned long)load32(p + 0),
+	       (unsigned long)load32(p + 4));
+	f = 0;
+      }
+
+      putchar('.');
+      fflush(stdout);
+    }
+    putchar('\n');
+    if (f)
+      printf("*** stage one ok\n");
+  }
+
+  /* --- Stage 2: key scheduling --- */
+
+  {
+    static struct {
+      uint_32 c[2];
+    } table[] = {
+      {{ 0xF9AD597Cu, 0x49DB005Eu }},
+      {{ 0xE91D21C1u, 0xD961A6D6u }},
+      {{ 0xE9C2B70Au, 0x1BC65CF3u }},
+      {{ 0xBE1E6394u, 0x08640F05u }},
+      {{ 0xB39E4448u, 0x1BDB1E6Eu }},
+      {{ 0x9457AA83u, 0xB1928C0Du }},
+      {{ 0x8BB77032u, 0xF960629Du }},
+      {{ 0xE87A244Eu, 0x2CC85E82u }},
+      {{ 0x15750E7Au, 0x4F4EC577u }},
+      {{ 0x122BA70Bu, 0x3AB64AE0u }},
+      {{ 0x3A833C9Au, 0xFFC537F6u }},
+      {{ 0x9409DA87u, 0xA90F6BF2u }},
+      {{ 0x884F8062u, 0x5060B8B4u }},
+      {{ 0x1F85031Cu, 0x19E11968u }},
+      {{ 0x79D9373Au, 0x714CA34Fu }},
+      {{ 0x93142887u, 0xEE3BE15Cu }},
+      {{ 0x03429E83u, 0x8CE2D14Bu }},
+      {{ 0xA4299E27u, 0x469FF67Bu }},
+      {{ 0xAFD5AED1u, 0xC1BC96A8u }},
+      {{ 0x10851C0Eu, 0x3858DA9Fu }},
+      {{ 0xE6F51ED7u, 0x9B9DB21Fu }},
+      {{ 0x64A6E14Au, 0xFD36B46Fu }},
+      {{ 0x80C7D7D4u, 0x5A5479ADu }},
+      {{ 0x05044B62u, 0xFA52D080u }},
+    };
+
+    unsigned char kk[] = {
+      0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 0x87,
+      0x78, 0x69, 0x5A, 0x4B, 0x3C, 0x2D, 0x1E, 0x0F,
+      0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77
+    };
+
+    int i;
+    int f = 1;
+
+    printf("*** stage two: ");
+    fflush(stdout);
+
+    for (i = 0; i < sizeof(kk); i++) {
+      blowfish_key k;
+      unsigned char p[8] = { 0xFE, 0xDC, 0xBA, 0x98,
+			     0x76, 0x54, 0x32, 0x10 };
+
+      blowfish_setKey(&k, kk, i + 1);
+      blowfish_encrypt(&k, p, p);
+
+      if (load32(p + 0) != table[i].c[0] ||
+	  load32(p + 4) != table[i].c[1]) {
+	printf("!!! bad encryption\n"
+	       "  key length = %i\n"
+	       "    expected = %08lx-%08lx\n"
+	       "  calculated = %08lx-%08lx\n",
+	       i + 1,
+	       (unsigned long)table[i].c[0],
+	       (unsigned long)table[i].c[1],
+	       (unsigned long)load32(p + 0),
+	       (unsigned long)load32(p + 4));
+	f = 0;
+      }
+
+      putchar('.');
+      fflush(stdout);
+    }
+
+    putchar('\n');
+
+    if (f)
+      printf("*** stage two ok\n");
+  }
+
+  return (0);
+
+}      
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/blowfish.h b/src/blowfish.h
new file mode 100644
index 0000000..23519a6
--- /dev/null
+++ b/src/blowfish.h
@@ -0,0 +1,110 @@
+/* -*-c-*-
+ *
+ * $Id: blowfish.h,v 1.1 1997/07/21 13:47:53 mdw Exp $
+ *
+ * Blowfish encryption routines
+ *
+ * (c) 1997 Mark Wooding
+ */
+ 
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: blowfish.h,v $
+ * Revision 1.1  1997/07/21 13:47:53  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef BLOWFISH_H
+#define BLOWFISH_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Type definitions --------------------------------------------------*/
+
+/* --- A blowfish expanded key --- */
+
+typedef struct blowfish_key {
+  uint_32 p[18];
+  uint_32 s0[256];
+  uint_32 s1[256];
+  uint_32 s2[256];
+  uint_32 s3[256];
+} blowfish_key;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @blowfish_encrypt@ --- *
+ *
+ * Arguments:	@const blowfish_key *k@ = pointer to key block
+ *		@const voic *from@ = block to encrypt from
+ *		@void *to@ = block to encrypt to
+ *
+ * Returns:	---
+ *
+ * Use:		Encrypts a block using the Blowfish algorithm.
+ */
+
+extern void blowfish_encrypt(const blowfish_key */*k*/,
+			     const void */*from*/, void */*to*/);
+
+
+/* --- @blowfish_decrypt@ --- *
+ *
+ * Arguments:	@const blowfish_key *k@ = pointer to key block
+ *		@const void *from@ = block to decrypt from
+ *		@void *to@ = block to decrypt to
+ *
+ * Returns:	---
+ *
+ * Use:		Decrypts a block using the Blowfish algorithm.
+ */
+
+extern void blowfish_decrypt(const blowfish_key */*k*/,
+			     const void */*from*/, void */*to*/);
+
+/* --- @blowfish_setKey@ --- *
+ *
+ * Arguments:	@blowfish_key *kb@ = pointer to key block to fill
+ *		@void *k@ = pointer to key data
+ *		@size_t sz@ = length of data in bytes
+ *
+ * Returns:	---
+ *
+ * Use:		Expands a key which isn't represented as a number of whole
+ *		words.  This is a nonstandard extension, although it can be
+ *		used to support 40-bit keys, which some governments might
+ *		find more palatable than 160-bit (or 448-bit!) keys.
+ */
+
+extern void blowfish_setKey(blowfish_key */*kb*/,
+			    const void */*k*/, size_t /*sz*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/src/check.c b/src/check.c
new file mode 100644
index 0000000..9ed0b6f
--- /dev/null
+++ b/src/check.c
@@ -0,0 +1,286 @@
+/* -*-c-*-
+ *
+ * $Id: check.c,v 1.1 1997/07/21 13:47:53 mdw Exp $
+ *
+ * Check validity of requests
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: check.c,v $
+ * Revision 1.1  1997/07/21 13:47:53  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+/* --- Unix headers --- */
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+
+#include <netdb.h>
+#include <unistd.h>
+
+/* --- Local headers --- */
+
+#include "become.h"
+#include "config.h"
+#include "crypt.h"
+#include "idea.h"
+#include "lexer.h"
+#include "name.h"
+#include "rule.h"
+#include "parser.h"
+#include "tx.h"
+#include "utils.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @check__client@ --- *
+ *
+ * Arguments:	@request *rq@ = pointer to request buffer
+ *		@const char *serv@ = pointer to the server
+ *		@int port@ = port number to use
+ *
+ * Returns:	Nonzero if OK, zero if forbidden
+ *
+ * Use:		Contacts a server to decide whether the request is OK.
+ */
+
+static int check__client(request *rq, const char *serv, int port)
+{
+  int fd;
+  struct sockaddr_in ssin;
+  unsigned char crq[crq_size];
+  unsigned char k[IDEA_KEYSIZE];
+  unsigned char sk[IDEA_KEYSIZE];
+  time_t t;
+  pid_t pid;
+
+  /* --- Create my socket --- */
+
+  if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
+    die("couldn't create socket: %s", strerror(errno));
+
+  /* --- Bind myself to some address --- */
+
+  {
+    struct sockaddr_in sin;
+
+    sin.sin_family = AF_INET;
+    sin.sin_port = 0;
+    sin.sin_addr.s_addr = htonl(INADDR_ANY);
+
+    if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+      die("couldn't bind socket to address: %s", strerror(errno));
+  }
+
+  /* --- Find the server's address --- */
+
+  {
+    struct hostent *he;
+
+    /* --- Resolve the server's name --- */
+
+    if ((he = gethostbyname(serv)) == 0)
+      die("couldn't find server host `%s'", serv);
+
+    /* --- Build the address block --- */
+
+    ssin.sin_family = AF_INET;
+    ssin.sin_port = htons(port);
+    memcpy(&ssin.sin_addr, he->h_addr, sizeof(struct in_addr));
+  }
+
+  /* --- Read in the encryption key --- */
+
+  {
+    FILE *fp;
+
+    if ((fp = fopen(file_KEY, "r")) == 0) {
+      die("couldn't open key file `%s': %s", file_KEY,
+	  strerror(errno));
+    }
+    tx_getBits(k, 128, fp);
+  }
+
+  /* --- Now build a request packet --- */
+
+  t = time(0);
+  pid = getpid();
+  crypt_packRequest(rq, crq, t, pid, k, sk);
+
+  /* --- Send the packet to the server --- */
+
+  if (sendto(fd, (char *)crq, sizeof(crq), 0,
+	     (struct sockaddr *)&ssin, sizeof(ssin)) < 0) {
+    burn(k);
+    die("couldn't send request to server: %s", strerror(errno));
+  }
+  burn(k);
+
+  /* --- Now wait for a reply --- */
+
+  {
+    fd_set fds;
+    struct timeval when, now, tv;
+
+    gettimeofday(&when, 0);
+    when.tv_sec += 10;
+
+    for (;;) {
+      int i;
+
+      /* --- Sort out when to return --- */
+
+      gettimeofday(&now, 0);
+      if (now.tv_usec > when.tv_usec) {
+	now.tv_usec -= 1000000;
+	now.tv_sec += 1;
+      }
+      tv.tv_sec = when.tv_sec - now.tv_sec;
+      tv.tv_usec = when.tv_usec - now.tv_usec;
+
+      /* --- Sort out file descriptors to watch --- */
+
+      FD_ZERO(&fds);
+      FD_SET(fd, &fds);
+
+      /* --- Wait for them --- */
+
+      i = select(FD_SETSIZE, &fds, 0, 0, &tv);
+      if (i == 0)
+	die("no answer from server");
+      if (i < 0)
+	die("error waiting for reply: %s", strerror(errno));
+
+      /* --- A reply should be waiting now --- */
+
+      {
+	struct sockaddr_in sin;
+	int slen = sizeof(sin);
+	unsigned char buff[256];
+	int answer;
+
+	/* --- Read the reply data --- */
+
+	if (recvfrom(fd, (char *)buff, sizeof(buff), 0,
+		     (struct sockaddr *)&sin, &slen) < 0)
+	  die("error reading server's reply: %s", strerror(errno));
+
+	/* --- Verify the sender --- */
+
+	if (sin.sin_addr.s_addr != ssin.sin_addr.s_addr ||
+	    sin.sin_port != ssin.sin_port)
+	  continue;
+    
+	/* --- Unpack and verify the response --- */
+
+	answer = crypt_unpackReply(buff, sk, t, pid);
+	if (answer < 0)
+	  continue;
+	return (answer);
+      }
+    }
+  }
+
+  die("internal error: can't get here in check_client");
+  return (0);
+}
+
+/* --- @check@ --- *
+ *
+ * Arguments:	@request *rq@ = pointer to request buffer
+ *
+ * Returns:	Nonzero if OK, zero if forbidden
+ *
+ * Use:		Checks to see if the request is acceptable.
+ */
+
+int check(request *rq)
+{
+  FILE *fp;
+
+  /* --- Check if we need to talk to a server --- */
+
+  if ((fp = fopen(file_SERVER, "r")) != 0) {
+    char buff[64];
+    int port;
+    int ch;
+
+    if (fscanf(fp, " %63[^: \t] ", buff) < 1)
+      die("error in `%s'", file_SERVER);
+    ch = getc(fp);
+    if (ch == ':') {
+      char b[64];
+      struct servent *se;
+
+      fscanf(fp, "%s", b);
+      if ((se = getservbyname(b, 0)) != 0)
+	port = ntohs(se->s_port);
+      else if ((port = atoi(b)) == 0)
+	die("error in `%s'", file_SERVER);
+    } else {
+      struct servent *se;
+
+      if ((se = getservbyname(quis(), "udp")) == 0)
+	die("no idea which port to use");
+      port = ntohs(se->s_port);
+    }
+    fclose(fp);
+    return (check__client(rq, buff, port));
+  }
+
+  /* --- Read the configuration in and go --- */
+
+  if ((fp = fopen(file_RULES, "r")) == 0) {
+    die("couldn't read configuration file `%s': %s",
+	file_RULES, strerror(errno));
+  }
+
+  name_init();
+  rule_init();
+  lexer_scan(fp);
+  yyparse();
+
+  return (rule_check(rq));
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/check.h b/src/check.h
new file mode 100644
index 0000000..24f5cb9
--- /dev/null
+++ b/src/check.h
@@ -0,0 +1,70 @@
+/* -*-c-*-
+ *
+ * $Id: check.h,v 1.1 1997/07/21 13:47:52 mdw Exp $
+ *
+ * Check validity of requests
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: check.h,v $
+ * Revision 1.1  1997/07/21 13:47:52  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef CHECK_H
+#define CHECK_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#ifndef BECOME_H
+#  include "become.h"
+#endif
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @check@ --- *
+ *
+ * Arguments:	@request *rq@ = pointer to request buffer
+ *
+ * Returns:	Nonzero if OK, zero if forbidden
+ *
+ * Use:		Checks to see if the request is acceptable.
+ */
+
+extern int check(request */*rq*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
+
diff --git a/src/class.c b/src/class.c
new file mode 100644
index 0000000..d2f5e4b
--- /dev/null
+++ b/src/class.c
@@ -0,0 +1,325 @@
+/* -*-c-*-
+ *
+ * $Id: class.c,v 1.1 1997/07/21 13:47:52 mdw Exp $
+ *
+ * Handling classes of things nicely
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: class.c,v $
+ * Revision 1.1  1997/07/21 13:47:52  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Unix headers --- */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+
+#include <netdb.h>
+
+/* --- Local headers --- */
+
+#include "class.h"
+#include "set.h"
+#include "sym.h"
+#include "utils.h"
+
+/*----- Global variables --------------------------------------------------*/
+
+static classdef class__all = { clType_all, -1, 0 };
+classdef *class_all = &class__all;
+
+/*----- Wildcard matching -------------------------------------------------*/
+
+/* --- @class__wildMatch@ --- *
+ *
+ * Arguments:	@const char *pat@ = pointer to pattern string
+ *		@const char *p@ = pointer to target string
+ *
+ * Returns:	Zero if no match, nonzero if match.
+ *
+ * Use:		Wildcard-matches the pattern against the target string.
+ */
+
+static int class__wildMatch(const char *pat, const char *p)
+{
+  for (;;) {
+    if (*pat == 0 && *p == 0)
+      return (42); /* For sadism's sake */
+    else if (*pat == '*') {
+      while (*pat == '*')
+	pat++;
+      do {
+	if (class__wildMatch(pat, p))
+	  return (27); /* Nyahaha */
+	p++;
+      } while (*p);
+      return (0);
+    } else if (*pat == '?' || *pat == *p)
+      p++, pat++;
+    else
+      return (0);
+  }
+}
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @class_create@ --- *
+ *
+ * Arguments:	@unsigned type@ = type of the class to create
+ *		@sym_table *t@ = pointer to symbol table which stores info
+ *
+ * Returns:	Pointer to a @classdef@ which maintains the class's info.
+ *
+ * Use:		Creates a new class definition.  The new definition has one
+ *		reference attached to it (which is the one you get returned).
+ */
+
+classdef *class_create(unsigned type, sym_table *t)
+{
+  classdef *c = xmalloc(sizeof(*c));
+  c->type = type;
+  c->ref = 1;
+  c->t = t;
+  return (c);
+}
+
+/* --- @class_inc@ --- *
+ *
+ * Arguments:	@classdef *c@ = pointer to a class block
+ *
+ * Returns:	---
+ *
+ * Use:		Adds a reference to the class definition.
+ */
+
+void class_inc(classdef *c)
+{
+  if (c != class_all)
+    c->ref++;
+}
+
+/* --- @class_dec@ --- *
+ *
+ * Arguments:	@classdef *c@ = pointer to a class block
+ *
+ * Returns:	---
+ *
+ * Use:		Removes a reference to a class block.
+ */
+
+void class_dec(classdef *c)
+{
+  if (c != class_all && !--c->ref) {
+    sym_destroyTable(c->t);
+    free(c);
+  }
+}
+
+/* --- @class_userMatch@ --- *
+ *
+ * Arguments:	@classdef *c@ = pointer to a class block
+ *		@int u@ = uid number to check against
+ *
+ * Returns:	Zero if no match, nonzero if it matched.
+ *
+ * Use:		Looks to see if a user is in a group.
+ */
+
+int class_userMatch(classdef *c, int u)
+{
+  if (~c->type & clType_user)
+    return (0);
+  else if (c == class_all)
+    return (1);
+  else
+    return (sym_find(c->t, (char *)&u, sizeof(u), 0, 0) != 0);
+}
+
+/* --- @class_commandMatch@ --- *
+ *
+ * Arguments:	@classdef *c@ = pointer to a class block
+ *		@const char *p@ = pointer to command string to match
+ *
+ * Returns:	Zero for no match, nonzero if it matched.
+ *
+ * Use:		Tries to match a string against the wildcard patterns in the
+ *		given class.  Note that this involves examining all the
+ *		items, so it's not too quick.  Then again, you don't have
+ *		big command classes, do you...?
+ */
+
+int class_commandMatch(classdef *c, const char *p)
+{
+  sym_iter i;
+  sym_base *s;
+
+  if (~c->type & clType_command)
+    return (0);
+  else if (c == class_all)
+    return (1);
+  else {
+    for (sym_createIter(&i, c->t); (s = sym_next(&i)) != 0; ) {
+      if (class__wildMatch(s->name, p))
+	return (1);
+    }
+    return (0);
+  }
+}
+
+/* --- @class_hostMatch@ --- *
+ *
+ * Arguments:	@classdef *c@ = pointer to class block
+ *		@struct in_addr addr@ = IP address to match
+ *
+ * Returns:	Zero for no match, nonzero if it matched.
+ *
+ * Use:		Tries to match an IP address against the patterns and masks
+ *		in the given class.
+ */
+
+int class_hostMatch(classdef *c, struct in_addr addr)
+{
+  sym_iter i;
+  sym_base *s;
+  const char *a;
+  struct hostent *he;
+  char **p;
+
+  if (~c->type & clType_host)
+    return (0);
+  else if (c == class_all)
+    return (1);
+  else {
+
+    /* --- Get the dotted-quad and other names for the address --- */
+
+    if ((a = inet_ntoa(addr)) == 0)
+      return (0);
+    if ((he = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET)) == 0)
+      return (0);
+
+    /* --- Now search the list for a match --- *
+     *
+     * This is almost infinitely slow, I'm afraid.
+     */
+
+    for (sym_createIter(&i, c->t); (s = sym_next(&i)) != 0; ) {
+
+      /* --- Check the dotted-quad name first --- */
+
+      if (class__wildMatch(s->name, a))
+	return (1);
+
+      /* --- Now try the host's main name --- */
+
+      if (class__wildMatch(s->name, he->h_name))
+	return (1);
+
+      /* --- Now go through all the names --- */
+
+      for (p = he->h_aliases; *p; p++) {
+	if (class__wildMatch(s->name, *p))
+	  return (1);
+      }
+    }
+    return (0);
+  }
+}
+
+/* --- @class_dump@ --- *
+ *
+ * Arguments:	@classdef *c@ = pointer to a class block
+ *		@FILE *fp@ = pointer to a stream object
+ *
+ * Returns:	---
+ *
+ * Use:		Dumps the contents of a class to a stream.
+ */
+
+void class_dump(classdef *c, FILE *fp)
+{
+  sym_iter i;
+  sym_base *s;
+
+  /* --- Write a little header block --- */
+
+  {
+    const char *typetbl[] = {
+      "bad class",
+      "user",
+      "command",
+      "user/command",
+      "host",
+      "host/user",
+      "host/command",
+      "all",
+    };
+
+    fprintf(fp,
+	    "*** Type: %s\n"
+	    "*** Refs: %u\n"
+	    "*** \n"
+	    "*** Items:\n",
+	    c->type < clType__limit ? typetbl[c->type] : "bad class",
+	    c->ref);
+  }
+
+  /* --- Dump the table --- */
+
+  if (c->type != clType_all) {
+    for (sym_createIter(&i, c->t); (s = sym_next(&i)) != 0; ) {
+      switch (c->type) {
+	case clType_user:
+	  fprintf(fp, "***   %i\n", *(int *)s->name);
+	  break;
+	case clType_command:
+	case clType_host:
+	  fputs("***   ", fp);
+	  fwrite(s->name, s->len, 1, fp);
+	  putc('\n', fp);
+	  break;
+      }
+    }
+  }
+
+  putc('\n', fp);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/class.h b/src/class.h
new file mode 100644
index 0000000..cbe3929
--- /dev/null
+++ b/src/class.h
@@ -0,0 +1,175 @@
+/* -*-c-*-
+ *
+ * $Id: class.h,v 1.1 1997/07/21 13:47:52 mdw Exp $
+ *
+ * Handling classes of things nicely
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: class.h,v $
+ * Revision 1.1  1997/07/21 13:47:52  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef CLASS_H
+#define CLASS_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#ifndef SYM_H
+#  include "sym.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- Class types --- */
+
+enum {
+  clType_user = 1,			/* Class of users */
+  clType_command = 2,			/* Class of command strings */
+  clType_host = 4,			/* Class of host names */
+  clType_all = 7,			/* All of the above (maintain me) */
+  clType__limit				/* First undefined class type */
+};
+
+/* --- Class block --- */
+
+typedef struct classdef {
+  unsigned type;			/* Type of this class */
+  unsigned ref;				/* Reference count */
+  sym_table *t;				/* Symbol table for this class */
+} classdef;
+
+/*----- Global variables --------------------------------------------------*/
+
+extern classdef *class_all;		/* The match-everything class */
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @class_create@ --- *
+ *
+ * Arguments:	@unsigned type@ = type of the class to create
+ *		@sym_table *t@ = pointer to symbol table which stores info
+ *
+ * Returns:	Pointer to a @classdef@ which maintains the class's info.
+ *
+ * Use:		Creates a new class definition.  The new definition has one
+ *		reference attached to it (which is the one you get returned).
+ */
+
+extern classdef *class_create(unsigned /*type*/, sym_table */*t*/);
+
+/* --- @class_inc@ --- *
+ *
+ * Arguments:	@classdef *c@ = pointer to a class block
+ *
+ * Returns:	---
+ *
+ * Use:		Adds a reference to the class definition.
+ */
+
+extern void class_inc(classdef */*c*/);
+
+/* --- @class_dec@ --- *
+ *
+ * Arguments:	@classdef *c@ = pointer to a class block
+ *
+ * Returns:	---
+ *
+ * Use:		Removes a reference to a class block.
+ */
+
+extern void class_dec(classdef */*c*/);
+
+/* --- @class_userMatch@ --- *
+ *
+ * Arguments:	@classdef *c@ = pointer to a class block
+ *		@int u@ = uid number to check against
+ *
+ * Returns:	Zero if no match, nonzero if it matched.
+ *
+ * Use:		Looks to see if a user is in a group.
+ */
+
+extern int class_userMatch(classdef */*c*/, int /*u*/);
+
+/* --- @class_commandMatch@ --- *
+ *
+ * Arguments:	@classdef *c@ = pointer to a class block
+ *		@const char *p@ = pointer to command string to match
+ *
+ * Returns:	Zero for no match, nonzero if it matched.
+ *
+ * Use:		Tries to match a string against the wildcard patterns in the
+ *		given class.  Note that this involves examining all the
+ *		items, so it's not too quick.  Then again, you don't have
+ *		big command classes, do you...?
+ */
+
+extern int class_commandMatch(classdef */*c*/, const char */*p*/);
+
+/* --- @class_hostMatch@ --- *
+ *
+ * Arguments:	@classdef *c@ = pointer to class block
+ *		@struct in_addr addr@ = IP address to match
+ *
+ * Returns:	Zero for no match, nonzero if it matched.
+ *
+ * Use:		Tries to match an IP address against the patterns and masks
+ *		in the given class.
+ */
+
+extern int class_hostMatch(classdef */*c*/, struct in_addr /*addr*/);
+
+/* --- @class_dump@ --- *
+ *
+ * Arguments:	@classdef *c@ = pointer to a class block
+ *		@FILE *fp@ = pointer to a stream object
+ *
+ * Returns:	---
+ *
+ * Use:		Dumps the contents of a class to a stream.
+ */
+
+extern void class_dump(classdef */*c*/, FILE */*fp*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
+
diff --git a/src/crypt.c b/src/crypt.c
new file mode 100644
index 0000000..86ed605
--- /dev/null
+++ b/src/crypt.c
@@ -0,0 +1,566 @@
+/* -*-c-*-
+ *
+ * $Id: crypt.c,v 1.1 1997/07/21 13:47:51 mdw Exp $
+ *
+ * Cryptographic transfer of `become' requests
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: crypt.c,v $
+ * Revision 1.1  1997/07/21 13:47:51  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+/* --- Unix headers --- */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+
+/* --- Local headers --- */
+
+#include "config.h"
+#include "crypt.h"
+#include "icrypt.h"
+#include "idea.h"
+#include "md5.h"
+#include "tx.h"
+#include "utils.h"
+
+/*----- Magic numbers -----------------------------------------------------*/
+
+#define crypt__timeError 15		/* Seconds error to permit */
+#define crypt__seedBits 512		/* Number of random seed bits */
+
+/*----- Dump a block of data ----------------------------------------------*/
+
+/* --- @crypt__dump@ --- *
+ *
+ * Arguments:   @char *p@ = a string to display at the top of the message
+ *              @unsigned char *buf@ = pointer to a buffer to display
+ *              @size_t sz@ = size of the buffer
+ *		@FILE *fp@ = file to dump on
+ *
+ * Returns:     --
+ *
+ * Use:         Dumps a block of data to the terminal.  This allows a
+ *              programmer to examine the traffic between client and server,
+ *              and possibly locate bugs.
+ */
+
+#ifndef NDEBUG
+
+static void crypt__dump(const char *p, const unsigned char *buf,
+			int sz, FILE *fp)
+{
+  int i;
+  fprintf(stderr,"+++ %s\n",p);
+  while (sz>0)
+  {
+    fprintf(stderr,"+++ ");
+    for (i=0;i<8;i++)
+    {
+      if (i<sz)
+        fprintf(stderr,"%02x ",buf[i] & 0xff);
+      else
+        fputs("** ",stderr);
+    }
+    fputs(": ",stderr);
+    for (i=0;i<8;i++)
+      putc(i<sz ? (isprint(buf[i]) ? buf[i] : '.') : '*',stderr);
+    putc('\n',stderr);
+    buf+=8;
+    sz-=8;
+  }
+  putc('\n',stderr);
+}
+
+#else
+
+#define crypt__dump(p, buf, sz, fp) ((void)0)
+
+#endif
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @crypt__sessionKey@ --- *
+ *
+ * Arguments:	@const char *seedfile@ = pointer to name of seed file
+ *		@unsigned char *k@ = our secret key
+ *		@time_t t@ = the current time
+ *		@pid_t pid@ = our process id
+ *		@unsigned *sk@ = where to store the session key
+ *		@unsigned char *iv@ = where to store the IV
+ *
+ * Returns:	---
+ *
+ * Use:		Decides on a random session key and initialisation vector.
+ */
+
+static void crypt__sessionKey(const char *seedfile, unsigned char *k,
+			      time_t t, pid_t pid,
+			      unsigned char *sk, unsigned char *iv)
+{
+  FILE *fp;				/* File handle for reading */
+  unsigned char s[crypt__seedBits / 8];	/* Random seed block */
+  unsigned char b[4 + 4];
+					/* For building the hash block */
+
+  /* --- Read the old seed in --- *
+   *
+   * Interlock with other processes locking this file.
+   */
+
+  {
+#ifndef TEST_RIG
+    struct flock l;
+#endif
+
+    if ((fp = fopen(seedfile, "r+")) == 0)
+      die("couldn't open random seed file `%s': %s",
+	  seedfile, strerror(errno));
+
+#ifndef TEST_RIG
+    l.l_type = F_WRLCK;
+    l.l_whence = SEEK_SET;
+    l.l_start = 0;
+    l.l_len = 0;
+    if (fcntl(fileno(fp), F_SETLKW, &l) < 0)
+      die("couldn't lock random number file: %s", strerror(errno));
+#endif
+
+    tx_getBits(s, crypt__seedBits, fp);
+  }
+
+  crypt__dump("Initial seed", s, crypt__seedBits / 8, stdout);
+
+  /* --- Build a block of data to hash --- */
+
+  store32(b + 0, t);
+  store32(b + 4, pid);
+
+  /* --- Work out the session key --- */
+
+  {
+    md5 md;
+
+    md5_init(&md);
+    md5_buffer(&md, s, sizeof(s));
+    md5_buffer(&md, b, sizeof(b));
+    md5_buffer(&md, k, IDEA_KEYSIZE);
+    md5_final(&md, sk);
+
+    crypt__dump("Session key", sk, 128 / 8, stdout);
+    burn(md);
+  }
+
+  /* --- Build the new seed and write it back again --- */
+
+  if (crypt__seedBits > 128) {
+    unsigned char tb[MD5_HASHSIZE];
+    int i;
+
+    memcpy(tb, s + crypt__seedBits / 8 - sizeof(tb), sizeof(tb));
+    memmove(s + sizeof(tb), s, crypt__seedBits / 8 - sizeof(tb));
+    memcpy(s, tb, sizeof(tb));
+    for (i = 0; i < sizeof(sk); i++)
+      s[i] ^= sk[i];
+    burn(tb);
+  }
+
+  /* --- Take the seed we have and hash it again to get an IV --- */
+
+  {
+    unsigned char mdv[MD5_HASHSIZE];
+    md5 md;
+
+    md5_init(&md);
+    md5_buffer(&md, b, sizeof(b));
+    md5_buffer(&md, k, IDEA_KEYSIZE);
+    md5_buffer(&md, s, sizeof(s));
+    md5_final(&md, mdv);
+    memcpy(iv, mdv, IDEA_BLKSIZE);
+    crypt__dump("IV", iv, IDEA_BLKSIZE, stdout);
+    burn(md); burn(mdv);
+  }
+
+
+  /* --- Lock the file again --- *
+   *
+   * We're closing the file after we've finished, so we don't need to
+   * unlock it afterwards.
+   */
+
+  crypt__dump("Final seed", s, crypt__seedBits / 8, stdout);
+
+  rewind(fp);
+  tx_putBits(s, crypt__seedBits, fp);
+  fclose(fp);
+
+  /* --- Destroy sensitive data --- */
+
+  burn(b); burn(s);
+}
+
+/* --- @crypt_packRequest@ --- *
+ *
+ * Arguments:	@request *rq@ = pointer to request block
+ *		@unsigned char *buff@ = pointer to a buffer
+ *		@time_t t@ = the current time
+ *		@pid_t pid@ = my process ID
+ *		@unsigned char *k@ = pointer to 128-bit key
+ *		@unsigned char *sk@ = where to put the session key
+ *
+ * Returns:	---
+ *
+ * Use:		Packs a request block into a buffer.  The buffer should have
+ *		space for at least @crq_size@ bytes.  The buffer comes back
+ *		encrypted and ready to send.
+ */
+
+void crypt_packRequest(request *rq, unsigned char *buff,
+		       time_t t, pid_t pid,
+		       unsigned char *k, unsigned char *sk)
+{
+  /* --- First, build the easy stuff in the block --- */
+
+  buff[crq_cryptType] = cryptType_idea;
+  store32(buff + crq_time, t);
+  store32(buff + crq_pid, pid);
+  store32(buff + crq_from, rq->from);
+  store32(buff + crq_to, rq->to);
+
+  /* --- The string causes a few problems --- *
+   *
+   * There's a good chance that the string will be a good deal shorter than
+   * the space allowed for it.  This will probably mean lots of zeroes, and a
+   * very easy known-plaintext job for a potential attacker.  (An early
+   * version of this code used @strncpy@ which is even worse!)
+   *
+   * I'll fill the block with random (from @rand@(3) -- nothing too
+   * elaborate) and then encrypt it using IDEA in CFB mode, using the first
+   * few bytes as the key.  This should provide a sufficiently unpredictable
+   * background for the block.
+   */
+
+  {
+    icrypt_job j;
+    unsigned char *p;
+    unsigned u;
+
+    /* --- Initialise the buffer with junk --- */
+
+    srand((unsigned int)(t ^ pid));	/* Seed the (bad) RNG */
+    for (p = buff + crq_cmd; p < buff + crq_cmd + CMDLEN_MAX; p++) {
+      u = rand();
+      *p = u ^ (u >> 8);
+    }
+
+    /* --- Now make the junk a whole lot harder to predict --- */
+
+    p = buff + crq_cmd;
+    icrypt_init(&j, p, 0);
+    icrypt_encrypt(&j, p, p, CMDLEN_MAX);
+    burn(j);
+
+    /* --- Copy the string into here --- */
+
+    strcpy((char *)buff + crq_cmd, rq->cmd);
+  }
+
+  /* --- Generate a session key --- */
+
+  {
+    crypt__sessionKey(file_RANDSEED, k, t, pid, sk, buff + crq_iv);
+    memcpy(buff + crq_session, sk, IDEA_KEYSIZE);
+  }
+
+  /* --- Checksum the finished data --- */
+
+  {
+    md5 md;
+    unsigned char mdv[MD5_HASHSIZE];
+
+    md5_init(&md);
+    md5_buffer(&md, buff + crq_cipher, crq_check - crq_cipher);
+    md5_final(&md, mdv);
+    memcpy(buff + crq_check, mdv, 4);
+    burn(md); burn(mdv);
+  }
+
+  /* --- Encrypt the block --- *
+   *
+   * First, encrypt the session key using the master key.  Since the session
+   * key is effectively random, this makes cracking the master key much
+   * harder.  The rest of the block is then encrypted with the session key,
+   * using the IV left over from encrypting the session key.
+   */
+
+  {
+    icrypt_job j;
+
+    crypt__dump("request, before encryption", buff, crq_size, stdout);
+
+    icrypt_init(&j, k, buff + crq_iv);
+    icrypt_encrypt(&j, buff + crq_session, buff + crq_session, IDEA_KEYSIZE);
+    icrypt_reset(&j, sk, 0);
+    icrypt_encrypt(&j, buff + crq_cipher,
+		   buff + crq_cipher, crq_size - crq_cipher);
+    burn(j);
+
+    crypt__dump("request, after encryption", buff, crq_size, stdout);
+  }
+}
+
+/* --- @crypt_unpackRequest@ --- *
+ *
+ * Arguments:	@reqest *rq@ = pointer to destination request block
+ *		@unsigned char *buff@ = pointer to source buffer
+ *		@unsigned char *k@ = pointer to encryption key
+ *		@unsigned char *sk@ = pointer to where to store session key
+ *		@unsigned char *rpl@ = where to start building reply
+ *
+ * Returns:	Nonzero if it was decrypted OK
+ *
+ * Use:		Decrypts and unpacks a request buffer.
+ */
+
+int crypt_unpackRequest(request *rq, unsigned char *buff,
+			unsigned char *k, unsigned char *sk,
+			unsigned char *rpl)
+{
+  {
+    /* --- Check the encryption format --- */
+
+    if (buff[crq_cryptType] != cryptType_idea)
+      return (0);
+  }
+
+  {
+    /* --- First things first: decrypt the block --- */
+
+    icrypt_job j;
+
+    crypt__dump("request, before decryption", buff, crq_size, stdout);
+
+    icrypt_init(&j, k, buff + crq_iv);
+    icrypt_decrypt(&j, buff + crq_session, buff + crq_session, IDEA_KEYSIZE);
+    memcpy(sk, buff + crq_session, IDEA_KEYSIZE);
+    icrypt_reset(&j, sk, 0);
+    icrypt_decrypt(&j, buff + crq_cipher,
+		   buff + crq_cipher, crq_size - crq_cipher);
+    icrypt_saveIV(&j, rpl + crp_iv);
+
+    memset(buff + crq_session, 0, IDEA_KEYSIZE); /* Burn, baby, burn */
+    burn(j);
+
+    crypt__dump("request, after decryption", buff, crq_size, stdout);
+  }
+
+  {
+    /* --- Check the validity of the data therein --- */
+
+    md5 md;
+    unsigned char mdv[MD5_HASHSIZE];
+
+    md5_init(&md);
+    md5_buffer(&md, buff + crq_cipher, crq_check - crq_cipher);
+    md5_final(&md, mdv);
+    if (memcmp(mdv, buff + crq_check, 4) != 0) {
+      syslog(LOG_INFO, "packet rejected: bad checksum");
+      return (0);
+    }
+    burn(md); burn(mdv);
+  }
+
+  {
+    /* --- Extract fields from the block --- */
+
+    rq->from = load32(buff + crq_from);
+    rq->to = load32(buff + crq_to);
+    memcpy(rq->cmd, buff + crq_cmd, CMDLEN_MAX);
+  }
+
+  {
+    /* --- Fill in bits of the reply block --- */
+
+    long t = (long)time(0);
+    long u = (long)load32(buff + crq_time);
+
+    if (t - u > crypt__timeError || u - t > crypt__timeError) {
+      syslog(LOG_INFO, "packet rejected: bad time");
+      return (0);
+    }
+    memcpy(rpl + crp_time, buff + crq_time, 8);
+  }
+
+  /* --- Done --- */
+
+  return (1);
+}
+
+/* --- @crypt_packReply@ --- *
+ *
+ * Arguments:	@char *buff@ = pointer to reply block
+ *		@unsigned char *sk@ = pointer to session key
+ *		@int answer@ = yes or no
+ *
+ * Returns:	---
+ *
+ * Use:		Packs and encrypts a reply block.
+ */
+
+void crypt_packReply(unsigned char *buff, unsigned char *sk, int answer)
+{
+  {
+    /* --- Store the answer --- */
+
+    buff[crp_answer] = (answer != 0);
+  }
+
+  {
+    /* --- Build the checksum --- */
+
+    md5 md;
+    unsigned char mdv[MD5_HASHSIZE];
+
+    md5_init(&md);
+    md5_buffer(&md, buff + crp_cipher, crp_check - crp_cipher);
+    md5_final(&md, mdv);
+    memcpy(buff + crp_check, mdv, 4);
+    burn(md); burn(mdv);
+  }
+
+  {
+    /* --- Encrypt the buffer --- */
+
+    icrypt_job j;
+    icrypt_init(&j, sk, buff + crp_iv);
+    crypt__dump("reply, before encryption", buff, crp_size, stdout);
+    icrypt_encrypt(&j, buff + crp_cipher,
+		   buff + crp_cipher, crp_size - crp_cipher);
+    crypt__dump("reply, after encryption", buff, crp_size, stdout);
+    burn(j);
+  }
+}
+
+/* --- @crypt_unpackReply@ --- *
+ *
+ * Arguments:	@unsigned char *buff@ = pointer to reply buffer
+ *		@unsigned char *sk@ = pointer to session key
+ *		@time_t t@ = time at which request was sent
+ *		@pid_t pid@ = my process ID
+ *
+ * Returns:	>0 if request granted, zero if denied, <0 if reply rejected
+ *
+ * Use:		Unpacks a reply block, and informs the caller of the outcome.
+ */
+
+int crypt_unpackReply(unsigned char *buff, unsigned char *sk,
+		      time_t t, pid_t pid)
+{
+  {
+    /* --- Decrypt my reply block --- */
+
+    icrypt_job j;
+    icrypt_init(&j, sk, buff + crp_iv);
+    crypt__dump("reply, before decryption", buff, crp_size, stdout);
+    icrypt_decrypt(&j, buff + crp_cipher,
+		   buff + crp_cipher, crp_size - crp_cipher);
+    crypt__dump("reply, after decryption", buff, crp_size, stdout);
+    burn(j);
+  }
+
+  {
+    /* --- Check validity --- */
+
+    md5 md;
+    unsigned char mdv[MD5_HASHSIZE];
+    char b[8];
+
+    /* --- Check the checksum --- */
+
+    md5_init(&md);
+    md5_buffer(&md, buff + crp_cipher, crp_check - crp_cipher);
+    md5_final(&md, mdv);
+    if (memcmp(buff + crp_check, mdv, 4) != 0) {
+      syslog(LOG_INFO, "reply rejected: bad checksum");
+      return (-1);
+    }
+
+    /* --- Check the identifier --- */
+
+    store32(b + 0, t); store32(b + 4, pid);
+    if (memcmp(b, buff + crp_time, sizeof(b)) != 0) {
+      syslog(LOG_INFO, "reply rejected: bad identification marker");
+      return (-1);
+    }
+  }
+
+  /* --- Return the value --- */
+
+  return (buff[crp_answer]);
+}
+
+/*----- Test rig ----------------------------------------------------------*/
+
+#ifdef TEST_RIG
+
+int main(int argc, char *argv[])
+{
+  time_t t = time(0);
+  pid_t pid = getpid();
+  unsigned char buff[8];
+  unsigned char sk[IDEA_KEYSIZE], k[IDEA_KEYSIZE];
+  FILE *fp;
+
+  ego(argv[0]);
+  if (argc < 3)
+    die("bad args");
+  fp = fopen(argv[1], "r");
+  if (!fp)
+    die("fopen: %s", strerror(errno));
+  tx_getBits(k, 128, fp);
+  fclose(fp);
+  crypt__sessionKey(argv[2], k, t, pid, sk, buff);
+  return (0);
+}
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/crypt.h b/src/crypt.h
new file mode 100644
index 0000000..b508b45
--- /dev/null
+++ b/src/crypt.h
@@ -0,0 +1,181 @@
+/* -*-c-*-
+ *
+ * $Id: crypt.h,v 1.1 1997/07/21 13:47:51 mdw Exp $
+ *
+ * Cryptographic transfer of `become' requests
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: crypt.h,v $
+ * Revision 1.1  1997/07/21 13:47:51  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef CRYPT_H
+#define CRYPT_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#include <string.h>
+
+#ifndef BECOME_H
+#  include "become.h"
+#endif
+
+#ifndef CONFIG_H
+#  include "config.h"
+#endif
+
+/*----- Type definitions and data structures ------------------------------*/
+
+/* --- Encryption formats --- */
+
+enum {
+  cryptType_idea,			/* Symmetric IDEA encryption */
+  cryptType_rsa				/* Public key RSA (later project) */
+};
+
+/* --- Encrypted buffer format --- *
+ *
+ * C structures are no good here.  Time for some explicit offsets.
+ */
+
+enum {
+  crq_cryptType = 0,			/* Encryption type (1 byte) */
+  crq_iv = crq_cryptType + 1,		/* Plaintext IV (8 bytes) */
+  crq_session = crq_iv + 8,		/* IDEA session key (16 bytes) */
+  crq_cipher = crq_session + 16,	/* Where to start encrypting */
+  crq_time = crq_cipher,		/* Time stamp (4 bytes) */
+  crq_pid = crq_time + 4,		/* Process ID (4 bytes) */
+  crq_from = crq_pid + 4,		/* From user id (4 bytes) */
+  crq_to = crq_from + 4,		/* To user id (4 bytes) */
+  crq_cmd = crq_to + 4,			/* Command string (lots of bytes) */
+  crq_check = crq_cmd + CMDLEN_MAX,	/* Checksum for request (4 bytes) */
+  crq_size = crq_check + 4		/* Size of encrypted request */
+};
+
+/* --- Encrypted result format --- */
+
+enum {
+  crp_iv = 0,				/* Plaintext IV (8 bytes) */
+  crp_cipher = crp_iv + 8,		/* Where to start encrypting */
+  crp_time = crp_cipher,		/* Time of request (4 bytes) */
+  crp_pid = crp_time + 4,		/* Process ID of client (4 bytes) */
+  crp_answer = crp_pid + 4,		/* Answer (1 or 0) (1 byte) */
+  crp_check = crp_answer + 1,		/* Checksum for reply (4 bytes) */
+  crp_size = crp_check + 4		/* Size of encrypted reply */
+};
+
+/*----- Macros ------------------------------------------------------------*/
+
+/* --- @burn@ --- *
+ *
+ * Arguments:	@obj@ = some object
+ *
+ * Use:		Writes zero bytes over the object.
+ */
+
+#define burn(obj) ((void)memset(&obj, 0, sizeof(obj)))
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @crypt_packRequest@ --- *
+ *
+ * Arguments:	@request *rq@ = pointer to request block
+ *		@unsigned char *buff@ = pointer to a buffer
+ *		@time_t t@ = the current time
+ *		@pid_t pid@ = my process ID
+ *		@unsigned char *k@ = pointer to 128-bit key
+ *		@unsigned char *sk@ = where to put the session key
+ *
+ * Returns:	The number of bytes written.
+ *
+ * Use:		Packs a request block into a buffer.  The buffer should have
+ *		space for at least @crq_size@ bytes.  The buffer comes back
+ *		encrypted and ready to send.
+ */
+
+extern void crypt_packRequest(request */*rq*/, unsigned char */*buff*/,
+			      time_t /*t*/, pid_t /*pid*/,
+			      unsigned char */*k*/, unsigned char */*sk*/);
+
+/* --- @crypt_unpackRequest@ --- *
+ *
+ * Arguments:	@reqest *rq@ = pointer to destination request block
+ *		@unsigned char *buff@ = pointer to source buffer
+ *		@unsigned char *k@ = pointer to encryption key
+ *		@unsigned char *sk@ = pointer to where to store session key
+ *		@unsigned char *rpl@ = where to start building reply
+ *
+ * Returns:	---
+ *
+ * Use:		Decrypts and unpacks a request buffer.
+ */
+
+extern int crypt_unpackRequest(request */*rq*/, unsigned char */*buff*/,
+			       unsigned char */*k*/, unsigned char */*sk*/,
+			       unsigned char */*rpl*/);
+
+/* --- @crypt_packReply@ --- *
+ *
+ * Arguments:	@unsigned char *buff@ = pointer to reply block
+ *		@unsigned char *sk@ = pointer to session key
+ *		@int answer@ = yes or no
+ *
+ * Returns:	---
+ *
+ * Use:		Packs and encrypts a reply block.
+ */
+
+extern void crypt_packReply(unsigned char */*buff*/, unsigned char */*sk*/,
+			    int /*answer*/);
+
+/* --- @crypt_unpackReply@ --- *
+ *
+ * Arguments:	@unsigned char *buff@ = pointer to reply buffer
+ *		@unsigned char *sk@ = pointer to session key
+ *		@time_t t@ = time at which request was sent
+ *		@pid_t pid@ = my process ID
+ *
+ * Returns:	>0 if request granted, zero if denied, <0 if reply rejected
+ *
+ * Use:		Unpacks a reply block, and informs the caller of the outcome.
+ */
+
+extern int crypt_unpackReply(unsigned char */*buff*/, unsigned char */*sk*/,
+			     time_t /*t*/, pid_t /*pid*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/src/daemon.c b/src/daemon.c
new file mode 100644
index 0000000..eb36b39
--- /dev/null
+++ b/src/daemon.c
@@ -0,0 +1,419 @@
+/* -*-c-*-
+ *
+ * $Id: daemon.c,v 1.1 1997/07/21 13:47:50 mdw Exp $
+ *
+ * Running a `become' daemon
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: daemon.c,v $
+ * Revision 1.1  1997/07/21 13:47:50  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <errno.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Unix headers --- */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+
+#include <netdb.h>
+#include <syslog.h>
+#include <unistd.h>
+
+/* --- Local headers --- */
+
+#include "become.h"
+#include "config.h"
+#include "crypt.h"
+#include "daemon.h"
+#include "idea.h"
+#include "lexer.h"
+#include "name.h"
+#include "parser.h"
+#include "rule.h"
+#include "tx.h"
+#include "userdb.h"
+#include "utils.h"
+
+/*----- Arbitrary constants -----------------------------------------------*/
+
+#define daemon__awakeEvery (5 * 60)	/* Awaken this often to rescan */
+
+/*----- Static variables --------------------------------------------------*/
+
+static int daemon__running = 0;		/* Am I running as a daemon? */
+static int daemon__port = -1;		/* No particular port yet */
+static volatile sig_atomic_t daemon__rescan = 0; /* Rescan as soon as poss */
+#define daemon__signum daemon__rescan	/* Alias for readbility */
+static int daemon__readKey = 0;		/* Have I read a key? */
+static unsigned char daemon__key[IDEA_KEYSIZE];	/* encryption key */
+static jmp_buf daemon__dieBuf;		/* Jump here to kill the daemon */
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @daemon_usePort@ --- *
+ *
+ * Arguments:	@int port@ = port to use, please
+ *
+ * Returns:	---
+ *
+ * Use:		Instructs the daemon to listen to the given port.
+ */
+
+void daemon_usePort(int port)
+{
+  daemon__port = port;
+}
+
+/* --- @daemon_readKey@ --- *
+ *
+ * Arguments:	@const char *kf@ = name of file containing key
+ *
+ * Returns:	---
+ *
+ * Use:		Instructs the daemon to read the named key file.
+ */
+
+void daemon_readKey(const char *kf)
+{
+  FILE *fp;
+
+  if (!daemon__running)
+    return;
+
+  if ((fp = fopen(kf, "r")) == 0) {
+    syslog(LOG_WARNING, "couldn't read key file: %e");
+    return;
+  }
+  tx_getBits(daemon__key, 128, fp);
+  fclose(fp);
+  daemon__readKey = 1;
+  return;
+}
+
+/* --- @daemon__readConfig@ --- *
+ *
+ * Arguments:	@const char *cf@ = pointer to configuration file to use
+ *
+ * Returns:	Zero if it worked, nonzero if it hurt...
+ *
+ * Use:		Reads the configuration file, and other things.
+ */
+
+static int daemon__readConfig(const char *cf)
+{
+  FILE *fp;
+
+  daemon__readKey = 0;
+  if ((fp = fopen(cf, "r")) == 0)
+    return (-1);
+  lexer_scan(fp);
+  yyparse();
+  fclose(fp);
+  if (!daemon__readKey)
+    daemon_readKey(file_KEY);
+  daemon__rescan = 0;
+  return (0);
+}
+
+/* --- @daemon__restart@ --- *
+ *
+ * Arguments:	@int sig@ = the signal number
+ *
+ * Returns:	---
+ *
+ * Use:		Handles signals.  Causes the configuration file to be reread.
+ */
+
+static void daemon__restart(int sig)
+{
+  daemon__rescan = 1;
+  signal(sig, daemon__restart);
+}
+
+/* --- @daemon__die@ --- *
+ *
+ * Arguments:	@int sig@ = the signal number
+ *
+ * Returns:	via @longjmp@
+ *
+ * Use:		Handles other signals.  Causes the daemon to die.
+ */
+
+static void daemon__die(int sig)
+{
+  daemon__signum = sig;
+  longjmp(daemon__dieBuf, 1);
+}
+
+/* --- @daemon__read@ --- *
+ *
+ * Arguments:	@int fd@ = socket handle
+ *
+ * Returns:	---
+ *
+ * Use:		Examines a buffer, and returns a response.
+ */
+
+void daemon__read(int fd)
+{
+  unsigned char buff[65536];		/* Buffer for incoming packets */
+  unsigned char rpl[crp_size];		/* Buffer for outgoing replies */
+  struct sockaddr_in sin;		/* Address of packet sender */
+  char sender[64];			/* Sender's hostname (resolved) */
+  unsigned char sk[IDEA_KEYSIZE];	/* Session key for reply */
+  request rq;				/* Request buffer for verification */
+
+  /* --- Read the message --- */
+
+  {
+    int slen = sizeof(sin);
+
+    if (recvfrom(fd, (char *)buff, sizeof(buff), 0,
+		 (struct sockaddr *)&sin, &slen) < 0) {
+      syslog(LOG_INFO, "duff packet received: %e");
+      return;
+    }
+  }
+
+  /* --- Resolve the host name --- */
+
+  {
+    struct hostent *he = gethostbyaddr((char *)&sin.sin_addr,
+				       sizeof(sin.sin_addr),
+				       AF_INET);
+    sender[0] = 0;
+    strncat(sender,
+	    he ? he->h_name : inet_ntoa(sin.sin_addr),
+	    sizeof(sender));
+    syslog(LOG_DEBUG, "packet received from %s", sender);
+  }
+
+  /* --- Unpack the block --- */
+
+  if (crypt_unpackRequest(&rq, buff, daemon__key, sk, rpl) == 0) {
+    burn(buff);
+    syslog(LOG_INFO, "packet from %s rejected", sender);
+    return;
+  }
+  burn(buff);
+
+  /* --- Fill in the sender's address in the request block --- */
+
+  rq.host = sin.sin_addr;
+
+  /* --- Build a reply block --- */
+
+  {
+    int answer = rule_check(&rq);
+    syslog(LOG_INFO, "request from %s for %i to become %i to run %s %s",
+	   sender, rq.from, rq.to, rq.cmd, answer ? "granted" : "denied");
+    crypt_packReply(rpl, sk, answer);
+    burn(sk);
+  }
+
+  /* --- Send the reply off --- */
+
+  sendto(fd, (char *)rpl, crp_size, 0, (struct sockaddr *)&sin, sizeof(sin));
+  burn(rpl);
+}
+
+/* --- @daemon_init@ --- *
+ *
+ * Arguments:	@const char *cf@ = pointer to name of configuration file
+ *		@int port@ = port to listen to, or %$-1$% for default
+ *
+ * Returns:	Never.
+ *
+ * Use:		Starts `become' up in daemon mode.
+ */
+
+void daemon_init(const char *cf, int port)
+{
+  int s;
+
+  /* --- Remove my root privileges --- *
+   *
+   * Just in case there's anything dodgy in my configuration file, or the
+   * user wants me to start on a funny port.
+   */
+
+  seteuid(getuid());
+
+  /* --- Initialise bits of the program --- */
+
+  daemon__running = 1;
+  daemon__port = port;
+  userdb_init();
+  userdb_local();
+  userdb_yp();
+  name_init();
+  rule_init();
+  openlog(quis(), 0, LOG_DAEMON);
+  syslog(LOG_NOTICE, "starting up");
+
+  if (daemon__readConfig(cf))
+    die("couldn't read configuration file");
+
+  /* --- Decide on a port to use --- *
+   *
+   * If I don't have a port yet (e.g., from the configuration file) then
+   * look it up in /etc/services under whatever name I was started as.
+   */
+
+  if (daemon__port <= 0) {
+    struct servent *se = getservbyname(quis(), "udp");
+    if (!se)
+      die("no idea which port to use");
+    daemon__port = ntohs(se->s_port);
+  }
+
+  /* --- Now set up a socket --- */
+
+  {
+    struct sockaddr_in sin;
+
+    if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
+      die("couldn't create socket: %s", strerror(errno));
+    sin.sin_family = AF_INET;
+    sin.sin_port = htons(daemon__port);
+    sin.sin_addr.s_addr = htonl(INADDR_ANY);
+    if (bind(s, (struct sockaddr *)&sin, sizeof(sin)))
+      die("couldn't bind socket to port: %s", strerror(errno));
+  }
+
+  /* --- Fork off into the sunset --- */
+
+#ifdef NDEBUG
+  {
+    int pid = fork();
+    FILE *fp;
+
+    /* --- Make a background process --- */
+
+    if (pid == -1)
+      die("couldn't fork daemon: %s", strerror(errno));
+    else if (pid != 0)
+      return;
+
+    /* --- Disconnect from the terminal --- */
+
+    setsid();
+
+    /* --- Write my process id to a file --- */
+
+    if ((fp = fopen(file_PID, "w")) != 0) {
+      fprintf(fp, "%lu\n", (unsigned long)getpid());
+      fclose(fp);
+    }
+  }
+#endif
+
+  /* --- Program in daemon death mode --- */
+
+  if (setjmp(daemon__dieBuf)) {
+    syslog(LOG_NOTICE, "killed by signal type %i", daemon__signum);
+    remove(file_PID);
+    exit(0);
+  }
+
+  /* --- Set signal handlers --- */
+
+  signal(SIGHUP, daemon__restart);
+  signal(SIGQUIT, daemon__restart);
+  signal(SIGINT, daemon__die);
+  signal(SIGTERM, daemon__die);
+  signal(SIGSEGV, daemon__die);
+  signal(SIGFPE, daemon__die);
+  signal(SIGBUS, daemon__die);
+
+  /* --- Now wait for something exciting to happen --- *
+   *
+   * Actually, every so often (5 minutes, perhaps) I need to wake up and
+   * rescan the users to see whether they've changed.  Time to play with
+   * @select@.
+   */
+
+  {
+    time_t when;
+
+    /* --- Find when I am, and thus when I need to be awoken again --- */
+
+    when = time(0) + daemon__awakeEvery;
+
+    for (;;) {
+      fd_set fds;
+      int i;
+
+      /* --- Set up the file descriptor tables --- */
+
+      FD_ZERO(&fds);
+      FD_SET(s, &fds);
+
+      /* --- Now wait for something interesting --- */
+
+      i = select(FD_SETSIZE, &fds, 0, 0, 0);
+
+      /* --- Now, see if I need to rescan the config --- */
+
+      if (daemon__rescan || time(0) - when > 0) {
+	daemon__rescan = 0;
+	syslog(LOG_INFO, "rescanning configuration file");
+	userdb_reinit();
+	userdb_local();
+	userdb_yp();
+	rule_reinit();
+	name_reinit();
+	if (daemon__readConfig(cf))
+	  syslog(LOG_ERR, "error reading configuration file");
+	when = time(0) + daemon__awakeEvery;
+      }
+
+      /* --- Read the data from the request --- */
+
+      if (i > 0)
+	daemon__read(s);
+    }
+  }
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/daemon.h b/src/daemon.h
new file mode 100644
index 0000000..ed3c05c
--- /dev/null
+++ b/src/daemon.h
@@ -0,0 +1,86 @@
+/* -*-c-*-
+ *
+ * $Id: daemon.h,v 1.1 1997/07/21 13:47:50 mdw Exp $
+ *
+ * Running a `become' daemon
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: daemon.h,v $
+ * Revision 1.1  1997/07/21 13:47:50  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef DAEMON_H
+#define DAEMON_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @daemon_usePort@ --- *
+ *
+ * Arguments:	@int port@ = port to use, please
+ *
+ * Returns:	---
+ *
+ * Use:		Instructs the daemon to listen to the given port.
+ */
+
+extern void daemon_usePort(int /*port*/);
+
+/* --- @daemon_readKey@ --- *
+ *
+ * Arguments:	@const char *kf@ = name of file containing key
+ *
+ * Returns:	---
+ *
+ * Use:		Instructs the daemon to read the named key file.
+ */
+
+extern void daemon_readKey(const char */*kf*/);
+
+/* --- @daemon_init@ --- *
+ *
+ * Arguments:	@const char *cf@ = pointer to name of configuration file
+ *		@int port@ = port to listen to, or %$-1$% for default
+ *
+ * Returns:	Never.
+ *
+ * Use:		Starts `become' up in daemon mode.
+ */
+
+extern void daemon_init(const char */*cf*/, int /*port*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/src/dbutils.h b/src/dbutils.h
new file mode 100644
index 0000000..70f24ee
--- /dev/null
+++ b/src/dbutils.h
@@ -0,0 +1,69 @@
+/* -*-c-*-
+ *
+ * $Id: dbutils.h,v 1.1 1997/07/21 13:47:50 mdw Exp $
+ *
+ * Debugging things
+ *
+ * (c) 1996 Straylight
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: dbutils.h,v $
+ * Revision 1.1  1997/07/21 13:47:50  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef DBUTILS_H
+#define DBUTILS_H
+
+/* --- Note --- *
+ *
+ * This is most certainly not portable.  I'm using VT100-specific control
+ * codes to clear the screen and @select@ to pause for a bit between
+ * allocations to stop the scree flicker driving me up the wall.
+ *
+ * The main routine just allocates and frees blocks randomly, and displays
+ */
+
+#if defined(__unix)
+#  include <sys/time.h>
+#  include <unistd.h>
+#  define cls fputs("\33[H\33[2J", stderr) /* VT100 specific */
+#  define pause do {							\
+     struct timeval tv = { 0, 10000 };					\
+     select(0, 0, 0, 0, &tv);						\
+   } while (0)
+#elif defined(__riscos)
+#  include "swiv.h"
+#  include "swis.h"
+#  define cls _swi(OS_WriteC, _in(0), 12)
+#  define pause _swi(OS_Byte, _in(0), 19)
+#else
+#  define cls				/* Tough.  Write these yourself. */
+#  define pause
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#endif
diff --git a/src/icrypt.c b/src/icrypt.c
new file mode 100644
index 0000000..828ce1e
--- /dev/null
+++ b/src/icrypt.c
@@ -0,0 +1,317 @@
+/* -*-c-*-
+ *
+ * $Id: icrypt.c,v 1.1 1997/07/21 13:47:49 mdw Exp $
+ *
+ * Higher level IDEA encryption
+ *
+ * (c) 1997 Mark Wooding
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: icrypt.c,v $
+ * Revision 1.1  1997/07/21 13:47:49  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* --- Local headers --- */
+
+#include "config.h"
+#include "icrypt.h"
+#include "idea.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @icrypt_init@ --- *
+ *
+ * Arguments:	@icrypt_job *j@ = pointer to job context block
+ *		@unsigned char *k@ = pointer to key data
+ *		@const unsigned char *iv@ = pointer to IV
+ *
+ * Returns:	---
+ *
+ * Use:		Primes the context block ready for encryption.
+ */
+
+void icrypt_init(icrypt_job *j, unsigned char *k, const unsigned char *iv)
+{
+  idea_ekeys(&j->k, k);
+  if (iv)
+    memcpy(j->iv, iv, IDEA_BLKSIZE);
+  else
+    memset(j->iv, 0, IDEA_BLKSIZE);
+  j->i = 8;
+}
+
+/* --- @icrypt_encrypt@ --- *
+ *
+ * Arguments:	@icrypt_job *j@ = job handle
+ *		@const void *src@ = pointer to source buffer
+ *		@void *dest@ = pointer to destination buffer
+ *		@size_t sz@ = size of buffers to handle
+ *
+ * Returns:	---
+ *
+ * Use:		Encrypts data from the source to the destination, using the
+ *		key attached to the job handle.
+ */
+
+void icrypt_encrypt(icrypt_job *j, const void *src, void *dest, size_t sz)
+{
+  const char *s = src;
+  char *d = dest;
+  int i;
+
+  /* --- First, use up bytes in the buffer --- */
+
+  while (j->i < IDEA_BLKSIZE && sz > 0) {
+    *d++ = j->iv[j->i++] ^= *s++;
+    sz--;
+  }
+  if (!sz) return;
+
+  /* --- Now encrypt larger chunks at a time --- */
+
+  while (sz >= IDEA_BLKSIZE) {
+
+    /* --- Freshen the IV --- */
+
+    idea_encrypt(&j->k, j->iv, j->iv);
+
+    /* --- Now encrypt some more bytes --- */
+
+    for (i = 0; i < IDEA_BLKSIZE; i++) {
+      *d++ = j->iv[i] ^= *s++;
+    }
+    sz -= IDEA_BLKSIZE;
+  }
+  if (!sz) return;
+
+  /* --- Do the tail-end bits --- */
+
+  idea_encrypt(&j->k, j->iv, j->iv);
+  j->i = 0;
+  while (sz) {
+    *d++ = j->iv[j->i++] ^= *s++;
+    sz--;
+  }
+}
+
+/* --- @icrypt_decrypt@ --- *
+ *
+ * Arguments:	@icrypt_job *j@ = job handle
+ *		@const void *src@ = pointer to source buffer
+ *		@void *dest@ = pointer to destination buffer
+ *		@size_t sz@ = size of buffers to handle
+ *
+ * Returns:	---
+ *
+ * Use:		Decrypts data from the source to the destination, using
+ *		the key attached to the job handle.
+ */
+
+void icrypt_decrypt(icrypt_job *j, const void *src, void *dest, size_t sz)
+{
+  const char *s = src;
+  char *d = dest;
+  int i;
+  char c;
+
+  /* --- First, use up bytes in the buffer --- */
+
+  while (j->i < IDEA_BLKSIZE && sz > 0) {
+    c = *s++;
+    *d++ = j->iv[j->i] ^ c;
+    j->iv[j->i++] = c;
+    sz--;
+  }
+  if (!sz) return;
+
+  /* --- Now encrypt larger chunks at a time --- */
+
+  while (sz >= IDEA_BLKSIZE) {
+
+    /* --- Freshen the IV --- */
+
+    idea_encrypt(&j->k, j->iv, j->iv);
+
+    /* --- Now encrypt some more bytes --- */
+
+    for (i = 0; i < IDEA_BLKSIZE; i++) {
+      c = *s++;
+      *d++ = j->iv[i] ^ c;
+      j->iv[i] = c;
+    }
+    sz -= IDEA_BLKSIZE;
+  }
+  if (!sz) return;
+
+  /* --- Do the tail-end bits --- */
+
+  idea_encrypt(&j->k, j->iv, j->iv);
+  j->i = 0;
+  while (sz) {
+    c = *s++;
+    *d++ = j->iv[j->i] ^ c;
+    j->iv[j->i++] = c;
+    sz--;
+  }
+}
+
+/* --- @icrypt_reset@ --- *
+ *
+ * Arguments:	@icrypt_job *j@ = pointer to job context block
+ *		@unsigned char *k@ = pointer to key data, or zero for
+ *			no change
+ *		@const unsigned char *iv@ = pointer to IV, or zero
+ *
+ * Returns:	---
+ *
+ * Use:		Alters the context block.  This can be used after recovery
+ *		of a session key, for example.
+ */
+
+void icrypt_reset(icrypt_job *j, unsigned char *k, const unsigned char *iv)
+{
+  if (k)
+    idea_ekeys(&j->k, k);
+  if (iv)
+    memcpy(j->iv, iv, IDEA_BLKSIZE);
+  else {
+    unsigned char b[IDEA_BLKSIZE];
+    int n = j->i, o = IDEA_BLKSIZE - j->i;
+
+    memcpy(b, j->iv, sizeof(b));
+    memcpy(j->iv, b + n, o);
+    memcpy(j->iv + o, b, n);
+  }
+  j->i = 8;
+}
+
+/* --- @icrypt_saveIV@ --- *
+ *
+ * Arguments:	@icrypt_job *j@ = pointer to job context block
+ *		@unsigned char *iv@ = where to store the IV
+ *
+ * Returns:	---
+ *
+ * Use:		Writes out the job's IV after munging it a little.
+ */
+
+void icrypt_saveIV(icrypt_job *j, unsigned char *iv)
+{
+  int n = j->i, o = IDEA_BLKSIZE - j->i;
+
+  memcpy(j->iv, iv + n, o);
+  memcpy(j->iv + o, iv, n);
+  idea_encrypt(&j->k, iv, iv);
+}
+
+/*----- Test rig ----------------------------------------------------------*/
+
+#ifdef TEST_RIG
+
+#include <errno.h>
+#include <pwd.h>
+#include <unistd.h>
+#include "mdwopt.h"
+#include "md5.h"
+#include "utils.h"
+
+void icrypt(icrypt_job *j,
+	    void (*proc)(icrypt_job *j,
+			 const void *src,
+			 void *dest,
+			 size_t sz),
+	    FILE *fp)
+{
+  char buff[71];
+  size_t r;
+
+  for (;;) {
+    r = fread(buff, 1, sizeof(buff), fp);
+    if (!r) break;
+    proc(j, buff, buff, r);
+    fwrite(buff, 1, r, stdout);
+  }
+}
+
+int main(int argc, char *argv[])
+{
+  icrypt_job j;
+  md5 md;
+  void (*proc)(icrypt_job *j,
+	       const void *src,
+	       void *dest,
+	       size_t sz) = icrypt_encrypt;
+
+  ego(argv[0]);
+
+  for (;;) {
+    int i = getopt(argc, argv, "d");
+    if (i < 0)
+      break;
+    if (i == 'd')
+      proc = icrypt_decrypt;
+  }
+
+  {
+    char *pass = getpass("Password: ");
+    md5_init(&md);
+    md5_buffer(&md, pass, strlen(pass));
+    memset(pass, 0, strlen(pass));
+    md5_final(&md);
+
+    icrypt_init(&j, md.val, 0);
+  }
+
+  if (optind >= argc)
+    icrypt(&j, proc, stdin);
+
+  while (optind < argc) {
+    char *p = argv[optind++];
+    if (strcmp(p, "-") == 0)
+      icrypt(&j, proc, stdin);
+    else {
+      FILE *fp = fopen(p, "rb");
+      if (!fp)
+	die("couldn't open `%s': %s", p, strerror(errno));
+      icrypt(&j, proc, fp);
+      fclose(fp);
+    }
+  }
+
+  return (0);
+}
+
+#endif    
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/icrypt.h b/src/icrypt.h
new file mode 100644
index 0000000..38deed9
--- /dev/null
+++ b/src/icrypt.h
@@ -0,0 +1,150 @@
+/* -*-c-*-
+ *
+ * $Id: icrypt.h,v 1.1 1997/07/21 13:47:49 mdw Exp $
+ *
+ * Higher level IDEA encryption
+ *
+ * (c) 1997 Mark Wooding
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: icrypt.h,v $
+ * Revision 1.1  1997/07/21 13:47:49  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef ICRYPT_H
+#define ICRYPT_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#include <stddef.h>
+
+#ifndef CONFIG_H
+#  include "config.h"
+#endif
+
+#ifndef IDEA_H
+#  include "idea.h"
+#endif
+
+/*----- Type definitions --------------------------------------------------*/
+
+/* --- @icrypt_job@ --- */
+
+typedef struct icrypt_job {
+  idea_key k;				/* IDEA key for en/decrypting */
+  int i;				/* Index into the IV buffer */
+  char iv[IDEA_BLKSIZE];		/* IV bytes for encrypting */
+} icrypt_job;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @icrypt_init@ --- *
+ *
+ * Arguments:	@icrypt_job *j@ = pointer to job context block
+ *		@unsigned char *k@ = pointer to key data
+ *		@const unsigned char *iv@ = pointer to IV
+ *
+ * Returns:	---
+ *
+ * Use:		Primes the context block ready for encryption.
+ */
+
+extern void icrypt_init(icrypt_job */*j*/,
+			unsigned char */*k*/,
+			const unsigned char */*iv*/);
+
+/* --- @icrypt_encrypt@ --- *
+ *
+ * Arguments:	@icrypt_job *j@ = job handle
+ *		@const void *src@ = pointer to source buffer
+ *		@void *dest@ = pointer to destination buffer
+ *		@size_t sz@ = size of buffers to handle
+ *
+ * Returns:	---
+ *
+ * Use:		Encrypts data from the source to the destination, using the
+ *		key attached to the job handle.
+ */
+
+extern void icrypt_encrypt(icrypt_job */*j*/, const void */*src*/,
+			   void */*dest*/, size_t /*sz*/);
+
+/* --- @icrypt_decrypt@ --- *
+ *
+ * Arguments:	@icrypt_job *j@ = job handle
+ *		@const void *src@ = pointer to source buffer
+ *		@void *dest@ = pointer to destination buffer
+ *		@size_t sz@ = size of buffers to handle
+ *
+ * Returns:	---
+ *
+ * Use:		Decrypts data from the source to the destination, using
+ *		the key attached to the job handle.
+ */
+
+extern void icrypt_decrypt(icrypt_job */*j*/, const void */*src*/,
+			   void */*dest*/, size_t /*sz*/);
+
+/* --- @icrypt_reset@ --- *
+ *
+ * Arguments:	@icrypt_job *j@ = pointer to job context block
+ *		@unsigned char *k@ = pointer to key data, or zero for
+ *			no change
+ *		@const unsigned char *iv@ = pointer to IV, or zero
+ *
+ * Returns:	---
+ *
+ * Use:		Alters the context block.  This can be used after recovery
+ *		of a session key, for example.
+ */
+
+extern void icrypt_reset(icrypt_job */*j*/,
+			 unsigned char */*k*/,
+			 const unsigned char */*iv*/);
+
+/* --- @icrypt_saveIV@ --- *
+ *
+ * Arguments:	@icrypt_job *j@ = pointer to job context block
+ *		@unsigned char *iv@ = where to store the IV
+ *
+ * Returns:	---
+ *
+ * Use:		Writes out the job's IV after munging it a little.
+ */
+
+extern void icrypt_saveIV(icrypt_job */*j*/, unsigned char */*iv*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/src/idea.c b/src/idea.c
new file mode 100644
index 0000000..e1bacfe
--- /dev/null
+++ b/src/idea.c
@@ -0,0 +1,501 @@
+/* -*-c-*-
+ *
+ * $Id: idea.c,v 1.1 1997/07/21 13:47:49 mdw Exp $
+ *
+ * IDEA encryption routines
+ *  Based on Straylight ARM assembler routines
+ *
+ * (c) 1996, 1997 Mark Wooding
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: idea.c,v $
+ * Revision 1.1  1997/07/21 13:47:49  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Notes -------------------------------------------------------------*
+ *
+ * This code is optimised for 32-bit processors with reasonable numbers of
+ * registers.  Hopefully it should still work on a Spectrum, although rather
+ * slowly.  I do assume two's complement arithmetic.
+ *
+ * Since this is actually /decompiled/, by hand, from some existing assembler
+ * code, you can expect some parts to be a little strange.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+
+#include "config.h"
+#include "idea.h"
+#include "utils.h"
+
+/*----- Low-level support functions ---------------------------------------*/
+
+/* --- @idea__inv@ --- *
+ *
+ * Arguments:   @int n@ = number to invert
+ *
+ * Returns:     Multiplicative inverse of n, mod 2^{16} + 1
+ */
+
+static int idea__inv(int n)
+{
+  long m, a, b, q, r, t;
+
+  /* --- Check the easy case --- */
+
+  if (!n)
+    return (0);
+
+  /* --- Start off the loop --- */
+
+  m = 0x10001L;
+  a = 1;
+  b = 0;
+  for (;;) {
+    q = m / n, r = m % n;
+    if (!r)
+      break;
+    m = n, n = r;
+    t = a, a = b - q * a, b = t;
+  }
+
+  /* --- Get return value in range --- */
+
+  if (a < 0)
+    a += 1;
+  return ((int) a & 0xFFFF);
+}
+
+/* --- @_mul@ --- *
+ *
+ * An evil macro to do multiplication.  Satan lives here.
+ */
+
+#define _mul(x, y)							\
+  (void)(								\
+    x &= ffff, x ?							\
+      ( y &= ffff, y ?							\
+        ((y = x * y), x = y & ffff, y = y >> 16, x < y ?		\
+          (x = x - y + 1) : (x = x - y)) :				\
+      (x = 1 - x)) :							\
+    (x = 1 - y)								\
+  )
+
+/*----- Key unpacking functions -------------------------------------------*/
+
+/* --- @idea_ekeys@ --- *
+ *
+ * Arguments:   @idea_key *k@ = the expanded key buffer
+ *		@const unsigned char *key@ = the user's key encryption key
+ *
+ * Returns:	---
+ *
+ * Use:         Unpacks an encryption key.
+ */
+
+void idea_ekeys(idea_key *k, const unsigned char *key)
+{
+  /* --- Convince compiler to do this properly --- */
+
+  register const int ffff = 0xFFFF;
+
+  uint_32 ka, kb, kc, kd;
+  int count;
+  int *p = k->k;
+
+  /* --- Load the 4 words from the block --- *
+   *
+   * Don't ask.
+   */
+
+  ka = load32(key +  0);
+  kb = load32(key +  4);
+  kc = load32(key +  8);
+  kd = load32(key + 12);
+
+  for (count = 48; count > 0; count -= 8) {
+
+    /* --- Unpack halfwords into the block --- */
+
+    *p++ = (ka >> 16) & ffff;
+    *p++ = ka & ffff;
+    *p++ = (kb >> 16) & ffff;
+    *p++ = kb & ffff;
+    *p++ = (kc >> 16) & ffff;
+    *p++ = kc & ffff;
+    *p++ = (kd >> 16) & ffff;
+    *p++ = kd & ffff;
+
+    /* --- Now rotate the 128-bit key --- */
+
+    {
+      uint_32 kx = ka;
+      ka = ((ka << 25) | (kb >> 7)) & 0xffffffffu;
+      kb = ((kb << 25) | (kc >> 7)) & 0xffffffffu;
+      kc = ((kc << 25) | (kd >> 7)) & 0xffffffffu;
+      kd = ((kd << 25) | (kx >> 7)) & 0xffffffffu;
+    }
+  }
+
+  /* --- Write the tail-enders over --- */
+
+  *p++ = (ka >> 16) & ffff;
+  *p++ = ka & ffff;
+  *p++ = (kb >> 16) & ffff;
+  *p++ = kb & ffff;
+}
+
+/* --- @idea_invertKey@ --- *
+ *
+ * Arguments:	@const idea_key *in@ = pointer to input expanded key buffer
+ *		@idea_key *out@ = pointer to output expanded key buffer
+ *
+ * Returns:	---
+ *
+ * Use:		Computes the inverse (decryption) key given an expanded
+ *		IDEA encryption key.
+ */
+
+void idea_invertKey(const idea_key *in, idea_key *out)
+{
+  int i;
+  unsigned a, b, c, d, e, f;
+  int *ibuf = in->k, *obuf = out->k;
+  
+  /* --- Deal with identical input and output buffers --- */
+
+  if (in == out) {
+    idea_key t;
+    memcpy(&t, in, sizeof(t));
+    idea_invertKey(&t, out);
+    return;
+  }
+
+  /* --- Do the real work --- */
+
+  ibuf += IDEA_EXPKEYSIZE;
+  for (i = 8; i; i--) {
+    ibuf -= 6;
+    a = ibuf[0];
+    b = ibuf[1];
+    c = ibuf[2];
+    d = ibuf[3];
+    e = ibuf[4];
+    f = ibuf[5];
+
+    c = idea__inv(c);
+    f = idea__inv(f);
+    d = 0x10000 - d;
+    e = 0x10000 - e;
+
+    if (i < 8)
+      d ^= e, e ^= d, d ^= e;
+
+    obuf[0] = c;
+    obuf[1] = d;
+    obuf[2] = e;
+    obuf[3] = f;
+    obuf[4] = a;
+    obuf[5] = b;
+    obuf += 6;
+  }
+
+  /* --- Deal with the tail-enders --- */
+
+  ibuf -= 4;
+  c = ibuf[0];
+  d = ibuf[1];
+  e = ibuf[2];
+  f = ibuf[3];
+
+  c = idea__inv(c);
+  f = idea__inv(f);
+  d = 0x10000 - d;
+  e = 0x10000 - e;
+
+  obuf[0] = c;
+  obuf[1] = d;
+  obuf[2] = e;
+  obuf[3] = f;
+}
+
+/* --- @idea_dkeys@ --- *
+ *
+ * Arguments:   @idea_key *k@ = the expanded key buffer
+ *		@const unsigned char *key@ = the user's key encryption key
+ *
+ * Returns:     ---
+ *
+ * Use:         Unpacks a decryption key.
+ */
+
+void idea_dkeys(idea_key *k, const unsigned char *key)
+{
+  idea_key t;
+  idea_ekeys(&t, key);
+  idea_invertKey(&t, k);
+}
+
+/*----- Main IDEA cipher --------------------------------------------------*/
+
+/* --- @idea_encrypt@ --- *
+ *
+ * Arguments:	@const idea_key *k@ = key to use
+ *		@const void *src@ = block to encrypt
+ *		@void *dest@ = where to store the result
+ *
+ * Returns:     ---
+ *
+ * Use:         Encrypts (or decrypts) a block, using the IDEA cryptosystem.
+ *              Since the decryption operation is the same as encryption
+ *              except that a different key buffer is used, this is all we
+ *              need to complete the simple bits.
+ *
+ *              For people following this at home: I've been very sloppy
+ *              about chopping off excess bits from the ints here.  Most of
+ *              the time it doesn't matter, and when it does, in the
+ *              multiplication stage, the macro does this for us.
+ *
+ *              Our @register const int ffff@ makes another appearance.  This
+ *              might suggest to compilers that having this constant
+ *              available would be beneficial.
+ *
+ *              Registers are in short supply here.  So is legibility.
+ */
+
+#if defined(TEST_RIG) && defined(DUMPROUNDS)
+#  define _dump(a,b,c,d)						\
+    printf("   %5lu %5lu %5lu %5lu\n",					\
+	   a & ffff, b & ffff, c & ffff, d & ffff)
+#else
+#  define _dump(a,b,c,d) ((void)0)
+#endif
+
+#define _round(a, b, c, d) do {						\
+  _dump(a, b, c, d);							\
+  u = kp[0]; v = kp[1]; w = kp[2]; x = kp[3]; y = kp[4]; z = kp[5];	\
+  kp += 6;								\
+  _mul(a, u); b += v; c += w; _mul(d, x);				\
+  u = a ^ c; v = b ^ d; _mul(u, y); v += u; _mul(v, z); u += v;		\
+  a ^= v; b ^= u; c ^= v; d ^= u;					\
+  _dump(a, b, c, d);							\
+} while (0)								\
+
+void idea_encrypt(const idea_key *k, const void *src, void *dest)
+{
+  register const int ffff = 0xFFFF;
+  const unsigned char *usrc = src;
+  unsigned char *udest = dest;
+  int *kp = k->k;
+
+  uint_32 a, b, c, d;
+  uint_32 u, v, w, x, y, z;
+
+  /* --- Unpack next block into registers --- */
+
+  a = (usrc[0] << 8) | usrc[1];
+  b = (usrc[2] << 8) | usrc[3];
+  c = (usrc[4] << 8) | usrc[5];
+  d = (usrc[6] << 8) | usrc[7];
+
+  /* --- Now run the block through the eight rounds --- *
+   *
+   * Notice how the arguments swap around so as I don't have to move the
+   * values about.
+   */
+
+  _round(a, b, c, d);
+  _round(a, c, b, d);
+  _round(a, b, c, d);
+  _round(a, c, b, d);
+
+  _round(a, b, c, d);
+  _round(a, c, b, d);
+  _round(a, b, c, d);
+  _round(a, c, b, d);
+
+  /* --- Do the output transformation --- */
+
+  u = kp[0];
+  v = kp[1];
+  w = kp[2];
+  x = kp[3];
+  _mul(a, u);
+  b += w;
+  c += v;
+  _mul(d, x);
+
+  /* --- Repack and store the block --- */
+
+  udest[0] = (a >> 8) & 0xFF; udest[1] = a & 0xFF;
+  udest[2] = (c >> 8) & 0xFF; udest[3] = c & 0xFF;
+  udest[4] = (b >> 8) & 0xFF; udest[5] = b & 0xFF;
+  udest[6] = (d >> 8) & 0xFF; udest[7] = d & 0xFF;
+}
+
+/*----- Debugging driver --------------------------------------------------*/
+
+#ifdef TEST_RIG
+
+#define TESTENCRYPTION
+
+void dumpbuf(int *k)
+{
+  int i;
+  printf("Round ");
+  for (i = 1; i <= 6; i++)
+    printf("%5i ", i);
+  for (i = 0; i < 52; i++) {
+    if (i % 6 == 0)
+      printf("\n  %i   ", i / 6 + 1);
+    printf("%5i ", *k++);
+  }
+  printf("\n\n");
+}
+
+void dumpblk(char *bb)
+{
+  unsigned char *b = (unsigned char *)bb;
+  printf("++ %5u %5u %5u %5u\n",
+	 (b[0]<<8)|b[1],
+	 (b[2]<<8)|b[3],
+	 (b[4]<<8)|b[5],
+	 (b[6]<<8)|b[7]);
+}
+
+int main(void)
+{
+
+#ifdef TESTMULTIPLY
+  {
+    unsigned int i, j;
+    char buf[256];
+    int ffff = 0xFFFF;
+    for (;;) {
+      gets(buf);
+      if (!buf[0])
+	break;
+      sscanf(buf, "%u%u", &i, &j);
+      _mul(i, j);
+      printf("%u\n", i);
+    }
+  }
+#endif
+
+#ifdef TESTENCRYPTION
+  {
+    int i;
+    int f;
+
+    unsigned char k[] = { 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8 };
+    idea_key e, d;
+    unsigned char b[] = { 0, 0, 0, 1, 0, 2, 0, 3 };
+
+    static idea_key correct_e = { {
+	  1,	 2,	3,     4,     5,     6, 
+	  7,	 8,  1024,  1536,  2048,  2560, 
+       3072,  3584,  4096,   512,    16,    20, 
+	 24,	28,    32,     4,     8,    12, 
+      10240, 12288, 14336, 16384,  2048,  4096, 
+       6144,  8192,   112,   128,    16,    32, 
+	 48,	64,    80,    96,     0,  8192, 
+      16384, 24576, 32768, 40960, 49152, 57345, 
+	128,	192,   256,  320
+    } };
+
+    static idea_key correct_d = { {
+      65025, 65344, 65280, 26010, 49152, 57345, 
+      65533, 32768, 40960, 52428,     0,  8192, 
+      42326, 65456, 65472, 21163,    16,    32, 
+      21835, 65424, 57344, 65025,  2048,  4096, 
+      13101, 51200, 53248, 65533,     8,    12, 
+      19115, 65504, 65508, 49153,    16,    20, 
+      43670, 61440, 61952, 65409,  2048,  2560, 
+      18725, 64512, 65528, 21803,     5,     6, 
+	  1, 65534, 65533, 49153
+    } };
+
+    static unsigned char correct_encrypt[] = {
+      4603 / 256, 4603 % 256,
+      60715 / 256, 60715 % 256,
+      408 / 256, 408 % 256,
+      28133 / 256, 28133 % 256
+    };
+
+    static unsigned char correct_decrypt[] = {
+      0, 0, 0, 1, 0, 2, 0, 3
+    };
+
+    idea_ekeys(&e, k);
+    dumpbuf(e.k);
+
+    f = 1;
+    for (i = 0; i < IDEA_EXPKEYSIZE; i++) {
+      if (e.k[i] != correct_e.k[i]) {
+	f = 0;
+	printf("!!! bad encryption key values!\n\n");
+      }
+    }
+    if (f)
+      printf("*** expanded encryption key correct\n\n");
+
+    idea_dkeys(&d, k);
+    dumpbuf(d.k);
+
+    f = 1;
+    for (i = 0; i < IDEA_EXPKEYSIZE; i++) {
+      if (d.k[i] != correct_d.k[i]) {
+	f = 0;
+	printf("!!! bad decryption key values!\n\n");
+      }
+    }
+    if (f)
+      printf("*** expanded decryption key correct\n\n");
+
+    idea_encrypt(&e, b, b);
+    dumpblk(b);
+    if (memcmp(b, correct_encrypt, 8) == 0)
+      printf("*** correct encipherment\n\n");
+    else
+      printf("!!! bad encipherment\n\n");
+
+    idea_encrypt(&d, b, b);
+    dumpblk(b);
+    if (memcmp(b, correct_decrypt, 8) == 0)
+      printf("*** correct decipherment\n");
+    else
+      printf("!!! bad decipherment\n");
+  }
+#endif
+
+  return (0);
+}
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/idea.h b/src/idea.h
new file mode 100644
index 0000000..79ca601
--- /dev/null
+++ b/src/idea.h
@@ -0,0 +1,126 @@
+/* -*-c-*-
+ *
+ * $Id: idea.h,v 1.1 1997/07/21 13:47:48 mdw Exp $
+ *
+ * IDEA encryption routines
+ *  Based on Straylight ARM assembler routines
+ *
+ * (c) 1996, 1997 Mark Wooding
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: idea.h,v $
+ * Revision 1.1  1997/07/21 13:47:48  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef IDEA_H
+#define IDEA_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#ifndef CONFIG_H
+#  include "config.h"
+#endif
+
+/*----- Useful constants --------------------------------------------------*/
+
+#define IDEA_BLKSIZE (8u)		/* Number of bytes in IDEA block */
+#define IDEA_KEYSIZE (16u)		/* Number of bytes in IDEA key */
+#define IDEA_EXPKEYSIZE (52u)		/* Number of ints in expanded key */
+
+/*----- Type definitions --------------------------------------------------*/
+
+typedef struct idea_key {
+  int k[IDEA_EXPKEYSIZE];		/* Subkey array */
+} idea_key;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @idea_ekeys@ --- *
+ *
+ * Arguments:   @idea_key *k@ = the expanded key buffer
+ *		@const unsigned char *key@ = the user's key encryption key
+ *
+ * Returns:	---
+ *
+ * Use:         Unpacks an encryption key.
+ */
+
+extern void idea_ekeys(idea_key */*k*/, const unsigned char */*key*/);
+
+/* --- @idea_invertKey@ --- *
+ *
+ * Arguments:	@const idea_key *in@ = pointer to input expanded key buffer
+ *		@idea_key *out@ = pointer to output expanded key buffer
+ *
+ * Returns:	---
+ *
+ * Use:		Computes the inverse (decryption) key given an expanded
+ *		IDEA encryption key.
+ */
+
+extern void idea_invertKey(const idea_key */*in*/, idea_key */*out*/);
+
+/* --- @idea_dkeys@ --- *
+ *
+ * Arguments:   @idea_key *k@ = the expanded key buffer
+ *		@const unsigned char *key@ = the user's key encryption key
+ *
+ * Returns:     ---
+ *
+ * Use:         Unpacks a decryption key.
+ */
+
+extern void idea_dkeys(idea_key */*k*/, const unsigned char */*key*/);
+
+/* --- @idea_encrypt@ --- *
+ *
+ * Arguments:	@const idea_key *k@ = key to use
+ *		@const void *src@ = block to encrypt
+ *		@void *dest@ = where to store the result
+ *              
+ *
+ * Returns:     ---
+ *
+ * Use:         Encrypts (or decrypts) a block, using the IDEA cryptosystem.
+ *              Since the decryption operation is the same as encryption
+ *              except that a different key buffer is used, this is all we
+ *              need to complete the simple bits.
+ */
+
+extern void idea_encrypt(const idea_key */*k*/,
+			 const void */*src*/, void */*dest*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/src/keygen.c b/src/keygen.c
new file mode 100644
index 0000000..17bf93c
--- /dev/null
+++ b/src/keygen.c
@@ -0,0 +1,577 @@
+/* -*-c-*-
+ *
+ * $Id: keygen.c,v 1.1 1997/07/21 13:47:48 mdw Exp $
+ *
+ * Key generation
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: keygen.c,v $
+ * Revision 1.1  1997/07/21 13:47:48  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Unix headers --- */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <fcntl.h>
+
+#include <termios.h>
+#include <unistd.h>
+
+/* --- Local headers --- */
+
+#include "config.h"
+#include "tx.h"
+#include "mdwopt.h"
+#include "utils.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static struct termios kg__raw, kg__old;	/* Terminal settings */
+static int kg__tty;			/* File handle for the terminal */
+static FILE *kg__ttyfp;			/* Stream pointer for terminal */
+static unsigned int kg__flags;		/* Various interesting flags */
+
+enum {
+  kgFlag__cbreak = 1			/* Terminal is in cbreak mode */
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @kg__cbreak@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Makes the terminal return characters as soon as they're
+ *		asked for.
+ */
+
+void kg__cbreak(void)
+{
+  /* --- Don't do this if I don't have to --- */
+
+  if (kg__flags & kgFlag__cbreak)
+    return;
+
+  /* --- Fetch the old attributes, and remember them --- */
+
+  if (tcgetattr(kg__tty, &kg__old))
+    die("couldn't read terminal attributes: %s", strerror(errno));
+  memcpy(&kg__raw, &kg__old, sizeof(kg__raw));
+
+  /* --- Now modify them for raw mode --- */
+
+  kg__raw.c_lflag &= ~(ICANON | ECHO);
+  kg__raw.c_cc[VTIME] = 0;
+  kg__raw.c_cc[VMIN] = 1;
+
+  /* --- Remember the new state, and away we go --- */
+
+  kg__flags |= kgFlag__cbreak;
+  if (tcsetattr(kg__tty, TCSAFLUSH, &kg__raw))
+    die("couldn't set terminal attributes: %s", strerror(errno));
+}
+
+/* --- @kg__crepair@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Unbreaks a cbroken tty.  Obvious, innit?
+ */
+
+static void kg__crepair(void)
+{
+  /* --- Don't do this if I don't have to --- */
+
+  if (~kg__flags & kgFlag__cbreak)
+    return;
+
+  /* --- Reset the old attributes --- */
+
+  tcsetattr(kg__tty, TCSAFLUSH, &kg__old);
+  kg__flags &= ~kgFlag__cbreak;
+}
+
+/* --- @kg__signal@ --- *
+ *
+ * Arguments:	@int sig@ = signal number
+ *
+ * Returns:	---
+ *
+ * Use:		Tidies up if I get a signal.
+ */
+
+static void kg__signal(int sig)
+{
+  kg__crepair();
+  signal(sig, SIG_DFL);
+  raise(sig);
+}
+
+/* --- @kgFmt__binary@ --- *
+ *
+ * Arguments:	@unsigned char *k@ = pointer to key buffer
+ *		@size_t bits@ = number of bits to write
+ *		@FILE *fp@ = stream to write on
+ *
+ * Returns:	---
+ *
+ * Use:		Writes bits on a stream.
+ */
+
+static void kgFmt__binary(unsigned char *k, size_t bits, FILE *fp)
+{
+  fwrite(k, 1, bits / 8, fp);
+}
+
+/* --- @kgFmt__base64@ --- *
+ *
+ * Arguments:	@unsigned char *k@ = pointer to key buffer
+ *		@size_t bits@ = number of bits to write
+ *		@FILE *fp@ = stream to write on
+ *
+ * Returns:	---
+ *
+ * Use:		Writes bits on a stream in an encoded way.
+ */
+
+static void kgFmt__base64(unsigned char *k, size_t bits, FILE *fp)
+{
+  static const char xlt[64] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+				"abcdefghijklmnopqrstuvwxyz"
+				"0123456789+/" };
+  unsigned long b;
+  int ll = 0;
+
+  while (bits > 24) {
+    b = ((k[0] & 0xffu) << 16) | ((k[1] & 0xffu) << 8) | (k[2] & 0xffu);
+    k += 3;
+    bits -= 24;
+    putc(xlt[(b >> 18) & 0x3fu], fp);
+    putc(xlt[(b >> 12) & 0x3fu], fp);
+    putc(xlt[(b >>  6) & 0x3fu], fp);
+    putc(xlt[(b >>  0) & 0x3fu], fp);
+    if ((ll += 4) > 70) {
+      putc('\n', fp);
+      ll = 0;
+    }
+  }
+
+  b = 0;
+  switch (bits) {
+    case 24:
+      b = *k++ & 0xffu;
+    case 16:
+      b = (b << 8) | (*k++ & 0xffu);
+    case 8:
+      b = (b << 8) | (*k++ & 0xffu);
+  }
+  b <<= (24 - bits);
+  putc(xlt[(b >> 18) & 0x3fu], fp);
+  putc(xlt[(b >> 12) & 0x3fu], fp);
+  switch (bits) {
+    case 24:
+      putc(xlt[(b >>  6) & 0x3fu], fp);
+      putc(xlt[(b >>  0) & 0x3fu], fp);
+      break;
+    case 16:
+      putc(xlt[(b >>  6) & 0x3fu], fp);
+      putc('=', fp);
+      break;
+    case 8:
+      fputs("==", fp);
+      break;
+  }
+
+  putc('\n', fp);
+}
+
+/* --- @kg__gen@ --- *
+ *
+ * Arguments:	@unsigned char *ui@ = pointer to array to fill in
+ *		@size_t sz@ = number of bits to generate
+ *
+ * Returns:	---
+ *
+ * Use:		Uses key timings to generate random numbers.  Maybe.
+ */
+
+static void kg__gen(unsigned char *ui, size_t sz)
+{
+  int bits = 32 < sz ? 32 : sz;
+  size_t wsz = (sz + 31u) & ~31u;
+  unsigned long last, ldiff = 0;
+  unsigned long a = 0;
+  unsigned long fact = 1000000 / CLOCKS_PER_SEC;
+
+  fprintf(kg__ttyfp,
+"I need to get %i random bits; I'll do this by timing your keypresses.\n"
+"Please type some arbitrary text until I say `done'.\n",
+	  sz);
+
+  {
+    struct timeval tv;
+    gettimeofday(&tv, 0);
+    last = tv.tv_usec / fact + tv.tv_sec * fact;
+  }
+
+  while (sz) {
+    int useful;
+    uint_32 orr, xor;
+
+    /* --- Print current status --- */
+
+    fprintf(kg__ttyfp, "\r%5i...", sz);
+    fflush(kg__ttyfp);
+
+    /* --- Read the next character --- */
+
+    {
+      char buff[16];
+      if (read(kg__tty, buff, sizeof(buff)) < 0)
+	die("couldn't read from terminal: %s", strerror(errno));
+    }
+
+    /* --- Fiddle with times --- *
+     *
+     * Read the time now.  Turn it into 32 bits of useful information, and
+     * find the difference between that and the previous time.  Compare this
+     * with the difference between the previous pair of keypresses.
+     */
+
+    {
+      struct timeval tv;
+      uint_32 n, nd;
+
+      gettimeofday(&tv, 0);
+      n = tv.tv_usec / fact + tv.tv_sec * fact;
+      if (!ldiff) {
+	ldiff = n - last;
+	last = n;
+	continue;
+      }
+      nd = n - last;
+      orr = nd | ldiff;
+      xor = nd ^ ldiff;
+      D( printf("\nlast = %08lx, next = %08lx, ldiff = %08lx, nd = %08lx\n",
+		(unsigned long)last, (unsigned long)n,
+		(unsigned long)ldiff, (unsigned long)nd);
+	 printf("xor = %08lx\n", (unsigned long)xor); )
+      ldiff = nd;
+      last = n;
+    }
+
+    /* --- Find the useful bits in this value --- *
+     *
+     * Find the least significant set bit in @bowl@ and chop it off.  Then
+     * find the most significant set bit and chop that off two.  The rest is
+     * probably interesting.
+     */
+
+    {
+      unsigned long i;
+
+      if (!orr || !xor)
+	continue; /* erk! */
+
+      useful = 30;
+
+      i = 0x80000000ul;
+      if (xor & i) {
+	while (i && (xor & i) != 0) {
+	  i >>= 1;
+	  useful--;
+	}
+      } else {
+	while (i && (xor & i) == 0) {
+	  i >>= 1;
+	  useful--;
+	}
+      }
+      xor &= ~-i;
+
+      while ((orr & 1) == 0) {
+	useful--;
+	orr >>= 1;
+	xor >>= 1;
+      }
+      xor >>= 1;
+
+      if (useful < 1)
+	continue;
+    }
+
+    /* --- Now add the bits in the mixing bowl to my stash --- *
+     *
+     * There are two cases:
+     *
+     * 1. I have more bits than will fit into the accumulator.  Then I must
+     *    put as many bits into the accumulator as will fit, store the
+     *    accumulator, and remove the spent bits.
+     *
+     * 2. I have too few bits to fit in the accumulator.  Then shift them
+     *    in and return.
+     */
+
+    while (sz && useful) {
+
+      D( printf("got %i bits, need %i/%i: %8lx\n",
+		useful, bits, sz, (unsigned long)xor); )
+
+      if (useful >= bits) {
+
+	D( printf("shifted acc = %08lx\n"
+		  "   new bits = %08lx\n"
+		  "     result = %08lx\n",
+		  (unsigned long)(a << bits),
+		  (unsigned long)(xor >> (useful - bits)),
+		  (unsigned long)((a << bits) | (xor >> (useful - bits)))); )
+
+	a = (a << bits) | (xor >> (useful - bits));
+
+	sz -= bits;
+	wsz -= bits;
+	useful -= bits;
+
+	if (sz == 0) {
+	  a <<= wsz;
+	  bits = 32 - wsz;
+	} else
+	  bits = 32;
+
+	while (bits > 0) {
+	  D( printf("writing %02x\n", (a >> 24) & 0xffu); )
+	  *ui++ = (a >> 24) & 0xffu;
+	  a <<= 8;
+	  bits -= 8;
+	}
+	a = 0;
+
+	bits = 32 < sz ? 32 : sz;
+
+      } else {
+
+	D( printf("shifted acc = %08lx\n"
+		  "   new bits = %08lx\n"
+		  "     result = %08lx\n",
+		  (unsigned long)(a << useful),
+		  (unsigned long)(xor),
+		  (unsigned long)((a << useful) | (xor))); )
+	a = (a << useful) | xor;
+	bits -= useful;
+	sz -= useful;
+	wsz -= useful;
+	useful = 0;
+      }
+    }
+  }
+
+  fputs("\rDone!         \n", kg__ttyfp);
+  putc('\a', kg__ttyfp); fflush(kg__ttyfp);
+  sleep(1);
+}
+
+/* --- @main@ --- *
+ *
+ * Arguments:	@int argc@ = number of arguments
+ *		@char *argv[]@ = array of arguments
+ *
+ * Returns:	Zero if it worked, nonzero if it didn't.
+ *
+ * Use:		Generates random numbers from the keyboard.
+ */
+
+int main(int argc, char *argv[])
+{
+  unsigned char *uip;
+  size_t sz = 128;
+  const char *file = 0;
+  FILE *fp;
+
+  /* --- Formats table --- */
+
+  static struct {
+    const char *name;
+    void (*proc)(unsigned char *k, size_t bits, FILE *fp);
+  } format[] = {
+    { "tx",	tx_putBits },
+    { "hex",	tx_putBits },
+    { "binary",	kgFmt__binary },
+    { "base64",	kgFmt__base64 },
+    { 0, 0 }
+  };
+
+  void (*fmt)(unsigned char *, size_t, FILE *) = tx_putBits;
+
+  /* --- Explain who I am --- */
+
+  ego(argv[0]);
+
+  /* --- Read arguments --- */
+
+  for (;;) {
+    static struct option opts[] = {
+      { "help",		0,		0,	'h' },
+      { "bits",		gFlag_argReq,	0,	'b' },
+      { "output",	gFlag_argReq,	0,	'o' },
+      { "format",	gFlag_argReq,	0,	'f' },
+      { 0,		0,		0,	0 }
+    };
+
+    int i = mdwopt(argc, argv, "hb:o:f:", opts, 0, 0, 0);
+
+    if (i < 0)
+      break;
+    switch (i) {
+      case 'h':
+	printf(""
+"Usage: %s [--bits=BITS] [--output=FILE] [--format=FORMAT]\n"
+"\n"
+"Generates BITS (by default, 128) random bits by timing keypresses.  The\n"
+"resulting number is written to FILE, or standard output in the given\n"
+"format.  Formats currently supported are:\n"
+"\n"
+"tx, hex		Hexadecimal, with punctuating dashes.\n"
+"binary		Raw binary.\n"
+"base64		Base64-encoded binary; the result is printable ASCII.\n",
+	       quis());
+	exit(0);
+	break;
+      case 'b':
+	sz = atoi(optarg);
+	if (sz == 0)
+	  die("bad number of bits (illegible or zero)");
+	if (sz % 8)
+	  die("can only generate a whole number of 8-bit bytes");
+	break;
+      case 'o':
+	file = optarg;
+	break;
+      case 'f':
+	fmt = 0;
+	for (i = 0; format[i].name; i++) {
+	  const char *p = format[i].name, *q = optarg;
+	  for (;;) {
+	    if (*q == 0)
+	      break;
+	    if (*p != *q)
+	      break;
+	    p++, q++;
+	  }
+	  if (!*q) {
+	    if (fmt)
+	      die("ambiguous format name: `%s'", optarg);
+	    fmt = format[i].proc;
+	  }
+	}
+	if (!fmt)
+	  die("unknown format name: `%s'", optarg);
+	break;
+      case '?':
+	exit(1);
+	break;
+    }
+  }
+
+  if (optind < argc) {
+    fprintf(stderr, "Usage: %s [--bits=BITS] [--output=FILE]\n", quis());
+    exit(1);
+  }
+
+  /* --- Allocate memory --- */
+
+  uip = xmalloc(sz / 8);
+
+  /* --- Open the terminal --- *
+   *
+   * I'd like to be able to @fprintf@ to the terminal, so use @fopen@.
+   */
+
+  if ((kg__ttyfp = fopen("/dev/tty", "r+")) == 0)
+    die("couldn't open terminal: %s", strerror(errno));
+  kg__tty = fileno(kg__ttyfp);
+
+  /* --- Open the output file, if one is specified --- */
+
+  if (file) {
+    int fd;
+
+    /* --- Open the file oddly --- *
+     *
+     * There's a good reason for this.  I want to be able to @fprintf@ (for
+     * the benefit of @tx_putWords@ mainly) but I also want to ensure that
+     * only the user has read permissions for the file.  So I'll use @open@
+     * with an appropriate mode and then @fdopen@ the file descriptor to get
+     * a stream.  Yuk.
+     */
+
+    if ((fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0)
+      die("couldn't open output file: %s", strerror(errno));
+    if ((fp = fdopen(fd, "w")) == 0)
+      die("couldn't attach stream to output file: %s", strerror(errno));
+  } else
+    fp = stdout;
+
+  /* --- Tidy up nicely if I die --- */
+
+  signal(SIGINT, kg__signal);
+  signal(SIGTERM, kg__signal);
+  atexit(kg__crepair);
+
+  /* --- Put the terminal into cbreak, read the key, and restore --- */
+
+  kg__cbreak();
+  kg__gen(uip, sz);
+  kg__crepair();
+
+  /* --- Now write the number and exit --- */
+
+  D( fputs("*** ", fp); tx_putBits(uip, sz, stdout); )
+  fmt(uip, sz, fp);
+  if (file)
+    fclose(fp);
+  memset(uip, 0, sz / 8);		/* Burn temporary buffer */
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/lexer.h b/src/lexer.h
new file mode 100644
index 0000000..603d382
--- /dev/null
+++ b/src/lexer.h
@@ -0,0 +1,83 @@
+/* -*-c-*-
+ *
+ * $Id: lexer.h,v 1.1 1997/07/21 13:47:48 mdw Exp $
+ *
+ * Lexical analyser for `become.conf' files
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: lexer.h,v $
+ * Revision 1.1  1997/07/21 13:47:48  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef LEXER_H
+#define LEXER_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#include <stdio.h>
+
+/*----- Global variables --------------------------------------------------*/
+
+extern int lex_line;			/* Current line number */
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @yylex@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	The next token, or zero for EOF.
+ *
+ * Use:		Scans the input file.
+ */
+
+extern int yylex(void);
+
+/* --- @lexer_scan@ --- *
+ *
+ * Arguments:	@FILE *fp@ = pointer to a stream object to scan
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises the scanner ready to parse from the given
+ *		stream.
+ */
+
+extern void lexer_scan(FILE */*fp*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/src/lexer.l b/src/lexer.l
new file mode 100644
index 0000000..597690a
--- /dev/null
+++ b/src/lexer.l
@@ -0,0 +1,173 @@
+/* -*-c-*-
+ *
+ * $Id: lexer.l,v 1.1 1997/07/21 13:47:48 mdw Exp $
+ *
+ * Lexical analyser for `become.conf' files
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: lexer.l,v $
+ * Revision 1.1  1997/07/21 13:47:48  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Declarations section ----------------------------------------------*/
+
+/* --- Header files --- */
+
+%{
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "become.h"
+#include "lexer.h"
+#include "parser.h"
+#include "utils.h"
+%}
+
+/* --- Start conditions --- */
+
+%x s_KEYWORD
+%x s_NORMAL
+%x s_STRING
+
+/* --- A handy static buffer --- */
+
+ static char lex__buff[4096];
+ static char *lex__ptr;
+
+/* --- Line number --- */
+
+ int lex_line;
+
+%%
+
+ /*---- Main scanner definition -------------------------------------------*/
+
+ /* --- Comments --- */
+
+<INITIAL,s_KEYWORD,s_NORMAL>{
+  "#".*\n			lex_line++;
+
+ /* --- Whitespace --- */
+
+  [ \t]				/* munch */
+  \n				lex_line++;
+}
+
+ /* --- Keywords --- */
+
+<INITIAL,s_KEYWORD>{
+  user				BEGIN(s_NORMAL); return (USER);
+  command			BEGIN(s_NORMAL); return (COMMAND);
+  host				BEGIN(s_NORMAL); return (HOST);
+  allow				BEGIN(s_NORMAL); return (ALLOW);
+  port				BEGIN(s_NORMAL); return (PORT);
+  keyfile			BEGIN(s_NORMAL); return (KEYFILE);
+  .				BEGIN(s_NORMAL); return (BADTOKEN);
+}
+ /* --- Other sorts of tokens --- */
+
+<s_NORMAL>{
+  [0-9]*			yylval.i = atoi(yytext); return (INT);
+  [a-zA-Z_][a-zA-Z_0-9]*	yylval.s = yytext; return (WORD);
+  \"				BEGIN(s_STRING); lex__ptr = lex__buff;
+  ";"				BEGIN(s_KEYWORD); return (';');
+  "->"				return (ARROW);
+  .				return (yytext[0]);
+}
+
+ /* --- Strings and things --- *
+  *
+  * Be a little careful about buffer overflows here.
+  */
+
+<s_STRING>{
+  \\.				{
+				  if (lex__ptr >
+				      lex__buff + sizeof(lex__buff) - 8) {
+				    moan("string too long at line %i",
+					 lex_line);
+				    *lex__ptr++ = 0;
+				    yylval.s = lex__buff;
+				    BEGIN(s_NORMAL);
+				    return (STRING);
+				  }
+				  *lex__ptr++ = yytext[1];
+				}
+  \"				{
+				  *lex__ptr++ = 0;
+				  yylval.s = lex__buff;
+				  BEGIN(s_NORMAL);
+				  return (STRING);
+				}
+  \n				|
+  <<EOF>>			{
+				  moan("missing `\"', inserted at line %i",
+				       lex_line);
+				  lex_line++;
+				  *lex__ptr++ = 0;
+				  yylval.s = lex__buff;
+				  BEGIN(s_NORMAL);
+				  return (STRING);
+				}
+  .				{
+				  if (lex__ptr >
+				      lex__buff + sizeof(lex__buff) - 8) {
+				    moan("string too long at line %i",
+					 lex_line);
+				    *lex__ptr++ = 0;
+				    yylval.s = lex__buff;
+				    BEGIN(s_NORMAL);
+				    return (STRING);
+				  }
+				  *lex__ptr++ = yytext[0];
+				}
+}
+
+%%
+
+/*----- Support routines --------------------------------------------------*/
+
+/* --- @lexer_scan@ --- *
+ *
+ * Arguments:	@FILE *fp@ = pointer to a stream object to scan
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises the scanner ready to parse from the given
+ *		stream.
+ */
+
+void lexer_scan(FILE *fp)
+{
+  yyin = fp;
+  lex_line = 1;
+  BEGIN(INITIAL);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/md5.c b/src/md5.c
new file mode 100644
index 0000000..80cadf2
--- /dev/null
+++ b/src/md5.c
@@ -0,0 +1,496 @@
+/* -*-c-*-
+ *
+ * $Id: md5.c,v 1.1 1997/07/21 13:47:47 mdw Exp $
+ *
+ * MD-5 secure hash routines
+ *  Based on RSA MD-5 code
+ *
+ * (c) 1996, 1997 Mark Wooding
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: md5.c,v $
+ * Revision 1.1  1997/07/21 13:47:47  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+#include <string.h>
+
+#include "config.h"
+#include "md5.h"
+#include "utils.h"
+
+/*----- Define MD-5 transform ---------------------------------------------*/
+
+/* --- Low-level nonlinear functions --- */
+
+#define f(x,y,z) (((x) & (y)) | ((~x) & (z)))
+#define g(x,y,z) (((x) & (z)) | ((y) & (~z)))
+#define h(x,y,z) ((x) ^ (y) ^ (z))
+#define i(x,y,z) ((y) ^ ((x) | (~z)))
+
+#define rol(x,b) (((x) << (b)) | ((x) >> (32-(b))))
+
+/* --- Rotations applied at various stages --- */
+
+#define S11 7u
+#define S12 12u
+#define S13 17
+#define S14 22u
+#define S21 5u
+#define S22 9u
+#define S23 14u
+#define S24 20u
+#define S31 4u
+#define S32 11u
+#define S33 16u
+#define S34 23u
+#define S41 6u
+#define S42 10u
+#define S43 15u
+#define S44 21u
+
+/* --- Higher level nonlinear functions --- */
+
+#define ff(a, b, c, d, x, s, m) ( a += f(b, c, d) + x + m,		\
+				  a = rol(a, s), a += b )
+
+#define gg(a, b, c, d, x, s, m) ( a += g(b, c, d) + x + m,		\
+				  a = rol(a, s), a += b )
+
+#define hh(a, b, c, d, x, s, m) ( a += h(b, c, d) + x + m,		\
+				  a = rol(a, s), a += b )
+
+#define ii(a, b, c, d, x, s, m) ( a += i(b, c, d) + x + m,		\
+				  a = rol(a, s), a += b )
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @md5__trans@ --- *
+ *
+ * Arguments:   @uint_32 *v@ = pointer to chaining variables (updated)
+ *		@const unsigned char *buf@ = pointer to a 64-byte block
+ *
+ * Returns:	---
+ *
+ * Use:         Performs the main MD5 transform on a block of data.
+ */
+
+static void md5__trans(uint_32 *v, const unsigned char *buf)
+{
+  uint_32 a, b, c, d;
+  uint_32 ib[16];
+  size_t i;
+
+  /* --- Initialise my internal registers --- */
+
+  a = v[0], b = v[1], c = v[2], d = v[3];
+
+  /* --- Turn the buffer into 32-bit words --- */
+
+  for (i = 0; i < 16; i++)
+    ib[i] = load32_l(buf + (i << 2));
+
+  /* --- Round one --- */
+
+  ff(a, b, c, d, ib[ 0], S11, 0xd76aa478);	/* 1 */
+  ff(d, a, b, c, ib[ 1], S12, 0xe8c7b756);	/* 2 */
+  ff(c, d, a, b, ib[ 2], S13, 0x242070db);	/* 3 */
+  ff(b, c, d, a, ib[ 3], S14, 0xc1bdceee);	/* 4 */
+  ff(a, b, c, d, ib[ 4], S11, 0xf57c0faf);	/* 5 */
+  ff(d, a, b, c, ib[ 5], S12, 0x4787c62a);	/* 6 */
+  ff(c, d, a, b, ib[ 6], S13, 0xa8304613);	/* 7 */
+  ff(b, c, d, a, ib[ 7], S14, 0xfd469501);	/* 8 */
+  ff(a, b, c, d, ib[ 8], S11, 0x698098d8);	/* 9 */
+  ff(d, a, b, c, ib[ 9], S12, 0x8b44f7af);	/* 10 */
+  ff(c, d, a, b, ib[10], S13, 0xffff5bb1);	/* 11 */
+  ff(b, c, d, a, ib[11], S14, 0x895cd7be);	/* 12 */
+  ff(a, b, c, d, ib[12], S11, 0x6b901122);	/* 13 */
+  ff(d, a, b, c, ib[13], S12, 0xfd987193);	/* 14 */
+  ff(c, d, a, b, ib[14], S13, 0xa679438e);	/* 15 */
+  ff(b, c, d, a, ib[15], S14, 0x49b40821);	/* 16 */
+
+  /* --- Round two --- */
+
+  gg(a, b, c, d, ib[ 1], S21, 0xf61e2562);	/* 17 */
+  gg(d, a, b, c, ib[ 6], S22, 0xc040b340);	/* 18 */
+  gg(c, d, a, b, ib[11], S23, 0x265e5a51);	/* 19 */
+  gg(b, c, d, a, ib[ 0], S24, 0xe9b6c7aa);	/* 20 */
+  gg(a, b, c, d, ib[ 5], S21, 0xd62f105d);	/* 21 */
+  gg(d, a, b, c, ib[10], S22, 0x02441453);	/* 22 */
+  gg(c, d, a, b, ib[15], S23, 0xd8a1e681);	/* 23 */
+  gg(b, c, d, a, ib[ 4], S24, 0xe7d3fbc8);	/* 24 */
+  gg(a, b, c, d, ib[ 9], S21, 0x21e1cde6);	/* 25 */
+  gg(d, a, b, c, ib[14], S22, 0xc33707d6);	/* 26 */
+  gg(c, d, a, b, ib[ 3], S23, 0xf4d50d87);	/* 27 */
+  gg(b, c, d, a, ib[ 8], S24, 0x455a14ed);	/* 28 */
+  gg(a, b, c, d, ib[13], S21, 0xa9e3e905);	/* 29 */
+  gg(d, a, b, c, ib[ 2], S22, 0xfcefa3f8);	/* 30 */
+  gg(c, d, a, b, ib[ 7], S23, 0x676f02d9);	/* 31 */
+  gg(b, c, d, a, ib[12], S24, 0x8d2a4c8a);	/* 32 */
+
+  /* --- Round three --- */
+
+  hh(a, b, c, d, ib[ 5], S31, 0xfffa3942);	/* 33 */
+  hh(d, a, b, c, ib[ 8], S32, 0x8771f681);	/* 34 */
+  hh(c, d, a, b, ib[11], S33, 0x6d9d6122);	/* 35 */
+  hh(b, c, d, a, ib[14], S34, 0xfde5380c);	/* 36 */
+  hh(a, b, c, d, ib[ 1], S31, 0xa4beea44);	/* 37 */
+  hh(d, a, b, c, ib[ 4], S32, 0x4bdecfa9);	/* 38 */
+  hh(c, d, a, b, ib[ 7], S33, 0xf6bb4b60);	/* 39 */
+  hh(b, c, d, a, ib[10], S34, 0xbebfbc70);	/* 40 */
+  hh(a, b, c, d, ib[13], S31, 0x289b7ec6);	/* 41 */
+  hh(d, a, b, c, ib[ 0], S32, 0xeaa127fa);	/* 42 */
+  hh(c, d, a, b, ib[ 3], S33, 0xd4ef3085);	/* 43 */
+  hh(b, c, d, a, ib[ 6], S34, 0x04881d05);	/* 44 */
+  hh(a, b, c, d, ib[ 9], S31, 0xd9d4d039);	/* 45 */
+  hh(d, a, b, c, ib[12], S32, 0xe6db99e5);	/* 46 */
+  hh(c, d, a, b, ib[15], S33, 0x1fa27cf8);	/* 47 */
+  hh(b, c, d, a, ib[ 2], S34, 0xc4ac5665);	/* 48 */
+
+  /* --- Round four --- */
+
+  ii(a, b, c, d, ib[ 0], S41, 0xf4292244);	/* 49 */
+  ii(d, a, b, c, ib[ 7], S42, 0x432aff97);	/* 50 */
+  ii(c, d, a, b, ib[14], S43, 0xab9423a7);	/* 51 */
+  ii(b, c, d, a, ib[ 5], S44, 0xfc93a039);	/* 52 */
+  ii(a, b, c, d, ib[12], S41, 0x655b59c3);	/* 53 */
+  ii(d, a, b, c, ib[ 3], S42, 0x8f0ccc92);	/* 54 */
+  ii(c, d, a, b, ib[10], S43, 0xffeff47d);	/* 55 */
+  ii(b, c, d, a, ib[ 1], S44, 0x85845dd1);	/* 56 */
+  ii(a, b, c, d, ib[ 8], S41, 0x6fa87e4f);	/* 57 */
+  ii(d, a, b, c, ib[15], S42, 0xfe2ce6e0);	/* 58 */
+  ii(c, d, a, b, ib[ 6], S43, 0xa3014314);	/* 59 */
+  ii(b, c, d, a, ib[13], S44, 0x4e0811a1);	/* 60 */
+  ii(a, b, c, d, ib[ 4], S41, 0xf7537e82);	/* 61 */
+  ii(d, a, b, c, ib[11], S42, 0xbd3af235);	/* 62 */
+  ii(c, d, a, b, ib[ 2], S43, 0x2ad7d2bb);	/* 63 */
+  ii(b, c, d, a, ib[ 9], S44, 0xeb86d391);	/* 64 */
+
+  /* --- Update the context --- */
+
+  v[0] += a, v[1] += b, v[2] += c, v[3] += d;
+}
+
+/* --- @md5_trans@ --- *
+ *
+ * Arguments:	@unsigned char *v@ = pointer to chaining block (updated)
+ *		@const unsigned char *buf@ = pointer to input buffer
+ *
+ * Returns:	---
+ *
+ * Use:		Performs the MD5 transformation on a chunk of data.  This may
+ *		be useful for using MD5 in MDC-type cipher constructions.
+ */
+
+void md5_trans(unsigned char *v, const unsigned char *buf)
+{
+  uint_32 vv[4];
+
+  vv[0] = load32_l(v +  0);
+  vv[1] = load32_l(v +  4);
+  vv[2] = load32_l(v +  8);
+  vv[3] = load32_l(v + 12);
+  md5__trans(vv, buf);
+  store32_l(v +  0, vv[0]);
+  store32_l(v +  4, vv[1]);
+  store32_l(v +  8, vv[2]);
+  store32_l(v + 12, vv[3]);
+}
+
+/* --- @md5_buffer@ --- *
+ *
+ * Arguments:   @md5 *m@ = pointer to an MD5 context
+ *              @const void *buff@ = pointer to buffer of data
+ *              @size_t size@ = size of buffer
+ *
+ * Returns:	---
+ *
+ * Use:         Hashes the buffer of data.  You can call @md5_buffer@
+ *              lots of times during an MD5 job, to allow big files to be
+ *              split into little ones.
+ */
+
+void md5_buffer(md5 *m, const void *buff, size_t size)
+{
+  long int s = size;
+  const unsigned char *b = buff;
+  unsigned long x, y;
+
+  /* --- Maybe there's some data already in the buffer --- */
+
+  if (x = m->size & 63, x) {
+    y = 64 - x;
+    if (y > s) {
+      memcpy(x + m->buf, b, s);
+      m->size += s;
+      return;
+    }
+    memcpy(x + m->buf, b, y);
+    md5__trans(m->val, m->buf);
+    s -= y, b += y;
+  }
+
+  /* --- Now do whole buffers-full --- */
+
+  while (s >= 64) {
+    md5__trans(m->val, b);
+    s -= 64, b += 64;
+  }
+
+  /* --- Tidy up the tail end --- */
+
+  if (s)
+    memcpy(m->buf, b, s);
+  m->size += size;
+}
+
+/* --- @md5_key@ --- *
+ *
+ * Arguments:	@md5 *m@ = pointer to an MD5 context
+ *		@const unsigned char *k@ = pointer to a 4-word `key'
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises a context buffer, with a chosen initialisation
+ *		string (instead of the standard MD5 value).  This allows you
+ *		to use NMAC message authentication, should the urge take you.
+ */
+
+void md5_key(md5 *m, const unsigned char *k)
+{
+  m->size = 0;
+  m->val[0] = load32_l(k +  0);
+  m->val[0] = load32_l(k +  4);
+  m->val[0] = load32_l(k +  8);
+  m->val[0] = load32_l(k + 12);
+}
+
+/* --- @md5_init@ --- *
+ *
+ * Arguments:   @md5 *m@ = pointer to an MD5 context
+ *
+ * Returns:	---
+ *
+ * Use:         Initialises the context buffer, so that you can do an
+ *              MD5 job.
+ */
+
+void md5_init(md5 *m)
+{
+  m->size = 0;
+  m->val[0] = 0x67452301;
+  m->val[1] = 0xefcdab89;
+  m->val[2] = 0x98badcfe;
+  m->val[3] = 0x10325476;
+}
+
+/* --- @md5_final@ --- *
+ *
+ * Arguments:   @md5 *m@ = pointer to context buffer
+ *		@unsigned char *v@ = where to store the value
+ *
+ * Returns:	---
+ *
+ * Use:         Finalises an MD5 buffer, so that you can use the result.
+ */
+
+void md5_final(md5 *m, unsigned char *v)
+{
+  int s = m->size;
+
+  /* --- Pad out the block --- */
+
+  {
+    const static unsigned char pad[64] = { 0x80 };
+    int p = m->size & 63;
+    p = (p < 56) ? (p = 56 - p) : (p = 120 - p);
+    md5_buffer(m, pad, p);
+  }
+
+  /* --- Append the length --- */
+
+  {
+    unsigned char b[8];
+
+    store32_l(b + 0, s << 3);
+    store32_l(b + 4, s >> 29);
+    md5_buffer(m, b, 8);
+  }
+
+  /* --- Write out the value --- */
+
+  if (v) {
+    store32_l(v +  0, m->val[0]);
+    store32_l(v +  4, m->val[1]);
+    store32_l(v +  8, m->val[2]);
+    store32_l(v + 12, m->val[3]);
+  }
+}
+
+/*----- Test driver -------------------------------------------------------*/
+
+#ifdef TEST_RIG
+
+int main(int argc, char *argv[])
+{
+  if (argc > 1 && strcmp(argv[1], "-x") == 0) {
+
+    static struct {
+      const char *string;
+      uint_32 md[4];
+    } table[] = {
+      "",		{ 0xd98c1dd4, 0x04b2008f, 0x980980e9, 0x7e42f8ec },
+      "a",		{ 0xb975c10c, 0xa8b6f1c0, 0xe299c331, 0x61267769 },
+      "abc",		{ 0x98500190, 0xb04fd23c, 0x7d3f96d6, 0x727fe128 },
+      "message digest",	{ 0x7d696bf9, 0x8d93b77c, 0x312f5a52, 0xd061f1aa },
+      "abcdefghijklmnopqrstuvwxyz",
+			{ 0xd7d3fcc3, 0x00e49261, 0x6c49fb7d, 0x3be167ca },
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+			{ 0x98ab74d1, 0xf5d977d2, 0x2c1c61a5, 0x9f9d419f },
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n"
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n",
+			{ 0xbaa7652b, 0x5e10cd4a, 0xde9acbf2, 0xfa0b9fbd }
+    };
+    int steptbl[] = { 3, 18, 32, 37, 63, 64, 65, 256, 1024, -1 };
+
+    md5 m;
+    int i, j;
+    const char *p;
+    size_t sz, a, d;
+    int f = 1;
+
+    for (i = 0; i < sizeof(table) / sizeof(table[0]); i++) {
+      sz = strlen(table[i].string);
+      for (j = 0; steptbl[j] < sz && steptbl[j] != -1; j++) {
+	p = table[i].string;
+	d = steptbl[j];
+	md5_init(&m);
+	for (a = sz; a > d; a -= d) {
+	  md5_buffer(&m, p, d);
+	  p += d;
+	}
+	md5_buffer(&m, p, a);
+	md5_final(&m, 0);
+	if (m.val[0] != table[i].md[0] ||
+	    m.val[1] != table[i].md[1] ||
+	    m.val[2] != table[i].md[2] ||
+	    m.val[3] != table[i].md[3]) {
+	  printf("!!! bad value, string == `%s', step size == %i\n"
+		 "!!! expected %08lx-%08lx-%08lx-%08lx, found "
+		 "%08lx-%08lx-%08lx-%08lx\n\n",
+		 table[i].string, d,
+		 (unsigned long)table[i].md[0],
+		 (unsigned long)table[i].md[1],
+		 (unsigned long)table[i].md[2],
+		 (unsigned long)table[i].md[3],
+		 (unsigned long)m.val[0],
+		 (unsigned long)m.val[1],
+		 (unsigned long)m.val[2],
+		 (unsigned long)m.val[3]);
+	  f = 0;
+	}
+      }
+      md5_init(&m);
+      md5_buffer(&m, table[i].string, sz);
+      md5_final(&m, 0);
+      if (m.val[0] != table[i].md[0] ||
+	  m.val[1] != table[i].md[1] ||
+	  m.val[2] != table[i].md[2] ||
+	  m.val[3] != table[i].md[3]) {
+	printf("!!! bad value, string == `%s', step size == entire string\n"
+	       "!!! expected %08lx-%08lx-%08lx-%08lx, found "
+	       "%08lx-%08lx-%08lx-%08lx\n\n",
+	       table[i].string,
+	       (unsigned long)table[i].md[0],
+	       (unsigned long)table[i].md[1],
+	       (unsigned long)table[i].md[2],
+	       (unsigned long)table[i].md[3],
+	       (unsigned long)m.val[0],
+	       (unsigned long)m.val[1],
+	       (unsigned long)m.val[2],
+	       (unsigned long)m.val[3]);
+	f = 0;
+      } else {
+	printf("`%s' => %08lx-%08lx-%08lx-%08lx\n",
+	       table[i].string,
+	       (unsigned long)m.val[0],
+	       (unsigned long)m.val[1],
+	       (unsigned long)m.val[2],
+	       (unsigned long)m.val[3]);
+      }
+    }
+    
+  } else {
+
+    char buff[4096];
+    md5 m;
+    int i;
+    int read;
+
+    md5_init(&m);
+    while (read = fread(buff, 1, 4096, stdin), read)
+      md5_buffer(&m, buff, read);
+    md5_final(&m, 0);
+
+    for (i = 0; i < 4; i++)
+      printf("%02lx%02lx%02lx%02lx",
+	     (unsigned long)( (m.val[i] & 0x000000FF) >>  0 ),
+	     (unsigned long)( (m.val[i] & 0x0000FF00) >>  8 ),
+	     (unsigned long)( (m.val[i] & 0x00FF0000) >> 16 ),
+	     (unsigned long)( (m.val[i] & 0xFF000000) >> 24 ));
+    putc('\n', stdout);
+
+  }
+
+  return (0);
+}
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/md5.h b/src/md5.h
new file mode 100644
index 0000000..a105630
--- /dev/null
+++ b/src/md5.h
@@ -0,0 +1,146 @@
+/* -*-c-*-
+ *
+ * $Id: md5.h,v 1.1 1997/07/21 13:47:47 mdw Exp $
+ *
+ * MD-5 secure hash routines
+ *  Based on RSA MD-5 code
+ *
+ * (c) 1996, 1997 Mark Wooding
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: md5.h,v $
+ * Revision 1.1  1997/07/21 13:47:47  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef MD5_H
+#define MD5_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#include <stddef.h>
+
+#ifndef CONFIG_H
+#  include "config.h"
+#endif
+
+/*----- Useful constants --------------------------------------------------*/
+
+#define MD5_HASHSIZE (16u)		/* Size of an MD5 hash */
+
+/*----- Type definitions --------------------------------------------------*/
+
+/* --- MD5 context buffer --- */
+
+typedef struct {
+  uint_32 val[4];			/* Result of the hash function */
+  unsigned long size;			/* Current size of data in bytes */
+  unsigned char buf[64];		/* Buffer accumulating next block */
+} md5;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @md5_trans@ --- *
+ *
+ * Arguments:	@unsigned char *v@ = pointer to chaining block (updated)
+ *		@const unsigned char *buf@ = pointer to input buffer
+ *
+ * Returns:	---
+ *
+ * Use:		Performs the MD5 transformation on a chunk of data.  This may
+ *		be useful for using MD5 in MDC-type cipher constructions.
+ */
+
+extern void md5_trans(unsigned char */*v*/, const unsigned char */*buf*/);
+
+/* --- @md5_buffer@ --- *
+ *
+ * Arguments:   @md5 *m@ = pointer to an MD5 context
+ *              @const void *buff@ = pointer to buffer of data
+ *              @size_t size@ = size of buffer
+ *
+ * Returns:	---
+ *
+ * Use:         Hashes the buffer of data.  You can call md5_buffer
+ *              lots of times during an MD5 job, to allow big files to be
+ *              split into little ones.
+ *
+ *              This routine could be improved lots, to compare with the
+ *              Straylight ARM assembler implementation, although that
+ *              requires lots of work.  Remember that the ARM version
+ *              doesn't need to do endianness-fiddling.
+ */
+
+extern void md5_buffer(md5 */*m*/, const void */*buff*/, size_t /*size*/);
+
+/* --- @md5_key@ --- *
+ *
+ * Arguments:	@md5 *m@ = pointer to an MD5 context
+ *		@const unsigned char *k@ = pointer to a 4-word `key'
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises a context buffer, with a chosen initialisation
+ *		string (instead of the standard MD5 value).  This allows you
+ *		to use NMAC message authentication, should the urge take you.
+ */
+
+extern void md5_key(md5 */*m*/, const unsigned char */*k*/);
+
+/* --- @md5_init@ --- *
+ *
+ * Arguments:   @md5 *m@ = pointer to an MD5 context
+ *
+ * Returns:	---
+ *
+ * Use:         Initialises the context buffer, so that you can do an
+ *              MD5 job.
+ */
+
+extern void md5_init(md5 */*m*/);
+
+/* --- @md5_final@ --- *
+ *
+ * Arguments:   @md5 *m@ = pointer to context buffer
+ *		@unsigned char *v@ = where to store the value
+ *
+ * Returns:	---
+ *
+ * Use:         Finalises an MD5 buffer, so that you can use the result.
+ */
+
+extern void md5_final(md5 */*m*/, unsigned char */*v*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/src/mdwopt.c b/src/mdwopt.c
new file mode 100644
index 0000000..af30491
--- /dev/null
+++ b/src/mdwopt.c
@@ -0,0 +1,766 @@
+/* -*-c-*-
+ *
+ * $Id: mdwopt.c,v 1.1 1997/07/21 13:47:47 mdw Exp $
+ *
+ * Options parsing, similar to GNU @getopt_long@
+ *
+ * (c) 1996 Mark Wooding
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of many programs.
+ *
+ * `mdwopt' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `mdwopt' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `mdwopt'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: mdwopt.c,v $
+ * Revision 1.1  1997/07/21 13:47:47  mdw
+ * Initial revision
+ *
+ * Revision 1.3  1997/02/26 00:41:10  mdw
+ * Added GPL notice to the top.  Slight formatting changes.
+ *
+ * Revision 1.2  1996/10/28 13:12:13  mdw
+ * Fixed calls to ctype.h routines.  Arguments are cast to unsigned char
+ * to avoid invoking undefined behaviour caused by signedness of chars.
+ *
+ * Revision 1.1  1996/09/24 18:01:28  mdw
+ * Initial revision
+ *
+ */
+
+/*----- External dependencies ---------------------------------------------*/
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mdwopt.h"
+
+/*----- Configuration things ----------------------------------------------*/
+
+#if defined(__riscos)
+#  define PATHSEP '.'
+#elif defined(__OS2__) || defined(__MSDOS__)
+#  define PATHSEP '\\'
+#else /* Assume a sane filing system */
+#  define PATHSEP '/'
+#endif
+
+/*----- Global variables --------------------------------------------------*/
+
+mdwopt_data mdwopt_global = {0, 0, 0, 1, 0, 0, 0, 0, 0};
+
+enum {
+  ord__permute = 0,			/* Permute the options (default) */
+  ord__return = 1,			/* Return non-option things */
+  ord__posix = 2,			/* Do POSIX-type hacking */
+  ord__negate = 4			/* Magic negate-next-thing flag */
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @mo__nextWord@ --- *
+ *
+ * Arguments:   @int argc@ = number of command line options
+ *              @char *argv[]@ = pointer to command line options
+ *              @mdwopt_data *data@ = pointer to persistent state
+ *
+ * Returns:     Pointer to the next word to handle, or 0
+ *
+ * Use:         Extracts the next word from the command line or environment
+ *              variable.
+ */
+
+static char *mo__nextWord(int argc, char *const *argv, mdwopt_data *data)
+{
+  if (data->ind == -1) {
+    char *p = data->env;
+    char *q;
+    while (isspace((unsigned char)*p))
+      p++;
+    q = p;
+    while (*p && !isspace((unsigned char)*p))
+      p++;
+    *p = 0;
+    data->env = p + 1;
+    if (p != q)
+      return (q);
+    free(data->estart);
+    data->env = 0;
+    data->ind = 1;
+  }
+
+  if (data->next == argc)
+    return (0);
+  return (argv[data->next++]);
+}
+
+/* --- @mo__permute@ --- *
+ *
+ * Arguments:   @char *argv[]@ = pointer to command line arguments
+ *              @mdwopt_data *data@ = pointer to persistent data
+ *
+ * Returns:     --
+ *
+ * Use:         Moves a command line option into the right place.
+ */
+
+static void mo__permute(char *const *argv, mdwopt_data *data)
+{
+  char **v = (char **)argv;
+  if (data->ind != -1) {
+    int i = data->next - 1;
+    char *p = v[i];
+    while (i > data->ind) {
+      v[i] = v[i - 1];
+      i--;
+    }
+    v[i] = p;
+    data->ind++;
+  }
+}
+
+/* --- @mo__findOpt@ --- *
+ *
+ * Arguments:   @int o@ = which option to search for
+ *              @const char *shortopt@ = short options string to search
+ *              @mdwopt_data *data@ = pointer to persistant state
+ *
+ * Returns:     Pointer to rest of short options string (including magic
+ *              characters)
+ *
+ * Use:         Looks up a short option in the given string.
+ */
+
+static const char *mo__findOpt(int o, const char *shortopt,
+			       mdwopt_data *data)
+{
+  const char *p = shortopt;		/* Point to short opts table */
+  for (;;) {
+    if (!*p)				/* No more options left */
+      return (0);
+
+    if (o != *p || (p[1] != '+' && data->order & ord__negate)) {
+      p++;				/* Skip this option entry */
+      while (*p == '+')			/* Jump a `%|+|%' sign */
+	p++;
+      while (*p == ':')			/* And jump any `%|:|%' characters */
+	p++;				/* Just in case there are any */
+    }
+    else
+      return (p + 1);
+  }
+}
+
+/* --- @mdwopt@ --- *
+ *
+ * Arguments:   @int argc@ = number of command line arguments
+ *              @char * const *argv@ = pointer to command line arguments
+ *              @const char *shortopt@ = pointer to short options information
+ *              @const struct option *longopts@ = pointer to long opts info
+ *              @int *longind@ = where to store matched longopt
+ *              @mdwopt_data *data@ = persistent state for the parser
+ *              @int flags@ = various useful flags
+ *
+ * Returns:     Value of option found next, or an error character, or
+ *              @EOF@ for the last thing.
+ *
+ * Use:         Reads options.  The routine should be more-or-less compatible
+ *              with standard getopts, although it provides many more
+ *              features even than the standard GNU implementation.
+ *
+ *              The precise manner of options parsing is determined by
+ *              various flag settings, which are described below.  By setting
+ *              flag values appropriately, you can achieve behaviour very
+ *              similar to most other getopt routines.
+ *
+ *
+ *          How options parsing appears to users
+ *
+ *              A command line consists of a number of `words' (which may
+ *              contain spaces, according to various shell quoting
+ *              conventions).  A word may be an option, an argument to an
+ *              option, or a non-option.  An option begins with a special
+ *              character, usually `%|-|%', although `%|+|%' is also used
+ *              sometimes.  As special exceptions, the word containing only a
+ *              `%|-|%' is considered to be a non-option, since it usually
+ *              represents standard input or output as a filename, and the
+ *              word containing a double-dash `%|--|%' is used to mark all
+ *              following words as being non-options regardless of their
+ *              initial character.
+ *
+ *              Traditionally, all words after the first non-option have been
+ *              considered to be non-options automatically, so that options
+ *              must be specified before filenames.  However, this
+ *              implementation can extract all the options from the command
+ *              line regardless of their position.  This can usually be
+ *              disabled by setting one of the environment variables
+ *              `%|POSIXLY_CORRECT|%' or `%|_POSIX_OPTION_ORDER|%'.
+ *
+ *              There are two different styles of options: `short' and
+ *              `long'.
+ *
+ *              Short options are the sort which Unix has known for ages: an
+ *              option is a single letter, preceded by a `%|-|%'.  Short
+ *              options can be joined together to save space (and possibly to
+ *              make silly words): e.g., instead of giving options
+ *              `%|-x -y|%', a user could write `%|-xy|%'.  Some short
+ *              options can have arguments, which appear after the option
+ *              letter, either immediately following, or in the next `word'
+ *              (so an option with an argument could be written as
+ *              `%|-o foo|%' or as `%|-ofoo|%').  Note that options with
+ *              optional arguments must be written in the second style.
+ *
+ *              When a short option controls a flag setting, it is sometimes
+ *              possible to explicitly turn the flag off, as well as turning
+ *              it on, (usually to override default options).  This is
+ *              usually done by using a `%|+|%' instead of a `%|-|%' to
+ *              introduce the option.
+ *
+ *              Long options, as popularised by the GNU utilities, are given
+ *              long-ish memorable names, preceded by a double-dash `%|--|%'.
+ *              Since their names are more than a single character, long
+ *              options can't be combined in the same way as short options.
+ *              Arguments to long options may be given either in the same
+ *              `word', separated from the option name by an equals sign, or
+ *              in the following `word'.
+ *
+ *              Long option names can be abbreviated if necessary, as long
+ *              as the abbreviation is unique.  This means that options can
+ *              have sensible and memorable names but still not require much
+ *              typing from an experienced user.
+ *
+ *              Like short options, long options can control flag settings.
+ *              The options to manipulate these settings come in pairs: an
+ *              option of the form `%|--set-flag|%' might set the flag, while
+ *              an option of the form `%|--no-set-flag|%' might clear it.
+ *
+ *              It is usual for applications to provide both short and long
+ *              options with identical behaviour.  Some applications with
+ *              lots of options may only provide long options (although they
+ *              will often be only two or three characters long).  In this
+ *              case, long options can be preceded with a single `%|-|%'
+ *              character, and negated by a `%|+|%' character.
+ *
+ *              Finally, some (older) programs accept arguments of the form
+ *              `%%@.{"-"<number>}%%', to set some numerical parameter,
+ *              typically a line count of some kind.
+ *
+ *
+ *          How programs parse options
+ *
+ *              An application parses its options by calling mdwopt
+ *              repeatedly.  Each time it is called, mdwopt returns a value
+ *              describing the option just read, and stores information about
+ *              the option in a data block.  The value %$-1$% is returned
+ *              when there are no more options to be read.  The `%|?|%'
+ *              character is returned when an error is encountered.
+ *
+ *              Before starting to parse options, the value @data->ind@ must
+ *              be set to 0 or 1. The value of @data->err@ can also be set,
+ *              to choose whether errors are reported by mdwopt.
+ *
+ *              The program's `@argc@' and `@argv@' arguments are passed to
+ *              the options parser, so that it can read the command line.  A
+ *              flags word is also passed, allowing the program fine control
+ *              over parsing.  The flags are described above.
+ *
+ *              Short options are described by a string, which once upon a
+ *              time just contained the permitted option characters.  Now the
+ *              options string begins with a collection of flag characters,
+ *              and various flag characters can be put after options
+ *              characters to change their properties.
+ *
+ *              If the first character of the short options string is
+ *              `%|+|%', `%|-|%' or `%|!|%', the order in which options are
+ *              read is modified, as follows:
+ *
+ *              `%|+|%' forces the POSIX order to be used. As soon as a non-
+ *                      option is found, mdwopt returns %$-1$%.
+ *
+ *              `%|-|%' makes mdwopt treat non-options as being `special'
+ *                      sorts of option. When a non-option word is found, the
+ *                      value 0 is returned, and the actual text of the word
+ *                      is stored as being the option's argument.
+ *
+ *              `%|!|%' forces the default order to be used.  The entire
+ *                      command line is scanned for options, which are
+ *                      returned in order.  However, during this process,
+ *                      the options are moved in the @argv@ array, so that
+ *                      they appear before the non- options.
+ *
+ *              A `%|:|%' character may be placed after the ordering flag (or
+ *              at the very beginning if no ordering flag is given) which
+ *              indicates that the character `%|:|%', rather than `%|?|%',
+ *              should be returned if a missing argument error is detected.
+ *
+ *              Each option in the string can be followed by a `%|+|%' sign,
+ *              indicating that it can be negated, a `%|:|%' sign indicating
+ *              that it requires an argument, or a `%|::|%' string,
+ *              indicating an optional argument.  Both `%|+|%' and `%|:|%' or
+ *              `%|::|%' may be given, although the `%|+|%' must come first.
+ *
+ *              If an option is found, the option character is returned to
+ *              the caller.  A pointer to an argument is stored in
+ *              @data->arg@, or @NULL@ is stored if there was no argument.
+ *              If a negated option was found, the option character is
+ *              returned ORred with @gFlag_negated@ (bit 8 set).
+ *
+ *              Long options are described in a table.  Each entry in the
+ *              table is of type @struct option@, and the table is terminated
+ *              by an entry whose @name@ field is null.  Each option has
+ *              a flags word which, due to historical reasons, is called
+ *              @has_arg@.  This describes various properties of the option,
+ *              such as what sort of argument it takes, and whether it can
+ *              be negated.
+ *
+ *              When mdwopt finds a long option, it looks the name up in the
+ *              table. The index of the matching entry is stored in the
+ *              @longind@ variable, passed to mdwopt (unless @longind@ is 0):
+ *              a value of %$-1$% indicates that no long option was
+ *              found. The behaviour is then dependent on the values in the
+ *              table entry.  If @flag@ is nonzero, it points to an integer
+ *              to be modified by mdwopt.  Usually the value in the @val@
+ *              field is simply stored in the @flag@ variable. If the flag
+ *              @gFlag_switch@ is set, however, the value is combined with
+ *              the existing value of the flags using a bitwise OR.  If
+ *              @gFlag_negate@ is set, then the flag bit will be cleared if a
+ *              matching negated long option is found.  The value 0 is
+ *              returned.
+ *
+ *              If @flag@ is zero, the value in @val@ is returned by mdwopt,
+ *              possibly with bit 8 set if the option was negated.
+ *
+ *              Arguments for long options are stored in @data->arg@, as
+ *              before.
+ *
+ *              Numeric options, if enabled, cause the value `%|#|%' to be
+ *              returned, and the numeric value to be stored in @data->opt@.
+ *
+ *              If the flag @gFlag_envVar@ is set on entry, options will be
+ *              extracted from an environment variable whose name is built by
+ *              capitalising all the letters of the program's name.  (This
+ *              allows a user to have different default settings for a
+ *              program, by calling it through different symbolic links.)  */
+
+int mdwopt(int argc, char *const *argv,
+	   const char *shortopt,
+	   const struct option *longopts, int *longind,
+	   mdwopt_data *data, int flags)
+{
+  /* --- Local variables --- */
+
+  char *p, *q, *r;			/* Some useful things to have */
+  char *prefix;				/* Prefix from this option */
+  int i;				/* Always useful */
+  char noarg = '?';			/* Standard missing-arg char */
+
+  /* --- Sort out our data --- */
+
+  if (!data)				/* If default data requested */
+    data = &mdwopt_global;		/* Then use the global stuff */
+
+  /* --- See if this is the first time --- */
+
+  if (data->ind == 0 || (data->ind == 1 && ~flags & gFlag_noProgName)) {
+
+    /* --- Sort out default returning order --- */
+
+    if (getenv("_POSIX_OPTION_ORDER") || /* Examine environment for opts */
+	getenv("POSIXLY_CORRECT"))	/* To see if we disable features */
+      data->order = ord__posix;		/* If set, use POSIX ordering */
+    else
+      data->order = ord__permute;	/* Otherwise mangle the options */
+
+    /* --- Now see what the caller actually wants --- */
+
+    switch (shortopt[0]) {		/* Look at the first character */
+      case '-':				/* `%|-|%' turns on in-orderness */
+	data->order = ord__return;
+	break;
+      case '+':				/* `%|+|%' turns on POSIXness */
+	data->order = ord__posix;
+	break;
+      case '!':				/* `%|!|%' ignores POSIXness */
+	data->order = ord__permute;
+	break;
+    }
+
+    /* --- Now decide on the program's name --- */
+
+    if (~flags & gFlag_noProgName) {
+      p = q = (char *)argv[0];
+      while (*p) {
+	if (*p++ == PATHSEP)
+	  q = p;
+      }
+      data->prog = q;
+
+      data->ind = data->next = 1;
+      data->list = 0;
+
+      /* --- See about environment variables --- *
+       *
+       * Be careful.  The program may be setuid, and an attacker might have
+       * given us a long name in @argv[0]@.  If the name is very long, don't
+       * support this option.
+       */
+
+      if (flags & gFlag_envVar && strlen(data->prog) < 48) {
+
+	char buf[64];
+
+	/* --- For RISC OS, support a different format --- *
+	 *
+	 * Acorn's RISC OS tends to put settings in variables named
+	 * `App$Options' rather than `APP'.  Under RISC OS, I'll support
+	 * both methods, just to avoid confuddlement.
+	 */
+
+#ifdef __riscos
+	sprintf(buf, "%s$Options", data->prog);
+	p = getenv(buf);
+	if (!p) {
+#endif
+
+	  p = buf;			/* Point to a buffer */
+	  q = data->prog;		/* Point to program name */
+	  while (*q)			/* While characters left here */
+	    *p++ = toupper(*q++);	/* Copy and uppercase */
+	  *p++ = 0;			/* Terminate my copy of this */
+	  p = getenv(buf);		/* Get the value of the variable */
+
+#ifdef __riscos
+	}
+#endif
+
+	/* --- Copy the options string into a buffer --- */
+
+	if (p) {			/* If it is defined */
+	  q = malloc(strlen(p) + 1);	/* Allocate space for a copy */
+	  if (!q) {			/* If that failed */
+	    fprintf(stderr,		/* Report a nice error */
+		    "%s: Not enough memory to read settings in "
+		    "environment variable\n",
+		    data->prog);
+	  } else {			/* Otherwise */
+	    strcpy(q, p);		/* Copy the text over */
+	    data->ind = -1;		/* Mark that we're parsing envvar */
+	    data->env = data->estart = q; /* And store the pointer away */
+	  }
+	}
+
+      }
+    }
+    else
+      data->ind = data->next = 0;
+  }
+
+  /* --- Do some initial bodgery --- *
+   *
+   * The @shortopt@ string can have some interesting characters at the
+   * beginning.  We'll skip past them.
+   */
+
+  switch (shortopt[0]) {
+    case '+':
+    case '-':
+    case '!':
+      shortopt++;
+      break;
+  }
+
+  if (shortopt[0] == ':') {
+    noarg = shortopt[0];
+    shortopt++;
+  }
+
+  if (longind)				/* Allow longind to be null */
+    *longind = -1;			/* Clear this to avoid confusion */
+  data->opt = -1;			/* And this too */
+  data->arg = 0;			/* No option set up here */
+
+  /* --- Now go off and search for an option --- */
+
+  if (!data->list || !*data->list) {
+    data->order &= 3;			/* Clear negation flag */
+
+    /* --- Now we need to find the next option --- *
+     *
+     * Exactly how we do this depends on the settings of the order variable.
+     * We identify options as being things starting with `%|-|%', and which
+     * aren't equal to `%|-|%' or `%|--|%'.  We'll look for options until:
+     *
+     *   * We find something which isn't an option AND @order == ord__posix@
+     *   * We find a `%|--|%'
+     *   * We reach the end of the list
+     *
+     * There are some added little wrinkles, which we'll meet as we go.
+     */
+
+    for (;;) {				/* Keep looping for a while */
+      p = mo__nextWord(argc, argv, data); /* Get the next word out */
+      if (!p)				/* If there's no next word */
+	return (EOF);			/* There's no more now */
+
+      /* --- See if we've found an option --- */
+
+      if ((p[0] == '-' || (p[0] == '+' && flags & gFlag_negation)) &&
+	  p[1] != 0) {
+	if (strcmp(p, "--") == 0) {	/* If this is the magic marker */
+	  mo__permute(argv, data);	/* Stow the magic marker item */
+	  return (EOF);			/* There's nothing else to do */
+	}
+	break;				/* We've found something! */
+      }
+
+      /* --- Figure out how to proceed --- */
+
+      switch (data->order & 3) {
+	case ord__posix:		/* POSIX option order */
+	  return (EOF);			/* This is easy */
+	  break;
+	case ord__permute:		/* Permute the option order */
+	  break;
+	case ord__return:		/* Return each argument */
+	  mo__permute(argv, data);	/* Insert word in same place */
+	  data->arg = p;		/* Point to the argument */
+	  return (0);			/* Return the value */
+      }
+    }
+
+    /* --- We found an option --- */
+
+    mo__permute(argv, data);		/* Do any permuting necessary */
+
+    /* --- Check for a numeric option --- *
+     *
+     * We only check the first character (or the second if the first is a
+     * sign).  This ought to be enough.
+     */
+
+    if (flags & gFlag_numbers && (p[0] == '-' || flags & gFlag_negNumber)) {
+      if (((p[1] == '+' || p[1] == '-') && isdigit((unsigned char)p[2])) ||
+	  isdigit((unsigned char)p[1])) {
+	data->opt = strtol(p + 1, &data->arg, 10);
+	while (isspace((unsigned char)data->arg[0]))
+	  data->arg++;
+	if (!data->arg[0])
+	  data->arg = 0;
+	return (p[0] == '-' ? '#' : '#' | gFlag_negated);
+      }
+    }
+
+    /* --- Check for a long option --- */
+
+    if (p[0] == '+')
+      data->order |= ord__negate;
+
+    if (((p[0] == '-' && p[1] == '-') ||
+	 (flags & gFlag_noShorts && !mo__findOpt(p[1], shortopt, data))) &&
+	(~flags & gFlag_noLongs))	/* Is this a long option? */
+    {
+      int match = -1;			/* Count matches as we go */
+
+      if (p[0] == '+') {		/* If it's negated */
+	data->order |= ord__negate;	/* Set the negate flag */
+	p++;				/* Point to the main text */
+	prefix = "+";			/* Set the prefix string up */
+      } else if (p[1] == '-') {		/* If this is a `%|--|%' option */
+	if ((flags & gFlag_negation) && strncmp(p + 2, "no-", 3) == 0) {
+	  p += 5;			/* Point to main text */
+	  prefix = "--no-";		/* And set the prefix */
+	  data->order |= ord__negate;	/* Set the negatedness flag */
+	} else {
+	  p += 2;			/* Point to the main text */
+	  prefix = "--";		/* Remember the prefix string */
+	}
+      } else {
+	if ((flags & gFlag_negation) && strncmp(p + 1, "no-", 3) == 0) {
+	  p += 4;			/* Find the text */
+	  prefix = "-no-";		/* Set the prefix */
+	  data->order |= ord__negate;	/* Set negatedness flag */
+	} else {
+	  p++;				/* Otherwise find the text */
+	  prefix = "-";			/* And remember the prefix */
+	}
+      }
+
+      for (i = 0; longopts[i].name; i++) { /* Loop through the options */
+	if ((data->order & ord__negate) &&
+	    (~longopts[i].has_arg & gFlag_negate))
+	  continue;			/* If neg and opt doesn't allow */
+
+	r = (char *) longopts[i].name;	/* Point to the name string */
+	q = p;				/* Point to the string start */
+	for (;;) {			/* Do a loop here */
+	  if (*q == 0 || *q == '=') {	/* End of the option string? */
+	    if (*r == 0) {		/* If end of other string */
+	      match = i;		/* This is the match */
+	      goto botched;		/* And exit the loop now */
+	    }
+	    if (match == -1) {		/* If no match currently */
+	      match = i;		/* Then this is it, here */
+	      break;			/* Stop looking now */
+	    } else {
+	      match = -1;		/* Else it's ambiguous */
+	      goto botched;		/* So give up right now */
+	    }
+	  }
+	  else if (*q != *r)		/* Otherwise if mismatch */
+	    break;			/* Abort this loop */
+	  q++, r++;			/* Increment the counters */
+	}
+      }
+
+    botched:
+      if (match == -1) {		/* If we couldn't find a match */
+	if (data->err) {
+	  fprintf(stderr, "%s: unrecognised option `%s%s'\n",
+		  data->prog,
+		  prefix, p);
+	}
+	return ('?');
+      }
+
+      if (longind)			/* Allow longind to be null */
+	*longind = match;		/* Store the match away */
+
+      /* --- Handle argument behaviour --- */
+
+      while (*p != 0 && *p != '=')	/* Find the argument string */
+	p++;
+      p = (*p ? p + 1 : 0);		/* Sort out argument presence */
+      q = (char *) longopts[match].name; /* Remember the name here */
+
+      switch (longopts[match].has_arg & 3) {
+	case no_argument:
+	  if (p) {
+	    if (data->err) {
+	      fprintf(stderr,
+		      "%s: option `%s%s' does not accept arguments\n",
+		      data->prog,
+		      prefix, q);
+	    }
+	    return ('?');
+	  }
+	  break;
+
+	case required_argument:
+	  if (!p) {			/* If no argument given */
+	    p = mo__nextWord(argc, argv, data);
+
+	    if (!p) {			/* If no more arguments */
+	      if (data->err) {
+		fprintf(stderr, "%s: option `%s%s' requires an argument\n",
+			data->prog,
+			prefix, q);
+	      }
+	      return (noarg);
+	    }
+
+	    mo__permute(argv, data);
+	  }
+	  break;
+
+	case optional_argument:
+	  /* Who cares? */
+	  break;
+      }
+      data->arg = p;
+
+      /* --- Do correct things now we have a match --- */
+
+      if (longopts[match].flag) {	/* If he has a @flag@ argument */
+	if (longopts[match].has_arg & gFlag_switch) {
+	  if (data->order & ord__negate)
+	    *longopts[match].flag &= ~longopts[match].val;
+	  else
+	    *longopts[match].flag |= longopts[match].val;
+	} else {
+	  if (data->order & ord__negate)
+	    *longopts[match].flag = 0;
+	  else
+	    *longopts[match].flag = longopts[match].val;
+	}
+	return (0);			/* And return something */
+      } else {
+	if (data->order & ord__negate)
+	  return (longopts[match].val | gFlag_negated);
+	else
+	  return (longopts[match].val);
+      }
+    }
+
+    /* --- Do short options things --- */
+
+    else {
+      if (p[0] == '+')			/* If starts with a `%|+|%' */
+	data->order |= ord__negate;
+      data->list = p + 1;		/* Omit leading `%|-|%'/`%|+|%' */
+    }
+  }
+
+  /* --- Now process the short options --- */
+
+  i = *data->list++;			/* Get the next option letter */
+  data->opt = i;			/* Store this away nicely */
+
+  p = (char *) mo__findOpt(i, shortopt, data);
+  if (!p) {				/* No more options left */
+    if (data->err) {
+      fprintf(stderr, "%s: unknown option `%c%c'\n",
+	      data->prog,
+	      data->order & ord__negate ? '+' : '-',
+	      i);
+    }
+    return ('?');
+  }
+
+  data->opt = i;			/* Store this for the caller */
+
+  /* --- Sort out an argument, if we expect one --- */
+
+  if (p[0] == ':') {			/* If we expect an option */
+    q = (data->list[0] ? data->list : 0); /* If argument expected, use it */
+    data->list = 0;			/* Kill the remaining options */
+    if (p[1] != ':' && !q) {		/* If no arg, and not optional */
+
+      /* --- Same code as before --- */
+
+      q = mo__nextWord(argc, argv, data); /* Read the next word */
+      if (!q) {				/* If no more arguments */
+	if (data->err) {
+	  fprintf(stderr, "%s: option `%c%c' requires an argument\n",
+		  data->prog,
+		  data->order & ord__negate ? '+' : '-',
+		  i);
+	}
+	return (noarg);
+      }
+      mo__permute(argv, data);
+    }
+
+    data->arg = q;
+  }
+  return ((data->order & ord__negate) ? i | gFlag_negated : i);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/mdwopt.h b/src/mdwopt.h
new file mode 100644
index 0000000..38e89e4
--- /dev/null
+++ b/src/mdwopt.h
@@ -0,0 +1,421 @@
+/* -*-c-*-
+ *
+ * $Id: mdwopt.h,v 1.1 1997/07/21 13:47:47 mdw Exp $
+ *
+ * Options parsing, similar to GNU @getopt_long@
+ *
+ * (c) 1996 Mark Wooding
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of many programs.
+ *
+ * `mdwopt' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `mdwopt' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `mdwopt'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: mdwopt.h,v $
+ * Revision 1.1  1997/07/21 13:47:47  mdw
+ * Initial revision
+ *
+ * Revision 1.4  1997/02/26 00:39:55  mdw
+ * Added GPL notice to the top.  Slight formatting changes.  Commented out
+ * arguments to functions.
+ *
+ * Revision 1.3  1996/12/31 19:41:33  mdw
+ * Formatting changes.
+ *
+ * Revision 1.2  1996/11/23 00:47:25  mdw
+ * Added `MdwOpt' object from the `anagram' source code.
+ *
+ * Revision 1.1  1996/09/24 18:01:43  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef MDWOPT_H
+#define MDWOPT_H
+
+/*----- Options handling structures ---------------------------------------*/
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/* --- @mdwopt_data@ --- *
+ *
+ * Contains all the information needed by the @mdwopt@ routine to do its
+ * work.
+ */
+
+typedef struct {
+  /* --- Public variables --- */
+
+  char *arg;				/* Arg of current option, or 0 */
+  int opt;				/* Value of current option */
+  int ind;				/* 0 for init, index when done */
+  int err;				/* Set nonzero for error messages */
+  char *prog;				/* Program name (from @argv[0]@) */
+
+  /* --- Private variables --- *
+   *
+   * Don't play with these, please.
+   */
+
+  char *list;				/* Current short options pointer */
+  int next;				/* Next argument, unpermuted */
+  int order;				/* Ordering of options, flags */
+  char *env;				/* Where we are in the env var */
+  char *estart;				/* Pointer to env var buffer */
+}
+mdwopt_data;
+
+/*----- Global variables --------------------------------------------------*/
+
+extern mdwopt_data mdwopt_global;	/* The default global data */
+
+/* --- For compatibility with older programs (and prettiness) --- *
+ *
+ * The macros here access the global structure defined above.  I consider it
+ * to be perfectly acceptable to use these macros in new code, because it
+ * looks nicer than playing with @mdwopt_global@.
+ */
+
+#define optarg (mdwopt_global.arg)	/* Argument of current option */
+#define optopt (mdwopt_global.opt)	/* Code of current option */
+#define opterr (mdwopt_global.err)	/* Zero to report error messages */
+#define optind (mdwopt_global.ind)	/* Index of first non-option */
+#define optprog (mdwopt_global.prog)	/* Pointer to program name */
+
+/*----- Type definitions --------------------------------------------------*/
+
+/* --- Long options definition table --- */
+
+struct option {
+  const char *name;			/* Name of the long option */
+  int has_arg;				/* Does it have an argument? */
+  int *flag;				/* Address of flag variable */
+  int val;				/* Value to store/return */
+};
+
+/* --- Old-style names for argument flags in long options table --- */
+
+enum {
+  no_argument,				/* No argument required */
+  required_argument,			/* User must specify argument */
+  optional_argument			/* Argument is optional */
+};
+
+/* --- New style flag names --- */
+
+enum {
+  gFlag_argReq = 1,			/* Required argument */
+  gFlag_argOpt = 2,			/* Optional argument */
+  gFlag_switch = 4,			/* OR val into flag, don't store */
+  gFlag_negate = 8,			/* Allow long option to be negated */
+  gFlag__last_long_opt_flag = 0		/* Dummy value */
+};
+
+enum {
+  gFlag_noLongs = 1,			/* Don't read long options */
+  gFlag_noShorts = 2,			/* Don't read short options */
+  gFlag_numbers = 4,			/* Read numeric options */
+  gFlag_negation = 8,			/* Allow `%|+|%' for negations */
+  gFlag_envVar = 16,			/* Parse options from env var */
+  gFlag_noProgName = 32,		/* Don't set @optprog@  */
+  gFlag_negNumber = 64,			/* Allow negated number options */
+  gFlag__last_mdwopt_flag = 0		/* Dummy value */
+};
+
+enum {
+  gFlag_negated = 256,			/* Option flag was negated by user */
+  gFlag__last_return_flag = 0		/* Dummy value */
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @mdwopt@ --- *
+ *
+ * Arguments:   @int argc@ = number of command line arguments
+ *              @char * const *argv@ = pointer to command line arguments
+ *              @const char *shortopt@ = pointer to short options information
+ *              @const struct option *longopts@ = pointer to long opts info
+ *              @int *longind@ = where to store matched longopt
+ *              @mdwopt_data *data@ = persistent state for the parser
+ *              @int flags@ = various useful flags
+ *
+ * Returns:     Value of option found next, or an error character, or
+ *              @EOF@ for the last thing.
+ *
+ * Use:         Reads options.  The routine should be more-or-less compatible
+ *              with standard getopts, although it provides many more
+ *              features even than the standard GNU implementation.
+ *
+ *              The precise manner of options parsing is determined by
+ *              various flag settings, which are described below.  By setting
+ *              flag values appropriately, you can achieve behaviour very
+ *              similar to most other getopt routines.
+ *
+ *
+ *          How options parsing appears to users
+ *
+ *              A command line consists of a number of `words' (which may
+ *              contain spaces, according to various shell quoting
+ *              conventions).  A word may be an option, an argument to an
+ *              option, or a non-option.  An option begins with a special
+ *              character, usually `%|-|%', although `%|+|%' is also used
+ *              sometimes.  As special exceptions, the word containing only a
+ *              `%|-|%' is considered to be a non-option, since it usually
+ *              represents standard input or output as a filename, and the
+ *              word containing a double-dash `%|--|%' is used to mark all
+ *              following words as being non-options regardless of their
+ *              initial character.
+ *
+ *              Traditionally, all words after the first non-option have been
+ *              considered to be non-options automatically, so that options
+ *              must be specified before filenames.  However, this
+ *              implementation can extract all the options from the command
+ *              line regardless of their position.  This can usually be
+ *              disabled by setting one of the environment variables
+ *              `%|POSIXLY_CORRECT|%' or `%|_POSIX_OPTION_ORDER|%'.
+ *
+ *              There are two different styles of options: `short' and
+ *              `long'.
+ *
+ *              Short options are the sort which Unix has known for ages: an
+ *              option is a single letter, preceded by a `%|-|%'.  Short
+ *              options can be joined together to save space (and possibly to
+ *              make silly words): e.g., instead of giving options
+ *              `%|-x.-y|%', a user could write `%|-xy|%'.  Some short
+ *              options can have arguments, which appear after the option
+ *              letter, either immediately following, or in the next `word'
+ *              (so an option with an argument could be written as
+ *              `%|-o foo|%' or as `%|-ofoo|%').  Note that options with
+ *		optional arguments must be written in the second style.
+ *
+ *              When a short option controls a flag setting, it is sometimes
+ *              possible to explicitly turn the flag off, as well as turning
+ *              it on, (usually to override default options).  This is
+ *              usually done by using a `%|+|%' instead of a `%|-|%' to
+ *              introduce the option.
+ *
+ *              Long options, as popularised by the GNU utilities, are given
+ *              long-ish memorable names, preceded by a double-dash `%|--|%'.
+ *              Since their names are more than a single character, long
+ *              options can't be combined in the same way as short options.
+ *              Arguments to long options may be given either in the same
+ *              `word', separated from the option name by an equals sign, or
+ *              in the following `word'.
+ *
+ *              Long option names can be abbreviated if necessary, as long
+ *              as the abbreviation is unique.  This means that options can
+ *              have sensible and memorable names but still not require much
+ *              typing from an experienced user.
+ *
+ *              Like short options, long options can control flag settings.
+ *              The options to manipulate these settings come in pairs: an
+ *              option of the form `%|--set-flag|%' might set the flag, while
+ *              an option of the form `%|--no-set-flag|%' might clear it.
+ *
+ *              It is usual for applications to provide both short and long
+ *              options with identical behaviour.  Some applications with
+ *              lots of options may only provide long options (although they
+ *              will often be only two or three characters long).  In this
+ *              case, long options can be preceded with a single `%|-|%'
+ *              character, and negated by a `%|+|%' character.
+ *
+ *              Finally, some (older) programs accept arguments of the form
+ *              `%%@.{"-"<number>}%%', to set some numerical parameter,
+ *              typically a line count of some kind.
+ *
+ *
+ *          How programs parse options
+ *
+ *              An application parses its options by calling mdwopt
+ *              repeatedly.  Each time it is called, mdwopt returns a value
+ *              describing the option just read, and stores information about
+ *              the option in a data block.  The value %$-1$% is returned
+ *              when there are no more options to be read.  The `%|?|%'
+ *              character is returned when an error is encountered.
+ *
+ *              Before starting to parse options, the value @data->ind@ must
+ *              be set to 0 or 1. The value of @data->err@ can also be set,
+ *              to choose whether errors are reported by mdwopt.
+ *
+ *              The program's `@argc@' and `@argv@' arguments are passed to
+ *              the options parser, so that it can read the command line.  A
+ *              flags word is also passed, allowing the program fine control
+ *              over parsing.  The flags are described above.
+ *
+ *              Short options are described by a string, which once upon a
+ *              time just contained the permitted option characters.  Now the
+ *              options string begins with a collection of flag characters,
+ *              and various flag characters can be put after options
+ *              characters to change their properties.
+ *
+ *              If the first character of the short options string is
+ *              `%|+|%', `%|-|%' or `%|!|%', the order in which options are
+ *              read is modified, as follows:
+ *
+ *              `%|+|%' forces the POSIX order to be used. As soon as a non-
+ *                      option is found, mdwopt returns %$-1$%.
+ *
+ *              `%|-|%' makes mdwopt treat non-options as being `special'
+ *                      sorts of option. When a non-option word is found, the
+ *                      value 0 is returned, and the actual text of the word
+ *                      is stored as being the option's argument.
+ *
+ *              `%|!|%' forces the default order to be used.  The entire
+ *                      command line is scanned for options, which are
+ *                      returned in order.  However, during this process,
+ *                      the options are moved in the @argv@ array, so that
+ *                      they appear before the non- options.
+ *
+ *              A `%|:|%' character may be placed after the ordering flag (or
+ *              at the very beginning if no ordering flag is given) which
+ *              indicates that the character `%|:|%', rather than `%|?|%',
+ *              should be returned if a missing argument error is detected.
+ *
+ *              Each option in the string can be followed by a `%|+|%' sign,
+ *              indicating that it can be negated, a `%|:|%' sign indicating
+ *              that it requires an argument, or a `%|::|%' string,
+ *              indicating an optional argument.  Both `%|+|%' and `%|:|%' or
+ *              `%|::|%' may be given, although the `%|+|%' must come first.
+ *
+ *              If an option is found, the option character is returned to
+ *              the caller.  A pointer to an argument is stored in
+ *              @data->arg@, or @NULL@ is stored if there was no argument.
+ *              If a negated option was found, the option character is
+ *              returned ORred with @gFlag_negated@ (bit 8 set).
+ *
+ *              Long options are described in a table.  Each entry in the
+ *              table is of type @struct option@, and the table is terminated
+ *              by an entry whose @name@ field is null.  Each option has
+ *              a flags word which, due to historical reasons, is called
+ *              @has_arg@.  This describes various properties of the option,
+ *              such as what sort of argument it takes, and whether it can
+ *              be negated.
+ *
+ *              When mdwopt finds a long option, it looks the name up in the
+ *              table. The index of the matching entry is stored in the
+ *              @longind@ variable, passed to mdwopt (unless @longind@ is 0):
+ *              a value of %$-1$% indicates that no long option was
+ *              found. The behaviour is then dependent on the values in the
+ *              table entry.  If @flag@ is nonzero, it points to an integer
+ *              to be modified by mdwopt.  Usually the value in the @val@
+ *              field is simply stored in the @flag@ variable. If the flag
+ *              @gFlag_switch@ is set, however, the value is combined with
+ *              the existing value of the flags using a bitwise OR.  If
+ *              @gFlag_negate@ is set, then the flag bit will be cleared if a
+ *              matching negated long option is found.  The value 0 is
+ *              returned.
+ *
+ *              If @flag@ is zero, the value in @val@ is returned by mdwopt,
+ *              possibly with bit 8 set if the option was negated.
+ *
+ *              Arguments for long options are stored in @data->arg@, as
+ *              before.
+ *
+ *              Numeric options, if enabled, cause the value `%|#|%' to be
+ *              returned, and the numeric value to be stored in @data->opt@.
+ *
+ *              If the flag @gFlag_envVar@ is set on entry, options will be
+ *              extracted from an environment variable whose name is built by
+ *              capitalising all the letters of the program's name.  (This
+ *              allows a user to have different default settings for a
+ *              program, by calling it through different symbolic links.)  */
+
+extern int mdwopt(int /*argc*/, char *const */*argv*/,
+		  const char */*shortopt*/,
+		  const struct option */*longopts*/, int */*longind*/,
+		  mdwopt_data */*data*/, int /*flags*/);
+
+/* --- Macros for more commonly used routines --- */
+
+#define getopt(c, v, o) mdwopt(c, v, o, 0, 0, 0, gFlag_noLongs)
+#define getopt_long(c, v, o, l, li) mdwopt(c, v, o, l, li, 0, 0)
+#define getopt_long_only(c, v, o, l, li)				\
+  mdwopt(c, v, o, l, li, 0, gFlag_noShorts)
+
+#ifdef __cplusplus
+}
+#endif
+
+/*----- C++ wrapper class -------------------------------------------------*/
+
+#ifdef __cplusplus
+
+/* --- Class: @MdwOpt@ --- *
+ *
+ * Parent:      ---
+ *
+ * Methods:     @MdwOpt@ -- construct a new mdwopt object with the given
+ *                      arguments.  These are remembered for later use.
+ *              @arg@ -- return the argument of the current option
+ *                      arguments.  These are remembered for later use.
+ *              @arg@ -- return the argument of the current option
+ *              @opt@ -- return the value of the current option
+ *		@ind@ -- return the index of the next unread argument
+ *		@longind@ -- return index of current long option in table
+ *              @errors@ -- return or set whether we report errors to the
+ *                      user
+ *              @prog@ -- return program name from @argv[0]@
+ *              @next@ -- return next option read from the table
+ *
+ * Use:         A simple C++ class for encapsulating the options parser.
+ *		The methods are all nice and simple, and extremely similar
+ *		to the normal C interface described above.
+ */
+
+class MdwOpt {
+  protected:
+    int argc;
+    char * const *argv;
+    const char *shortopts;
+    const struct option *longopts;
+    int long_ind;
+    int flags;
+
+    mdwopt_data data;
+
+  public:
+    MdwOpt(int c, char * const *v, const char *so,
+	   const struct option *lo, int f=0) :
+      argc(c), argv(v), shortopts(so), longopts(lo), flags(f) {
+	data.ind = 0;
+	data.err = 1;
+    }
+
+    const char *arg(void) const { return (data.arg); }
+    int opt(void) const { return (data.opt); }
+    int errors(void) const { return (data.err); }
+    int errors(int e) { int oe = data.err; data.err = e; return (oe); }
+    int ind(void) const { return (data.ind); }
+    int longind(void) const { return (long_ind); }
+    const char *prog(void) const { return (data.prog); }
+
+    int next(void) {
+      return (mdwopt(argc, argv, shortopts,
+		     longopts, &long_ind, &data, flags));
+    }
+};
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#endif
diff --git a/src/name.c b/src/name.c
new file mode 100644
index 0000000..faa06ab
--- /dev/null
+++ b/src/name.c
@@ -0,0 +1,248 @@
+/* -*-c-*-
+ *
+ * $Id: name.c,v 1.1 1997/07/21 13:47:46 mdw Exp $
+ *
+ * Looking up of names in symbol tables
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: name.c,v $
+ * Revision 1.1  1997/07/21 13:47:46  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Unix headers --- */
+
+#include <grp.h>
+#include <pwd.h>
+
+/* --- Local headers --- */
+
+#include "class.h"
+#include "name.h"
+#include "sym.h"
+#include "userdb.h"
+#include "utils.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static sym_table name__table;		/* Symbol table for everything */
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @name_init@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises the name table.  Requires the user database to
+ *		be populated (see @userdb_local@ and @userdb_yp@).
+ */
+
+void name_init(void)
+{
+  /* --- Initialise the name table --- */
+
+  sym_createTable(&name__table);
+
+  /* --- Insert all the users and groups into the table --- */
+
+  {
+    struct passwd *pw;
+    struct group *gr;
+
+    userdb_iterateUsers();
+    while ((pw = userdb_nextUser()) != 0) {
+      unsigned f;
+      name *n;
+      int u;
+
+      /* --- First, add the user to the table --- */
+
+      n = sym_find(&name__table, pw->pw_name, -1, sizeof(name), &f);
+      if (!f) {
+	sym_table *t = xmalloc(sizeof(*t));
+	sym_createTable(t);
+	n->c = class_create(clType_user, t);
+      }
+      u = pw->pw_uid;
+      sym_find(n->c->t, (char *)&u, sizeof(u), sizeof(sym_base), 0);
+
+      /* --- Now handle the user's default group --- */
+
+      if ((gr = userdb_groupById(pw->pw_gid)) != 0) {
+	n = sym_find(&name__table, gr->gr_name, -1, sizeof(name), &f);
+	if (!f) {
+	  sym_table *t = xmalloc(sizeof(*t));
+	  sym_createTable(t);
+	  n->c = class_create(clType_user, t);
+	}
+	sym_find(n->c->t, (char *)&u, sizeof(u), sizeof(sym_base), 0);
+      }
+    }
+  }
+
+  /* --- Now get the subsidiary groups --- */
+
+  {
+    struct group *gr;
+    struct passwd *pw;
+    char **p;
+
+    userdb_iterateGroups();
+    while ((gr = userdb_nextGroup()) != 0) {
+      unsigned f;
+      name *n;
+      int u;
+
+      n = sym_find(&name__table, gr->gr_name, -1, sizeof(name), &f);
+      if (!f) {
+	sym_table *t = xmalloc(sizeof(*t));
+	sym_createTable(t);
+	n->c = class_create(clType_user, t);
+      }
+
+      for (p = gr->gr_mem; *p; p++) {
+	if ((pw = userdb_userByName(*p)) != 0) {
+	  u = pw->pw_uid;
+	  sym_find(n->c->t, (char *)&u, sizeof(u), sizeof(sym_base), 0);
+	}
+      }
+    }
+  }
+
+  /* --- Finally add in the `all' class --- *
+   *
+   * Do that now, to prevent it being overwritten by the above.
+   */
+
+  {
+    name *n;
+    unsigned f;
+
+    n = sym_find(&name__table, "all", -1, sizeof(name), &f);
+    if (f)
+      class_dec(n->c);
+    n->c = class_all;
+  }
+}
+
+/* --- @name_reinit@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Reinitialises the names table.  It's cleared and then
+ *		initialised with the current user and group ids as for
+ *		@name_init@ above.
+ */
+
+void name_reinit(void)
+{
+  /* --- Empty the symbol table --- */
+
+  {
+    sym_iter i;
+    name *n;
+
+    for (sym_createIter(&i, &name__table); (n = sym_next(&i)) != 0; ) {
+      if (n->c)
+	class_dec(n->c);
+    }
+  }
+
+  /* --- Destroy and recreate the table --- */
+
+  sym_destroyTable(&name__table);
+  name_init();
+}
+
+/* --- @name_find@ --- *
+ *
+ * Arguments:	@const char *p@ = pointer to name to look up
+ *		@unsigned create@ = whether to create the item
+ *		@unsigned *f@ = whether the item was created
+ *
+ * Returns:	Pointer to a @name@ block containing the symbol, or
+ *		zero if it wasn't found and we didn't want to create a
+ *		new one.
+ *
+ * Use:		Looks up a name in the symbol table and returns the
+ *		item so located.
+ */
+
+name *name_find(const char *p, unsigned create, unsigned *f)
+{
+  /* --- This is a trivial veneer onto @sym_find@ --- */
+
+  return (sym_find(&name__table, p, -1, create ? sizeof(name) : 0, f));
+}
+
+/* --- @name_dump@ --- *
+ *
+ * Arguments:	@FILE *fp@ = stream to dump on
+ *
+ * Returns:	---
+ *
+ * Use:		Dumps a complete listing of the symbol table.
+ */
+
+void name_dump(FILE *fp)
+{
+  sym_iter i;
+  name *n;
+
+  for (sym_createIter(&i, &name__table); (n = sym_next(&i)) != 0; ) {
+    fprintf(fp, "\n--- name `%s'\n", n->base.name);
+    class_dump(n->c, fp);
+  }
+}  
+
+/*----- Test driver -------------------------------------------------------*/
+
+#ifdef TEST_RIG
+
+int main(void)
+{
+  userdb_init();
+  userdb_local();
+  userdb_yp();
+  name_init();
+  name_dump(stdout);
+  return (0);
+}
+
+#endif
diff --git a/src/name.h b/src/name.h
new file mode 100644
index 0000000..8d94ed5
--- /dev/null
+++ b/src/name.h
@@ -0,0 +1,123 @@
+/* -*-c-*-
+ *
+ * $Id: name.h,v 1.1 1997/07/21 13:47:46 mdw Exp $
+ *
+ * Looking up of names in symbol tables
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: name.h,v $
+ * Revision 1.1  1997/07/21 13:47:46  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef NAMES_H
+#define NAMES_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#ifndef CLASS_H
+#  include "class.h"
+#endif
+
+#ifndef SYM_H
+#  include "sym.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct name {
+  sym_base base;			/* Base block for symbol table */
+  classdef *c;				/* Base class pointer */
+} name;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @name_init@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises the name table.  Requires the user database to
+ *		be populated (see @userdb_local@ and @userdb_yp@).
+ */
+
+extern void name_init(void);
+
+/* --- @name_reinit@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Reinitialises the names table.  It's cleared and then
+ *		initialised with the current user and group ids as for
+ *		@name_init@ above.
+ */
+
+extern void name_reinit(void);
+
+/* --- @name_find@ --- *
+ *
+ * Arguments:	@const char *p@ = pointer to name to look up
+ *		@unsigned create@ = whether to create the item
+ *		@unsigned *f@ = whether the item was created
+ *
+ * Returns:	Pointer to a @name@ block containing the symbol, or
+ *		zero if it wasn't found and we didn't want to create a
+ *		new one.
+ *
+ * Use:		Looks up a name in the symbol table and returns the
+ *		item so located.
+ */
+
+extern name *name_find(const char */*p*/,
+		       unsigned /*create*/,
+		       unsigned */*f*/);
+
+/* --- @name_dump@ --- *
+ *
+ * Arguments:	@FILE *fp@ = stream to dump on
+ *
+ * Returns:	---
+ *
+ * Use:		Dumps a complete listing of the symbol table.
+ */
+
+extern void name_dump(FILE */*fp*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/src/parser.h b/src/parser.h
new file mode 100644
index 0000000..cd64a67
--- /dev/null
+++ b/src/parser.h
@@ -0,0 +1,79 @@
+/* -*-c-*-
+ *
+ * $Id: parser.h,v 1.1 1997/07/21 13:47:46 mdw Exp $
+ *
+ * Parser for `become.conf' files
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: parser.h,v $
+ * Revision 1.1  1997/07/21 13:47:46  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef PARSER_H
+#define PARSER_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#ifndef CLASS_H
+#  include "class.h"
+#endif
+
+#ifndef NAME_H
+#  include "name.h"
+#endif
+
+#include "parser.tab.h"			/* Token definitions and things */
+
+/*----- Global variables --------------------------------------------------*/
+
+extern int yydebug;			/* Debugging flag for parser */
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @yyparse@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	Zero if it worked, nonzero if it didn't.
+ *
+ * Use:		Parses configuration files.
+ */
+
+extern int yyparse(void);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/src/parser.y b/src/parser.y
new file mode 100644
index 0000000..0a8462f
--- /dev/null
+++ b/src/parser.y
@@ -0,0 +1,507 @@
+/* -*-c-*-
+ *
+ * $Id: parser.y,v 1.1 1997/07/21 13:47:45 mdw Exp $
+ *
+ * Parser for `become.conf' files
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: parser.y,v $
+ * Revision 1.1  1997/07/21 13:47:45  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+%{
+
+/* --- ANSI headers --- */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Unix headers --- */
+
+#include <sys/types.h>
+
+#include <pwd.h>
+#include <unistd.h>
+
+/* --- Local headers --- */
+
+#include "class.h"
+#include "daemon.h"
+#include "lexer.h"
+#include "name.h"
+#include "rule.h"
+#include "set.h"
+#include "sym.h"
+#include "userdb.h"
+#include "utils.h"
+
+%}
+/*----- Stack type --------------------------------------------------------*/
+
+%union {
+  long i;
+  char *s;
+  name *n;
+  classdef *c;
+}
+
+/*----- Token and rule declarations ---------------------------------------*/
+
+/* --- Tokens --- */
+
+%token BADTOKEN
+%token USER
+%token COMMAND
+%token HOST
+%token ALLOW
+%token PORT
+%token KEYFILE
+%token <i> INT
+%token <s> WORD
+%token <s> STRING
+%token ARROW
+
+%left ','
+%left '-'
+%left '|'
+%left '&'
+
+/* --- Rules --- */
+
+%type <c> user_class command_class host_class
+%type <c> user_class_opt command_class_opt host_class_opt
+%type <n> name
+
+/*----- Error reporting ---------------------------------------------------*/
+%{
+
+#define YYDEBUG 1
+#define YYERROR_VERBOSE
+
+/* --- @yyprint@ --- *
+ *
+ * Arguments:	@FILE *fp@ = pointer to stream to write on
+ *		@int type@ = pointer to token type
+ *		@YYSTYPE v@ = token value
+ *
+ * Returns:	---
+ *
+ * Use:		Displays the semantic value of a token.
+ */
+
+#define YYPRINT(fp, type, value) yyprint(fp, type, value)
+
+static void yyprint(FILE *fp, int type, YYSTYPE v)
+{
+  switch (type) {
+    case INT:
+      fprintf(fp, " %li", v.i);
+      break;
+    case WORD:
+    case STRING:
+      fprintf(fp, " `%s'", v.s);
+      break;
+  }
+}
+
+/* --- @yyerror@ --- *
+ *
+ * Arguments:	@const char *msg@ = pointer to error message
+ *
+ * Returns:	---
+ *
+ * Use:		Reports parse errors.
+ */
+
+static void yyerror(const char *msg)
+{
+  moan("%s at line %i", msg, lex_line);
+}
+
+%}
+%%
+/*----- The actual grammar ------------------------------------------------*/
+
+/* --- Simple driver things --- */
+
+file		: /* empty */
+		| file statement
+		;
+
+statement	: user_spec
+		| command_spec
+		| host_spec
+		| allow_spec
+		| port_spec
+		| key_spec
+		| error ';'
+		;
+
+/* --- Main statement types --- */
+
+user_spec	: USER name '=' user_class ';' {
+			  if ($2->c)
+			    class_dec($2->c);
+			  $2->c = $4;
+			}
+		;
+
+command_spec	: COMMAND name '=' command_class ';' {
+			  if ($2->c)
+			    class_dec($2->c);
+			  $2->c = $4;
+			}
+		;
+
+host_spec	: HOST name '=' host_class ';' {
+			  if ($2->c)
+			    class_dec($2->c);
+			  $2->c = $4;
+			}
+		;
+
+port_spec	: PORT INT ';' { daemon_usePort($2); }
+		;
+
+key_spec	: KEYFILE STRING ';' { daemon_readKey($2); }
+
+/* --- Parsing allow specifications --- */
+
+allow_spec	: ALLOW host_class_opt user_class ARROW
+		   user_class_opt command_class_opt ';' {
+			  rule_add($2, $3, $5, $6);
+			}
+
+host_class_opt	: /* empty */ { $$ = class_all; }
+		| '[' host_class ']' { $$ = $2; }
+		;
+
+user_class_opt	: /* empty */ { $$ = class_all; }
+		| user_class { $$ = $1; }
+		;
+
+command_class_opt : /* empty */ { $$ = class_all; }
+		| ':' command_class { $$ = $2; }
+		;
+
+/* --- Names get translated into symbols quickly --- */
+
+name		: WORD	{
+			  unsigned f;
+			  name *n = name_find($1, 1, &f);
+			  if (!f)
+			    n->c = 0;
+ 			  $$ = n;
+			}
+
+/*----- Various class expression types ------------------------------------*
+ *
+ * Unfortunately, all these need to handle token types slightly differently
+ * and I can't be bothered to remember the current state.
+ */
+
+/* --- User class expressions --- */
+
+user_class	: user_class ',' user_class {
+			  if ($1->type != $3->type) {
+			    yyerror("type mismatch");
+			    class_dec($1);
+			    class_dec($3);
+			    YYERROR;
+			  } else {
+			    if ($1 == class_all)
+			      $$ = class_all;
+			    else
+			      $$ = class_create($1->type,
+						set_union($1->t, $3->t));
+			    class_dec($1);
+			    class_dec($3);
+			  }
+			}
+		| user_class '-' user_class {
+			  if ($1->type != $3->type) {
+			    yyerror("type mismatch");
+			    class_dec($1);
+			    class_dec($3);
+			    YYERROR;
+			  } else {
+			    $$ = class_create($1->type,
+					      set_subtract($1->t, $3->t));
+			    class_dec($1);
+			    class_dec($3);
+			  }
+			}
+		| user_class '&' user_class {
+			  if ($1->type != $3->type) {
+			    yyerror("type mismatch");
+			    class_dec($1);
+			    class_dec($3);
+			    YYERROR;
+			  } else {
+			    if ($1 == class_all)
+			      $$ = class_all;
+			    else
+			      $$ = class_create($1->type,
+						set_intersect($1->t, $3->t));
+			    class_dec($1);
+			    class_dec($3);
+			  }
+			}
+		| user_class '|' user_class {
+			  if ($1->type != $3->type) {
+			    yyerror("type mismatch");
+			    class_dec($1);
+			    class_dec($3);
+			    YYERROR;
+			  } else {
+			    if ($1 == class_all)
+			      $$ = class_all;
+			    else
+			      $$ = class_create($1->type,
+						set_union($1->t, $3->t));
+			    class_dec($1);
+			    class_dec($3);
+			  }
+			}
+		| INT	{
+			  sym_table *t = xmalloc(sizeof(*t));
+			  int u = $1;
+			  sym_createTable(t);
+			  sym_find(t, (char *)&u, sizeof(u),
+				   sizeof(sym_base), 0);
+			  $$ = class_create(clType_user, t);
+			}
+		| STRING {
+			  struct passwd *pw;
+			  sym_table *t;
+			  int u;
+			  if ((pw = userdb_userByName($1)) == 0) {
+			    moan("user `%s' not known at line %i",
+				 $1, lex_line);
+			    YYERROR;
+			  } else {
+			    t = xmalloc(sizeof(*t));
+			    u = pw->pw_uid;
+			    sym_createTable(t);
+			    sym_find(t, (char *)&u, sizeof(u),
+				     sizeof(sym_base), 0);
+			    $$ = class_create(clType_user, t);
+			  }
+			}
+		| WORD	{
+			  name *n = name_find($1, 0, 0);
+			  if (!n || !n->c) {
+			    moan("class `%s' not found at line %i",
+				 $1, lex_line);
+			    YYERROR;
+			  } else if (~n->c->type & clType_user) {
+			    yyerror("type mismatch");
+			    YYERROR;
+			  } else {
+			    $$ = n->c;
+			    class_inc(n->c);
+			  }
+			}
+		| '(' user_class ')' { $$ = $2; }
+		;
+
+/* --- Command class expressions --- */
+
+command_class	: command_class ',' command_class {
+			  if ($1->type != $3->type) {
+			    yyerror("type mismatch");
+			    class_dec($1);
+			    class_dec($3);
+			    YYERROR;
+			  } else {
+			    if ($1 == class_all)
+			      $$ = class_all;
+			    else
+			      $$ = class_create($1->type,
+						set_union($1->t, $3->t));
+			    class_dec($1);
+			    class_dec($3);
+			  }
+			}
+		| command_class '-' command_class {
+			  if ($1->type != $3->type) {
+			    yyerror("type mismatch");
+			    class_dec($1);
+			    class_dec($3);
+			    YYERROR;
+			  } else {
+			    $$ = class_create($1->type,
+					      set_subtract($1->t, $3->t));
+			    class_dec($1);
+			    class_dec($3);
+			  }
+			}
+		| command_class '&' command_class {
+			  if ($1->type != $3->type) {
+			    yyerror("type mismatch");
+			    class_dec($1);
+			    class_dec($3);
+			    YYERROR;
+			  } else {
+			    if ($1 == class_all)
+			      $$ = class_all;
+			    else
+			      $$ = class_create($1->type,
+						set_intersect($1->t, $3->t));
+			    class_dec($1);
+			    class_dec($3);
+			  }
+			}
+		| command_class '|' command_class {
+			  if ($1->type != $3->type) {
+			    yyerror("type mismatch");
+			    class_dec($1);
+			    class_dec($3);
+			    YYERROR;
+			  } else {
+			    if ($1 == class_all)
+			      $$ = class_all;
+			    else
+			      $$ = class_create($1->type,
+						set_union($1->t, $3->t));
+			    class_dec($1);
+			    class_dec($3);
+			  }
+			}
+		| STRING {
+			  sym_table *t = xmalloc(sizeof(*t));
+			  sym_createTable(t);
+			  sym_find(t, $1, -1, sizeof(sym_base), 0);
+			  $$ = class_create(clType_command, t);
+			}
+		| WORD	{
+			  name *n = name_find($1, 0, 0);
+			  if (!n || !n->c) {
+			    moan("class `%s' not found at line %i",
+				 $1, lex_line);
+			    YYERROR;
+			  } else if (~n->c->type & clType_command) {
+			    yyerror("type mismatch");
+			    YYERROR;
+			  } else {
+			    $$ = n->c;
+			    class_inc(n->c);
+			  }
+			}
+		| '(' command_class ')' { $$ = $2; }
+		;
+
+/* --- Host class expressions --- */
+
+host_class : host_class ',' host_class {
+			  if ($1->type != $3->type) {
+			    yyerror("type mismatch");
+			    class_dec($1);
+			    class_dec($3);
+			    YYERROR;
+			  } else {
+			    if ($1 == class_all)
+			      $$ = class_all;
+			    else
+			      $$ = class_create($1->type,
+						set_union($1->t, $3->t));
+			    class_dec($1);
+			    class_dec($3);
+			  }
+			}
+		| host_class '-' host_class {
+			  if ($1->type != $3->type) {
+			    yyerror("type mismatch");
+			    class_dec($1);
+			    class_dec($3);
+			    YYERROR;
+			  } else {
+			    $$ = class_create($1->type,
+					      set_subtract($1->t, $3->t));
+			    class_dec($1);
+			    class_dec($3);
+			  }
+			}
+		| host_class '&' host_class {
+			  if ($1->type != $3->type) {
+			    yyerror("type mismatch");
+			    class_dec($1);
+			    class_dec($3);
+			    YYERROR;
+			  } else {
+			    if ($1 == class_all)
+			      $$ = class_all;
+			    else
+			      $$ = class_create($1->type,
+						set_intersect($1->t, $3->t));
+			    class_dec($1);
+			    class_dec($3);
+			  }
+			}
+		| host_class '|' host_class {
+			  if ($1->type != $3->type) {
+			    yyerror("type mismatch");
+			    class_dec($1);
+			    class_dec($3);
+			    YYERROR;
+			  } else {
+			    if ($1 == class_all)
+			      $$ = class_all;
+			    else
+			      $$ = class_create($1->type,
+						set_union($1->t, $3->t));
+			    class_dec($1);
+			    class_dec($3);
+			  }
+			}
+		| STRING {
+			  sym_table *t = xmalloc(sizeof(*t));
+			  sym_createTable(t);
+			  sym_find(t, $1, -1, sizeof(sym_base), 0);
+			  $$ = class_create(clType_host, t);
+			}
+		| WORD	{
+			  name *n = name_find($1, 0, 0);
+			  if (!n || !n->c) {
+			    moan("class `%s' not found at line %i",
+				 $1, lex_line);
+			    YYERROR;
+			  } else if (~n->c->type & clType_host) {
+			    yyerror("type mismatch");
+			    YYERROR;
+			  } else {
+			    $$ = n->c;
+			    class_inc(n->c);
+			  }
+			}
+		| '(' host_class ')' { $$ = $2; }
+		;
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/rule.c b/src/rule.c
new file mode 100644
index 0000000..000c290
--- /dev/null
+++ b/src/rule.c
@@ -0,0 +1,158 @@
+/* -*-c-*-
+ *
+ * $Id: rule.c,v 1.1 1997/07/21 13:47:45 mdw Exp $
+ *
+ * Managing rule sets
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: rule.c,v $
+ * Revision 1.1  1997/07/21 13:47:45  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Local headers --- */
+
+#include "become.h"
+#include "class.h"
+#include "rule.h"
+#include "utils.h"
+
+/*----- Type definitions --------------------------------------------------*/
+
+/* --- Rule block --- */
+
+typedef struct rule {
+  struct rule *next;			/* Next rule in the list */
+  classdef *host;			/* Hosts this rule applies to */
+  classdef *from;			/* From users in this class */
+  classdef *to;				/* To users in this class */
+  classdef *cmd;			/* To run commands in this class */
+} rule;
+
+/*----- Static variables --------------------------------------------------*/
+
+static rule *rule__list;		/* List of rules */
+static rule *rule__tail;		/* Pointer to last rule item */
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @rule_init@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Intialises the rule database.
+ */
+
+void rule_init(void)
+{
+  rule__list = 0;
+  rule__tail = (rule *)&rule__list;
+}
+
+/* --- @rule_reinit@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Reinitialises the rule database.
+ */
+
+void rule_reinit(void)
+{
+  rule *r = rule__list;
+  rule *rr;
+
+  while (r) {
+    rr = r->next;
+    free(r);
+    r = rr;
+  }
+
+  rule_init();
+}
+
+/* --- @rule_add@ --- *
+ *
+ * Arguments:	@classdef *host@ = class of hosts this rule applies to
+ *		@classdef *from@ = class of users allowed to change
+ *		@classdef *to@ = class of users allowed to be changed to
+ *		@classdef *cmd@ = class of commands allowed
+ *
+ * Returns:	---
+ *
+ * Use:		Registers another rule.
+ */
+
+void rule_add(classdef *host, classdef *from, classdef *to, classdef *cmd)
+{
+  rule *r = xmalloc(sizeof(*r));
+
+  r->host = host;
+  r->from = from;
+  r->to = to;
+  r->cmd = cmd;
+  r->next = 0;
+  rule__tail->next = r;
+  rule__tail = r;
+}
+
+/* --- @rule_check@ --- *
+ *
+ * Arguments:	@request *r@ = pointer to a request block
+ *
+ * Returns:	Zero if disallowed, nonzero if allowed.
+ *
+ * Use:		Checks a request to see if it's allowed.
+ */
+
+int rule_check(request *r)
+{
+  rule *rr = rule__list;
+
+  while (rr) {
+    if (class_userMatch(rr->from, r->from) &&
+	class_userMatch(rr->to, r->to) &&
+	class_commandMatch(rr->cmd, r->cmd) &&
+	class_hostMatch(rr->host, r->host))
+      return (1);
+    rr = rr->next;
+  }
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/rule.h b/src/rule.h
new file mode 100644
index 0000000..7e337f6
--- /dev/null
+++ b/src/rule.h
@@ -0,0 +1,115 @@
+/* -*-c-*-
+ *
+ * $Id: rule.h,v 1.1 1997/07/21 13:47:45 mdw Exp $
+ *
+ * Managing rule sets
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: rule.h,v $
+ * Revision 1.1  1997/07/21 13:47:45  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef RULE_H
+#define RULE_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#ifndef BECOME_H
+#  include "become.h"
+#endif
+
+#ifndef CLASS_H
+#  include "class.h"
+#endif
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @rule_init@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Intialises the rule database.
+ */
+
+extern void rule_init(void);
+
+/* --- @rule_reinit@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Reinitialises the rule database.
+ */
+
+extern void rule_reinit(void);
+
+/* --- @rule_add@ --- *
+ *
+ * Arguments:	@classdef *host@ = class of hosts this rule applies to
+ *		@classdef *from@ = class of users allowed to change
+ *		@classdef *to@ = class of users allowed to be changed to
+ *		@classdef *cmd@ = class of commands allowed
+ *
+ * Returns:	---
+ *
+ * Use:		Registers another rule.
+ */
+
+extern void rule_add(classdef */*host*/, classdef */*from*/,
+		     classdef */*to*/, classdef */*cmd*/);
+
+/* --- @rule_check@ --- *
+ *
+ * Arguments:	@request *r@ = pointer to a request block
+ *
+ * Returns:	Zero if disallowed, nonzero if allowed.
+ *
+ * Use:		Checks a request to see if it's allowed.
+ */
+
+extern int rule_check(request */*r*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/src/set.c b/src/set.c
new file mode 100644
index 0000000..7c36bb7
--- /dev/null
+++ b/src/set.c
@@ -0,0 +1,161 @@
+/* -*-c-*-
+ *
+ * $Id: set.c,v 1.1 1997/07/21 13:47:44 mdw Exp $
+ *
+ * Management of sets (for the use of the class expression handler)
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: set.c,v $
+ * Revision 1.1  1997/07/21 13:47:44  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Local headers --- */
+
+#include "set.h"
+#include "sym.h"
+#include "utils.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @set_subtract@ --- *
+ *
+ * Arguments:	@sym_table *a@ = pointer to table @A@
+ *		@sym_table *b@ = pointer to table @B@
+ *
+ * Returns:	A symbol table containing all the elements in @A@ which don't
+ *		appear in @B@.
+ *
+ * Use:		Subtracts a symbol table from another symbol table.  Assumes
+ *		that there's no data following the actual @sym_base@ block
+ *		for each item in the table.
+ */
+
+sym_table *set_subtract(sym_table *a, sym_table *b)
+{
+  sym_table *c = xmalloc(sizeof(*c));
+  sym_iter i;
+  sym_base *s;
+  unsigned f;
+
+  sym_createTable(c);
+  for (sym_createIter(&i, a); (s = sym_next(&i)) != 0; ) {
+    if (!sym_find(b, s->name, s->len, 0, &f))
+      sym_find(c, s->name, s->len, sizeof(sym_base), 0);
+  }
+
+  return (c);
+}
+
+/* --- @set_intersect@ --- *
+ *
+ * Arguments:	@sym_table *a@ = pointer to table @A@
+ *		@sym_table *b@ = pointer to table @B@
+ *
+ * Returns:	A symbol table containing all the elements in @A@ which also
+ *		appear in @B@.
+ *
+ * Use:		Constructs the intersection of two symbol tables.  Assumes
+ *		that there's no data following the actual @sym_base@ block
+ *		for each item in the table.
+ */
+
+sym_table *set_intersect(sym_table *a, sym_table *b)
+{
+  sym_table *c = xmalloc(sizeof(*c));
+  sym_iter i;
+  sym_base *s;
+  unsigned f;
+
+  sym_createTable(c);
+  for (sym_createIter(&i, a); (s = sym_next(&i)) != 0; ) {
+    if (sym_find(b, s->name, s->len, 0, &f))
+      sym_find(c, s->name, s->len, sizeof(sym_base), 0);
+  }
+
+  return (c);
+}
+
+/* --- @set_union@ --- *
+ *
+ * Arguments:	@sym_table *a@ = pointer to table @A@
+ *		@sym_table *b@ = pointer to table @B@
+ *
+ * Returns:	A symbol table containing all the elements in @A@ and those
+ *		in @B@.
+ *
+ * Use:		Constructs the union of two symbol tables.  Assumes that
+ *		there's no data following the actual @sym_base@ block for
+ *		each item in the table.
+ */
+
+sym_table *set_union(sym_table *a, sym_table *b)
+{
+  sym_table *c = xmalloc(sizeof(*c));
+  sym_iter i;
+  sym_base *s;
+
+  sym_createTable(c);
+  for (sym_createIter(&i, a); (s = sym_next(&i)) != 0; )
+    sym_find(c, s->name, s->len, sizeof(sym_base), 0);
+
+  for (sym_createIter(&i, b); (s = sym_next(&i)) != 0; )
+    sym_find(c, s->name, s->len, sizeof(sym_base), 0);
+
+  return (c);
+}
+
+/* --- @set_copy@ --- *
+ *
+ * Arguments:	@sym_table *a@ = pointer to table
+ *
+ * Returns:	A copy of the symbol table.
+ *
+ * Use:		Copies a symbol table.  Same assumptions again...
+ */
+
+sym_table *set_copy(sym_table *a)
+{
+  sym_table *c = xmalloc(sizeof(*c));
+  sym_iter i;
+  sym_base *s;
+
+  for (sym_createIter(&i, a); (s = sym_next(&i)) != 0; )
+    sym_find(c, s->name, s->len, sizeof(sym_base), 0);
+
+  return (c);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/set.h b/src/set.h
new file mode 100644
index 0000000..62b15c5
--- /dev/null
+++ b/src/set.h
@@ -0,0 +1,114 @@
+/* -*-c-*-
+ *
+ * $Id: set.h,v 1.1 1997/07/21 13:47:44 mdw Exp $
+ *
+ * Management of sets (for the use of the class expression handler)
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: set.h,v $
+ * Revision 1.1  1997/07/21 13:47:44  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef SET_H
+#define SET_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#ifndef SYM_H
+#  include "sym.h"
+#endif
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @set_subtract@ --- *
+ *
+ * Arguments:	@sym_table *a@ = pointer to table @A@
+ *		@sym_table *b@ = pointer to table @B@
+ *
+ * Returns:	A symbol table containing all the elements in @A@ which don't
+ *		appear in @B@.
+ *
+ * Use:		Subtracts a symbol table from another symbol table.  Assumes
+ *		that there's no data following the actual @sym_base@ block
+ *		for each item in the table.
+ */
+
+extern sym_table *set_subtract(sym_table */*a*/, sym_table */*b*/);
+
+/* --- @set_intersect@ --- *
+ *
+ * Arguments:	@sym_table *a@ = pointer to table @A@
+ *		@sym_table *b@ = pointer to table @B@
+ *
+ * Returns:	A symbol table containing all the elements in @A@ which also
+ *		appear in @B@.
+ *
+ * Use:		Constructs the intersection of two symbol tables.  Assumes
+ *		that there's no data following the actual @sym_base@ block
+ *		for each item in the table.
+ */
+
+sym_table *set_intersect(sym_table */*a*/, sym_table */*b*/);
+
+/* --- @set_union@ --- *
+ *
+ * Arguments:	@sym_table *a@ = pointer to table @A@
+ *		@sym_table *b@ = pointer to table @B@
+ *
+ * Returns:	A symbol table containing all the elements in @A@ and those
+ *		in @B@.
+ *
+ * Use:		Constructs the union of two symbol tables.  Assumes that
+ *		there's no data following the actual @sym_base@ block for
+ *		each item in the table.
+ */
+
+sym_table *set_union(sym_table */*a*/, sym_table */*b*/);
+
+/* --- @set_copy@ --- *
+ *
+ * Arguments:	@sym_table *a@ = pointer to table
+ *
+ * Returns:	A copy of the symbol table.
+ *
+ * Use:		Copies a symbol table.  Same assumptions again...
+ */
+
+sym_table *set_copy(sym_table */*a*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/src/sym.c b/src/sym.c
new file mode 100644
index 0000000..d882f5f
--- /dev/null
+++ b/src/sym.c
@@ -0,0 +1,681 @@
+/* -*-c-*-
+ *
+ * $Id: sym.c,v 1.1 1997/07/21 13:47:44 mdw Exp $
+ *
+ * Symbol table management
+ *
+ * (c) 1996 Straylight
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: sym.c,v $
+ * Revision 1.1  1997/07/21 13:47:44  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Local headers --- */
+
+#include "sym.h"
+#include "utils.h"
+
+/*----- Tuning parameters -------------------------------------------------*/
+
+/* --- Initial hash table size --- *
+ *
+ * This is the initial @mask@ value.  It must be of the form %$2^n - 1$%,
+ * so that it can be used to mask of the bottom bits of a hash value.
+ */
+
+#define sym__iSize 255			/* Size of a new hash table */
+
+/* --- Maximum load factor --- *
+ *
+ * This parameter controls how much the table has to be loaded before the
+ * table is extended.  The number of elements %$n$%, the number of bins %$b$%
+ * and the limit %$l$% satisfy the relation %$n < bl$%; if a new item is
+ * added to the table and this relation is found to be false, the table is
+ * doubled in size.
+ *
+ * The parameter @sym__limFactor@ is the value of %$l$% multiplied by 256;
+ * fixed point arithmetic is used to calculate the allowable load.  Note
+ * that the exact calculation of this criterion isn't important; what's more
+ * significant is that the parameter allows tuning of the rehashing process.
+ *
+ * The current value is %$3 \over 4$%, which appears to be reasonable on the
+ * face of things.
+ */
+
+#define sym__limFactor 0x0C0		/* Load factor for growing table */
+
+/*----- CRC table ---------------------------------------------------------*/
+
+/*****************************************************************/
+/*                                                               */
+/* CRC LOOKUP TABLE                                              */
+/* ================                                              */
+/*                                                               */
+/* The following CRC lookup table was generated automagically    */
+/* by `crcgen', which is based on the Rocksoft^tm Model CRC      */
+/* Algorithm Table Generation Program.  The model parameters     */
+/* supplied to `crcgen' were:                                    */
+/*                                                               */
+/*    Width   : 32 bits                                          */
+/*    Poly    : 0x04C11DB7                                       */
+/*    Init    : 0xFFFFFFFF                                       */
+/*    XorOut  : 0xFFFFFFFF                                       */
+/*    Reverse : Yes                                              */
+/*    Check   : 0xCBF43926                                       */
+/*                                                               */
+/* For more information on the Rocksoft^tm Model CRC Algorithm,  */
+/* see the document titled `A Painless Guide to CRC Error        */
+/* Detection Algorithms' by Ross Williams                        */
+/* (ross@@guest.adelaide.edu.au.). This document is likely to be */
+/* in the FTP archive `ftp.adelaide.edu.au/pub/rocksoft'.        */
+/*                                                               */
+/*****************************************************************/
+
+static unsigned long  sym__crcTable[256] = {
+  0x00000000L, 0x77073096L, 0xEE0E612CL, 0x990951BAL,
+  0x076DC419L, 0x706AF48FL, 0xE963A535L, 0x9E6495A3L,
+  0x0EDB8832L, 0x79DCB8A4L, 0xE0D5E91EL, 0x97D2D988L,
+  0x09B64C2BL, 0x7EB17CBDL, 0xE7B82D07L, 0x90BF1D91L,
+  0x1DB71064L, 0x6AB020F2L, 0xF3B97148L, 0x84BE41DEL,
+  0x1ADAD47DL, 0x6DDDE4EBL, 0xF4D4B551L, 0x83D385C7L,
+  0x136C9856L, 0x646BA8C0L, 0xFD62F97AL, 0x8A65C9ECL,
+  0x14015C4FL, 0x63066CD9L, 0xFA0F3D63L, 0x8D080DF5L,
+  0x3B6E20C8L, 0x4C69105EL, 0xD56041E4L, 0xA2677172L,
+  0x3C03E4D1L, 0x4B04D447L, 0xD20D85FDL, 0xA50AB56BL,
+  0x35B5A8FAL, 0x42B2986CL, 0xDBBBC9D6L, 0xACBCF940L,
+  0x32D86CE3L, 0x45DF5C75L, 0xDCD60DCFL, 0xABD13D59L,
+  0x26D930ACL, 0x51DE003AL, 0xC8D75180L, 0xBFD06116L,
+  0x21B4F4B5L, 0x56B3C423L, 0xCFBA9599L, 0xB8BDA50FL,
+  0x2802B89EL, 0x5F058808L, 0xC60CD9B2L, 0xB10BE924L,
+  0x2F6F7C87L, 0x58684C11L, 0xC1611DABL, 0xB6662D3DL,
+  0x76DC4190L, 0x01DB7106L, 0x98D220BCL, 0xEFD5102AL,
+  0x71B18589L, 0x06B6B51FL, 0x9FBFE4A5L, 0xE8B8D433L,
+  0x7807C9A2L, 0x0F00F934L, 0x9609A88EL, 0xE10E9818L,
+  0x7F6A0DBBL, 0x086D3D2DL, 0x91646C97L, 0xE6635C01L,
+  0x6B6B51F4L, 0x1C6C6162L, 0x856530D8L, 0xF262004EL,
+  0x6C0695EDL, 0x1B01A57BL, 0x8208F4C1L, 0xF50FC457L,
+  0x65B0D9C6L, 0x12B7E950L, 0x8BBEB8EAL, 0xFCB9887CL,
+  0x62DD1DDFL, 0x15DA2D49L, 0x8CD37CF3L, 0xFBD44C65L,
+  0x4DB26158L, 0x3AB551CEL, 0xA3BC0074L, 0xD4BB30E2L,
+  0x4ADFA541L, 0x3DD895D7L, 0xA4D1C46DL, 0xD3D6F4FBL,
+  0x4369E96AL, 0x346ED9FCL, 0xAD678846L, 0xDA60B8D0L,
+  0x44042D73L, 0x33031DE5L, 0xAA0A4C5FL, 0xDD0D7CC9L,
+  0x5005713CL, 0x270241AAL, 0xBE0B1010L, 0xC90C2086L,
+  0x5768B525L, 0x206F85B3L, 0xB966D409L, 0xCE61E49FL,
+  0x5EDEF90EL, 0x29D9C998L, 0xB0D09822L, 0xC7D7A8B4L,
+  0x59B33D17L, 0x2EB40D81L, 0xB7BD5C3BL, 0xC0BA6CADL,
+  0xEDB88320L, 0x9ABFB3B6L, 0x03B6E20CL, 0x74B1D29AL,
+  0xEAD54739L, 0x9DD277AFL, 0x04DB2615L, 0x73DC1683L,
+  0xE3630B12L, 0x94643B84L, 0x0D6D6A3EL, 0x7A6A5AA8L,
+  0xE40ECF0BL, 0x9309FF9DL, 0x0A00AE27L, 0x7D079EB1L,
+  0xF00F9344L, 0x8708A3D2L, 0x1E01F268L, 0x6906C2FEL,
+  0xF762575DL, 0x806567CBL, 0x196C3671L, 0x6E6B06E7L,
+  0xFED41B76L, 0x89D32BE0L, 0x10DA7A5AL, 0x67DD4ACCL,
+  0xF9B9DF6FL, 0x8EBEEFF9L, 0x17B7BE43L, 0x60B08ED5L,
+  0xD6D6A3E8L, 0xA1D1937EL, 0x38D8C2C4L, 0x4FDFF252L,
+  0xD1BB67F1L, 0xA6BC5767L, 0x3FB506DDL, 0x48B2364BL,
+  0xD80D2BDAL, 0xAF0A1B4CL, 0x36034AF6L, 0x41047A60L,
+  0xDF60EFC3L, 0xA867DF55L, 0x316E8EEFL, 0x4669BE79L,
+  0xCB61B38CL, 0xBC66831AL, 0x256FD2A0L, 0x5268E236L,
+  0xCC0C7795L, 0xBB0B4703L, 0x220216B9L, 0x5505262FL,
+  0xC5BA3BBEL, 0xB2BD0B28L, 0x2BB45A92L, 0x5CB36A04L,
+  0xC2D7FFA7L, 0xB5D0CF31L, 0x2CD99E8BL, 0x5BDEAE1DL,
+  0x9B64C2B0L, 0xEC63F226L, 0x756AA39CL, 0x026D930AL,
+  0x9C0906A9L, 0xEB0E363FL, 0x72076785L, 0x05005713L,
+  0x95BF4A82L, 0xE2B87A14L, 0x7BB12BAEL, 0x0CB61B38L,
+  0x92D28E9BL, 0xE5D5BE0DL, 0x7CDCEFB7L, 0x0BDBDF21L,
+  0x86D3D2D4L, 0xF1D4E242L, 0x68DDB3F8L, 0x1FDA836EL,
+  0x81BE16CDL, 0xF6B9265BL, 0x6FB077E1L, 0x18B74777L,
+  0x88085AE6L, 0xFF0F6A70L, 0x66063BCAL, 0x11010B5CL,
+  0x8F659EFFL, 0xF862AE69L, 0x616BFFD3L, 0x166CCF45L,
+  0xA00AE278L, 0xD70DD2EEL, 0x4E048354L, 0x3903B3C2L,
+  0xA7672661L, 0xD06016F7L, 0x4969474DL, 0x3E6E77DBL,
+  0xAED16A4AL, 0xD9D65ADCL, 0x40DF0B66L, 0x37D83BF0L,
+  0xA9BCAE53L, 0xDEBB9EC5L, 0x47B2CF7FL, 0x30B5FFE9L,
+  0xBDBDF21CL, 0xCABAC28AL, 0x53B39330L, 0x24B4A3A6L,
+  0xBAD03605L, 0xCDD70693L, 0x54DE5729L, 0x23D967BFL,
+  0xB3667A2EL, 0xC4614AB8L, 0x5D681B02L, 0x2A6F2B94L,
+  0xB40BBE37L, 0xC30C8EA1L, 0x5A05DF1BL, 0x2D02EF8DL,
+};
+
+/*****************************************************************/
+/*                   End of CRC Lookup Table                     */
+/*****************************************************************/
+
+/* --- Work out the CRC of a bytestring --- */
+
+#define sym__crc(s_, l_, hv_) do {					\
+  unsigned long h_ = 0xFFFFFFFFL;					\
+  const char *p_ = s_;							\
+  const char *e_ = (s_) + (l_);						\
+									\
+  while (p_ < e_)							\
+    h_ = (h_ >> 8) ^ (sym__crcTable[(*p_++ ^ h_) & 0xFF]);		\
+  hv_ = ~h_ & 0xFFFFFFFFL;						\
+} while (0)
+
+/*----- Main code ---------------------------------------------------------*/
+
+#ifndef DEBUG_CRC
+
+/* --- @sym_createTable@ --- *
+ *
+ * Arguments:	@sym_table *t@ = symbol table to initialise
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises the given symbol table.
+ */
+
+void sym_createTable(sym_table *t)
+{
+  size_t i;
+
+  t->mask = sym__iSize;
+  t->c = (sym__iSize * sym__limFactor) >> 8;
+  t->a = xmalloc((t->mask + 1) * sizeof(sym_base *));
+
+  for (i = 0; i < sym__iSize + 1; i++)
+    t->a[i] = 0;  
+}
+
+/* --- @sym_destroyTable@ --- *
+ *
+ * Arguments:	@sym_table *t@ = pointer to symbol table in question
+ *
+ * Returns:	---
+ *
+ * Use:		Destroys a symbol table, freeing all the memory it used to
+ *		occupy.
+ */
+
+void sym_destroyTable(sym_table *t)
+{
+  size_t i;
+  sym_base *p, *q;
+
+  for (i = 0; i <= t->mask; i++) {
+    p = t->a[i];
+    while (p) {
+      q = p->next;
+      free(p);
+      p = q;
+    }
+  }
+}
+
+/* --- @sym_find@ --- *
+ *
+ * Arguments:	@sym_table *t@ = pointer to symbol table in question
+ *		@const char *n@ = pointer to symbol table to look up
+ *		@long l@ = length of the name string or negative to measure
+ *		@size_t sz@ = size of desired symbol object, or zero
+ *		@unsigned *f@ = pointer to a flag, or null.
+ *
+ * Returns:	The address of a @sym_base@ structure, or null if not found
+ *		and @sz@ is zero.
+ *
+ * Use:		Looks up a symbol in a given symbol table.  The name is
+ *		passed by the address of its first character.  The length
+ *		may be given, in which case the name may contain arbitrary
+ *		binary data, or it may be given as a negative number, in
+ *		which case the length of the name is calculated as
+ *		@strlen(n)@.
+ *
+ *		The return value is the address of a pointer to a @sym_base@
+ *		block (which may have other things on the end, as above).  If
+ *		the symbol could be found, the return value points to the
+ *		symbol block.  If the symbol wasn't there, then if @sz@ is
+ *		nonzero, a new symbol is created and its address is returned;
+ *		otherwise a null pointer is returned.
+ *
+ *		The value of @*f@ indicates whether a new symbol entry was
+ *		created: a nonzero value indicates that an old value was
+ *		found.
+ */
+
+void *sym_find(sym_table *t, const char *n, long l, size_t sz, unsigned *f)
+{
+  unsigned long hash;			/* Hash value for user's name */
+  size_t len = l < 0 ? strlen(n) + 1 : l; /* Find length of user's name */
+  sym_base *bin;			/* Bin containing our item */
+  sym_base *p, *q;			/* Pointer wandering through list */
+
+  /* --- Find the correct bin --- */
+
+  sym__crc(n, len, hash);		/* Find hash value for this name */
+  bin = p = (sym_base *)(t->a + (hash & t->mask));
+
+  /* --- Search the bin list --- */
+
+  while (p->next) {
+    if (hash == p->next->hash &&	/* Check hash values first */
+	len == p->next->len &&		/* Then compare string lengths */
+	!memcmp(n, p->next->name, len))	/* And finally compare the strings */
+    {
+      /* --- Found a match --- *
+       *
+       * As a minor, and probably pointless, tweak, move the item to the
+       * front of its bin list.
+       */
+
+      q = p->next;			/* Find the actual symbol block */
+      p->next = q->next;		/* Extract block from bin list */
+      q->next = bin->next;		/* Set up symbol's next pointer */
+      bin->next = q;			/* And reinsert the block */
+
+      /* --- Return the block --- */
+
+      if (f) *f = 1;			/* Didn't fail to find the item */
+      return (q);			/* And return the block */
+    }
+
+    p = p->next;			/* Move onto the next item */
+  }
+
+  /* --- Couldn't find the item there --- */
+
+  if (f) *f = 0;			/* Failed to find the block */
+  if (!sz) return (0);			/* Return zero if not creating */
+
+  /* --- Create a new symbol block and initialise it --- */
+
+  p = xmalloc(sz);			/* Create a new symbol block */
+  p->next = bin->next;			/* Set up the next pointer */
+  p->hash = hash;			/* Set up the hash value too */
+  p->name = xmalloc(len);		/* Allocate a block for the name */
+  p->len = len;				/* And set up the string length */
+  memcpy(p->name, n, len);		/* And copy the string over */
+
+  bin->next = p;			/* Put the pointer into the bin */
+
+  /* --- Consider growing the array --- */
+
+  if (!--t->c) {
+    unsigned long m = t->mask + 1;	/* Next set bit in has word */
+    sym_base *p, *q, *r;		/* More useful pointers */
+    size_t i, lim;			/* Loop counter and limit */
+
+    /* --- Update values in the anchor block --- */
+
+    t->c = ((t->mask + 1) * sym__limFactor) >> 8; /* Set load value */
+    t->mask = (t->mask + 1) * 2 - 1;	/* Set the new mask value */
+    t->a = xrealloc(t->a, (t->mask + 1) * sizeof(sym_base *));
+
+    /* --- Now wander through the table rehashing things --- *
+     *
+     * This loop is very careful to avoid problems with aliasing.  The items
+     * are dealt with from the end backwards to avoid overwriting bins before
+     * they've been processed.
+     */
+
+    lim = (t->mask + 1) >> 1;
+    for (i = 0; i < lim; i++) {
+      /* --- Some initialisation --- */
+
+      r = t->a[i];			/* Find the list we're dissecting */
+      p = (sym_base *)(t->a + i);	/* Find bit-clear list */
+      q = (sym_base *)(t->a + i + lim);	/* And the bit-set lsit */
+
+      /* --- Now go through the @r@ list --- */
+
+      while (r) {
+	if (r->hash & m)		/* Is the next bit set? */
+	  q = q->next = r;		/* Yes, so fit up previous link */
+	else
+	  p = p->next = r;		/* No, so fit up previous link */
+	r = r->next;			/* Move onto the next item */
+      }
+      p->next = q->next = 0;		/* Null terminate the new lists */
+    }
+  }
+
+  /* --- Finished that, so return the new symbol block --- */
+
+  return (p);
+}
+
+/* --- @sym_remove@ --- *
+ *
+ * Arguments:	@sym_table *i@ = pointer to a symbol table object
+ *		@void *b@ = pointer to symbol table entry
+ *
+ * Returns:	---
+ *
+ * Use:		Removes the object from the symbol table.  The space occupied
+ *		by the object and its name is freed; anything else attached
+ *		to the entry should already be gone by this point.
+ */
+
+void sym_remove(sym_table *t, void *b)
+{
+  /* --- A quick comment --- *
+   *
+   * Since the @sym_base@ block contains the hash, finding the element in the
+   * bin list is really quick -- it's not worth bothering with things like
+   * doubly linked lists.
+   */
+
+  sym_base *p = b;
+  sym_base *bin = (sym_base *)(t->a + (p->hash & t->mask));
+
+  /* --- Find the item in the bin list --- */
+
+  while (bin->next) {
+    if (bin->next == p)
+      break;
+    bin = bin->next;
+  }
+  if (!bin->next)
+    return;
+
+  /* --- Now just remove the item from the list and free it --- *
+   *
+   * Oh, and bump the load counter.
+   */
+
+  bin->next = p->next;
+  free(p->name);
+  free(p);
+  t->c++;
+}
+
+/* --- @sym_createIter@ --- *
+ *
+ * Arguments:	@sym_iter *i@ = pointer to an iterator object
+ *		@sym_table *t@ = pointer to a symbol table object
+ *
+ * Returns:	---
+ *
+ * Use:		Creates a new symbol table iterator which may be used to
+ *		iterate through a symbol table.
+ */
+
+void sym_createIter(sym_iter *i, sym_table *t)
+{
+  i->t = t;
+  i->i = 0;
+  i->n = 0;
+}
+
+/* --- @sym_next@ --- *
+ *
+ * Arguments:	@sym_iter *i@ = pointer to iterator object
+ *
+ * Returns:	Pointer to the next symbol found, or null when finished.
+ *
+ * Use:		Returns the next symbol from the table.  Symbols are not
+ *		returned in any particular order.
+ */
+
+void *sym_next(sym_iter *i)
+{
+  sym_base *p;
+
+  /* --- Find the next item --- */
+
+  while (!i->n) {
+    if (i->i > i->t->mask)
+      return (0);
+    i->n = i->t->a[i->i++];
+  }
+
+  /* --- Update the iterator block --- */
+
+  p = i->n;
+  i->n = p->next;
+
+  /* --- Done --- */
+
+  return (p);
+}
+
+#endif
+
+/*----- CRC test code -----------------------------------------------------*/
+
+#ifdef DEBUG_CRC
+
+int main(void)
+{
+  unsigned long h;
+  sym__crc("123456789", 9, h);
+  printf("crc check == %04x\n", h);
+  return (0);
+}
+
+#endif
+
+/*----- Symbol table test code --------------------------------------------*/
+
+#ifdef TEST_RIG
+
+#include <errno.h>
+#include <time.h>
+
+#include "dbutils.h"
+
+typedef struct sym_word {
+  sym_base base;
+  size_t i;
+} sym_word;
+
+
+/* --- What it does --- *
+ *
+ * Reads the file /usr/dict/words (change to some other file full of
+ * interesting and appropriate bits of text to taste) into a big buffer and
+ * picks apart into lines.  Then picks lines at random and enters them into
+ * the symbol table.
+ */
+
+int main(void)
+{
+  char *buff, *p, *lim;
+  size_t sz, done;
+  FILE *fp;
+  int i;
+  char **line;
+  sym_word **flag;
+  sym_table tbl;
+  int entries;
+
+  /* --- Initialise for reading the file --- */
+
+  sz = BUFSIZ;
+  buff = xmalloc(sz + 1);
+  done = 0;
+
+  if ((fp = fopen("/usr/dict/words", "r")) == 0)
+    fprintf(stderr, "buggered ;-( (%s)\n", strerror(errno));
+
+  /* --- Read buffers of text --- *
+   *
+   * Read a buffer.  If more to come, double the buffer size and try again.
+   * This is the method I recommended to comp.lang.c, so I may as well try
+   * it.
+   */
+
+  for (;;) {
+    i = fread(buff + done, 1, sz - done, fp);
+    done += i;
+    if (done != sz)
+      break;
+    sz <<= 1;
+    buff = xrealloc(buff, sz + 1);
+  }
+
+  /* --- Count the lines --- */
+
+  lim = buff + done;
+
+  sz = 1;
+  for (p = buff; p < lim; p++)
+    if (*p == '\n') sz++;
+
+  /* --- Build a table of line starts --- */
+
+  line = xmalloc(sz * sizeof(char *));
+  i = 0;
+  line[i++] = buff;
+  for (p = buff; p < lim; p++)
+    if (*p == '\n') *p = 0, line[i++] = p + 1;
+  *lim = 0;
+
+  /* --- Build a table of lines --- *
+   *
+   * This reverses the mapping which the symbol table performs, so that its
+   * accuracy can be tested.
+   */
+
+  flag = xmalloc(sz * sizeof(sym_word *));
+  for (i = 0; i < sz; i++)
+    flag[i] = 0;
+  entries = 0;
+
+  sym_createTable(&tbl);
+
+  for (;;) {
+    i = (unsigned)rand() % sz;
+
+    switch (rand() % 5)
+    {
+      case 0: {
+	sym_word *w;
+
+	printf("find `%s'\n", line[i]);
+
+	w = sym_find(&tbl, line[i], -1, 0, 0);
+	if (w != flag[i])
+	  printf("*** error: find `%s' gave %p not %p\n",
+		 line[i], (void *)w, (void *)flag[i]);
+	else if (w && w->i != i)
+	  printf("*** error: find sym for `%s' gives index %i not %i\n",
+		 line[i], w->i, i);	
+      } break;
+
+      case 1: {
+	unsigned f;
+	sym_word *w;
+
+	printf("create `%s'\n", line[i]);
+
+	w = sym_find(&tbl, line[i], -1, sizeof(sym_word), &f);
+	if (f)
+	{
+	  if (w != flag[i])
+	    printf("*** error: create `%s' gave %p not %p\n",
+		   line[i], (void *)w, (void *)flag[i]);
+	  else if (w && w->i != i)
+	    printf("*** error: create sym for `%s' gives index %i not %i\n",
+		   line[i], w->i, i);
+	}
+	else
+	{
+	  if (flag[i])
+	    printf("*** error: create `%s' gave new block, should be %p\n",
+		   line[i], (void *)flag[i]);
+	  else {
+	    flag[i] = w;
+	    w->i = i;
+	    entries++;
+	  }
+	}
+      } break;
+
+      case 2: {
+	sym_iter it;
+	sym_word *w, **ntbl;
+
+	printf("iterate\n");
+
+	ntbl = xmalloc(sz * sizeof(sym_word *));
+	memcpy(ntbl, flag, sz * sizeof(sym_word *));
+	sym_createIter(&it, &tbl);
+
+	while ((w = sym_next(&it)) != 0) {
+	  if (ntbl[w->i] == 0)
+	    printf("*** error: iterate returned duff item %i\n", w->i);
+	  else
+	    ntbl[w->i] = 0;
+	}
+
+	for (i = 0; i < sz; i++)
+	  if (ntbl[i]) printf("*** error: iterate didn't return item %i\n",
+			      i);
+	free(ntbl);
+      } break;
+
+      case 3: {
+	sym_base *b;
+	int v = rand() & 255 ? 0 : 1;
+
+	printf("dump\n");
+
+	for (i = 0; i <= tbl.mask; i++) {
+	  if (!tbl.a[i]) continue;
+	  if (v) printf("  %i: ", i);
+	  b = tbl.a[i];
+	  while (b) {
+	    if ((b->hash & tbl.mask) != i)
+	      printf("*** error: bad hash value found");
+	    if (v) printf("`%s'(%08lx:%lu) ",
+			  line[((sym_word *)b)->i],
+			  b->hash,
+			  b->hash & tbl.mask);
+	    b = b->next;
+	  }
+	  if (v) putchar('\n');
+	}
+      } break;
+
+      case 4: {
+	if (flag[i]) {
+	  printf("remove `%s'\n", flag[i]->base.name);
+	  sym_remove(&tbl, flag[i]);
+	  flag[i] = 0;
+	  entries--;
+	}
+      } break;
+    }
+
+  }
+
+  return (0);
+}
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/sym.h b/src/sym.h
new file mode 100644
index 0000000..8f708e1
--- /dev/null
+++ b/src/sym.h
@@ -0,0 +1,190 @@
+/* -*-c-*-
+ *
+ * $Id: sym.h,v 1.1 1997/07/21 13:47:43 mdw Exp $
+ *
+ * Symbol table management
+ *
+ * (c) 1996 Straylight
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: sym.h,v $
+ * Revision 1.1  1997/07/21 13:47:43  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef SYM_H
+#define SYM_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#include <stddef.h>
+
+/*----- Type definitions --------------------------------------------------*/
+
+/* --- Symbol table --- *
+ *
+ * A @sym_table@ contains the information needed to manage a symbol table.
+ * Users shouldn't fiddle with this information directly, but it needs to be
+ * here so that objects of the correct type can be created.
+ */
+
+typedef struct sym_table {
+  unsigned long mask;			/* Bit mask for hashing purposes */
+  size_t c;				/* Down counter for growing table */
+  struct sym_base **a;			/* Array of hash bins */
+} sym_table;
+
+/* --- A symbol table entry --- *
+ *
+ * I don't care what actually gets stored in symbol entries because I don't
+ * create them: that's the responsibility of my client.  All I care about
+ * here is that whatever gets passed to me is a structure whose first member
+ * is a @sym_base@.  The ANSI guarantees about structure layout are
+ * sufficient to allow me to manipulate such objects.
+ */
+
+typedef struct sym_base {
+  struct sym_base *next;		/* Next symbol in hash bin */
+  unsigned long hash;			/* Hash value for symbol's name */
+  char *name;				/* Name of this symbol */
+  size_t len;				/* Length of the symbol's name */
+} sym_base;
+
+/* --- An iterator block --- */
+
+typedef struct sym_iter {
+  sym_table *t;				/* Symbol table being iterated */
+  sym_base *n;				/* Address of next item to return */
+  size_t i;				/* Index of next hash bin to use */
+} sym_iter;
+
+/*----- External functions ------------------------------------------------*/
+
+/* --- @sym_createTable@ --- *
+ *
+ * Arguments:	@sym_table *t@ = symbol table to initialise
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises the given symbol table.
+ */
+
+extern void sym_createTable(sym_table */*t*/);
+
+/* --- @sym_destroyTable@ --- *
+ *
+ * Arguments:	@sym_table *t@ = pointer to symbol table in question
+ *
+ * Returns:	---
+ *
+ * Use:		Destroys a symbol table, freeing all the memory it used to
+ *		occupy.
+ */
+
+extern void sym_destroyTable(sym_table */*t*/);
+
+/* --- @sym_find@ --- *
+ *
+ * Arguments:	@sym_table *t@ = pointer to symbol table in question
+ *		@const char *n@ = pointer to symbol table to look up
+ *		@long l@ = length of the name string or negative to measure
+ *		@size_t sz@ = size of desired symbol object, or zero
+ *		@unsigned *f@ = pointer to a flag, or null.
+ *
+ * Returns:	The address of a @sym_base@ structure, or null if not found
+ *		and @sz@ is zero.
+ *
+ * Use:		Looks up a symbol in a given symbol table.  The name is
+ *		passed by the address of its first character.  The length
+ *		may be given, in which case the name may contain arbitrary
+ *		binary data, or it may be given as a negative number, in
+ *		which case the length of the name is calculated as
+ *		@strlen(n)@.
+ *
+ *		The return value is the address of a pointer to a @sym_base@
+ *		block (which may have other things on the end, as above).  If
+ *		the symbol could be found, the return value points to the
+ *		symbol block.  If the symbol wasn't there, then if @sz@ is
+ *		nonzero, a new symbol is created and its address is returned;
+ *		otherwise a null pointer is returned.
+ *
+ *		The value of @*f@ indicates whether a new symbol entry was
+ *		created: a nonzero value indicates that an old value was
+ *		found.
+ */
+
+extern void *sym_find(sym_table */*t*/, const char */*n*/, long /*l*/,
+		      size_t /*sz*/, unsigned */*f*/);
+
+/* --- @sym_remove@ --- *
+ *
+ * Arguments:	@sym_table *i@ = pointer to a symbol table object
+ *		@void *b@ = pointer to symbol table entry
+ *
+ * Returns:	---
+ *
+ * Use:		Removes the object from the symbol table.  The space occupied
+ *		by the object and its name is freed; anything else attached
+ *		to the entry should already be gone by this point.
+ */
+
+extern void sym_remove(sym_table */*t*/, void */*b*/);
+
+/* --- @sym_createIter@ --- *
+ *
+ * Arguments:	@sym_iter *i@ = pointer to an iterator object
+ *		@sym_table *t@ = pointer to a symbol table object
+ *
+ * Returns:	---
+ *
+ * Use:		Creates a new symbol table iterator which may be used to
+ *		iterate through a symbol table.
+ */
+
+extern void sym_createIter(sym_iter */*i*/, sym_table */*t*/);
+
+/* --- @sym_next@ --- *
+ *
+ * Arguments:	@sym_iter *i@ = pointer to iterator object
+ *
+ * Returns:	Pointer to the next symbol found, or null when finished.
+ *
+ * Use:		Returns the next symbol from the table.  Symbols are not
+ *		returned in any particular order.
+ */
+
+extern void *sym_next(sym_iter */*i*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/src/tx.c b/src/tx.c
new file mode 100644
index 0000000..b17456a
--- /dev/null
+++ b/src/tx.c
@@ -0,0 +1,178 @@
+/* -*-c-*-
+ *
+ * $Id: tx.c,v 1.1 1997/07/21 13:47:43 mdw Exp $
+ *
+ * Transfer for keys and other large integers
+ *
+ * (c) 1997 Mark Wooding
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: tx.c,v $
+ * Revision 1.1  1997/07/21 13:47:43  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Local headers --- */
+
+#include "config.h"
+#include "tx.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @tx_getBits@ --- *
+ *
+ * Arguments:	@unsigned char *k@ = pointer to key array to unpack into
+ *		@size_t sz@ = number of bits to read (elements in array)
+ *	       	@FILE *fp@ = stream to read from
+ *
+ * Returns:	---
+ *
+ * Use:		Reads a number of bits into an array.  The least significant
+ *		bits of the final word are cleared to zero.
+ */
+
+void tx_getBits(unsigned char *k, size_t sz, FILE *fp)
+{
+  int i = 0;
+  unsigned char a = 0;
+  unsigned int ch;
+  size_t wsz = (size_t)((sz + 7ul) & ~7ul);
+  int t;
+
+  while ((t = getc(fp)) != EOF) {
+
+    /* --- Allow separators for readbility --- */
+
+    if (t == '-')
+      continue;
+
+    /* --- Standard converting-from-hex-digit code --- *
+     *
+     * Assumes that 'a'--'f' and 'A'--'F' are contiguous and in order, in
+     * addition to the guarantee that '0'--'9' are like this.  The assumption
+     * is true in ASCII (and character sets based thereon) and EBCDIC.
+     */
+
+    ch = (unsigned)t;
+    ch -= '0';
+    if (ch > 9) ch -= 'A' - '0' - 10;
+    if (ch > 15) ch -= 'a' - 'A';
+    if (ch > 15) break;
+
+    /* --- Accumulate and maybe store --- */
+
+    a = (a << 4) | ch;
+    i++;
+    sz -= 4, wsz -= 4;
+    if ((i & 1) == 0)
+      *k++ = a, a = 0;
+    if (!sz)
+      break;
+  }
+
+  /* --- Pad the rest out with zeros --- */
+
+  while (wsz) {
+    a <<= 4;
+    i++;
+    wsz -= 4;
+    if ((i & 1) == 0)
+      *k++ = a, a = 0;
+  }
+}
+
+/* --- @tx_putBits@ --- *
+ *
+ * Arguments:	@unsigned char *k@ = pointer to key block
+ *		@size_t sz@ = number of bits to write
+ *		@FILE *fp@ = pointer to stream to write on
+ *
+ * Returns:	---
+ *
+ * Use:		Complements @tx_getBits@ above.  Writes a number of bits
+ *		to a file in an easy-to-read and transportable format (hex!)
+ */
+
+void tx_putBits(unsigned char *k, size_t sz, FILE *fp)
+{
+  const static char hex[16] = "0123456789abcdef";
+  size_t dash;
+  size_t d;
+  unsigned char i;
+
+  /* --- Don't do anything unless we have to --- */
+
+  if (!sz)
+    return;
+
+  /* --- Now decide where to `dash' the output --- */
+
+  if (sz % 32 == 0)
+    dash = 4;
+  else if (sz % 40 == 0)
+    dash = 5;
+  else
+    dash = 0;
+
+  /* --- Start writing values out --- */
+
+  d = dash;
+
+  for (;;) {
+
+    /* --- Write next byte out --- */
+
+    i = *k++;
+    putc(hex[(i >> 4) & 0x0fu], fp);
+    putc(hex[(i >> 0) & 0x0fu], fp);
+
+    /* --- If done, stop now --- */
+
+    if (sz -= 8, sz == 0)
+      break;
+
+    /* --- If need a dash, print one --- */
+
+    if (!--d) {
+      putc('-', fp);
+      d = dash;
+    }
+  }
+
+  /* --- Print the final newline --- */
+
+  fputc('\n', fp);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/tx.h b/src/tx.h
new file mode 100644
index 0000000..4aaaaee
--- /dev/null
+++ b/src/tx.h
@@ -0,0 +1,84 @@
+/* -*-c-*-
+ *
+ * $Id: tx.h,v 1.1 1997/07/21 13:47:43 mdw Exp $
+ *
+ * Transfer for keys and other large integers
+ *
+ * (c) 1997 Mark Wooding
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: tx.h,v $
+ * Revision 1.1  1997/07/21 13:47:43  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef TX_H
+#define TX_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#include <stddef.h>
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @tx_getBits@ --- *
+ *
+ * Arguments:	@unsigned char *k@ = pointer to key array to unpack into
+ *		@size_t sz@ = number of bits to read (elements in array)
+ *	       	@FILE *fp@ = stream to read from
+ *
+ * Returns:	---
+ *
+ * Use:		Reads a number of bits into an array.  The least significant
+ *		bits of the final word are cleared to zero.
+ */
+
+extern void tx_getBits(unsigned char */*k*/, size_t /*sz*/, FILE */*fp*/);
+
+/* --- @tx_putBits@ --- *
+ *
+ * Arguments:	@unsigned char *k@ = pointer to key block
+ *		@size_t sz@ = number of bits to write
+ *		@FILE *fp@ = pointer to stream to write on
+ *
+ * Returns:	---
+ *
+ * Use:		Complements @tx_getBits@ above.  Writes a number of bits
+ *		to a file in an easy-to-read and transportable format (hex!)
+ */
+
+extern void tx_putBits(unsigned char */*k*/, size_t /*sz*/, FILE */*fp*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/src/userdb.c b/src/userdb.c
new file mode 100644
index 0000000..bcefc85
--- /dev/null
+++ b/src/userdb.c
@@ -0,0 +1,784 @@
+/* -*-c-*-
+ *
+ * $Id: userdb.c,v 1.1 1997/07/21 13:47:43 mdw Exp $
+ *
+ * User database management
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: userdb.c,v $
+ * Revision 1.1  1997/07/21 13:47:43  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Unix headers --- */
+
+#include <sys/types.h>
+
+#include <grp.h>
+#include <pwd.h>
+#include <unistd.h>
+
+/* --- Local headers --- */
+
+#include "config.h"
+#include "sym.h"
+#include "userdb.h"
+#include "utils.h"
+
+/*----- Type definitions --------------------------------------------------*/
+
+/* --- A map link --- */
+
+typedef struct userdb__node {
+  struct userdb__node *next;
+  void *rec;
+} userdb__node;
+
+/* --- A reference to a real record --- */
+
+typedef struct userdb__sym {
+  sym_base _base;
+  void *rec;
+} userdb__sym;
+
+/* --- A name- and number-mapping --- */
+
+typedef struct userdb__map {
+  sym_table nmap;
+  sym_table idmap;
+  userdb__node *list;
+} userdb__map;
+
+/*----- Static variables --------------------------------------------------*/
+
+static userdb__map userdb__users;	/* Map of user info blocks */
+static sym_iter userdb__useri;		/* Iterator for users */
+static userdb__map userdb__groups;	/* Map of group info blocks */
+static sym_iter userdb__groupi;		/* Iterator for groups */
+
+/*----- Map management functions ------------------------------------------*/
+
+/* --- @userdb__createMap@ --- *
+ *
+ * Arguments:	@userdb__map *m@ = pointer to a map block
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises a map table.
+ */
+
+static void userdb__createMap(userdb__map *m)
+{
+  sym_createTable(&m->nmap);
+  sym_createTable(&m->idmap);
+  m->list = 0;
+}
+
+/* --- @userdb__addToMap@ --- *
+ *
+ * Arguments:	@userdb__map *m@ = pointer to the map block
+ *		@const char *name@ = pointer to the item's name
+ *		@int id@ = the item's id number
+ *		@void *rec@ = pointer to the actual record
+ *
+ * Returns:	---
+ *
+ * Use:		Adds an item to the given map.
+ */
+
+static void userdb__addToMap(userdb__map *m,
+			     const char *name,
+			     int id, void *rec)
+{
+  unsigned f;
+  userdb__sym *s;
+  userdb__node *n;
+
+  s = sym_find(&m->nmap, name, -1, sizeof(*s), &f);
+  if (!f)
+    s->rec = rec;
+
+  s = sym_find(&m->idmap, (char *)&id, sizeof(id), sizeof(*s), &f);
+  if (!f)
+    s->rec = rec;
+
+  n = xmalloc(sizeof(*n));
+  n->rec = rec;
+  n->next = m->list;
+  m->list = n;
+}
+
+/* --- @userdb__byName@ --- *
+ *
+ * Arguments:	@userdb__map *m@ = pointer to a map block
+ *		@const char *name@ = name to look up
+ *
+ * Returns:	A pointer to the appropriate block, or zero if not found.
+ *
+ * Use:		Looks up a name in a mapping and returns the result.
+ */
+
+static void *userdb__byName(userdb__map *m, const char *name)
+{
+  userdb__sym *s = sym_find(&m->nmap, name, -1, 0, 0);
+  return (s ? s->rec : 0);
+}
+
+/* --- @userdb__byId@ --- *
+ *
+ * Arguments:	@userdb__map *m@ = pointer to a map block
+ *		@int id@ = id number to find
+ *
+ * Returns:	A pointer to the appropriate block, or zero if not found.
+ *
+ * Use:		Looks up an ID in a mapping, and returns the result.
+ */
+
+static void *userdb__byId(userdb__map *m, int id)
+{
+  userdb__sym *s = sym_find(&m->idmap, (char *)&id, sizeof(id), 0, 0);
+  return (s ? s->rec : 0);
+}
+
+/* --- @userdb__clearMap@ --- *
+ *
+ * Arguments:	@userdb__map *m@ = pointer to a map block
+ *		@void (*freerec)(void *rec)@ = pointer to a free-record proc
+ *
+ * Returns:	---
+ *
+ * Use:		Clears a map, emptying it and releasing the memory it
+ *		occupied.
+ */
+
+static void userdb__clearMap(userdb__map *m, void (*freerec)(void *rec))
+{
+  userdb__node *n, *t;
+
+  sym_destroyTable(&m->nmap);
+  sym_destroyTable(&m->idmap);
+
+  for (n = m->list; n; n = t) {
+    t = n->next;
+    freerec(n->rec);
+    free(n);
+  }
+}
+
+/*----- User and group block management -----------------------------------*/
+
+/* --- @userdb__dumpUser@ --- *
+ *
+ * Arguments:	@const struct passwd *pw@ = pointer to a user block
+ *		@FILE *fp@ = pointer to stream to write on
+ *
+ * Returns:	---
+ *
+ * Use:		Writes a user's informationt to a stream.
+ */
+
+#ifndef NDEBUG
+
+static void userdb__dumpUser(const struct passwd *pw, FILE *fp)
+{
+  printf("\n"
+	 "***   name == %s\n"
+	 "*** passwd == %s\n"
+	 "***    uid == %i\n"
+	 "***    gid == %i\n"
+	 "***  gecos == %s\n"
+	 "***   home == %s\n"
+	 "***  shell == %s\n",
+	 pw->pw_name, pw->pw_passwd, (int)pw->pw_uid, (int)pw->pw_gid,
+	 pw->pw_gecos, pw->pw_dir, pw->pw_shell);
+}
+
+#else
+
+#define userdb__dumpUser(pw, fp) ((void)0)
+
+#endif
+
+/* --- @userdb_copyUser@ --- *
+ *
+ * Arguments:	@struct passwd *pw@ = pointer to block to copy
+ *
+ * Returns:	Pointer to the copy.
+ *
+ * Use:		Copies a user block.  The copy is `deep' so all the strings
+ *		are copied too.  Free the copy with @userdb_freeUser@ when
+ *		you don't want it any more.
+ */
+
+struct passwd *userdb_copyUser(struct passwd *pw)
+{
+  struct passwd *npw;
+
+  if (!pw)
+    return (0);
+
+  npw = xmalloc(sizeof(*npw));
+
+  npw->pw_name = xstrdup(pw->pw_name);
+  npw->pw_passwd = xstrdup(pw->pw_passwd);
+  npw->pw_uid = pw->pw_uid;
+  npw->pw_gid = pw->pw_gid;
+  npw->pw_gecos = xstrdup(pw->pw_gecos);
+  npw->pw_dir = xstrdup(pw->pw_dir);
+  npw->pw_shell = xstrdup(pw->pw_shell);
+
+  return (npw);
+}
+
+/* --- @userdb__buildUser@ --- *
+ *
+ * Arguments:	@char *s@ = pointer to user string
+ *
+ * Returns:	Pointer to a user block.
+ *
+ * Use:		Converts a line from a user file into a password entry.
+ *		Note that the string is corrupted by @strtok@ while it gets
+ *		parsed.
+ */
+
+static struct passwd *userdb__buildUser(char *s)
+{
+  struct passwd *pw = xmalloc(sizeof(*pw));
+
+  s = strtok(s, ":"); if (!s) goto tidy_0; pw->pw_name = xstrdup(s);
+  s = strtok(0, ":"); if (!s) goto tidy_1; pw->pw_passwd = xstrdup(s);
+  s = strtok(0, ":"); if (!s) goto tidy_2; pw->pw_uid = atoi(s);
+  s = strtok(0, ":"); if (!s) goto tidy_2; pw->pw_gid = atoi(s);
+  s = strtok(0, ":"); if (!s) goto tidy_2; pw->pw_gecos = xstrdup(s);
+  s = strtok(0, ":"); if (!s) goto tidy_3; pw->pw_dir = xstrdup(s);
+  s = strtok(0, ":"); if (!s) goto tidy_4; pw->pw_shell = xstrdup(s);
+  return (pw);
+
+  /* --- Error handling --- */
+
+tidy_4:
+  free(pw->pw_dir);
+tidy_3:
+  free(pw->pw_gecos);
+tidy_2:
+  free(pw->pw_passwd);
+tidy_1:
+  free(pw->pw_name);
+tidy_0:
+  free(pw);
+
+  return (0);
+}
+
+/* --- @userdb_freeUser@ --- *
+ *
+ * Arguments:	@void *rec@ = pointer to a user record
+ *
+ * Returns:	---
+ *
+ * Use:		Frees a user record.
+ */
+
+void userdb_freeUser(void *rec)
+{
+  struct passwd *pw;
+
+  if (!rec)
+    return;
+
+  pw = rec;
+  free(pw->pw_name);
+  free(pw->pw_passwd);
+  free(pw->pw_gecos);
+  free(pw->pw_dir);
+  free(pw->pw_shell);
+  free(pw);
+} 
+
+/* --- @userdb__dumpGroup@ --- *
+ *
+ * Arguments:	@const struct group *gr@ = pointer to a group block
+ *		@FILE *fp@ = pointer to stream to write on
+ *
+ * Returns:	---
+ *
+ * Use:		Writes a group's information to a stream.
+ */
+
+#ifndef NDEBUG
+
+static void userdb__dumpGroup(const struct group *gr, FILE *fp)
+{
+  char *const *p;
+
+  printf("\n"
+	 "***   name == %s\n"
+	 "*** passwd == %s\n"
+	 "***    gid == %i\n"
+	 "*** members...\n",
+	 gr->gr_name, gr->gr_passwd, (int)gr->gr_gid);
+  for (p = gr->gr_mem; *p; p++)
+    printf("***   %s\n", *p);
+}
+
+#else
+
+#define userdb__dumpUser(pw, fp) ((void)0)
+
+#endif
+
+/* --- @userdb_copyGroup@ --- *
+ *
+ * Arguments:	@struct group *gr@ = pointer to group block
+ *
+ * Returns:	Pointer to copied block
+ *
+ * Use:		Copies a group block.  The copy is `deep' so all the strings
+ *		are copied too.  Free the copy with @userdb_freeGroup@ when
+ *		you don't want it any more.
+ */
+
+struct group *userdb_copyGroup(struct group *gr)
+{
+  struct group *ngr;
+  int i, max;
+
+  if (!gr)
+    return (0);
+
+  ngr = xmalloc(sizeof(*ngr));
+
+  ngr->gr_name = xstrdup(gr->gr_name);
+  ngr->gr_passwd = xstrdup(gr->gr_passwd);
+  ngr->gr_gid = gr->gr_gid;
+
+  for (max = 0; gr->gr_mem[max]; max++)
+    ;
+  ngr->gr_mem = xmalloc((max + 1) * sizeof(char *));
+  for (i = 0; i < max; i++)
+    ngr->gr_mem[i] = xstrdup(gr->gr_mem[i]);
+  ngr->gr_mem[max] = 0;
+
+  return (ngr);
+}
+
+/* --- @userdb__buildGroup@ --- *
+ *
+ * Arguments:	@char *s@ = pointer to group line string
+ *
+ * Returns:	Pointer to a group block
+ *
+ * Use:		Parses an entry in the groups file.  The string is garbled
+ *		by @strtok@ as we go.
+ */
+
+static struct group *userdb__buildGroup(char *s)
+{
+  struct group *gr = xmalloc(sizeof(*gr));
+  char *p;
+  int i;
+
+  /* --- Do the easy bits --- */
+
+  s = strtok(s, ":"); if (!s) goto tidy_0; gr->gr_name = xstrdup(s);
+  s = strtok(0, ":"); if (!s) goto tidy_1; gr->gr_passwd = xstrdup(s);
+  s = strtok(0, ":"); if (!s) goto tidy_2; gr->gr_gid = atoi(s);
+
+  /* --- Find the start of the member list --- */
+
+  s = strtok(0, "");
+  if (!s)
+    goto tidy_2;
+
+  /* --- Count the number of members --- */
+
+  p = s;
+  i = 0;
+  for (;;) {
+    i++;
+    if ((p = strpbrk(p, ",")) == 0)
+      break;
+    p++;
+  }
+
+  /* --- Allocate the block and fill it --- */
+
+  gr->gr_mem = xmalloc((i + 1) * sizeof(char *));
+  i = 0;
+  s = strtok(s, ",");
+  do {
+    gr->gr_mem[i++] = xstrdup(s);
+    s = strtok(0, ",");
+  } while (s);
+  gr->gr_mem[i] = 0;
+
+  return (gr);
+
+  /* --- Various tidying-up things --- */
+
+tidy_2:
+  free(gr->gr_passwd);
+tidy_1:
+  free(gr->gr_name);
+tidy_0:
+  free(gr);
+
+  return (0);
+}
+
+/* --- @userdb_freeGroup@ --- *
+ *
+ * Arguments:	@void *rec@ = pointer to a group record
+ *
+ * Returns:	---
+ *
+ * Use:		Frees a group record.
+ */
+
+void userdb_freeGroup(void *rec)
+{
+  struct group *gr;
+  char **p;
+
+  if (!rec)
+    return;
+
+  gr = rec;
+  free(gr->gr_name);
+  free(gr->gr_passwd);
+  for (p = gr->gr_mem; *p; p++)
+    free(*p);
+  free(gr->gr_mem);
+  free(gr);
+} 
+
+/*----- Higher-level functions --------------------------------------------*/
+
+/* --- @userdb_local@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Reads the local list of users into the maps.
+ */
+
+void userdb_local(void)
+{
+  D( printf("adding local users...\n"); )
+
+  /* --- Fetch users first --- */
+
+  {
+    struct passwd *pw;
+
+    setpwent();
+    while ((pw = getpwent()) != 0) {
+      D( userdb__dumpUser(pw, stdout); )
+      if (!userdb__byName(&userdb__users, pw->pw_name))
+	userdb__addToMap(&userdb__users, pw->pw_name, pw->pw_uid,
+			 userdb_copyUser(pw));
+    }
+    endpwent();
+  }
+
+  /* --- Then fetch groups --- */
+
+  {
+    struct group *gr;
+
+    setgrent();
+    while ((gr = getgrent()) != 0) {
+      D( userdb__dumpGroup(gr, stdout); )
+      if (!userdb__byName(&userdb__groups, gr->gr_name))
+	userdb__addToMap(&userdb__groups, gr->gr_name, gr->gr_gid,
+			 userdb_copyGroup(gr));
+    }
+    endgrent();
+  }
+}
+
+/* --- @userdb__getLine@ --- *
+ *
+ * Arguments:	@char *buff@ = pointer to buffer to read into
+ *		@size_t sz@ = size of the buffer
+ *		@FILE *fp@ = pointer to stream to read on
+ *
+ * Returns:	Zero if something didn't work.
+ *
+ * Use:		Reads a line into the buffer.  If the line didn't fit, bits
+ *		of it are thrown away.  The newline character is not
+ *		included.
+ */
+
+static char *userdb__getLine(char *buff, size_t sz, FILE *fp)
+{
+  if ((buff = fgets(buff, sz, fp)) == 0)
+    return (0);
+  sz = strlen(buff) - 1;
+  if (buff[sz] == '\n')
+    buff[sz] = 0;
+  else for (;;) {
+    int ch = getc(fp);
+    if (ch == '\n' || ch == EOF)
+      break;
+  }
+  return (buff);
+}
+  
+/* --- @userdb_yp@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Fetches the YP database of users.
+ */
+
+void userdb_yp(void)
+{
+
+#ifdef HAVE_YP
+
+  char line[1024];
+  FILE *fp;
+
+  D( printf("adding nis users\n"); )
+
+  /* --- First, users --- */
+
+  if ((fp = popen("ypcat passwd", "r")) != 0) {
+    while (userdb__getLine(line, sizeof(line), fp)) {
+      struct passwd *pw;
+
+      if ((pw = userdb__buildUser(line)) != 0) {
+	D( userdb__dumpUser(pw, stdout); )
+	if (userdb__byName(&userdb__users, pw->pw_name))
+	  userdb_freeUser(pw);
+	else
+	  userdb__addToMap(&userdb__users, pw->pw_name, pw->pw_uid, pw);
+      }
+    }
+    pclose(fp);
+  }
+
+  /* --- Next, groups --- */
+
+
+  if ((fp = popen("ypcat group", "r")) != 0) {
+    while (userdb__getLine(line, sizeof(line), fp)) {
+      struct group *gr;
+
+      if ((gr = userdb__buildGroup(line)) != 0) {
+	D( userdb__dumpGroup(gr, stdout); )
+	if (userdb__byName(&userdb__groups, gr->gr_name))
+	  userdb_freeGroup(gr);
+	else
+	  userdb__addToMap(&userdb__groups, gr->gr_name, gr->gr_gid, gr);
+      }
+    }
+    pclose(fp);
+  }
+
+#endif
+
+}
+
+/* --- @userdb_userByName@, @userdb_userById@ --- *
+ *
+ * Arguments:	@const char *name@ = pointer to user's name
+ *		@uid_t id@ = user id to find
+ *
+ * Returns:	Pointer to user block, or zero if not found.
+ *
+ * Use:		Looks up a user by name or id.
+ */
+
+struct passwd *userdb_userByName(const char *name)
+{ return (userdb__byName(&userdb__users, name)); }
+
+struct passwd *userdb_userById(uid_t id)
+{ return (userdb__byId(&userdb__users, id)); }
+
+/* --- @userdb_iterateUsers@, @userdb_iterateUsers_r@ --- *
+ *
+ * Arguments:	@userdb_iter *i@ = pointer to a symbol table iterator object
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises an iteration for the user database.
+ */
+
+void userdb_iterateUsers(void)
+{ userdb_iterateUsers_r(&userdb__useri); }
+
+void userdb_iterateUsers_r(userdb_iter *i)
+{ sym_createIter(i, &userdb__users.nmap); }
+
+/* --- @userdb_nextUser@, @userdb_nextUser_r@ --- *
+ *
+ * Arguments:	@userdb_iter *i@ = pointer to a symbol table iterator oject
+ *
+ * Returns:	Pointer to the next user block, or null.
+ *
+ * Use:		Returns another user block.
+ */
+
+struct passwd *userdb_nextUser(void)
+{ return (userdb_nextUser_r(&userdb__useri)); }
+
+struct passwd *userdb_nextUser_r(userdb_iter *i)
+{
+  userdb__sym *s = sym_next(i);
+  return (s ? s->rec : 0);
+}
+
+/* --- @userdb_groupByName@, @userdb_groupById@ --- *
+ *
+ * Arguments:	@const char *name@ = pointer to group's name
+ *		@gid_t id@ = group id to find
+ *
+ * Returns:	Pointer to group block, or zero if not found.
+ *
+ * Use:		Looks up a group by name or id.
+ */
+
+struct group *userdb_groupByName(const char *name)
+{ return (userdb__byName(&userdb__groups, name)); }
+
+struct group *userdb_groupById(gid_t id)
+{ return (userdb__byId(&userdb__groups, id)); }
+
+/* --- @userdb_iterateGroups@, @userdb_iterateGroups_r@ --- *
+ *
+ * Arguments:	@userdb_iter *i@ = pointer to a symbol table iterator object
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises an iteration for the group database.
+ */
+
+void userdb_iterateGroups(void)
+{ userdb_iterateGroups_r(&userdb__groupi); }
+
+void userdb_iterateGroups_r(userdb_iter *i)
+{ sym_createIter(i, &userdb__groups.nmap); }
+
+/* --- @userdb_nextGroup@, @userdb_nextGroup_r@ --- *
+ *
+ * Arguments:	@userdb_iter *i@ = pointer to a symbol table iterator oject
+ *
+ * Returns:	Pointer to the next group block, or null.
+ *
+ * Use:		Returns another group block.
+ */
+
+struct group *userdb_nextGroup(void)
+{ return (userdb_nextGroup_r(&userdb__groupi)); }
+
+struct group *userdb_nextGroup_r(userdb_iter *i)
+{
+  userdb__sym *s = sym_next(i);
+  return (s ? s->rec : 0);
+}
+
+/* --- @userdb_init@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises the user database.
+ */
+
+void userdb_init(void)
+{
+  userdb__createMap(&userdb__users);
+  userdb__createMap(&userdb__groups);
+}
+
+/* --- @userdb_reinit@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Reinitialises the user database.
+ */
+
+void userdb_reinit(void)
+{
+  userdb__clearMap(&userdb__users, userdb_freeUser);
+  userdb__clearMap(&userdb__groups, userdb_freeGroup);
+  userdb_init();
+}
+
+/*----- Test rig ----------------------------------------------------------*/
+
+#ifdef TEST_RIG
+
+void dumpit(const char *msg)
+{
+  printf("\n\n$$$ %s\n", msg);
+
+  {
+    struct passwd *pw;
+    for (userdb_iterateUsers(); (pw = userdb_nextUser()) != 0; )
+      userdb__dumpUser(pw, stdout);
+  }
+
+  {
+    struct group *gr;
+    for (userdb_iterateGroups(); (gr = userdb_nextGroup()) != 0; )
+      userdb__dumpGroup(gr, stdout);
+  }
+}
+
+int main(void)
+{
+  userdb_init();
+  dumpit("cleared");
+  userdb_local();
+  dumpit("local");
+  userdb_yp();
+  dumpit("yp");
+  return (0);
+}
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/userdb.h b/src/userdb.h
new file mode 100644
index 0000000..91038d7
--- /dev/null
+++ b/src/userdb.h
@@ -0,0 +1,235 @@
+/* -*-c-*-
+ *
+ * $Id*
+ *
+ * User database management
+ *
+ * (c) 1997 EBI
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: userdb.h,v $
+ * Revision 1.1  1997/07/21 13:47:42  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef USERDB_H
+#define USERDB_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required headers --------------------------------------------------*/
+
+#include <grp.h>
+#include <pwd.h>
+
+#ifndef SYM_H
+#  include "sym.h"
+#endif
+
+/*----- Type definitions --------------------------------------------------*/
+
+/* --- Iterator objects --- */
+
+typedef sym_iter userdb_iter;
+
+/*----- User and group block manipulation ---------------------------------*/
+
+/* --- @userdb_copyUser@ --- *
+ *
+ * Arguments:	@struct passwd *pw@ = pointer to block to copy
+ *
+ * Returns:	Pointer to the copy.
+ *
+ * Use:		Copies a user block.  The copy is `deep' so all the strings
+ *		are copied too.  Free the copy with @userdb_freeUser@ when
+ *		you don't want it any more.
+ */
+
+extern struct passwd *userdb_copyUser(struct passwd */*pw*/);
+
+/* --- @userdb_freeUser@ --- *
+ *
+ * Arguments:	@void *rec@ = pointer to a user record
+ *
+ * Returns:	---
+ *
+ * Use:		Frees a user record.
+ */
+
+extern void userdb_freeUser(void */*rec*/);
+
+/* --- @userdb_copyGroup@ --- *
+ *
+ * Arguments:	@struct group *gr@ = pointer to group block
+ *
+ * Returns:	Pointer to copied block
+ *
+ * Use:		Copies a group block.  The copy is `deep' so all the strings
+ *		are copied too.  Free the copy with @userdb_freeGroup@ when
+ *		you don't want it any more.
+ */
+
+extern struct group *userdb_copyGroup(struct group */*gr*/);
+
+/* --- @userdb_freeGroup@ --- *
+ *
+ * Arguments:	@void *rec@ = pointer to a group record
+ *
+ * Returns:	---
+ *
+ * Use:		Frees a group record.
+ */
+
+extern void userdb_freeGroup(void */*rec*/);
+
+/*----- Internal user database mapping ------------------------------------*/
+
+/* --- @userdb_local@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Reads the local list of users into the maps.
+ */
+
+extern void userdb_local(void);
+
+/* --- @userdb_yp@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Fetches the YP database of users.
+ */
+
+extern void userdb_yp(void);
+
+/* --- @userdb_userByName@, @userdb_userById@ --- *
+ *
+ * Arguments:	@const char *name@ = pointer to user's name
+ *		@uid_t id@ = user id to find
+ *
+ * Returns:	Pointer to user block, or zero if not found.
+ *
+ * Use:		Looks up a user by name or id.
+ */
+
+extern struct passwd *userdb_userByName(const char */*name*/);
+extern struct passwd *userdb_userById(uid_t /*id*/);
+
+/* --- @userdb_iterateUsers@, @userdb_iterateUsers_r@ --- *
+ *
+ * Arguments:	@userdb_iter *i@ = pointer to a symbol table iterator object
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises an iteration for the user database.
+ */
+
+extern void userdb_iterateUsers(void);
+extern void userdb_iterateUsers_r(userdb_iter */*i*/);
+
+/* --- @userdb_nextUser@, @userdb_nextUser_r@ --- *
+ *
+ * Arguments:	@userdb_iter *i@ = pointer to a symbol table iterator oject
+ *
+ * Returns:	Pointer to the next user block, or null.
+ *
+ * Use:		Returns another user block.
+ */
+
+extern struct passwd *userdb_nextUser(void);
+extern struct passwd *userdb_nextUser_r(userdb_iter */*i*/);
+
+/* --- @userdb_groupByName@, @userdb_groupById@ --- *
+ *
+ * Arguments:	@const char *name@ = pointer to group's name
+ *		@gid_t id@ = group id to find
+ *
+ * Returns:	Pointer to group block, or zero if not found.
+ *
+ * Use:		Looks up a group by name or id.
+ */
+
+extern struct group *userdb_groupByName(const char */*name*/);
+extern struct group *userdb_groupById(gid_t /*id*/);
+
+/* --- @userdb_iterateGroups@, @userdb_iterateGroups_r@ --- *
+ *
+ * Arguments:	@userdb_iter *i@ = pointer to a symbol table iterator object
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises an iteration for the group database.
+ */
+
+extern void userdb_iterateGroups(void);
+extern void userdb_iterateGroups_r(userdb_iter */*i*/);
+
+/* --- @userdb_nextGroup@, @userdb_nextGroup_r@ --- *
+ *
+ * Arguments:	@userdb_iter *i@ = pointer to a symbol table iterator oject
+ *
+ * Returns:	Pointer to the next group block, or null.
+ *
+ * Use:		Returns another group block.
+ */
+
+extern struct group *userdb_nextGroup(void);
+extern struct group *userdb_nextGroup_r(userdb_iter */*i*/);
+
+/* --- @userdb_init@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Initialises the user database.
+ */
+
+extern void userdb_init(void);
+
+/* --- @userdb_reinit@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	---
+ *
+ * Use:		Reinitialises the user database.
+ */
+
+extern void userdb_reinit(void);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..a46a310
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,199 @@
+/* -*-c-*-
+ *
+ * $Id: utils.c,v 1.1 1997/07/21 13:47:42 mdw Exp $
+ *
+ * Miscellaneous useful bits of code.
+ *
+ * (c) 1997 Mark Wooding
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: utils.c,v $
+ * Revision 1.1  1997/07/21 13:47:42  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/* --- ANSI headers --- */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* --- Local headers --- */
+
+#include "config.h"
+#include "utils.h"
+
+/*----- Static data -------------------------------------------------------*/
+
+static const char *myname = 0;		/* What's my name? */
+
+/*----- Program name handling ---------------------------------------------*/
+
+/* --- @quis@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	Pointer to the program name.
+ *
+ * Use:		Returns the program name.
+ */
+
+const char *quis(void)
+{
+  return (myname);
+}
+
+/* --- @ego@ --- *
+ *
+ * Arguments:	@const char *p@ = pointer to program name
+ *
+ * Returns:	---
+ *
+ * Use:		Tells the utils library what the program's name is.
+ */
+
+#ifndef PATHSEP
+#  if defined(__riscos)
+#    define PATHSEP '.'
+#  elif defined(__unix) || defined(unix)
+#    define PATHSEP '/'
+#  else
+#    define PATHSEP '\\'
+#  endif
+#endif
+
+void ego(const char *p)
+{
+  const char *q = p;
+  while (*q) {
+    if (*q++ == PATHSEP)
+      p = q;
+  }
+  myname = p;
+}
+
+/*----- Error reporting ---------------------------------------------------*/
+
+/* --- @moan@ --- *
+ *
+ * Arguments:	@const char *f@ = a @printf@-style format string
+ *		@...@ = other arguments
+ *
+ * Returns:	---
+ *
+ * Use:		Reports an error.
+ */
+
+void moan(const char *f, ...)
+{
+  va_list ap;
+  va_start(ap, f);
+  fprintf(stderr, "%s: ", myname);
+  vfprintf(stderr, f, ap);
+  va_end(ap);
+  putc('\n', stderr);
+}
+
+/* --- @die@ --- *
+ *
+ * Arguments:	@const char *f@ = a @printf@-style format string
+ *		@...@ = other arguments
+ *
+ * Returns:	Never.
+ *
+ * Use:		Reports an error and hari-kiris.  Like @moan@ above, only
+ *		more permanent.
+ */
+
+void die(const char *f, ...)
+{
+  va_list ap;
+  va_start(ap, f);
+  fprintf(stderr, "%s: ", myname);
+  vfprintf(stderr, f, ap);
+  va_end(ap);
+  putc('\n', stderr);
+  exit(EXIT_FAILURE);
+}
+
+/*----- Memory management functions ---------------------------------------*/
+
+/* --- @xmalloc@ --- *
+ *
+ * Arguments:	@size_t sz@ = size of block to allocate
+ *
+ * Returns:	Pointer to allocated block.
+ *
+ * Use:		Allocates memory.  If the memory isn't available, we don't
+ *		hang aroung long enough for a normal function return.
+ */
+
+void *xmalloc(size_t sz)
+{
+  void *p = malloc(sz);
+  if (!p)
+    die("not enough memory");
+  return (p);
+}
+
+/* --- @xstrdup@ --- *
+ *
+ * Arguments:	@const char *s@ = pointer to a string
+ *
+ * Returns:	Pointer to a copy of the string.
+ *
+ * Use:		Copies a string (like @strdup@ would, if it existed).
+ */
+
+char *xstrdup(const char *s)
+{
+  size_t sz = strlen(s) + 1;
+  char *p = xmalloc(sz);
+  memcpy(p, s, sz);
+  return (p);
+}
+
+/* --- @xrealloc@ --- *
+ *
+ * Arguments:	@void *p@ = pointer to a block of memory
+ *		@size_t sz@ = new size desired for the block
+ *
+ * Returns:	Pointer to the resized memory block (which is almost
+ *		certainly not in the same place any more).
+ *
+ * Use:		Resizes a memory block.
+ */
+
+void *xrealloc(void *p, size_t sz)
+{
+  p = realloc(p, sz);
+  if (!p)
+    die("not enough memory");
+  return (p);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/src/utils.h b/src/utils.h
new file mode 100644
index 0000000..09ee6d2
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,176 @@
+/* -*-c-*-
+ *
+ * $Id: utils.h,v 1.1 1997/07/21 13:47:42 mdw Exp $
+ *
+ * Miscellaneous useful bits of code.
+ *
+ * (c) 1997 Mark Wooding
+ */
+
+/*----- Licencing notice --------------------------------------------------*
+ *
+ * This file is part of `become'
+ *
+ * `Become' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `Become' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `become'; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: utils.h,v $
+ * Revision 1.1  1997/07/21 13:47:42  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Required header files ---------------------------------------------*/
+
+#include <stddef.h>
+#include <stdlib.h>
+
+/*----- Storing and retrieving numbers ------------------------------------*
+ *
+ * These use big-endian conventions, because they seem more usual in network
+ * applications.  I actually think that little-endian is more sensible...
+ */
+
+#define load32(p)							\
+  ((((unsigned char)(p)[0] & 0xFF) << 24) |				\
+   (((unsigned char)(p)[1] & 0xFF) << 16) |				\
+   (((unsigned char)(p)[2] & 0xFF) <<  8) |				\
+   (((unsigned char)(p)[3] & 0xFF) <<  0))
+
+#define store32(p, v)							\
+  ((p)[0] = ((unsigned long)(v) >> 24) & 0xFF,				\
+   (p)[1] = ((unsigned long)(v) >> 16) & 0xFF,				\
+   (p)[2] = ((unsigned long)(v) >>  8) & 0xFF,				\
+   (p)[3] = ((unsigned long)(v) >>  0) & 0xFF)
+
+/* --- Little-endian versions (for MD5) --- */
+
+#define load32_l(p)							\
+  ((((unsigned char)(p)[0] & 0xFF) <<  0) |				\
+   (((unsigned char)(p)[1] & 0xFF) <<  8) |				\
+   (((unsigned char)(p)[2] & 0xFF) << 16) |				\
+   (((unsigned char)(p)[3] & 0xFF) << 24))
+
+#define store32_l(p, v)							\
+  ((p)[0] = ((unsigned long)(v) >>  0) & 0xFF,				\
+   (p)[1] = ((unsigned long)(v) >>  8) & 0xFF,				\
+   (p)[2] = ((unsigned long)(v) >> 16) & 0xFF,				\
+   (p)[3] = ((unsigned long)(v) >> 24) & 0xFF)
+
+/*----- Program name handling ---------------------------------------------*/
+
+/* --- @quis@ --- *
+ *
+ * Arguments:	---
+ *
+ * Returns:	Pointer to the program name.
+ *
+ * Use:		Returns the program name.
+ */
+
+extern const char *quis(void);
+
+/* --- @ego@ --- *
+ *
+ * Arguments:	@const char *p@ = pointer to program name
+ *
+ * Returns:	---
+ *
+ * Use:		Tells the utils library what the program's name is.
+ */
+
+extern void ego(const char */*p*/);
+
+/*----- Error reporting ---------------------------------------------------*/
+
+/* --- @moan@ --- *
+ *
+ * Arguments:	@const char *f@ = a @printf@-style format string
+ *		@...@ = other arguments
+ *
+ * Returns:	---
+ *
+ * Use:		Reports an error.
+ */
+
+extern void moan(const char */*f*/, ...);
+
+/* --- @die@ --- *
+ *
+ * Arguments:	@const char *f@ = a @printf@-style format string
+ *		@...@ = other arguments
+ *
+ * Returns:	Never.
+ *
+ * Use:		Reports an error and hari-kiris.  Like @moan@ above, only
+ *		more permanent.
+ */
+
+extern void die(const char */*f*/, ...);
+
+/*----- Memory management functions ---------------------------------------*/
+
+/* --- @xmalloc@ --- *
+ *
+ * Arguments:	@size_t sz@ = size of block to allocate
+ *
+ * Returns:	Pointer to allocated block.
+ *
+ * Use:		Allocates memory.  If the memory isn't available, we don't
+ *		hang aroung long enough for a normal function return.
+ */
+
+extern void *xmalloc(size_t /*sz*/);
+
+/* --- @xstrdup@ --- *
+ *
+ * Arguments:	@const char *s@ = pointer to a string
+ *
+ * Returns:	Pointer to a copy of the string.
+ *
+ * Use:		Copies a string (like @strdup@ would, if it existed).
+ */
+
+extern char *xstrdup(const char */*s*/);
+
+/* --- @xrealloc@ --- *
+ *
+ * Arguments:	@void *p@ = pointer to a block of memory
+ *		@size_t sz@ = new size desired for the block
+ *
+ * Returns:	Pointer to the resized memory block (which is almost
+ *		certainly not in the same place any more).
+ *
+ * Use:		Resizes a memory block.
+ */
+
+extern void *xrealloc(void */*p*/, size_t /*sz*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
-- 
2.11.0

