# Mail via SMTP versenden



## Bgag (3. Juni 2008)

Abend!
Ich habe vor langer Zeit mal eine kleine Klasse geschrieben mit der man Mails über Socket-Verbindungen verschicken kann. Dieses Script war eigentlich nur zur Übung gedacht und kam daher auch nie wirklich zum Einsatz. Auf die Idee kam ich durch die Tutorials Sending Email in PHP: The hacker way und Mailen mit PHP. Heute bin ich über dieses Script gestolpert und habe es als erstes auf den Stand von PHP5 gebracht. Da diese Klasse weder eine Möglichkeit zur Authentifizierung noch zum Versenden von Anhängen bietet, wollte ich dies noch hinzufügen. Gerade sitze ich an der Authentifizierung, der auch ein eigenes RFC (2554) gewidmet ist und verzweifel so ein bisschen. Zumindest wird die Mail versendet und kommt auch an. Leider gibt es jetzt einige Probleme.


 Betreff, Datum, Empfänger und Mailer werden in die Mail geschrieben
 Authentifizierung funktioniert nicht
 Fehler bei der Verarbeitung der CC- und BCC-Daten

Wie der Aufruf der Klasse und der Verlauf des Protokolls aussieht, kann man hier sehen. (Werde dann wohl über jeden Klick auf diesen Link per Mail informiert ) In der Klasse sind zudem nützliche Methoden zu finden, die ich in anderen RFC basierten Klassen bereits verwendet habe und mit denen ich eigentlich sehr zufrieden bin. (_log()_, _cmd()_, _getLog()_, _getReply()_). Ich wäre sehr dankbar, wenn jemand mal über die Klasse schauen könnte und mir meine scheinbar geschlossenen Augen öffnen könnte.
MfG, Andy


----------



## Bgag (5. Juni 2008)

Morgen!
Ich habe an der Klasse einiges geändert, doch leider bestehen die oben genannten Fehler immer noch. zudem bekomme ich sehr häufig den Fehler _500 Bad Syntax_. Leider weiß ich nicht woran das liegt, denn eigentlich ist alles korrekt gesendet. Wäre euch echt dankbar für einige Tipps und Anregungen. Das Testscript ist weiterhin online.
MfG, Andy

PS: Der Inhalt der Ankommenden Mail sieht so aus:

```
Subject: Sending mail via PHP!

Date: Thu, 05 Jun 2008 10:03:34 +0200

To: , Andreas2209@web.de

X-Mailer: SMTP Class



Hello. If you can read this message, I was able to send my first mail with my new SmtpConnect-Class. MfG, Andy
```
Ein Betreff existiert nicht und als empfänger steht _undisclosed-recipients:;_

_SmtpConnect.php_

