--- server/wallet-backend	2016-01-17 19:13:02.000000000 -0800
+++ /dev/null	2016-01-23 14:00:27.000000000 -0800
@@ -1,695 +0,0 @@
-#!/usr/bin/perl
-#
-# Wallet server for storing and retrieving secure data.
-
-use 5.008;
-use strict;
-use warnings;
-
-use Getopt::Long qw(GetOptions);
-use Sys::Syslog qw(openlog syslog);
-use Wallet::Server;
-
-# Set to zero to suppress syslog logging, which is used for testing and for
-# the -q option.  Set to a reference to a string to append messages to that
-# string instead.
-our $SYSLOG;
-$SYSLOG = 1 unless defined $SYSLOG;
-
-##############################################################################
-# Logging
-##############################################################################
-
-# Initialize logging.
-sub log_init {
-    if (ref $SYSLOG) {
-        $$SYSLOG = '';
-    } elsif ($SYSLOG) {
-        openlog ('wallet-backend', 'pid', 'auth');
-    }
-}
-
-# Get an identity string for the user suitable for including in log messages.
-sub identity {
-    my $identity = '';
-    if ($ENV{REMOTE_USER}) {
-        $identity = $ENV{REMOTE_USER};
-        my $host = $ENV{REMOTE_HOST} || $ENV{REMOTE_ADDR};
-        $identity .= " ($host)" if $host;
-    }
-    return $identity;
-}
-
-# Log an error message to both syslog and to stderr and exit with a non-zero
-# status.
-sub error {
-    my $message = join ('', @_);
-    if ($SYSLOG) {
-        my $identity = identity;
-        my $log;
-        if ($identity) {
-            $log = "error for $identity: $message";
-        } else {
-            $log = "error: $message";
-        }
-        $log =~ s/[^\x20-\x7e]/_/g;
-        if (ref $SYSLOG) {
-            $$SYSLOG .= "$log\n";
-        } else {
-            syslog ('err', "%s", $log);
-        }
-    }
-    die "$message\n";
-}
-
-# Log a wallet failure message for a given command to both syslog and to
-# stderr and exit with a non-zero status.  Takes the message and the command
-# that was being run.
-sub failure {
-    my ($message, @command) = @_;
-    if ($SYSLOG) {
-        my $log = "command @command from " . identity . " failed: $message";
-        $log =~ s/[^\x20-\x7e]/_/g;
-        if (ref $SYSLOG) {
-            $$SYSLOG .= "$log\n";
-        } else {
-            syslog ('err', "%s", $log);
-        }
-    }
-    die "$message\n";
-}
-
-# Log a wallet success message for a given command.
-sub success {
-    my (@command) = @_;
-    if ($SYSLOG) {
-        my $log = "command @command from " . identity . " succeeded";
-        $log =~ s/[^\x20-\x7e]/_/g;
-        if (ref $SYSLOG) {
-            $$SYSLOG .= "$log\n";
-        } else {
-            syslog ('info', "%s", $log);
-        }
-    }
-}
-
-##############################################################################
-# Parameter checking
-##############################################################################
-
-# Check all arguments against a very restricted set of allowed characters and
-# to ensure the right number of arguments are taken.  The arguments are the
-# number of arguments expected (minimum and maximum), a reference to an array
-# of which argument numbers shouldn't be checked, and then the arguments.
-#
-# This function is probably temporary and will be replaced with something that
-# knows more about the syntax of each command and can check more things.
-sub check_args {
-    my ($min, $max, $exclude, @args) = @_;
-    if (@args < $min) {
-        error "insufficient arguments";
-    } elsif (@args > $max and $max != -1) {
-        error "too many arguments";
-    }
-    my %exclude = map { $_ => 1 } @$exclude;
-    for (my $i = 1; $i <= @args; $i++) {
-        next if $exclude{$i};
-        unless ($args[$i - 1] =~ m,^[\w_/\@.-]*\z,) {
-            error "invalid characters in argument: $args[$i - 1]";
-        }
-    }
-}
-
-##############################################################################
-# Implementation
-##############################################################################
-
-# Parse and execute a command.  We wrap this in a subroutine call for easier
-# testing.
-sub command {
-    log_init;
-    my $user = $ENV{REMOTE_USER} or error "REMOTE_USER not set";
-    my $host = $ENV{REMOTE_HOST} || $ENV{REMOTE_ADDR}
-        or error "neither REMOTE_HOST nor REMOTE_ADDR set";
-
-    # Instantiate the server object.
-    my $server = Wallet::Server->new ($user, $host);
-
-    # Parse command-line options and dispatch to the appropriate calls.
-    my ($command, @args) = @_;
-    if ($command eq 'acl') {
-        my $action = shift @args;
-        if ($action eq 'add') {
-            check_args (3, 3, [3], @args);
-            $server->acl_add (@args) or failure ($server->error, @_);
-        } elsif ($action eq 'check') {
-            check_args (1, 1, [], @args);
-            my $status = $server->acl_check (@args);
-            if (!defined ($status)) {
-                failure ($server->error, @_);
-            } else {
-                print $status ? "yes\n" : "no\n";
-            }
-        } elsif ($action eq 'create') {
-            check_args (1, 1, [], @args);
-            $server->acl_create (@args) or failure ($server->error, @_);
-        } elsif ($action eq 'destroy') {
-            check_args (1, 1, [], @args);
-            $server->acl_destroy (@args) or failure ($server->error, @_);
-        } elsif ($action eq 'history') {
-            check_args (1, 1, [], @args);
-            my $output = $server->acl_history (@args);
-            if (defined $output) {
-                print $output;
-            } else {
-                failure ($server->error, @_);
-            }
-        } elsif ($action eq 'remove') {
-            check_args (3, 3, [3], @args);
-            $server->acl_remove (@args) or failure ($server->error, @_);
-        } elsif ($action eq 'rename') {
-            check_args (2, 2, [], @args);
-            $server->acl_rename (@args) or failure ($server->error, @_);
-        } elsif ($action eq 'replace') {
-            check_args (2, 2, [], @args);
-            $server->acl_replace (@args) or failure ($server->error, @_);
-        } elsif ($action eq 'show') {
-            check_args (1, 1, [], @args);
-            my $output = $server->acl_show (@args);
-            if (defined $output) {
-                print $output;
-            } else {
-                failure ($server->error, @_);
-            }
-        } else {
-            error "unknown command acl $action";
-        }
-    } elsif ($command eq 'autocreate') {
-        check_args (2, 2, [], @args);
-        $server->autocreate (@args) or failure ($server->error, @_);
-    } elsif ($command eq 'check') {
-        check_args (2, 2, [], @args);
-        my $status = $server->check (@args);
-        if (!defined ($status)) {
-            failure ($server->error, @_);
-        } else {
-            print $status ? "yes\n" : "no\n";
-        }
-    } elsif ($command eq 'comment') {
-        check_args (2, 3, [3], @args);
-        if (@args > 2) {
-            $server->comment (@args) or failure ($server->error, @_);
-        } else {
-            my $output = $server->comment (@args);
-            if (defined $output) {
-                print $output, "\n";
-            } elsif (not $server->error) {
-                print "No comment set\n";
-            } else {
-                failure ($server->error, @_);
-            }
-        }
-    } elsif ($command eq 'create') {
-        check_args (2, 2, [], @args);
-        $server->create (@args) or failure ($server->error, @_);
-    } elsif ($command eq 'destroy') {
-        check_args (2, 2, [], @args);
-        $server->destroy (@args) or failure ($server->error, @_);
-    } elsif ($command eq 'expires') {
-        check_args (2, 3, [], @args);
-        if (@args > 2) {
-            $server->expires (@args) or failure ($server->error, @_);
-        } else {
-            my $output = $server->expires (@args);
-            if (defined $output) {
-                print $output, "\n";
-            } elsif (not $server->error) {
-                print "No expiration set\n";
-            } else {
-                failure ($server->error, @_);
-            }
-        }
-    } elsif ($command eq 'flag') {
-        my $action = shift @args;
-        check_args (3, 3, [], @args);
-        if ($action eq 'clear') {
-            $server->flag_clear (@args) or failure ($server->error, @_);
-        } elsif ($action eq 'set') {
-            $server->flag_set (@args) or failure ($server->error, @_);
-        } else {
-            error "unknown command flag $action";
-        }
-    } elsif ($command eq 'get') {
-        check_args (2, 2, [], @args);
-        my $output = $server->get (@args);
-        if (defined $output) {
-            print $output;
-        } else {
-            failure ($server->error, @_);
-        }
-    } elsif ($command eq 'getacl') {
-        check_args (3, 3, [], @args);
-        my $output = $server->acl (@args);
-        if (defined $output) {
-            print $output, "\n";
-        } elsif (not $server->error) {
-            print "No ACL set\n";
-        } else {
-            failure ($server->error, @_);
-        }
-    } elsif ($command eq 'getattr') {
-        check_args (3, 3, [], @args);
-        my @result = $server->attr (@args);
-        if (not @result and $server->error) {
-            failure ($server->error, @_);
-        } elsif (@result) {
-            print join ("\n", @result, '');
-        }
-    } elsif ($command eq 'history') {
-        check_args (2, 2, [], @args);
-        my $output = $server->history (@args);
-        if (defined $output) {
-            print $output;
-        } else {
-            failure ($server->error, @_);
-        }
-    } elsif ($command eq 'owner') {
-        check_args (2, 3, [], @args);
-        if (@args > 2) {
-            $server->owner (@args) or failure ($server->error, @_);
-        } else {
-            my $output = $server->owner (@args);
-            if (defined $output) {
-                print $output, "\n";
-            } elsif (not $server->error) {
-                print "No owner set\n";
-            } else {
-                failure ($server->error, @_);
-            }
-        }
-    } elsif ($command eq 'rename') {
-        check_args (3, 3, [], @args);
-        $server->rename (@args) or failure ($server->error, @_);
-    } elsif ($command eq 'setacl') {
-        check_args (4, 4, [], @args);
-        $server->acl (@args) or failure ($server->error, @_);
-    } elsif ($command eq 'setattr') {
-        check_args (4, -1, [], @args);
-        $server->attr (@args) or failure ($server->error, @_);
-    } elsif ($command eq 'show') {
-        check_args (2, 2, [], @args);
-        my $output = $server->show (@args);
-        if (defined $output) {
-            print $output;
-        } else {
-            failure ($server->error, @_);
-        }
-    } elsif ($command eq 'store') {
-        check_args (2, 3, [3], @args);
-        if (@args == 2) {
-            local $/;
-            $args[2] = <STDIN>;
-        }
-        splice (@_, 3);
-        $server->store (@args) or failure ($server->error, @_);
-    } elsif ($command eq 'update') {
-        check_args (2, 2, [], @args);
-        my $output = $server->update (@args);
-        if (defined $output) {
-            print $output;
-        } else {
-            failure ($server->error, @_);
-        }
-    } else {
-        error "unknown command $command";
-    }
-    success (@_);
-}
-
-# Parse command-line options.
-my ($quiet);
-Getopt::Long::config ('require_order');
-GetOptions ('q|quiet' => \$quiet) or exit 1;
-$SYSLOG = 0 if $quiet;
-
-# Run the command.
-command (@ARGV);
-
-__END__
-
-##############################################################################
-# Documentation
-##############################################################################
-
-# The commands section of this document is duplicated from the documentation
-# for wallet and should be kept in sync.
-
-=for stopwords
-wallet-backend backend backend-specific remctld ACL acl timestamp getacl
-setacl metadata keytab keytabs enctypes enctype ktadd KDC Allbery
-autocreate MERCHANTABILITY NONINFRINGEMENT sublicense
-
-=head1 NAME
-
-wallet-backend - Wallet server for storing and retrieving secure data
-
-=head1 SYNOPSIS
-
-B<wallet-backend> [B<-q>] I<command> [I<args> ...]
-
-=head1 DESCRIPTION
-
-B<wallet-backend> implements the interface between B<remctld> and the
-wallet system.  It is written to run under B<remctld> and expects the
-authenticated identity of the remote user in the REMOTE_USER environment
-variable.  It uses REMOTE_HOST or REMOTE_ADDR if REMOTE_HOST isn't set for
-additional trace information.  It accepts the command from B<remctld> on
-the command line, creates a Wallet::Server object, and calls the
-appropriate methods.
-
-This program is a fairly thin wrapper around Wallet::Server that
-translates command strings into method calls and returns the results.  It
-does check all arguments except for the <data> argument to the store
-command and rejects any argument not matching C<^[\w_/.-]+\z>; in other
-words, only alphanumerics, underscore (C<_>), slash (C</>), period (C<.>),
-and hyphen (C<->) are permitted in arguments.  This provides some
-additional security over and above the checking already done by the rest
-of the wallet code.
-
-=head1 OPTIONS
-
-=over 4
-
-=item B<--quiet>, B<-q>
-
-If this option is given, B<wallet-backend> will not log its actions to
-syslog.
-
-=back
-
-=head1 COMMANDS
-
-Most commands are only available to wallet administrators (users on the
-C<ADMIN> ACL).  The exceptions are C<acl check>, C<check>, C<get>,
-C<store>, C<show>, C<destroy>, C<flag clear>, C<flag set>, C<getattr>,
-C<setattr>, and C<history>.  C<acl check> and C<check> can be run by
-anyone.  All of the rest of those commands have their own ACLs except
-C<getattr> and C<history>, which use the C<show> ACL, C<setattr>, which
-uses the C<store> ACL, and C<comment>, which uses the owner or C<show> ACL
-depending on whether one is setting or retrieving the comment.  If the
-appropriate ACL is set, it alone is checked to see if the user has access.
-Otherwise, C<destroy>, C<get>, C<store>, C<show>, C<getattr>, C<setattr>,
-C<history>, and C<comment> access is permitted if the user is authorized
-by the owner ACL of the object.
-
-Administrators can run any command on any object or ACL except for C<get>
-and C<store>.  For C<get> and C<store>, they must still be authorized by
-either the appropriate specific ACL or the owner ACL.
-
-If the locked flag is set on an object, no commands can be run on that
-object that change data except the C<flags> commands, nor can the C<get>
-command be used on that object.  C<show>, C<history>, C<getacl>,
-C<getattr>, and C<owner>, C<comment>, or C<expires> without an argument
-can still be used on that object.
-
-For more information on attributes, see L<ATTRIBUTES>.
-
-=over 4
-
-=item acl add <id> <scheme> <identifier>
-
-Add an entry with <scheme> and <identifier> to the ACL <id>.  <id> may be
-either the name of an ACL or its numeric identifier.
-
-=item acl check <id>
-
-Check whether an ACL with the ID <id> already exists.  If it does, prints
-C<yes>; if not, prints C<no>.
-
-=item acl create <name>
-
-Create a new, empty ACL with name <name>.  When setting an ACL on an
-object with a set of entries that don't match an existing ACL, first
-create a new ACL with C<acl create>, add the appropriate entries to it
-with C<acl add>, and then set the ACL on an object with the C<owner> or
-C<setacl> commands.
-
-=item acl destroy <id>
-
-Destroy the ACL <id>.  This ACL must no longer be referenced by any object
-or the ACL destruction will fail.  The special ACL named C<ADMIN> cannot
-be destroyed.
-
-=item acl history <id>
-
-Display the history of the ACL <id>.  Each change to the ACL (not
-including changes to the name of the ACL) will be represented by two
-lines.  The first line will have a timestamp of the change followed by a
-description of the change, and the second line will give the user who made
-the change and the host from which the change was made.
-
-=item acl remove <id> <scheme> <identifier>
-
-Remove the entry with <scheme> and <identifier> from the ACL <id>.  <id>
-may be either the name of an ACL or its numeric identifier.  The last
-entry in the special ACL C<ADMIN> cannot be removed to protect against
-accidental lockout, but administrators can remove themselves from the
-C<ADMIN> ACL and can leave only a non-functioning entry on the ACL.  Use
-caution when removing entries from the C<ADMIN> ACL.
-
-=item acl rename <id> <name>
-
-Renames the ACL identified by <id> to <name>.  This changes the
-human-readable name, not the underlying numeric ID, so the ACL's
-associations with objects will be unchanged.  The C<ADMIN> ACL may not be
-renamed.  <id> may be either the current name or the numeric ID.  <name>
-must not be all-numeric.  To rename an ACL, the current user must be
-authorized by the C<ADMIN> ACL.
-
-=item acl replace <id> <new-id>
-
-Find any objects owned by <id>, and then change their ownership to
-<new_id> instead.  <new-id> should already exist, and may already have
-some objects owned by it.  <id> is not deleted afterwards, though in
-most cases that is probably your next step.  The C<ADMIN> ACL may not be
-replaced from.  <id> and <new-id> may be either the current name or the
-numeric ID.  To replace an ACL, the current user must be authorized by
-the C<ADMIN> ACL.
-
-=item acl show <id>
-
-Display the name, numeric ID, and entries of the ACL <id>.
-
-=item autocreate <type> <name>
-
-Create a new object of type <type> with name <name>.  The user must be
-listed in the default ACL for an object with that type and name, and the
-object will be created with that default ACL set as the object owner.
-
-=item check <type> <name>
-
-Check whether an object of type <type> and name <name> already exists.  If
-it does, prints C<yes>; if not, prints C<no>.
-
-=item comment <type> <name> [<comment>]
-
-If <comment> is not given, displays the current comment for the object
-identified by <type> and <name>, or C<No comment set> if none is set.
-
-If <comment> is given, sets the comment on the object identified by
-<type> and <name> to <comment>.  If <comment> is the empty string, clears
-the comment.
-
-=item create <type> <name>
-
-Create a new object of type <type> with name <name>.  With some backends,
-this will trigger creation of an entry in an external system as well.
-The new object will have no ACLs and no owner set, so usually the
-administrator will want to then set an owner with C<owner> so that the
-object will be usable.
-
-=item destroy <type> <name>
-
-Destroy the object identified by <type> and <name>.  With some backends,
-this will trigger destruction of an object in an external system as well.
-
-=item expires <type> <name> [<date> [<time>]]
-
-If <date> is not given, displays the current expiration of the object
-identified by <type> and <name>, or C<No expiration set> if none is set.
-The expiration will be displayed in seconds since epoch.
-
-If <date> is given, sets the expiration on the object identified by <type>
-and <name> to <date> and (if given) <time>.  <date> and <time> must be in
-some format that can be parsed by the Perl Date::Parse module.  Most
-common formats are supported; if in doubt, use C<YYYY-MM-DD HH:MM:SS>.  If
-<date> is the empty string, clears the expiration of the object.
-
-Currently, the expiration of an object is not used.
-
-=item flag clear <type> <name> <flag>
-
-Clears the flag <flag> on the object identified by <type> and <name>.
-
-=item flag set <type> <name> <flag>
-
-Sets the flag <flag> on the object identified by <type> and <name>.
-Recognized flags are C<locked>, which prevents all further actions on that
-object until the flag is cleared, and C<unchanging>, which tells the
-object backend to not generate new data on get but instead return the same
-data as previously returned.  The C<unchanging> flag is not meaningful for
-objects that do not generate new data on the fly.
-
-=item get <type> <name>
-
-Prints to standard output the data associated with the object identified
-by <type> and <name>.  This may trigger generation of new data and
-invalidate old data for that object depending on the object type.
-
-=item getacl <type> <name> <acl>
-
-Prints the ACL <acl>, which must be one of C<get>, C<store>, C<show>,
-C<destroy>, or C<flags>, for the object identified by <type> and <name>.
-Prints C<No ACL set> if that ACL isn't set on that object.  Remember that
-if the C<get>, C<store>, or C<show> ACLs aren't set, authorization falls
-back to checking the owner ACL.  See the C<owner> command for displaying
-or setting it.
-
-=item getattr <type> <name> <attr>
-
-Prints the object attribute <attr> for the object identified by <type> and
-<name>.  Attributes are used to store backend-specific information for a
-particular object type, and <attr> must be an attribute type known to the
-underlying object implementation.  The attribute values, if any, are
-printed one per line.  If the attribute is not set on this object, nothing
-is printed.
-
-=item history <type> <name>
-
-Displays the history for the object identified by <type> and <name>.  This
-human-readable output will have two lines for each action that changes the
-object, plus for any get action.  The first line has the timestamp of the
-action and the action, and the second line gives the user who performed
-the action and the host from which they performed it.
-
-=item owner <type> <name> [<owner>]
-
-If <owner> is not given, displays the current owner ACL of the object
-identified by <type> and <name>, or C<No owner set> if none is set.  The
-result will be the name of an ACL.
-
-If <owner> is given, sets the owner of the object identified by <type> and
-<name> to <owner>.  If <owner> is the empty string, clears the owner of
-the object.
-
-=item rename <type> <name> <new-name>
-
-Renames an existing object.  This currently only supports file objects,
-where it renames the object itself, then the name and location of the
-object in the file store.
-
-=item setacl <type> <name> <acl> <id>
-
-Sets the ACL <acl>, which must be one of C<get>, C<store>, C<show>,
-C<destroy>, or C<flags>, to <id> on the object identified by <type> and
-<name>.  If <id> is the empty string, clears that ACL on the object.
-
-=item setattr <type> <name> <attr> <value> [<value> ...]
-
-Sets the object attribute <attr> for the object identified by <type> and
-<name>.  Attributes are used to store backend-specific information for a
-particular object type, and <attr> must be an attribute type known to the
-underlying object implementation.  To clear the attribute for this object,
-pass in a <value> of the empty string (C<''>).
-
-=item show <type> <name>
-
-Displays the current object metadata for the object identified by <type>
-and <name>.  This human-readable output will show the object type and
-name, the owner, any specific ACLs set on the object, the expiration if
-any, and the user, remote host, and time when the object was created, last
-stored, and last downloaded.
-
-=item store <type> <name> [<data>]
-
-Stores <data> for the object identified by <type> and <name> for later
-retrieval with C<get>.  Not all object types support this.  If <data> is
-not given as an argument, it will be read from standard input.
-
-=item update <type> <name>
-
-Prints to standard output the data associated with the object identified
-by <type> and <name>.  If the object is one that can have changing
-information, such as a keytab or password, then we generate new data for
-that object regardless of whether there is current data or the unchanging
-flag is set.
-
-=back
-
-=head1 ATTRIBUTES
-
-Object attributes store additional properties and configuration
-information for objects stored in the wallet.  They are displayed as part
-of the object data with C<show>, retrieved with C<getattr>, and set with
-C<setattr>.
-
-=head2 Keytab Attributes
-
-Keytab objects support the following attributes:
-
-=over 4
-
-=item enctypes
-
-Restricts the generated keytab to a specific set of encryption types.  The
-values of this attribute must be enctype strings recognized by Kerberos
-(strings like C<aes256-cts-hmac-sha1-96> or C<des-cbc-crc>).  Note that
-the salt should not be included; since the salt is irrelevant for keytab
-keys, it will always be set to C<normal> by the wallet.
-
-If this attribute is set, the specified enctype list will be passed to
-ktadd when get() is called for that keytab.  If it is not set, the default
-set in the KDC will be used.
-
-This attribute is ignored if the C<unchanging> flag is set on a keytab.
-Keytabs retrieved with C<unchanging> set will contain all keys present in
-the KDC for that Kerberos principal and therefore may contain different
-enctypes than those requested by this attribute.
-
-=back
-
-=head1 AUTHOR
-
-Russ Allbery <eagle@eyrie.org>
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright 2007, 2008, 2010, 2011, 2012, 2013 The Board of Trustees of the
-Leland Stanford Junior University
-
-Permission is hereby granted, free of charge, to any person obtaining a
-copy of this software and associated documentation files (the "Software"),
-to deal in the Software without restriction, including without limitation
-the rights to use, copy, modify, merge, publish, distribute, sublicense,
-and/or sell copies of the Software, and to permit persons to whom the
-Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
-THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-DEALINGS IN THE SOFTWARE.
-
-=head1 SEE ALSO
-
-Wallet::Server(3), remctld(8)
-
-This program is part of the wallet system.  The current version is
-available from L<http://www.eyrie.org/~eagle/software/wallet/>.
-
-=cut
--- /dev/null	2016-01-23 14:00:27.000000000 -0800
+++ server/wallet-backend.in	2016-01-17 19:13:02.000000000 -0800
@@ -0,0 +1,695 @@
+#!@PERL@
+#
+# Wallet server for storing and retrieving secure data.
+
+use 5.008;
+use strict;
+use warnings;
+
+use Getopt::Long qw(GetOptions);
+use Sys::Syslog qw(openlog syslog);
+use Wallet::Server;
+
+# Set to zero to suppress syslog logging, which is used for testing and for
+# the -q option.  Set to a reference to a string to append messages to that
+# string instead.
+our $SYSLOG;
+$SYSLOG = 1 unless defined $SYSLOG;
+
+##############################################################################
+# Logging
+##############################################################################
+
+# Initialize logging.
+sub log_init {
+    if (ref $SYSLOG) {
+        $$SYSLOG = '';
+    } elsif ($SYSLOG) {
+        openlog ('wallet-backend', 'pid', 'auth');
+    }
+}
+
+# Get an identity string for the user suitable for including in log messages.
+sub identity {
+    my $identity = '';
+    if ($ENV{REMOTE_USER}) {
+        $identity = $ENV{REMOTE_USER};
+        my $host = $ENV{REMOTE_HOST} || $ENV{REMOTE_ADDR};
+        $identity .= " ($host)" if $host;
+    }
+    return $identity;
+}
+
+# Log an error message to both syslog and to stderr and exit with a non-zero
+# status.
+sub error {
+    my $message = join ('', @_);
+    if ($SYSLOG) {
+        my $identity = identity;
+        my $log;
+        if ($identity) {
+            $log = "error for $identity: $message";
+        } else {
+            $log = "error: $message";
+        }
+        $log =~ s/[^\x20-\x7e]/_/g;
+        if (ref $SYSLOG) {
+            $$SYSLOG .= "$log\n";
+        } else {
+            syslog ('err', "%s", $log);
+        }
+    }
+    die "$message\n";
+}
+
+# Log a wallet failure message for a given command to both syslog and to
+# stderr and exit with a non-zero status.  Takes the message and the command
+# that was being run.
+sub failure {
+    my ($message, @command) = @_;
+    if ($SYSLOG) {
+        my $log = "command @command from " . identity . " failed: $message";
+        $log =~ s/[^\x20-\x7e]/_/g;
+        if (ref $SYSLOG) {
+            $$SYSLOG .= "$log\n";
+        } else {
+            syslog ('err', "%s", $log);
+        }
+    }
+    die "$message\n";
+}
+
+# Log a wallet success message for a given command.
+sub success {
+    my (@command) = @_;
+    if ($SYSLOG) {
+        my $log = "command @command from " . identity . " succeeded";
+        $log =~ s/[^\x20-\x7e]/_/g;
+        if (ref $SYSLOG) {
+            $$SYSLOG .= "$log\n";
+        } else {
+            syslog ('info', "%s", $log);
+        }
+    }
+}
+
+##############################################################################
+# Parameter checking
+##############################################################################
+
+# Check all arguments against a very restricted set of allowed characters and
+# to ensure the right number of arguments are taken.  The arguments are the
+# number of arguments expected (minimum and maximum), a reference to an array
+# of which argument numbers shouldn't be checked, and then the arguments.
+#
+# This function is probably temporary and will be replaced with something that
+# knows more about the syntax of each command and can check more things.
+sub check_args {
+    my ($min, $max, $exclude, @args) = @_;
+    if (@args < $min) {
+        error "insufficient arguments";
+    } elsif (@args > $max and $max != -1) {
+        error "too many arguments";
+    }
+    my %exclude = map { $_ => 1 } @$exclude;
+    for (my $i = 1; $i <= @args; $i++) {
+        next if $exclude{$i};
+        unless ($args[$i - 1] =~ m,^[\w_/\@.-]*\z,) {
+            error "invalid characters in argument: $args[$i - 1]";
+        }
+    }
+}
+
+##############################################################################
+# Implementation
+##############################################################################
+
+# Parse and execute a command.  We wrap this in a subroutine call for easier
+# testing.
+sub command {
+    log_init;
+    my $user = $ENV{REMOTE_USER} or error "REMOTE_USER not set";
+    my $host = $ENV{REMOTE_HOST} || $ENV{REMOTE_ADDR}
+        or error "neither REMOTE_HOST nor REMOTE_ADDR set";
+
+    # Instantiate the server object.
+    my $server = Wallet::Server->new ($user, $host);
+
+    # Parse command-line options and dispatch to the appropriate calls.
+    my ($command, @args) = @_;
+    if ($command eq 'acl') {
+        my $action = shift @args;
+        if ($action eq 'add') {
+            check_args (3, 3, [3], @args);
+            $server->acl_add (@args) or failure ($server->error, @_);
+        } elsif ($action eq 'check') {
+            check_args (1, 1, [], @args);
+            my $status = $server->acl_check (@args);
+            if (!defined ($status)) {
+                failure ($server->error, @_);
+            } else {
+                print $status ? "yes\n" : "no\n";
+            }
+        } elsif ($action eq 'create') {
+            check_args (1, 1, [], @args);
+            $server->acl_create (@args) or failure ($server->error, @_);
+        } elsif ($action eq 'destroy') {
+            check_args (1, 1, [], @args);
+            $server->acl_destroy (@args) or failure ($server->error, @_);
+        } elsif ($action eq 'history') {
+            check_args (1, 1, [], @args);
+            my $output = $server->acl_history (@args);
+            if (defined $output) {
+                print $output;
+            } else {
+                failure ($server->error, @_);
+            }
+        } elsif ($action eq 'remove') {
+            check_args (3, 3, [3], @args);
+            $server->acl_remove (@args) or failure ($server->error, @_);
+        } elsif ($action eq 'rename') {
+            check_args (2, 2, [], @args);
+            $server->acl_rename (@args) or failure ($server->error, @_);
+        } elsif ($action eq 'replace') {
+            check_args (2, 2, [], @args);
+            $server->acl_replace (@args) or failure ($server->error, @_);
+        } elsif ($action eq 'show') {
+            check_args (1, 1, [], @args);
+            my $output = $server->acl_show (@args);
+            if (defined $output) {
+                print $output;
+            } else {
+                failure ($server->error, @_);
+            }
+        } else {
+            error "unknown command acl $action";
+        }
+    } elsif ($command eq 'autocreate') {
+        check_args (2, 2, [], @args);
+        $server->autocreate (@args) or failure ($server->error, @_);
+    } elsif ($command eq 'check') {
+        check_args (2, 2, [], @args);
+        my $status = $server->check (@args);
+        if (!defined ($status)) {
+            failure ($server->error, @_);
+        } else {
+            print $status ? "yes\n" : "no\n";
+        }
+    } elsif ($command eq 'comment') {
+        check_args (2, 3, [3], @args);
+        if (@args > 2) {
+            $server->comment (@args) or failure ($server->error, @_);
+        } else {
+            my $output = $server->comment (@args);
+            if (defined $output) {
+                print $output, "\n";
+            } elsif (not $server->error) {
+                print "No comment set\n";
+            } else {
+                failure ($server->error, @_);
+            }
+        }
+    } elsif ($command eq 'create') {
+        check_args (2, 2, [], @args);
+        $server->create (@args) or failure ($server->error, @_);
+    } elsif ($command eq 'destroy') {
+        check_args (2, 2, [], @args);
+        $server->destroy (@args) or failure ($server->error, @_);
+    } elsif ($command eq 'expires') {
+        check_args (2, 3, [], @args);
+        if (@args > 2) {
+            $server->expires (@args) or failure ($server->error, @_);
+        } else {
+            my $output = $server->expires (@args);
+            if (defined $output) {
+                print $output, "\n";
+            } elsif (not $server->error) {
+                print "No expiration set\n";
+            } else {
+                failure ($server->error, @_);
+            }
+        }
+    } elsif ($command eq 'flag') {
+        my $action = shift @args;
+        check_args (3, 3, [], @args);
+        if ($action eq 'clear') {
+            $server->flag_clear (@args) or failure ($server->error, @_);
+        } elsif ($action eq 'set') {
+            $server->flag_set (@args) or failure ($server->error, @_);
+        } else {
+            error "unknown command flag $action";
+        }
+    } elsif ($command eq 'get') {
+        check_args (2, 2, [], @args);
+        my $output = $server->get (@args);
+        if (defined $output) {
+            print $output;
+        } else {
+            failure ($server->error, @_);
+        }
+    } elsif ($command eq 'getacl') {
+        check_args (3, 3, [], @args);
+        my $output = $server->acl (@args);
+        if (defined $output) {
+            print $output, "\n";
+        } elsif (not $server->error) {
+            print "No ACL set\n";
+        } else {
+            failure ($server->error, @_);
+        }
+    } elsif ($command eq 'getattr') {
+        check_args (3, 3, [], @args);
+        my @result = $server->attr (@args);
+        if (not @result and $server->error) {
+            failure ($server->error, @_);
+        } elsif (@result) {
+            print join ("\n", @result, '');
+        }
+    } elsif ($command eq 'history') {
+        check_args (2, 2, [], @args);
+        my $output = $server->history (@args);
+        if (defined $output) {
+            print $output;
+        } else {
+            failure ($server->error, @_);
+        }
+    } elsif ($command eq 'owner') {
+        check_args (2, 3, [], @args);
+        if (@args > 2) {
+            $server->owner (@args) or failure ($server->error, @_);
+        } else {
+            my $output = $server->owner (@args);
+            if (defined $output) {
+                print $output, "\n";
+            } elsif (not $server->error) {
+                print "No owner set\n";
+            } else {
+                failure ($server->error, @_);
+            }
+        }
+    } elsif ($command eq 'rename') {
+        check_args (3, 3, [], @args);
+        $server->rename (@args) or failure ($server->error, @_);
+    } elsif ($command eq 'setacl') {
+        check_args (4, 4, [], @args);
+        $server->acl (@args) or failure ($server->error, @_);
+    } elsif ($command eq 'setattr') {
+        check_args (4, -1, [], @args);
+        $server->attr (@args) or failure ($server->error, @_);
+    } elsif ($command eq 'show') {
+        check_args (2, 2, [], @args);
+        my $output = $server->show (@args);
+        if (defined $output) {
+            print $output;
+        } else {
+            failure ($server->error, @_);
+        }
+    } elsif ($command eq 'store') {
+        check_args (2, 3, [3], @args);
+        if (@args == 2) {
+            local $/;
+            $args[2] = <STDIN>;
+        }
+        splice (@_, 3);
+        $server->store (@args) or failure ($server->error, @_);
+    } elsif ($command eq 'update') {
+        check_args (2, 2, [], @args);
+        my $output = $server->update (@args);
+        if (defined $output) {
+            print $output;
+        } else {
+            failure ($server->error, @_);
+        }
+    } else {
+        error "unknown command $command";
+    }
+    success (@_);
+}
+
+# Parse command-line options.
+my ($quiet);
+Getopt::Long::config ('require_order');
+GetOptions ('q|quiet' => \$quiet) or exit 1;
+$SYSLOG = 0 if $quiet;
+
+# Run the command.
+command (@ARGV);
+
+__END__
+
+##############################################################################
+# Documentation
+##############################################################################
+
+# The commands section of this document is duplicated from the documentation
+# for wallet and should be kept in sync.
+
+=for stopwords
+wallet-backend backend backend-specific remctld ACL acl timestamp getacl
+setacl metadata keytab keytabs enctypes enctype ktadd KDC Allbery
+autocreate MERCHANTABILITY NONINFRINGEMENT sublicense
+
+=head1 NAME
+
+wallet-backend - Wallet server for storing and retrieving secure data
+
+=head1 SYNOPSIS
+
+B<wallet-backend> [B<-q>] I<command> [I<args> ...]
+
+=head1 DESCRIPTION
+
+B<wallet-backend> implements the interface between B<remctld> and the
+wallet system.  It is written to run under B<remctld> and expects the
+authenticated identity of the remote user in the REMOTE_USER environment
+variable.  It uses REMOTE_HOST or REMOTE_ADDR if REMOTE_HOST isn't set for
+additional trace information.  It accepts the command from B<remctld> on
+the command line, creates a Wallet::Server object, and calls the
+appropriate methods.
+
+This program is a fairly thin wrapper around Wallet::Server that
+translates command strings into method calls and returns the results.  It
+does check all arguments except for the <data> argument to the store
+command and rejects any argument not matching C<^[\w_/.-]+\z>; in other
+words, only alphanumerics, underscore (C<_>), slash (C</>), period (C<.>),
+and hyphen (C<->) are permitted in arguments.  This provides some
+additional security over and above the checking already done by the rest
+of the wallet code.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--quiet>, B<-q>
+
+If this option is given, B<wallet-backend> will not log its actions to
+syslog.
+
+=back
+
+=head1 COMMANDS
+
+Most commands are only available to wallet administrators (users on the
+C<ADMIN> ACL).  The exceptions are C<acl check>, C<check>, C<get>,
+C<store>, C<show>, C<destroy>, C<flag clear>, C<flag set>, C<getattr>,
+C<setattr>, and C<history>.  C<acl check> and C<check> can be run by
+anyone.  All of the rest of those commands have their own ACLs except
+C<getattr> and C<history>, which use the C<show> ACL, C<setattr>, which
+uses the C<store> ACL, and C<comment>, which uses the owner or C<show> ACL
+depending on whether one is setting or retrieving the comment.  If the
+appropriate ACL is set, it alone is checked to see if the user has access.
+Otherwise, C<destroy>, C<get>, C<store>, C<show>, C<getattr>, C<setattr>,
+C<history>, and C<comment> access is permitted if the user is authorized
+by the owner ACL of the object.
+
+Administrators can run any command on any object or ACL except for C<get>
+and C<store>.  For C<get> and C<store>, they must still be authorized by
+either the appropriate specific ACL or the owner ACL.
+
+If the locked flag is set on an object, no commands can be run on that
+object that change data except the C<flags> commands, nor can the C<get>
+command be used on that object.  C<show>, C<history>, C<getacl>,
+C<getattr>, and C<owner>, C<comment>, or C<expires> without an argument
+can still be used on that object.
+
+For more information on attributes, see L<ATTRIBUTES>.
+
+=over 4
+
+=item acl add <id> <scheme> <identifier>
+
+Add an entry with <scheme> and <identifier> to the ACL <id>.  <id> may be
+either the name of an ACL or its numeric identifier.
+
+=item acl check <id>
+
+Check whether an ACL with the ID <id> already exists.  If it does, prints
+C<yes>; if not, prints C<no>.
+
+=item acl create <name>
+
+Create a new, empty ACL with name <name>.  When setting an ACL on an
+object with a set of entries that don't match an existing ACL, first
+create a new ACL with C<acl create>, add the appropriate entries to it
+with C<acl add>, and then set the ACL on an object with the C<owner> or
+C<setacl> commands.
+
+=item acl destroy <id>
+
+Destroy the ACL <id>.  This ACL must no longer be referenced by any object
+or the ACL destruction will fail.  The special ACL named C<ADMIN> cannot
+be destroyed.
+
+=item acl history <id>
+
+Display the history of the ACL <id>.  Each change to the ACL (not
+including changes to the name of the ACL) will be represented by two
+lines.  The first line will have a timestamp of the change followed by a
+description of the change, and the second line will give the user who made
+the change and the host from which the change was made.
+
+=item acl remove <id> <scheme> <identifier>
+
+Remove the entry with <scheme> and <identifier> from the ACL <id>.  <id>
+may be either the name of an ACL or its numeric identifier.  The last
+entry in the special ACL C<ADMIN> cannot be removed to protect against
+accidental lockout, but administrators can remove themselves from the
+C<ADMIN> ACL and can leave only a non-functioning entry on the ACL.  Use
+caution when removing entries from the C<ADMIN> ACL.
+
+=item acl rename <id> <name>
+
+Renames the ACL identified by <id> to <name>.  This changes the
+human-readable name, not the underlying numeric ID, so the ACL's
+associations with objects will be unchanged.  The C<ADMIN> ACL may not be
+renamed.  <id> may be either the current name or the numeric ID.  <name>
+must not be all-numeric.  To rename an ACL, the current user must be
+authorized by the C<ADMIN> ACL.
+
+=item acl replace <id> <new-id>
+
+Find any objects owned by <id>, and then change their ownership to
+<new_id> instead.  <new-id> should already exist, and may already have
+some objects owned by it.  <id> is not deleted afterwards, though in
+most cases that is probably your next step.  The C<ADMIN> ACL may not be
+replaced from.  <id> and <new-id> may be either the current name or the
+numeric ID.  To replace an ACL, the current user must be authorized by
+the C<ADMIN> ACL.
+
+=item acl show <id>
+
+Display the name, numeric ID, and entries of the ACL <id>.
+
+=item autocreate <type> <name>
+
+Create a new object of type <type> with name <name>.  The user must be
+listed in the default ACL for an object with that type and name, and the
+object will be created with that default ACL set as the object owner.
+
+=item check <type> <name>
+
+Check whether an object of type <type> and name <name> already exists.  If
+it does, prints C<yes>; if not, prints C<no>.
+
+=item comment <type> <name> [<comment>]
+
+If <comment> is not given, displays the current comment for the object
+identified by <type> and <name>, or C<No comment set> if none is set.
+
+If <comment> is given, sets the comment on the object identified by
+<type> and <name> to <comment>.  If <comment> is the empty string, clears
+the comment.
+
+=item create <type> <name>
+
+Create a new object of type <type> with name <name>.  With some backends,
+this will trigger creation of an entry in an external system as well.
+The new object will have no ACLs and no owner set, so usually the
+administrator will want to then set an owner with C<owner> so that the
+object will be usable.
+
+=item destroy <type> <name>
+
+Destroy the object identified by <type> and <name>.  With some backends,
+this will trigger destruction of an object in an external system as well.
+
+=item expires <type> <name> [<date> [<time>]]
+
+If <date> is not given, displays the current expiration of the object
+identified by <type> and <name>, or C<No expiration set> if none is set.
+The expiration will be displayed in seconds since epoch.
+
+If <date> is given, sets the expiration on the object identified by <type>
+and <name> to <date> and (if given) <time>.  <date> and <time> must be in
+some format that can be parsed by the Perl Date::Parse module.  Most
+common formats are supported; if in doubt, use C<YYYY-MM-DD HH:MM:SS>.  If
+<date> is the empty string, clears the expiration of the object.
+
+Currently, the expiration of an object is not used.
+
+=item flag clear <type> <name> <flag>
+
+Clears the flag <flag> on the object identified by <type> and <name>.
+
+=item flag set <type> <name> <flag>
+
+Sets the flag <flag> on the object identified by <type> and <name>.
+Recognized flags are C<locked>, which prevents all further actions on that
+object until the flag is cleared, and C<unchanging>, which tells the
+object backend to not generate new data on get but instead return the same
+data as previously returned.  The C<unchanging> flag is not meaningful for
+objects that do not generate new data on the fly.
+
+=item get <type> <name>
+
+Prints to standard output the data associated with the object identified
+by <type> and <name>.  This may trigger generation of new data and
+invalidate old data for that object depending on the object type.
+
+=item getacl <type> <name> <acl>
+
+Prints the ACL <acl>, which must be one of C<get>, C<store>, C<show>,
+C<destroy>, or C<flags>, for the object identified by <type> and <name>.
+Prints C<No ACL set> if that ACL isn't set on that object.  Remember that
+if the C<get>, C<store>, or C<show> ACLs aren't set, authorization falls
+back to checking the owner ACL.  See the C<owner> command for displaying
+or setting it.
+
+=item getattr <type> <name> <attr>
+
+Prints the object attribute <attr> for the object identified by <type> and
+<name>.  Attributes are used to store backend-specific information for a
+particular object type, and <attr> must be an attribute type known to the
+underlying object implementation.  The attribute values, if any, are
+printed one per line.  If the attribute is not set on this object, nothing
+is printed.
+
+=item history <type> <name>
+
+Displays the history for the object identified by <type> and <name>.  This
+human-readable output will have two lines for each action that changes the
+object, plus for any get action.  The first line has the timestamp of the
+action and the action, and the second line gives the user who performed
+the action and the host from which they performed it.
+
+=item owner <type> <name> [<owner>]
+
+If <owner> is not given, displays the current owner ACL of the object
+identified by <type> and <name>, or C<No owner set> if none is set.  The
+result will be the name of an ACL.
+
+If <owner> is given, sets the owner of the object identified by <type> and
+<name> to <owner>.  If <owner> is the empty string, clears the owner of
+the object.
+
+=item rename <type> <name> <new-name>
+
+Renames an existing object.  This currently only supports file objects,
+where it renames the object itself, then the name and location of the
+object in the file store.
+
+=item setacl <type> <name> <acl> <id>
+
+Sets the ACL <acl>, which must be one of C<get>, C<store>, C<show>,
+C<destroy>, or C<flags>, to <id> on the object identified by <type> and
+<name>.  If <id> is the empty string, clears that ACL on the object.
+
+=item setattr <type> <name> <attr> <value> [<value> ...]
+
+Sets the object attribute <attr> for the object identified by <type> and
+<name>.  Attributes are used to store backend-specific information for a
+particular object type, and <attr> must be an attribute type known to the
+underlying object implementation.  To clear the attribute for this object,
+pass in a <value> of the empty string (C<''>).
+
+=item show <type> <name>
+
+Displays the current object metadata for the object identified by <type>
+and <name>.  This human-readable output will show the object type and
+name, the owner, any specific ACLs set on the object, the expiration if
+any, and the user, remote host, and time when the object was created, last
+stored, and last downloaded.
+
+=item store <type> <name> [<data>]
+
+Stores <data> for the object identified by <type> and <name> for later
+retrieval with C<get>.  Not all object types support this.  If <data> is
+not given as an argument, it will be read from standard input.
+
+=item update <type> <name>
+
+Prints to standard output the data associated with the object identified
+by <type> and <name>.  If the object is one that can have changing
+information, such as a keytab or password, then we generate new data for
+that object regardless of whether there is current data or the unchanging
+flag is set.
+
+=back
+
+=head1 ATTRIBUTES
+
+Object attributes store additional properties and configuration
+information for objects stored in the wallet.  They are displayed as part
+of the object data with C<show>, retrieved with C<getattr>, and set with
+C<setattr>.
+
+=head2 Keytab Attributes
+
+Keytab objects support the following attributes:
+
+=over 4
+
+=item enctypes
+
+Restricts the generated keytab to a specific set of encryption types.  The
+values of this attribute must be enctype strings recognized by Kerberos
+(strings like C<aes256-cts-hmac-sha1-96> or C<des-cbc-crc>).  Note that
+the salt should not be included; since the salt is irrelevant for keytab
+keys, it will always be set to C<normal> by the wallet.
+
+If this attribute is set, the specified enctype list will be passed to
+ktadd when get() is called for that keytab.  If it is not set, the default
+set in the KDC will be used.
+
+This attribute is ignored if the C<unchanging> flag is set on a keytab.
+Keytabs retrieved with C<unchanging> set will contain all keys present in
+the KDC for that Kerberos principal and therefore may contain different
+enctypes than those requested by this attribute.
+
+=back
+
+=head1 AUTHOR
+
+Russ Allbery <eagle@eyrie.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007, 2008, 2010, 2011, 2012, 2013 The Board of Trustees of the
+Leland Stanford Junior University
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+=head1 SEE ALSO
+
+Wallet::Server(3), remctld(8)
+
+This program is part of the wallet system.  The current version is
+available from L<http://www.eyrie.org/~eagle/software/wallet/>.
+
+=cut