Final(?) version

This commit is contained in:
Polonkai Gergely 2011-09-30 13:42:45 +02:00 committed by Polonkai Gergely
parent 30f450df66
commit 31c6abc039
7 changed files with 345 additions and 72 deletions

View File

@ -23,21 +23,11 @@ interface gatewayBackend
/** /**
* removeToken() Remove a logged out user's token * removeToken() Remove a logged out user's token
* *
* @param String $ip
* @param String $sessionId
* @param String $token * @param String $token
*/ */
public function removeToken($token); public function removeToken($ip, $sessionId, $token);
/**
* sendSMS()
*
* Send SMS message to recipient's phone number
* @param String $token
* @param String $recipient
* @param String $message
* @param Array $passwordLocations
* @return Boolean
*/
public function sendSMS($token, $recipient, $message, $passwordLocations);
/** /**
* auditLog() Log audit messages * auditLog() Log audit messages
@ -51,10 +41,11 @@ interface gatewayBackend
/** /**
* messageLog() Log sent messages * messageLog() Log sent messages
* *
* @param Integer $senderId
* @param String $recipient * @param String $recipient
* @param String $message * @param String $message
* @param String $ip * @param String $ip
*/ */
public function messageLog($recipient, $message, $ip); public function messageLog($senderId, $recipient, $message, $ip);
} }

56
gnokiiSMSBackend.php Normal file
View File

@ -0,0 +1,56 @@
<?php
require_once 'smsBackend.php';
class gnokiiSMSBackend implements smsBackend
{
protected $gnokii_path = '/usr/bin/gnokii';
public function __construct($gnokii_path = null)
{
if ($gnokii_path !== null)
{
$this->gnokii_path = $gnokii_path;
}
if (!is_executable($this->gnokii_path))
{
throw new Exception('Gnokii executable not found (should be at ' . $this->gnokii_path . ')');
}
}
/**
* sendSMS() Sends an SMS message to the given recipient
*
* @param String $recipient
* @param String $message
* @return Boolean
*/
public function sendSMS($recipient, $message)
{
$descriptors = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w'),
);
$cmd = escapeshellcmd($this->gnokii_path) . ' --sendsms ' . escapeshellarg($recipient);
$process = proc_open($cmd, $descriptors, $pipes);
if (is_resource($process))
{
fwrite($pipes[0], $message);
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
$return_value = proc_close($process);
if ($return_value != 0)
{
throw new Exception('Unable to send SMS: ' . $stderr . '; ' . $stdout);
}
}
else
{
throw new Exception('Unable to send SMS: cannot start gnokii');
}
}
}

View File

