Mail via SMTP versenden

B

Bgag

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.

  1. Betreff, Datum, Empfänger und Mailer werden in die Mail geschrieben
  2. Authentifizierung funktioniert nicht
  3. 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 :rolleyes:) 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
 
Zuletzt bearbeitet von einem Moderator:
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:
Code:
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
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>
 
Zuletzt bearbeitet von einem Moderator:
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...
 
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
 
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.
 
@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.

PHP:
		// 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
 
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.
 
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. :confused:
 
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.
 
Zurück