*
* Copyrights are listed in chronological order, by contributions.
*
* wpDirAuth: WordPress Directory Authentication
* Copyright (c) 2007 Stephane Daury - http://stephane.daury.org/
*
* wpDirAuth and wpLDAP Patch Contributions
* Copyright (c) 2007 PKR Internet, LLC - http://www.pkrinternet.com/
*
* wpDirAuth Patch Contributions
* Copyright (c) 2007 Todd Beverly
*
* wpLDAP: WordPress LDAP Authentication
* Copyright (c) 2007 Ashay Suresh Manjure - http://ashay.org/
*
* wpDirAuth is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation.
*
* wpDirAuth is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* @todo Always stay on top of security and user input validation while
* staying backwards compatible enough until PHP4 support is dropped in
* WP (serious patches welcomed, please see code). Note that we do
* heavily rely on WP's admin ACL scheme, by necessity.
*/
/*
PLUGIN META INFO FOR WORDPRESS LISTINGS
Plugin Name: wpDirAuth
Plugin URI: http://tekartist.org/labs/wordpress/plugins/wpdirauth/
Description: WordPress Directory Authentication (LDAP/LDAPS).
Works with most LDAP enabled directory services, such as OpenLDAP,
Apache Directory, Microsoft Active Directory, Novell eDirectory,
Sun Java System Directory Server, etc.
Originally revived and upgraded from a patched version of wpLDAP.
Version: 1.1
Author: Stephane Daury and whoever wants to help
Author URI: http://stephane.daury.org/
*/
/**
* wpDirAuth version.
*/
define('WPDIRAUTH_VERSION', '1.1');
/**
* wpDirAuth signature.
*/
define('WPDIRAUTH_SIGNATURE', 'wpDirAuth '.WPDIRAUTH_VERSION);
/**
* Default LDAP field to search against when locating the user's profile.
*/
define('WPDIRAUTH_DEFAULT_FILTER', 'samAccountName');
/**
* Default login screen message.
*/
define('WPDIRAUTH_DEFAULT_LOGINSCREENMSG', '%s members can login directly using their institutional password.');
/**
* Default password change message.
*/
define('WPDIRAUTH_DEFAULT_CHANGEPASSMSG', 'To change a %s password, please refer to the official institutional password policy.');
/**
* Allowed HTML (messages)
*/
define('WPDIRAUTH_ALLOWED_TAGS', '- ');
if (function_exists('wp_login') || function_exists('wp_setcookie') || !function_exists('ldap_connect')) {
/**
* SAFE MODE
*/
/**
* SAFE MODE: wpDirAuth plugin configuration panel.
* Processes and outputs the wpDirAuth configuration form, with a conflict message.
*
* @return void
*/
function wpDirAuth_safeConflictMessage()
{
$wpDARef = WPDIRAUTH_SIGNATURE;
if (!function_exists('ldap_connect')) {
$message = <<<________EOS
Sorry, but your PHP install does not seem to have access to the LDAP features.
wpDirAuth is now running in safe mode.'
Quote from the PHP manual LDAP section:
LDAP support in PHP is not enabled by default. You will need to use the
--with-ldap[=DIR] configuration option when compiling PHP to enable LDAP
support. DIR is the LDAP base install directory. To enable SASL support,
be sure --with-ldap-sasl[=DIR] is used, and that sasl.h exists on the system.
________EOS;
}
else {
$message = <<<________EOS
Sorry, but another plugin seems to be conflicting with wpDirAuth.
wpDirAuth is now running in safe mode as to not impair the other plugin's operations.'
The wp_login and wp_setcookie WordPress
pluggable functions
have already been redefined, and wpDirAuth cannot provide directory authentication
without having access to these functions.
Please disable any WP plugins that deal with authentication in order to use wpDirAuth.
Unfortunately, we cannot provide you with more info as to which plugin is in conflict.
________EOS;
}
echo <<<________EOS
Directory Authentication Options: Plugin Conflict
$message
$wpDARef
________EOS;
}
/**
* SAFE MODE: Adds the `Directory Auth.` menu entry in the Wordpress Admin section.
* Also activates the wpDirAuth config panel, with a conflict message, as a callback function.
*
* @uses wpDirAuth_safeConflictMessage
*/
function wpDirAuth_safeAddMenu()
{
if (function_exists('add_options_page')) {
add_options_page(
'Directory Authentication Options: Plugin Conflict',
'!! Directory Auth. !!',
9,
basename(__FILE__),
'wpDirAuth_safeConflictMessage'
);
}
}
/**
* SAFE MODE: Add custom WordPress actions.
*
* @uses wpDirAuth_safeAddMenu
*/
if (function_exists('add_action')) {
add_action('admin_menu', 'wpDirAuth_safeAddMenu');
}
}
else {
/**
* STANDARD MODE
*/
/**
* Cookie marker.
* Generates a random string to be used as salt for the password
* hash cookie checks in wp_setcookie and wp_login
*
* @return string 55 chars-long salty goodness (md5 + uniqid)
*/
function wpDirAuth_makeCookieMarker()
{
$cookieMarker = md5(
$_SERVER['SERVER_SIGNATURE']
.$_SERVER['HTTP_USER_AGENT']
.$_SERVER['REMOTE_ADDR']
).uniqid(microtime(),true);
update_option("dirAuthCookieMarker",$cookieMarker);
return $cookieMarker;
}
/**
* LDAP bind test
* Tries two different documented method of php-based ldap binding.
* Note: passing params by reference, no need for copies (unlike in
* wpDirAuth_auth where it is desirable).
*
* @param object &$connection LDAP connection
* @param string &$username LDAP username
* @param string &$password LDAP password
* @return boolean Binding status
* */
function wpDirAuth_bindTest(&$connection, &$username, &$password)
{
if ( ($isBound = @ldap_bind($connection, $username, $password)) === false ) {
// @see wpLDAP comment at http://ashay.org/?page_id=133#comment-558
$isBound = @ldap_bind($connection,"uid=$username,$baseDn", $password);
}
return $isBound;
}
/**
* Custom LDAP authentication module.
* The returned keys are in the same format used by WP for
* the wp_insert_user and wp_update_user functions.
*
* @param string $username LDAP username
* @param string $password LDAP password
* @return boolean false OR array Directory email, last_name and first_name
*
* @uses WPDIRAUTH_DEFAULT_FILTER
* @uses wpDirAuth_bindTest
*/
function wpDirAuth_auth($username, $password)
{
global $error, $pwd;
$errorTitle = ''.__('Directory Login Error').': ';
$controllers = explode(',', get_option('dirAuthControllers'));
$baseDn = get_option('dirAuthBaseDn');
$preBindUser = get_option('dirAuthPreBindUser');
$preBindPassword = get_option('dirAuthPreBindPassword');
$accountSuffix = get_option('dirAuthAccountSuffix');
$filter = get_option('dirAuthFilter');
$enableSsl = get_option('dirAuthEnableSsl');
$returnKeys = array('sn', 'givenname', 'mail');
$isBound = $isPreBound = $isLoggedIn = false;
if (count($controllers) > 1) {
// shuffle the domain controllers for pseudo load balancing and fault tolerance.
shuffle($controllers);
}
elseif (count($controllers) == 0) {
$error = $errorTitle
. __(' wpDirAuth config error: no domain controllers specified.');
$pwd = '';
return false;
}
if ($accountSuffix) $username .= $accountSuffix;
/**
* Only setup protocol value if ldaps is required to help with older AD
* @see http://groups.google.com/group/wpdirauth-support/browse_thread/thread/7b744c7ad66a4829
*/
$protocol = ($enableSsl) ? 'ldaps://' : '';
if (!$filter) $filter = WPDIRAUTH_DEFAULT_FILTER;
$filterQuery = "($filter=$username)";
// Connection pool loop - Haha, PooL LooP
foreach ($controllers as $dc) {
/**
* The lack of conditional check at the ldap_connect() level below
* is because with php and openldap 2.x, the ldap_connect() will
* always return a resource as it does not actually connect but just
* initializes the connecting parameters. The actual connection happens
* with ldap_bind() which is itself wrapped in a conditional check.
* @see Notes in return value definition at http://php.net/ldap_connect
*/
$connection = @ldap_connect($protocol.$dc);
/**
* Copes with W2K3/AD issue.
* @see http://bugs.php.net/bug.php?id=30670
*/
if (@ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, 3)) {
@ldap_set_option($connection, LDAP_OPT_REFERRALS, 0);
}
if ($preBindUser && $preBindPassword) {
/**
* Use case 1: Servers requiring pre-binding with admin defined
* credentials to search for the user's full DN before attempting
* to login.
* @see http://dev.wp-plugins.org/ticket/681
*/
if ( $isPreBound = wpDirAuth_bindTest($connection, $preBindUser, $preBindPassword) === true ) {
if ( ($results = @ldap_search($connection, $baseDn, $filterQuery, $returnKeys)) !== false ) {
if ( ($userDn = @ldap_get_dn($connection, ldap_first_entry($connection, $results))) !== false ) {
if ( ($isBound = wpDirAuth_bindTest($connection, $userDn, $password)) === true ) {
$isLoggedIn = true; // valid server, valid login, move on
break; // valid server, valid login, move on
}
}
}
}
}
elseif ( ($isBound = wpDirAuth_bindTest($connection, $username, $password)) === true ) {
/**
* Use case 2: Servers that will not let you bind anonymously
* but will let the end user bind directly.
* @see http://groups.google.com/group/wpdirauth-support/browse_thread/thread/8fd16c05266fc832
*/
$isLoggedIn = true;
break; // valid server, valid login, move on
}
elseif ( ($isBound = @ldap_bind($connection)) === true ) {
/**
* Use case 3: Servers that might require a full user DN to
* actually login and therefore let you bind anonymously first .
* Try ldap_search + ldap_get_dn before attempting a login.
* @see http://wordpress.org/support/topic/129814?replies=34#post-603644
*/
if ( ($results = @ldap_search($connection, $baseDn, $filterQuery, $returnKeys)) !== false ) {
if ( ($userDn = @ldap_get_dn($connection, ldap_first_entry($connection, $results))) !== false ) {
if ( ($isBound = wpDirAuth_bindTest($connection, $userDn, $password)) === true ) {
$isLoggedIn = true; // valid server, valid login, move on
break; // valid server, valid login, move on
}
}
}
}
}
if ( ($preBindUser && $preBindPassword) && ( ! $isPreBound ) ) {
$error = $errorTitle
. __(' wpDirAuth config error: No directory server available for authentication, OR pre-binding credentials denied.');
$pwd = '';
return false;
}
elseif ( ! $isBound) {
$error = $errorTitle
. __(' wpDirAuth config error: No directory server available for authentication.');
$pwd = '';
return false;
}
elseif ( ! $isLoggedIn) {
$error = $errorTitle
. __(' Could not authenticate user. Please check your credentials.')
. " [$username]";
$pwd = '';
return false;
}
else {
/**
* Search for profile, if still needed.
* @see $results in preceding loop: Use case 3
*/
if (!$results) $results = @ldap_search($connection, $baseDn, $filterQuery, $returnKeys);
if (!$results) {
$error = $errorTitle
. __('Directory authentication initially succeeded, but no
valid profile was found (search procedure).')
." [$filter]";
$pwd = '';
return false;
}
else{
$userInfo = @ldap_get_entries($connection, $results);
$count = intval($userInfo['count']);
if ($count < 1) {
$error = $errorTitle
. __('Directory authentication initially succeeded, but no
valid profile was found ("get entries" procedure).')
." [$filter]";
$pwd = '';
return false;
}
elseif ($count > 1) {
$error = $errorTitle
. __('Directory authentication initially succeeded, but the
username you sent is not a unique profile identifier.')
. " [$filter]";
$pwd = '';
return false;
}
else {
$email = isset($userInfo[0]['mail'][0])
? $userInfo[0]['mail'][0] : '';
$lastName = isset($userInfo[0]['sn'][0])
? $userInfo[0]['sn'][0] : '';
$firstName = isset($userInfo[0]['givenname'][0])
? $userInfo[0]['givenname'][0] : '';
return array(
'email' => $email,
'last_name' => $lastName,
'first_name' => $firstName
);
}
}
}
}
/**
* Runs stripslashes, html_entity_decode, then strip_tags with
* allowed html if requested.
*
* No input sashimi for us (hopefully).
*
* @param string $value Value to `sanitize`
* @param boolean $allowed Set to true for WPDIRAUTH_ALLOWED_TAGS
* @return string Cleaner value.
*
* @uses WPDIRAUTH_ALLOWED_TAGS
*/
function wpDirAuth_sanitize($value, $allowed = false)
{
$allowed = ($allowed) ? WPDIRAUTH_ALLOWED_TAGS : '';
return strip_tags(html_entity_decode(stripslashes($value)), $allowed);
}
/**
* wpDirAuth plugin configuration panel.
* Processes and outputs the wpDirAuth configuration form.
*
* @return void
*
* @uses WPDIRAUTH_DEFAULT_FILTER
* @uses WPDIRAUTH_DEFAULT_LOGINSCREENMSG
* @uses WPDIRAUTH_DEFAULT_CHANGEPASSMSG
* @uses WPDIRAUTH_ALLOWED_TAGS
* @uses wpDirAuth_makeCookieMarker
* @uses wpDirAuth_sanitize
*/
function wpDirAuth_optionsPanel()
{
global $userdata;
$wpDARef = WPDIRAUTH_SIGNATURE;
$allowedHTML = htmlentities(WPDIRAUTH_ALLOWED_TAGS);
$curUserIsDirUser = get_usermeta($userdata->ID, 'wpDirAuthFlag');
if ($curUserIsDirUser) {
echo <<<____________EOS
Directory Authentication Options
Because any changes made to directory authentication
options can adversly affect your session when logged in
as a directory user, you must be logged in as a
WordPress-only administrator user to update these settings.
If such a user no longer exists in the database, please
create a new one
using the appropriate WordPress admin tool.
$wpDARef
____________EOS;
return;
}
if ($_POST) {
// Booleans
$enable = intval($_POST['dirAuthEnable']) == 1 ? 1 : 0;
$enableSsl = intval($_POST['dirAuthEnableSsl']) == 1 ? 1 : 0;
$requireSsl = intval($_POST['dirAuthRequireSsl']) == 1 ? 1 : 0;
$TOS = intval($_POST['dirAuthTOS']) == 1 ? 1 : 0;
// Strings, no HTML
$controllers = wpDirAuth_sanitize($_POST['dirAuthControllers']);
$baseDn = wpDirAuth_sanitize($_POST['dirAuthBaseDn']);
$preBindUser = wpDirAuth_sanitize($_POST['dirAuthPreBindUser']);
$preBindPassword = wpDirAuth_sanitize($_POST['dirAuthPreBindPassword']);
$preBindPassCheck = wpDirAuth_sanitize($_POST['dirAuthPreBindPassCheck']);
$accountSuffix = wpDirAuth_sanitize($_POST['dirAuthAccountSuffix']);
$filter = wpDirAuth_sanitize($_POST['dirAuthFilter']);
$institution = wpDirAuth_sanitize($_POST['dirAuthInstitution']);
// Have to be allowed to contain some HTML
$loginScreenMsg = wpDirAuth_sanitize($_POST['dirAuthLoginScreenMsg'], true);
$changePassMsg = wpDirAuth_sanitize($_POST['dirAuthChangePassMsg'], true);
update_option('dirAuthEnable', $enable);
update_option('dirAuthEnableSsl', $enableSsl);
update_option('dirAuthRequireSsl', $requireSsl);
update_option('dirAuthControllers', $controllers);
update_option('dirAuthBaseDn', $baseDn);
update_option('dirAuthPreBindUser', $preBindUser);
update_option('dirAuthAccountSuffix', $accountSuffix);
update_option('dirAuthFilter', $filter);
update_option('dirAuthInstitution', $institution);
update_option('dirAuthLoginScreenMsg', $loginScreenMsg);
update_option('dirAuthChangePassMsg', $changePassMsg);
update_option('dirAuthTOS', $TOS);
// Only store/override the value if a new one is being sent a bind user is set.
if ( $preBindUser && $preBindPassword && ($preBindPassCheck == $preBindPassword) )
update_option('dirAuthPreBindPassword', $preBindPassword);
// Clear the stored password if the Bind DN is null
elseif ( ! $preBindUser)
update_option('dirAuthPreBindPassword', '');
if (get_option('dirAuthEnable') && !get_option('dirAuthCookieMarker'))
wpDirAuth_makeCookieMarker();
echo 'Your new settings were saved successfully.
';
// Be sure to clear $preBindPassword, not to be displayed onscreen or in source
unset($preBindPassword);
}
else {
// Booleans
$enable = intval(get_option('dirAuthEnable')) == 1 ? 1 : 0;
$enableSsl = intval(get_option('dirAuthEnableSsl')) == 1 ? 1 : 0;
$requireSsl = intval(get_option('dirAuthRequireSsl')) == 1 ? 1 : 0;
$TOS = intval(get_option('dirAuthTOS')) == 1 ? 1 : 0;
// Strings, no HTML
$controllers = wpDirAuth_sanitize(get_option('dirAuthControllers'));
$baseDn = wpDirAuth_sanitize(get_option('dirAuthBaseDn'));
$preBindUser = wpDirAuth_sanitize(get_option('dirAuthPreBindUser'));
$accountSuffix = wpDirAuth_sanitize(get_option('dirAuthAccountSuffix'));
$filter = wpDirAuth_sanitize(get_option('dirAuthFilter'));
$institution = wpDirAuth_sanitize(get_option('dirAuthInstitution'));
// Have to be allowed to contain some HTML
$loginScreenMsg = wpDirAuth_sanitize(get_option('dirAuthLoginScreenMsg'), true);
$changePassMsg = wpDirAuth_sanitize(get_option('dirAuthChangePassMsg'), true);
}
$controllers = htmlspecialchars($controllers);
$baseDn = htmlspecialchars($baseDn);
$preBindUser = htmlspecialchars($preBindUser);
$accountSuffix = htmlspecialchars($accountSuffix);
$filter = htmlspecialchars($filter);
$institution = htmlspecialchars($institution);
$loginScreenMsg = htmlspecialchars($loginScreenMsg);
$changePassMsg = htmlspecialchars($changePassMsg);
if ($enable) {
$tEnable = "checked";
}
else {
$fEnable = "checked";
}
$defaultFilter = WPDIRAUTH_DEFAULT_FILTER;
if (!$filter) {
$filter = $defaultFilter;
}
if (!$institution) {
$institution = '[YOUR INSTITUTION]';
}
if (!$loginScreenMsg) {
$loginScreenMsg = sprintf(WPDIRAUTH_DEFAULT_LOGINSCREENMSG, $institution);
}
if (!$changePassMsg) {
$changePassMsg = sprintf(WPDIRAUTH_DEFAULT_CHANGEPASSMSG, $institution);
}
if ($enableSsl) {
$tSsl = "checked";
}
else {
$fSsl = "checked";
}
if ($requireSsl) {
$tWpSsl = "checked";
}
else {
$fWpSsl = "checked";
}
if ($TOS) {
$tTOS = "checked";
}
else {
$fTOS = "checked";
}
$wpDAV = WPDIRAUTH_VERSION;
echo <<<________EOS
Directory Authentication Options
Powered by $wpDARef.
________EOS;
}
/**
* Adds the `Directory Auth.` menu entry in the Wordpress Admin section.
* Also activates the wpDirAuth config panel as a callback function.
*
* @uses wpDirAuth_optionsPanel
*/
function wpDirAuth_addMenu()
{
if (function_exists('add_options_page')) {
add_options_page(
'Directory Authentication Options',
'Directory Auth.',
9,
basename(__FILE__),
'wpDirAuth_optionsPanel'
);
}
}
/**
* Extending WP's login_form.
* Enforces the admin defined SSL login preferences and adds a directory
* login related message to the standard WP login screen.
*
* @uses WPDIRAUTH_DEFAULT_LOGINSCREENMSG
*/
function wpDirAuth_loginFormExtra()
{
if (get_option('dirAuthEnable')) {
if (isset($_SERVER['SCRIPT_URI']) && preg_match('|^http|',$_SERVER['SCRIPT_URI'])) {
$selfURL = $_SERVER['SCRIPT_URI'];
}
else {
/**
* $_SERVER['SCRIPT_URI'] seems to be unavilable in some PHP
* installs, and $_SERVER['REQUEST_URI'] and $_SERVER['PHP_SELF']
* have been known to sometimes have the same issue.
* Thanks to Todd Beverly for helping out with this one. :)
* @see http://wordpress.org/support/topic/129814?replies=27#post-605423
*/
$selfURL = sprintf(
'http%s://%s%s',
(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 's' : ''),
$_SERVER['HTTP_HOST'],
(isset($_SERVER['REQUEST_URI'])
? $_SERVER['REQUEST_URI']
: $_SERVER["SCRIPT_NAME"].'?'.$_SERVER['QUERY_STRING'])
);
}
if (get_option('dirAuthRequireSsl') && (!preg_match('|^https|',$selfURL))) {
$refreshJS = '" />';
$refreshMeta = '';
$refreshMsg = 'Please access the encrypted version of this page.';
if (headers_sent()) {
echo $refreshJS.$refreshMeta.''.$refreshMsg.'