```
<pre>

<?php  
error_reporting(E_ALL);

/***
* The SmtpConnect class allows sending mails via SMTP
*  
* @package SmtpConnect
* @version 0.2
* @author Andreas Wilhelm <Andreas2209@web.de>
* @copyright Andreas Wilhelm
**/  
class SmtpConnect 
{
	// declare class variables
	private $host; 
	private $port;
	private $user;
	private $pwd;
	
	private $cc;
	private $bcc;
	
	private $sock;	
	
	private $log;

	/**
	* Constructor - Is called when the class is instanced
	*
	* @access: public
	* @param Str $host
	* @param Int $port
	* @param Bool $log	
	* @return NONE
	*/
	public function __construct($host='localhost', $port=25)
	{
		// set server-variables
		$this->host = $host;
		$this->port = $port;
	}

	/**
	* connect() - Connects to the given smtp-server
	*
	* @access: public
	* @return Handle
	*/
	public function connect()
	{		
		// control-connection handle is saved to $handle
		$this->sock = @fsockopen($this->host, $this->port);
		if (!$this->sock)
			throw new Exception("Connection failed."); 
		
		// switch to non-blocking mode - just return data no response
		set_socket_blocking($this->sock, true);
		
		// set timeout of the server connection
		stream_set_timeout($this->sock, 0, 200000);
		
		return true;
	}

	/**
	* auth() - Saves the data for authorisation
	*
	* @access: public
	* @param Str $user
	* @param Str $pwd
	* @return NONE
	*/
	public function auth($user, $pwd)
	{
		// save user and password for authorisation
		$this->user = $user;
		$this->pwd = $pwd;
	}
	
	/**
	* setCc() - Sets the carbon copy header
	*
	* @access: public
	* @param Str $cc
	* @return NONE
	*/
	public function setCc($cc)
	{
		if ( !empty($cc) )
		{
      // split string into an array
      $cc = split(",", $cc);
      
      // check if all mail-addresses valid
      foreach($cc as &$mail)
      {
				$mail = trim($mail);
        if( !filter_var($mail, FILTER_VALIDATE_EMAIL) )
        {
          throw new Exception("Invalid mail-address $mail.");
        }
        
        else
        {          
          // modify mail-address
          $mail = "<$mail>";
        }
      }
        
      // create list of mails
      $newCc = implode(",", $cc);
      
      $this->cc = $newCc;
		}
	}
	
	/**
	* setBcc() - Sets the blind carbon copy header
	*
	* @access: public
	* @param Str $bcc
	* @return NONE
	*/
	public function setBcc($bcc)
	{
		if ( !empty($bcc) )
		{
      // split string into an array
      $bcc = split(",", $bcc);
      
      // check if all mail-addresses valid
      foreach($bcc as $key => $mail)
      {
				$mail = trim($mail);
        if( !filter_var($mail, FILTER_VALIDATE_EMAIL) )
        {
          throw new Exception("Invalid mail-address $mail.");
        }
        
        else
        {          
          // modify mail-address
          $mail = "<$mail>";
        }
      }
        
      // create list of mails
      $newBcc = implode(",", $bcc);
      
      $this->bcc = $newBcc;
		}
	}

	/**
	* send() - Checks the  given data and send  a mail via smtp
	*
	* @access: public
	* @param Str $from
	* @param Str $to
	* @param Str $subject
	* @param Str $message
	* @return Boolean
	*/
	public function send($from, $to, $subject, $message)
	{				
		// check if an addressor is specified
		if( empty($from) )
		{
			throw new Exception('No addressor specified.');
		}
		
		// check if mail-address is valid
		if ( !filter_var($from, FILTER_VALIDATE_EMAIL) )
		{
			throw new Exception("Invalid mail-address $from.");
		}
		
		// check if email adress is specified
		if( empty($to) )
		{
			throw new Exception('No email address specified.');
		}
		
		else
		{
      // save mail addresses to an array
      $to = explode(",", $to);
      
      //check if mail-addresses are valid
      foreach($to as &$mail)
      {
        if( !filter_var($mail, FILTER_VALIDATE_EMAIL) )
        {
          throw new Exception("Invalid mail-address $mail.");
        }
        
        // build to-header
        else
        {
          // modify mail-address
          $mail = "<$mail>";     
        }
      }
			
      // create list of mails
      $toHeader = implode(",", $to);
		}		
		
		// check if a subject is set
		if( empty($subject) )
		{
			throw new Exception('Subject is empty.');
		}
		
		// make message RFC821 compliant
		$message = preg_replace("/(?<!\r)\n/si", "\r\n", $message);
		
		// check if a message is given
		if( empty($message) )
		{
			throw new Exception('Message is empty.');
		}

		// connect to the server
		$this->connect();
	
		if($this->sock)
		{
			$this->check('220');
	
			if( !empty($this->user) && !empty($this->pwd) )
			{
				// send EHLO -spezified in RFC 2554
				$this->cmd("EHLO " . $this->host);
				$this->check('250');
		
				$this->cmd("AUTH LOGIN");
				$this->check('334');
				$this->cmd(base64_encode($this->user));
				$this->check('334');
				$this->cmd(base64_encode($this->pwd));
				$this->check('235');
			}
			
			else
			{
				// Send the RFC821 specified HELO.
				$this->cmd('HELO ' . $this->host);
				$this->check('250');
			}
	
			// specify addressor
			$this->cmd("MAIL FROM: <$from>");
			$this->check('250');
	
			// send to-header
      $this->cmd("RCPT TO: $toHeader");
			$this->check('250');
	    
			// initiate data-transfere
			$this->cmd('DATA'); 
	
			// check the reply
			$this->check('354');
	
			// send date
			$this->cmd("Date: ".date('r'));
			$this->check('250');
	
			// specify addressor
			$this->cmd("From: <$from>");
	
			// send subject
			$this->cmd("Subject: $subject");
	
			// send reply-to-header
      $this->cmd("Reply-To: $toHeader");
	
			// send to-header
      $this->cmd("To: $toHeader");
			
			// send cc
      $this->cmd('CC: ' . $this->cc);
			
			// send bcc
      $this->cmd('BCC: ' . $this->bcc);
	
			// smtp mailer-header
			$this->cmd("X-Mailer: SMTP Class\r\n");
	
			// send the message
			$this->cmd("$message\r\n");
	
			// send end parameter
			$this->cmd('.');
			
			if( !$this->check('250') )
			{
				$this->cmd('QUIT');
				fclose($this->sock);
				return false;
			}
	
			// quit and close socket
			$this->cmd("QUIT"); 
			$this->check('221');
			fclose($this->sock);	
			return true;
		}
	    
		else
		{
			return false;
		}
	}

	/**
	* cmd() - Sets a ftp-command given by the user
	*
	* @access: public
	* @param Str $cmd
	* @return NONE
	*/
	public function cmd($cmd)
	{
		fputs($this->sock, "$cmd\r\n");
		$this->log("&gt; $cmd");
	}	
	
	/**
	* getReply() - Gets the reply of the ftp-server
	*
	* @access: public
	* @param Int $i
	* @return String
	*/
	public function getReply($i = 0)
	{
		$go = true;
		$message = "";
		
		do 
		{	
			$tmp = @fgets($this->sock, 1024);
			if($tmp === false) 
			{
				$go = false;
			} 
			
			else 
			{
				$message .= $tmp;
				if( preg_match('/^([0-9]{3})(-(.*[\r\n]{1,2})+\\1)? [^\r\n]+[\r\n]{1,2}$/', $message) ) $go = false;
			}
		} while($go);
		
		$this->log($message);
		
		return $message;
	}

	/**
	* checkControl() - Checks if the response of a command is ok
	*
	* @access: public
	* @param Str $reply
	* @return Boolean
	*/
	public function valid()
	{
		// get response of the server
		$this->response = $this->getReply();
		
		// check the response and say if everything is allright
		return (empty($this->response) || preg_match('/^[5]/', $this->response)) ? false : true;
	}
		
	/**
	* check() - Checks if the response-code is correct
	*
	* @access: public
	* @param Str $code
	* @return Boolean
	*/
	public function check($code)
	{
		if($this->valid())
		{
			$pat = '/^'. $code .'/';
			if( preg_match($pat, $this->response))
			{
				return true;
			}				
		}	
			
		return false;
	}
			
	/**
	* log() - Saves all request to the server and their responses into $this->log
	*
	* @access: private
	* @return NONE
	*/
	private function log($str)
	{
		$this->log .= "$str<br>";
	}
			
	/**
	* getLog() - Prints out all requests to the server and their responses 
	*
	* @access: public
	* @return NONE
	*/
	public function getLog()
	{
		return $this->log;
	}

}

try
{
	$from = 'pseudo@web.de';
	$to = 'Andreas2209@web.de';
	$subject = 'Sending mail via PHP!';
	$message = 'Hello. If you can read this message, I was able to send my first mail with my new SmtpConnect-Class. MfG, Andy';

	$mail = new SmtpConnect('localhost', 25);
	$mail->setCc('psycho@web.de, bombe@gmx.net');
	$mail->setBcc('lol@hotmail.com, gummi@web.de');
	$mail->send($from, $to, $subject, $message);
	echo $mail->getLog();
}

catch(Exception $e) 
{
	echo $e->getMessage();
}
?>

</pre>
```


