<?php
// $Id$

/**
 * @file
 * ldapsync keeps LDAP and Drupal user lists synchronized.
 *
 * for testing purposes only, go to /ldapsync to execute the synchronization
 * in the future, this will run on cron or via a button on the (forthcoming) settings page
 */


/**
 * Implements hook_init().
 */
function ldapsync_init() {
  include_once(drupal_get_path('module', 'ldapsync') .'/ldap_integration/libdebug.php');
  require_once(drupal_get_path('module', 'ldapsync') .'/ldap_integration/LDAPInterface.php');
}


/**
 * Implementation of hook_help().
 */
function ldapsync_help($path, $arg) {
  $output = '';
  switch ($path) {
    case "admin/help#ldapsync":
      $output = '<p>'.  t("Searches LDAP to update Drupal user membership and information.") .'</p>';
      break;
  }
  return $output;
}


function ldapsync_menu() {
  $items = array();

  // this path just for testing -- will make into a "sync now" form button in the settings page
  $items['ldapsync'] = array(
    'title' => 'Search LDAP for testing',
    'page callback' => 'ldapsync_sync',
    'type' => MENU_CALLBACK,
    'access arguments' => array('administer ldap modules'),
  );
  return $items;
}


/**
 * Implements hook_cron().
 */
function ldapsync_cron() {
  // will call ldapsync through cron once it's tested (probably once each night)
}


/**
 * Main routine
 */
function ldapsync_sync() {

  global $_ldapsync_ldap;

  // load faculty information from Education Edge -- EE id, username, email, real name, title department, phone number
  $faculty = educationedge_get_faculty_by_username();

  // find all users in specified OU (using base DN and bind information from ldapauth)
  $ldap_users = ldapsync_search();

  // cycle through LDAP users and take appropriate action on the Drupal side
  $count_new_users=0; $count_updated_users=0; $count_disabled_users=0;
  foreach (array_keys($ldap_users) as $name) {

    // LDAP object must be a named account
    if (!$name) continue;

    // check whether user is in an OU mapped in module settings (need to create admin/settings/ldapsync page)
    $dn = $ldap_users[$name]['dn'];

    // does user exist in Drupal (find by username)? If not, create it (using process in ldapauth).
    $account = user_load(array('name' => $name));
    if (!$account->uid) {

        // user does not exist in Drupal. Let's create it. (any reason not to?)

        // create this user in Drupal
        $pass = user_password(20);  // generate a random password (Drupal will auth against ldap anyway)
        $mail = $ldap_users[$name]['mail'];
        $init = $mail = key_exists(($_ldapsync_ldap->getOption('mail_attr') ? $_ldapsync_ldap->getOption('mail_attr') : LDAPAUTH_DEFAULT_MAIL_ATTR), $ldap_users[$name]) ? $ldap_users[$name][$_ldapsync_ldap->getOption('mail_attr')] : $name;  // not sure what $init is for, borrowed from ldapauth
        $userinfo = array('name' => $name, 'pass' => $pass, 'mail' => $mail, 'init' => $init, 'status' => 1, 'authname_ldapauth' => $name, 'ldap_authentified' => TRUE, 'ldap_dn' => $ldap_users[$name]['dn'], 'ldap_config' => $_ldapsync_ldap->getOption('sid'));
        // comment out the following line if you're just testing and don't yet want to create the new user.
        $user = user_save('', $userinfo);
        // uncomment the following line if you're just testing and want to see the properties of the users that ldapsync wants to create
        // $page_content .= "<p>Create user ". $name ." ". $pass ." ". $mail ." ". $init ."<br />". $ldap_users[$name]['dn'] ."</p>";
        $count_new_users++;

    }
    else {

        // user exists in Drupal -- check a few things, but most users will require no further action

        // check authentication method
        $data = unserialize($account->data);
        if (!$data['ldap_authentified']) {
          // user exists as a local Drupal account -- name conflict! -- log and stop processing this user
          watchdog('ldapsync', 'Could not create ldap-authentified account for user '. $name .' because a local user by that name already exists.');
          continue;
        }

        // check for accuracy of extended profile fields and update if different (need a pref for this, will conflict with user-controlled profile fields)
        // examples include title, department, phone number -- implement later

        // is user disabled in LDAP? If so, disable in Drupal. This is how it works in AD. How about other LDAP services?
        if ($ldap_users[$name]['status'] & 2) {
          // disable in Drupal
          $result = db_query("UPDATE {users} SET `status`=0 WHERE `uid`='%s'", $account->uid);  // do we need to add another check to the WHERE clause just in case? User_load should provide us only one result, right?
          // do we need to check this result or will db_query perform the necessary error handling?
          watchdog('ldapsync', 'Disabled LDAP-authentified user '. $name .' because the corresponding LDAP account is disabled.');
          $count_updated_users++;
        }

        // if no conditions are met, then take no further action. This LDAP user is properly set up in Drupal.

    }
  }

  // does LDAP require unbind? Don't see it in ldapauth.

  // do we have any LDAP-authentified Drupal users who don't exist in LDAP?
  $result = db_query("SELECT `uid`,`name`,`data` FROM {users} WHERE `status`=1");
  while ($row = db_fetch_array($result)) {
    if (!in_array($row['name'], array_keys($ldap_users))) {
        $data = unserialize($row['data']);
        if ($data['ldap_authentified']) {
            // disable for now and warn admin of possible case for deletion
            // probably should not make delete option available, because one could wipe out a good chunk of Drupal users with a misconfiguration of LDAP
            $result = db_query("UPDATE {users} SET `status`=0 WHERE `uid`='%s'", $row['uid']);  // do we need to add another check to the WHERE clause just in case? User_load should provide us only one result, right?
            watchdog('ldapsync', 'Disabled LDAP-authentified user '. $name .' because the corresponding LDAP account does not exist.');
            $count_disabled_users++;
        }
    }
  }

  // send watchdog message with process summary
  $summary = 'Completed LDAP sync. New users: '. $count_new_users .'. Updated users: '. $count_updated_users .'. Disabled users: '. $count_disabled_users .'.';
  watchdog('ldapsync', $summary);

  // output results (just for testing)
  return $page_content .'<p>'. $summary ."</p>";

}



