mirror of
				https://git.lapiole.org/dani/ansible-roles.git
				synced 2025-10-27 00:41:35 +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
 | 
