<?php
////////////////////////////////////////////////////
// SMTP - PHP SMTP class
//
// Version 1.02
//
// Define an SMTP class that can be used to connect
// and communicate with any SMTP server. It implements
// all the SMTP functions defined in RFC821 except TURN.
//
// Author: Chris Ryan
//
// License: LGPL, see LICENSE
////////////////////////////////////////////////////

/**
 * SMTP is rfc 821 compliant and implements all the rfc 821 SMTP
 * commands except TURN which will always return a not implemented
 * error. SMTP also provides some utility methods for sending mail
 * to an SMTP server.
 * @package PHPMailer
 * @author Chris Ryan
 */
class SMTP
{
    
/**
     *  SMTP server port
     *  @var int
     */
    
var $SMTP_PORT 25;
    
    
/**
     *  SMTP reply line ending
     *  @var string
     */
    
var $CRLF "\r\n";
    
    
/**
     *  Sets whether debugging is turned on
     *  @var bool
     */
    
var $do_debug;       # the level of debug to perform

    /**#@+
     * @access private
     */
    
var $smtp_conn;      # the socket to the server
    
var $error;          # error if any on the last call
    
var $helo_rply;      # the reply the server sent to us for HELO
    /**#@-*/

    /**
     * Initialize the class so that the data is in a known state.
     * @access public
     * @return void
     */
    
function SMTP() {
        
$this->smtp_conn 0;
        
$this->error null;
        
$this->helo_rply null;

        
$this->do_debug 0;
    }

    
/*************************************************************
     *                    CONNECTION FUNCTIONS                  *
     ***********************************************************/