----------



## Dennis Wronka (5. Juni 2008)

Ich hab nicht nur eine Klasse fuer HTTP, sondern auch eine fuer SMTP. 
Wenn Du's nicht eilig hast solltest Du noch ein paar Stunden warten, denn heut Abend will ich schauen dass ich die aktuelle Release-Fassung hochlade.

Hier gibt es auch ein Tutorial zu der Klasse, aber das basiert auf der ersten Version und ist noch "etwas" fehlerhaft.
Was mich daran erinnert dass ich noch ein paar Tutorials zu aktualisieren habe...


----------



## Bgag (5. Juni 2008)

Wow du hast schon einige Klassen verfasst! Gibts dazu auch ne Übersicht? Beschäftige mich gerade im Rahmen eines Projektes mit den wichtigsten RFCs und zudem möchte ich so auch meine PHP-Kenntnisse verbessern. Dabei soll auch ein besonderes Augenmerk auf OOP liegen.
MfG, Andy


----------



## Loomis (5. Juni 2008)

Der Link zu Dennis` Klassen steht ja in der Signatur ("PHP Class Collection")


----------



## Gumbo (5. Juni 2008)

Ich finde, du solltest die SMTP-Klasse nicht überladen. So sollte die SMTP-Klasse nur für die Kommunikation zwischen Client und SMTP-Server zuständig sein. Der Rest, beispielsweise das Verfassen einer E-Mail-Nachricht, sollte eine andere Instanz übernehmen.


----------



## Bgag (5. Juni 2008)

*@Loomes* Manchmal sollte man sich vielleicht doch die Signaturen der anderen ansehen. Sind schon einige Klasse. Nette Dinge dabei.

*@Gumbo* Verstehe deinen Einwand nicht so ganz. Was meinst du damit?

*@all*
Die Header werden nun richtig gesendet. Die Funktionstüchtigkeit bei Authentifizierung habe ich noch nicht getestet. Der Fehler bei der Verarbeitung besteht noch, habe mich allerdings auch noch nicht darum gekümmert. Es gibt aber noch ein Problem bei der Verarbeitung der Empfänger-Daten. Der betreffende Code-Ausschnitt sieht momentan so aus. Leider gibt es trotz _error_reporting(E_ALL)_ keine Fehlermeldungen.


```
// check if email adress is specified
		if( empty($to) )
		{
			throw new Exception('No email address specified.');
		}
		
		else
		{
      // save mail addresses to an array
      $to = explode(",", $to);
      
      //check if mail-addresses are valid
      foreach($to as &$mail)
      {
        if( !filter_var($mail, FILTER_VALIDATE_EMAIL) )
        {
          throw new Exception("Invalid mail-address $mail.");
        }
        
        // build to-header
        else
        {
          // modify mail-address
          $mail = "<$mail>";     
        }
      }
			
      // create list of mails
      $toHeader = implode(",", $to);
		}
