<?php
//
// @(#)$Id$
//
// nikEmployeeNumbers (nikENlib) retrieval and manipulation of employee numbers
// from the Nikhef LDAP directory
// for proper functioning it MUST connect to LDAP, and the password MUST
// be configured in an external file (see config section below)
//
// this module exports the following functions
//
// function nikENfindEmployeeNumberByUid($uid) 
//   retrieve the employee number of user <uid>
// function nikENfindUidByEmployeeNumber($employeeNumber)
//   retrieve the uid for employee <number>
// function nikENfindCNByEmployeeNumber($employeeNumber)
//   retrieve the friendly common name for employee <number>
// function nikENfindCNByUid($uid)
//   retrieve the friendly common name for user <uid>
// function nikENlistAllElegibleUids()
//   get a list of all <uids> that match the LDAP filter
// function nikENlistAllElegibleEmployeeNumbers()
//   get a list of all <employee numbers> that match the LDAP filter
//
// If you want to do bulk-operations (such as reporting) it is faster
// to first cache *all* of the possible entries. This is done implicitly
// by the "nikENlistAllElegible*" functions, but it may be called 
// explicitly via
// function nikENcacheLdap()
//   retrieve all relevant entries from LDAP and cache in memory
//
// NOTES
//   This library is designed to work with the NikIdM system, and may
//   not be suitable for any other purpose (or even for any purpose)
//
// FILES /usr/local/etc/nikENconfig.conf
//   a <attribute> <value> file with the following permitted directives
//   "(binddn|bindpw|ldapurl|ldapdit|ldapfilter)"A
//   The default LDAP filter for finding elegible employees is
//     (&(employeeNumber=*)(eduPersonAffiliation=employee))
//   and any filter MUSt be enclosed in brackets
//
// BUGS
//   may be present, exterminate them with DDT
// 
// ---------------------------------------------------------------------------
// static configuration, override via config file
$nikENconfigFile="/usr/local/etc/nikENconfig.conf";
$nikENbindDN="cn=agent-mortal-urenreg,ou=Managers,dc=farmnet,dc=nikhef,dc=nl";
$nikENbindPassword="USE_CONFIG_FILE_FOR_THIS_PLEASE";
$nikENldapUrl="ldaps://ldap.nikhef.nl/";
$nikENldapDIT="ou=LocalUsers,dc=farmnet,dc=nikhef,dc=nl";
$nikENldapFilter="(&(employeeNumber=*)(eduPersonAffiliation=employee))";
$nikENtesting=0;

// ---------------------------------------------------------------------------
// base code
if ( !file_exists($nikENconfigFile) ) {
  error_log("nikEmployeeNumber: invoked, but no config file found");
  exit(1);
}
if ( !$fh = fopen($nikENconfigFile,"r") ) {
  error_log("nikEmployeeNumber: cannot open config file $nikENconfigFile");
  exit(1);
}
while (!feof($fh)) {
  $nikENcLine = fgets($fh,8192);
  if ( $nikENcLine === false ) break;
  rtrim($nikENcLine);
  if (!preg_match("/^(binddn|bindpw|ldapurl|ldapdit|ldapfilter)\s+(.*)$/",
    $nikENcLine,$nikENcm)) {
      error_log("nikEmployeeNumber: syntax error in config file");
      exit(1);
  }
  if ( $nikENcm[1] == "binddn" ) $nikENbindDN=$nikENcm[2];
  if ( $nikENcm[1] == "bindpw" ) $nikENbindPassword=$nikENcm[2];
  if ( $nikENcm[1] == "ldapurl" ) $nikENldapUrl=$nikENcm[2];
  if ( $nikENcm[1] == "ldapdit" ) $nikENldapDIT=$nikENcm[2];
  if ( $nikENcm[1] == "ldapfilter" ) $nikENldapFilter=$nikENcm[2];
}
fclose($fh);

if ( !preg_match("/^\(.*=.*\)$/",$nikENldapFilter) ) {
  error_log("nikENlib: LDAP filter expression invalid"); exit(1);
}
if ( !preg_match("/^ldaps?:\/\/\w+\.\w+.*\/.*$/",$nikENldapUrl) ) {
  error_log("nikENlib: LDAP server URL invalid syntax"); exit(1);
}
if ( !preg_match("/^\w+=.*,\w+=.*$/",$nikENldapDIT) ) {
  error_log("nikENlib: LDAP DIT invalid syntax"); exit(1);
}
if ( !preg_match("/^\w+=.*,\w+=.*$/",$nikENbindDN) ) {
  error_log("nikENlib: LDAP bind DN invalid syntax"); exit(1);
}

// ---------------------------------------------------------------------------
// public API function
//
function nikENfindEmployeeNumberByUid($uid) {
  global $nikENemplNoByUid,$nikENUidByEmplNo,$nikENCNByUid;
  if (!preg_match("/[a-z0-9]{1,16}/",$uid)) {
    error_log("nikENfindEmployeeNumberByUid invoked with non-alpha ".
              "or too long argument");
    exit(1);
  }
  if ( isset($nikENemplNoByUid[$uid]) ) return $nikENemplNoByUid[$uid];
  if ( ! nikENldapRetrieve("uid",$uid) ) return false;
  return $nikENemplNoByUid[$uid];
}

function nikENfindUidByEmployeeNumber($employeeNumber) {
  global $nikENemplNoByUid,$nikENUidByEmplNo,$nikENCNByUid;
  if (!preg_match("/\d+/",$employeeNumber)) {
    error_log("nikENfindUidByEmployeeNumber invoked with non-numeric argument");
    exit(1);
  }
  if ( isset( $nikENUidByEmplNo[$employeeNumber]) ) 
    return $nikENUidByEmplNo[$employeeNumber];
  if ( ! nikENldapRetrieve("employeenumber",$employeeNumber) ) return false;
  return $nikENUidByEmplNo[$employeeNumber];
}

function nikENfindCNByEmployeeNumber($employeeNumber) {
  global $nikENemplNoByUid,$nikENUidByEmplNo,$nikENCNByUid;
  if (!preg_match("/\d+/",$employeeNumber)) {
    error_log("nikENfindUidByEmployeeNumber invoked with non-numeric argument");
    exit(1);
  }
  if ( isset( $nikENUidByEmplNo[$employeeNumber]) ) 
    return $nikENUidByEmplNo[$employeeNumber];
  if ( ! nikENldapRetrieve("employeenumber",$employeeNumber) ) return false;
  return $nikENCNByUid[$nikENUidByEmplNo[$employeeNumber]];
}

function nikENfindCNByUid($uid) {
  global $nikENemplNoByUid,$nikENUidByEmplNo,$nikENCNByUid;
  if (!preg_match("/[a-z0-9]{1,16}/",$uid)) {
    error_log("nikENfindEmployeeNumberByUid invoked with non-alpha ".
              "or too long argument");
    exit(1);
  }
  if ( isset( $nikENCNByUid[$uid]) ) 
    return $nikENCNByUid[$uid];
  if ( ! nikENldapRetrieve("uid",$uid) ) return false;
  return $nikENCNByUid[$uid];
}

function nikENlistAllElegibleUids() {
  global $nikENemplNoByUid,$nikENUidByEmplNo,$nikENCNByUid;
  nikENcacheLdap();
  return array_keys($nikENemplNoByUid);
}

function nikENlistAllElegibleEmployeeNumbers() {
  global $nikENemplNoByUid,$nikENUidByEmplNo,$nikENCNByUid;
  nikENcacheLdap();
  return array_keys($nikENUidByEmplNo);
}

// ---------------------------------------------------------------------------
// Internal support functions
//
function nikENldapInit() {
  global $nikENbindDN,$nikENbindPassword,$nikENldapUrl;
  global $nikENds;

  if ( ! ($nikENds = ldap_connect($nikENldapUrl) ) ) {
    error_log("nikEmployeeNumber: cannot connect to LDAP server");
    return false;
  }
  if ( ! ($bindresult = ldap_bind($nikENds,$nikENbindDN,$nikENbindPassword) )) {
    error_log("nikEmployeeNumber: cannot bind to LDAP as agent");
    return false;
  }
  return true;
}

