<?php
/******************************************************************************************************
* Project: PHP IMSP CLIENT LIBRARY
* FILE: cIMSPStream.PHP
* PROVIDES: Basic IMSP connectivity and defines the CIMSPStream object.
*
* Copyright 2002 Michael Rubinsky <mike@theupstairsroom.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
********************************************************************************************************
* Change Log:
*
* 4/16/02: mjr Version 1.1
*
* -Improved the imspError() routine for more informative error messages.
* -Added a showImspErrors property to allow turning on and off the display of the IMSP specific errors.
* (This does not affect the behaviour of PHP errors).
* -Released project under the GNU GPL
*
* 3/19/02: mjr
*
* -Changed the imspRecieve() function making it responsible for determining if the server returns
* a "OK", "BAD" or "NO" response. It returns the words "OK", "BAD", or "NO" if that is the case.
* All other server responses are passed back as-is to the caller. Any failures are still reported
* as a returned FALSE.
*
* 3/31/02: mjr
*
* -Updated the constructor function and the logout() function to use the new imspSend() and
* imspRecieve() routines.
*
* -Seperated out the authentication code into a seperate function to allow for easier future
* support of other Authentication mechs.
*
* -Added beginnings of an error handling scheme.
********************************************************************************************************/
//Constant Definitions
define("IMSP_CRLF","\r\n");
define("IMSP_RESPONSE_TAG_LENGTH",5);
define("IMSP_DEFAULT_RESPONSE_LENGTH",512);
//These define regExp that should match the respective server response strings
define ("IMSP_CONNECTION_OK","^\* OK");
define ("IMSP_LOGIN_OK","OK User");
define ("IMSP_COMMAND_CONTINUATION_RESPONSE","^\+");
//Exit code values
define ("IMSP_EXIT_LOGIN_FAILED","Login to host failed");
define ("IMSP_EXIT_CONNECTION_FAILED","Connection failure");
define ("IMSP_NOT_EXPECTED_RESPONSE", "Did not recieve the expected response from the server.");
define ("IMSP_EXIT_UNEXPECTED_RESPONSE", "Did not recieve the expected response from the server."); //For compatability
define ("IMSP_SYNTAX_ERROR","The server did not understand your request.");
define ("IMSP_BAD_SYNTAX","The server did not understand your request."); //For backwards compatability.
define ("IMSP_NO","Server returned a NO response");
define ("IMSP_EXIT_BAD_ARGUMENT","The wrong type of arguement was passed to this function");
define ("IMSP_ENTRY_LOCKED","That addressbook entry is locked or cannot be unlocked.");
class IMSPStream {
/****************************************************************************************
* Public Property Declarations
****************************************************************************************/
var $imsp_server = "localhost"; //Defaults to "localhost" for the IMSP server host
var $imsp_port = "406"; //...on the default port
var $user; //Property for the username
var $pass; //Holds the user's password
var $auth_method = "PLAIN"; //Set this to the type of Authentication Method to be used. (ONLY PLAIN SUPPORTED AT THIS TIME)
var $exitCode; //Routines may place exit codes into this property for checking by the client application
var $log_name = "imspstream.log"; //The name of the log to write to if logging enabled.
var $logging = true; //Set this to "TRUE" to enable logging.
var $log_level = 2; // 1 = Normal user level logging
// 2 = Include client debugging info (response strings etc..)
var $seperator = "."; //Heirarchy seperator (not used in the class...here as a convienience to the client)
var $showImspErrors = TRUE; //Determines if IMSP errors are passed thru to the client.
/****************************************************************************************
* "Private" or "Friend" Declarations
****************************************************************************************/
var $commandPrefix = "A"; //Holds the current alphabetic portion of command tag
var $commandCount = 1; //Holds the current numeric portion of the command tag
var $tag = ""; //Holds the most current command tag
var $stream; //reference to the file pointer for the IMSP stream
var $log_stream; //file stream to log file;
var $connectionID; //Holds uniques identifier for this connection (used only for logging purposes as of this version)
function IMSPStream($local_imsp_server="", $local_port="") {
// Check for passed in values for server and port number
if ($local_imsp_server) {
$this->imsp_server = $local_imsp_server;
}
if($local_port) {
$this->imsp_port = $local_port;
}
$this->connectionID = time(); //get a somewhat unique identifier...good enough for logging purposes.
}
function login($username,$pass) {
$this->user = $username;
$this->pass = $pass;
if ($this->logging) {
$this->open_log($this->log_name); //open log
$this->write_to_log("Starting IMSP Session");
}
// Now try to open the IMSP connection
if (!$this->imspOpen()) {
$this->imspError();
return FALSE;
}
if ($this->log_stream) {
$this->write_to_log("Connection OK");
}
/*Everything is ok...so try to login to the server
*For now, we do things simply, using LOGIN command
*
*in release version, we will want to support SASL logins
*/
Switch ($this->auth_method) {
case "PLAIN":
if (!$this->imspPlainTextLogin()) {
//Login failed
if($this->exitCode = "") {
$this->exit_code = IMSP_EXIT_LOGIN_FAILED;
}
//$this->imspError();
return FALSE;
}
break;
default:
//must specify an auth method
$this->exit_code = IMSP_EXIT_LOGIN_FAILED;
$this->imspError("Must specify an authentication method in the auth_method property.");
return FALSE;
}
//OK. We are logged in to the server
if ($this->log_stream) {
$this->write_to_log("LOGIN for user $this->user successful.");
}
return TRUE;
}
/****************************************************************************************
*logout() - logs out of the server and closes the IMSP stream
****************************************************************************************/
function logout() {
//Disconnect from the server
if($this->log_stream){
$this->write_to_log("Closing Connection.");
}
$command_string = "LOGOUT"; //Build the command to send to the server
if (!$this->imspSend($command_string)) {
$this->imspError();
return FALSE;
} else {
//Should we test here for the BYE response? Is there a need for this?
fclose($this->stream);
if ($this->log_stream) {
$this->close_log();
}
return TRUE;
}
}
/**************************************************************************************
* IMSP connectivity functions
*************************************************************************************/
function imspOpen() {
// Now try to open the IMSP connection
$fp = fsockopen($this->imsp_server,$this->imsp_port);
// Check for failure
if(!$fp) {
$this->exit_code = IMSP_CONNECTION_FAILED;
$this->imspError();
return FALSE;
}
//save the file pointer
$this->stream = $fp;
//Get The Server Response
$server_response = $this->imspRecieve();
//Check that it is what was expected
if(!ereg(IMSP_CONNECTION_OK,$server_response)) {
$this->exit_code = IMSP_NOT_EXPECTED_RESONSE;
$this->imspError();
fclose($fp);
return FALSE;
}
return TRUE;
}
function imspSend($commandText, $includeTag = TRUE, $sendCRLF=TRUE){
if(!$this->stream){
//no connection!!
$this->exit_code = IMSP_EXIT_CONNECTION_FAILED;
return FALSE;
}
if ($includeTag) {
$this->tag = $this->getNextCommandTag();
$command_text = "$this->tag ";
}
$command_text .= $commandText;
if ($sendCRLF) {
$command_text .= IMSP_CRLF;
}
if ($this->log_stream && $this->log_level > 1) {
$this->write_to_log("[CLIENT COMMAND] $command_text");
}
if (!fputs($this->stream,$command_text)) {
//something wrong with connection?
$this->exit_code = IMSP_EXIT_CONNECTION_FAILED;
return FALSE;
} else {
return TRUE;
//don't forget to report success!
}
}
function imspRecieve() {
//Recieves a single CRLF terminated server status response from the server.
if (!$this->stream){
//no connection!!
$this->exit_code = IMSP_EXIT_CONNECTION_FAILED;
return FALSE;
}
$server_response = trim(fgets($this->stream,IMSP_DEFAULT_RESPONSE_LENGTH));
if ($this->log_stream && $this->log_level > 1) {
$this->write_to_log("[SERVER RESPONSE] $server_response");
}
//should we see if the response is simply an OK, BAD or NO in response to an tagged command?
//Parse out the response
$currentTag = $this->tag;
if (ereg("^" . $currentTag . " NO", $server_response)) {
//Could not perform action.
$this->exitCode = IMSP_NO;
return "NO";
}
if (ereg("^" . $currentTag . " BAD",$server_response)) {
//Bad syntax or entry names
$this->exitCode = IMSP_SYNTAX_ERROR;
return "BAD";
}
if (ereg("^" . $currentTag . " OK",$server_response)) {
return "OK";
}
//If it was not a "NO", "BAD" or "OK" response, then it is up to the
//calling function to decide what to do with it
return $server_response;
}
function getServerResponseChunks(){
//Retrieves a CRLF terminated response from the server and splits it into an array delimeted by a <space> and returns array
$server_response = trim(fgets($this->stream,IMSP_DEFAULT_RESPONSE_LENGTH));
$chunks = split(" ",$server_response);
return $chunks;
}
function recieveStringLiteral($length){
return trim(fread($this->stream,$length));
}
/**************************************************************************************
*Authentication Routines
*************************************************************************************/
function imspPlainTextLogin() {
/*Everything is ok...so try to login to the server
*For now, we do things simply, using LOGIN command
*
*in release version, we will want to support SASL logins
*/
//Now build the command string
$command_string = "LOGIN $this->user $this->pass";
//make sure any logging is at level 1 only to ensure no password information
//will be sent to the log during the imspSend() call.
$logLevel = $this->log_level;
$this->log_level = 1;
if (!$this->imspSend($command_string)) {
$this->exitCode = IMSP_CONNECTION_FAILURE;
return FALSE;
}
//restore original logging level
$this->log_level = $logLevel;
//Get the response
$server_response = $this->imspRecieve();
if ($server_response!="OK") {
//login failed
$this->exitCode = IMSP_LOGIN_FAILED;
return FALSE;
}
return TRUE;
}
/**************************************************************************************
* Utility Functions
*************************************************************************************/
function getNextCommandTag() {
$newtag = $this->commandPrefix;
if ($this->commandCount < 10) {
$newtag .= "000" . (string) $this->commandCount;
} elseif ($this->commandCount < 100){
$newtag .= "00" . (string) $this->commandCount;
} elseif ($this->commandCount < 999) {
$newtag .= "0" . (string) $this->commandCount;
}
//increment it for the next command
$this->commandCount++;
return $newtag;
}
function quoteSpacedString($string) {
if (ereg(" ",$string)) {
return "\"" . $string . "\"";
} else {
return $string;
}
}
function imspError($server_text = "") {
if ($this->log_stream) {
$this->write_to_log("[ERROR]$this->exit_code");
}
if ($this->showImspErrors) {
echo("There was an error with the IMSP service.\n");
echo("\tThe error code is: $this->exitCode\n");
if($server_text != "") {
echo("\tThe following information is also available: $server_text\n");
}
}
//reset the exitCode
$this->exitCode = "";
}
/******************************************************************************************
*Logging functions
*****************************************************************************************/
function open_log($logfile){
$fp = @fopen("log/" . $logfile,"a");
if (!$fp) {
return FALSE;
} else {
$this->log_stream = $fp;
return TRUE;
}
}
function write_to_log($message) {
@fputs($this->log_stream,date("r") . ":[$this->connectionID][$this->user] $message\n");
}
function close_log(){
@fclose($this->log_stream);
}
}//End of Class Definition
?> |