diff --git a/gatewayBackend.php b/gatewayBackend.php index a74b319..e1b1a8b 100644 --- a/gatewayBackend.php +++ b/gatewayBackend.php @@ -23,22 +23,12 @@ interface gatewayBackend /** * removeToken() Remove a logged out user's token * + * @param String $ip + * @param String $sessionId * @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 * @@ -51,10 +41,11 @@ interface gatewayBackend /** * messageLog() Log sent messages * - * @param String $recipient - * @param String $message - * @param String $ip + * @param Integer $senderId + * @param String $recipient + * @param String $message + * @param String $ip */ - public function messageLog($recipient, $message, $ip); + public function messageLog($senderId, $recipient, $message, $ip); } diff --git a/gnokiiSMSBackend.php b/gnokiiSMSBackend.php new file mode 100644 index 0000000..1a8da93 --- /dev/null +++ b/gnokiiSMSBackend.php @@ -0,0 +1,56 @@ +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'); + } + } +} + diff --git a/json_sms.php b/json_sms.php index 95cdaea..bc851f8 100644 --- a/json_sms.php +++ b/json_sms.php @@ -2,12 +2,13 @@ require_once 'jsonRPCServer.php'; require_once 'smsSender.php'; require_once 'postgresGatewayBackend.php'; +require_once 'gnokiiSMSBackend.php'; session_start(); try { - $backend = new postgresGatewayBackend('localhost', 'sms_gateway', 'quaiy8Zu', 'sms_gateway'); + $dbBackend = new postgresGatewayBackend('localhost', 'sms_gateway', 'quaiy8Zu', 'sms_gateway'); } catch (PDOException $e) { @@ -17,11 +18,21 @@ catch (PDOException $e) try { - $smsSender = new smsSender(session_id(), $backend); + $smsBackend = new gnokiiSMSBackend(); } 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; } diff --git a/postgresGatewayBackend.php b/postgresGatewayBackend.php index 3e4ae05..a36f5a5 100644 --- a/postgresGatewayBackend.php +++ b/postgresGatewayBackend.php @@ -1,61 +1,208 @@ 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()); + } + } + + 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) { - $query = 'SELECT id, password FROM users WHERE username = :username:'; - $sth = $this->dbh->prepare($query); - if ($sth->execute(array(':username:' => $username))) + $urec = array(); + 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 = '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); + } } - else + catch (PDOException $e) { - throw new Exception('AuthFail', self::GWBE_DBERROR); + 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) { - 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 removeToken($token) + public function removeToken($ip, $sessionId, $token) { - return null; - } - - public function sendSMS($token, $recipient, $message, $passwordLocations) - { - 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) + { + } } } diff --git a/smsBackend.php b/smsBackend.php new file mode 100644 index 0000000..bc5c3fc --- /dev/null +++ b/smsBackend.php @@ -0,0 +1,6 @@ +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) + try { - 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'); - } + $tokenObj = $this->dbBackend->checkToken($token, $this->sessionId, $_SERVER['REMOTE_ADDR']); } - else + catch (Exception $e) { - audit_log('Message sending attempt from $ip with invalid token'); - throw new Exception('Authentication failed. Reason: Invalid Token'); + $this->dbBackend->auditLog($_SERVER['REMOTE_ADDR'], 'send', 'Message sending attempt by invalid token ' . $token); + throw new Exception('Authentication failed. Reason: Bad Token', 0, $e); } - */ - /* TODO: implement */ - throw new Exception('This feature is not yet implemented'); + + 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, '', $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'); + } } } diff --git a/smsToken.php b/smsToken.php new file mode 100644 index 0000000..15acfd2 --- /dev/null +++ b/smsToken.php @@ -0,0 +1,29 @@ +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; + } +} +