function nikENcacheLdap() {
  global $nikENds;
  global $nikENldapDIT,$nikENldapFilter;
  global $nikENemplNoByUid;
  global $nikENUidByEmplNo;
  global $nikENCNByUid;

  if ( isset($nikENCNbyUid) ) return true;

  if ( !isset($nikENds) ) {
    if (!nikENldapInit()) return false;
  }

  $searchresult = ldap_search($nikENds,$nikENldapDIT,$nikENldapFilter,
    array("uid","cn","employeeNumber"));
  if ( !$searchresult ) {
    error_log("nikEmployeeNumber: nikENcacheLdap cannot search directory");
    return false;
  }

  $gentries = ldap_get_entries($nikENds, $searchresult);
  for ( $i=0 ; $i < $gentries["count"] ; $i++) {
    // skip this entry unless we have some actual useful data
    if ( !isset($gentries[$i]["cn"][0]) || !$gentries[$i]["cn"][0] ) continue;
    if ( !isset($gentries[$i]["uid"][0]) || !$gentries[$i]["uid"][0] ) continue;
    if ( !isset($gentries[$i]["employeenumber"][0]) || !$gentries[$i]["employeenumber"][0] ) continue;

    $nikENUidByEmplNo[$gentries[$i]["employeenumber"][0]] = 
      $gentries[$i]["uid"][0];
    $nikENemplNoByUid[$gentries[$i]["uid"][0]] = 
      $gentries[$i]["employeenumber"][0];
    $nikENCNByUid[$gentries[$i]["uid"][0]] = $gentries[$i]["cn"][0];
  }

  ldap_close($nikENds);
  unset($nikENds);

  return true;
}

function nikENldapRetrieve($attr,$value) {
  global $nikENds,$nikENldapDIT,$nikENldapFilter;
  global $nikENemplNoByUid,$nikENUidByEmplNo,$nikENCNByUid;

  if ( !preg_match("/^(cn|uid|employeenumber)$/",$attr) ) {
    error_log("nikENldapRetrieve invoked with invalid attribute name");
    return false;
  }

  if ( !isset($nikENds) ) nikENldapInit();
  if ( !isset($nikENds) ) return false;

  $searchresult = ldap_search($nikENds,$nikENldapDIT,
    "(&($attr=$value)$nikENldapFilter)",array("uid","cn","employeeNumber"));
  if ( !$searchresult ) {
    error_log("nikEmployeeNumber: nikENldapRetrieve cannot search directory for $attr");
    return false;
  }

  $gentries = ldap_get_entries($nikENds, $searchresult);
  // skip this entry unless we have some actual useful data
  if ( !isset($gentries) || $gentries["count"] != 1 ) return false;
  if ( !isset($gentries[0]["cn"][0]) || !$gentries[0]["cn"][0] ) return false;
  if ( !isset($gentries[0]["uid"][0]) || 
       !$gentries[0]["uid"][0] ) return false;
  if ( !isset($gentries[0]["employeenumber"][0]) || 
       !$gentries[0]["employeenumber"][0] ) return false;

  $nikENUidByEmplNo[$gentries[0]["employeenumber"][0]] = 
    $gentries[0]["uid"][0];
  $nikENemplNoByUid[$gentries[0]["uid"][0]] = 
    $gentries[0]["employeenumber"][0];
  $nikENCNByUid[$gentries[0]["uid"][0]] = $gentries[0]["cn"][0];

  return true;
}



// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Examples and unit tests
//

if ( $nikENtesting ) {
  print "<html><head><title>Testing nikEN</title></head>\n<body>\n<pre>\n";
  print "employeeNumber of davidg is ".nikENfindEmployeeNumberByUid("davidg")."\n";
  print "Name of davidg is ".nikENfindCNByUid("davidg")."\n";
  print "Name of employee 35470 is ".nikENfindCNByEmployeeNumber(35470)."\n";

  print "The following people are elegible to urenreg:\n";
  foreach ( nikENlistAllElegibleUids() as $k ) {
    print "  $k\n";
  }

  print "\n</pre>\n</body></html>\n";
}
