#! /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