```
MfG, Andy


----------



## Gumbo (5. Juni 2008)

Den Aufgaben deiner Klasse nach zu urteilen, müsste sie eher „EmailComposerAndSubmitterViaSmtp“ heißen, da sie ja nicht nur eine Verbindung zum SMTP-Server aufbaut sondern auch noch eine E-Mail verfasst und diese verschickt.


----------



## Bgag (5. Juni 2008)

Da magst du recht habe, aber ich denke nicht, dass es sehr sinnvoll ist eine reine Verbindung zu einem SMTP-Server herzustellen. Die Klasse macht ja nichts anderes, als gegebene Inhalte über die Verbindung zu versenden. Damit das Sinn macht muss ich diese Inhalte halt auch auf ihre Korrektheit überprüfen. Kann sie ja gerne umtaufen in *SmtpMail()*.
MfG, Andy

PS: Habe oben mal die geänderte Klasse hochgeladen. Hat leider noch ein paar Fehler.


----------



## Dennis Wronka (5. Juni 2008)

Wie Du eventuell gesehen hast hab ich dies bei mir gesplittet.
Grund ist eben der von Gumbo genannte, dass man eben getrennte Klassen fuer spezialisierte Aufgaben hat.
Und man kann die mit meiner Mail-Klasse erstellten Mails ja auch ueber mail() versenden, was ja nun auch in der aktuellen Version mit in der Klasse integriert ist.

Was fuer Probleme hast Du denn noch bei Deiner Klasse? Gibt es Fehlermeldungen?
Wie hast Du das SMTP-Protokoll implementiert? Hast Du Dich lediglich am RFC orientiert oder hast Du auch echten SMTP-Traffic analysiert.
Fuer mich hab ich festgestellt dass ich wesentlich schneller zum Ergebnis (also dem implementierten Protokoll) komme wenn ich mir mit Wireshark echten Traffic anschaue.


----------



## Bgag (5. Juni 2008)

Hallo!
Also ich habe mich vorallem mit dem RFC beschäftigt und mir das Log von meiner Verbindung angeschaut, dass in der Klasse mitgeschrieben wird. Zu den Fehlermeldungen: Es gibt leider keine. Es ist etwas verwirrend. Wie du dem Codeschnipsel entnehmen kannst, den ich als letztes gepostet habe, verarbeite ich die Empfänger-Daten so, dass es keinen Fehler beim Versenden gibt. Das geänderte wird dann in _$toHeader_ gespeichert. Leider ist diese Variable leer. Komischerweise wird die Mail trotzdem versendet und kommt auch an. Weißt du wieso das so ist? Wo liegt der Fehler? Werde die Klasse nachher nochmal so umbasteln, dass eben die Verarbeitung der übergebenen Daten wegfällt und eben nur die Daten gesendet werden. Das mitloggen werde ich allerdings lassen, da ich das ganz praktisch finde. Dann werde ich auch eine Klasse _Mail extends SmtpConnect_ schreiben, die ähnlich der _mail()_-Funktion aufgebaut sein wird. So war doch eure Anmerkung gemeint oder nicht?
MfG, Andy


----------



## Gumbo (5. Juni 2008)

Naja, eine Mail-Klasse ist eigentlich keine Spezialisierung einer SMTP-Klasse, wozu Vererbungen eigentlich verwendet werden. Es ist wenn überhaupt eine Helferklasse, da sie sich nur die Funktionen der SMTP-Klasse zunutze macht. Aber selbst das schränkt den Einsatzradius der Mail-Klasse stark ein.


----------



## Bgag (5. Juni 2008)

ch werde es glaube ich einfach bei einer Klasse belassen. Überarbeiten muss ich die Klasse trotzdem kmplett. Würde mich allerdings sehr freuen, wenn mir jemand bei dem oberen Code-Schnipsel noch helfen könnte. Warum ist nach folgender Zeile das Array $to leer?

```
$mail = "<$mail>";
```
MfG, Andy


----------



## Bgag (22. Juli 2008)

Guten Morgen!
Ich hatte gerade mal wieder Zeit und Lust mich an die SmtpMail-Klasse zu setzen. Ich habe mal deinen Rat Gumbo befolgt und nur die grundlegenden Befehle des Simple Mail Transfer Protocols in einzelnen Methoden zusammengefasst, auf denen man nun weiter aufbauen kann. Nun könnte man zum Beispiel noch eine Klasse Mail schreiben, die mit Hilfe dieser Methoden das verschicken von Mails und vielleicht sogar das hinzufügen von Anhängen vereinfacht. Was haltet ihr denn nun von dieser SMTP-Klasse? Ist sie so wie sie nun ist besser als vorher und ist es auch das , was du meintest Gumbo? Würde mich über Rückmeldungen und Anregungen freuen.
MfG, Andy

*PS:* Ein brauchbarer Beispiel-Aufruf der Klasse ist auch am Ende des Spripts enthalten.


*SmtpMail.php*

```
<?php  
error_reporting(E_ALL);

/***
* The SmtpMail class allows sending mails via SMTP
*  
* @package SmtpMail
* @version 0.3
* @author Andreas Wilhelm <Andreas2209@web.de>
* @copyright Andreas Wilhelm
**/  
class SmtpMail 
{
	// declare class variables
	private $host; 
	private $port;
	
	private $sock;	
	
	private $log;

	/**
	* Constructor - Is called when the class is instanced
	*
	* @access: public
	* @param Str $host
	* @param Int $port
	* @return NONE
	*/
	public function __construct($host='localhost', $port=25)
	{
		// set server-variables
		$this->host = $host;
		$this->port = $port;
	}

	/**
	* connect() - Connects to the given smtp-server
	*
	* @access: public
	* @return Boolean
	*/
	public function connect()
	{		
		// control-connection handle is saved to $handle
		$this->sock = @fsockopen($this->host, $this->port);
		if ( !$this->sock OR !$this->check('220') )
			throw new Exception("Connection failed."); 
		
		// switch to non-blocking mode - just return data no response
		set_socket_blocking($this->sock, true);
		
		// set timeout of the server connection
		stream_set_timeout($this->sock, 0, 200000);
		
		return true;
	}

	/**
	* ehlo() - Sends greeting to secured server
	*
	* @access: public
	* @param Str $user
	* @param Str $pwd
	* @return Boolean
	*/
	public function ehlo($user, $pwd)
	{
		// send EHLO -spezified in RFC 2554
		$this->cmd("EHLO " . $this->host);
		if( !$this->check('250') )
			throw new Exception("Failed to send EHLO.");
		
		// send authentification-identifier
		$this->cmd("AUTH LOGIN");		
		if( !$this->check('334') )
			throw new Exception("Failed to send AUTH.");
		
		// send user-name
		$this->cmd(base64_encode($this->user));		
		if( !$this->check('334') )
			throw new Exception("Failed to send user-name.");
		
		// send password
		$this->cmd(base64_encode($this->pwd));
		if( !$this->check('235') )
			throw new Exception("Failed to send password.");
		
		return true;
	}

	/**
	* helo() - Sends greeting to server
	*
	* @access: public
	* @return Boolean
	*/
	public function helo()
	{
		// Send the RFC821 specified HELO.
		$this->cmd('HELO ' . $this->host);	
		if( !$this->check('250') )
			throw new Exception("Failed to send HELO.");
		
		return true;
	}

	/**
	* from() - Sends specified addressor
	*
	* @access: public
	* @param Str $from
	* @return Boolean
	*/
	public function from($from)
	{
		// specify addressor
		$this->cmd("MAIL FROM: $from");
		if( !$this->check('250') )
			throw new Exception("Failed to send addressor.");
		
		return true;
	}

