mirror of
git://git.gnu.org.ua/pam-modules.git
synced 2025-04-26 00:19:52 +03:00
315 lines
7.8 KiB
Perl
Executable file
315 lines
7.8 KiB
Perl
Executable file
#! /usr/bin/perl
|
|
# This file is part of pam-modules.
|
|
# Copyright (C) 2014-2022 Sergey Poznyakoff
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3, or (at your option)
|
|
# any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along
|
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
use strict;
|
|
use Net::LDAP;
|
|
use Sys::Hostname;
|
|
|
|
=head1 NAME
|
|
|
|
ldappubkey - get user public ssh keys from the LDAP database
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
B<ldappubkey> I<LOGIN>
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Produces on the standard output public ssh keys for the user I<LOGIN>, each
|
|
on a separate line. The program is designed for use with B<sshd>(8) version
|
|
6.2p1 or higher. Public keys are obtained from a LDAP database. The
|
|
configuration is looked up in the following files: B</etc/ldap.conf>,
|
|
B</etc/ldap/ldap.conf> and B</etc/openldap/ldap.conf>. These files are
|
|
tried in this order and the first one of them that exists is read.
|
|
If the environment variable B<LDAP_CONF> is defined, the file it points
|
|
to is tried first.
|
|
|
|
The following configuration statements are used (all keywords are
|
|
case-insensitive):
|
|
|
|
=over 4
|
|
|
|
=item B<uri> B<ldap[si]://>[I<name>[:I<port>]] ...>
|
|
|
|
Specifies the URI of the LDAP server (or servers) to connect to. The default
|
|
is B<ldap://127.0.0.1>.
|
|
|
|
=item B<base> I<DN>
|
|
|
|
Specifies the default base DN to use when performing ldap operations.
|
|
The base must be specified as a Distinguished Name in LDAP format.
|
|
|
|
=item B<binddn> I<DN>
|
|
|
|
Specifies the default bind DN to use.
|
|
|
|
=item B<bindpw> I<PASS>
|
|
|
|
Specifies the password to use with B<binddn>.
|
|
|
|
=item B<uid> I<ATTR>
|
|
|
|
Name of the attribute to use instead of B<uid>. LDAP records are searched
|
|
using the filter defined with the B<PublicKeyFilter> attribute (see below).
|
|
|
|
=item B<ssl start_tls>
|
|
|
|
Use TLS
|
|
|
|
=item B<tls_cacert> I<FILE>
|
|
|
|
Specifies the file that contains certificates for all of the Certificate
|
|
Authorities the client will recognize.
|
|
|
|
=item B<tls_cacertdir> I<DIR>
|
|
|
|
Path of a directory that contains Certificate Authority certificates in
|
|
separate individual files. The B<tls_cacert> statement takes precedence
|
|
over B<tls_cacertdir>.
|
|
|
|
=item B<tls_cert> I<FILE>
|
|
|
|
Specifies the file that contains the client certificate.
|
|
|
|
=item B<tls_key> I<FILE>
|
|
|
|
Specifies the file that contains the private key that matches the
|
|
certificate stored in the B<tls_cert> file.
|
|
|
|
=item B<tls_cipher_suite> I<SPEC>
|
|
|
|
Specifies acceptable cipher suite and preference order.
|
|
|
|
=item B<tls_reqcert> I<LEVEL>
|
|
|
|
Specifies what checks to perform on server certificates in a TLS session.
|
|
I<LEVEL> is one of B<never>, B<allow>, B<try>, B<demand> or B<hard>.
|
|
|
|
=item B<PublicKeyAttribute> I<ATTR> [I<ATTR>...]
|
|
|
|
List of attributes that hold the public key. Default is C<grayPublicKey>.
|
|
|
|
=item B<PublicKeyFilter> I<FILTER>
|
|
|
|
LDAP filter used to retrieve the objects that may contain public keys. The
|
|
I<FILTER> string can contain the following variables
|
|
|
|
=over 8
|
|
|
|
=item B<$uid>
|
|
|
|
The value of B<uid> setting (see above).
|
|
|
|
=item B<$arg>
|
|
|
|
First command line argument.
|
|
|
|
=item B<$hostname>
|
|
|
|
Full hostname of the machine.
|
|
|
|
=back
|
|
|
|
Default value is:
|
|
|
|
(&(objectClass=posixAccount)($uid=$arg))
|
|
|
|
=item B<PublicKeyPAMService> I<NAME>
|
|
|
|
If this setting is present, B<ldappubkey> will first try to authenticate
|
|
using LDAP service I<NAME> and the user name supplied in the command line.
|
|
It will exit immediately if the authentication fails.
|
|
|
|
This can be used to enforce additional restrictions on who is allowed to
|
|
log in remotely on the server.
|
|
|
|
=back
|
|
|
|
=head1 OPTIONS
|
|
|
|
=over 4
|
|
|
|
=item B<-h>
|
|
|
|
Show program usage.
|
|
|
|
=item B<--help>
|
|
|
|
Show detailed help page.
|
|
|
|
=back
|
|
|
|
=head1 ENVIRONMENT
|
|
|
|
=over 4
|
|
|
|
=item B<LDAP_CONF>
|
|
|
|
If defined, names the alternative configuration file to read.
|
|
|
|
=item B<GITCONFIG_TEMPLATE>
|
|
|
|
Name of the template file to use instead of the default B<.gitconfig>.
|
|
|
|
=back
|
|
|
|
=head1 SEE ALSO
|
|
|
|
B<sshd>(8), B<sshd_config>(5), B<ldap.conf>(5).
|
|
|
|
=head1 BUGS
|
|
|
|
LDAP filter string is hardcoded.
|
|
|
|
=head1 AUTHOR
|
|
|
|
Sergey Poznyakoff <gray@gnu.org>
|
|
|
|
=cut
|
|
|
|
# ###################################
|
|
# Configuration file handling
|
|
# ###################################
|
|
|
|
my %config = ('uri' => 'ldap://127.0.0.1', 'uid' => 'uid',
|
|
'publickeyattribute' => 'grayPublicKey',
|
|
'publickeyfilter' => '(&(objectClass=posixAccount)($uid=$arg))');
|
|
|
|
sub read_config_file($) {
|
|
my $config_file = shift;
|
|
my $file;
|
|
my $line = 0;
|
|
|
|
open($file, "<", $config_file) or die("cannot open $config_file: $!");
|
|
while (<$file>) {
|
|
++$line;
|
|
chomp;
|
|
s/^\s+//;
|
|
s/\s+$//;
|
|
s/#.*//;
|
|
next if ($_ eq "");
|
|
my @kwp = split(/\s+/, $_, 2);
|
|
$config{lc($kwp[0])} = $kwp[1];
|
|
}
|
|
close($file);
|
|
}
|
|
|
|
sub get_fqdn_hostname {
|
|
my $name = hostname();
|
|
if (my ($fqdn) = gethostbyname($name)) {
|
|
return $fqdn;
|
|
} else {
|
|
return $name;
|
|
}
|
|
}
|
|
|
|
sub assert {
|
|
my $mesg = shift;
|
|
my $action = shift;
|
|
die("An error occurred $action: ".$mesg->error) if ($mesg->code);
|
|
return $mesg;
|
|
}
|
|
|
|
# ###################################
|
|
# MAIN
|
|
# ###################################
|
|
|
|
die "bad number of arguments; try perldoc $0 for more info"
|
|
unless ($#ARGV == 0);
|
|
|
|
## Read configuration
|
|
my @config_files = ("/etc/ldap.conf", "/etc/ldap/ldap.conf",
|
|
"/etc/openldap/ldap.conf");
|
|
unshift @config_files, $ENV{LDAP_CONF} if defined($ENV{LDAP_CONF});
|
|
|
|
foreach my $file (@config_files) {
|
|
if (-e $file) {
|
|
read_config_file($file);
|
|
last;
|
|
}
|
|
}
|
|
|
|
my $ldap = Net::LDAP->new($config{'uri'})
|
|
or die("Unable to connect to LDAP server $config{'uri'}: $!");
|
|
|
|
if ($config{ssl} eq 'start_tls') {
|
|
my %args;
|
|
|
|
$args{capath} = $config{tls_cacertdir}
|
|
if (defined($config{tls_cacertdir}));
|
|
$args{cafile} = $config{tls_cacert}
|
|
if (defined($config{tls_cacert}));
|
|
if ($config{tls_reqcert} eq 'none') {
|
|
$args{verify} = 'never';
|
|
} elsif ($config{tls_reqcert} eq 'allow') {
|
|
$args{verify} = 'optional';
|
|
} elsif ($config{tls_reqcert} eq 'demand'
|
|
or $config{tls_reqcert} eq 'hard') {
|
|
$args{verify} = 'require';
|
|
} elsif ($config{tls_reqcert} eq 'try') {
|
|
$args{verify} = 'optional'; # FIXME: That's wrong
|
|
}
|
|
$args{clientcert} = $config{tls_cert}
|
|
if (defined($config{tls_cert}));
|
|
$args{clientkey} = $config{tls_key}
|
|
if (defined($config{tls_key}));
|
|
$args{ciphers} = $config{tls_cipher_suite}
|
|
if (defined($config{tls_cipher_suite}));
|
|
|
|
assert($ldap->start_tls(%args), "TLS negotiation");
|
|
}
|
|
|
|
my @bindargs = ();
|
|
if (defined($config{'binddn'})) {
|
|
push(@bindargs, $config{'binddn'});
|
|
push(@bindargs, password => $config{'bindpw'})
|
|
if defined($config{'bindpw'});
|
|
}
|
|
assert($ldap->bind(@bindargs), "binding to the server");
|
|
|
|
my @attrs = split /\s+/, $config{'publickeyattribute'};
|
|
my $filter = $config{'publickeyfilter'};
|
|
my $uid = "$config{'uid'}";
|
|
my $arg = $ARGV[0];
|
|
my $hostname = get_fqdn_hostname();
|
|
$filter =~ s/(?<!\\)(\$(?:uid|arg|hostname))/$1/eeg;
|
|
|
|
if (my $service = $config{publickeypamservice}) {
|
|
use Authen::PAM qw(:constants);
|
|
my $pamh;
|
|
ref($pamh = new Authen::PAM($service, $arg)) ||
|
|
die "Error code $pamh during PAM init!";
|
|
my $res = $pamh->pam_authenticate;
|
|
if ($res != PAM_SUCCESS) {
|
|
die "pam_authenticate: ". $pamh->pam_strerror($res) . "\n";
|
|
}
|
|
}
|
|
|
|
my $res = assert($ldap->search(base => $config{'base'},
|
|
filter => $filter,
|
|
attrs => \@attrs ),
|
|
"searching for $filter in $config{'base'}");
|
|
|
|
foreach my $entry ($res->entries()) {
|
|
for (map { my $a = $entry->get_value($_, asref => 1); $a ? @$a : () } @attrs) {
|
|
print "$_\n";
|
|
}
|
|
}
|
|
|
|
# END
|
|
|
|
|