    /**
     * Connect to the server specified on the port specified.
     * If the port is not specified use the default SMTP_PORT.
     * If tval is specified then a connection will try and be
     * established with the server for that number of seconds.
     * If tval is not specified the default is 30 seconds to
     * try on the connection.
     *
     * SMTP CODE SUCCESS: 220
     * SMTP CODE FAILURE: 421
     * @access public
     * @return bool
     */
    
function Connect($host,$port=0,$tval=30) {
        
# set the error val to null so there is no confusion
        
$this->error null;

        
# make sure we are __not__ connected
        
if($this->connected()) {
            
# ok we are connected! what should we do?
            # for now we will just give an error saying we
            # are already connected
            
$this->error =
                array(
"error" => "Already connected to a server");
            return 
false;
        }

        if(empty(
$port)) {
            
$port $this->SMTP_PORT;
        }

        
#connect to the smtp server
        
$this->smtp_conn fsockopen($host,    # the host of the server
                                     
$port,    # the port to use
                                     
$errno,   # error number if any
                                     
$errstr,  # error message if any
                                     
$tval);   # give up after ? secs
        # verify we connected properly
        
if(empty($this->smtp_conn)) {
            
$this->error = array("error" => "Failed to connect to server",
                                 
"errno" => $errno,
                                 
"errstr" => $errstr);
            if(
$this->do_debug >= 1) {
                echo 
"SMTP -> ERROR: " $this->error["error"] .
                         
": $errstr ($errno)" $this->CRLF;
            }
            return 
false;
        }

        
# sometimes the SMTP server takes a little longer to respond
        # so we will give it a longer timeout for the first read
        // Windows still does not have support for this timeout function
        
if(substr(PHP_OS03) != "WIN")
           
socket_set_timeout($this->smtp_conn$tval0);

        
# get any announcement stuff
        
$announce $this->get_lines();

        
# set the timeout  of any socket functions at 1/10 of a second
        //if(function_exists("socket_set_timeout"))
        //   socket_set_timeout($this->smtp_conn, 0, 100000);

        
if($this->do_debug >= 2) {
            echo 
"SMTP -> FROM SERVER:" $this->CRLF $announce;
        }

        return 
true;
    }

    
/**
     * Performs SMTP authentication.  Must be run after running the
     * Hello() method.  Returns true if successfully authenticated.
     * @access public
     * @return bool
     */
    
function Authenticate($username$password) {
        
// Start authentication
        
fputs($this->smtp_conn,"AUTH LOGIN" $this->CRLF);

        
$rply $this->get_lines();
        
$code substr($rply,0,3);

        if(
$code != 334) {
            
$this->error =
                array(
"error" => "AUTH not accepted from server",
                      
"smtp_code" => $code,
                      
"smtp_msg" => substr($rply,4));
            if(
$this->do_debug >= 1) {
                echo 
"SMTP -> ERROR: " $this->error["error"] .
                         
": " $rply $this->CRLF;
            }
            return 
false;
        }

        
// Send encoded username
        
fputs($this->smtp_connbase64_encode($username) . $this->CRLF);

        
$rply $this->get_lines();
        
$code substr($rply,0,3);

        if(
$code != 334) {
            
$this->error =
                array(
"error" => "Username not accepted from server",
                      
"smtp_code" => $code,
                      
"smtp_msg" => substr($rply,4));
            if(
$this->do_debug >= 1) {
                echo 
"SMTP -> ERROR: " $this->error["error"] .
                         
": " $rply $this->CRLF;
            }
            return 
false;
        }

        
// Send encoded password
        
fputs($this->smtp_connbase64_encode($password) . $this->CRLF);

        
$rply $this->get_lines();
        
$code substr($rply,0,3);

        if(
$code != 235) {
            
$this->error =
                array(
"error" => "Password not accepted from server",
                      
"smtp_code" => $code,
                      
"smtp_msg" => substr($rply,4));
            if(
$this->do_debug >= 1) {
                echo 
"SMTP -> ERROR: " $this->error["error"] .
                         
": " $rply $this->CRLF;
            }
            return 
false;
        }

        return 
true;
    }

    
/**
     * Returns true if connected to a server otherwise false
     * @access private
     * @return bool
     */
    
function Connected() {
        if(!empty(
$this->smtp_conn)) {
            
$sock_status socket_get_status($this->smtp_conn);
            if(
$sock_status["eof"]) {
                
# hmm this is an odd situation... the socket is
                # valid but we aren't connected anymore
                
if($this->do_debug >= 1) {
                    echo 
"SMTP -> NOTICE:" $this->CRLF .
                         
"EOF caught while checking if connected";
                }
                
$this->Close();
                return 
false;
            }
            return 
true# everything looks good
        
}
        return 
false;
    }

    
/**
     * Closes the socket and cleans up the state of the class.
     * It is not considered good to use this function without
     * first trying to use QUIT.
     * @access public
     * @return void
     */
    
function Close() {
        
$this->error null# so there is no confusion
        
$this->helo_rply null;
        if(!empty(
$this->smtp_conn)) {
            
# close the connection and cleanup
            
fclose($this->smtp_conn);
            
$this->smtp_conn 0;
        }
    }


    
/***************************************************************
     *                        SMTP COMMANDS                       *
     *************************************************************/