	/**
	* rcpt() - Sends specified acceptor
	*
	* @access: public
	* @param Str $to
	* @return Boolean
	*/
	public function rcpt($to)
	{
		// send specified acceptor
		$this->cmd("RCPT TO: $to");		
		if( !$this->check('250') )
			throw new Exception("Failed to send acceptor.");
		
		return true;
	}
  
	/**
	* data() - Sends the data to the server
	*
	* @access: public
	* @param Str $message
	* @param Arr $header
	* @return NONE
	*/      
	public function data($message, $header)
	{
		// initiate data-transfere
		$this->cmd('DATA'); 
		if( !$this->check('354') )
			throw new Exception("Data-transfere failed.");
			
		// validate header-data
		if( !is_array($header) )
			throw new Exception("Header-data must be an array.");
			
		// initiate counter
		$i = 0;
			
		// include header data
		foreach( $header as $key => $value)
		{
			// send header
			if( $i < count($header)-1 )
			{
				$this->cmd("$key: $value");
			}
			
			else
			{
				$this->cmd("$key: $value\r\n");			
			}
			
			$i++;			
		}
	
		// send the message
		$this->cmd("$message\r\n");
	
		// send end parameter
		$this->cmd('.');
		
		$this->check('250');
	}
  
	/**
	* quit() - Closes the server-connection
	*
	* @access: public
	* @return NONE
	*/      
	public function quit()
	{
		$this->cmd("QUIT"); 
		$this->check('221');
		fclose($this->sock);	
		return true;
	}

	/**
	* cmd() - Sets a ftp-command given by the user
	*
	* @access: public
	* @param Str $cmd
	* @return NONE
	*/
	public function cmd($cmd)
	{
		fputs($this->sock, "$cmd\r\n");
		$this->log("&gt; $cmd");
	}	
	
	/**
	* getReply() - Gets the reply of the ftp-server
	*
	* @access: public
	* @return String
	*/
	public function getReply()
	{
		$go = true;
		$message = "";
		
		do 
		{	
			$tmp = @fgets($this->sock, 1024);
			if($tmp === false) 
			{
				$go = false;
			} 
			
			else 
			{
				$message .= $tmp;
				if( preg_match('/^([0-9]{3})(-(.*[\r\n]{1,2})+\\1)? [^\r\n]+[\r\n]{1,2}$/', $message) ) $go = false;
			}
		} while($go);
		
		$this->log($message);
		
		return $message;
	}

	/**
	* checkControl() - Checks if the response of a command is ok
	*
	* @access: public
	* @param Str $reply
	* @return Boolean
	*/
	public function valid()
	{
		// get response of the server
		$this->response = $this->getReply();
		
		// check the response and say if everything is allright
		return (empty($this->response) || preg_match('/^[5]/', $this->response)) ? false : true;
	}
		
	/**
	* check() - Checks if the response-code is correct
	*
	* @access: public
	* @param Str $code
	* @return Boolean
	*/
	public function check($code)
	{
		if($this->valid())
		{
			$pat = '/^'. $code .'/';
			if( preg_match($pat, $this->response))
			{
				return true;
			}				
		}	
			
		return false;
	}
			
	/**
	* log() - Saves all request to the server and their responses into $this->log
	*
	* @access: private
	* @return NONE
	*/
	private function log($str)
	{
		$this->log .= "$str<br>";
	}
			
	/**
	* getLog() - Prints out all requests to the server and their responses 
	*
	* @access: public
	* @return NONE
	*/
	public function getLog()
	{
		return $this->log;
	}

}

try
{			
	$from = 'pseudo@web.de';
	$to = 'fantasy@web.de';
	$date = date('r');
	$header = array( 
		'Date' => $date,
		'From' => $from,
		'Subject' => 'Sending mail via PHP!',
		'To' => $to,
		'X-Mailer' => 'Avedo - SmtpMail');
	$message = "Hello. \nIf you can read this message, I was able to send my first mail with my new SmtpMail-Class. \nThat's great! So have a beer and enjoy the show! \nMfG, Andy";

	$mail = new SmtpMail('localhost', 25);
	$mail->connect();
	$mail->helo();
	$mail->from($from);
	$mail->rcpt($to);
	$mail->data($message, $header);
	$mail->quit();
	echo $mail->getLog();
}

