#!/usr/bin/perl -w

use strict;
use Net::LDAPS;
use Net::LDAP::Constant qw(LDAP_SUCCESS);
use Sys::Syslog qw(:standard :macros);
use Net::LDAP::Util qw(escape_filter_value);


my $prg_name = $0;
$prg_name =~ s/.*\///;

my $ldap_host = 'localhost';
my $ldap_port = 636;

my $pam_user = 'PAM_USER';
my $pam_type = 'PAM_TYPE';
my $pam_password = 'PAM_AUTHTOK';

sub syslog_escape {
  my $str = shift;
  my @chr = split(//, $str);

  for(my $i=0; $i<@chr; $i++) {
    if (ord($chr[$i])>127) {
      $chr[$i] = sprintf('\0x%X', ord($chr[$i]));
    };
  };

  return join('', @chr);
};

sub logger {
  my $priority = shift;
  my $msg = shift;

  openlog($prg_name, 'pid', LOG_LOCAL0);
  syslog($priority, syslog_escape($msg));
  closelog;
};

sub local_die {
  logger(LOG_ERR, @_);
  die;
};

sub log_pam_env {
  my @out;

  foreach my $key (keys %ENV) {
    next unless ($key =~ /^PAM_/);

    if ($key eq 'PAM_AUTHTOK') {
      if (exists $ENV{$key}) {
	if ($ENV{$pam_password} eq '') {
	  push @out, "$key=";
        } else {
	  push @out, "$key=*hidden*" ;
	};
      };
    } else {
      push @out, "$key=".$ENV{$key};
    };
  };

  logger(LOG_ERR, 'PAM env: '.join(' ', @out));
};

# Log all PAM_* env variables we got from LDAP server
log_pam_env();

my $ldaps = Net::LDAPS->new($ldap_host,
			    port => $ldap_port) or die "$@";
my $conn = $ldaps->bind;    # TODO an anonymous bind


unless ($ENV{$pam_user}) {
  local_die "Missing $pam_user in environment";
};

unless ($ENV{$pam_type}) {
  local_die "Missing $pam_type in environment";
};

unless ($ENV{$pam_password}) {
  if ($ENV{$pam_type} eq 'auth') {
    local_die "Missing $pam_password in environment";
  };
};

my $filter = '(objectClass=appPassword)';
if ($ENV{$pam_type} eq 'auth') {
  $filter = "(&$filter(altUserPassword=".escape_filter_value($ENV{$pam_password})."))";
};
logger(LOG_ERR, $filter);
my $mesg = $ldaps->search( # perform a search
			  base   => $ENV{$pam_user},
			  filter => $filter,
			 );

if ($mesg->code == LDAP_SUCCESS) {
  foreach my $entry ($mesg->entries) {
    # todo pridat popisku hesla ktera matchla
    logger(LOG_ERR, 'Matched: '.$entry->dn);
    exit 0;
  };
  local_die('Invalid password.');
} else {
  local_die $mesg->error;
};

# K tomuhle bychom se nikdy nemeli dostat
local_die('This should not happen.');