    /**
     * Issues a data command and sends the msg_data to the server
     * finializing the mail transaction. $msg_data is the message
     * that is to be send with the headers. Each header needs to be
     * on a single line followed by a <CRLF> with the message headers
     * and the message body being seperated by and additional <CRLF>.
     *
     * Implements rfc 821: DATA <CRLF>
     *
     * SMTP CODE INTERMEDIATE: 354
     *     [data]
     *     <CRLF>.<CRLF>
     *     SMTP CODE SUCCESS: 250
     *     SMTP CODE FAILURE: 552,554,451,452
     * SMTP CODE FAILURE: 451,554
     * SMTP CODE ERROR  : 500,501,503,421
     * @access public
     * @return bool
     */
    
function Data($msg_data) {
        
$this->error null# so no confusion is caused

        
if(!$this->connected()) {
            
$this->error = array(
                    
"error" => "Called Data() without being connected");
            return 
false;
        }

        
fputs($this->smtp_conn,"DATA" $this->CRLF);

        
$rply $this->get_lines();
        
$code substr($rply,0,3);

        if(
$this->do_debug >= 2) {
            echo 
"SMTP -> FROM SERVER:" $this->CRLF $rply;
        }

        if(
$code != 354) {
            
$this->error =
                array(
"error" => "DATA command not accepted from server",
                      
"smtp_code" => $code,
                      
"smtp_msg" => substr($rply,4));
            if(
$this->do_debug >= 1) {
                echo 
"SMTP -> ERROR: " $this->error["error"] .
                         
": " $rply $this->CRLF;
            }
            return 
false;
        }

        
# the server is ready to accept data!
        # according to rfc 821 we should not send more than 1000
        # including the CRLF
        # characters on a single line so we will break the data up
        # into lines by \r and/or \n then if needed we will break
        # each of those into smaller lines to fit within the limit.
        # in addition we will be looking for lines that start with
        # a period '.' and append and additional period '.' to that
        # line. NOTE: this does not count towards are limit.

        # normalize the line breaks so we know the explode works
        
$msg_data str_replace("\r\n","\n",$msg_data);
        
$msg_data str_replace("\r","\n",$msg_data);
        
$lines explode("\n",$msg_data);

        
# we need to find a good way to determine is headers are
        # in the msg_data or if it is a straight msg body
        # currently I'm assuming rfc 822 definitions of msg headers
        # and if the first field of the first line (':' sperated)
        # does not contain a space then it _should_ be a header
        # and we can process all lines before a blank "" line as
        # headers.
        
$field substr($lines[0],0,strpos($lines[0],":"));
        
$in_headers false;
        if(!empty(
$field) && !strstr($field," ")) {
            
$in_headers true;
        }

        
$max_line_length 998# used below; set here for ease in change

        
while(list(,$line) = @each($lines)) {
            
$lines_out null;
            if(
$line == "" && $in_headers) {
                
$in_headers false;
            }
            
# ok we need to break this line up into several
            # smaller lines
            
while(strlen($line) > $max_line_length) {
                
$pos strrpos(substr($line,0,$max_line_length)," ");

                
# Patch to fix DOS attack
                
if(!$pos) {
                    
$pos $max_line_length 1;
                }

                
$lines_out[] = substr($line,0,$pos);
                
$line substr($line,$pos 1);
                
# if we are processing headers we need to
                # add a LWSP-char to the front of the new line
                # rfc 822 on long msg headers
                
if($in_headers) {
                    
$line "\t" $line;
                }
            }
            
$lines_out[] = $line;

            
# now send the lines to the server
            
while(list(,$line_out) = @each($lines_out)) {
                if(
strlen($line_out) > 0)
                {
                    if(
substr($line_out01) == ".") {
                        
$line_out "." $line_out;
                    }
                }
                
fputs($this->smtp_conn,$line_out $this->CRLF);
            }
        }

        
# ok all the message data has been sent so lets get this
        # over with aleady
        
fputs($this->smtp_conn$this->CRLF "." $this->CRLF);

        
$rply $this->get_lines();
        
$code substr($rply,0,3);

        if(
$this->do_debug >= 2) {
            echo 
"SMTP -> FROM SERVER:" $this->CRLF $rply;
        }

        if(
$code != 250) {
            
$this->error =
                array(
"error" => "DATA not accepted from server",
                      
"smtp_code" => $code,
                      
"smtp_msg" => substr($rply,4));
            if(
$this->do_debug >= 1) {
                echo 
"SMTP -> ERROR: " $this->error["error"] .
                         
": " $rply $this->CRLF;
            }
            return 
false;
        }
        return 
true;
    }

    
/**
     * Expand takes the name and asks the server to list all the
     * people who are members of the _list_. Expand will return
     * back and array of the result or false if an error occurs.
     * Each value in the array returned has the format of:
     *     [ <full-name> <sp> ] <path>
     * The definition of <path> is defined in rfc 821
     *
     * Implements rfc 821: EXPN <SP> <string> <CRLF>
     *
     * SMTP CODE SUCCESS: 250
     * SMTP CODE FAILURE: 550
     * SMTP CODE ERROR  : 500,501,502,504,421
     * @access public
     * @return string array
     */
    
function Expand($name) {
        
$this->error null# so no confusion is caused

        
if(!$this->connected()) {
            
$this->error = array(
                    
"error" => "Called Expand() without being connected");
            return 
false;
        }

        
fputs($this->smtp_conn,"EXPN " $name $this->CRLF);

        
$rply $this->get_lines();
        
$code substr($rply,0,3);

        if(
$this->do_debug >= 2) {
            echo 
"SMTP -> FROM SERVER:" $this->CRLF $rply;
        }

        if(
$code != 250) {
            
$this->error =
                array(
"error" => "EXPN not accepted from server",
                      
"smtp_code" => $code,
                      
"smtp_msg" => substr($rply,4));
            if(
$this->do_debug >= 1) {
                echo 
"SMTP -> ERROR: " $this->error["error"] .
                         
": " $rply $this->CRLF;
            }
            return 
false;
        }

        
# parse the reply and place in our array to return to user
        
$entries explode($this->CRLF,$rply);
        while(list(,
$l) = @each($entries)) {
            
$list[] = substr($l,4);
        }

        return 
$list;
    }

    
/**
     * Sends the HELO command to the smtp server.
     * This makes sure that we and the server are in
     * the same known state.
     *
     * Implements from rfc 821: HELO <SP> <domain> <CRLF>
     *
     * SMTP CODE SUCCESS: 250
     * SMTP CODE ERROR  : 500, 501, 504, 421
     * @access public
     * @return bool
     */
    
function Hello($host="") {
        
$this->error null# so no confusion is caused

        
if(!$this->connected()) {
            
$this->error = array(
                    
"error" => "Called Hello() without being connected");
            return 
false;
        }

        
# if a hostname for the HELO wasn't specified determine
        # a suitable one to send
        
if(empty($host)) {
            
# we need to determine some sort of appopiate default
            # to send to the server
            
$host "localhost";
        }

        
// Send extended hello first (RFC 2821)
        
if(!$this->SendHello("EHLO"$host))
        {
            if(!
$this->SendHello("HELO"$host))
                return 
false;
        }

        return 
true;
    }

    
/**
     * Sends a HELO/EHLO command.
     * @access private
     * @return bool
     */
    
function SendHello($hello$host) {
        
fputs($this->smtp_conn$hello " " $host $this->CRLF);

        
$rply $this->get_lines();
        
$code substr($rply,0,3);

        if(
$this->do_debug >= 2) {
            echo 
"SMTP -> FROM SERVER: " $this->CRLF $rply;
        }

        if(
$code != 250) {
            
$this->error =
                array(
"error" => $hello " not accepted from server",
                      
"smtp_code" => $code,
                      
"smtp_msg" => substr($rply,4));
            if(
$this->do_debug >= 1) {
                echo 
"SMTP -> ERROR: " $this->error["error"] .
                         
": " $rply $this->CRLF;
            }
            return 
false;
        }

        
$this->helo_rply $rply;
        
        return 
true;
    }

    
/**
     * Gets help information on the keyword specified. If the keyword
     * is not specified then returns generic help, ussually contianing
     * A list of keywords that help is available on. This function
     * returns the results back to the user. It is up to the user to
     * handle the returned data. If an error occurs then false is
     * returned with $this->error set appropiately.
     *
     * Implements rfc 821: HELP [ <SP> <string> ] <CRLF>
     *
     * SMTP CODE SUCCESS: 211,214
     * SMTP CODE ERROR  : 500,501,502,504,421
     * @access public
     * @return string
     */
    
function Help($keyword="") {
        
$this->error null# to avoid confusion

        
if(!$this->connected()) {
            
$this->error = array(
                    
"error" => "Called Help() without being connected");
            return 
false;
        }

        
$extra "";
        if(!empty(
$keyword)) {
            
$extra " " $keyword;
        }

        
fputs($this->smtp_conn,"HELP" $extra $this->CRLF);

        
$rply $this->get_lines();
        
$code substr($rply,0,3);

        if(
$this->do_debug >= 2) {
            echo 
"SMTP -> FROM SERVER:" $this->CRLF $rply;
        }

        if(
$code != 211 && $code != 214) {
            
$this->error =
                array(
"error" => "HELP not accepted from server",
                      
"smtp_code" => $code,
                      
"smtp_msg" => substr($rply,4));
            if(
$this->do_debug >= 1) {
                echo 
"SMTP -> ERROR: " $this->error["error"] .
                         
": " $rply $this->CRLF;
            }
            return 
false;
        }

        return 
$rply;
    }

    
/**
     * Starts a mail transaction from the email address specified in
     * $from. Returns true if successful or false otherwise. If True
     * the mail transaction is started and then one or more Recipient
     * commands may be called followed by a Data command.
     *
     * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF>
     *
     * SMTP CODE SUCCESS: 250
     * SMTP CODE SUCCESS: 552,451,452
     * SMTP CODE SUCCESS: 500,501,421
     * @access public
     * @return bool
     */
    
function Mail($from) {
        
$this->error null# so no confusion is caused

        
if(!$this->connected()) {
            
$this->error = array(
                    
"error" => "Called Mail() without being connected");
            return 
false;
        }

        
fputs($this->smtp_conn,"MAIL FROM:<" $from ">" $this->CRLF);

        
$rply $this->get_lines();
        
$code substr($rply,0,3);

        if(
$this->do_debug >= 2) {
            echo 
"SMTP -> FROM SERVER:" $this->CRLF $rply;
        }

        if(
$code != 250) {
            
$this->error =
                array(
"error" => "MAIL not accepted from server",
                      
"smtp_code" => $code,
                      
"smtp_msg" => substr($rply,4));
            if(
$this->do_debug >= 1) {
                echo 
"SMTP -> ERROR: " $this->error["error"] .
                         
": " $rply $this->CRLF;
            }
            return 
false;
        }
        return 
true;
    }

    
/**
     * Sends the command NOOP to the SMTP server.
     *
     * Implements from rfc 821: NOOP <CRLF>
     *
     * SMTP CODE SUCCESS: 250
     * SMTP CODE ERROR  : 500, 421
     * @access public
     * @return bool
     */
    
function Noop() {
        
$this->error null# so no confusion is caused

        
if(!$this->connected()) {
            
$this->error = array(
                    
"error" => "Called Noop() without being connected");
            return 
false;
        }

        
fputs($this->smtp_conn,"NOOP" $this->CRLF);

        
$rply $this->get_lines();
        
$code substr($rply,0,3);

        if(
$this->do_debug >= 2) {
            echo 
"SMTP -> FROM SERVER:" $this->CRLF $rply;
        }

        if(
$code != 250) {
            
$this->error =
                array(
"error" => "NOOP not accepted from server",
                      
"smtp_code" => $code,
                      
"smtp_msg" => substr($rply,4));
            if(
$this->do_debug >= 1) {
                echo 
"SMTP -> ERROR: " $this->error["error"] .
                         
": " $rply $this->CRLF;
            }
            return 
false;
        }
        return 
true;
    }

    
/**
     * Sends the quit command to the server and then closes the socket
     * if there is no error or the $close_on_error argument is true.
     *
     * Implements from rfc 821: QUIT <CRLF>
     *
     * SMTP CODE SUCCESS: 221
     * SMTP CODE ERROR  : 500
     * @access public
     * @return bool
     */
    
function Quit($close_on_error=true) {
        
$this->error null# so there is no confusion

        
if(!$this->connected()) {
            
$this->error = array(
                    
"error" => "Called Quit() without being connected");
            return 
false;
        }

        
# send the quit command to the server
        
fputs($this->smtp_conn,"quit" $this->CRLF);

        
# get any good-bye messages
        
$byemsg $this->get_lines();

        if(
$this->do_debug >= 2) {
            echo 
"SMTP -> FROM SERVER:" $this->CRLF $byemsg;
        }

        
$rval true;
        
$e null;

        
$code substr($byemsg,0,3);
        if(
$code != 221) {
            
# use e as a tmp var cause Close will overwrite $this->error
            
$e = array("error" => "SMTP server rejected quit command",
                       
"smtp_code" => $code,
                       
"smtp_rply" => substr($byemsg,4));
            
$rval false;
            if(
$this->do_debug >= 1) {
                echo </