catch(Exception $e) 
{
	echo $e->getMessage();
}
?>
```


----------



## Dennis Wronka (22. Juli 2008)

Hab mal fix drueber geschaut und die Klasse sieht soweit schonmal nicht schlecht aus.
Ein paar Anmerkungen hab ich aber:

Mir ist bei den Tests zu meiner Klasse kein einziger Mail-Server ueber den Weg gelaufen der HELO verlangt und EHLO nicht unterstuetzt. Es mag da vielleicht noch ein paar Ausnahmen geben, aber jeder aktuelle Mail-Server sollte EHLO unterstuetzen.
Der Weg ueber EHLO setzt nicht voraus dass der User sich authentifiziert. Entsprechend ist der Login-Code dort meiner Meinung nach falsch platziert.
LOGIN ist die unsicherste Authentifizierungsmethode die fuer SMTP verfuegbar ist. Aehnlich einfach zu implementieren, aber zumindest ein klein wenig sicherer (wenngleich nicht viel) ist PLAIN.
Zusaetzlich zu diesen beiden einfach Methoden empfehle ich noch die Nutzung einer der sichereren Methoden wie z.B. CRAM-MD5 oder Digest-MD5, denn nicht alle Dienste unterstuetzen PLAIN oder gar LOGIN. Wenn ich mich recht erinnere laesst GMail Dich damit z.B. nicht rein.
Wenn Du die 4 genannten Mechanismen unterstuetzt solltest Du aber im Grunde mit jedem SMTP-Server der Logins unterstuetzt arbeiten koennen. Wenn Du's ganz wild willst kannst Du ja auch noch NTLM hinzufuegen.
Informationen findest Du hier.
SSL-Support waere auch keine so schlechte Idee und ist einfach zu implementieren.


----------



## Bgag (22. Juli 2008)

Hallo!
Danke für deine Rückmeldung. Werde mich die Tage mal ran setzen und das von dir angesprochene ändern. Sollte ja alles nicht so schwer sein.
MfG, Andy


----------



## Bgag (13. August 2008)

Guten Abend!
Ich habe mal wieder etwas an der Smtp-Klasse gearbeitet. Ich habe auf anraten von Dennis die _helo()_-Methode aus der Klasse gestrichen. Zudem habe ich die Authentifizierung von der _ehlo()_-Methode getrennt, da ich ebenfalls von Dennis darauf aufmerksam gemacht wurde, dass EHLO keine Authentifizierund vorraussetzt. Des weiteren habe ich nun auch PLAIN in meine Klasse integriert. Nun soll sie noch CRAM-MD5 unterstützen und dort hapert es momentan auch. Ich steige nicht so ganz durch das betreffende RFC durch. Später soll die Klasse dann noch um Digest-MD5 erweitert werden, doch das hat noch Zeit. Wäre nett, wenn mir jemand erstmal bei meinem momentanen Problem helfen könnte. SSL-Support wird auch noch kommen.
MfG, Andy

//EDIT: Habe gerade folgendes über SSL und SMTP gelesen. Was meint ihr dazu.


			
				Wikipedia hat gesagt.:
			
		

> Von der Verwendung des SMTPS- wird zugunsten des neueren STARTTLS-Verfahrens, welches keinen eigenen Port benötigt, abgeraten. Deshalb wird auch für SMTPS der Port 465 nicht mehr in der Liste der well-known-Ports der IANA geführt. Die Nutzung des Verfahrens sowie dieses Ports ist dennoch noch weit verbreitet.


----------



## Dennis Wronka (13. August 2008)

Fuer mich hier ist SMTPS nicht unwichtig, denn mein ISP blockt Zugriffe auf netzfremde Mail-Server ueber SMTP. SMTPS hingegen, wie z.B. von GMX angeboten, funktioniert wunderbar.
Entsprechend wuerde ich, auch wenn es nicht (mehr) so viel genutzt wird implementieren, vor allem weil es wirklich einfach ist.
Der einzig noetige Schritt ist bei fsockopen() ein ssl:// vor den Hostnamen zu setzen und den richtigen Port zu waehlen.
Als Beispiel verweise ich wieder auf meine SMTP-Klasse. Selbiges gilt auch fuer die Implementation von CRAM-MD5.

Hier mal, ganz frei Schnauze, der Ablauf von CRAM-MD5:
In der Antwort vom Server (nach AUTH) bekommst Du einen Challenge gesendet welcher mit Base64 kodiert ist.
Diesen brauchst Du spaeter bei der Erzeugung des Hash-Wertes. Meinen Beobachtungen zufolge scheint dies in der Regel ein Timestamp zu sein. Dadurch sollen wahrscheinlich Replay-Angriffe vermieden werden.

Da die Blockgroesse bei CRAM-MD5 64 Byte ist muss das Passwort mit 0-Bytes (chr(0x00)) auf die entsprechende Laenge gebracht werden.
Anschliessend brauchst Du zwei Strings die bei der Hash-Erstellung fuer ein XOR gegen den Passwort-String genutzt werden.
Der erste enthaelt 64 Byte 0x36, der zweite 64 Byte 0x5C.

Nun kommt der lustige Teil.
Der erste Hash den Du erstellst muss im Hex-Format vorliegen. In PHP5 kannst Du md5() als 2. Parameter true uebergeben um zum gewuenschten Ergebnis zu kommen. In PHP4 musst Du mit pack() arbeiten um den Hash umzuwandeln.
Dieser Hash besteht aus folgenden Werten: (Passwort XOR erster String) + dekodierter Zeitstempel vom Server

Der zweite Hash wird ganz normal erstellt, auf den zweiten Parameter von md5() (oder den Einsatz von pack()) kannst Du hier entsprechend verzichten.
Dieser setzt sich wie folgt zusammen: (Passwort XOR zweiter String) + erster Hash

Diesen Hash haengst Du nun, mit einem Leerzeichen getrennt hinter den Usernamen, kodierst das ganze Konstrukt mittels Base64 und schickst es zum Server.

Siehst Du, so einfach ist das. 

Edit: Noch was zu TLS. Ich hab mir das mit STARTTLS vor ein paar Monaten kurz angeschaut. Dies zu implementieren koennte recht aufwaendig werden.


----------



## Bgag (14. August 2008)

Guten Morgen!
Danke für deine Antwort. Du hast mir wiedermal sehr geholfen. Du hast im übrigen Recht. Es wird vom Server einen Timestamp und der Host kodiert in _Base64_ zurückgeliefert. Stand im betreffenden _RFC_. Hatte ich wohl beim ersten Lesen nicht richtig verstanden. Leider konnte ich die Authentifizierungs-Methode _CRAM-MD5_ nicht testen, da weder mein privater Server noch web.de dies unterstützen. Dafür unterstützen beide _LOGIN_ und _PLAIN_ und damit funktioniert das ganze auch hervorragend.

Den _Secure Sockets Layer_ Support habe ich jetzt nicht noch einmal spezifisch eingebaut, wie du es vorgeschlagen hast, da man dies aufgrund der Strukturierung meiner Klasse relativ einfach direkt machen kann, da man den Server und den Port bei der Instanzierung der Klasse angeben kann. Zudem hat dies den Vorteil, dass man auch _SSL_ Verbindungen über andere Ports erzeugen kann (siehe Google Mail _465_ oder _587_).Ich hätte das auch ganz gerne getestet, doch leider unterstützen die genannten Server auch das nicht. Sollte mir wohl ein Konto bei Google Mail zulegen. Mag den Laden aber eigentlich nicht.

Die Methode _helo()_ habe ich wieder in die Klasse aufgenommen, da sie immernoch sehr häufig unterstützt wird und ich es nicht für falsch halte sich einfach mal mit drin zu haben. Sie stört ja nicht, auch wenn ich zumeist _ehlo()_ verwende.

Abschließend hätte ich allerdings doch noch eine Frage. Hälst du es oder (ich richte die Frage besser mal an alle) haltet ihr es für richtig in einer solchen, auf Socket-Verbindungen basierenden, Klasse die Fehler-behandlung mit Exceptions zu machen und wenn ja wo seht ihr die Vorteile und wenn nein, wo liegen eurer Meinung nach die Nachteile und wie sähe eure Alternative aus?

Natürlich würde ich mich auch über allgemeine Rückmeldungen zu der Klasse freuen. Mich würde zum Beispiel sehr interessieren, ob die Dokumentation so ok ist, die Methoden sinnvoll angelegt sind und vorallem ob die Programmierung so Anklang findet.
MfG, Andy

*SmtpConnect.php*

```
<?php  
error_reporting(E_ALL);

