<?php
/*
This file is part of Thomas Bley's moregroupware webmail2/calendar2/admin2 module. (tb-mgw)

More information:
-----------------

Simple Groupware Solutions Thomas Bley
Fichtenstr. 23
Germany, 85640 Putzbrunn

Contact:
--------

info@simple-groupware.de

http://www.simple-groupware.de


Copyright 2002-2003 Thomas Bley
tb-mgw is distributed under the terms of the GNU General Public License 

tb-mgw 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.

tb-mgw 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 tb-mgw; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

// class_smtp
// Moregroupware.org

/***************************************
** Filename.......: class.smtp.php
** Project........: SMTP Class
** Version........: 1.0.5
** Last Modified..: 21 December 2001
***************************************/

	define('SMTP_STATUS_NOT_CONNECTED', 1, TRUE);
	define('SMTP_STATUS_CONNECTED', 2, TRUE);

	class smtp{

		var $authenticated;
		var $connection;
		var $recipients;
		var $headers;
		var $timeout;
		var $errors;
		var $status;
		var $body;
		var $from;
		var $host;
		var $port;
		var $helo;
		var $auth;
		var $user;
		var $pass;

		/***************************************
        ** Constructor function. Arguments:
		** $params - An assoc array of parameters:
		**
		**   host    - The hostname of the smtp server		Default: localhost
		**   port    - The port the smtp server runs on		Default: 25
		**   helo    - What to send as the HELO command		Default: localhost
		**             (typically the hostname of the
		**             machine this script runs on)
		**   auth    - Whether to use basic authentication	Default: FALSE
		**   user    - Username for authentication			Default: <blank>
		**   pass    - Password for authentication			Default: <blank>
		**   timeout - The timeout in seconds for the call	Default: 5
		**             to fsockopen()
        ***************************************/

		function cram_md5_response ($username,$password,$challenge) {
		  $challenge=base64_decode($challenge);
		  $hash=bin2hex($this->hmac_md5($challenge,$password));
		  $response=base64_encode($username . " " . $hash) . "\r\n";
		  return $response;
		}

		function hmac_md5($data, $key='') {
		    if (!$key) {
		      return pack('H*',md5($data));
		    }
		    $key = str_pad($key,64,chr(0x00));
		    if (strlen($key) > 64) {
		      $key = pack("H*",md5($key));
		    }
		    $k_ipad =  $key ^ str_repeat(chr(0x36), 64) ;
		    $k_opad =  $key ^ str_repeat(chr(0x5c), 64) ;
		    $hmac=$this->hmac_md5($k_opad . pack("H*",md5($k_ipad . $data)) );
		    return $hmac;
		}

		function smtp($params = array()){

//			if(!defined('CRLF'))
			
			define('CRLF2', "\r\n", TRUE); // \r\n !!!!

			$this->authenticated	= FALSE;			
			$this->timeout			= 5;
			$this->status			= SMTP_STATUS_NOT_CONNECTED;
			$this->host				= 'localhost';
			$this->port				= 25;
			$this->helo				= 'localhost';
			$this->auth				= FALSE;
			$this->user				= '';
			$this->pass				= '';
			$this->errors   		= array();

			foreach($params as $key => $value){
				$this->$key = $value;
			}
		}

		/***************************************
        ** Connect function. This will, when called
		** statically, create a new smtp object, 
		** call the connect function (ie this function)
		** and return it. When not called statically,
		** it will connect to the server and send
		** the HELO command.
        ***************************************/

		function &connect($params = array()){

			if(!isset($this->status)){
				$obj = new smtp($params);
				if($obj->connect()){
					$obj->status = SMTP_STATUS_CONNECTED;
				}

				return $obj;

			}else{
				$this->connection = fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
				if(function_exists('socket_set_timeout')){
					@socket_set_timeout($this->connection, 5, 0);
				}

				$greeting = $this->get_data();
				if(is_resource($this->connection)){
					return $this->auth ? $this->ehlo() : $this->helo();
				}else{
					$this->errors[] = 'Failed to connect to server: '.$errstr;
					return FALSE;
				}
			}
		}

		/***************************************
        ** Function which handles sending the mail.
		** Arguments:
		** $params	- Optional assoc array of parameters.
		**            Can contain:
		**              recipients - Indexed array of recipients
		**              from       - The from address. (used in MAIL FROM:),
		**                           this will be the return path
		**              headers    - Indexed array of headers, one header per array entry
		**              body       - The body of the email
		**            It can also contain any of the parameters from the connect()
		**            function
        ***************************************/

		function send($params = array()){
                
			foreach($params as $key => $value){
				$this->set($key, $value);
			}

			if($this->is_connected()){

				// Do we auth or not? Note the distinction between the auth variable and auth() function
				if($this->auth AND !$this->authenticated){
					if(!$this->auth())
						return FALSE;
				}

				$this->mail($this->from);

				if(is_array($this->recipients))
					foreach($this->recipients as $value)
						$this->rcpt($value);
				else
					$this->rcpt($this->recipients);

				if(!($this->data()))
					return FALSE;

                foreach ($this->headers as $key=>$key2) {
                  if ($key2=="Cc: " or $key2=="References: " or $key2=="In-Reply-To: ") {
                    unset($this->headers[$key]);
                  }
                }
                    
				// Transparency
				$headers = str_replace(CRLF2.'.', CRLF2.'..', trim(implode(CRLF2, $this->headers)));
				$body    = str_replace(CRLF2.'.', CRLF2.'..', $this->body);
				$body    = $body[0] == '.' ? '.'.$body : $body;

				$this->send_data($headers);
				$this->send_data('');

				$this->send_data(str_replace(CRLF,CRLF2,$body),true);
				$this->send_data('.');
				$error = trim($this->get_data());
				$result = (substr($error, 0, 3) == '250' or substr($error,0,3)== '354');
				if (!$result and $error!="") {$this->errors[] = 'Body / Headers failed, output: ' . $error;}

				//$this->rset();
                // die("fertig");
				return $result;
			}else{
				$this->errors[] = 'Not connected!';
				return FALSE;
			}
		}
		
		/***************************************
        ** Function to implement HELO cmd
        ***************************************/

		function helo(){
			if(is_resource($this->connection)
					AND $this->send_data('HELO '.$this->helo)
					AND substr(trim($error = $this->get_data()), 0, 3) === '250' ){

				return TRUE;

			}else{
				$this->errors[] = 'HELO command failed, output: ' . trim(substr(trim($error),3));
				return FALSE;
			}
		}
		
		/***************************************
        ** Function to implement EHLO cmd
        ***************************************/

		function ehlo(){
			if(is_resource($this->connection)
					AND $this->send_data('EHLO '.$this->helo)
					AND substr(trim($error = $this->get_data()), 0, 3) === '250' ){

				return TRUE;

			}else{
				$this->errors[] = 'EHLO command failed, output: ' . trim(substr(trim($error),3));
				return FALSE;
			}
		}
		
		/***************************************
        ** Function to implement RSET cmd
        ***************************************/

		function rset(){
			if(is_resource($this->connection)
					AND $this->send_data('RSET')
					AND substr(trim($error = $this->get_data()), 0, 3) === '250' ){

				return TRUE;

			}else{
				$this->errors[] = 'RSET command failed, output: ' . trim(substr(trim($error),3));
				return FALSE;
			}
		}
		
		/***************************************
        ** Function to implement QUIT cmd
        ***************************************/

		function quit(){
			if(is_resource($this->connection)
					AND $this->send_data('QUIT')
					AND (substr(trim($error = $this->get_data()), 0, 3) === '221' or substr(trim($error),0,3) === '250')){

				fclose($this->connection);
				$this->status = SMTP_STATUS_NOT_CONNECTED;
				return TRUE;

			}else{
				$this->errors[] = 'QUIT command failed, output: ' . trim(substr(trim($error),3));
				return FALSE;
			}
		}
		
		/***************************************
        ** Function to implement AUTH cmd
        ***************************************/

		function auth(){
			if(is_resource($this->connection)

					AND ($this->send_data('AUTH CRAM-MD5')
					AND substr(trim($error = $this->get_data()), 0, 3) === '334'
					AND $this->send_data($this->cram_md5_response($this->user,$this->pass,substr($error,4))))
					AND substr(trim($error = $this->get_data()), 0, 3) === '235'

					or ($this->send_data('AUTH LOGIN')
					AND substr(trim($error = $this->get_data()), 0, 3) === '334'
					AND $this->send_data(base64_encode($this->user))			// Send username
					AND substr(trim($error = $this->get_data()),0,3) === '334'
					AND $this->send_data(base64_encode($this->pass))			// Send password
					AND substr(trim($error = $this->get_data()),0,3) === '235') ){

				$this->authenticated = TRUE;
				return TRUE;

			}else{
				$this->errors[] = 'AUTH command failed: ' . trim(substr(trim($error),3));
				return FALSE;
			}
		}

		/***************************************
        ** Function that handles the MAIL FROM: cmd
        ***************************************/
		
		function mail($from){
			if (strpos($from,"<") === false and strpos($from,">") === false) {$from = "<".$from.">";}
			if($this->is_connected()
				AND $this->send_data('MAIL FROM: '.$from)
				AND (substr(trim($error = $this->get_data()), 0, 3) === '250' or substr(trim($error),0,3)=== '502')){

				return TRUE;

			}else
				$this->errors[] = 'MAIL FROM command failed: ' . trim(substr(trim($error),3));
				return FALSE;
		}

		/***************************************
        ** Function that handles the RCPT TO: cmd
        ***************************************/
		
		function rcpt($to){
			if (strpos($to,"<") === false and strpos($to,">") === false) {$to = "<".$to.">";}
			if($this->is_connected()
				AND $this->send_data('RCPT TO: '.$to)
				AND substr(trim($error = $this->get_data()), 0, 3) === '250' ){

				return TRUE;

			}else{
				$this->errors[] = 'RCPT TO command failed: ' . trim(substr(trim($error),3));
				return FALSE;
			}
		}

		/***************************************
        ** Function that sends the DATA cmd
        ***************************************/

		function data(){

			if($this->is_connected()
				AND $this->send_data('DATA')
				AND (substr(trim($error = $this->get_data()), 0, 3) === '354' or substr(trim($error),0,3) === '250')){
 
				return TRUE;

			}else{
				$this->errors[] = 'DATA command failed: ' . trim(substr(trim($error),3));
				return FALSE;
			}
		}

		/***************************************
        ** Function to determine if this object
		** is connected to the server or not.
        ***************************************/

		function is_connected(){

			return (is_resource($this->connection) AND ($this->status === SMTP_STATUS_CONNECTED));
		}

		/***************************************
        ** Function to send a bit of data
        ***************************************/

		function send_data($data,$check=false){
            global $mail_attachments;

			if(is_resource($this->connection)){
              if ($check and count($mail_attachments)!=0) {

                $data2 = explode("\r\n",$data);
              
                foreach ($data2 as $line) {

                  if (in_array($line,$mail_attachments) and file_exists($line)) {
                    $filename = basename($line);
                    $ext = strtolower(substr($line,-strpos(strrev($line),'.')));
                  
      		        if($fp = fopen($line, 'rb')){
          	          while(!feof($fp)){
                        $line2 = fread($fp, 1440);
                        if ($ext!="eml" and $ext!="ics" and $ext!="vcs" and $filename!="rfc822") {
                            $line2 = rtrim(chunk_split(base64_encode($line2), 72, CRLF2)).CRLF2;
                        } else if ($ext=="eml" or $ext=="ics" and $ext=="vcs") {
                          $line2 = $line2;
						} else {
                          $line2 = str_replace(CRLF,CRLF2,$line2);
                        }
				        $result = fwrite($this->connection, $line2);
			          }
			          fclose($fp);
                    }
                  } else {
				    $result = fwrite($this->connection, $line.CRLF2);
                  }
                }
              } else {
				  return fwrite($this->connection, $data.CRLF2);
              }
			}else
				return FALSE;
		}

		/***************************************
        ** Function to get data.
        ***************************************/

		function &get_data(){

			$return = '';
			$line   = '';
			$loops  = 0;

			if(is_resource($this->connection)){
				while((strpos($return, CRLF2) === FALSE OR substr($line,3,1) !== ' ') AND $loops < 100){
					$line    = fgets($this->connection, 512);
					$return .= $line;
					$loops++;
				}
				return $return;

			}else
				return FALSE;
		}

		/***************************************
        ** Sets a variable
        ***************************************/
		
		function set($var, $value){

			$this->$var = $value;
			return TRUE;
		}

	} // End of class
?>