7 changed files with 347 additions and 74 deletions
@ -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'); |
||||
} |
||||
} |
||||
} |
||||
|
@ -1,61 +1,208 @@
|
||||
<?php |
||||
require_once 'gatewayBackend.php'; |
||||
require_once 'smsToken.php'; |
||||
|
||||
final class postgresGatewayBackend implements gatewayBackend |
||||
{ |
||||
const GWBE_SUCCESS = 0; |
||||
const GWBE_DBERROR = 1; |
||||
const GWBE_AUTHFAIL = 2; |
||||
const GWBE_INACTIVE = 3; |
||||
const GWBE_SERVERR = 4; |
||||
|
||||
private $dbh = null; |
||||
|
||||
public function __construct($dbHost, $dbUser, $dbPassword, $dbName) |
||||
{ |
||||
$dsn = 'pgsql:host=' . $dbHost . ';dbname=' . $dbName; |
||||
$this->dbh = new PDO($dsn, $dbUser, $dbPassword); |
||||
$dsn = 'pgsql:host=' . $dbHost . ';dbname=' . $dbName . ';user=' . $dbUser . ';password=' . $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()); |
||||
} |
||||
} |
||||
|
||||
public function getToken($username, $password, $ip, $sessionId) |
||||
private function tokenExists($token) |
||||
{ |
||||
$query = 'SELECT id, password FROM users WHERE username = :username:'; |
||||
$query = 'SELECT id FROM tokens WHERE token = ?'; |
||||
$sth = $this->dbh->prepare($query); |
||||
if ($sth->execute(array(':username:' => $username))) |
||||
$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 |
||||
{ |
||||
/* |
||||
audit_log('Unsuccessful login by $username from $ip'); |
||||
audit_log('Could not create token for $username at $ip'); |
||||
return 'Authentication failed. Reason: Internal Server Error'; |
||||
*/ |
||||
$query = 'UPDATE tokens SET start_time = NOW() WHERE token = ?'; |
||||
$sth = $this->dbh->prepare($query); |
||||
$sth->execute(array($token)); |
||||
} |
||||
else |
||||
catch (PDOException $e) |
||||
{ |
||||
throw new Exception('AuthFail', self::GWBE_DBERROR); |
||||
} |
||||
} |
||||
|
||||
public function checkToken($token, $sessionId, $ip) |
||||
public function getToken($username, $password, $ip, $sessionId) |
||||
{ |
||||
return null; |
||||
$urec = array(); |
||||
try |
||||
{ |
||||
$query = 'SELECT id, valid, password FROM users WHERE username = ?'; |
||||
$sth = $this->dbh->prepare($query); |
||||
$sth->execute(array($username)); |
||||
$urec = $sth->fetch(); |
||||
|
||||
if ($urec === false) |
||||
{ |
||||
$this->auditLog($ip, 'login', 'Unsuccessful login with unknown username ' . $username); |
||||
throw new Exception('Authentication failure', self::GWBE_AUTHFAIL); |
||||
} |
||||
if ($urec['valid'] == false) |
||||
{ |
||||
$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 removeToken($token) |
||||
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); |
||||
} |
||||
|
||||
error_log('Unknown Error in checkToken()'); |
||||
throw new Exception('Authentication failed. Reason: Unknown Error'); |
||||
} |
||||
|
||||
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) |
||||
{ |
||||
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) |
||||
{ |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
@ -0,0 +1,6 @@
|
||||
<?php |
||||
interface smsBackend |
||||
{ |
||||
public function sendSMS($recipient, $message); |
||||
} |
||||
|
@ -1,62 +1,95 @@
|
||||
<?php |
||||
require_once 'smsToken.php'; |
||||
|
||||
class smsSender |
||||
{ |
||||
protected $sessionId = null; |
||||
protected $backend; |
||||
protected $dbBackend; |
||||
protected $smsBackend; |
||||
|
||||
public function __construct($backend, $sessionId) |
||||
public function __construct($dbBackend, $smsBackend, $sessionId) |
||||
{ |
||||
$this->sessionId = $sessionId; |
||||
$this->backend = $backend; |
||||
$this->dbBackend = $dbBackend; |
||||
$this->smsBackend = $smsBackend; |
||||
} |
||||
|
||||
public function login($username, $password) |
||||
{ |
||||
$token = ''; |
||||
|
||||
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) |
||||
{ |
||||
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; |
||||
} |
||||
|
||||
public function send($token, $recipient, $message, $passwordLocations) |
||||
{ |
||||
/* |
||||
if (valid_token($token) |
||||
{ |
||||
if (send_sms($recipient, $message)) |
||||
{ |
||||
audit_log('Successful message sending by $token->username at $ip'); |
||||
message_log('$message successfully sent to $recipient'); |
||||
} |
||||
else |
||||
{ |
||||
audit_log('Message sending failed for $token->username at $ip'); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
audit_log('Message sending attempt from $ip with invalid token'); |
||||
throw new Exception('Authentication failed. Reason: Invalid Token'); |
||||
} |
||||
*/ |
||||
/* TODO: implement */ |
||||
throw new Exception('This feature is not yet implemented'); |
||||
try |
||||
{ |
||||
$tokenObj = $this->dbBackend->checkToken($token, $this->sessionId, $_SERVER['REMOTE_ADDR']); |
||||
} |
||||
catch (Exception $e) |
||||
{ |
||||
$this->dbBackend->auditLog($_SERVER['REMOTE_ADDR'], 'send', 'Message sending attempt by invalid token ' . $token); |
||||
throw new Exception('Authentication failed. Reason: Bad Token', 0, $e); |
||||
} |
||||
|
||||
try |
||||
{ |
||||
$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) |
||||
{ |
||||
error_log('SMS sending cannot be logged due to a database error!'); |
||||
$this->dbBackend->auditLog($_SERVER['REMOTE_ADDR'], 'send', 'SMS sending by ' . $tokenObj->getUserName() . ' cannot be logged due to a database error'); |
||||
} |
||||
catch (Exception $e) |
||||
{ |
||||
$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) |
||||
{ |
||||
/* |
||||
delete_token($token); |
||||
audit_log('$token->username logged out at $ip'); |
||||
return 'success'; |
||||
*/ |
||||
/* TODO: implement */ |
||||
throw new Exception('This feature is not yet implemented'); |
||||
try |
||||
{ |
||||
$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'; |
||||
} |
||||
catch (Exception $e) |
||||
{ |
||||
error_log('Logout failed: ' . $e->getMessage()); |
||||
$this->dbBackend->auditLog('Logout failed: ' . $e->getMessage()); |
||||
throw new Exception('Logout failed: Internal Server Error'); |
||||
} |
||||
} |
||||
} |
||||
|
@ -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; |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue