#!/usr/bin/perl
#
# AUTHPROTOCOL: STDIN
#
use strict;
use Getopt::Std;

our(%opts);
getopts('hvc:', \%opts);

print STDERR "Give <username> <password>:\n" if ! -p STDIN;

my $firstline  = <STDIN>;
my $secondline = <STDIN>;
chomp $firstline;
chomp $secondline;

my ($uid,$pass);
if (!$secondline) {
    # AUTHPROTO: STDIN
    ($uid,$pass) = split /\s+/,$firstline,2;
}
else {
    # AUTHPROTO: STDIN2
    $uid = $firstline;
    $pass = $secondline;
}

$opts{'h'} && help_exit();
my $configfile  = $ENV{CONFIG}  || $opts{'c'} || '/etc/benno-web/ldapauth.conf';
my $verbose     = $ENV{VERBOSE} || $opts{'v'};
my $DEBUG       = $ENV{DEBUG};  # LDAP DEBUG LEVEL

if (!$pass) {
    print "ERROR ERR_NOPASS\n";
    print STDERR "Aufruf: $0 <username> <passwort>\n";
    exit 1;
}

print STDERR "Read config: $configfile\n" if $verbose;
my $config = read_config($configfile);

if ($config->{remove_domainsuffix} eq 'true') {
    $uid =~ s/\@.+$//;
}

my $LDAP;
if ($config->{usersuffix} and not $config->{userfilter}) {
    # search not necessary, thus overwrite settings with current user data
    $config->{binddn}   = "$config->{userattr}=$uid,$config->{usersuffix}";
    $config->{password} = $pass;
}
if (!$config->{binddn}) {
    print STDERR "Cannot connect LDAP server without binddn. Exit.\n";
    exit 1;
}
print STDERR "Connect to $config->{host} with: $config->{binddn}\n" if $verbose;
$LDAP = LWs::LDAP->new($config);

my $ret;
my $UserEntry = $LDAP->get_user($uid,$config->{userfilter});
if ($UserEntry) {
    my $dn = $UserEntry->dn();
    print STDERR "Bind LDAP as: $dn\n" if $verbose;
    $ret = $LDAP->user_auth($dn,$pass);
}

if ($ret !~ /^OK/) {
    print "ERROR ERR_AUTH\n";
    exit;
}
my @email     = $UserEntry->get_value($config->{email});
my @alias     = $UserEntry->get_value($config->{alias});
my @addemail  = get_list($config->{addemail});
my @addresses = (@email, @alias, @addemail);

@addresses = format_addresslist(@addresses);

my $role      = uc $UserEntry->get_value($config->{role}) || 'USER';

# overwrite role of admin user
foreach my $adminid (get_list($config->{adminuser})) {
    if (lc($uid) eq lc($adminid)) {
        $role = 'ADMIN';
    }
}

if ($role eq 'ADMIN' || $role eq 'REVISOR') {
    push @addresses, '*@*';
}

my @containers;
unless(@containers = $UserEntry->get_value($config->{container})) {
    @containers = ($config->{default_container} || 'BennoContainer');
}

print "ROLE $role\n";
print 'DISPLAYNAME '.$UserEntry->get_value('cn')."\n";

foreach my $address (@addresses) {
    print "MAIL $address\n";
}
foreach my $container (@containers) {
    print "ARCHIVE $container\n";
}


### SUBS #######################################################################
sub help_exit
{
    print "Usage: $0 [-h] [-v] [-c <configfile>]\n";
    print "\n";
    print "  -c <configfile>    Configfile (default /etc/benno-web/ldapauth.conf)\n";
    print "  -v                 Verbose output\n";
    print "  -h                 This help\n";

    exit;
}


# read_config
sub read_config
{
  my $configfile = shift;
  my $config = {
      userattr              => 'uid',
      objectclass           => 'posixAccount',
      tls                   => 'false',
      email                 => 'mail',
      alias                 => 'emailAlias',
      addemail              => 'bennoEmailAddress',
      role                  => 'bennoRole',
      container             => 'bennoContainer',
      default_container     => 'BennoContainer',
      adminuser             => 'benno',
      remove_domainsuffix   => 'false',
    };
  # _very_ simple config file parser
  #
  # Config format:   var = val
  #
  open CONF, "$configfile" or die "Cannot open config file $configfile. $!\n";
  foreach my $line (<CONF>) {
      next if $line =~ /^$/;
      next if $line =~ /^#/;
      chomp $line;
      my ($var,$val) = split(/=/, $line,2);
      # strip ws
      $var =~ s/\s//g;
      $val =~ s/^\s+//g;
      $val =~ s/\s+$//g;
      $config->{$var} = $val;
  }
  close CONF;
  return $config;
}

