mirror of
				https://git.lapiole.org/dani/ansible-roles.git
				synced 2025-11-03 20:31:26 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			766 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			766 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
#! /usr/bin/perl
 | 
						|
# Copyright (C) 2014 by P. Tomulik <ptomulik@meil.pw.edu.pl>
 | 
						|
#
 | 
						|
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
# you may not use this file except in compliance with the License.
 | 
						|
# You may obtain a copy of the License at
 | 
						|
#
 | 
						|
# http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
#
 | 
						|
# Unless required by applicable law or agreed to in writing, software
 | 
						|
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
# See the License for the specific language governing permissions and
 | 
						|
# limitations under the License.
 | 
						|
 | 
						|
 | 
						|
# Dependencies:
 | 
						|
#   perl-base
 | 
						|
#   libnet-ldap-perl
 | 
						|
#   libdata-dump-perl
 | 
						|
 | 
						|
use Net::LDAP;
 | 
						|
use Data::Dump;
 | 
						|
use Getopt::Long;
 | 
						|
use String::Escape qw( unbackslash );
 | 
						|
 | 
						|
my $attrs;
 | 
						|
my $base;
 | 
						|
my $binddn;
 | 
						|
my $binddn_env;
 | 
						|
my $bindpw;
 | 
						|
my $bindpw_env;
 | 
						|
my $debug = 0;
 | 
						|
my $deref;
 | 
						|
my $extra_filter;
 | 
						|
my $filter;
 | 
						|
my $group;
 | 
						|
my $ldapuri;
 | 
						|
my $multiple;
 | 
						|
my $starttls;
 | 
						|
my $passwd;
 | 
						|
my $passwd_env;
 | 
						|
my $print;
 | 
						|
my $rebind;
 | 
						|
my $scope;
 | 
						|
my $user;
 | 
						|
my $user_attr = 'uid';
 | 
						|
my $user_env;
 | 
						|
my $user_filter;
 | 
						|
my $verbose;
 | 
						|
my $separator = '\n';
 | 
						|
my $value_map;
 | 
						|
my $tag = 'openxpki-auth-ldap';
 | 
						|
 | 
						|
 | 
						|
Getopt::Long::Configure('no_auto_abbrev', 'no_ignore_case', 'auto_help');
 | 
						|
Getopt::Long::GetOptions(
 | 
						|
            'attrs|a=s' => \$attrs,
 | 
						|
            'base|b=s' => \$base,
 | 
						|
            'binddn|d=s' => \$binddn,
 | 
						|
            'binddn-env|D=s' => \$binddn_env,
 | 
						|
            'bindpw|w=s' => \$bindpw,
 | 
						|
            'bindpw-env|W=s' => \$bindpw_env,
 | 
						|
            'debug|g' => \$debug,
 | 
						|
            'deref=s' => \$deref,
 | 
						|
            'extra-filter=s' => \$extra_filter,
 | 
						|
            'filter|f=s' => \$filter,
 | 
						|
            'group|G=s' => \$group,
 | 
						|
            'ldapuri|H=s' => \$ldapuri,
 | 
						|
            'multiple|m' => \$multiple,
 | 
						|
            'starttls|t' => \$starttls,
 | 
						|
            'passwd|p=s' => \$passwd,
 | 
						|
            'passwd-env|P=s' => \$passwd_env,
 | 
						|
            'print=s' => \$print,
 | 
						|
            'rebind|r' => \$rebind,
 | 
						|
            'scope|s=s' => \$scope,
 | 
						|
            'user|u=s' => \$user,
 | 
						|
            'user-attr=s' => \$user_attr,
 | 
						|
            'user-env|U=s' => \$user_env,
 | 
						|
            'user-filter=s' => \$user_filter,
 | 
						|
            'verbose|V' => \$verbose,
 | 
						|
            'separator=s' => \$separator,
 | 
						|
            'value-map|k=s%' => \$value_map
 | 
						|
) or exit(1);
 | 
						|
 | 
						|
