From 3a0b8c4e06f4ffc31aba03559002939ab226f6b8 Mon Sep 17 00:00:00 2001
From: Joel Koglin <joel@freelock.com>
Date: Fri, 26 Oct 2012 17:32:45 -0700
Subject: [PATCH] Squashed commit of the following:

    added support for ldap login security modes
    you can now go to /admin/settings/ldap/ldapauth and Under "Security Options" is a "Choose security mode" section
    added a check for only CN and base DC, this makes it possible to move users around in OU's and still be able to match them without converting to puid yet.
    fixed typo in a variable in ldaphelp ($create_user) had an _ and thus was undefined
    added extra debug messages for ldapauth module
---
 .../ldap_integration/includes/ldap.core.inc        |   79 +++++++++++++++++---
 .../modules/ldap_integration/ldapauth.admin.inc    |   15 ++++
 sites/all/modules/ldap_integration/ldapauth.module |   26 +++++--
 .../ldap_integration/ldaphelp/ldaphelp_status.inc  |   20 ++++-
 4 files changed, 124 insertions(+), 16 deletions(-)

diff --git a/ldap_integration/includes/ldap.core.inc b/ldap_integration/includes/ldap.core.inc
index 98b3d7d..b1ffdd5 100644
--- a/ldap_integration/includes/ldap.core.inc
+++ b/ldap_integration/includes/ldap.core.inc
@@ -414,25 +414,58 @@ function ldapauth_drupal_user_lookup( $ldap, $name, $dn, &$error, $puid=NULL ) {
   }
   // Not using PUID so just use name.
   else {
-    $drupal_name = ldapauth_drupal_user_name($name, $ldap, $dn);;
+    $drupal_name = ldapauth_drupal_user_name($name, $ldap, $dn);
+    ldapauth_debug_msg(t("authenticate: Mapped login name to local drupal account name, @name.", array('@name' => $name)));
     $account = user_load(array('name' => $drupal_name));
 
     if ( ! $account ) {
       return FALSE;
     }
+    $auth = array();
     // Double check that ldap user matches this account.
     if ( $account->ldap_authentified ) {
-      // Do DNs map
-      if ( drupal_strtolower($account->ldap_dn) == drupal_strtolower($dn)) {
-        $old_server = ldapauth_server_load($account->ldap_config);
-        // Do sids match or old sid does not exist
-        if ( $account->ldap_config == $sid || empty( $old_server )) {
-          $account->ldap_puid = $name;  // Default if puid attr not set.
-          return $account;
+      $auth[LDAPAUTH_SECURITY_NONE] = TRUE;
+      // Do users CN and DCs match servers CN and DCs
+      if (_ldapauth_getCNDC(drupal_strtolower($account->ldap_dn)) == _ldapauth_getCNDC(drupal_strtolower($dn))) {
+        $auth[LDAPAUTH_SECURITY_LIGHT] = TRUE;
+        // Do DNs map
+        if (drupal_strtolower($account->ldap_dn) == drupal_strtolower($dn)) {
+          $auth[LDAPAUTH_SECURITY_MEDIUM] = TRUE;
+          $old_server = ldapauth_server_load($account->ldap_config);
+          // Do sids match or old sid does not exist
+          if ($account->ldap_config == $sid || empty($old_server)) {
+            $auth[LDAPAUTH_SECURITY_PARANOID] = TRUE;
+          }
+          else {
+            ldapauth_debug_msg(t("authenticate: @name, old DN entry exists AND user's DN's sid does not matche this sid", array('@name' => $name)));
+          }
         }
+        else {
+          ldapauth_debug_msg(t("authenticate: @name, local account DN, @local, does not matche server DN, @server.", array(
+            '@name' => $name,
+            '@local' => drupal_strtolower($account->ldap_dn),
+            '@server' => drupal_strtolower($dn)
+          )));
+        }
+      } else {
+        ldapauth_debug_msg(t("authenticate: @name, local account DN, @local, does not matche server DN, @server.", array(
+          '@name' => $name,
+          '@local' => _ldapauth_getCNDC(drupal_strtolower($account->ldap_dn)),
+          '@server' => _ldapauth_getCNDC(drupal_strtolower($dn))
+        )));
       }
-      return FALSE;
+      if($auth[LDAPAUTH_SECURITY_MODE] == TRUE) {
+        ldapauth_debug_msg(t("authenticate: @name, logged in on security level @sec", array('@name' => $name, '@sec' => LDAPAUTH_SECURITY_MODE)));
+        $account->ldap_puid = $name;  // Default if puid attr not set.
+        return $account;
+      } else {
+        ldapauth_debug_msg(t("authenticate: Mapped user, @name, does not pass all LDAP matching tests.", array('@name' => $name)));
+        return FALSE;
+      }
+    } else {
+      ldapauth_debug_msg(t("authenticate: @name, is not ldap_authentified", array('@name' => $name)));
     }
+    // if ( !$account->ldap_authentified )
     $account->ldap_puid = $name;  // Default if puid attr not set.
     return $account;
   }
@@ -678,3 +711,31 @@ function ldapauth_debug_msg( $message, $variables=NULL ) {
     watchdog('ldapauth', $message, $variables, WATCHDOG_DEBUG);
   }
 }
+
+/**
+ * @param $str Full ldap string
+ * @return mixed the cn from the full ldap string
+ */
+function _ldapauth_getCN($str) {
+  $pattern = '/[cC][nN]=(\\\,|[^,])+,/';
+  $matches = array();
+  preg_match($pattern,$str,$matches);
+  return $matches[0];
+}
+
+/**
+ * @param $str
+ * @return mixed the full dc suffix
+ */
+function _ldapauth_getDC($str) {
+  $pattern = '/[dD][cC]=.+$/';
+  $matches = array();
+  preg_match($pattern,$str,$matches);
+  return $matches[0];
+}
+
+function _ldapauth_getCNDC($str) {
+  $cn = _ldapauth_getCN($str);
+  $dc = _ldapauth_getDC($str);
+  return $cn.$dc;
+}
diff --git a/ldap_integration/ldapauth.admin.inc b/ldap_integration/ldapauth.admin.inc
index 84261e3..a3b8906 100644
--- a/ldap_integration/ldapauth.admin.inc
+++ b/ldap_integration/ldapauth.admin.inc
@@ -19,6 +19,12 @@ function ldapauth_admin_settings() {
     LDAPAUTH_AUTH_MIXED => t('Mixed mode. The LDAP authentication is performed only if Drupal authentication fails'),
     LDAPAUTH_AUTH_EXCLUSIVED => t('LDAP directory only')
   );
+  $options_login_security = array(
+    LDAPAUTH_SECURITY_PARANOID => t('Paranoid Security. Local account to ldap account matching requires a full match of the CN, OU, DN and the serverid from the ldap config.'),
+    LDAPAUTH_SECURITY_MEDIUM => t('Medium Security. Local account to ldap account matching requires a full match of the CN, OU, DN'),
+    LDAPAUTH_SECURITY_LIGHT => t('Light Security. Local account to ldap accout matching only requires the CN and baseDN match the servers CN and base DN.'),
+    LDAPAUTH_SECURITY_NONE => t('No Security. Local account to ldap accout matching does not do any extra verification'),
+  );
   $options_login_conflict = array(
     LDAPAUTH_CONFLICT_LOG => t('Disallow login and log the conflict'),
     LDAPAUTH_CONFLICT_RESOLVE => t('Associate local account with the LDAP entry')
@@ -83,6 +89,14 @@ function ldapauth_admin_settings() {
     '#default_value' => variable_get('ldapauth_create_users', TRUE),
     '#description' => t('If checked, then LDAP will create a new Drupal user against the user information supplied by the user authenticated by LDAP. If not checked only the already available users will be authenticated.'),
   );
+  $form['security-options']['ldapauth_security_mode'] = array(
+    '#type' => 'radios',
+    '#title' => t('Choose security mode'),
+    '#description' => t('Pick the security mode. This is the security mode for non puid ldap lookups, it is only used if the puid attribute is not set. Enhanced is the default and best pratice. Light and None provide support for being able to move users around in OU\'s and still be able to find the proper account to map to. Note: This is only used if the puid attribute mapping is not set.'),
+    '#default_value' => LDAPAUTH_SECURITY_MODE,
+    '#options' => $options_login_security,
+    '#required' => TRUE,
+  );
 
   $form['ldap-ui'] = array(
     '#type' => 'fieldset',
@@ -147,6 +161,7 @@ function ldapauth_admin_settings_submit($form, &$form_state) {
       variable_set('ldapauth_create_users', $values['ldapauth_create_users']);
       variable_set('ldapauth_alter_username_field', $values['ldapauth_alter_username_field']);
       variable_set('ldapauth_debug', $values['ldapauth_debug']);
+      variable_set('ldapauth_security_mode', $values['ldapauth_security_mode']);
 
       drupal_set_message(t('The configuration options have been saved.'));
       break;
diff --git a/ldap_integration/ldapauth.module b/ldap_integration/ldapauth.module
index 0c7ff38..19b4941 100644
--- a/ldap_integration/ldapauth.module
+++ b/ldap_integration/ldapauth.module
@@ -17,6 +17,10 @@ define('LDAPAUTH_EMAIL_FIELD_REMOVE',   1);
 define('LDAPAUTH_EMAIL_FIELD_DISABLE',  2);
 define('LDAPAUTH_PROFILE',              'LDAP authentication');
 define('LDAPAUTH_PROFILE_WEIGHT',       4);
+define('LDAPAUTH_SECURITY_NONE',        0);
+define('LDAPAUTH_SECURITY_LIGHT',       1);
+define('LDAPAUTH_SECURITY_MEDIUM',      2);
+define('LDAPAUTH_SECURITY_PARANOID',    3);
 define('LDAPAUTH_USERNAME_FIELD_NO',       0);
 define('LDAPAUTH_USERNAME_FIELD_REMOVE',   1);
 define('LDAPAUTH_USERNAME_FIELD_DISABLE',  2);
@@ -38,6 +42,7 @@ define('LDAPAUTH_ALTER_EMAIL_FIELD',    variable_get('ldapauth_alter_email_field
 define('LDAPAUTH_DEFAULT_USER_ATTR',    variable_get('ldapauth_default_user_attr', 'uid'));
 define('LDAPAUTH_DEFAULT_MAIL_ATTR',    variable_get('ldapauth_default_mail_attr', 'mail'));
 define('LDAPAUTH_ALTER_USERNAME_FIELD', variable_get('ldapauth_alter_username_field', LDAPAUTH_USERNAME_FIELD_NO));
+define('LDAPAUTH_SECURITY_MODE',        variable_get('ldapauth_security_mode', LDAPAUTH_SECURITY_PARANOID));
 
 //////////////////////////////////////////////////////////////////////////////
 // Core API hooks
@@ -429,7 +434,7 @@ function ldapauth_authenticate($form_values = array()) {
 
   // Find and Authenticate LDAP user.
   if (!($dn = _ldapauth_auth($name, $pass))) {
-    ldapauth_debug_msg(t("authenticate: User, @user, was not found in LDAP or authentication failed..", array('@user' => $name )));
+    ldapauth_debug_msg(t("authenticate: User, @user, was not found in LDAP or authentication failed.", array('@user' => $name )));
     return;
   }
   ldapauth_debug_msg(t("authenticate: User, @user, mapped to LDAP DN, @dn.", array('@user' => $name, '@dn' => $dn )));
@@ -470,7 +475,7 @@ function ldapauth_authenticate($form_values = array()) {
 
   // No matching Drupal user, try to create one for this LDAP account.
   if (!$account) {
-    ldapauth_debug_msg(t("authenticate: Attempting to creating local Drupal user, @user.", array('@user' => $name )));
+    ldapauth_debug_msg(t("authenticate: Attempting to create local Drupal user, @user.", array('@user' => $name )));
     $error = '';
     $account = ldapauth_drupal_user_create($_ldapauth_ldap, $name, $dn, $error);
     if ( $account === FALSE ) {
@@ -550,18 +555,23 @@ function _ldapauth_auth($name, $pass, $create_account = FALSE) {
 
   // Don't allow empty passwords because they cause problems on some setups.
   // http://drupal.org/node/87831
-  if (empty($pass))
+  if (empty($pass)) {
+    ldapauth_debug_msg(t("authenticate: User, @user, rejected due to empty password.", array('@user' => $name)));
     return FALSE;
+  }
 
   // Cycle through LDAP configurations.  First one to succeed wins.
   $result = db_query("SELECT sid FROM {ldapauth} WHERE status = 1 ORDER BY weight");
   while ($row = db_fetch_object($result)) {
 
     // Initialize LDAP.
-    if (!_ldapauth_init($row->sid))
+    if (!_ldapauth_init($row->sid)) {
+      ldapauth_debug_msg(t("authenticate: Failed to initialize LDAP connection to server id: @sid.", array('@sid' => $row->sid)));
       return FALSE;
+    }
 
     // Look up the user in LDAP.
+    ldapauth_debug_msg(t("authenticate: Attempting to find, @name, in LDAP.", array('@name' => $name)));
     if (!($ldap = _ldapauth_user_lookup($name)) || !isset($ldap['dn']))
       continue;
 
@@ -617,15 +627,19 @@ function _ldapauth_user_lookup($name) {
     return;
   }
   foreach (explode("\r\n", $_ldapauth_ldap->getOption('basedn')) as $base_dn) {
-    if (empty($base_dn))
+    if (empty($base_dn)) {
+      ldapauth_debug_msg(t("authenticate: LDAP search, skipping empty BaseDN in options string.", array()));
       continue;
+    }
 
     $name_attr = $_ldapauth_ldap->getOption('user_attr') ? $_ldapauth_ldap->getOption('user_attr') : LDAPAUTH_DEFAULT_USER_ATTR;
     $filter = $name_attr .'='. $login_name;
     $attrs = ldapauth_attributes_needed(LDAPAUTH_SYNC_CONTEXT_AUTHENTICATE_DRUPAL_USER, $_ldapauth_ldap->getOption('sid'));
     $result = $_ldapauth_ldap->search( $base_dn, $filter, $attrs );
-    if (!$result)
+    if (!$result) {
+      ldapauth_debug_msg(t("authenticate: LDAP search query returned empty for BaseDN: '@dn', with filter: '@filter', and attrs: '@attrs'.", array('@dn' => $base_dn, '@filter' => $filter, '@attrs' => $attrs )));
       continue;
+    }
 
     $num_matches = $result['count'];
     // Must find exactly one user for authentication to.
diff --git a/ldap_integration/ldaphelp/ldaphelp_status.inc b/ldap_integration/ldaphelp/ldaphelp_status.inc
index 6d72a64..826a1af 100644
--- a/ldap_integration/ldaphelp/ldaphelp_status.inc
+++ b/ldap_integration/ldaphelp/ldaphelp_status.inc
@@ -165,10 +165,28 @@ function ldaphelp_get_ldapauth(&$info) {
   $status[] =  array('title' => 'Security: Sync Passwords', value => $info['ldapauth']['ldapauth_disable_pass_change_text'], 'severity' => "0");
 
   $create_users = variable_get('ldapauth_create_users', TRUE);
-  $info['ldapauth']['ldapauth_disable_pass_change_text'] = ($create_users_ == FALSE) ? "Do not " : "Do";
+  $info['ldapauth']['ldapauth_disable_pass_change_text'] = ($create_users == FALSE) ? "Do not " : "Do";
   $info['ldapauth']['ldapauth_disable_pass_change_text'] .= 'create new Drupal users if not present.';
   $status[] =  array('title' => 'Security: New Users', value => $info['ldapauth']['ldapauth_disable_pass_change_text'], 'severity' => "0");
 
+  switch(LDAPAUTH_SECURITY_MODE) {
+    case LDAPAUTH_SECURITY_PARANOID:
+      $info['ldapauth']['ldapauth_disable_pass_change_text'] = 'Paranoid ';
+      break;
+    case LDAPAUTH_SECURITY_MEDIUM:
+      $info['ldapauth']['ldapauth_disable_pass_change_text'] = 'Medium ';
+      break;
+    case LDAPAUTH_SECURITY_LIGHT:
+      $info['ldapauth']['ldapauth_disable_pass_change_text'] = 'Light ';
+      break;
+    case LDAPAUTH_SECURITY_NONE:
+      $info['ldapauth']['ldapauth_disable_pass_change_text'] = 'No ';
+      break;
+  }
+
+  $info['ldapauth']['ldapauth_disable_pass_change_text'] .= ' security is used when matching local drupal users with ldap accounts';
+  $status[] =  array('title' => 'Security: LDAP user matching security', value => $info['ldapauth']['ldapauth_disable_pass_change_text'], 'severity' => "0");
+
   // UI Options settings
   switch ( LDAPAUTH_ALTER_USERNAME_FIELD ) {
     case 0:
-- 
1.7.9.5