function ldapsync_search() {

  global $_ldapsync_ldap;

  // Cycle through LDAP configurations.
  $result = db_query("SELECT sid FROM {ldapauth} WHERE status = '1' ORDER BY sid");
  while ($row = db_fetch_object($result)) {

  // Initialize LDAP.
  if (!_ldapsync_init($row->sid)) {
      watchdog('ldapsync', 'ldapsync init failed for ldap server '. $row->sid .'.');
      continue;
  }

  // If there is no bindn and bindpw - the connect will be an anonymous connect.
  $_ldapsync_ldap->connect($_ldapsync_ldap->getOption('binddn'), $_ldapsync_ldap->getOption('bindpw'));
  $users=array();
  foreach (explode("\r\n", $_ldapsync_ldap->getOption('basedn')) as $base_dn) {
    if (empty($base_dn)) continue;

        // re-initialize database object each time
        $ldapresult = array();

        // execute LDAP search
        $name_attr = $_ldapsync_ldap->getOption('user_attr') ? $_ldapsync_ldap->getOption('user_attr') : LDAPAUTH_DEFAULT_USER_ATTR;
        $filter = "$name_attr=*";  // finds all users
        $ldapresult = $_ldapsync_ldap->search($base_dn, $filter);  // filter param seems to be causing error: mysqli_fetch_object() expects parameter 1 to be mysqli_result, array given
        if (!$ldapresult) continue;

        // cycle through results to build array of user information
        $num_matches = $ldapresult['count'];
        foreach ($ldapresult as $entry) {
        $name = drupal_strtolower($entry['samaccountname'][0]);
        //$cn = $entry['cn'][0];  // commented out because these might be useful later to populate custom profile fields
        $users[$name]['dn'] = $entry['dn'];
        //$displayname = $entry['displayname'][0];
        $users[$name]['status'] = $entry['useraccountcontrol'][0];  // future reference: ($status & 2) TRUE indicates disabled account
        $users[$name]['mail'] = $entry['mail'][0];
        //$firstname = $entry['givenname'][0];
        //$lastname = $entry['sn'][0];

      }
    }
  }

  return $users;

}


//////////////////////////////////////////////////////////////////////////////
// Auxiliary functions

/**
 * Initiates the LDAPInterfase class.
 *
 * @param $sid
 *   An ID of the LDAP server configuration.
 *
 * @return
 */
function _ldapsync_init($sid) {
  global $_ldapsync_ldap;

  if ($row = db_fetch_object(db_query("SELECT * FROM {ldapauth} WHERE sid = %d", $sid))) {
    $_ldapsync_ldap = new LDAPInterface();
    $_ldapsync_ldap->setOption('sid', $row->sid);
    $_ldapsync_ldap->setOption('name', $row->name);
    $_ldapsync_ldap->setOption('server', $row->server);
    $_ldapsync_ldap->setOption('port', $row->port);
    $_ldapsync_ldap->setOption('tls', $row->tls);
    $_ldapsync_ldap->setOption('encrypted', $row->encrypted);
    $_ldapsync_ldap->setOption('basedn', $row->basedn);
    $_ldapsync_ldap->setOption('user_attr', $row->user_attr);
    $_ldapsync_ldap->setOption('mail_attr', $row->mail_attr);
    $_ldapsync_ldap->setOption('binddn', $row->binddn);
    $_ldapsync_ldap->setOption('bindpw', $row->bindpw);
    return $_ldapsync_ldap;
  }
}