summaryrefslogtreecommitdiffstats
path: root/userdb
diff options
context:
space:
mode:
authorSam Varshavchik2013-08-19 16:39:41 -0400
committerSam Varshavchik2013-08-25 14:43:51 -0400
commit9c45d9ad13fdf439d44d7443ae75da15ea0223ed (patch)
tree7a81a04cb51efb078ee350859a64be2ebc6b8813 /userdb
parenta9520698b770168d1f33d6301463bb70a19655ec (diff)
downloadcourier-libs-9c45d9ad13fdf439d44d7443ae75da15ea0223ed.tar.bz2
Initial checkin
Imported from subversion report, converted to git. Updated all paths in scripts and makefiles, reflecting the new directory hierarchy.
Diffstat (limited to 'userdb')
-rw-r--r--userdb/Makefile.am73
-rw-r--r--userdb/configure.in170
-rw-r--r--userdb/html2man.pl.in174
-rw-r--r--userdb/makeuserdb.in171
-rw-r--r--userdb/makeuserdb.sgml324
-rw-r--r--userdb/pw2userdb.in70
-rw-r--r--userdb/userdb.c411
-rw-r--r--userdb/userdb.h68
-rw-r--r--userdb/userdb.pl.in263
-rw-r--r--userdb/userdb.sgml265
-rw-r--r--userdb/userdb2.c57
-rw-r--r--userdb/userdbmkpw.c119
-rw-r--r--userdb/userdbpw.c251
-rw-r--r--userdb/userdbpw.sgml129
14 files changed, 2545 insertions, 0 deletions
diff --git a/userdb/Makefile.am b/userdb/Makefile.am
new file mode 100644
index 0000000..90a72ae
--- /dev/null
+++ b/userdb/Makefile.am
@@ -0,0 +1,73 @@
+#
+# Copyright 1998 - 2008 Double Precision, Inc. See COPYING for
+# distribution information.
+
+noinst_LTLIBRARIES=libuserdb.la
+
+libuserdb_la_SOURCES=userdb.c userdb.h userdb2.c userdbmkpw.c
+
+BUILT_SOURCES= makeuserdb.html.in makeuserdb.8.in \
+ userdb.html.in userdb.8.in \
+ userdbpw.html.in userdbpw.8.in
+
+noinst_SCRIPTS=makeuserdb pw2userdb dummy
+noinst_PROGRAMS=userdbpw
+noinst_DATA=makeuserdb.html userdb.html userdbpw.html
+
+userdbpw_SOURCES=userdbpw.c
+userdbpw_LDADD=libuserdb.la @HMACLIB@ @MD5LIB@ @SHA1LIB@ @CRYPTLIBS@
+userdbpw_LDFLAGS=-static
+
+man8=makeuserdb.8 userdb.8 userdbpw.8
+man_MANS=$(man8)
+
+CLEANFILES=$(man8) $(noinst_DATA) dummy
+
+makeuserdb.html: makeuserdb.html.in
+ ./config.status --file=$@
+
+makeuserdb.8: makeuserdb.8.in
+ ./config.status --file=$@
+
+userdb.html: userdb.html.in
+ ./config.status --file=$@
+
+userdb.8: userdb.8.in
+ ./config.status --file=$@
+
+userdbpw.html: userdbpw.html.in
+ ./config.status --file=$@
+
+userdbpw.8: userdbpw.8.in
+ ./config.status --file=$@
+
+if HAVE_SGML
+makeuserdb.html.in: makeuserdb.sgml ../docbook/sgml2html
+ ../docbook/sgml2html makeuserdb.sgml makeuserdb.html.in
+
+makeuserdb.8.in: makeuserdb.sgml ../docbook/sgml2man
+ ../docbook/sgml2man makeuserdb.sgml makeuserdb.8.in
+ mv makeuserdb.8 makeuserdb.8.in
+
+userdb.html.in: userdb.sgml ../docbook/sgml2html
+ ../docbook/sgml2html userdb.sgml userdb.html.in
+
+userdb.8.in: userdb.sgml ../docbook/sgml2man
+ ../docbook/sgml2man userdb.sgml userdb.8.in
+ mv userdb.8 userdb.8.in
+
+userdbpw.html.in: userdbpw.sgml ../docbook/sgml2html
+ ../docbook/sgml2html userdbpw.sgml userdbpw.html.in
+
+userdbpw.8.in: userdbpw.sgml ../docbook/sgml2man
+ ../docbook/sgml2man userdbpw.sgml userdbpw.8.in
+ mv userdbpw.8 userdbpw.8.in
+
+endif
+
+EXTRA_DIST=$(BUILT_SOURCES)
+
+# Temporary autoconf kludge:
+
+dummy: $(man8)
+ touch dummy
diff --git a/userdb/configure.in b/userdb/configure.in
new file mode 100644
index 0000000..1a4585b
--- /dev/null
+++ b/userdb/configure.in
@@ -0,0 +1,170 @@
+dnl Process this file with autoconf to produce a configure script.
+dnl
+dnl
+dnl Copyright 1998 - 2007 Double Precision, Inc. See COPYING for
+dnl distribution information.
+
+AC_INIT(libuserdb, 0.10, [courier-users@lists.sourceforge.net])
+
+>confdefs.h # Kill PACKAGE_ macros.
+
+LPATH="$PATH:/usr/local/bin"
+
+AC_CONFIG_SRCDIR(userdb.c)
+AC_CONFIG_AUX_DIR(../..)
+AM_INIT_AUTOMAKE([foreign no-define])
+AM_CONFIG_HEADER(config.h)
+
+dnl Checks for programs.
+AC_PROG_AWK
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LN_S
+AC_PROG_LIBTOOL
+AC_PATH_PROGS(PERL, perl5 perl, perl, $LPATH)
+
+if test "$PERL" = "perl"
+then
+ AC_MSG_ERROR(Perl not found.)
+fi
+
+if test "$GCC" = "yes"
+then
+ CFLAGS="$CFLAGS -Wall"
+fi
+
+CFLAGS="$CFLAGS -I.. -I${srcdir}/.."
+
+dnl Checks for libraries.
+
+AC_ARG_WITH(userdb, [ ], userdb="$withval", userdb="/etc/userdb")
+AC_SUBST(userdb)
+USERDB="`echo $userdb | tr '[a-z]' '[A-Z]'`"
+AC_SUBST(USERDB)
+
+AC_ARG_WITH(userdbmandir, [ ], mandir="$withval")
+
+AC_ARG_WITH(userdbtmpdir, [ ], tmpdir="$withval", tmpdir="")
+
+changequote({,})
+
+if test "$tmpdir" = ""
+then
+ tmpdir=`echo "$userdb" | sed 's/\/[^\/]*$//'`
+fi
+
+changequote([,])
+
+AC_SUBST(userdbtmpdir)
+AC_SUBST(tmpdir)
+
+AC_ARG_WITH(makedatprog, [ ], makedat="$withval", makedat="$bindir/makedat")
+AC_SUBST(makedat)
+
+if test -d ${srcdir}/../md5
+then
+ AC_DEFINE_UNQUOTED(HAVE_MD5,1,[ Whether libmd5.a is present ])
+ MD5LIB=../md5/libmd5.la
+else
+ MD5LIB=""
+fi
+AC_SUBST(MD5LIB)
+
+if test -d ${srcdir}/../sha1
+then
+ SHA1LIB=../sha1/libsha1.la
+else
+ SHA1LIB=""
+fi
+AC_SUBST(SHA1LIB)
+
+if test -d ${srcdir}/../libhmac
+then
+ AC_DEFINE_UNQUOTED(HAVE_HMAC,1, [ Whether libhmac.a is present ])
+ HMACLIB=../libhmac/libhmac.la
+else
+ HMACLIB=""
+fi
+AC_SUBST(HMACLIB)
+
+dnl Checks for header files.
+AC_HEADER_STDC
+AC_CHECK_HEADERS(sys/stat.h sys/time.h unistd.h fcntl.h termios.h crypt.h)
+AC_HEADER_TIME
+
+dnl Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+AC_PID_T
+AC_TYPE_UID_T
+AC_TYPE_SIGNAL
+AC_SYS_LARGEFILE
+
+dnl Other checks
+
+AC_CHECK_LIB(crypt, crypt, CRYPTLIBS="-lcrypt")
+saveLIBS="$LIBS"
+LIBS="$CRYPTLIBS $LIBS"
+AC_CHECK_FUNC(crypt, AC_DEFINE_UNQUOTED(HAVE_CRYPT,1,
+ [ Whether the crypt() function is available ]))
+LIBS="$saveLIBS"
+AC_CACHE_CHECK([for crypt() prototype],userdb_cv_NEED_CRYPT_PROTOTYPE,
+
+ AC_TRY_COMPILE( [
+ #if HAVE_CRYPT_H
+ #include <crypt.h>
+ #endif
+ #if HAVE_UNISTD_H
+ #include <unistd.h>
+ #endif
+ int crypt(int, int);
+
+ ], [], userdb_cv_NEED_CRYPT_PROTOTYPE=1,
+ userdb_cv_NEED_CRYPT_PROTOTYPE=0 )
+
+ )
+
+AC_DEFINE_UNQUOTED(NEED_CRYPT_PROTOTYPE, $userdb_cv_NEED_CRYPT_PROTOTYPE,
+ [ Whether crypt() must be explicitly prototyped ])
+AC_SUBST(CRYPTLIBS)
+
+AC_CHECK_FUNCS(isatty gettimeofday)
+
+AC_ARG_WITH(random, [ --with-random=/dev/urandom - location of the system random file generator
+--without-random - there is no system random file generator ],
+ random="$withval",
+ random="y")
+
+case "$random" in
+/*)
+ ;;
+y*|1*)
+ AC_CACHE_CHECK([for random source],userdb_cv_RANDOM,
+
+ if test -c /dev/urandom
+ then
+ userdb_cv_RANDOM=/dev/urandom
+ else
+ if test -c /dev/random
+ then
+ userdb_cv_RANDOM=/dev/random
+ else
+ userdb_cv_RANDOM="none"
+ fi
+ fi
+ )
+ random="$userdb_cv_RANDOM"
+ ;;
+*)
+ random="none"
+ ;;
+esac
+
+if test "$random" != "none"
+then
+ AC_DEFINE_UNQUOTED(RANDOM, "$random",
+ [ System random number generator ])
+fi
+
+AM_CONDITIONAL(HAVE_SGML, test -d ${srcdir}/../docbook)
+
+AC_OUTPUT(Makefile userdb.pl makeuserdb pw2userdb)
diff --git a/userdb/html2man.pl.in b/userdb/html2man.pl.in
new file mode 100644
index 0000000..0a8ca70
--- /dev/null
+++ b/userdb/html2man.pl.in
@@ -0,0 +1,174 @@
+#! @PERL@
+#
+# Copyright 1998 - 1999 Double Precision, Inc. See COPYING for
+# distribution information.
+
+############################################################################
+#
+# Preprocess HTML file: put all directives on a separate line. Remove
+# blank lines.
+#
+#
+############################################################################
+
+$pid=open(FD, "-|");
+
+die "Can't fork.\n" unless defined $pid;
+
+if ($pid == 0)
+{
+ while (<>)
+ {
+ if ( $_ =~ s/^ *<[lL][iI]>// )
+ {
+ $line=$_;
+ $line=<> if $line eq "\n";
+ chop $line;
+ $line =~ s/ - /\n/;
+ ($line0,$line1)=split(/\n/,$line);
+ $line0 =~ s/"/\\"/g;
+ $line0 =~ s/\\/\\\\/g;
+ print ".TP\n.B \"$line0\n$line1\n";
+ next;
+ }
+ while ( /<[^>]*\n$/ )
+ {
+ chop;
+ $foo=$_;
+ last unless defined ($_=<>);
+ $_="$foo$_";
+ }
+ print;
+ }
+ exit 0;
+}
+
+$pid2=open(FD2, "-|");
+die "Can't fork.\n" unless defined $pid2;
+
+sub dosubst {
+ s/<[^>]*>//g;
+ s/&nbsp;/ /g;
+ s/&lt;/</g;
+ s/&gt;/>/g;
+ s/&amp;/\&/g;
+}
+
+$INH1=0;
+$INBODY=0;
+
+$inpre=0;
+
+if ($pid2 == 0)
+{
+ while (<FD>)
+ {
+ s/\\/\\\\/g;
+ s/<[iI]>/\\fI/g;
+ s/<\/[iI]>/\\fP/g;
+ s/<BR>/\n.br/g;
+ s/<br>/\n.br/g;
+ s/<[pP]>/\n.PP\n/g;
+ s/^\n\././;
+
+ s/^ *// unless $inpre;
+ if (s/^<[hH]1>/.SH NAME\n/)
+ {
+ $INH1=1;
+ }
+ s/-/\\-/ if $INH1;
+ $INH1=0 if ( /<\/[hH]1>/ );
+
+ if (s/^<[hH]2>//)
+ {
+ $_=<FD> if $_ eq "\n";
+ &dosubst;
+ $_ =~ s/^/.SH "/;
+ print $_;
+ next;
+ }
+
+ if (s/^<[hH][3456789]>//)
+ {
+ $_=<FD> if $_ eq "\n";
+ &dosubst;
+ $_ =~ s/^/.SS "/;
+ print $_;
+ next;
+ }
+ if (/^ *<(TITLE|title)>/)
+ {
+ while ( ! /<\/(title|TITLE)>/)
+ {
+ chop;
+ $_ = $_ . <FD>;
+ }
+ }
+
+
+ if (/^ *<(TITLE|title)>(.*)<\/(title|TITLE)>/)
+ {
+ ($cmd, $desc)=split(/ - /,$2);
+ $cmd =~ s/ *$//;
+ $desc =~ s/^ *//;
+
+ open (DATE, 'date "+%B %e, %Y" | ')
+ || die "Can't run date.\n";
+ $date=<DATE>;
+ close(DATE);
+ chomp $date;
+ $TITLE=".TH \"$cmd\" [SECTION] \"$date\" \"Double Precision, Inc.\" \"\"\n";
+ next;
+ }
+
+ if (/^<!-- *SECTION/)
+ {
+ chop;
+ s/.*SECTION *//;
+ s/ .*//;
+ $SECTION=$_;
+ next;
+ }
+ if (/^<!-- \$Id/)
+ {
+ s/.*\$Id/\$Id/;
+ s/ *-->.*//;
+ $RCS=".\\\" $_";
+ print $RCS if $INBODY;
+ next;
+ }
+ if (/<\/(HEAD|head)>/)
+ {
+ $TITLE =~ s/\[SECTION\]/$SECTION/;
+ print $TITLE;
+ print $RCS;
+ print ".\\\" Copyright 1998-1999 Double Precision, Inc. See COPYING for\n";
+ print ".\\\" distribution information.\n";
+ $INBODY=1;
+ }
+
+ s/^\./\\\&./ unless /^\.(SH|PP|br|TP|B|I) / || /^\.(SH|PP|br|TP|B|I|)\n/;
+
+ $inpre=1 if s/^<(PRE|pre)>/.nf\n\n/;
+ $inpre=0 if s/<\/(PRE|pre)>/\n.fi\n.PP/;
+
+ &dosubst;
+ print "$_";
+ }
+ exit 0;
+}
+
+$first=1;
+$innf=0;
+while (<FD2>)
+{
+ $first=0 if /^.TH/;
+ next if $first;
+ next if (! $innf) && /^\n$/;
+ $innf=1 if /^\.nf/;
+ $innf=0 if /^\.fi/;
+
+ s/^ ? ? ?// if $innf;
+ print;
+}
+exit 0;
diff --git a/userdb/makeuserdb.in b/userdb/makeuserdb.in
new file mode 100644
index 0000000..4765309
--- /dev/null
+++ b/userdb/makeuserdb.in
@@ -0,0 +1,171 @@
+#! @PERL@
+#
+# Create userdb database
+#
+# Usage: makeuserdb
+#
+#
+# Copyright 1998 - 2006 Double Precision, Inc. See COPYING for
+# distribution information.
+
+use Fcntl ':flock';
+
+$prefix="@prefix@";
+$exec_prefix="@exec_prefix@";
+$bindir="@bindir@";
+
+$ENV{'PATH'}="@bindir@:/usr/bin:/usr/local/bin:/bin";
+
+$dbfile="@userdb@";
+
+$makedat="@makedat@";
+
+$name=shift @ARGV;
+if ($name eq "-f") {
+ $dbfile=shift @ARGV;
+ $dbfile=~s/\/$//;
+}
+
+$datfile=$dbfile.".dat";
+# XXX the lock file here is etc/userdb.lock but the userdb command uses etc/.lock.userdb
+$lockfile=$dbfile.".lock";
+$shadowfile=$dbfile."shadow.dat";
+$tmpdatfile=$dbfile.".tmp";
+$tmpshadowfile=$dbfile."shadow.tmp";
+
+$mode=(stat($dbfile))[2];
+die "$dbfile: not found.\n" unless defined $mode;
+
+die "$dbfile: MAY NOT HAVE GROUP OR WORLD PERMISSIONS!!\n"
+ if ( $mode & 077);
+
+eval {
+ die "SYMLINK\n" if -l $dbfile;
+};
+
+die "ERROR: Wrong makeuserdb command.\n ($dbfile is a symbolic link)\n"
+ if $@ eq "SYMLINK\n";
+
+eval {
+ die "SYMLINK\n" if -l $datfile;
+};
+
+die "ERROR: Wrong makeuserdb command.\n ($datfile is a symbolic link)\n"
+ if $@ eq "SYMLINK\n";
+
+eval {
+ die "SYMLINK\n" if -l $shadowfile;
+};
+
+die "ERROR: Wrong makeuserdb command.\n ($shadowfile is a symbolic link)\n"
+ if $@ eq "SYMLINK\n";
+
+umask (022);
+open(LOCK, ">$lockfile") or die "Can't open $lockfile: $!";
+flock(LOCK,LOCK_EX) || die "Can't lock $lockfile: $!";
+
+open (DBPIPE, "| ${makedat} - $tmpdatfile $datfile") || die "$!\n";
+umask (066);
+open (SHADOWPIPE, "| ${makedat} - $tmpshadowfile $shadowfile")
+ || die "$!\n";
+
+eval {
+
+ if ( -d $dbfile )
+ {
+ my (@dirs);
+ my (@files);
+
+ push @dirs, $dbfile;
+ while ( $#dirs >= 0 )
+ {
+ $dir=shift @dirs;
+ opendir(DIR, $dir) || die "$!\n";
+ while ( defined($filename=readdir(DIR)))
+ {
+ next if $filename =~ /^\./;
+ if ( -d "$dir/$filename" )
+ {
+ push @dirs, "$dir/$filename";
+ }
+ else
+ {
+ push @files, "$dir/$filename";
+ }
+ }
+ closedir(DIR);
+ }
+
+ while (defined ($filename=shift @files))
+ {
+ &do_file( $filename );
+ }
+ }
+ else
+ {
+ &do_file( $dbfile );
+ }
+
+ print DBPIPE ".\n" || die "$!\n";
+ print SHADOWPIPE ".\n" || die "$!\n";
+} ;
+
+$err=$@;
+if ($err)
+{
+ print "$err";
+ exit (1);
+}
+
+close(DBPIPE) || die "$!\n";
+exit (1) if $?;
+close(SHADOWPIPE) || die "$!\n";
+exit (1) if $?;
+
+exit (0);
+
+sub do_file {
+my ($filename)=@_;
+my ($addr, $fields);
+my (@nonshadow, @shadow);
+
+my $location=substr($filename, length("@userdb@"));
+
+ $location =~ s/^\///;
+ $location =~ s/\/$//;
+ $location .= "/" if $location ne "";
+
+ open (F, $filename) || die "$filename: $!\n";
+ while (<F>)
+ {
+ if ( /^[\n#]/ || ! /^([^\t]*)\t(.*)/ )
+ {
+ print DBPIPE;
+ print SHADOWPIPE;
+ next;
+ }
+ ($addr,$fields)=($1,$2);
+ undef @nonshadow;
+ undef @shadow;
+
+ foreach ( split (/\|/, $fields ) )
+ {
+ if ( /^[^=]*pw=/ )
+ {
+ push @shadow, $_;
+ }
+ else
+ {
+ push @nonshadow, $_;
+ }
+ }
+
+ push @nonshadow, "_=$location";
+ ( print DBPIPE "$addr\t" . join("|", @nonshadow) . "\n"
+ || die "$!\n" ) if $#nonshadow >= 0;
+ ( print SHADOWPIPE "$addr\t" . join("|", @shadow) . "\n"
+ || die "$!\n" ) if $#shadow >= 0;
+ }
+ print DBPIPE "\n";
+ print SHADOWPIPE "\n";
+}
diff --git a/userdb/makeuserdb.sgml b/userdb/makeuserdb.sgml
new file mode 100644
index 0000000..f10270a
--- /dev/null
+++ b/userdb/makeuserdb.sgml
@@ -0,0 +1,324 @@
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+ "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+
+ <!-- Copyright 1998 - 2007 Double Precision, Inc. See COPYING for -->
+ <!-- distribution information. -->
+
+<refentry id="makeuserdb">
+
+ <refmeta>
+ <refentrytitle>makeuserdb</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo>Double Precision, Inc.</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>makeuserdb</refname>
+ <refpurpose>create @userdb@</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>makeuserdb</command>
+ <arg>-f <replaceable>filename</replaceable></arg>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>pw2userdb</command>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>vchkpw2userdb</command>
+ <arg>--vpopmailhome=<replaceable>dir</replaceable></arg>
+ <arg>--todir=<replaceable>dir</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>
+<command>makeuserdb</command> creates <filename>@userdb@.dat</filename> from
+the contents of <filename>@userdb@</filename>.
+<filename>@userdb@</filename>'s contents are described later in this document.
+<application>Maildrop</application>,
+<application>Courier</application>, and other applications use
+<filename>@userdb@.dat</filename> as a
+substitute/complement for your system password file.
+The usual purpose for
+<filename>@userdb@.dat</filename> is to specify "virtual" accounts - accounts
+that do
+not have an associated system login.
+Usually (but not necessarily) all virtual accounts share the same
+system userid.
+<filename>@userdb@.dat</filename> may also replace
+your system password file. Because the system password file is a text file,
+when there's a large number of accounts it will be significantly faster to
+search
+<filename>@userdb.dat@</filename>, which is a binary database,
+instead of a flat text file that the system password file usually is.</para>
+
+ <para>
+The <command>makeuserdb</command> command can be safely executed during
+normal system activity.</para>
+
+ <para>
+The <option>-f</option> option creates
+<filename><replaceable>filename</replaceable>.dat</filename> from
+<filename><replaceable>filename</replaceable></filename>, instead of the
+default <filename>@userdb@.dat</filename> from
+<filename>@userdb@</filename>.</para>
+
+ <refsect2>
+ <title>Format of <filename>@userdb@</filename></title>
+ <para>
+<filename>@userdb@</filename> is a plain text file that can be created using
+any text editor. Blank lines are ignored. Lines that start with the #
+character are comments, and are also ignored.
+Other lines define properties of a single
+"account", one line per account.
+<filename>@userdb@</filename> may be a directory instead of a plain file.
+In that case all files in <filename>@userdb@</filename> are essentially
+concatenated, and are treated as a single file.
+Each line takes the following format:</para>
+
+ <blockquote>
+ <informalexample>
+ <literallayout><replaceable>name</replaceable><token>&lt;TAB&gt;</token><replaceable>field</replaceable>=<replaceable>value</replaceable>|<replaceable>field</replaceable>=<replaceable>value</replaceable>...</literallayout>
+ </informalexample>
+ </blockquote>
+
+<para><replaceable>name</replaceable> is the account name.
+<replaceable>name</replaceable> MUST contain only lowercase characters
+If <application>Courier</application> is
+configured to treat lowercase and uppercase account names as
+identical, <replaceable>name</replaceable> is followed by exactly one tab
+character, then a list of field/value pairs separated by vertical slashes.
+<replaceable>field</replaceable> is the name of the field,
+<replaceable>value</replaceable> is the field value.
+Fields and values themself cannot contain slashes or control characters.
+Fields may be
+specified in any order. Here are all the currently defined fields. Note that
+not every field is used by every application that reads
+<filename>@userdb@.dat</filename>.</para>
+
+ <blockquote>
+ <para>
+<parameter>uid</parameter> - <replaceable>value</replaceable> is a (possibly)
+unique numerical user ID for this account.</para>
+
+ <para>
+<parameter>gid</parameter> - <replaceable>value</replaceable> is a (possibly)
+unique numerical group ID for this account.</para>
+
+ <para>
+<parameter>home</parameter> - <replaceable>value</replaceable> is the account's home
+directory.</para>
+
+ <para>
+<parameter>shell</parameter> - <replaceable>value</replaceable> is the account's default
+login shell.</para>
+
+
+ <para>
+<parameter>systempw</parameter> - <replaceable>value</replaceable> is the account's
+password. See
+<ulink url="userdbpw.html"><citerefentry><refentrytitle>userdbpw</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink>
+for details on how to set up this field.</para>
+
+ <para>
+<parameter>pop3pw, esmtppw, imappw...</parameter> - <replaceable>value</replaceable>
+specifies a separate password used only for authenticating access using a
+specific service, such as POP3, IMAP, or anything else. If not defined,
+<parameter>systempw</parameter> is always used. This allows access to an account to be
+restricted only to certain services, such as POP3, even if other services
+are also enabled on the server.</para>
+
+ <para>
+<parameter>mail</parameter> - <replaceable>value</replaceable> specifies the location of
+the account's Maildir mailbox. This is an optional field that is normally
+used when <command>userdb</command> is used to provide aliases for other
+mail accounts. For example, one particular multi-domain E-mail
+service configuration
+that's used by both <application>Qmail</application> and
+<application>Courier</application> servers is to deliver mail for a
+mailbox in a virtual domain, such as "user@example.com", to a local mailbox
+called "example-user". Instead of requiring the E-mail account
+holder to log in as
+"example-user" to download mail from this account, a <command>userdb</command>
+entry for "user@example.com" is set up with <parameter>mail</parameter> set to the
+location of example-user's Maildir mailbox, thus hiding the internal
+mail configuration from the E-mail account holder's view.</para>
+
+ <para>
+<parameter>quota</parameter> - <replaceable>value</replaceable> specifies the
+maildir quota for the account's Maildir.
+This has nothing to do with actual filesystem quotas.
+<application>Courier</application> has a
+software-based Maildir quota enforcement
+mechanism which requires additional setup and configuration.
+See
+<ulink url="maildirquota.html"><citerefentry><refentrytitle>maildirquota</refentrytitle><manvolnum>7</manvolnum></citerefentry></ulink>
+for additional information.</para>
+ </blockquote>
+ </refsect2>
+ <refsect2>
+ <title><filename>@userdb@shadow.dat</filename></title>
+
+ <para>
+All fields whose name ends with 'pw' will NOT copied to
+<filename>@userdb@.dat</filename>. These fields will be copied to
+<filename>@userdb@shadow.dat</filename>.
+<command>makeuserdb</command> creates <filename>@userdb@shadow.dat</filename>
+without any group and world permissions.
+Note that <command>makeuserdb</command> reports an error
+if <command>@userdb@</command> has any group
+or world permissions.</para>
+ </refsect2>
+
+ <refsect2>
+
+ <title>CONVERTING <filename>/etc/passwd</filename>
+and vpopmail to <filename>@userdb@</filename> format</title>
+
+ <para>
+<command>pw2userdb</command> reads the <filename>/etc/passwd</filename> and
+<filename>/etc/shadow</filename> files and converts all entries to the
+<filename>@userdb@</filename> format,
+printing the result on standard output.
+The output of <command>pw2userdb</command>
+can be saved as <command>@userdb@</command> (or as some file in this
+subdirectory).
+Linear searches of <filename>/etc/passwd</filename> can
+be very slow when you have
+tens of thousands of accounts.
+Programs like <command>maildrop</command> always look in
+<filename>@userdb@</filename> first.
+By saving the system password file in
+<filename>@userdb@</filename> it is possible to significantly reduce the
+amount of
+time it takes to look up this information.</para>
+
+ <para>
+After saving the output of <command>pw2userdb</command>, you must still run
+<command>makeuserdb</command> to create
+<filename>@userdb@.dat</filename>.</para>
+
+ <para>
+<command>vchkpw2userdb</command> converts a vpopmail-style
+directory hierarchy to the <filename>@userdb@</filename> format.
+This is an external virtual domain management package that's often used
+with <application>Qmail</application> servers.</para>
+
+ <para>
+Generally, an account named 'vpopmail' is reserved for this purpose.
+In
+that account the file <filename>users/vpasswd</filename> has the same
+layout as
+<filename>/etc/passwd</filename>, and performs a similar function, except
+that all userid in <filename>users/vpasswd</filename> have the same userid.
+Additionally, the
+<filename>domains</filename> subdirectory stores virtual accounts for
+multiple domains. For example,
+<filename>domains/example.com/vpasswd</filename>
+has the passwd file for the domain <parameter>example.com</parameter>.
+Some systems also have a soft link, <parameter>domains/default</parameter>,
+that points to a domain that's considered a "default" domain.</para>
+
+ <para>
+The <command>vchkpw2userdb</command> reads all this information, and tries to
+convert it into the <filename>@userdb@</filename> format. The
+<parameter>--vpopmailhost</parameter> option specifies the top level
+directory, if it is
+not the home directory of the vpopmail account.</para>
+
+ <para>
+The <command>vchkpw2userdb</command> script prints the results on standard
+output. If specified, the <parameter>--todir</parameter> option
+tries to convert all
+<filename>vpasswd</filename> files one at a time, saving each one
+individually in <replaceable>dir</replaceable>. For example:</para>
+
+<blockquote>
+ <informalexample>
+ <literallayout>
+mkdir @userdb@
+vchkpw2userdb --todir=@userdb@/vpopmail
+makeuserdb
+</literallayout>
+ </informalexample>
+ </blockquote>
+
+ <para>
+It is still necessary to run <command>makeuserdb</command>, of course, to
+create the binary database file <filename>@userdb@.dat</filename></para>
+
+ <para>
+NOTE: You are still required to create the <command>@userdb@</command> entry
+which maps
+system userids back to accounts,
+"<replaceable>uid</replaceable>=<token>&lt;TAB&gt;</token><replaceable>name</replaceable>",
+if that's applicable. <command>vchkpw2userdb</command> will not do it for
+you.</para>
+
+ <para>
+NOTE: <command>makeuserdb</command> may complain about duplicate entries, if
+your "default" entries in <filename>users/vpasswd</filename> or
+<filename>domains/default/vpasswd</filename> are the same as anything in any
+other <filename>@userdb@</filename> file. It is also likely that you'll end
+up with duplicate, but distinct, entries for every account in the default
+domain. For
+example, if your default domain is example.com, you'll end up with duplicate
+entries - you'll have entries for both <parameter>user</parameter> and
+<parameter>user@example.com</parameter>.</para>
+
+ <para>If you intend to maintain the master set of accounts using
+vchkpw/vpopmail,
+in order to avoid cleaning this up every time, you might want to consider
+doing the following: run <command>vchkpw2userdb</command> once, using the
+<parameter>--todir</parameter> option.
+Then, go into the resulting directory, and
+replace one of the redundant files with a soft link to
+<filename>/dev/null</filename>.
+This allows you to run
+<command>vchkpw2userdb</command> without having to go in and
+cleaning up again, afterwards.</para>
+ </refsect2>
+ </refsect1>
+
+ <refsect1>
+ <title>FILES</title>
+
+ <literallayout>
+<filename>@userdb@</filename>
+<filename>@userdb@.dat</filename>
+<filename>@userdb@shadow.dat</filename>
+<filename>@tmpdir@/userdb.tmp</filename> - temporary file
+<filename>@tmpdir@/userdbshadow.tmp</filename> - temporary file
+</literallayout>
+
+ </refsect1>
+ <refsect1>
+ <title>BUGS</title>
+
+
+ <para><command>makeuserdb</command> is a Perl script, and uses Perl's portable
+locking.
+Perl's documentation notes that certain combinations of locking options may
+not work with some networks.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>
+<ulink url="userdb.html"><citerefentry><refentrytitle>userdb</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink>,
+<ulink url="maildrop.html"><citerefentry><refentrytitle>maildrop</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink>,
+<ulink url="courier.html"><citerefentry><refentrytitle>courier</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink>,
+<ulink url="maildirquota.html"><citerefentry><refentrytitle>maildirquota</refentrytitle><manvolnum>7</manvolnum></citerefentry></ulink>.
+</para>
+
+ </refsect1>
+
+</refentry>
+
diff --git a/userdb/pw2userdb.in b/userdb/pw2userdb.in
new file mode 100644
index 0000000..b036d9d
--- /dev/null
+++ b/userdb/pw2userdb.in
@@ -0,0 +1,70 @@
+#! @PERL@
+#
+# Convert /etc/passwd and /etc/shadow to userdb format.
+#
+#
+# Copyright 1998 - 1999 Double Precision, Inc. See COPYING for
+# distribution information.
+
+use Getopt::Long;
+
+#
+# Some undocumented options here (for vchkpw2userdb)
+#
+
+die "Invalid options.\n" unless
+ GetOptions("passwd=s" => \$passwd, "shadow=s" => \$shadow,
+ "noshadow" => \$noshadow, "nouid" => \$nouid,
+ "domain=s" => \$domain, "vpopuid" => \$vpopuid );
+
+($dummy, $dummy, $fixed_uid, $fixed_gid)=getpwnam("vpopmail")
+ if $vpopuid;
+
+$passwd="/etc/passwd" unless $passwd =~ /./;
+$shadow="/etc/shadow" unless $shadow =~ /./;
+
+$domain="" unless $domain =~ /./;
+$domain="\@$domain" if $domain =~ /./;
+
+open(PASSWD, $passwd) || die "$!\n";
+
+while (<PASSWD>)
+{
+ chop if /\n$/;
+ next if /^#/;
+ ($acct,$passwd,$uid,$gid,$name,$home,$shell)=split( /:/ );
+
+ ($uid,$gid)=($fixed_uid,$fixed_gid) if $vpopuid;
+
+ $PASSWORD{$acct}=$passwd if $passwd ne "x";
+ $UID{$acct}=$uid;
+ $GID{$acct}=$gid;
+ $HOME{$acct}=$home;
+ $SHELL{$acct}=$shell;
+
+ $name =~ s/\|/./g; # Just in case
+ $GECOS{$acct}=$name;
+}
+close (PASSWD);
+
+if ( -f $shadow && ! $noshadow)
+{
+ open (SHADOW, $shadow) || die "$!\n";
+ while (<SHADOW>)
+ {
+ next if /^#/;
+ ($acct,$passwd,$dummy)=split(/:/);
+ $PASSWORD{$acct}=$passwd;
+ }
+ close (SHADOW);
+}
+
+while ( defined ($key=each %UID))
+{
+ print "$key$domain\tuid=$UID{$key}|gid=$GID{$key}|home=$HOME{$key}" .
+ ( $SHELL{$key} =~ /./ ? "|shell=$SHELL{$key}":"") .
+ ( $PASSWORD{$key} =~ /./ ? "|systempw=$PASSWORD{$key}":"") .
+ ( $GECOS{$key} =~ /./ ? "|gecos=$GECOS{$key}":"") .
+ "\n";
+ print "$UID{$key}=\t$key\n" unless $nouid;
+}
diff --git a/userdb/userdb.c b/userdb/userdb.c
new file mode 100644
index 0000000..e5a6066
--- /dev/null
+++ b/userdb/userdb.c
@@ -0,0 +1,411 @@
+/*
+** Copyright 1998 - 2007 Double Precision, Inc.
+** See COPYING for distribution information.
+*/
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "dbobj.h"
+#include "userdb.h"
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/types.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
+
+static struct dbobj d;
+static time_t dt;
+static ino_t di;
+
+static int initialized=0;
+int userdb_debug_level=0;
+
+/* Open userdb.dat, if already opened, see if it changed, if so reopen */
+
+void userdb_init(const char *n)
+{
+struct stat stat_buf;
+
+ if (initialized)
+ {
+ if (stat(n, &stat_buf) ||
+ stat_buf.st_mtime != dt ||
+ stat_buf.st_ino != di)
+ {
+ dbobj_close(&d);
+ initialized=0;
+ dt=stat_buf.st_mtime;
+ di=stat_buf.st_ino;
+ }
+ }
+ else if (stat(n, &stat_buf))
+ {
+ if (userdb_debug_level)
+ fprintf(stderr,
+ "DEBUG: userdb: unable to stat %s: %s\n",
+ n, strerror(errno));
+ return;
+ }
+ else
+ {
+ dt=stat_buf.st_mtime;
+ di=stat_buf.st_ino;
+ }
+
+ if (!initialized)
+ {
+ if (dbobj_open(&d, n, "R"))
+ {
+ if (userdb_debug_level)
+ fprintf(stderr,
+ "DEBUG: userdb: failed to open %s\n",
+ n);
+ return;
+ }
+ if (userdb_debug_level)
+ fprintf(stderr, "DEBUG: userdb: opened %s\n", n);
+ initialized=1;
+ }
+}
+
+void userdb_close()
+{
+ if (initialized)
+ {
+ dbobj_close(&d);
+ initialized=0;
+ }
+ userdb_debug_level=0;
+}
+
+void userdb_set_debug(int lvl)
+{
+ userdb_debug_level = lvl;
+}
+
+/* Fetch a record from userdb.dat */
+
+char *userdb(const char *u)
+{
+char *p,*q;
+size_t l;
+
+ if (!initialized)
+ {
+ errno=ENOENT;
+ return (0);
+ }
+
+ q=dbobj_fetch(&d, u, strlen(u), &l, "");
+ if (!q)
+ {
+ if (userdb_debug_level)
+ fprintf(stderr, "DEBUG: userdb: entry not found\n");
+ errno=ENOENT;
+ return(0);
+ }
+
+ p=malloc(l+1);
+ if (!p)
+ {
+ free(q);
+ return (0);
+ }
+
+ if (l) memcpy(p, q, l);
+ free(q);
+ p[l]=0;
+ return (p);
+}
+
+/* Return a pointer to a specific field in this record */
+
+const char *userdb_get(const char *u, const char *n, int *l)
+{
+int nl=strlen(n);
+
+ while (u && *u)
+ {
+ if (memcmp(u, n, nl) == 0 &&
+ (u[nl] == 0 || u[nl] == '=' || u[nl] == '|'))
+ {
+ u += nl;
+ *l=0;
+ if (*u == '=')
+ {
+ ++u;
+ while ( u[*l] && u[*l] != '|')
+ ++ *l;
+ }
+ return (u);
+ }
+ u=strchr(u, '|');
+ if (u) ++u;
+ }
+ return (0);
+}
+
+/* Extract field as an unsigned int */
+
+unsigned userdb_getu(const char *u, const char *n, unsigned defnum)
+{
+ int l;
+ const char *p;
+
+ if ((p=userdb_get(u, n, &l)) != 0)
+ {
+ defnum=0;
+ while (l && *p >= '0' && *p <= '9')
+ {
+ defnum = defnum * 10 + (*p++ - '0');
+ --l;
+ }
+ }
+ return (defnum);
+}
+
+/* Extract a field into a dynamically allocated buffer */
+
+char *userdb_gets(const char *u, const char *n)
+{
+ int l;
+ const char *p;
+ char *q;
+
+ if ((p=userdb_get(u, n, &l)) != 0)
+ {
+ q=malloc(l+1);
+ if (!q)
+ return (0);
+
+ if (l) memcpy(q, p, l);
+ q[l]=0;
+ return (q);
+ }
+ errno=ENOENT;
+ return (0);
+}
+
+/* Create a userdbs structure based upon a uid (reverse lookup) */
+
+struct userdbs *userdb_createsuid(uid_t u)
+{
+char buf[80];
+char *p=buf+sizeof(buf)-1, *q;
+struct userdbs *s;
+
+ /* Lookup uid= record */
+
+ *p=0;
+ *--p='=';
+ do
+ {
+ *--p= "0123456789"[u % 10];
+ u=u/10;
+ } while (u);
+ p=userdb(p);
+ if (!p) return (0);
+
+ /* Have account name, now look it up. */
+
+ q=userdb(p);
+ if (!q)
+ {
+ free(p);
+ return (0);
+ }
+ s=userdb_creates(q);
+ if (s)
+ s->udb_name=p;
+ else
+ free(p);
+ free(q);
+ return (s);
+}
+
+static struct userdbs *userdb_enum(char *key, size_t keylen,
+ char *val, size_t vallen)
+{
+ if (key)
+ {
+ char *valz=malloc(vallen+1);
+
+ if (valz)
+ {
+ struct userdbs *udbs;
+
+ memcpy(valz, val, vallen);
+ valz[vallen]=0;
+
+ udbs=userdb_creates(valz);
+
+ if (udbs)
+ {
+ if ((udbs->udb_name=malloc(keylen+1)) != NULL)
+ {
+ memcpy(udbs->udb_name, key, keylen);
+ udbs->udb_name[keylen]=0;
+ free(valz);
+ return udbs;
+ }
+ userdb_frees(udbs);
+ }
+ free(valz);
+ }
+ }
+ return NULL;
+}
+
+
+struct userdbs *userdb_enum_first()
+{
+ char *val;
+ size_t vallen;
+ size_t keylen;
+ char *key=dbobj_firstkey(&d, &keylen, &val, &vallen);
+
+ if (key)
+ {
+ struct userdbs *udbs=userdb_enum(key, keylen, val, vallen);
+
+ free(val);
+
+ if (udbs)
+ return udbs;
+
+ /* Could be a reverse UID entry */
+
+ return userdb_enum_next();
+ }
+ return NULL;
+}
+
+struct userdbs *userdb_enum_next()
+{
+ char *val;
+ size_t vallen;
+ size_t keylen;
+ char *key;
+
+ while ((key=dbobj_nextkey(&d, &keylen, &val, &vallen)) != NULL)
+ {
+ struct userdbs *udbs=userdb_enum(key, keylen, val, vallen);
+
+ free(val);
+
+ if (udbs)
+ return udbs;
+ }
+ return NULL;
+}
+
+/* Extracted a userdb.dat record, convert it to a userdbs structure */
+
+struct userdbs *userdb_creates(const char *u)
+{
+struct userdbs *udbs=(struct userdbs *)malloc(sizeof(struct userdbs));
+char *s;
+
+ if (!udbs) return (0);
+ memset((char *)udbs, 0, sizeof(*udbs));
+
+ if ((udbs->udb_dir=userdb_gets(u, "home")) == 0)
+ {
+ if (userdb_debug_level)
+ fprintf(stderr,
+ "DEBUG: userdb: required value 'home' is missing\n");
+ userdb_frees(udbs);
+ return (0);
+ }
+
+ if ((s=userdb_gets(u, "uid")) != 0)
+ {
+ udbs->udb_uid=atol(s);
+ free(s);
+ if ((s=userdb_gets(u, "gid")) != 0)
+ {
+ udbs->udb_gid=atol(s);
+ free(s);
+
+ if ((s=userdb_gets(u, "shell")) != 0)
+ udbs->udb_shell=s;
+ else if (errno != ENOENT)
+ {
+ userdb_frees(udbs);
+ return (0);
+ }
+
+ if ((s=userdb_gets(u, "mail")) != 0)
+ udbs->udb_mailbox=s;
+ else if (errno != ENOENT)
+ {
+ userdb_frees(udbs);
+ return (0);
+ }
+ if ((s=userdb_gets(u, "quota")) != 0)
+ udbs->udb_quota=s;
+ else if (errno != ENOENT)
+ {
+ userdb_frees(udbs);
+ return (0);
+ }
+ if ((s=userdb_gets(u, "gecos")) != 0)
+ udbs->udb_gecos=s;
+ else if (errno != ENOENT)
+ {
+ userdb_frees(udbs);
+ return (0);
+ }
+ if ((s=userdb_gets(u, "options")) != 0)
+ udbs->udb_options=s;
+ else if (errno != ENOENT)
+ {
+ userdb_frees(udbs);
+ return (0);
+ }
+ udbs->udb_source=userdb_gets(u, "_");
+ if (userdb_debug_level)
+ fprintf(stderr,
+ "DEBUG: userdb: home=%s, uid=%ld, gid=%ld, shell=%s, "
+ "mail=%s, quota=%s, gecos=%s, options=%s\n",
+ udbs->udb_dir ? udbs->udb_dir : "<unset>",
+ (long)udbs->udb_uid, (long)udbs->udb_gid,
+ udbs->udb_shell ? udbs->udb_shell : "<unset>",
+ udbs->udb_mailbox ? udbs->udb_mailbox : "<unset>",
+ udbs->udb_quota ? udbs->udb_quota : "<unset>",
+ udbs->udb_gecos ? udbs->udb_gecos : "<unset>",
+ udbs->udb_options ? udbs->udb_options : "<unset>");
+ return (udbs);
+ }
+ else
+ if (userdb_debug_level)
+ fprintf(stderr,
+ "DEBUG: userdb: required value 'gid' is missing\n");
+ }
+ else
+ if (userdb_debug_level)
+ fprintf(stderr,
+ "DEBUG: userdb: required value 'uid' is missing\n");
+ userdb_frees(udbs);
+ return (0);
+}
+
+void userdb_frees(struct userdbs *u)
+{
+ if (u->udb_options) free(u->udb_options);
+ if (u->udb_name) free(u->udb_name);
+ if (u->udb_gecos) free(u->udb_gecos);
+ if (u->udb_dir) free(u->udb_dir);
+ if (u->udb_shell) free(u->udb_shell);
+ if (u->udb_mailbox) free(u->udb_mailbox);
+ if (u->udb_quota) free(u->udb_quota);
+ if (u->udb_source) free(u->udb_source);
+ free(u);
+}
+
diff --git a/userdb/userdb.h b/userdb/userdb.h
new file mode 100644
index 0000000..3b2690a
--- /dev/null
+++ b/userdb/userdb.h
@@ -0,0 +1,68 @@
+#ifndef userdb_h
+#define userdb_h
+
+/*
+** Copyright 1998 - 2001 Double Precision, Inc.
+** See COPYING for distribution information.
+*/
+
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ Functions to access local/config/userdb.dat
+*/
+
+void userdb_set_debug(int);
+void userdb_init(const char *);
+void userdb_close();
+char *userdb(const char *); /* Fetch the record */
+char *userdbshadow(const char *,
+ const char *); /* Fetch the userdbshadow record */
+
+ /* Extract field from the record */
+const char *userdb_get(const char *, /* The record */
+ const char *, /* Field name */
+ int *); /* Content length returned */
+
+ /* Extract numerical field from record */
+
+unsigned userdb_getu(const char *, /* The record */
+ const char *, /* Field name */
+ unsigned); /* Returned if field not found */
+
+ /* Extract string into malloced buffer */
+char *userdb_gets(const char *, /* The record */
+ const char *); /* The field */
+
+struct userdbs {
+ char *udb_name; /* Account name, ONLY set by userdb_createsuid */
+ char *udb_gecos; /* GECOS */
+ char *udb_dir; /* Home directory */
+ char *udb_shell; /* Shell */
+ char *udb_mailbox; /* Default mailbox */
+ char *udb_quota; /* Maildir quota */
+ char *udb_options; /* Options, see INSTALL */
+ uid_t udb_uid;
+ gid_t udb_gid;
+
+ char *udb_source; /* Non-blank - source file in userdb dir */
+ } ;
+
+struct userdbs *userdb_creates(const char *);
+struct userdbs *userdb_createsuid(uid_t);
+struct userdbs *userdb_enum_first();
+struct userdbs *userdb_enum_next();
+
+void userdb_frees(struct userdbs *);
+char *userdb_mkmd5pw(const char *);
+
+#ifdef __cplusplus
+} ;
+#endif
+
+#endif
diff --git a/userdb/userdb.pl.in b/userdb/userdb.pl.in
new file mode 100644
index 0000000..db16bcc
--- /dev/null
+++ b/userdb/userdb.pl.in
@@ -0,0 +1,263 @@
+#! @PERL@
+#
+# Copyright 1998 - 1999 Double Precision, Inc. See COPYING for
+# distribution information.
+
+use Fcntl ':flock';
+
+$prefix="@prefix@";
+$exec_prefix="@exec_prefix@";
+$userdb="@userdb@";
+
+eval {
+ die "SYMLINK\n" if -l $userdb;
+};
+
+die "ERROR: Wrong userdb command.\n ($userdb is a symbolic link)\n"
+ if $@ eq "SYMLINK\n";
+
+sub usage {
+ print "Usage: $0 [path/.../ | -f file ]name set field=value field=value...\n";
+ print " $0 [path/.../ | -f file ]name unset field field...\n";
+ print " $0 [path/.../ | -f file ]name del\n";
+ print " $0 -show [path/... | -f file ] [name]\n";
+ exit 1;
+}
+
+$name=shift @ARGV;
+$doshow=0;
+
+if ($name eq "-show")
+{
+ $doshow=1;
+ $name=shift @ARGV;
+}
+
+if ($name eq "-f")
+{
+ $userdb=shift @ARGV;
+ $name=shift @ARGV;
+}
+elsif ( $name =~ /^(.*)\/([^\/]*)$/ )
+{
+ $userdb="$userdb/$1";
+ $name=$2;
+}
+
+
+if ($doshow)
+{
+ &usage unless $userdb =~ /./;
+}
+else
+{
+ $verb=shift @ARGV;
+
+ &usage unless $verb =~ /./ && $name =~ /./ && $userdb =~ /./;
+}
+
+while (defined ($link= &safe_readlink($userdb)))
+{
+ $userdb .= "/";
+ $userdb = "" if $link =~ /^\//;
+ $userdb .= $link;
+}
+
+$tmpuserdb= $userdb =~ /^(.*)\/([^\/]*)$/ ? "$1/.tmp.$2":".tmp.$userdb";
+$lockuserdb= $userdb =~ /^(.*)\/([^\/]*)$/ ? "$1/.lock.$2":".lock.$userdb";
+
+if ( $doshow && ! defined $name)
+{
+}
+else
+{
+ die "Invalid name: $name\n"
+ unless $name =~ /^[\@a-zA-Z0-9\.\-\_\:\+]+$/;
+}
+
+grep( (/[\|\n]/ && die "Invalid field or value.\n"), @ARGV);
+
+umask(066);
+
+open(LOCK, ">$lockuserdb") or die "Can't open $lockuserdb: $!";
+flock(LOCK,LOCK_EX) || die "Can't lock $lockuserdb: $!";
+
+if ( $doshow )
+{
+ if (open (OLDFILE, $userdb))
+ {
+ stat(OLDFILE);
+ die "$userdb: not a file.\n" unless -f _;
+
+ while ( defined($_=<OLDFILE>) )
+ {
+ chop if /\n$/;
+ next if /^#/;
+ next unless /^([^\t]+)(\t(.*))?$/;
+ ($addr,$vals)=($1,$3);
+ if (defined $name)
+ {
+ if ($name eq $addr)
+ {
+ $vals =~ s/\|/\n/g;
+ print "$vals\n";
+ last;
+ }
+ }
+ else
+ {
+ print "$addr\n";
+ }
+ }
+ }
+ close (OLDFILE);
+}
+elsif ( $verb eq "set" )
+{
+ $isatty=1;
+
+ eval {
+ $isatty=0 unless -t STDIN;
+ } ;
+
+ &doadd;
+ $mode= (stat $userdb)[2];
+ chmod ($mode & 0777,$tmpuserdb ) if defined $mode;
+ rename $tmpuserdb,$userdb;
+}
+elsif ( $verb eq "unset" )
+{
+ if ($#ARGV >= 0 && &dodel)
+ {
+ $mode= (stat $userdb)[2];
+ chmod ($mode & 0777 ,$tmpuserdb) if defined $mode;
+ rename ($tmpuserdb,$userdb)
+ }
+}
+elsif ( $verb eq "del" )
+{
+ &usage unless $#ARGV < 0;
+ if (&dodel)
+ {
+ $mode= (stat $userdb)[2];
+ chmod ($mode & 0777 ,$tmpuserdb) if defined $mode;
+ rename ($tmpuserdb,$userdb)
+ }
+}
+else
+{
+ &usage;
+}
+exit 0;
+
+sub doadd {
+my (%FIELDS);
+my ($key, $in);
+
+ foreach $key (@ARGV)
+ {
+ next if $key =~ /=/;
+ print "$name.$key: " if $isatty;
+ exit 1 unless defined ($in=<STDIN>);
+ chop $in if $in =~ /\n$/;
+ die "Invalid value.\n" if $in =~ /[\|\n]/;
+ $key = "$key=$in";
+ }
+
+ open (NEWFILE, ">$tmpuserdb") || die "$!\n";
+ if (open (OLDFILE, $userdb))
+ {
+ stat(OLDFILE);
+ die "$userdb: not a file.\n" unless -f _;
+ while ( defined($_=<OLDFILE>) )
+ {
+ chop if /\n$/;
+ if ( /^([^\t]+)(\t(.*))?$/ && ($1 eq $name))
+ {
+ grep( (/^([^=]*)(=.*)?$/,
+ $FIELDS{$1}="$2"), split(/\|/, $3));
+ while ( defined ($key=shift @ARGV))
+ {
+ $key =~ /^([^=]*)(=.*)?$/;
+ $FIELDS{$1}="$2";
+ }
+ $name="$name\t";
+ grep ( $name="$name$_$FIELDS{$_}|",
+ keys %FIELDS);
+ chop $name;
+ print NEWFILE "$name\n" || die "$!\n";
+ while (<OLDFILE>)
+ {
+ print NEWFILE || die "$!\n";
+ }
+ close (OLDFILE);
+ close (NEWFILE) || die "$!\n";
+ return;
+ }
+ print NEWFILE "$_\n" || die "$!\n";
+ }
+ close (OLDFILE);
+ }
+
+ $name="$name\t";
+ grep ( $name="$name$_|", @ARGV );
+ chop $name;
+ print NEWFILE "$name\n" || die "$!\n";
+ close (NEWFILE) || die "$!\n";
+}
+
+sub dodel {
+my (%FIELDS);
+
+ open (NEWFILE, ">$tmpuserdb") || die "$!\n";
+ if (open (OLDFILE, $userdb))
+ {
+ stat(OLDFILE);
+ die "$userdb: not a file.\n" unless -f _;
+ while ( defined($_=<OLDFILE>) )
+ {
+ chop if /\n$/;
+ if ( /^([^\t]+)(\t(.*))?$/ && ($1 eq $name))
+ {
+ if ($#ARGV >= 0)
+ {
+ grep( (/^([^=]*)(=.*)?$/,
+ $FIELDS{$1}=$2),
+ split(/\|/, $3));
+ grep( delete $FIELDS{$_}, @ARGV);
+
+ $name="$name\t";
+ grep ( $name="$name$_$FIELDS{$_}|",
+ keys %FIELDS);
+ chop $name;
+ $name="$name\n";
+ print NEWFILE "$name" || die "$!\n";
+ }
+ while (<OLDFILE>)
+ {
+ print NEWFILE || die "$!\n";
+ }
+ close (OLDFILE);
+ close (NEWFILE) || die "$!\n";
+ return (1);
+ }
+ print NEWFILE "$_\n" || die "$!\n";
+ }
+ close (OLDFILE);
+ }
+ unlink "$tmpuserdb";
+ return (0);
+}
+
+sub safe_readlink {
+my ($l)=@_;
+my ($err,$link);
+
+ eval {
+
+ $link=readlink($l);
+ } ;
+
+ $link=undef if $@;
+ return $link;
+}
diff --git a/userdb/userdb.sgml b/userdb/userdb.sgml
new file mode 100644
index 0000000..6bb6a13
--- /dev/null
+++ b/userdb/userdb.sgml
@@ -0,0 +1,265 @@
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+ "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+
+ <!-- Copyright 1998 - 2007 Double Precision, Inc. See COPYING for -->
+ <!-- distribution information. -->
+
+<refentry id="userdb">
+
+ <refmeta>
+ <refentrytitle>userdb</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo>Double Precision, Inc.</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>userdb</refname>
+ <refpurpose>manipulate @userdb@</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>userdb</command>
+ <arg choice='req'><replaceable>addr</replaceable></arg>
+ <arg choice='plain'>set</arg>
+ <arg choice='req' rep='repeat'><replaceable>field</replaceable>=<replaceable>value</replaceable></arg>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>userdb</command>
+ <arg choice='req'><replaceable>addr</replaceable></arg>
+ <arg choice='plain'>unset</arg>
+ <arg choice='req' rep='repeat'><replaceable>field</replaceable></arg>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>userdb</command>
+ <arg choice='req'><replaceable>addr</replaceable></arg>
+ <arg choice='plain'>del</arg>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>userdb</command>
+ <arg choice='req'><replaceable>path/addr</replaceable></arg>
+ <group>
+ <arg choice='plain'>set</arg>
+ <arg choice='plain'>unset</arg>
+ <arg choice='plain'>del</arg>
+ </group>
+ <arg choice='plain'>...</arg>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>userdb</command>
+ <arg choice='plain'>-f</arg>
+ <arg choice='req'><replaceable>file</replaceable></arg>
+ <arg choice='req'><replaceable>adr</replaceable></arg>
+ <group>
+ <arg choice='plain'>set</arg>
+ <arg choice='plain'>unset</arg>
+ <arg choice='plain'>del</arg>
+ </group>
+ <arg choice='plain'>...</arg>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>userdb</command>
+ <arg choice='plain'>-show</arg>
+ <arg choice='req'><replaceable>path</replaceable></arg>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>userdb</command>
+ <arg choice='plain'>-show</arg>
+ <arg choice='req'><replaceable>path</replaceable></arg>
+ <arg choice='req'><replaceable>addr</replaceable></arg>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>userdb</command>
+ <arg choice='plain'>-show</arg>
+ <arg choice='plain'>-f</arg>
+ <arg choice='req'><replaceable>file</replaceable></arg>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>userdb</command>
+ <arg choice='plain'>-show</arg>
+ <arg choice='plain'>-f</arg>
+ <arg choice='req'><replaceable>file</replaceable></arg>
+ <arg choice='req'><replaceable>addr</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>
+<command>userdb</command> is a convenient script to individually manipulate
+entries in <filename>@userdb@</filename>. See
+<ulink url="makeuserdb.html"><citerefentry><refentrytitle>makeuserdb</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink>
+for a description of its contents. <filename>@userdb@</filename> can always
+be edited using any text editor, but <command>userdb</command> is a
+convenient way to modify this file from another script.</para>
+
+ <para>
+<filename>@userdb@</filename> can also be a subdirectory, instead of a file.
+Specify <option><replaceable>foo/bar/addr</replaceable></option> to manipulate
+<option><replaceable>addr</replaceable></option> in the file
+<filename>@userdb@<replaceable>/foo/bar</replaceable></filename>. You can
+also use the
+<option>-f</option> flag: <option>-f
+<replaceable>@userdb@/foo/bar</replaceable></option> is equivalent. Use
+whatever form makes the most sense to you.</para>
+
+ <para>
+<filename>@userdb@</filename> must not have any group or world
+permissions. That's
+because its contents may include system passwords (depending upon the
+application which uses this virtual user account database).</para>
+
+ <para>
+Each line in <filename>@userdb@</filename> takes following form:</para>
+
+<blockquote>
+<computeroutput>
+<replaceable>addr</replaceable><token>&lt;TAB&gt;</token><replaceable>field</replaceable>=<replaceable>value</replaceable>|<replaceable>field</replaceable>=<replaceable>value</replaceable>...
+</computeroutput>
+ </blockquote>
+
+ <para>
+<replaceable>addr</replaceable> specifies a unique virtual address. It
+is followed by a single
+tab character, then a list of
+<replaceable>field</replaceable>=<replaceable>value</replaceable> pairs,
+separated by
+vertical slash characters. See
+<ulink url="makeuserdb.html"><citerefentry><refentrytitle>makeuserdb</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink>
+for field definitions.</para>
+
+ <para>
+A text editor can be used to add blank lines or comments in
+<filename>@userdb@</filename>. Any blank lines or comments are ignored by the
+<command>userdb</command> script.</para>
+
+ <para>
+The names of the actual fields, and their contents, are defined entirely by
+applications that use the <filename>@userdb@</filename> database, the
+<command>userdb</command> command just adds or removes arbitrary fields.</para>
+
+
+ <para>
+For example:</para>
+<blockquote>
+ <informalexample>
+ <literallayout><command>userdb default/info set mail=/home/mail/info</command></literallayout>
+ </informalexample>
+ </blockquote>
+
+ <para>
+This command accesses the address "info" in
+<filename>@userdb@/default</filename>.</para>
+
+ <para>
+If the second argument to <command>userdb</command> is
+"<parameter>set</parameter>", the
+remaining arguments are taken as
+<parameter><replaceable>field</replaceable>=<replaceable>value</replaceable></parameter> pairs, which are
+added to the record for <replaceable>addr</replaceable>. If there is no
+record for <replaceable>addr</replaceable>, a
+new record will be appended to the file. If
+<replaceable>addr</replaceable> exists, any existing
+values of any specified fields are removed. If
+<parameter>=<replaceable>value</replaceable></parameter> is missing,
+<command>userdb</command> stops and prompts for it. This is useful if
+you're setting
+a password field, where you do not want to specify the password on the command
+line, which can be seen by the
+<citerefentry><refentrytitle>ps</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+command. If <command>userdb</command> is being
+executed by a script, the value can be provided on standard input.</para>
+
+ <para>Use "<parameter>unset</parameter>" to delete fields from an existing
+record. Use
+"<parameter>del</parameter>" to delete all fields in the existing record,
+plus the record itself.</para>
+
+
+ <refsect2><title>DISPLAYING <filename>@userdb@</filename></title>
+
+ <para>
+If the first argument to userdb
+is <parameter>-show</parameter>, <command>userdb</command>
+displays the contents of <filename>@userdb@</filename>. If
+<filename>@userdb@</filename> is a
+subdirectory,
+<parameter><replaceable>path</replaceable></parameter> must refer to a
+specific file in <filename>@userdb@</filename>. The
+<parameter>-f</parameter> option can be used instead of
+<parameter><replaceable>path</replaceable></parameter> in order to specify an
+arbitrary file.</para>
+
+ <para>
+If
+<parameter><replaceable>addr</replaceable></parameter> is not specified,
+<command>userdb</command> produces a list, on standard
+output, containing all addresses found in the file, on per line. If
+<parameter><replaceable>addr</replaceable></parameter> is specified,
+<command>userdb</command> produces a list, on standard output, of
+all the fields in <filename>@userdb@</filename> for this
+<parameter><replaceable>addr</replaceable></parameter>.</para>
+
+ </refsect2>
+
+ <refsect2>
+ <title>REBUILDING <filename>@userdb@.dat</filename></title>
+
+ <para>
+The actual virtual account/address database is
+<filename>@userdb@.dat</filename>.
+This is a binary database file. <command>@userdb@</command> is the plain text
+version. After running <command>userdb</command>, execute the
+<ulink url="makeuserdb.html"><citerefentry><refentrytitle>makeuserdb</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink> command to rebuild
+<filename>@userdb@.dat</filename> for the changes to take effect.</para>
+ </refsect2>
+
+ </refsect1>
+
+ <refsect1>
+ <title>BUGS</title>
+
+ <para>
+<parameter><replaceable>addr</replaceable></parameter> must be unique.
+If <filename>@userdb@</filename> is a subdirectory,
+it's possible to create the same
+<parameter><replaceable>addr</replaceable></parameter>
+in different files in the subdirectory.
+This is an error that is not currently detected by <command>userdb</command>,
+however the subsequent
+<ulink url="makeuserdb.html"><citerefentry><refentrytitle>makeuserdb</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink> command
+will fail with an error message.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>FILES</title>
+
+ <para>
+<filename> @userdb@</filename> - plain text file, or directory of plain text files</para>
+ <para>
+<filename> .lock.filename</filename> - lock file for <filename>filename</filename></para>
+ <para>
+<filename> .tmp.filename</filename> - temporary file used to create new contents of <filename>filename</filename></para>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>
+<ulink url="makeuserdb.html"><citerefentry><refentrytitle>makeuserdb</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink>,
+
+<ulink url="userdbpw.html"><citerefentry><refentrytitle>userdbpw</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink></para>
+
+ </refsect1>
+
+</refentry>
+
diff --git a/userdb/userdb2.c b/userdb/userdb2.c
new file mode 100644
index 0000000..fd904c1
--- /dev/null
+++ b/userdb/userdb2.c
@@ -0,0 +1,57 @@
+/*
+** Copyright 1998 - 2007 Double Precision, Inc.
+** See COPYING for distribution information.
+*/
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "dbobj.h"
+#include "userdb.h"
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+
+extern int userdb_debug_level;
+
+char *userdbshadow(const char *sh, const char *u)
+{
+struct dbobj d;
+char *p,*q;
+size_t l;
+
+ dbobj_init(&d);
+
+ if (dbobj_open(&d, sh, "R"))
+ {
+ if (userdb_debug_level)
+ fprintf(stderr,
+ "DEBUG: userdbshadow: unable to open %s\n", sh);
+ return (0);
+ }
+
+ q=dbobj_fetch(&d, u, strlen(u), &l, "");
+ dbobj_close(&d);
+ if (!q)
+ {
+ if (userdb_debug_level)
+ fprintf(stderr,
+ "DEBUG: userdbshadow: entry not found\n");
+ errno=ENOENT;
+ return(0);
+ }
+
+ p=malloc(l+1);
+ if (!p)
+ {
+ free(q);
+ return (0);
+ }
+
+ if (l) memcpy(p, q, l);
+ free(q);
+ p[l]=0;
+ return (p);
+}
diff --git a/userdb/userdbmkpw.c b/userdb/userdbmkpw.c
new file mode 100644
index 0000000..3786a83
--- /dev/null
+++ b/userdb/userdbmkpw.c
@@ -0,0 +1,119 @@
+/*
+** Copyright 2001 Double Precision, Inc.
+** See COPYING for distribution information.
+*/
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <sys/types.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if TIME_WITH_SYS_TIME
+#include <sys/time.h>
+#include <time.h>
+#else
+#if HAVE_SYS_TIME_H
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#endif
+#if HAVE_MD5
+#include "md5/md5.h"
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#if HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+#if HAVE_CRYPT_H
+#include <crypt.h>
+#endif
+
+#if HAVE_CRYPT
+#if NEED_CRYPT_PROTOTYPE
+extern char *crypt(const char *, const char *);
+#endif
+#endif
+
+char userdb_hex64[]="./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+#ifdef RANDOM
+void userdb_get_random(char *buf, unsigned n)
+{
+int f=open(RANDOM, O_RDONLY);
+int l;
+
+ if (f < 0)
+ {
+ perror(RANDOM);
+ exit(1);
+ }
+ while (n)
+ {
+ l=read(f, buf, n);
+ if (l < 0)
+ {
+ perror("read");
+ exit(1);
+ }
+ n -= l;
+ buf += l;
+ }
+ close(f);
+}
+#endif
+
+#if HAVE_MD5
+char *userdb_mkmd5pw(const char *buf)
+{
+ int i;
+ char salt[9];
+
+ salt[8]=0;
+#ifdef RANDOM
+ userdb_get_random(salt, 8);
+ for (i=0; i<8; i++)
+ salt[i] = userdb_hex64[salt[i] & 63 ];
+
+#else
+ {
+
+ struct {
+#if HAVE_GETTIMEOFDAY
+ struct timeval tv;
+#else
+ time_t tv;
+#endif
+ pid_t p;
+ } s;
+
+ MD5_DIGEST d;
+#if HAVE_GETTIMEOFDAY
+ struct timezone tz;
+
+ gettimeofday(&s.tv, &tz);
+#else
+ time(&s.tv);
+#endif
+ s.p=getpid();
+
+ md5_digest(&s, sizeof(s), d);
+ for (i=0; i<8; i++)
+ salt[i]=userdb_hex64[ ((unsigned char *)d)[i] ];
+ }
+#endif
+ return (md5_crypt(buf, salt));
+}
+#endif
diff --git a/userdb/userdbpw.c b/userdb/userdbpw.c
new file mode 100644
index 0000000..23620df
--- /dev/null
+++ b/userdb/userdbpw.c
@@ -0,0 +1,251 @@
+/*
+** Copyright 1998 - 2006 Double Precision, Inc.
+** See COPYING for distribution information.
+*/
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <sys/types.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if TIME_WITH_SYS_TIME
+#include <sys/time.h>
+#include <time.h>
+#else
+#if HAVE_SYS_TIME_H
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#endif
+#if HAVE_MD5
+#include "md5/md5.h"
+#endif
+#if HAVE_HMAC
+#include "libhmac/hmac.h"
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#if HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+#if HAVE_CRYPT_H
+#include <crypt.h>
+#endif
+
+#if HAVE_CRYPT
+#if NEED_CRYPT_PROTOTYPE
+extern char *crypt(const char *, const char *);
+#endif
+#endif
+
+extern char userdb_hex64[];
+
+#ifdef RANDOM
+extern void userdb_get_random(char *buf, unsigned n);
+#endif
+
+#if HAVE_MD5
+
+char *userdb_mkmd5pw(const char *);
+
+#endif
+
+/*
+** Where possible, we turn off echo when entering the password.
+** We set up a signal handler to catch signals and restore the echo
+** prior to exiting.
+*/
+
+#if HAVE_TERMIOS_H
+static struct termios tios;
+static int have_tios;
+
+static RETSIGTYPE sighandler(int signum)
+{
+ if (write(1, "\n", 1) < 0)
+ ; /* ignore gcc warning */
+ tcsetattr(0, TCSANOW, &tios);
+ _exit(0);
+#if RETSIGTYPE != void
+ return (0);
+#endif
+}
+#endif
+
+static void read_pw(char *buf)
+{
+int n, c;
+
+ n=0;
+ while ((c=getchar()) != EOF && c != '\n')
+ if (n < BUFSIZ-1)
+ buf[n++]=c;
+ if (c == EOF && n == 0) exit(1);
+ buf[n]=0;
+}
+
+int main(int argc, char **argv)
+{
+int n=1;
+int md5=0;
+char buf[BUFSIZ];
+char salt[9];
+#if HAVE_HMAC
+struct hmac_hashinfo *hmac=0;
+#endif
+
+ while (n < argc)
+ {
+ if (strcmp(argv[n], "-md5") == 0)
+ {
+ md5=1;
+ ++n;
+ continue;
+ }
+#if HAVE_HMAC
+ if (strncmp(argv[n], "-hmac-", 6) == 0)
+ {
+ int i;
+
+ for (i=0; hmac_list[i] &&
+ strcmp(hmac_list[i]->hh_name, argv[n]+6); i++)
+ ;
+ if (hmac_list[i])
+ {
+ hmac=hmac_list[i];
+ ++n;
+ continue;
+ }
+ }
+#endif
+ fprintf(stderr, "%s: invalid argument.\n", argv[0]);
+ exit(1);
+ }
+
+ /* Read the password */
+#if HAVE_TERMIOS_H
+
+ have_tios=0;
+ if (tcgetattr(0, &tios) == 0)
+ {
+ struct termios tios2;
+ char buf2[BUFSIZ];
+
+ have_tios=1;
+ signal(SIGINT, sighandler);
+ signal(SIGHUP, sighandler);
+ tios2=tios;
+ tios2.c_lflag &= ~ECHO;
+ tcsetattr(0, TCSANOW, &tios2);
+
+ for (;;)
+ {
+ if (write(2, "Password: ", 10) < 0)
+ ; /* ignore gcc warning */
+ read_pw(buf);
+ if (write(2, "\nReenter password: ", 19) < 0)
+ ; /* ignore gcc warning */
+ read_pw(buf2);
+ if (strcmp(buf, buf2) == 0) break;
+ if (write(2, "\nPasswords don't match.\n\n", 25) < 0)
+ ; /* ignore gcc warning */
+ }
+
+ }
+ else
+#endif
+ read_pw(buf);
+
+#if HAVE_TERMIOS_H
+ if (have_tios)
+ {
+ if (write(2, "\n", 1) < 0)
+ ; /* ignore gcc warning */
+
+ tcsetattr(0, TCSANOW, &tios);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ }
+#endif
+
+ /* Set the password */
+
+#if HAVE_HMAC
+ if (hmac)
+ {
+ unsigned char *p=malloc(hmac->hh_L*2);
+ unsigned i;
+
+ if (!p)
+ {
+ perror("malloc");
+ exit(1);
+ }
+
+ hmac_hashkey(hmac, buf, strlen(buf), p, p+hmac->hh_L);
+ for (i=0; i<hmac->hh_L*2; i++)
+ printf("%02x", (int)p[i]);
+ printf("\n");
+ exit(0);
+ }
+#endif
+
+#if HAVE_CRYPT
+
+#else
+ md5=1;
+#endif
+
+#if HAVE_MD5
+ if (md5)
+ {
+
+ printf("%s\n", userdb_mkmd5pw(buf));
+ exit(0);
+ }
+#endif
+#ifdef RANDOM
+ userdb_get_random(salt, 2);
+ salt[0]=userdb_hex64[salt[0] & 63];
+ salt[1]=userdb_hex64[salt[0] & 63];
+#else
+ {
+ time_t t;
+ int i;
+
+ time(&t);
+ t ^= getpid();
+ salt[0]=0;
+ salt[1]=0;
+ for (i=0; i<6; i++)
+ {
+ salt[0] <<= 1;
+ salt[1] <<= 1;
+ salt[0] |= (t & 1);
+ t >>= 1;
+ salt[1] |= (t & 1);
+ t >>= 1;
+ }
+ salt[0]=userdb_hex64[(unsigned)salt[0]];
+ salt[1]=userdb_hex64[(unsigned)salt[1]];
+ }
+#endif
+
+#if HAVE_CRYPT
+ printf("%s\n", crypt(buf, salt));
+ fflush(stdout);
+#endif
+ return (0);
+}
diff --git a/userdb/userdbpw.sgml b/userdb/userdbpw.sgml
new file mode 100644
index 0000000..246c9cc
--- /dev/null
+++ b/userdb/userdbpw.sgml
@@ -0,0 +1,129 @@
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+ "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+
+ <!-- Copyright 1998 - 2007 Double Precision, Inc. See COPYING for -->
+ <!-- distribution information. -->
+
+<refentry id="userdbpw">
+
+ <refmeta>
+ <refentrytitle>userdbpw</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo>Double Precision, Inc.</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>userdbpw</refname>
+ <refpurpose>create an encrypted password</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>userdbpw</command>
+ <group>
+ <arg choice='opt'>-md5</arg>
+ <arg choice='opt'>-hmac-md5</arg>
+ <arg choice='opt'>-hmac-sha1</arg>
+ </group>
+ <arg choice='plain'>|</arg>
+ <command>userdb</command>
+ <arg choice='req'><replaceable>name</replaceable></arg>
+ <arg choice='plain'>set</arg>
+ <arg choice='req'><replaceable>field</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para><command>userdbpw</command> enables secure entry of encrypted
+passwords into <filename>@userdb@</filename>.</para>
+
+ <para><command>userdbpw</command> reads a single line of text on
+standard input, encrypts it, and prints the encrypted result to standard
+output.</para>
+
+ <para>If standard input is attached to a terminal device,
+<command>userdbpw</command> explicitly issues a "Password: " prompt on
+standard error, and turns off echo while the password is entered.</para>
+
+ <para>The <option>-md5</option> option is available on systems that use
+MD5-hashed passwords (such as systems that use the current version of the
+PAM library for authenticating, with MD5 passwords enabled).
+This option creates an MD5 password hash, instead of using the
+traditional <function>crypt()</function> function.</para>
+
+ <para><option>-hmac-md5</option> and <option>-hmac-sha1</option> options
+are available only if the userdb library is installed by an application
+that uses a challenge/response authentication mechanism.
+<option>-hmac-md5</option> creates an intermediate HMAC context using the
+MD5 hash function. <option>-hmac-sha1</option> uses the SHA1 hash function
+instead. Whether either HMAC function is actually available depends on the
+actual application that installs the <option>userdb</option> library.</para>
+
+ <para>Note that even though the result of HMAC hashing looks like an encrypted
+password, it's really not. HMAC-based challenge/response authentication
+mechanisms require the cleartext password to be available as cleartext.
+Computing an intermediate HMAC context does scramble the cleartext password,
+however if its compromised, it WILL be possible for an attacker to succesfully
+authenticate. Therefore, applications that use challenge/response
+authentication will store intermediate HMAC contexts in the "pw" fields in the
+userdb database, which will be compiled into the
+<filename>userdbshadow.dat</filename>
+database, which has group and world permissions turned off. The
+userdb library also requires that the cleartext userdb source for the
+<filename>userdb.dat</filename> and
+<filename>userdbshadow.dat</filename> databases is also stored with the
+group and world permissions turned off.</para>
+
+ <para><command>userdbpw</command> is usually used together in a pipe with
+<command>userdb</command>, which reads from standard input. For example:</para>
+
+ <blockquote>
+ <informalexample>
+ <programlisting><command>userdbpw -md5 | userdb users/john set systempw</command></programlisting>
+ </informalexample>
+ </blockquote>
+
+ <para>or:</para>
+
+<blockquote>
+ <informalexample>
+ <programlisting><command>userdbpw -hmac-md5 | userdb users/john set hmac-md5pw</command></programlisting>
+ </informalexample>
+ </blockquote>
+
+ <para>These commands set the <option>systempw</option> field in the record for
+the user <option>john</option> in <filename>@userdb@/users</filename> file, and the
+<option>hmac-md5pw</option> field. Don't forget to run
+<command>makeuserdb</command> for the change to take effect.</para>
+
+ <para>The following command does the same thing:</para>
+
+ <blockquote>
+ <informalexample>
+ <programlisting><command>userdb users/john set systempw=<option>SECRETPASSWORD</option></command></programlisting>
+ </informalexample>
+ </blockquote>
+
+ <para>However, this command passes the secret password as an argument to the
+<command>userdb</command> command, which can be viewed by anyone who happens
+to run
+<citerefentry><refentrytitle>ps</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+at the same time. Using <command>userdbpw</command> allows the secret password
+to be specified in a way that cannot be easily viewed by
+<citerefentry><refentrytitle>ps</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>
+<ulink url="userdb.html"><citerefentry><refentrytitle>userdb</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink>,
+
+<ulink url="makeuserdb.html"><citerefentry><refentrytitle>makeuserdb</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink></para>
+
+ </refsect1>
+
+</refentry>
+