@ -2,12 +2,13 @@
require_once 'jsonRPCServer.php'; require_once 'jsonRPCServer.php';
require_once 'smsSender.php'; require_once 'smsSender.php';
require_once 'postgresGatewayBackend.php'; require_once 'postgresGatewayBackend.php';
require_once 'gnokiiSMSBackend.php';
session_start(); session_start();
try try
{ {
$backend = new postgresGatewayBackend('localhost', 'sms_gateway', 'quaiy8Zu', 'sms_gateway'); $dbBackend = new postgresGatewayBackend('localhost', 'sms_gateway', 'quaiy8Zu', 'sms_gateway');
} }
catch (PDOException $e) catch (PDOException $e)
{ {
@ -17,11 +18,21 @@ catch (PDOException $e)
try try
{ {
$smsSender = new smsSender(session_id(), $backend); $smsBackend = new gnokiiSMSBackend();
} }
catch (Exception $e) catch (Exception $e)
{ {
header('Status: 500 Internal Server Error (Backend)'); header('Status: 500 Internal Server Error (SMS)');
exit;
}
try
{
$smsSender = new smsSender($dbBackend, $smsBackend, session_id());
}
catch (Exception $e)
{
header('Status: 500 Internal Server Error');
exit; exit;
} }

View File

@ -1,61 +1,208 @@
<?php <?php
require_once 'gatewayBackend.php'; require_once 'gatewayBackend.php';
require_once 'smsToken.php';
final class postgresGatewayBackend implements gatewayBackend final class postgresGatewayBackend implements gatewayBackend
{ {
const GWBE_SUCCESS = 0; const GWBE_SUCCESS = 0;
const GWBE_DBERROR = 1; const GWBE_DBERROR = 1;
const GWBE_AUTHFAIL = 2; const GWBE_AUTHFAIL = 2;
const GWBE_INACTIVE = 3;
const GWBE_SERVERR = 4;
private $dbh = null; private $dbh = null;
public function __construct($dbHost, $dbUser, $dbPassword, $dbName) public function __construct($dbHost, $dbUser, $dbPassword, $dbName)
{ {
$dsn = 'pgsql:host=' . $dbHost . ';dbname=' . $dbName; $dsn = 'pgsql:host=' . $dbHost . ';dbname=' . $dbName . ';user=' . $dbUser . ';password=' . $dbPassword;
$this->dbh = new PDO($dsn, $dbUser, $dbPassword); try
{
$this->dbh = new PDO($dsn, $dbUser, $dbPassword, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
}
catch (PDOException $e)
{
error_log('Unable to connect to database: ' . $e->getMessage());
}
}
private function tokenExists($token)
{
$query = 'SELECT id FROM tokens WHERE token = ?';
$sth = $this->dbh->prepare($query);
$sth->execute($token);
$row = $sth->fetch();
return ($row !== false);
}
private function generateToken()
{
do
{
$str = ' ';
for ($i = 0; $i < 32; $i++)
$str[$i] = chr(rand(32, 126));
$str = sha1($str);
}
while ($this->tokenExists($str));
return $str;
}
protected function refreshToken($token)
{
try
{
$query = 'UPDATE tokens SET start_time = NOW() WHERE token = ?';
$sth = $this->dbh->prepare($query);
$sth->execute(array($token));
}
catch (PDOException $e)
{
}
} }
public function getToken($username, $password, $ip, $sessionId) public function getToken($username, $password, $ip, $sessionId)
{ {
$query = 'SELECT id, password FROM users WHERE username = :username:'; $urec = array();
try
{
$query = 'SELECT id, valid, password FROM users WHERE username = ?';
$sth = $this->dbh->prepare($query); $sth = $this->dbh->prepare($query);
if ($sth->execute(array(':username:' => $username))) $sth->execute(array($username));
$urec = $sth->fetch();
if ($urec === false)
{ {
/* $this->auditLog($ip, 'login', 'Unsuccessful login with unknown username ' . $username);
audit_log('Unsuccessful login by $username from $ip'); throw new Exception('Authentication failure', self::GWBE_AUTHFAIL);
audit_log('Could not create token for $username at $ip');
return 'Authentication failed. Reason: Internal Server Error';
*/
} }
else if ($urec['valid'] == false)
{ {
throw new Exception('AuthFail', self::GWBE_DBERROR); $this->auditLog($ip, 'login', 'Unsuccessful login with disabled username ' . $username);
throw new Exception('Authentication failure', self::GWBE_INVALID);
} }
if ($urec['password'] != crypt($password, $urec['password']))
{
$this->auditLog($ip, 'login', 'Unsuccessful login with bad password for ' . $username);
throw new Exception('Authentication failure', self::GWBE_AUTHFAIL);
}
}
catch (PDOException $e)
{
throw new Exception('AuthFail: ' . $e->getMessage(), self::GWBE_DBERROR, $e);
}
try
{
$query = 'SELECT ip, token FROM tokens WHERE session_id = ?';
$sth = $this->dbh->prepare($query);
$sth->execute($sessionId);
$row = $sth->fetch();
if ($row !== false)
{
if ($row['ip'] != $ip)
{
throw new Exception('Authentication failed. Reason: session used from wrong IP address');
}
$this->refreshToken($token);
return $row['token'];
}
}
catch (PDOException $e)
{
throw new Exception('Authentication failed. Reason: Internal Server Error', 0, $e);
}
try
{
$token = $this->generateToken();
$query = 'INSERT INTO tokens (ip, session_id, token, start_time, user_id) VALUES (?, ?, ?, NOW(), ?)';
$sth = $this->dbh->prepare($query);
$sth->execute(array($ip, $sessionId, $token, $urec['id']));
$this->auditLog($ip, 'login', 'Successful login by ' . $username);
return $token;
}
catch (PDOException $e)
{
error_log('Database error: ' . $e->getMessage());
$this->auditLog($ip, 'login', 'Unable to save token for ' . $username . ': ' . $e->getMessage());
throw new Exception('Authentication failed. Reason: Internal Server Error');
}
/* How did we get here??? */
error_log('Unknown Error in getToken()');
throw new Exception('Unknown Error');
} }
public function checkToken($token, $sessionId, $ip) public function checkToken($token, $sessionId, $ip)
{ {
return null; try
{
$query = 'SELECT users.id AS uid, users.username AS uname FROM tokens LEFT JOIN users ON users.id = tokens.user_id WHERE ip = ? AND session_id = ? AND token = ? AND start_time + interval \'1 hour\' > now()';
$sth = $this->dbh->prepare($query);
$sth->execute(array($ip, $sessionId, $token));
$row = $sth->fetch();
if ($row === false)
{
throw new Exception('Authentication failed. Reason: Invalid Token');
}
$smsToken = new smsToken($row['uid'], $row['uname'], $sessionId, $ip, $token);
return $smsToken;
}
catch (PDOException $e)
{
throw new Exception('Authentication failed. Reason: Internal Server Error', 0, $e);
} }
public function removeToken($token) error_log('Unknown Error in checkToken()');
{ throw new Exception('Authentication failed. Reason: Unknown Error');
return null;
} }
public function sendSMS($token, $recipient, $message, $passwordLocations) public function removeToken($ip, $sessionId, $token)
{ {
return null; try
{
$tokenObj = $this->checkToken($token, $sessionId, $ip);
$query = 'DELETE FROM tokens WHERE token = ?';
$sth = $this->dbh->prepare($query);
$sth->execute(array($token));
return $tokenObj->getUserName();
}
catch (PDOException $e)
{
throw new Exception('Logout failed. Reasone: Internal Server Error');
}
catch (Exception $e)
{
throw new Exception('Authentication failed. Reason: Bad Token');
}
} }
public function auditLog($ip, $event, $message) public function auditLog($ip, $event, $message)
{ {
return null; try
{
$query = 'INSERT INTO audit_log (id, time, ip, event, message) VALUES (DEFAULT, NOW(), ?, ?, ?)';
$sth = $this->dbh->prepare($query);
$sth->execute(array($ip, $event, $message));
}
catch (PDOException $e)
{
error_log('Database error during SMS Audit Logging: ' . $e->getMessage());
}
} }
public function messageLog($recipient, $message, $ip) public function messageLog($senderId, $recipient, $message, $ip)
{ {
return null; try
{
$query = 'INSERT INTO log (id, sender, recipient, time, message, ip) VALUES (DEFAULT, ?, ?, NOW(), ?, ?)';
$sth = $this->dbh->prepare($query);
$sth->execute(array($senderId, $recipient, $message, $ip));
}
catch (PDOException $e)
{
}
} }
} }

6
smsBackend.php Normal file
View File

@ -0,0 +1,6 @@
<?php
interface smsBackend
{
public function sendSMS($recipient, $message);
}

View File

@ -1,62 +1,95 @@
<?php <?php
require_once 'smsToken.php';
class smsSender class smsSender
{ {
protected $sessionId = null; protected $sessionId = null;
protected $backend; protected $dbBackend;
protected $smsBackend;
public function __construct($backend, $sessionId) public function __construct($dbBackend, $smsBackend, $sessionId)
{ {
$this->sessionId = $sessionId; $this->sessionId = $sessionId;
$this->backend = $backend; $this->dbBackend = $dbBackend;
$this->smsBackend = $smsBackend;
} }
public function login($username, $password) public function login($username, $password)
{ {
$token = '';
try try
{ {
$token = $this->backend->getToken($username, $password, $_SERVER['REMOTE_ADDR'], $this->sessionId); $token = $this->dbBackend->getToken($username, $password, $_SERVER['REMOTE_ADDR'], $this->sessionId);
} }
catch (Exception $e) catch (Exception $e)
{ {
throw new Exception('Authentication failed. Reason: ' . $e->getMessage()); throw new Exception('Authentication failed. Reason: ' . $e->getMessage());
} }
$this->backend->auditLog($_SERVER['REMOTE_ADDR'], 'login', 'Successful login by ' . $username); $this->dbBackend->auditLog($_SERVER['REMOTE_ADDR'], 'login', 'Successful login by ' . $username);
return $token; return $token;
} }
public function send($token, $recipient, $message, $passwordLocations) public function send($token, $recipient, $message, $passwordLocations)
{ {
/* try
if (valid_token($token)
{ {
if (send_sms($recipient, $message)) $tokenObj = $this->dbBackend->checkToken($token, $this->sessionId, $_SERVER['REMOTE_ADDR']);
}
catch (Exception $e)
{ {
audit_log('Successful message sending by $token->username at $ip'); $this->dbBackend->auditLog($_SERVER['REMOTE_ADDR'], 'send', 'Message sending attempt by invalid token ' . $token);
message_log('$message successfully sent to $recipient'); throw new Exception('Authentication failed. Reason: Bad Token', 0, $e);
} }
else
try
{ {
audit_log('Message sending failed for $token->username at $ip'); $this->smsBackend->sendSMS($recipient, $message);
$this->dbBackend->auditLog($_SERVER['REMOTE_ADDR'], 'send', 'Successful SMS sending by ' . $tokenObj->getUsername());
$this->dbBackend->messageLog($tokenObj->getUserId(), $recipient, $this->maskPasswords($message, $passwordLocations), $_SERVER['REMOTE_ADDR']);
return 'success';
} }
} catch (PDOException $e)
else
{ {
audit_log('Message sending attempt from $ip with invalid token'); error_log('SMS sending cannot be logged due to a database error!');
throw new Exception('Authentication failed. Reason: Invalid Token'); $this->dbBackend->auditLog($_SERVER['REMOTE_ADDR'], 'send', 'SMS sending by ' . $tokenObj->getUserName() . ' cannot be logged due to a database error');
} }
*/ catch (Exception $e)
/* TODO: implement */ {
throw new Exception('This feature is not yet implemented'); $this->dbBackend->auditLog($_SERVER['REMOTE_ADDR'], 'send', 'Error during SMS sending by user ' . $token->getUserName() . ': ' . $e->getMessage());
error_log('Error during SMS sending: ' . $e->getMessage());
}
throw new Exception('Send failed: Unknown Error');
}
protected function maskPasswords($message, $passwordLocations)
{
$msg = $message;
foreach ($passwordLocations as $loc)
{
$msg = substr_replace($msg, '<masked password>', $loc[0], $loc[1]);
}
return $msg;
} }
public function logout($token) public function logout($token)
{ {
/* try
delete_token($token); {
audit_log('$token->username logged out at $ip'); $username = $this->dbBackend->removeToken($_SERVER['REMOTE_ADDR'], $this->sessionId, $token);
$this->dbBackend->auditLog($_SERVER['REMOTE_ADDR'], 'logout', $username . ' logged out successfully');
session_destroy();
session_id('');
unset($_COOKIE['PHPSESSID']);
return 'success'; return 'success';
*/ }
/* TODO: implement */ catch (Exception $e)
throw new Exception('This feature is not yet implemented'); {
error_log('Logout failed: ' . $e->getMessage());
$this->dbBackend->auditLog('Logout failed: ' . $e->getMessage());
throw new Exception('Logout failed: Internal Server Error');
}
} }
} }

29
smsToken.php Normal file
View File

@ -0,0 +1,29 @@
<?php
class smsToken
{
protected $userId;
protected $userName;
protected $sessionId;
protected $ip;
protected $token;
public function __construct($userId, $userName, $sessionId, $ip, $token)
{
$this->userId = $userId;
$this->userName = $userName;
$this->sessionId = $sessionId;
$this->ip = $ip;
$this->token = $token;
}
public function getUserId()
{
return $this->userId;
}
public function getUserName()
{
return $this->userName;
}
}