394 lines
11 KiB
PHP
394 lines
11 KiB
PHP
<?php
|
|
|
|
/**
|
|
*
|
|
* @file
|
|
* @author Bennet Bleßmann
|
|
* @author Einhard Leichtfuß
|
|
* @copyright © 2020 Bennet Bleßmann, Einhard Leichtfuß
|
|
* @license GNU General Public Licence 2.0 or later
|
|
*/
|
|
|
|
|
|
require_once(dirname(__FILE__) . "/StudiRegistrationCommon.php");
|
|
|
|
|
|
class StudiRegistration {
|
|
|
|
static function connect_to_ldap()
|
|
{
|
|
global $wgSRLdapServer, $wgSRLdapPwd, $wgSRLdapUser;
|
|
|
|
if (($ldap_conn = ldap_connect($wgSRLdapServer)) === false)
|
|
{
|
|
throw new Exception('Bad LDAP URI.');
|
|
}
|
|
|
|
if (! ldap_set_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, 3))
|
|
{
|
|
throw new Exception('Could not set LDAP protocol version.');
|
|
}
|
|
|
|
if (! ldap_start_tls($ldap_conn))
|
|
{
|
|
throw new Exception('Could not start TLS.');
|
|
}
|
|
|
|
if (! ldap_bind($ldap_conn, $wgSRLdapUser, $wgSRLdapPwd))
|
|
{
|
|
throw new Exception('Could not bind to server. Error is "'
|
|
. ldap_error($ldap_conn) . '"');
|
|
}
|
|
|
|
return $ldap_conn;
|
|
}
|
|
|
|
|
|
static function ldap_search_wrapper($ldap_conn, $filter,
|
|
$required_attributes = array('*'))
|
|
{
|
|
global $wgSRLdapBase;
|
|
|
|
$result = ldap_search($ldap_conn, "{$wgSRLdapBase}", $filter);
|
|
|
|
if ($result === false)
|
|
{
|
|
throw new Exception('LDAP Error: ' . ldap_error($ldap_conn));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
|
|
static function ldap_count_entries_wrapper($ldap_conn, $result)
|
|
{
|
|
$n_entries = ldap_count_entries($ldap_conn, $result);
|
|
|
|
if ($n_entries === false)
|
|
{
|
|
throw new Exception('LDAP Error: ' . ldap_error($ldap_conn));
|
|
}
|
|
|
|
return $n_entries;
|
|
}
|
|
|
|
|
|
static function ldap_get_entries_wrapper($ldap_conn, $result)
|
|
{
|
|
$entries = ldap_get_entries($ldap_conn, $result);
|
|
|
|
if ($entries === false)
|
|
{
|
|
throw new Exception('LDAP Error: ' . ldap_error($ldap_conn));
|
|
}
|
|
|
|
return $entries;
|
|
}
|
|
|
|
|
|
/**
|
|
* Search for a stu-name in the math-studis LDAP directory.
|
|
*
|
|
* Optionally, only consider active users or users with the specified token.
|
|
* Note that such a token is only considered, if $only_active is set to
|
|
* false.
|
|
*
|
|
* Both $stu_name and $token_hash may be null in which case they are not
|
|
* used in the filter.
|
|
*/
|
|
static function ldap_search_stu($ldap_conn, $stu_name, $only_active,
|
|
$token_hash2, $required_attributes = array('dn'))
|
|
{
|
|
$filters = array();
|
|
|
|
if ($stu_name !== null)
|
|
{
|
|
$secure_name = ldap_escape(ACC_PREFIX . $stu_name);
|
|
array_push($filters, "(uid={$secure_name})");
|
|
}
|
|
|
|
if ($only_active)
|
|
{
|
|
array_push($filters, '(!(' . LDAP_TOKEN_ATTR . '=*))');
|
|
}
|
|
else if ($token_hash2 !== null)
|
|
{
|
|
array_push($filters, '('
|
|
. LDAP_TOKEN_ATTR . '=' . ldap_escape($token_hash2)
|
|
. ')');
|
|
}
|
|
|
|
$merged_filter = '(&' . implode('', $filters) . ')';
|
|
|
|
return self::ldap_search_wrapper($ldap_conn, $merged_filter,
|
|
$required_attributes);
|
|
}
|
|
|
|
|
|
static function ldap_exists_stu($ldap_conn, $stu_name, $only_active = false,
|
|
$token_hash2 = null)
|
|
{
|
|
$result = self::ldap_search_stu($ldap_conn, $stu_name, $only_active,
|
|
$token_hash2);
|
|
|
|
return self::ldap_count_entries_wrapper($ldap_conn, $result) != 0;
|
|
}
|
|
|
|
|
|
static function ldap_find_stu_by_token($ldap_conn, $only_active,
|
|
$token_hash2, $required_attributes = array('dn'))
|
|
{
|
|
$result = self::ldap_search_stu($ldap_conn, null, $only_active,
|
|
$token_hash2, $required_attributes);
|
|
|
|
return self::ldap_get_entries_wrapper($ldap_conn, $result);
|
|
}
|
|
|
|
|
|
/**
|
|
* Verify whether an account has been created and enabled for a specific
|
|
* stu-name.
|
|
*
|
|
* Function to be called from the outside.
|
|
* Sets up an LDAP connection and closes it in the end.
|
|
*/
|
|
static function stu_exists($stu_name)
|
|
{
|
|
$ldap_conn = false;
|
|
|
|
try
|
|
{
|
|
$ldap_conn = self::connect_to_ldap();
|
|
return self::ldap_exists_stu($ldap_conn, $stu_name, true);
|
|
}
|
|
catch (Exception $err)
|
|
{
|
|
error_log("{$err}");
|
|
}
|
|
finally
|
|
{
|
|
// Unbind from LDAP if connecting was somehow unsuccessfull.
|
|
if ($ldap_conn)
|
|
ldap_unbind($ldap_conn);
|
|
}
|
|
}
|
|
|
|
|
|
static function create_registration($stu_name, $pw_hash, $token_hash,
|
|
$ml_req)
|
|
{
|
|
global $wgSRLdapBase;
|
|
|
|
$ldap_conn = false;
|
|
|
|
try
|
|
{
|
|
$ldap_conn = self::connect_to_ldap();
|
|
|
|
$stu_number = (int)(explode('stu', $stu_name)[1]);
|
|
$acc_name = ACC_PREFIX . $stu_name;
|
|
$secure_name = ldap_escape($acc_name);
|
|
$token_hash2 = hash(TOKEN_HASH_ALGO, $token_hash);
|
|
|
|
$entry = array(
|
|
'objectClass' => 'inetOrgPerson',
|
|
'uid' => $acc_name,
|
|
'cn' => $acc_name,
|
|
'sn' => $acc_name,
|
|
LDAP_TIMESTAMP_ATTR => time(),
|
|
'roomNumber' => ROOM_NUMBER_OFFSET + $stu_number,
|
|
|
|
// Prepend <!> to pw hash to disable account until
|
|
// registration is complete.
|
|
'userPassword' => "{CRYPT}!{$pw_hash}",
|
|
'employeeType' => 'Mathematik',
|
|
'mail' => $stu_name . MAIL_SUFFIX,
|
|
LDAP_TOKEN_ATTR => $token_hash2,
|
|
LDAP_ML_REQ_ATTR => $ml_req ? 'yes' : 'no'
|
|
);
|
|
|
|
$dn = "uid={$secure_name},{$wgSRLdapBase}";
|
|
|
|
/*
|
|
* If the account exists already (but has not been enabled9, delete
|
|
* it first. One might alternatively consider to replace values;
|
|
* note however, that any potentially superfluous attributes would
|
|
* not be deleted in this case.
|
|
*/
|
|
if (self::ldap_exists_stu($ldap_conn, $stu_name, false))
|
|
{
|
|
if (! ldap_delete($ldap_conn, $dn))
|
|
return false;
|
|
}
|
|
|
|
return ldap_add($ldap_conn, $dn, $entry);
|
|
}
|
|
catch (Exception $err)
|
|
{
|
|
error_log("{$err}");
|
|
}
|
|
finally
|
|
{
|
|
if ($ldap_conn)
|
|
ldap_unbind($ldap_conn);
|
|
}
|
|
}
|
|
|
|
|
|
static function complete_registration($token_hash, &$ml_error)
|
|
{
|
|
$ldap_conn = false;
|
|
$ml_error = false;
|
|
|
|
try
|
|
{
|
|
$ldap_conn = self::connect_to_ldap();
|
|
|
|
$token_hash2 = hash(TOKEN_HASH_ALGO, $token_hash);
|
|
|
|
// Note that PHP's ldap_search() is awkward in that it requires and
|
|
// returns all attribute names in lower case.
|
|
// This does not seem to apply to ldap_mod_replace().
|
|
$entries = self::ldap_find_stu_by_token($ldap_conn, false,
|
|
$token_hash2,
|
|
array('dn', 'uid', 'userpassword', 'mail',
|
|
strtolower(LDAP_TIMESTAMP_ATTR),
|
|
strtolower(LDAP_ML_REQ_ATTR))
|
|
);
|
|
|
|
if (count($entries) != 0)
|
|
{
|
|
$time_now = time();
|
|
$time_then = $entries[0][strtolower(LDAP_TIMESTAMP_ATTR)][0];
|
|
|
|
if ($time_now < $time_then)
|
|
{
|
|
throw new Exception("Expiry date set in the future.");
|
|
return false;
|
|
}
|
|
else if ($time_now - $time_then > TOKEN_EXPIRY_SECONDS)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Remove <!> before pw hash to enable account.
|
|
$enabledPassword = str_replace('{CRYPT}!', '{CRYPT}',
|
|
$entries[0]['userpassword'][0]);
|
|
|
|
// Associating an ettribute with the empty list / array
|
|
// effectively deletes the respective attribute.
|
|
$update = array( LDAP_TOKEN_ATTR => array()
|
|
, LDAP_TIMESTAMP_ATTR => array()
|
|
, LDAP_ML_REQ_ATTR => array()
|
|
, 'userPassword' => $enabledPassword
|
|
);
|
|
|
|
ldap_mod_replace($ldap_conn, $entries[0]['dn'], $update);
|
|
|
|
$acc_name = $entries[0]['uid'][0];
|
|
$ml_req = $entries[0][strtolower(LDAP_ML_REQ_ATTR)][0]
|
|
=== 'yes';
|
|
|
|
if ($ml_req)
|
|
{
|
|
$stu_mail = $entries[0]['mail'][0];
|
|
if (! self::add_user_to_ml($stu_mail))
|
|
{
|
|
$ml_error = true;
|
|
}
|
|
}
|
|
|
|
return $acc_name;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
catch (Exception $err)
|
|
{
|
|
error_log("{$err}");
|
|
}
|
|
finally
|
|
{
|
|
if ($ldap_conn)
|
|
ldap_unbind($ldap_conn);
|
|
}
|
|
}
|
|
|
|
|
|
static function add_user_to_ml($addr)
|
|
{
|
|
$pipe_desc = array(
|
|
0 => array("pipe", "r"), //STDIN
|
|
1 => array("pipe", "w") //STDOUT
|
|
);
|
|
|
|
$ssh_proc = proc_open('ssh bosch', $pipe_desc, $pipes);
|
|
|
|
fwrite($pipes[0], $addr);
|
|
fclose($pipes[0]);
|
|
$output = stream_get_contents($pipes[1]);
|
|
fclose($pipes[1]);
|
|
|
|
//make sure pipes are closed first otherwise risk deadlock
|
|
$retval = proc_close($ssh_proc);
|
|
|
|
// If already subscribed, a corresponding message is printed
|
|
// (i.e., $output non-empty).
|
|
return $retval === 0 && $output === '';
|
|
}
|
|
}
|
|
|
|
|
|
if (! defined('MEDIAWIKI')) {
|
|
global $wgSRLdapServer, $wgSRLdapPwd, $wgSRLdapUser,$wgSRLdapBase;
|
|
|
|
//TODO init globals
|
|
|
|
//TODO: verify input string lengths.
|
|
// This should probably be outsourced to a common third file.
|
|
|
|
if ($argc < 2)
|
|
die('Missing argument.\n');
|
|
|
|
try
|
|
{
|
|
switch ($argv[1])
|
|
{
|
|
case 'exists':
|
|
if ($argc !== 3)
|
|
{
|
|
die("'exists' requires one additional argument: stu_name\n");
|
|
}
|
|
self::stu_exists($argv[2]);
|
|
break;
|
|
case 'create_registration':
|
|
if ($argc !== 5)
|
|
{
|
|
die("'create_registration' requires three additional"
|
|
. " arguments: stu_name, password_hash and token_hash\n");
|
|
}
|
|
self::create_registration($argv[2], $argv[3], $argv[4]);
|
|
break;
|
|
case 'complete_registration':
|
|
if ($argc !== 4)
|
|
{
|
|
die("'complete_registration' requires two additional "
|
|
. " arguments: stu_name, token_hash\n");
|
|
}
|
|
self::complete_registration($argv[2]);
|
|
break;
|
|
default:
|
|
die("Expected first argument to be one of: 'exists',"
|
|
. " 'create_registration', 'complete_registration'\n");
|
|
}
|
|
}
|
|
catch(Exception $err)
|
|
{
|
|
error_log("{$err}");
|
|
}
|
|
}
|
|
|
|
# vi: tw=80 fo+=t ts=8 sts=0 et sw=4 sta ai
|
|
?>
|