# get_list
sub get_list
{
    my ($entry) = @_;

    return split /,(\s+)?/,$entry;
}


# format_addresslist
sub format_addresslist
{
    my @addresslist = @_;

    my $address;
    my %return_addresses;

    foreach my $token (@addresslist) {
        # strip MS Exchange smtp: prefix
        $token =~ s/^smtp:\s*//i;
        # extract plain address
        #                        (emailprefix|<empty>)@domain
        ($address) = $token =~ /(([a-zA-Z0-9_\*\.+-]+|)\@[a-zA-Z0-9-\.]+)/;
        next if $address eq '';

        # delete duplicates
        $return_addresses{$address} = 1;
    }
    # delete duplicates
    return (sort keys %return_addresses);
}


### EOP ###
1;
################################################################################
package LWs::LDAP;

use Net::LDAP;
use Carp;


=head1 NAME

LDAP

=head1 SYNOPSIS

    use C<LWS::LDAP>;

=head1 DESCRIPTION

=head1 METHODS

=head2 new($config)

Create new LWs::LDAP object and bind to ldap directory.

Throws exception if failed.

=cut
sub new
{
    my $this = shift;
    my $class = ref($this) || $this;
    my ($config) = shift;

    my $self = {};
    bless $self, $class;

    $self->{host}     = $config->{host}     || 'localhost';
    $self->{basedn}   = $config->{basedn}   || 'dc=site';
    $self->{binddn}   = $config->{binddn};
    $self->{password} = $config->{password};
    $self->{userattr} = $config->{userattr} || 'uid';
    $self->{objectclass} = $config->{objectclass} || 'posixAccount';

    $self->_connect_ldap();
    if ($config->{tls} eq 'true') {
        $self->{_LDAP}->start_tls(verify => 'none');
    }

    return $self;
}


# connect_ldap
sub _connect_ldap
{
    my $self = shift;
    my ($config) = shift;

    my $LDAP = Net::LDAP->new($self->{host}) or croak "$@";
    $DEBUG && $LDAP->debug($DEBUG);
    my $mesg;
    if ($self->{binddn}) {
        $mesg = $LDAP->bind($self->{binddn},'password' => $self->{password});
    }
    else {
        $mesg = $LDAP->bind();      # anonymous bind
    }
    $mesg->code and croak $mesg->error;
    $self->{_LDAP} = $LDAP;
}


# get_user($userid)
#
# returns a Net::LDAP::Entry object of the user
sub get_user
{
    my $self = shift;
    my $userid = shift;
    my $userfilter = shift;

    my $filter;
    if ($userfilter) {
        $filter = $userfilter;
    }
    else {
        $filter = "(&(objectClass=$self->{objectclass})($self->{userattr}=%s))";
    }

    my ($localpart,$domainpart) = $userid =~ /^(.+)\@(.+)$/;
    $filter =~ s/%u/$localpart/g;
    $filter =~ s/%d/$domainpart/g;
    $filter =~ s/%s/$userid/g;
    print "LDAP Search: \"$filter\"\n" if $verbose;
    my $mesg = $self->{_LDAP}->search( base   => $self->{basedn},
                                       filter => $filter
                                     );
    if ($mesg->code) {
        print STDERR "Error search \"$filter\": ",$mesg->error,".\n";
    }

    if ($mesg->count < 1) {
        print STDERR "No match for \"$filter\" in directory.\n";
    }

    if ($mesg->count > 1) {
        croak "Userid not uniqe at ldap directory.\n";
    }
    return $mesg->entry(0);
}

# user_auth($userdn,$userpass)
#
#
sub user_auth
{
    my $self = shift;
    my ($userdn,$userpass) = @_;
    my $mesg = $self->{_LDAP}->bind($userdn,'password' => $userpass);
    if ($mesg->code) {
        return 'ERROR AUTHENTICATION ERROR';
    }

    return 'OK';
}

### EOP ###
1;