/***
* Class SmtpConnect
* 
* The SmtpConnect class enables sending mails via SMTP.
* It is not very easily operated, but that is not required,
* because this class should just be the base for an comfortable
* Mail class. On the basis of some comments to this class i want
* to add that it sure supports Secure Sockets Layer connections.
* You just have to modify the host and port in the constructor.
* 
* @package Mail
* @subpackage SmtpConnect
* @version 0.4
* @author Andreas Wilhelm <Andreas2209@web.de>
* @copyright Andreas Wilhelm 
**/  
class SmtpConnect
{
	// declare class variables
	private $host; 
	private $port;
	
	private $sock;	
	
	private $auth;
	private $authTypes = array(
		'LOGIN',
		'PLAIN');
	
	private $response;
	private $log;

	/**
	* Constructor - Is called when the class is instanced
	*
	* @access: public
	* @param Str $host
	* @param Int $port
	* @return NONE
	*/
	public function __construct($host='localhost', $port=25)
	{
		// set server-variables
		$this->host = $host;
		$this->port = $port;
	}

	/**
	* connect() - Connects to the given server
	*
	* @access: public
	* @return Boolean
	*/
	public function connect()
	{		
		// control-connection handle is saved to $handle
		$this->sock = @fsockopen($this->host, $this->port);
		if ( !$this->sock OR !$this->check('220') )
			throw new Exception("Connection failed."); 
		
		// switch to non-blocking mode - just return data no response
		set_socket_blocking($this->sock, true);
		
		// set timeout of the server connection
		stream_set_timeout($this->sock, 0, 200000);
		
		return true;
	}

	/**
	* ehlo() - Sends greeting to secured server
	*
	* @access: public
	* @return Boolean
	*/
	public function ehlo()
	{
		// send EHLO -spezified in RFC 2554
		$this->cmd("EHLO " . $this->host);
		if( !$this->check('250') )
			throw new Exception("Failed to send EHLO.");
		
		return true;
	}

	/**
	* helo() - Sends greeting to server
	*
	* @access: public
	* @return Boolean
	*/
	public function helo()
	{
		// Send the RFC821 specified HELO.
		$this->cmd('HELO ' . $this->host);	
		if( !$this->check('250') )
			throw new Exception("Failed to send HELO.");
		
		return true;
	}

	/**
	* auth() - Sends authentification
	*
	* @access: public
	* @param Str $user
	* @param Str $pwd
	* @param Str $type
	* @return Boolean
	*/
	public function auth($user, $pwd, $type)
	{
		if( in_array($type, $this->authTypes) )
		{
			// send authentification-identifier
			$this->cmd("AUTH $type");		
				
			// catch first ready response
			$response = $this->getReply();
			if( substr($response,0,1) != 3 )
			{
				throw new Exception("Failed to send AUTH.");
			}
		}
		
		if( $type == 'LOGIN' )
		{
			// send user-name
			$this->cmd(base64_encode($user));		
			if( !$this->check('334') )
				throw new Exception("Failed to send user-name.");
			
			// send password
			$this->cmd(base64_encode($pwd));
		}
		
		elseif( $type == 'PLAIN' )
		{
			// prepare data
			$data = base64_encode($user.chr(0).$user.chr(0).$pwd);
			$this->cmd($data);
		}
		
		else
			throw new Exception("Authentification failed.");
			
		if( !$this->check('235') )
		{
			throw new Exception("Authentification failed.");
		}
			
		return true;
	}

	/**
	* from() - Sends specified addressor
	*
	* @access: public
	* @param Str $from
	* @return Boolean
	*/
	public function from($from)
	{
		// specify addressor
		$this->cmd("MAIL FROM: $from");
		if( !$this->check('250') )
			throw new Exception("Failed to send addressor.");
		
		return true;
	}

