summaryrefslogtreecommitdiff
path: root/perl/lib/Wallet/ACL/External.pm
blob: caed80ef87099e9e44727ddf6e5498d0d6e6d516 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# Wallet::ACL::External -- Wallet external ACL verifier
#
# Copyright 2016 Russ Allbery <eagle@eyrie.org>
#
# See LICENSE for licensing terms.

##############################################################################
# Modules and declarations
##############################################################################

package Wallet::ACL::External;

use 5.008;
use strict;
use warnings;

use POSIX qw(_exit);
use Wallet::ACL::Base;
use Wallet::Config;

our @ISA     = qw(Wallet::ACL::Base);
our $VERSION = '1.03';

##############################################################################
# Interface
##############################################################################

# Creates a new persistent verifier.  This just checks if the configuration
# is in place.
sub new {
    my $type = shift;
    unless ($Wallet::Config::EXTERNAL_COMMAND) {
        die "external ACL support not configured\n";
    }
    my $self = {};
    bless ($self, $type);
    return $self;
}

# The most trivial ACL verifier.  Returns true if the provided principal
# matches the ACL.
sub check {
    my ($self, $principal, $acl, $type, $name) = @_;
    unless ($principal) {
        $self->error ('no principal specified');
        return;
    }
    my @args = ($principal, $type, $name, $acl);
    my $pid = open (EXTERNAL, '-|');
    if (not defined $pid) {
        $self->error ("cannot fork: $!");
        return;
    } elsif ($pid == 0) {
        unless (open (STDERR, '>&STDOUT')) {
            warn "wallet: cannot dup stdout: $!\n";
            _exit(1);
        }
        unless (exec ($Wallet::Config::EXTERNAL_COMMAND, @args)) {
            warn "wallet: cannot run $Wallet::Config::EXTERNAL_COMMAND: $!\n";
            _exit(1);
        }
    }
    local $_;
    my @output = <EXTERNAL>;
    close EXTERNAL;
    if ($? == 0) {
        return 1;
    } else {
        if (@output) {
            $self->error ($output[0]);
            return;
        } else {
            return 0;
        }
    }
}

1;
__END__

##############################################################################
# Documentation
##############################################################################

=for stopwords
ACL Allbery verifier remctl

=head1 NAME

Wallet::ACL::External - Wallet ACL verifier using an external command

=head1 SYNOPSIS

    my $verifier = Wallet::ACL::External->new;
    my $status = $verifier->check ($principal, $acl);
    if (not defined $status) {
        die "Something failed: ", $verifier->error, "\n";
    } elsif ($status) {
        print "Access granted\n";
    } else {
        print "Access denied\n";
    }

=head1 DESCRIPTION

Wallet::ACL::External runs an external command to determine whether access is
granted.  The command configured via $EXTERNAL_COMMAND in L<Wallet::Config>
will be run.  The first argument to the command will be the principal
requesting access.  The identifier of the ACL will be split on whitespace and
passed in as the remaining arguments to this command.

No other arguments are passed to the command, but the command will have access
to all of the remctl environment variables seen by the wallet server (such as
REMOTE_USER).  For a full list of environment variables, see
L<remctld(8)/ENVIRONMENT>.

The external command should exit with a non-zero status but no output to
indicate a normal failure to satisfy the ACL.  Any output will be treated as
an error.

=head1 METHODS

=over 4

=item new()

Creates a new ACL verifier.  For this verifier, this just confirms that
the wallet configuration sets an external command.

=item check(PRINCIPAL, ACL, TYPE, NAME)

Returns true if the external command returns success when run with that
PRINCIPAL, object TYPE and NAME, and ACL.  So, for example, the ACL C<external
mdbset shell> will, when triggered by a request from rra@EXAMPLE.COM for the
object C<file password>, result in the command:

    $Wallet::Config::EXTERNAL_COMMAND rra@EXAMPLE.COM file password \
        'mdbset shell'

=item error()

Returns the error if check() returned undef.

=back

=head1 DIAGNOSTICS

The new() method may fail with one of the following exceptions:

=over 4

=item external ACL support not configured

The required configuration parameters were not set.  See L<Wallet::Config>
for the required configuration parameters and how to set them.

=back

Verifying an external ACL may fail with the following errors (returned by
the error() method):

=over 4

=item cannot fork: %s

The attempt to fork in order to execute the external ACL verifier
command failed, probably due to a lack of system resources.

=item no principal specified

The PRINCIPAL parameter to check() was undefined or the empty string.

=back

In addition, if the external command fails and produces some output,
that will be considered a failure and the first line of its output will
be returned as the error message.  The external command should exit
with a non-zero status but no error to indicate a normal failure.

=head1 SEE ALSO

remctld(8), Wallet::ACL(3), Wallet::ACL::Base(3), Wallet::Config(3),
wallet-backend(8)

This module is part of the wallet system.  The current version is
available from L<http://www.eyrie.org/~eagle/software/wallet/>.

=head1 AUTHOR

Russ Allbery <eagle@eyrie.org>

=cut