mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-23 08:23:12 +01:00
* EMailAdmin: Postfix tcp-map and Dovecot checkpassword scripts supporting ActiveDirectory using multivalued proxyAddresses attribute as implemented by emailadmin_smtp_ads
This commit is contained in:
parent
2ee2387831
commit
f98770409a
314
emailadmin/doc/dovecot_checkpassword_ads.php
Normal file
314
emailadmin/doc/dovecot_checkpassword_ads.php
Normal file
@ -0,0 +1,314 @@
|
||||
#!/usr/bin/php -Cq
|
||||
<?php
|
||||
/**
|
||||
* EGroupware -checkpasswd for Dovecot and Active Directory
|
||||
*
|
||||
* Quota is stored with "quota:" prefix in multivalued proxyAddresses attribute.
|
||||
* Group-memberships are passed to Dovecot to use them in ACL.
|
||||
*
|
||||
* Reads descriptor 3 through end of file and then closes descriptor 3.
|
||||
* There must be at most 512 bytes of data before end of file.
|
||||
*
|
||||
* The information supplied on descriptor 3 is a login name terminated by \0, a password terminated by \0,
|
||||
* a timestamp terminated by \0, and possibly more data.
|
||||
* There are no other restrictions on the form of the login name, password, and timestamp.
|
||||
*
|
||||
* If the password is unacceptable, checkpassword exits 1. If checkpassword is misused, it may instead exit 2.
|
||||
* If there is a temporary problem checking the password, checkpassword exits 111.
|
||||
*
|
||||
* If the password is acceptable, checkpassword runs prog. prog consists of one or more arguments.
|
||||
*
|
||||
* Following enviroment variables are used by Dovecot:
|
||||
* - SERVICE: contains eg. imap, pop3 or smtp
|
||||
* - TCPLOCALIP and TCPREMOTEIP: Client socket's IP addresses if available
|
||||
* Following is document, but does NOT work:
|
||||
* - MASTER_USER: If master login is attempted. This means that the password contains the master user's password and the normal username contains the user who master wants to log in as.
|
||||
* Found working:
|
||||
* - AUTH_LOGIN_USER: If master login is attempted. This means that username/password are from master, AUTH_LOGIN_USER is user master wants to log in as.
|
||||
*
|
||||
* Following enviroment variables are used on return:
|
||||
* - USER: modified user name
|
||||
* - HOME: mail_home
|
||||
* - EXTRA: userdb extra fields eg. "system_groups_user=... userdb_quota_rule=*:storage=10000"
|
||||
*
|
||||
* @author rb(at)stylite.de
|
||||
* @copyright (c) 2012-13 by rb(at)stylite.de
|
||||
* @package emailadmin
|
||||
* @link http://wiki2.dovecot.org/AuthDatabase/CheckPassword
|
||||
* @link http://cr.yp.to/checkpwd/interface.html
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
// protect from being called via HTTP
|
||||
if (isset($_SERVER['HTTP_HOST'])) die('This is a command line only script!');
|
||||
|
||||
// uncomment to write to log-file, otherwise errors go to stderr
|
||||
//$log = '/var/log/dovecot_checkpassword.log';
|
||||
//$log_verbose = true; // error's are always logged, set to true to log auth failures and success too
|
||||
|
||||
// ldap server settings
|
||||
$ldap_uri = 'ldaps://10.7.102.13/';
|
||||
$ldap_base = 'CN=Users,DC=gruene,DC=intern';
|
||||
$bind_dn = "CN=Administrator,$ldap_base";
|
||||
//$bind_dn = "Administrator@gruene.intern";
|
||||
//$bind_pw = 'secret';
|
||||
$version = 3;
|
||||
$use_tls = false;
|
||||
$search_base = $ldap_base;//'o=%d,dc=egroupware';
|
||||
$passdb_filter = $userdb_filter = '(&(objectCategory=person)(sAMAccountName=%s))';
|
||||
// %d for domain and %s for username given by Dovecot is set automatic
|
||||
$user_attrs = array(
|
||||
'%u' => 'samaccountname', // do NOT remove!
|
||||
// '%n' => 'uidnumber',
|
||||
// '%h' => 'mailmessagestore',
|
||||
'%q' => '{quota:}proxyaddresses',
|
||||
'%x' => 'dn',
|
||||
);
|
||||
$user_name = '%u'; // '%u@%d';
|
||||
$user_home = '/var/dovecot/imap/gruene/%u'; //'/var/dovecot/imap/%d/%u'; // mailbox location
|
||||
$extra = array(
|
||||
'userdb_quota_rule' => '*:bytes=%q',
|
||||
/* only for director
|
||||
'proxy' => 'Y',
|
||||
'nologin' => 'Y',
|
||||
'nopassword' => 'Y',
|
||||
*/
|
||||
);
|
||||
// get host by not set l attribute
|
||||
/* only for director
|
||||
$host_filter = 'o=%d';
|
||||
$host_base = 'dc=egroupware';
|
||||
$host_attr = 'l';
|
||||
$host_default = '10.40.8.200';
|
||||
*/
|
||||
|
||||
// to return Dovecot extra system_groups_user
|
||||
$group_base = $ldap_base;
|
||||
$group_filter = '(&(objectCategory=group)(member=%x))';
|
||||
$group_attr = 'cn';
|
||||
$group_append = ''; //'@%d';
|
||||
|
||||
$master_dn = $bind_dn; //"cn=admin,dc=egroupware";
|
||||
//$domain_master_dn = "cn=admin,o=%d,dc=egroupware";
|
||||
|
||||
ini_set('display_errors',false);
|
||||
error_reporting(E_ALL & ~E_NOTICE);
|
||||
if ($log) ini_set('error_log',$log);
|
||||
|
||||
if ($_SERVER['argc'] < 2)
|
||||
{
|
||||
fwrite(STDERR,"\nUsage: {$_SERVER['argv'][0]} prog-to-exec\n\n");
|
||||
fwrite(STDERR,"To test run:\n");
|
||||
fwrite(STDERR,"echo -en 'username\\0000''password\\0000' | {$_SERVER['argv'][0]} env 3<&0 ; echo $?\n");
|
||||
fwrite(STDERR,"echo -en 'username\\0000' | AUTHORIZED=1 {$_SERVER['argv'][0]} env 3<&0 ; echo $?\n");
|
||||
fwrite(STDERR,"echo -en '(dovecode-admin@domain|dovecot|cyrus)\\0000''master-password\\0000' | AUTH_LOGIN_USER=username {$_SERVER['argv'][0]} env 3<&0 ; echo $?\n\n");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
list($username,$password) = explode("\0",file_get_contents('php://fd/3'));
|
||||
if (isset($_SERVER['AUTH_LOGIN_USER']))
|
||||
{
|
||||
$master = $username;
|
||||
$username = $_SERVER['AUTH_LOGIN_USER'];
|
||||
}
|
||||
//error_log("dovecot_checkpassword '{$_SERVER['argv'][1]}': username='$username', password='$password', master='$master'");
|
||||
|
||||
$ds = ldap_connect($ldap_uri);
|
||||
if ($version) ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, $version);
|
||||
if ($use_tls) ldap_start_tls($ds);
|
||||
|
||||
if (!@ldap_bind($ds, $bind_dn, $bind_pw))
|
||||
{
|
||||
error_log("Can't connect to LDAP server $ldap_uri!");
|
||||
exit(111); // 111 = temporary problem
|
||||
}
|
||||
list(,$domain) = explode('@',$username);
|
||||
if (preg_match('/^(.*)\.imapc$/',$domain,$matches))
|
||||
{
|
||||
$domain = $matches[1];
|
||||
|
||||
$username = explode('.', $username);
|
||||
array_pop($username);
|
||||
$username = implode('.',$username);
|
||||
|
||||
$user_home = '/var/tmp/imapc-%d/%s';
|
||||
$extra = array(
|
||||
'userdb_mail' => 'imapc:/var/tmp/imapc-'.$domain.'/'.$username,
|
||||
//'userdb_imapc_password' => $password,
|
||||
//'userdb_imapc_host' => 'hugo.de',
|
||||
);
|
||||
}
|
||||
|
||||
$replace = array(
|
||||
'%d' => $domain,
|
||||
'%s' => $username,
|
||||
);
|
||||
$base = strtr($search_base, $replace);
|
||||
|
||||
if (($passdb_query = !isset($_SERVER['AUTHORIZED']) || $_SERVER['AUTHORIZED'] != 1))
|
||||
{
|
||||
$filter = $passdb_filter;
|
||||
|
||||
// authenticate with master user/password
|
||||
// master user name is hardcoded "dovecot", "cyrus" or "dovecot-admin@domain" and mapped currently to cn=admin,[o=domain,]dc=egroupware
|
||||
if (isset($master))
|
||||
{
|
||||
list($n,$d) = explode('@', $master);
|
||||
if (!($n === 'dovecot-admin' && $d === $domain || in_array($master,array('dovecot','cyrus'))))
|
||||
{
|
||||
// no valid master-user for given domain
|
||||
exit(1);
|
||||
}
|
||||
$dn = $d ? strtr($domain_master_dn,array('%d'=>$domain)) : $master_dn;
|
||||
if (!@ldap_bind($ds, $dn, $password))
|
||||
{
|
||||
if ($log_verbose) error_log("Can't bind as '$dn' with password '$password'! Authentication as master '$master' for user '$username' failed!");
|
||||
exit(111); // 111 = temporary problem
|
||||
}
|
||||
if ($log_verbose) error_log("Authentication as master '$master' for user '$username' succeeded!");
|
||||
$passdb_query = false;
|
||||
$filter = $userdb_filter;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter = $userdb_filter;
|
||||
putenv('AUTHORIZED=2');
|
||||
}
|
||||
$filter = strtr($filter, $replace);
|
||||
|
||||
// remove prefixes eg. "{quota:}proxyaddresses"
|
||||
$attrs = $user_attrs;
|
||||
foreach($attrs as &$a) if ($a[0] == '{') list(,$a) = explode('}', $a);
|
||||
|
||||
if (!($sr = ldap_search($ds, $base, $filter, array_values($attrs))))
|
||||
{
|
||||
error_log("Error ldap_search(\$ds, '$base', '$filter')!");
|
||||
exit(111); // 111 = temporary problem
|
||||
}
|
||||
$entries = ldap_get_entries($ds, $sr);
|
||||
|
||||
if (!$entries['count'])
|
||||
{
|
||||
if ($log_verbose) error_log("User '$username' NOT found!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ($entries['count'] > 1)
|
||||
{
|
||||
// should not happen for passdb, but could happen for aliases ...
|
||||
error_log("Error ldap_search(\$ds, '$base', '$filter') returned more then one user!");
|
||||
exit(111); // 111 = temporary problem
|
||||
}
|
||||
//print_r($entries);
|
||||
|
||||
if ($passdb_query)
|
||||
{
|
||||
// now authenticate user by trying to bind to found dn with given password
|
||||
if (!@ldap_bind($ds, $entries[0]['dn'], $password))
|
||||
{
|
||||
if ($log_verbose) error_log("Can't bind as '{$entries[0]['dn']}' with password '$password'! Authentication for user '$username' failed!");
|
||||
exit(1);
|
||||
}
|
||||
if ($log_verbose) error_log("Successfull authentication user '$username' dn='{$entries[0]['dn']}'.");
|
||||
}
|
||||
else // user-db query, no authentication
|
||||
{
|
||||
if ($log_verbose) error_log("User-db query for user '$username' dn='{$entries[0]['dn']}'.");
|
||||
}
|
||||
|
||||
// add additional placeholders from $user_attrs
|
||||
foreach($user_attrs as $placeholder => $attr)
|
||||
{
|
||||
if ($attr[0] == '{') // prefix given --> ignore all values without and remove it
|
||||
{
|
||||
list($prefix, $attr) = explode('}', substr($attr, 1));
|
||||
foreach($entries[0][$attr] as $key => $value)
|
||||
{
|
||||
if ($key === 'count') continue;
|
||||
if (strpos($value, $prefix) !== 0) continue;
|
||||
$replace[$placeholder] = substr($value, strlen($prefix));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$replace[$placeholder] = is_array($entries[0][$attr]) ? $entries[0][$attr][0] : $entries[0][$attr];
|
||||
}
|
||||
}
|
||||
|
||||
// search memberships
|
||||
if (isset($group_base) && $group_filter && $group_attr)
|
||||
{
|
||||
$base = strtr($group_base, $replace);
|
||||
$filter = strtr($group_filter, $replace);
|
||||
$append = strtr($group_append, $replace);
|
||||
if (($sr = ldap_search($ds, $base, $filter, array($group_attr))) &&
|
||||
($groups = ldap_get_entries($ds, $sr)) && $groups['count'])
|
||||
{
|
||||
//print_r($groups);
|
||||
$system_groups_user = array();
|
||||
foreach($groups as $key => $group)
|
||||
{
|
||||
if ($key === 'count') continue;
|
||||
$system_groups_user[] = $group[$group_attr][0].$append;
|
||||
}
|
||||
$extra['system_groups_user'] = implode(',', $system_groups_user); // todo: check separator
|
||||
}
|
||||
else
|
||||
{
|
||||
error_log("Error searching for memberships ldap_search(\$ds, '$base', '$filter')!");
|
||||
}
|
||||
}
|
||||
|
||||
// set host attribute for director to old imap
|
||||
if (isset($host_base) && isset($host_filter))
|
||||
{
|
||||
if (!($sr = ldap_search($ds, $host_base, $filter=strtr($host_filter,$replace), array($host_attr))))
|
||||
{
|
||||
error_log("Error ldap_search(\$ds, '$host_base', '$filter')!");
|
||||
exit(111); // 111 = temporary problem
|
||||
}
|
||||
$entries = ldap_get_entries($ds, $sr);
|
||||
if ($entries['count'] && !isset($entries[0][$host_attr]))
|
||||
{
|
||||
$extra['host'] = $host_default;
|
||||
}
|
||||
}
|
||||
// close ldap connection
|
||||
ldap_unbind($ds);
|
||||
|
||||
// build command to run
|
||||
array_shift($_SERVER['argv']);
|
||||
$cmd = array_shift($_SERVER['argv']);
|
||||
foreach($_SERVER['argv'] as $arg)
|
||||
{
|
||||
$cmd .= ' '.escapeshellarg($arg);
|
||||
}
|
||||
|
||||
// setting USER, HOME, EXTRA
|
||||
putenv('USER='.strtr($user_name, $replace));
|
||||
if ($user_home) putenv('HOME='.strtr($user_home, $replace));
|
||||
if ($extra)
|
||||
{
|
||||
foreach($extra as $name => $value)
|
||||
{
|
||||
if (($pos = strpos($value,'%')) !== false)
|
||||
{
|
||||
// check if replacement is set, otherwise skip whole extra-value
|
||||
if (!isset($replace[substr($value,$pos,2)]))
|
||||
{
|
||||
unset($extra[$name]);
|
||||
continue;
|
||||
}
|
||||
$value = strtr($value,$replace);
|
||||
}
|
||||
putenv($name.'='.$value);
|
||||
}
|
||||
putenv('EXTRA='.implode(' ', array_keys($extra)));
|
||||
}
|
||||
|
||||
// call given command and exit with it's exit-status
|
||||
passthru($cmd, $ret);
|
||||
|
||||
exit($ret);
|
405
emailadmin/doc/postfix_tcp_map_ads.php
Executable file
405
emailadmin/doc/postfix_tcp_map_ads.php
Executable file
@ -0,0 +1,405 @@
|
||||
#!/usr/bin/php -Cq
|
||||
<?php
|
||||
/**
|
||||
* EGroupware - tcp-map for Postfix and Active Directory
|
||||
*
|
||||
* Using multivalued proxyAddresses attribute as implemented in emailadmin_smtp_ads:
|
||||
* - "smtp:<email>" allows to receive mail for given <email>
|
||||
* (includes aliases AND primary email)
|
||||
* - "forward:<email>" forwards received mail to given <email>
|
||||
* (requires account to have at an "smtp:<email>" value!)
|
||||
* - ("forwardOnly" is used for no local mailbox, only forwards, not implemented!)
|
||||
* - ("quota:<quota>" is used to store quota)
|
||||
*
|
||||
* Groups can be used as distribution lists by assigning them an
|
||||
* email address via there mail attribute (no proxyAddress)
|
||||
*
|
||||
* PROTOCOL DESCRIPTION
|
||||
* The TCP map class implements a very simple protocol: the
|
||||
* client sends a request, and the server sends one reply.
|
||||
* Requests and replies are sent as one line of ASCII text,
|
||||
* terminated by the ASCII newline character. Request and
|
||||
* reply parameters (see below) are separated by whitespace.
|
||||
*
|
||||
* REQUEST FORMAT
|
||||
* Each request specifies a command, a lookup key, and possi-
|
||||
* bly a lookup result.
|
||||
*
|
||||
* get SPACE key NEWLINE
|
||||
* Look up data under the specified key.
|
||||
*
|
||||
* put SPACE key SPACE value NEWLINE
|
||||
* This request is currently not implemented.
|
||||
*
|
||||
* REPLY FORMAT
|
||||
* Each reply specifies a status code and text. Replies must
|
||||
* be no longer than 4096 characters including the newline
|
||||
* terminator.
|
||||
*
|
||||
* 500 SPACE text NEWLINE
|
||||
* In case of a lookup request, the requested data
|
||||
* does not exist. In case of an update request, the
|
||||
* request was rejected. The text describes the
|
||||
* nature of the problem.
|
||||
*
|
||||
* 400 SPACE text NEWLINE
|
||||
* This indicates an error condition. The text
|
||||
* describes the nature of the problem. The client
|
||||
* should retry the request later.
|
||||
*
|
||||
* 200 SPACE text NEWLINE
|
||||
* The request was successful. In the case of a lookup
|
||||
* request, the text contains an encoded version of
|
||||
* the requested data.
|
||||
*
|
||||
* ENCODING
|
||||
* In request and reply parameters, the character %, each
|
||||
* non-printing character, and each whitespace character must
|
||||
* be replaced by %XX, where XX is the corresponding ASCII
|
||||
* hexadecimal character value. The hexadecimal codes can be
|
||||
* specified in any case (upper, lower, mixed).
|
||||
*
|
||||
* The Postfix client always encodes a request. The server
|
||||
* may omit the encoding as long as the reply is guaranteed
|
||||
* to not contain the % or NEWLINE character.
|
||||
*
|
||||
* @author rb(at)stylite.de
|
||||
* @copyright (c) 2012-13 by rb(at)stylite.de
|
||||
* @package emailadmin
|
||||
* @link http://www.postfix.org/tcp_table.5.html
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
// protect from being called via HTTP
|
||||
if (isset($_SERVER['HTTP_HOST'])) die('This is a command line only script!');
|
||||
|
||||
// our defaults
|
||||
$default_host = 'localhost';
|
||||
$verbose = false;
|
||||
|
||||
// allow only clients matching that preg to access, should be only mserver IP
|
||||
//$only_client = '/^10\.40\.8\.210:/';
|
||||
|
||||
// uncomment to write to log-file, otherwise errors go to stderr
|
||||
//$log = 'syslog'; // or not set (stderr) or filename '/var/log/postfix_tcp_map.log';
|
||||
//$log_verbose = true; // error's are always logged, set to true to log failures and success too
|
||||
|
||||
// ldap server settings
|
||||
$ldap_uri = 'ldaps://10.7.102.13/';
|
||||
$base = 'CN=Users,DC=gruene,DC=intern';
|
||||
//$bind_dn = "CN=Administrator,$base";
|
||||
//$bind_dn = "Administrator@gruene.intern";
|
||||
//$bind_pw = 'secret';
|
||||
$version = 3;
|
||||
$use_tls = false;
|
||||
// supported maps
|
||||
$maps = array(
|
||||
// virtual mailbox map
|
||||
'mailboxes' => array(
|
||||
'base' => $base,
|
||||
'filter' => '(&(objectCategory=person)(proxyAddresses=smtp:%s))',
|
||||
'attrs' => 'samaccountname', // result-attrs must be lowercase!
|
||||
'port' => 2001,
|
||||
),
|
||||
// virtual alias maps
|
||||
'aliases' => array(
|
||||
'base' => $base,
|
||||
'filter' => '(&(objectCategory=person)(proxyAddresses=smtp:%s))',
|
||||
'attrs' => array('samaccountname','{forward:}proxyaddresses'),
|
||||
'port' => 2002,
|
||||
),
|
||||
// groups as distribution list
|
||||
'groups' => array(
|
||||
'base' => $base,
|
||||
'filter' => '(&(objectCategory=group)(mail=%s))',
|
||||
'attrs' => 'dn',
|
||||
// continue with resulting dn
|
||||
'filter1' => '(&(objectCategory=person)(proxyAddresses=smtp:*)(memberOf=%s))',
|
||||
'attrs1' => array('samaccountname','{forward:}proxyaddresses'),
|
||||
'port' => 2003,
|
||||
),
|
||||
);
|
||||
|
||||
ini_set('display_errors',false);
|
||||
error_reporting(E_ALL & ~E_NOTICE);
|
||||
if ($log) ini_set('error_log',$log);
|
||||
|
||||
function usage($extra=null)
|
||||
{
|
||||
global $maps;
|
||||
fwrite(STDERR, "\nUsage: $cmd [-v|--verbose] [-h|--help] [-l|--log (syslog|path)] [-q|--query [user@]domain (domains|mailboxes|alias|transport|canonical)] [host]\n\n");
|
||||
fwrite(STDERR, print_r($maps,true)."\n");
|
||||
if ($extra) fwrite(STDERR, "\n\n$extra\n\n");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
$cmd = basename(array_shift($_SERVER['argv']));
|
||||
|
||||
while (($arg = array_shift($_SERVER['argv'])) && $arg[0] == '-')
|
||||
{
|
||||
switch($arg)
|
||||
{
|
||||
case '-v': case '--verbose':
|
||||
$verbose = $log_verbose = true;
|
||||
break;
|
||||
|
||||
case '-h': case '--help':
|
||||
usage();
|
||||
break;
|
||||
|
||||
case '-l': case '--log':
|
||||
$log = array_shift($_SERVER['argv']);
|
||||
break;
|
||||
|
||||
case '-q': case '--query':
|
||||
if (count($_SERVER['argv']) == 2) // need 2 arguments
|
||||
{
|
||||
$request = 'get '.array_shift($_SERVER['argv'])."\n";
|
||||
$map = array_shift($_SERVER['argv']);
|
||||
echo respond($request, $map)."\n";
|
||||
exit;
|
||||
}
|
||||
usage();
|
||||
break;
|
||||
|
||||
default:
|
||||
usage("Unknown option '$arg'!");
|
||||
}
|
||||
}
|
||||
if ($_SERVER['argv']) usage();
|
||||
|
||||
if ($arg)
|
||||
{
|
||||
$host = $arg;
|
||||
}
|
||||
else
|
||||
{
|
||||
$host = $default_host;
|
||||
}
|
||||
|
||||
if ($verbose) echo "using $host\n";
|
||||
|
||||
$servers = $clients = $buffers = array();
|
||||
|
||||
// Create the server socket
|
||||
foreach($maps as $map => $data)
|
||||
{
|
||||
$addr = 'tcp://'.$host.':'.$data['port'];
|
||||
if (!($server = stream_socket_server($addr, $errno, $errstr)))
|
||||
{
|
||||
fwrite(STDERR, date('Y-m-d H:i:s').": Error calling stream_socket_server('$addr')!\n");
|
||||
fwrite(STDERR, $errstr." ($errno)\n");
|
||||
exit($errno);
|
||||
}
|
||||
$servers[$data['port']] = $server;
|
||||
$clients[$data['port']] = array();
|
||||
}
|
||||
while (true) // mail loop of tcp server --> never exits
|
||||
{
|
||||
$read = $servers;
|
||||
if ($clients) $read = array_merge($read, call_user_func_array('array_merge', array_values($clients)));
|
||||
if ($verbose) print 'about to call socket_select(array('.implode(',',$read).', ...) waiting... ';
|
||||
if (stream_select($read, $write=null, $except=null, null)) // null = block forever
|
||||
{
|
||||
foreach($read as $sock)
|
||||
{
|
||||
if (($port = array_search($sock, $servers)) !== false)
|
||||
{
|
||||
$client = stream_socket_accept($sock,$timeout,$client_addr); // @ required to get not timeout warning!
|
||||
|
||||
if ($verbose) echo "accepted connection $client from $client_addr on port $port\n";
|
||||
|
||||
if ($only_client && !preg_match($only_client,$client_addr))
|
||||
{
|
||||
fwrite($client,"Go away!\r\n");
|
||||
fclose($client);
|
||||
error_log(date('Y-m-d H:i:s').": Connection $client from wrong client $client_addr (does NOT match '$only_client') --> terminated");
|
||||
continue;
|
||||
}
|
||||
$clients[$port][] = $client;
|
||||
}
|
||||
elseif (feof($sock)) // client connection closed
|
||||
{
|
||||
if ($verbose) echo "client $sock closed connection\n";
|
||||
|
||||
foreach($clients as $port => &$socks)
|
||||
{
|
||||
if (($key = array_search($sock, $socks, true)) !== false)
|
||||
{
|
||||
unset($socks[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else // client send something
|
||||
{
|
||||
$buffer =& $buffers[$sock];
|
||||
|
||||
$buffer .= fread($sock, 8096);
|
||||
|
||||
if (strpos($buffer, "\n") !== false)
|
||||
{
|
||||
list($request, $buffer) = explode("\n", $buffer, 2);
|
||||
|
||||
foreach($maps as $map => $data)
|
||||
{
|
||||
if (($key = array_search($sock, $clients[$data['port']], true)) !== false)
|
||||
{
|
||||
if ($verbose) echo date('Y-m-d H:i:s').": client send: $request for map $map\n";
|
||||
|
||||
// Respond to client
|
||||
fwrite($sock, respond($request, $map));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($except)
|
||||
{
|
||||
echo "Exception: "; print_r($except);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// timeout expired
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* escapes a string for use in searchfilters meant for ldap_search.
|
||||
*
|
||||
* Escaped Characters are: '*', '(', ')', ' ', '\', NUL
|
||||
* It's actually a PHP-Bug, that we have to escape space.
|
||||
* For all other Characters, refer to RFC2254.
|
||||
*
|
||||
* @param string|array $string either a string to be escaped, or an array of values to be escaped
|
||||
* @return string
|
||||
*/
|
||||
function quote($string)
|
||||
{
|
||||
return str_replace(array('\\','*','(',')','\0',' '),array('\\\\','\*','\(','\)','\\0','\20'),$string);
|
||||
}
|
||||
|
||||
function respond($request, $map, $extra='')
|
||||
{
|
||||
static $ds;
|
||||
global $ldap_uri, $version, $use_tls, $bind_dn, $bind_pw;
|
||||
global $maps, $log_verbose;
|
||||
|
||||
if (($map == 'aliases' || $map == 'groups') && strpos($request,'@') === false && !$extra)
|
||||
{
|
||||
return "500 No domain aliases yet\n";
|
||||
}
|
||||
if (!isset($ds))
|
||||
{
|
||||
$ds = ldap_connect($ldap_uri);
|
||||
if ($version) ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, $version);
|
||||
if ($use_tls) ldap_start_tls($ds);
|
||||
|
||||
if (!@ldap_bind($ds, $bind_dn, $bind_pw))
|
||||
{
|
||||
error_log("$map: Can't connect to LDAP server $ldap_uri!");
|
||||
$ds = null;
|
||||
return "400 Can't connect to LDAP server $ldap_uri!\n"; // 400 (temp.) error
|
||||
}
|
||||
}
|
||||
if (!preg_match('/^get ([^\n]+)\n?$/', $request, $matches))
|
||||
{
|
||||
error_log("$map: Wrong format '$request'!");
|
||||
return "400 Wrong format '$request'!\n"; // 400 (temp.) error
|
||||
}
|
||||
$username = $matches[1];
|
||||
|
||||
list($name,$domain) = explode('@',$username);
|
||||
|
||||
/* check if we are responsible for the given domain
|
||||
if ($domain && $map != 'domains' && (int)($response = respond("get $domain", 'domains')) != 200)
|
||||
{
|
||||
return $response;
|
||||
}*/
|
||||
$replace = array(
|
||||
'%n' => quote($name),
|
||||
'%d' => quote($domain),
|
||||
'%s' => quote($username),
|
||||
);
|
||||
$base = strtr($maps[$map]['base'], $replace);
|
||||
$filter = strtr($maps[$map]['filter'.$extra], $replace);
|
||||
$prefix = isset($maps[$map]['prefix'.$extra]) ? str_replace(array('%n','%d','%s'),array($name,$domain,$username),$maps[$map]['prefix']) : '';
|
||||
$search_attrs = $attrs = (array)$maps[$map]['attrs'.$extra];
|
||||
// remove prefix like "{smtp:}proxyaddresses"
|
||||
foreach($search_attrs as &$attr)
|
||||
{
|
||||
if ($attr[0] == '{') list(,$attr) = explode('}', $attr);
|
||||
}
|
||||
unset($attr);
|
||||
|
||||
if (!($sr = @ldap_search($ds, $base, $filter, $search_attrs)))
|
||||
{
|
||||
$errno = ldap_errno($ds);
|
||||
$error = ldap_error($ds).' ('.$errno.')';
|
||||
|
||||
if ($errno == -1) // eg. -1 lost connection to ldap
|
||||
{
|
||||
error_log("$map: get '$username' --> 400 $error: !ldap_search(\$ds, '$base', '$filter')");
|
||||
ldap_close($ds);
|
||||
$ds = null; // force new connection on next lookup
|
||||
return "400 $error\n"; // 400 (temp.) error
|
||||
}
|
||||
else // happens if base containing domain does not exist
|
||||
{
|
||||
if ($log_verbose) error_log("$map: get '$username' --> 500 Not found: $error: !ldap_search(\$ds, '$base', '$filter')");
|
||||
return "500 Not found: $error\n"; // 500 not found
|
||||
}
|
||||
}
|
||||
$entries = ldap_get_entries($ds, $sr);
|
||||
|
||||
if (!$entries['count'])
|
||||
{
|
||||
if ($log_verbose) error_log("$map: get '$username' --> 500 not found");
|
||||
return "500 Not found\n"; // 500: Query returned no result
|
||||
}
|
||||
$response = array();
|
||||
foreach($entries as $key => $entry)
|
||||
{
|
||||
if ($key === 'count') continue;
|
||||
|
||||
foreach($attrs as $attr)
|
||||
{
|
||||
unset($filter_prefix);
|
||||
if ($attr[0] == '{')
|
||||
{
|
||||
list($filter_prefix, $attr) = explode('}', substr($attr, 1));
|
||||
}
|
||||
foreach((array)$entry[$attr] as $k => $mail)
|
||||
{
|
||||
if ($k !== 'count' && ($mail = trim($mail)))
|
||||
{
|
||||
if ($filter_prefix)
|
||||
{
|
||||
if (stripos($mail, $filter_prefix) === 0)
|
||||
{
|
||||
$mail = substr($mail, strlen($filter_prefix));
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$response[] = isset($maps[$map]['return']) ? $maps[$map]['return'] : $prefix.$mail;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$response)
|
||||
{
|
||||
if ($log_verbose) error_log("$map: get '$username' --> 500 not found");
|
||||
return "500 Not found\n"; // 500: Query returned no result
|
||||
}
|
||||
if (isset($maps[$map]['filter'.(1+$extra)]) && isset($maps[$map]['attrs'.(1+$extra)]))
|
||||
{
|
||||
return respond('get '.$response[0], $map, 1+$extra);
|
||||
}
|
||||
$response = '200 '.implode(',',$response)."\n";
|
||||
if ($log_verbose) error_log("$map: get '$username' --> $response");
|
||||
return $response;
|
||||
}
|
Loading…
Reference in New Issue
Block a user