427 lines
19 KiB
PHP
427 lines
19 KiB
PHP
|
<?php # $Id: serendipity_event_externalauth.php,v 1.22 2011/06/27 09:31:58 garvinhicking Exp $
|
||
|
|
||
|
if (IN_serendipity !== true) {
|
||
|
die ("Don't hack!");
|
||
|
}
|
||
|
|
||
|
// Probe for a language include with constants. Still include defines later on, if some constants were missing
|
||
|
$probelang = dirname(__FILE__) . '/' . $serendipity['charset'] . 'lang_' . $serendipity['lang'] . '.inc.php';
|
||
|
if (file_exists($probelang)) {
|
||
|
include $probelang;
|
||
|
}
|
||
|
|
||
|
include dirname(__FILE__) . '/lang_en.inc.php';
|
||
|
|
||
|
class serendipity_event_externalauth extends serendipity_event
|
||
|
{
|
||
|
var $title = PLUGIN_EVENT_EXTERNALAUTH_TITLE;
|
||
|
|
||
|
function introspect(&$propbag)
|
||
|
{
|
||
|
global $serendipity;
|
||
|
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_TITLE);
|
||
|
$propbag->add('description', PLUGIN_EVENT_EXTERNALAUTH_DESC);
|
||
|
$propbag->add('stackable', false);
|
||
|
$propbag->add('author', 'Garvin Hicking/Justin Alcorn');
|
||
|
$propbag->add('version', '1.22');
|
||
|
$propbag->add('requirements', array(
|
||
|
'serendipity' => '0.8',
|
||
|
'smarty' => '2.6.7',
|
||
|
'php' => '4.1.0'
|
||
|
));
|
||
|
$propbag->add('event_hooks', array(
|
||
|
'backend_auth' => true,
|
||
|
'frontend_configure' => true,
|
||
|
'backend_configure' => true,
|
||
|
'backend_loginfail' => true
|
||
|
));
|
||
|
$propbag->add('configuration', array('enable_ldap', 'enable_logging', 'fail2ban',
|
||
|
'host', 'port', 'rdn', 'source', 'userlevel', 'ldap_userlevel_attr',
|
||
|
'ldap_tls', 'firstlogin', 'auth_query', 'bind_user', 'bind_password', 'user_wysiwyg'));
|
||
|
$propbag->add('groups', array('BACKEND_USERMANAGEMENT'));
|
||
|
}
|
||
|
|
||
|
function introspect_config_item($name, &$propbag)
|
||
|
{
|
||
|
switch($name) {
|
||
|
case 'enable_ldap':
|
||
|
$propbag->add('type', 'boolean');
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_ENABLE_LDAP);
|
||
|
$propbag->add('description', '');
|
||
|
$propbag->add('default', true);
|
||
|
break;
|
||
|
|
||
|
case 'enable_logging':
|
||
|
$propbag->add('type', 'boolean');
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_ENABLE_LOGGING);
|
||
|
$propbag->add('description', '');
|
||
|
$propbag->add('default', true);
|
||
|
break;
|
||
|
|
||
|
case 'fail2ban':
|
||
|
$propbag->add('type', 'string');
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_FAIL2BAN);
|
||
|
$propbag->add('description', PLUGIN_EVENT_EXTERNALAUTH_FAIL2BAN_DESC);
|
||
|
$propbag->add('default', '');
|
||
|
break;
|
||
|
|
||
|
|
||
|
case 'firstlogin':
|
||
|
$propbag->add('type', 'boolean');
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_FIRSTLOGIN);
|
||
|
$propbag->add('description', PLUGIN_EVENT_EXTERNALAUTH_FIRSTLOGIN_DESC);
|
||
|
$propbag->add('default', true);
|
||
|
break;
|
||
|
|
||
|
case 'ldap_tls':
|
||
|
$propbag->add('type', 'boolean');
|
||
|
$propbag->add('name', 'LDAP TLS');
|
||
|
$propbag->add('description', '');
|
||
|
$propbag->add('default', false);
|
||
|
break;
|
||
|
|
||
|
case 'rdn':
|
||
|
$propbag->add('type', 'string');
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_RDN);
|
||
|
$propbag->add('description', PLUGIN_EVENT_EXTERNALAUTH_RDN_DESC);
|
||
|
$propbag->add('default', 'uid=%1,ou=people,dc=yourdomain,dc=com');
|
||
|
break;
|
||
|
|
||
|
case 'port':
|
||
|
$propbag->add('type', 'string');
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_PORT);
|
||
|
$propbag->add('description', PLUGIN_EVENT_EXTERNALAUTH_PORT_DESC);
|
||
|
$propbag->add('default', '');
|
||
|
break;
|
||
|
|
||
|
case 'host':
|
||
|
$propbag->add('type', 'string');
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_HOST);
|
||
|
$propbag->add('description', PLUGIN_EVENT_EXTERNALAUTH_HOST_DESC);
|
||
|
$propbag->add('default', 'localhost');
|
||
|
break;
|
||
|
|
||
|
case 'source':
|
||
|
$propbag->add('type', 'select');
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_SOURCE);
|
||
|
$propbag->add('description', PLUGIN_EVENT_EXTERNALAUTH_SOURCE_DESC);
|
||
|
$propbag->add('default', 'LDAP');
|
||
|
$propbag->add('select_values', array('LDAP' => 'LDAP'));
|
||
|
break;
|
||
|
|
||
|
case 'userlevel':
|
||
|
$propbag->add('type', 'select');
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_USERLEVEL);
|
||
|
$propbag->add('description', PLUGIN_EVENT_EXTERNALAUTH_USERLEVEL_DESC);
|
||
|
$propbag->add('default', USERLEVEL_EDITOR);
|
||
|
$propbag->add('select_values', array(
|
||
|
USERLEVEL_ADMIN => PLUGIN_EVENT_EXTERNALAUTH_USERLEVEL_ADMIN,
|
||
|
USERLEVEL_CHIEF => PLUGIN_EVENT_EXTERNALAUTH_USERLEVEL_CHIEF,
|
||
|
USERLEVEL_EDITOR => PLUGIN_EVENT_EXTERNALAUTH_USERLEVEL_EDITOR,
|
||
|
-1 => PLUGIN_EVENT_EXTERNALAUTH_USERLEVEL_DENY
|
||
|
));
|
||
|
break;
|
||
|
|
||
|
case 'ldap_userlevel_attr':
|
||
|
$propbag->add('type', 'string');
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_USERLEVEL_ATTR);
|
||
|
$propbag->add('description', PLUGIN_EVENT_EXTERNALAUTH_USERLEVEL_ATTR_DESC);
|
||
|
$propbag->add('default', 'serendipity_userlevel');
|
||
|
break;
|
||
|
// This is for braindead LDAPs where users do not have a consistent naming scheme so that we cannot
|
||
|
// use the 'rdn' path but must do a full query to find them :-(
|
||
|
// e.g. (&(objectcategory=person)(objectclass=user)(sAMAccountName=%1))
|
||
|
case 'auth_query':
|
||
|
$propbag->add('type', 'string');
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_QUERY);
|
||
|
$propbag->add('description', PLUGIN_EVENT_EXTERNALAUTH_QUERY_DESC);
|
||
|
$propbag->add('default', '');
|
||
|
break;
|
||
|
case 'bind_user':
|
||
|
$propbag->add('type', 'string');
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_BIND_USER);
|
||
|
$propbag->add('description', PLUGIN_EVENT_EXTERNALAUTH_BIND_USER_DESC);
|
||
|
$propbag->add('default', '');
|
||
|
break;
|
||
|
case 'bind_password':
|
||
|
$propbag->add('type', 'string');
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_BIND_PASSWORD);
|
||
|
$propbag->add('description', PLUGIN_EVENT_EXTERNALAUTH_BIND_PASSWORD_DESC);
|
||
|
$propbag->add('default', '');
|
||
|
break;
|
||
|
case 'user_wysiwyg':
|
||
|
$propbag->add('type', 'boolean');
|
||
|
$propbag->add('name', PLUGIN_EVENT_EXTERNALAUTH_USER_WYSIWYG);
|
||
|
$propbag->add('description', PLUGIN_EVENT_EXTERNALAUTH_USER_WYSIWYG_DESC);
|
||
|
$propbag->add('default', true);
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function generate_content(&$title) {
|
||
|
$title = PLUGIN_EVENT_EXTERNALAUTH_TITLE;
|
||
|
}
|
||
|
|
||
|
function debugmsg($msg) {
|
||
|
static $debug = false;
|
||
|
|
||
|
if ($debug) {
|
||
|
$fp = fopen('/tmp/s9yldap.log', 'a');
|
||
|
fwrite($fp, '[' . date('d.m.Y H:i') . '] ' . $msg . "\n");
|
||
|
fclose($fp);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function setupDB() {
|
||
|
global $serendipity;
|
||
|
|
||
|
$built = $this->get_config('dbl_created', null);
|
||
|
$fresh = false;
|
||
|
if (empty($built)) {
|
||
|
$i = serendipity_db_schema_import("CREATE TABLE {$serendipity['dbPrefix']}loginlog (
|
||
|
timestamp int(10) {UNSIGNED} default null,
|
||
|
authorid int(11) default '0',
|
||
|
action varchar(255) not null default '',
|
||
|
ip varchar(255) not null default '',
|
||
|
referer varchar(255) not null default '',
|
||
|
user_agent varchar(255) not null default '');");
|
||
|
$this->set_config('dbl_created', 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function set_wysiwyg($author) {
|
||
|
global $serendipity;
|
||
|
$result = serendipity_db_query("SELECT authorid FROM ". $serendipity['dbPrefix'] ."authors WHERE username = '". $author ."' LIMIT 1", true, 'assoc');
|
||
|
if (!is_array($result)) {
|
||
|
$this->debugmsg('Could not find author: ' . $author);
|
||
|
} else {
|
||
|
$authorid = $result['authorid'];
|
||
|
$result = serendipity_db_query("SELECT value FROM ". $serendipity['dbPrefix'] ."config WHERE name='wysiwyg' AND authorid = '". $authorid ."' LIMIT 1", true, 'assoc');
|
||
|
if (!is_array($result)) {
|
||
|
serendipity_db_query(sprintf("INSERT INTO {$serendipity['dbPrefix']}config (name, value, authorid) VALUES ('wysiwyg', 'true', '%s')",
|
||
|
$authorid));
|
||
|
} else {
|
||
|
serendipity_db_query("UPDATE ". $serendipity['dbPrefix'] ."config SET value = 'true' WHERE name='wysiwyg' AND authorid = ". $authorid);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function logger($type = 'frontend_login', $eventData = array()) {
|
||
|
global $serendipity;
|
||
|
|
||
|
$f2b = $this->get_config('fail2ban');
|
||
|
if ($f2b != '') {
|
||
|
$fp = fopen($f2b, 'a');
|
||
|
|
||
|
if ($type == 'fail') {
|
||
|
if (empty($eventData['user'])) return false;
|
||
|
if ($this->failtrack) return false;
|
||
|
$this->failtrack = true;
|
||
|
$msg = date('M d H:i:s ') . $_SERVER['HTTP_HOST'] . ' s9y[' . $_SERVER['REMOTE_PORT'] . $eventData['mode'] . ']: auth failure username: ' . $eventData['user'] . /*' password ' . $eventData['pass'] . */ ' from ' . $_SERVER['REMOTE_ADDR'] . ' / ' . $_SERVER['REQUEST_URI'] . " (" . serialize($eventData['ext']) . ")\n";
|
||
|
} elseif (serendipity_userLoggedIn() && !$_SESSION['login_tracked_' . $type]) {
|
||
|
$msg = date('M d H:i:s ') . $_SERVER['HTTP_HOST'] . ' s9y[' . $_SERVER['REMOTE_PORT'] . ']: auth okay: ' . $serendipity['serendipityUser'] . ' from ' . $_SERVER['REMOTE_ADDR'] . ' / ' . $_SERVER['REQUEST_URI'] . "\n";
|
||
|
}
|
||
|
fwrite($fp, $msg);
|
||
|
fclose($fp);
|
||
|
}
|
||
|
|
||
|
if ($type == 'fail') return false;
|
||
|
|
||
|
if (!serendipity_userLoggedIn()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ($_SESSION['login_tracked_' . $type]) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$timestamp = time();
|
||
|
$authorid = (int)$serendipity['authorid'];
|
||
|
$referer = serendipity_db_escape_string($_SERVER['HTTP_REFERER']);
|
||
|
$ip = serendipity_db_escape_string($_SERVER['REMOTE_ADDR']);
|
||
|
$ua = serendipity_db_escape_string($_SERVER['HTTP_USER_AGENT']);
|
||
|
$i = serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}loginlog (timestamp, authorid, action, ip, referer, user_agent)
|
||
|
VALUES ($timestamp, $authorid, '$type', '$ip', '$referer', '$ua')");
|
||
|
$_SESSION['login_tracked_' . $type] = true;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function event_hook($event, &$bag, &$eventData, &$addData) {
|
||
|
global $serendipity;
|
||
|
|
||
|
$hooks = &$bag->get('event_hooks');
|
||
|
if (isset($hooks[$event])) {
|
||
|
switch($event) {
|
||
|
case 'frontend_configure':
|
||
|
if (!serendipity_db_bool($this->get_config('enable_logging'))) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
$this->setupDB();
|
||
|
$this->logger();
|
||
|
break;
|
||
|
|
||
|
case 'backend_configure':
|
||
|
if (!serendipity_db_bool($this->get_config('enable_logging'))) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
$this->logger('backend_login');
|
||
|
break;
|
||
|
|
||
|
case 'backend_loginfail':
|
||
|
$this->logger('fail', $eventData);
|
||
|
break;
|
||
|
|
||
|
case 'backend_auth':
|
||
|
if (!serendipity_db_bool($this->get_config('enable_ldap'))) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
$this->debugmsg('Plugin authentication called.');
|
||
|
|
||
|
$is_md5 =& $eventData;
|
||
|
if ($is_md5) {
|
||
|
$md5_password = $addData['password'];
|
||
|
} else {
|
||
|
$md5_password = md5($addData['password']);
|
||
|
}
|
||
|
|
||
|
if ($_SESSION['serendipityAuthedUser'] == true && serendipity_db_bool($this->get_config('firstlogin'))) {
|
||
|
$this->debugmsg('Already authenticated, do not need to do that again');
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
switch($this->get_config('source')) {
|
||
|
case 'LDAP':
|
||
|
default:
|
||
|
$this->debugmsg('LDAP auth started.');
|
||
|
|
||
|
if (!function_exists('ldap_connect')) {
|
||
|
$this->debugmsg('No LDAP PHP support!');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$port = $this->get_config('port');
|
||
|
if (!empty($port)) {
|
||
|
$ds = ldap_connect($this->get_config('host'), $port);
|
||
|
} else {
|
||
|
$ds = ldap_connect($this->get_config('host'));
|
||
|
}
|
||
|
|
||
|
$this->debugmsg('LDAP connection to ' . $this->get_config('host') . ': ' . print_r($ds, true));
|
||
|
|
||
|
if ($ds) {
|
||
|
ldap_set_option($ds,LDAP_OPT_PROTOCOL_VERSION, 3);
|
||
|
// The following line is needed for MSAD and do no harm elsewhere
|
||
|
ldap_set_option($ds, LDAP_OPT_REFERRALS, 0);
|
||
|
$this->debugmsg('LDAP connection successful.');
|
||
|
if (serendipity_db_bool($this->get_config('ldap_tls'))) {
|
||
|
ldap_start_tls($ds);
|
||
|
}
|
||
|
|
||
|
if ($this->get_config('auth_query') == '') { // Standard LDAP with anonymous access
|
||
|
$rdn = str_replace(array('%1', '%2', '%3'),
|
||
|
array($addData['username'], $addData['password'], $md5_password),
|
||
|
$this->get_config('rdn')
|
||
|
);
|
||
|
$this->debugmsg('LDAP bind call: ' . $rdn);
|
||
|
if ($valid = @ldap_bind($ds, $rdn, $addData['password'])) {
|
||
|
$srch="(objectclass=*)";
|
||
|
$attributes=array("objectclass","mail",$this->get_config('ldap_userlevel_attr'));
|
||
|
if ($res = @ldap_read($ds,$rdn,$srch,$attributes)) {
|
||
|
$e = ldap_first_entry($ds,$res);
|
||
|
$attr = ldap_get_attributes($ds,$e);
|
||
|
$userlevel_attr = $this->get_config('ldap_userlevel_attr');
|
||
|
if ($attr[$userlevel_attr] > -1) {
|
||
|
$valid_userlevel = $attr[$userlevel_attr][0];
|
||
|
} else {
|
||
|
$valid_userlevel = $this->get_config('userlevel');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else { // LDAP with protected access and messy schema
|
||
|
$password = $addData['password'];
|
||
|
//convert password from possible iso-8859 to utf8
|
||
|
//$password = utf8_encode($password);
|
||
|
if ($this->get_config('bind_user')
|
||
|
&& ($valid = @ldap_bind($ds, $this->get_config('bind_user'), $this->get_config('bind_password')))) {
|
||
|
$auth_query = str_replace(array('%1', '%2', '%3'),
|
||
|
array($addData['username'], $password, $md5_password),
|
||
|
$this->get_config('auth_query'));
|
||
|
$this->debugmsg('$auth_query = ' . $auth_query);
|
||
|
if ($r = @ldap_search($ds, $this->get_config('rdn'), $auth_query)) {
|
||
|
$result = @ldap_get_entries( $ds, $r);
|
||
|
if ($result[0]) {
|
||
|
$attr = $result[0];
|
||
|
if (@ldap_bind( $ds, $attr['dn'],$password) ) { // OK
|
||
|
$userlevel_attr = $this->get_config('ldap_userlevel_attr');
|
||
|
if ($attr[$userlevel_attr] > -1) {
|
||
|
$valid_userlevel = $attr[$userlevel_attr][0];
|
||
|
} else {
|
||
|
$valid_userlevel = $this->get_config('userlevel');
|
||
|
}
|
||
|
} else {
|
||
|
$this->debugmsg('Wrong Password');
|
||
|
}
|
||
|
} else {
|
||
|
$this->debugmsg('Invalid Username');
|
||
|
}
|
||
|
} else {
|
||
|
$this->debugmsg('LDAP Search failed');
|
||
|
}
|
||
|
} else {
|
||
|
$this->debugmsg('Wrong LDAP Tool Account');
|
||
|
}
|
||
|
}
|
||
|
if ($valid) {
|
||
|
if ($valid_userlevel > -1) {
|
||
|
$this->debugmsg('Updating author! username='.$addData['username'].', email='.$attr["mail"][0].' realname='.$attr["name"][0]);
|
||
|
// If the ldap user exists and the password is correct, and the user has a valid userlevel OR the default userlevel is valid,
|
||
|
// update the user's record or create it if it doesn't exist.
|
||
|
serendipity_db_query("UPDATE {$serendipity['dbPrefix']}authors
|
||
|
SET password = '" . serendipity_db_escape_string($md5_password) . "', userlevel = " . $valid_userlevel .
|
||
|
" WHERE username = '" . serendipity_db_escape_string($addData['username']) . "'");
|
||
|
if (serendipity_db_matched_rows() == 0 ) {
|
||
|
// create a new one
|
||
|
serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}authors (username, password, email, mail_comments, mail_trackbacks, userlevel, realname)
|
||
|
VALUES ('". serendipity_db_escape_string($addData['username']) ."', '". serendipity_db_escape_string($md5_password) . "', '" . serendipity_db_escape_string($attr["mail"][0]) . "', 1, 1, " . $valid_userlevel . ", '" . serendipity_db_escape_string($attr["name"][0]) . "')");
|
||
|
if ($this->get_config('user_wysiwyg')) {
|
||
|
$this->set_wysiwyg(serendipity_db_escape_string($addData['username']));
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
$this->debugmsg('Updating author, invalidating login.');
|
||
|
// If the username and password are correct in the ldap server, but the userlevel is set to DENY, invalidate the SQL user.
|
||
|
serendipity_db_query("UPDATE {$serendipity['dbPrefix']}authors
|
||
|
SET password = '" . serendipity_db_escape_string(md5(time())) . "'
|
||
|
WHERE username = '" . serendipity_db_escape_string($addData['username']) . "'");
|
||
|
}
|
||
|
// if account does not exist in ldap server, or password is incorrect, return silently
|
||
|
// N.B. - this means that disabling a user in the LDAP server by changing the password will NOT disable the user in the SQL table.
|
||
|
} // end of if ($valid)
|
||
|
ldap_close($ds);
|
||
|
}
|
||
|
$this->debugmsg('LDAP connection close.');
|
||
|
break; // case source ldap (and default)
|
||
|
} // switch source
|
||
|
|
||
|
return true;
|
||
|
break; // case event backend_auth
|
||
|
|
||
|
default:
|
||
|
return false;
|
||
|
break;
|
||
|
} // switch event
|
||
|
} else {
|
||
|
return false;
|
||
|
} // isset hooks 'event'
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* vim: set sts=4 ts=4 expandtab : */
|
||
|
?>
|