if($debug) {
 | 
						|
  print STDERR "$tag: debug: initial values: \n";
 | 
						|
  print STDERR "$tag: debug:   attrs:           " . Data::Dump::dump($attrs) ."\n" if(defined($attrs));
 | 
						|
  print STDERR "$tag: debug:   base:            '$base'\n" if(defined($base));
 | 
						|
  print STDERR "$tag: debug:   binddn:          '$binddn'\n" if(defined($binddn));
 | 
						|
  print STDERR "$tag: debug:   binddn_env:      \$$binddn_env\n" if(defined($binddn_env));
 | 
						|
  print STDERR "$tag: debug:   bindpw:          '$bindpw'\n" if(defined($bindpw));
 | 
						|
  print STDERR "$tag: debug:   bindpw_env:      \$$bindpw_env\n" if(defined($bindpw_env));
 | 
						|
  print STDERR "$tag: debug:   debug:           $debug\n";
 | 
						|
  print STDERR "$tag: debug:   deref:           '$deref'\n" if(defined($deref));
 | 
						|
  print STDERR "$tag: debug:   extra_filter:    '$extra_filter'\n" if(defined($extra_filter));
 | 
						|
  print STDERR "$tag: debug:   filter:          '$filter'\n" if(defined($filter));
 | 
						|
  print STDERR "$tag: debug:   group:           '$group'\n" if(defined($group));
 | 
						|
  print STDERR "$tag: debug:   ldapuri:         '$ldapuri'\n" if(defined($ldapuri));
 | 
						|
  print STDERR "$tag: debug:   multiple:        '$multiple'\n" if(defined($multiple));
 | 
						|
  print STDERR "$tag: debug:   starttls:        '$starttls'\n" if(defined($starttls));
 | 
						|
  print STDERR "$tag: debug:   passwd:          '$passwd'\n" if(defined($passwd));
 | 
						|
  print STDERR "$tag: debug:   passwd_env:      \$$passwd_env\n" if(defined($passwd_env));
 | 
						|
  print STDERR "$tag: debug:   print:           '$print'\n" if(defined($print));
 | 
						|
  print STDERR "$tag: debug:   rebind:          $rebind\n" if(defined($rebind));
 | 
						|
  print STDERR "$tag: debug:   scope:           '$scope'\n" if(defined($scope));
 | 
						|
  print STDERR "$tag: debug:   user:            '$user'\n" if(defined($user));
 | 
						|
  print STDERR "$tag: debug:   user_attr:       '$user_attr'\n" if(defined($user));
 | 
						|
  print STDERR "$tag: debug:   user_env:        \$$user_env\n" if(defined($user_env));
 | 
						|
  print STDERR "$tag: debug:   user_filter:     '$user_filter'\n" if(defined($user_filter));
 | 
						|
  print STDERR "$tag: debug:   verbose:         $verbose\n" if(defined($verbose));
 | 
						|
  print STDERR "$tag: debug:   separator:       $separator\n" if(defined($separator));
 | 
						|
  print STDERR "$tag: debug:   value_map:        " . Data::Dump::dump($value_map) . "\n" if(defined($value_map));
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
if(!defined($ldapuri)) {
 | 
						|
  print STDERR "$tag: error: missing LDAP URI, you MUST specify it with -H or --ldapuri\n";
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
if(defined($binddn) && defined($binddn_env)) {
 | 
						|
  print STDERR "$tag: error: --binddn and --binddn-env can't be specified at the same time\n";
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
if(defined($bindpw) && defined($bindpw_env)) {
 | 
						|
  print STDERR "$tag: error: --bindpw and --bindpw-env can't be specified at the same time\n";
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
if(defined($binddn_env) && !exists($ENV{$binddn_env})) {
 | 
						|
  print STDERR "$tag: error: expected environment variable \$$binddn_env to contain bind DN but it's not set\n";
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
if(defined($bindpw_env) && !exists($ENV{$bindpw_env})) {
 | 
						|
  print STDERR "$tag: error: expected environment variable \$$bindpw_env to contain bind DN password but it's not set\n";
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
if(defined($user) && defined($user_env)) {
 | 
						|
  print STDERR "$tag: error: --user and --user-env can't be specified at the same time\n";
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
if(defined($user) && defined($user_filter)) {
 | 
						|
  print STDERR "$tag: error: --user and --user-filter can't be used at the same time\n";
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
if(defined($user) && defined($filter)) {
 | 
						|
  print STDERR "$tag: error: --user and --filter can't be used at the same time\n";
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
if(defined($user_filter) && defined($filter)) {
 | 
						|
  print STDERR "$tag: error: --user-filter and --filter can't be used at the same time\n";
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
if(defined($extra_filter) && defined($filter)) {
 | 
						|
  print STDERR "$tag: error: --extra-filter and --filter can't be used at the same time\n";
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
if(defined($passwd) && defined($passwd_env)) {
 | 
						|
  print STDERR "$tag: error: --passwd and --passwd-env can't be specified at the same time\n";
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
if(defined($user_env) && !exists($ENV{$user_env})) {
 | 
						|
  print STDERR "$tag: error: expected environment variable \$$user_env to contain user user but it's not set\n";
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
if(defined($passwd_env) && !exists($ENV{$passwd_env})) {
 | 
						|
  print STDERR "$tag: error: expected environment variable \$$passwd_env to contain user password but it's not set\n";
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
 | 
						|
# Retrieve credentials from environment
 | 
						|
#
 | 
						|
$binddn = $ENV{$binddn_env} if(defined($binddn_env));
 | 
						|
$bindpw = $ENV{$bindpw_env} if(defined($bindpw_env));
 | 
						|
$user = $ENV{$user_env} if(defined($user_env));
 | 
						|
$passwd = $ENV{$passwd_env} if(defined($passwd_env));
 | 
						|
 | 
						|
if(defined($user) && !defined($user_attr)) {
 | 
						|
  print STDERR "--user or --user-env given but --user-attr is empty\n";
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
 | 
						|
if(defined($user_attr) && defined($user)) {
 | 
						|
  print STDERR "$tag: info: authenticating user '$user'\n" if($verbose);
 | 
						|
  $user =~ s/\\/\\5C/g;
 | 
						|
  $user =~ s/\*/\\2A/g;
 | 
						|
  $user_filter = "$user_attr=$user";
 | 
						|
}
 | 
						|
 | 
						|
if(defined($user_filter)) {
 | 
						|
  if(defined($extra_filter)) {
 | 
						|
    $filter = "(&($user_filter)($extra_filter))";
 | 
						|
  } else {
 | 
						|
    $filter = "($user_filter)";
 | 
						|
  }
 | 
						|
  print STDERR "$tag: info: search filter set to '$filter'\n" if($verbose);
 | 
						|
}
 | 
						|
 | 
						|
if(defined($attrs)) {
 | 
						|
  my @tmp = split(/,/,$attrs);
 | 
						|
  $attrs = undef;
 | 
						|
  @$attrs = @tmp;
 | 
						|
}
 | 
						|
 | 
						|
my $groupattr;
 | 
						|
my $groupdn;
 | 
						|
 | 
						|
if(defined($group)) {
 | 
						|
  my ($p1, $p2) = split(/\//,$group,2);
 | 
						|
  if(!$p2) {
 | 
						|
    $groupdn = $p1;
 | 
						|
  } else {
 | 
						|
    $groupattr = $p1;
 | 
						|
    $groupdn = $p2;
 | 
						|
  }
 | 
						|
  $groupattr = 'member' if(!$groupattr);
 | 
						|
  if($debug) {
 | 
						|
    print STDERR "$tag: debug: \$groupdn='$groupdn'\n";
 | 
						|
    print STDERR "$tag: debug: \$groupattr='$groupattr'\n";
 | 
						|
  }
 | 
						|
  if(!$groupdn) {
 | 
						|
    print STDERR "$tag: error: group DN not provided for -G option\n";
 | 
						|
    exit(1);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
if($debug) {
 | 
						|
  print STDERR "$tag: debug: final values: \n";
 | 
						|
  print STDERR "$tag: debug:   attrs:           " . Data::Dump::dump($attrs) ."\n" if(defined($attrs));
 | 
						|
  print STDERR "$tag: debug:   base:            '$base'\n" if(defined($base));
 | 
						|
  print STDERR "$tag: debug:   binddn:          '$binddn'\n" if(defined($binddn));
 | 
						|
  print STDERR "$tag: debug:   binddn_env:      \$$binddn_env\n" if(defined($binddn_env));
 | 
						|
  print STDERR "$tag: debug:   bindpw:          '$bindpw'\n" if(defined($bindpw));
 | 
						|
  print STDERR "$tag: debug:   bindpw_env:      \$$bindpw_env\n" if(defined($bindpw_env));
 | 
						|
  print STDERR "$tag: debug:   debug:           $debug\n";
 | 
						|
  print STDERR "$tag: debug:   deref:           '$deref'\n" if(defined($deref));
 | 
						|
  print STDERR "$tag: debug:   extra_filter:    '$extra_filter'\n" if(defined($extra_filter));
 | 
						|
  print STDERR "$tag: debug:   filter:          '$filter'\n" if(defined($filter));
 | 
						|
  print STDERR "$tag: debug:   group:           '$group'\n" if(defined($group));
 | 
						|
  print STDERR "$tag: debug:   groupdn:         '$groupdn'\n" if(defined($groupdn));
 | 
						|
  print STDERR "$tag: debug:   groupattr:       '$groupattr'\n" if(defined($groupattr));
 | 
						|
  print STDERR "$tag: debug:   ldapuri:         '$ldapuri'\n" if(defined($ldapuri));
 | 
						|
  print STDERR "$tag: debug:   multiple:        '$multiple'\n" if(defined($multiple));
 | 
						|
  print STDERR "$tag: debug:   starttls::       '$starttls'\n" if(defined($starttls));
 | 
						|
  print STDERR "$tag: debug:   passwd:          '$passwd'\n" if(defined($passwd));
 | 
						|
  print STDERR "$tag: debug:   passwd_env:      \$$passwd_env\n" if(defined($passwd_env));
 | 
						|
  print STDERR "$tag: debug:   print:           '$print'\n" if(defined($print));
 | 
						|
  print STDERR "$tag: debug:   rebind:          $rebind\n" if(defined($rebind));
 | 
						|
  print STDERR "$tag: debug:   scope:           '$scope'\n" if(defined($scope));
 | 
						|
  print STDERR "$tag: debug:   user:            '$user'\n" if(defined($user));
 | 
						|
  print STDERR "$tag: debug:   user_attr:       '$user_attr'\n" if(defined($user));
 | 
						|
  print STDERR "$tag: debug:   user_env:        \$$user_env\n" if(defined($user_env));
 | 
						|
  print STDERR "$tag: debug:   user_filter:     '$user_filter'\n" if(defined($user_filter));
 | 
						|
  print STDERR "$tag: debug:   verbose:         $verbose\n" if(defined($verbose));
 | 
						|
  print STDERR "$tag: debug:   separator:       $separator\n" if(defined($separator));
 | 
						|
  print STDERR "$tag: debug:   value_map:        " . Data::Dump::dump($value_map) . "\n" if(defined($value_map));
 | 
						|
}
 | 
						|
 | 
						|
if($debug) {
 | 
						|
  print STDERR "$tag: debug: connecting to $ldapuri\n";
 | 
						|
  print STDERR "$tag: debug: \$ldap = Net::LDAP->new('$ldapuri', verify => require)\n";
 | 
						|
}
 | 
						|
my $ldap = Net::LDAP->new($ldapuri, verify => 'require') or die "$tag: error: $@";
 | 
						|
 | 
						|
if($starttls) {
 | 
						|
  if($debug) {
 | 
						|
    print STDERR "$tag: debug: Asking for Start TLS\n";
 | 
						|
    print STDERR "$tag: debug: \$ldap->start_tls( verify => 'require' )\n";
 | 
						|
  }
 | 
						|
  $ldap->start_tls( verify => 'require' ) or die "$tag: error: $@";
 | 
						|
}
 | 
						|
 | 
						|
if($binddn && $bindpw) {
 | 
						|
  print STDERR "$tag: debug: \$bind = \$ldap->bind('$binddn', password => '$bindpw')\n" if($debug);;
 | 
						|
  $bind = $ldap->bind($binddn, password => $bindpw);
 | 
						|
} else {
 | 
						|
  $bind = $ldap->bind();
 | 
						|
}
 | 
						|
if($debug) {
 | 
						|
  print STDERR "$tag: debug: \$bind->is_error() == ".$bind->is_error()."\n";
 | 
						|
  print STDERR "$tag: debug: \$bind->code == ".$bind->code."\n";
 | 
						|
}
 | 
						|
if($bind->is_error()) {
 | 
						|
  print STDERR "$tag: error: ".$bind->error()."\n" if($verbose);
 | 
						|
  exit($bind->code);
 | 
						|
}
 | 
						|
 | 
						|
my $userdn;
 | 
						|
if(defined($filter)) {
 | 
						|
  print STDERR "$tag: debug: search filter given - will search LDAP database to find user\n" if($debug);
 | 
						|
  my $args = {};
 | 
						|
  $args->{'attrs'} = []; # we're just searching for user DN
 | 
						|
  $args->{'base'} = $base if(defined($base));
 | 
						|
  $args->{'scope'} = $base if(defined($scope));
 | 
						|
  $args->{'deref'} = $base if(defined($deref));
 | 
						|
  $args->{'filter'} = $filter;
 | 
						|
  print STDERR "$tag: debug: \$result = \$ldap->search(". Data::Dump::dump($args) .")\n" if($debug);
 | 
						|
  $result = $ldap->search(%$args);
 | 
						|
  if($result->is_error()) {
 | 
						|
    print STDERR "$tag: error: ".$result->error()."\n" if($verbose);
 | 
						|
    exit($result->code);
 | 
						|
  }
 | 
						|
  print STDERR "$tag: debug: \$result->count == ". $result->count ."\n" if($debug);;
 | 
						|
  if($result->count == 0) {
 | 
						|
    print STDERR "$tag: error: user not found in database\n" if($verbose);
 | 
						|
    exit(1);
 | 
						|
  } elsif(!$multiple && $result->count > 1) {
 | 
						|
    print STDERR "$tag: error: ambiguous search result (found ".$result->count."entries)\n" if($verbose);
 | 
						|
    exit(1);
 | 
						|
  }
 | 
						|
  my @entries = $result->entries();
 | 
						|
  # Try all entries to bind to their DNs
 | 
						|
  my $i = 0;
 | 
						|
  foreach(@entries) {
 | 
						|
    my $entry = $_;
 | 
						|
    my $dn = $entry->dn();
 | 
						|
    if($debug) {
 | 
						|
      print STDERR "$tag: debug: [$i] trying " .$dn. "\n" if($debug);
 | 
						|
      print STDERR "$tag: debug: [$i] \$bind = \$ldap->bind('$dn', password => '$passwd')\n";
 | 
						|
    }
 | 
						|
    # Try to bind as user
 | 
						|
    my $bind = $ldap->bind($dn, password => $passwd);
 | 
						|
    print STDERR "$tag: debug: [$i] \$bind->is_error() == ". $bind->is_error() ."\n" if($debug);
 | 
						|
    if($bind->is_error()) {
 | 
						|
      print STDERR "$tag: debug: [$i] ".$bind->error()."\n" if($debug);
 | 
						|
    } else {
 | 
						|
      print STDERR "$tag: debug: [$i] bind successful\n" if($debug);
 | 
						|
      $userdn = $dn;
 | 
						|
    }
 | 
						|
    # Bind back to the original bind DN
 | 
						|
    if($binddn && $bindpw) {
 | 
						|
      if($debug) {
 | 
						|
        print STDERR "$tag: debug: [$i] binding back to $binddn\n";
 | 
						|
        print STDERR "$tag: debug: [$i] \$bind = \$ldap->bind('$binddn', password => '$bindpw')\n";
 | 
						|
      }
 | 
						|
      $bind = $ldap->bind($binddn, password => $bindpw);
 | 
						|
    } else {
 | 
						|
      $bind = $ldap->bind;
 | 
						|
    }
 | 
						|
    if($debug) {
 | 
						|
      print STDERR "$tag: debug: [$i] \$bind->is_error() == ".$bind->is_error()."\n";
 | 
						|
      print STDERR "$tag: debug: [$i] \$bind->code == ".$bind->code."\n";
 | 
						|
    }
 | 
						|
    if($bind->is_error()) {
 | 
						|
      print STDERR "$tag: error: ".$bind->error()."\n" if($verbose);
 | 
						|
      exit($bind->code);
 | 
						|
    }
 | 
						|
    # User initially authenticated, it's done unless we have to check its
 | 
						|
    # group participation.
 | 
						|
    if(defined($userdn)) {
 | 
						|
      if(defined($groupdn) && defined($groupattr)) {
 | 
						|
        # Group check
 | 
						|
        if($debug) {
 | 
						|
          print STDERR "$tag: debug: [$i] group check requested by user\n";
 | 
						|
          print STDERR "$tag: debug: [$i] \$result = \$ldap->compare('$groupdn', attr => '$groupattr', value => '$userdn')\n";
 | 
						|
        }
 | 
						|
        my $result = $ldap->compare($groupdn, attr => $groupattr, value => $userdn);
 | 
						|
        print STDERR "$tag: debug: [$i] \$result->is_error() == " . $result->is_error() . "\n" if($debug);
 | 
						|
        if($result->is_error()) {
 | 
						|
          print STDERR "$tag: error: ".$bind->error()."\n" if($verbose);
 | 
						|
          exit($result->code);
 | 
						|
        }
 | 
						|
        print STDERR "$tag: debug: [$i] \$result->code == " . $result->code . "\n" if($debug);
 | 
						|
        if($result->code == 6) { # compareTrue(6)
 | 
						|
          print STDERR "$tag: debug: [$i] user belongs to the group $group\n" if($debug);
 | 
						|
          last;
 | 
						|
        } else {
 | 
						|
          print STDERR "$tag: debug: [$i] user does not belong to the group $group\n" if($debug);
 | 
						|
          $userdn = undef;
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        last;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    $i += 1;
 | 
						|
  }
 | 
						|
  if(!defined($userdn)) {
 | 
						|
    print STDERR "$tag: error: authentication failed\n" if($verbose);
 | 
						|
    exit(1);
 | 
						|
  }
 | 
						|
} elsif($binddn && $bindpw) {
 | 
						|
  $userdn = $binddn;
 | 
						|
} else {
 | 
						|
  print STDERR "$tag: debug: no binddn, username nor search filter specified\n" if($debug);
 | 
						|
  print STDERR "$tag: error: authentication failed\n" if($verbose);
 | 
						|
  exit(1);
 | 
						|
}
 | 
						|
 | 
						|
#
 | 
						|
# Success! Do postprocessing.
 | 
						|
#
 | 
						|
print STDERR "$tag: info: successfully authenticated as '$userdn'\n" if($verbose);
 | 
						|
if(defined($print)) {
 | 
						|
  print STDERR "$tag: debug: print was requested by user\n" if($debug);
 | 
						|
  if($print =~ /%\{[a-zA-Z0-9_]+\}/) {
 | 
						|
    print STDERR "$tag: debug: print template contains placeholders -- will retrieve user attributes\n" if($debug);
 | 
						|
    if($rebind) {
 | 
						|
      if($debug) {
 | 
						|
        print STDERR "$tag: debug: rebind requested by user\n";
 | 
						|
        print STDERR "$tag: debug: \$bind = \$ldap->bind('$userdn', password => '$passwd')\n";
 | 
						|
      }
 | 
						|
      my $bind = $ldap->bind($userdn, password => $passwd);
 | 
						|
      print STDERR "$tag: debug: \$bind->is_error() == ". $bind->is_error() ."\n" if($debug);
 | 
						|
      if($bind->is_error()) {
 | 
						|
        print STDERR "$tag: debug: ".$bind->error()."\n" if($debug);
 | 
						|
      } else {
 | 
						|
        print STDERR "$tag: debug: bind successful\n" if($debug);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    my $args = {
 | 
						|
      'base' => $userdn,
 | 
						|
      'scope' => 'base',
 | 
						|
      'filter' => "objectClass=*"
 | 
						|
    };
 | 
						|
    $args->{'attrs'} = $attrs if(defined($attrs));
 | 
						|
    print STDERR "$tag: debug: \$result = \$ldap->search(" .Data::Dump::dump($args). ");\n" if($debug);
 | 
						|
    my $result = $ldap->search(%$args);
 | 
						|
    print STDERR "$tag: debug: \$result->is_error() == " . $result->is_error() . "\n" if($debug);
 | 
						|
    if($result->is_error()) {
 | 
						|
      print STDERR "$tag: error: ".$result->error()."\n" if($verbose);
 | 
						|
      exit($result->code);
 | 
						|
    }
 | 
						|
    print STDERR "$tag: debug: \$result->count() == " . $result->count() . "\n" if($debug);
 | 
						|
    if($result->count > 1) {
 | 
						|
      # Here we expect unique result, no matter what ..
 | 
						|
      print STDERR "$tag: error: ambiguous search result for dn: $userdn\n" if($verbose);
 | 
						|
      exit(1);
 | 
						|
    } elsif($result->count == 0) {
 | 
						|
      print STDERR "$tag: error: dn: $userdn not found in database\n" if($verbose);
 | 
						|
      exit(1);
 | 
						|
    }
 | 
						|
    my @entries = $result->entries();
 | 
						|
    my $userentry = @entries[0];
 | 
						|
    print STDERR "$tag: debug: substituting s/%{dn}/$userdn/gi\n" if($debug);
 | 
						|
    $print =~ s/%\{dn\}/$userdn/gi;
 | 
						|
    foreach my $attr ($userentry->attributes) {
 | 
						|
      my @values = $userentry->get_value($attr);
 | 
						|
      if($print =~ /%\{$attr\}/) {
 | 
						|
        if($debug) {
 | 
						|
          print STDERR "$tag: debug: substituting s/%{$attr}/$_/gi\n" foreach (@values);
 | 
						|
        }
 | 
						|
        # Map raw attr values with the attribute mapping
 | 
						|
        print STDERR "$tag: debug: Applying value mapping\n" if($debug);
 | 
						|
        @values = map { defined $value_map->{$_} ? $value_map->{$_} : $_ } @values;
 | 
						|
        $print =~ s/%{$attr}/join(unbackslash($separator),@values)/egi
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  print STDERR "$tag: debug: printing the requested string to stdout\n" if($debug);
 | 
						|
  print "$print\n";
 | 
						|
}
 | 
						|
exit(0);
 | 
						|
 | 
						|
__END__
 | 
						|
 | 
						|
=head1 NAME
 | 
						|
 | 
						|
openxpki-auth-ldap - Authenticate user against LDAP server.
 | 
						|
  
 | 
						|
=head1 SYNOPSIS
 | 
						|
 | 
						|
openxpki-auth-ldap B<-H> URI [options]
 | 
						|
 | 
						|
  Options:
 | 
						|
 | 
						|
    --attrs,-a attrs        user attributes to retrieve from LDAP
 | 
						|
    --base,-b searchbase    base DN for user search
 | 
						|
    --binddn,-d binddn      bind DN used to bind to LDAP directory
 | 
						|
    --binddn-env,-D name    name of environment variable providing the binddn
 | 
						|
    --bindpw,-w passwd      use this password for bind DN authentication
 | 
						|
    --bindpw-env,-W name    name of environment variable providint bindpw
 | 
						|
    --debug,-g              run in debug mode
 | 
						|
    --deref type            specify how aliases dereferencing is done
 | 
						|
    --extra-filter filter   extra filter used when searching LDAP
 | 
						|
    --filter filter         hard-coded filter used to find the user
 | 
						|
    --group,-G group        ensure that the user belongs to a group
 | 
						|
    --help                  print this help and exit
 | 
						|
    --ldapuri,-H ldapuri    URI referring to LDAP server
 | 
						|
    --multiple,-m           accept ambiguous search results
 | 
						|
    --passwd,-p passwd      password to be checked
 | 
						|
    --passwd-env,-P name    name of environment variable providing passwd
 | 
						|
    --print template        print a string specified by template
 | 
						|
    --scope,-s scope        ldap search scope: base, one, sub, children
 | 
						|
    --user,-u username      user name of the user to be authenticated
 | 
						|
    --user-attr attr        name of LDAP username attribute (default: uid)
 | 
						|
    --user-env,-U name      name of environment variable providing username
 | 
						|
    --user-filter,-u filter hard-coded ldap filter to find user in database
 | 
						|
    --verbose,-V            print errors/warnings to stderr
 | 
						|
    --separator             used with --print, set the separator for multi-valued attributes
 | 
						|
    --value-map,-k           map attribute values with another value
 | 
						|
 | 
						|
=head1 OPTIONS
 | 
						|
 | 
						|
=over 8
 | 
						|
 | 
						|
=item B<--attrs,-a> I<attr1>[,I<attr2>[,...]]
 | 
						|
 | 
						|
List of user attributes to retrieve from LDAP when B<--print> is requested.
 | 
						|
The attributes are only retrieved when B<--print> option is used and the print
 | 
						|
template contains placeholders (see B<--print>). By default all attributes are
 | 
						|
retrieved from LDAP. The B<--attrs> option may be used to limit the list of
 | 
						|
attributes being queried. The value is a comma-separated list of attribute
 | 
						|
names.
 | 
						|
 | 
						|
Example:
 | 
						|
 | 
						|
    openxpki-auth-ldap --attrs cn,gidNumber,telephoneNumber ...
 | 
						|
 | 
						|
=item B<--base,-b> I<searchbase>
 | 
						|
 | 
						|
Use I<searchbase> as starting point for user search. This is only used when
 | 
						|
B<--user>, B<--user-filter>, or B<--filter> is specified (otherwise the user
 | 
						|
DN is specified directly by B<--binddn> and the search step is not performed).
 | 
						|
 | 
						|
=item B<--binddn,-d> I<binddn>
 | 
						|
 | 
						|
Use Distinguished Name I<binddn> to bind to the LDAP directory. This option is
 | 
						|
provided for troubleshooting purposes only. You should avoid passing bind
 | 
						|
credentials via command line. In production use B<--binddn-env> instead.
 | 
						|
 | 
						|
=item B<--binddn-env,-D> I<binddn_env>
 | 
						|
 | 
						|
Use environment variable I<binddn_env> to pass bind DN to the script.
 | 
						|
 | 
						|
Example: 
 | 
						|
 | 
						|
  (export BINDDN='cn=admin,dc=example,dc=com'; openxpki-auth-ldap -D BINDDN ...)
 | 
						|
 | 
						|
=item B<--bindpw,-w> I<passwd>
 | 
						|
 | 
						|
Use I<passwd> as the password for simple authentication (for binding as
 | 
						|
binddn). This option is provided for troubleshooting purposes only. You should
 | 
						|
NEVER PASS PASSWORDS VIA COMMAND LINE. In production use B<--bindpw-env>
 | 
						|
instead.
 | 
						|
 | 
						|
=item B<--bindpw-env,-W> I<bindpw_env>
 | 
						|
 | 
						|
Use environment variable I<bindpw_env> to provide a password to be used for
 | 
						|
binding with binddn.
 | 
						|
 | 
						|
Example:
 | 
						|
 | 
						|
  (export BINDPW='secret'; openxpki-auth-ldap -W BINDPW ...)
 | 
						|
 | 
						|
=item B<--debug,-g>
 | 
						|
 | 
						|
Print debugging information to stderr.
 | 
						|
 | 
						|
=item B<--deref> I<type>
 | 
						|
 | 
						|
Specify how aliases dereferencing is done. The I<type> should be one of never,
 | 
						|
always, search, or find to specify that aliases are never dereferenced, always
 | 
						|
dereferenced, dereferenced when searching, or dereferenced only when locating
 | 
						|
the base object for the search. The default is to never dereference aliases.
 | 
						|
 | 
						|
=item B<--extra-filter> I<filter>
 | 
						|
 | 
						|
For use with --user or --user-filter. When constructing a search filter for
 | 
						|
user search it may be composed of two elements. The first is user_filter, which
 | 
						|
by assumption only places restrictions on user identifiers (e.g. uid). The
 | 
						|
second part called extra_filter may be arbitrary and provides additional,
 | 
						|
custom restrictions. The two parts: the B<--user-filter> and B<--extra-filter>
 | 
						|
are combined by the B<and> operator. This flag conflicts with B<--filter>.
 | 
						|
 | 
						|
Example:
 | 
						|
 | 
						|
  The flags:
 | 
						|
 | 
						|
     --user-filter 'uid=jsmith' --extra-filter 'accountStatus=active'"
 | 
						|
 | 
						|
  will result with the search filter:
 | 
						|
 | 
						|
     '(&(uid=jsmith)(accountStatus=active))'
 | 
						|
 | 
						|
=item B<--filter,-f> I<filter>
 | 
						|
 | 
						|
Hard-coded filter for user search. This flag conflicts with --user,
 | 
						|
--user-filter, and --extra-filter.
 | 
						|
 | 
						|
=item B<--group,-G> I<group>
 | 
						|
 | 
						|
Ensure that user belongs to a given group. The I<group> parameter specifies an
 | 
						|
entry containing Distinguished Names of users belonging to the group. The
 | 
						|
format of I<group> argument is:
 | 
						|
 | 
						|
   [attr/]dn
 | 
						|
 | 
						|
where C<attr> is the name of attribute collecting DNs of the group participants
 | 
						|
and C<dn> is a Distinguished Name of the group entry. If C<attr> is not
 | 
						|
provided, the default attribute C<'member'> is assumed.
 | 
						|
 | 
						|
 | 
						|
Example:
 | 
						|
 | 
						|
    # Let's say, we have the following group definition in LDAP:
 | 
						|
    #
 | 
						|
    # dn: cn=vip,dc=example,dc=org
 | 
						|
    # cn: pm-users
 | 
						|
    # objecClass: top
 | 
						|
    # objecClass: organizationalRole
 | 
						|
    # roleOccupant: uid=jsmith,ou=people,dc=example,dc=org
 | 
						|
    # roleOccupant: uid=pbrown,ou=people,dc=example,dc=org
 | 
						|
    #
 | 
						|
    # Then we may restrict authentication to this group participants only
 | 
						|
    # by using the following syntax:
 | 
						|
    
 | 
						|
    openxpki-auth-ldap -G "roleOccupant/cn=vip,dc=example,dc=org" ...
 | 
						|
      
 | 
						|
=item B<--help>
 | 
						|
 | 
						|
Print this help message.
 | 
						|
 | 
						|
=item B<--ldapuri,-H> I<ldapuri>
 | 
						|
 | 
						|
Specify URI referring to the ldap server; only the protocol/host/port fields
 | 
						|
are allowed.  
 | 
						|
 | 
						|
=item B<--multiple,-m>
 | 
						|
 | 
						|
Accept ambiguous search results. If the user search results with multiple
 | 
						|
entries, the script will try to bind to each of them (in order) untill first
 | 
						|
successful bind. By default ambiguous search results are discarded, that is
 | 
						|
they're reported as error.
 | 
						|
 | 
						|
=item B<--starttls,-t>
 | 
						|
 | 
						|
Upgrade the connection using Start TLS. If set, Start TLS is ran just after the
 | 
						|
connection is established, before any bind or search operation.
 | 
						|
 | 
						|
=item B<--passwd,-p> I<passwd>
 | 
						|
 | 
						|
Provide password for user (specified with B<--user>, B<--user-env>,
 | 
						|
B<--user-filter> or B<--filter>). This option is provided for troubleshooting
 | 
						|
purposes only. You should NEVER PASS PASSWORDS VIA COMMANDLINE in production.
 | 
						|
Instead, you should use B<--passwd-env>.
 | 
						|
 | 
						|
=item B<--passwd-env,-P> I<passwd_env>
 | 
						|
 | 
						|
Specify name of an environment variable carrying password for the user being
 | 
						|
authenticated (specified with either --user or --user-env).  
 | 
						|
 | 
						|
Example:
 | 
						|
 | 
						|
  (export PASSWD=secret; openxpki-auth-ldap --passwd-env PASSWD ...)
 | 
						|
 | 
						|
=item B<--print> I<template>
 | 
						|
 | 
						|
Print a string specified by I<template>. On successful authentication a string
 | 
						|
specified by I<template> will be printed to stdout. The template may contain
 | 
						|
placeholders in form C<%{attrName}>, where the C<attrName> is the name of LDAP
 | 
						|
attribute. The C<attrName> should be a name of attribute returned by user
 | 
						|
search (it should be included in B<--attrs> list, if B<--attrs> option is
 | 
						|
specified).
 | 
						|
 | 
						|
=item B<--scope,-s> {base|one|sub|children}
 | 
						|
 | 
						|
Specify the scope of the search to be one of base, one, sub, or children to
 | 
						|
specify a base object, one-level, subtree, or children search. The default is
 | 
						|
sub. Note: children scope requires LDAPv3 subordinate feature extension.
 | 
						|
 | 
						|
=item B<--user,-u> I<username>
 | 
						|
 | 
						|
Specify the user to be authenticated. This is usually a login/uid of the user
 | 
						|
to be found in LDAP and authenticated. This option is provided for
 | 
						|
troubleshooting purposes only. You should avoid passing user names via command
 | 
						|
line. In production you should use B<--user-env> instead.
 | 
						|
 | 
						|
=item B<--user-atttr> I<attr>
 | 
						|
 | 
						|
Name of LDAP attribute used for user name (default is: uid). The I<attr> is
 | 
						|
used together with the value of B<--user> I<username> option. When searching
 | 
						|
the LDAP directory for I<username>, the I<username> is compared to the values
 | 
						|
of I<attr> by the LDAP server.
 | 
						|
 | 
						|
=item B<--user-env,-U> I<user_env>
 | 
						|
 | 
						|
Specify name of an environment variable carrying username of the user that is
 | 
						|
to be authenticated.
 | 
						|
 | 
						|
Example:
 | 
						|
 | 
						|
  (export LOGIN=jsmith; openxpki-auth-ldap --passwd-env LOGIN ...)
 | 
						|
 | 
						|
=item B<--user-filter> I<filter>
 | 
						|
 | 
						|
Hard-code filter used to search users in LDAP database. Note, that
 | 
						|
B<--extra-filter>, if specified, will be appended to this user filter.
 | 
						|
 | 
						|
Example:
 | 
						|
 | 
						|
  openxpki-auth-ldap --user-filter 'uid=jsmi*'
 | 
						|
 | 
						|
=item B<--verbose,-V>
 | 
						|
 | 
						|
Print errors to stderr. Normally the script runs in quiet mode and only returns
 | 
						|
success/failure status to shell without printing anything anywhere. With
 | 
						|
B<--verbose> it outputs error/warning messages to stderr.
 | 
						|
 | 
						|
=item B<--separator>
 | 
						|
 | 
						|
Used with --print. If --print contains an attribute wich has multiple values,
 | 
						|
separates them with this string. Default is \n
 | 
						|
 | 
						|
=item B<--value-map,-k>
 | 
						|
 | 
						|
Used with --print. Lets you map raw attribute values with other values. Can be
 | 
						|
specified several times
 | 
						|
 | 
						|
=back	
 | 
						|
 | 
						|
=head1 DESCRIPTION
 | 
						|
 | 
						|
Authenticate users against LDAP server. The script is intended to be used in
 | 
						|
other scripts as an external authentication source.
 | 
						|
 | 
						|
Currently it allows for simple authentication (LDAP bind, SASL is not supported
 | 
						|
yet). The user to be authenticated may be specified by its Distinguished Name
 | 
						|
or the LDAP directory may be searched for the user.
 | 
						|
 | 
						|
All the options necessary to establish connection, find the user and perform
 | 
						|
the authentication are provided via command line and environment variables.
 | 
						|
Sensitive data (logins, passwords) should be passed via (temporary) environment
 | 
						|
variables (command line may be easilly disclosed by users having access to the
 | 
						|
operating system running the script).
 | 
						|
 | 
						|
=head1 EXAMPLES
 | 
						|
 | 
						|
    # Aanonymous bind. This always fails, even if the bind itself is successful.
 | 
						|
    openxpki-auth-ldap -g -V -H ldap://ldap.example.org 
 | 
						|
 | 
						|
    # Try to bind to uid=jsmith,ou=people,dc=example,dc=org using "secret" as
 | 
						|
    # the password.
 | 
						|
    (
 | 
						|
      export BINDDN="cn=jsmith,ou=people,dc=example,dc=org";
 | 
						|
      export BINDPW="secret";
 | 
						|
      openxpki-auth-ldap -g -V -H ldaps://ldap.example.org -D BINDDN -W BINDPW;
 | 
						|
    )
 | 
						|
 | 
						|
    # Try to authenticate user 'jsmith' with password 'secret'.
 | 
						|
    # This will try to anonymously bind to LDAP directory and then search for
 | 
						|
    # 'uid=jsmith' starting from base "ou=people,dc=example,dc=org".
 | 
						|
    # If 'jsmith' is found, the script will try to bind to its entry using
 | 
						|
    # the provided password.
 | 
						|
    (
 | 
						|
      export LOGIN="jsmith";
 | 
						|
      export PASSWD="secret";
 | 
						|
      openxpki-auth-ldap -g -V -H ldap://ldap.example.org -U LOGIN -P PASSWD \
 | 
						|
                         -b "ou=people,dc=example,dc=org";
 | 
						|
    )
 | 
						|
 | 
						|
    # Try to authenticate user 'jsmith' with password 'secret'.
 | 
						|
    # This will try to bind as "cn=admin,dc=example,dc=org" to LDAP directory
 | 
						|
    # and then search for 'uid=jsmith' starting from default base. If 'jsmith'
 | 
						|
    # is found, the script will try to bind to its entry using the provided
 | 
						|
    # password.
 | 
						|
    (
 | 
						|
      export BINDDN="cn=jsmith,ou=people,dc=example,dc=org";
 | 
						|
      export BINDPW="secret";
 | 
						|
      export LOGIN="jsmith";
 | 
						|
      export PASSWD="secret";
 | 
						|
      openxpki-auth-ldap -g -V -H ldap://ldap.example.org -D BINDDN -W BINDPW \
 | 
						|
                         -U LOGIN -P PASSWD -b "ou=people,dc=example,dc=org";
 | 
						|
    )
 | 
						|
 | 
						|
=cut
 |