	/**
	* rcpt() - Sends specified acceptor
	*
	* @access: public
	* @param Str $to
	* @return Boolean
	*/
	public function rcpt($to)
	{
		// send specified acceptor
		$this->cmd("RCPT TO: $to");		
		if( !$this->check('250') )
			throw new Exception("Failed to send acceptor.");
		
		return true;
	}
  
	/**
	* data() - Sends the data to the server
	*
	* @access: public
	* @param Str $message
	* @param Arr $header
	* @return NONE
	*/      
	public function data($message, $header)
	{
		// initiate data-transfere
		$this->cmd('DATA'); 
		if( !$this->check('354') )
			throw new Exception("Data-transfere failed.");
			
		// validate header-data
		if( !is_array($header) )
			throw new Exception("Header-data must be an array.");
			
		// initiate counter
		$i = 0;
			
		// include header data
		foreach( $header as $key => $value)
		{
			// send header
			if( $i < count($header)-1 )
			{
				$this->cmd("$key: $value");
			}
			
			else
			{
				$this->cmd("$key: $value\r\n");			
			}
			
			$i++;			
		}
	
		// send the message
		$this->cmd("$message\r\n");
	
		// send end parameter
		$this->cmd('.');
		
		$this->check('250');
	}
  
	/**
	* quit() - Closes the server-connection
	*
	* @access: public
	* @return NONE
	*/      
	public function quit()
	{
		$this->cmd("QUIT"); 
		$this->check('221');
		fclose($this->sock);	
		return true;
	}

	/**
	* cmd() - Sets a ftp-command given by the user
	*
	* @access: public
	* @param Str $cmd
	* @return NONE
	*/
	public function cmd($cmd)
	{
		fputs($this->sock, "$cmd\r\n");
		$this->log("&gt; $cmd");
	}	
	
	/**
	* getReply() - Catches the reply of the server
	*
	* @access: public
	* @return String
	*/
	public function getReply()
	{
		$go = true;
		$message = "";
		
		do 
		{	
			$tmp = @fgets($this->sock, 1024);
			if($tmp === false) 
			{
				$go = false;
			} 
			
			else 
			{
				$message .= $tmp;
				if( preg_match('/^([0-9]{3})(-(.*[\r\n]{1,2})+\\1)? [^\r\n]+[\r\n]{1,2}$/', $message) ) $go = false;
			}
		} while($go);
		
		$this->log($message);
		
		return $message;
	}

	/**
	* valid() - Checks if the response of a command is ok
	*
	* @access: public
	* @return Boolean
	*/
	public function valid()
	{
		// get response of the server
		$this->response = $this->getReply();
		
		// check the response and say if everything is allright
		return (empty($this->response) || preg_match('/^[5]/', $this->response)) ? false : true;
	}
		
	/**
	* check() - Checks if the response-code is correct
	*
	* @access: public
	* @param Str $code
	* @return Boolean
	*/
	public function check($code)
	{
		if( $this->valid() )
		{
			$pat = '/^'. $code .'/';
			if( preg_match($pat, $this->response))
			{
				return true;
			}				
		}	
			
		return false;
	}
			
	/**
	* log() - Saves all request to the server and their responses into $this->log
	*
	* @access: private
	* @return NONE
	*/
	private function log($str)
	{
		$this->log .= "$str<br />";
	}
			
	/**
	* getLog() - Prints out all requests to the server and their responses 
	*
	* @access: public
	* @return NONE
	*/
	public function getLog()
	{
		return $this->log;
	}

}

try
{			
	$from = 'info@avedo.net';
	$to = 'andreas2209@web.de';
	$date = date('r');
	$header = array( 
		'Date' => $date,
		'From' => $from,
		'Subject' => 'Sending mail via PHP!',
		'To' => $to,
		'X-Mailer' => 'Avedo Mailer');
	$message = "Hello. \nIf you can read this message, I was able to send my first mail with my new SmtpConnect-Class. \nThat's great! So have a beer and enjoy the show! \nMfG, Andy";

	$mail = new SmtpConnect('host');
	$mail->connect();
	$mail->ehlo();
	$mail->auth('user', 'pwd', 'PLAIN');
	$mail->from($from);
	$mail->rcpt($to);
	$mail->data($message, $header);
	$mail->quit();
	echo $mail->getLog();
}

catch(Exception $e) 
{
	echo $e->getMessage();
}
?>
```


----------



## Dennis Wronka (14. August 2008)

Da mir grad die Zeit fehlt die Klasse und ihre Dokumentation zu begutachten beschraenke ich mich jetzt einfach mal auf die Frage mit den Exceptions:

Da Deine Klasse in PHP5 geschrieben ist denke ich dass es durchaus sinnvoll ist dort auch mit Exceptions zu arbeiten.
Ich selbst habe, so muss ich zugeben, bislang nicht viel mit Exceptions gemacht, unter anderem weil ich beruflich leider noch auf PHP4 festhaenge, aber nach allem was ich so gelesen habe sind Exceptions meiner Meinung nach flexibler als trigger_error().

Man kann zwar sowohl klassische Fehler als auch Exceptions "auffangen" (z.B. bietet meine Klasse Catcher diese Moeglichkeit) aber der Vorteil den man bei Exceptions z.B. hat ist dass sie grundsaetzlich eine Klasse darstellen und diese somit auch erweiterbar sind, was bei den klassischen Fehlermeldungen nicht der Fall ist. Dort gibt es lediglich den Fehlertyp und die Fehlermeldung, und das war es dann auch schon.

Zudem sind Exceptions leichter zu fangen (Stichwort: try...catch) als es bei klassischen Fehlern der Fall ist. Somit kann im Code wesentlich besser mit Fehlerfaellen umgegangen werden.


----------

