next_token; } for($character=0;$characternext_token=substr($string,$found+1); return(substr($string,0,$found)); } else { $this->next_token=""; return($string); } } Function OutputDebug($message) { $message.="\n"; if($this->html_debug) $message=str_replace("\n","
\n",HtmlEntities($message)); echo $message; flush(); } Function SetDataAccessError($error) { $this->error=$error; if(function_exists("socket_get_status")) { $status=socket_get_status($this->connection); if($status["timed_out"]) $this->error.=gettext(": data access time out"); elseif($status["eof"]) { $this->error.=gettext(": the server disconnected"); $this->disconnected_error=1; } } } Function GetLine() { for($line="";;) { if(feof($this->connection)) { $this->error=gettext("reached the end of data while reading from the SMTP server conection"); return(""); } if(GetType($data=@fgets($this->connection,100))!="string" || strlen($data)==0) { $this->SetDataAccessError(gettext("it was not possible to read line from the SMTP server")); return(""); } $line.=$data; $length=strlen($line); if($length>=2 && substr($line,$length-2,2)=="\r\n") { $line=substr($line,0,$length-2); if($this->debug) $this->OutputDebug("S $line"); return($line); } } } Function PutLine($line) { if($this->debug) $this->OutputDebug("C $line"); if(!@fputs($this->connection,"$line\r\n")) { $this->SetDataAccessError(gettext("it was not possible to send a line to the SMTP server")); return(0); } return(1); } Function PutData(&$data) { if(strlen($data)) { if($this->debug) $this->OutputDebug("C $data"); if(!@fputs($this->connection,$data)) { $this->SetDataAccessError(gettext("it was not possible to send data to the SMTP server")); return(0); } } return(1); } Function VerifyResultLines($code,&$responses) { $responses=array(); Unset($this->result_code); while(strlen($line=$this->GetLine($this->connection))) { if(IsSet($this->result_code)) { if(strcmp($this->Tokenize($line," -"),$this->result_code)) { $this->error=$line; return(0); } } else { $this->result_code=$this->Tokenize($line," -"); if(GetType($code)=="array") { for($codes=0;$codesresult_code,$code[$codes]);$codes++); if($codes>=count($code)) { $this->error=$line; return(0); } } else { if(strcmp($this->result_code,$code)) { $this->error=$line; return(0); } } } $responses[]=$this->Tokenize(""); if(!strcmp($this->result_code,$this->Tokenize($line," "))) return(1); } return(-1); } Function FlushRecipients() { if($this->pending_sender) { if($this->VerifyResultLines("250",$responses)<=0) return(0); $this->pending_sender=0; } for(;$this->pending_recipients;$this->pending_recipients--) { if($this->VerifyResultLines(array("250","251"),$responses)<=0) return(0); } return(1); } Function ConnectToHost($domain, $port, $resolve_message) { if($this->ssl || $this->tls) { $version=explode(".",function_exists("phpversion") ? phpversion() : "3.0.7"); $php_version=intval($version[0])*1000000+intval($version[1])*1000+intval($version[2]); if($php_version<4003000) return(gettext("establishing SSL connections requires at least PHP version 4.3.0")); if(!function_exists("extension_loaded") || !extension_loaded("openssl")) return(gettext("establishing SSL connections requires the OpenSSL extension enabled")); } if(preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/',$domain)) $ip=$domain; else { if($this->debug) $this->OutputDebug($resolve_message); if(!strcmp($ip=@gethostbyname($domain),$domain)) return(sprintf(gettext("could not resolve host \"%s\""), $domain)); } if(strlen($this->exclude_address) && !strcmp(@gethostbyname($this->exclude_address),$ip)) return(sprintf(gettext("domain \"%s\" resolved to an address excluded to be valid"), $domain)); if($this->debug) $this->OutputDebug(sprintf(gettext('Connecting to host address "%1$s" port %2$s...'), $ip, $port)); if(($this->connection=($this->timeout ? @fsockopen(($this->ssl ? "ssl://" : "").$ip,$port,$errno,$error,$this->timeout) : @fsockopen(($this->ssl ? "ssl://" : "").$ip,$port)))) return(""); $error=($this->timeout ? strval($error) : "??"); switch($error) { case "-3": return(gettext("-3 socket could not be created")); case "-4": return(sprintf(gettext("-4 dns lookup on hostname \"%s\" failed"), $domain)); case "-5": return(gettext("-5 connection refused or timed out")); case "-6": return(gettext("-6 fdopen() call failed")); case "-7": return(gettext("-7 setvbuf() call failed")); } return(sprintf(gettext('could not connect to the host "%1$s": %2$s'), $domain, $error)); } Function SASLAuthenticate($mechanisms, $credentials, &$authenticated, &$mechanism) { $authenticated=0; if(!function_exists("class_exists") || !class_exists("sasl_client_class")) { $this->error=gettext("it is not possible to authenticate using the specified mechanism because the SASL library class is not loaded"); return(0); } $sasl=new sasl_client_class; $sasl->SetCredential("user",$credentials["user"]); $sasl->SetCredential("password",$credentials["password"]); if(IsSet($credentials["realm"])) $sasl->SetCredential("realm",$credentials["realm"]); if(IsSet($credentials["workstation"])) $sasl->SetCredential("workstation",$credentials["workstation"]); if(IsSet($credentials["mode"])) $sasl->SetCredential("mode",$credentials["mode"]); do { $status=$sasl->Start($mechanisms,$message,$interactions); } while($status==SASL_INTERACT); switch($status) { case SASL_CONTINUE: break; case SASL_NOMECH: if(strlen($this->authentication_mechanism)) { $this->error=printf(gettext('authenticated mechanism %1$s may not be used: %2$s'), $this->authentication_mechanism, $sasl->error); return(0); } break; default: $this->error=gettext("Could not start the SASL authentication client:") . " ".$sasl->error; return(0); } if(strlen($mechanism=$sasl->mechanism)) { if($this->PutLine("AUTH ".$sasl->mechanism.(IsSet($message) ? " ".base64_encode($message) : ""))==0) { $this->error=gettext("Could not send the AUTH command"); return(0); } if(!$this->VerifyResultLines(array("235","334"),$responses)) return(0); switch($this->result_code) { case "235": $response=""; $authenticated=1; break; case "334": $response=base64_decode($responses[0]); break; default: $this->error=gettext("Authentication error:") . " ".$responses[0]; return(0); } for(;!$authenticated;) { do { $status=$sasl->Step($response,$message,$interactions); } while($status==SASL_INTERACT); switch($status) { case SASL_CONTINUE: if($this->PutLine(base64_encode($message))==0) { $this->error=gettext("Could not send the authentication step message"); return(0); } if(!$this->VerifyResultLines(array("235","334"),$responses)) return(0); switch($this->result_code) { case "235": $response=""; $authenticated=1; break; case "334": $response=base64_decode($responses[0]); break; default: $this->error=gettext("Authentication error:") . " ".$responses[0]; return(0); } break; default: $this->error=gettext("Could not process the SASL authentication step:") . " ".$sasl->error; return(0); } } } return(1); } /* Public methods */ Function Connect($domain="") { if(strcmp($this->state,"Disconnected")) { $this->error=gettext("connection is already established"); return(0); } $this->disconnected_error=0; $this->error=$error=""; $this->esmtp_host=""; $this->esmtp_extensions=array(); $hosts=array(); if($this->direct_delivery) { if(strlen($domain)==0) return(1); $hosts=$weights=$mxhosts=array(); $getmxrr=$this->getmxrr; if(function_exists($getmxrr) && $getmxrr($domain,$hosts,$weights)) { for($host=0;$hosthost_name)) $hosts[]=$this->host_name; if(strlen($this->pop3_auth_host)) { $user=$this->user; if(strlen($user)==0) { $this->error=gettext("it was not specified the POP3 authentication user"); return(0); } $password=$this->password; if(strlen($password)==0) { $this->error=gettext("it was not specified the POP3 authentication password"); return(0); } $domain=$this->pop3_auth_host; $this->error=$this->ConnectToHost($domain, $this->pop3_auth_port, sprintf(gettext("Resolving POP3 authentication host \"%s\"..."), $domain)); if(strlen($this->error)) return(0); if(strlen($response=$this->GetLine())==0) return(0); if(strcmp($this->Tokenize($response," "),"+OK")) { $this->error=gettext("POP3 authentication server greeting was not found"); return(0); } if(!$this->PutLine("USER ".$this->user) || strlen($response=$this->GetLine())==0) return(0); if(strcmp($this->Tokenize($response," "),"+OK")) { $this->error=gettext("POP3 authentication user was not accepted:") . " ".$this->Tokenize("\r\n"); return(0); } if(!$this->PutLine("PASS ".$password) || strlen($response=$this->GetLine())==0) return(0); if(strcmp($this->Tokenize($response," "),"+OK")) { $this->error=gettext("POP3 authentication password was not accepted:") . " ".$this->Tokenize("\r\n"); return(0); } fclose($this->connection); $this->connection=0; } } if(count($hosts)==0) { $this->error=gettext("could not determine the SMTP to connect"); return(0); } for($host=0, $error="not connected";strlen($error) && $hostConnectToHost($domain, $this->host_port, sprintf(gettext("Resolving SMTP server domain \"%s\"..."), $domain)); } if(strlen($error)) { $this->error=$error; return(0); } $timeout=($this->data_timeout ? $this->data_timeout : $this->timeout); if($timeout && function_exists("socket_set_timeout")) socket_set_timeout($this->connection,$timeout,0); if($this->debug) $this->OutputDebug(sprintf(gettext("Connected to SMTP server \"%s\"."), $domain)); if($this->VerifyResultLines("220",$responses)>0) { // Send our HELLO $success = $this->hello($this->hostname()); if ($this->tls) $success = $this->startTLS(); if($success && strlen($this->user) && strlen($this->pop3_auth_host)==0) { if(!IsSet($this->esmtp_extensions["AUTH"])) { $this->error = gettext("server does not require authentication"); $success=0; } else { if(strlen($this->authentication_mechanism)) $mechanisms=array($this->authentication_mechanism); else { $mechanisms=array(); for($authentication=$this->Tokenize($this->esmtp_extensions["AUTH"]," ");strlen($authentication);$authentication=$this->Tokenize(" ")) $mechanisms[]=$authentication; } $credentials=array( "user"=>$this->user, "password"=>$this->password ); if(strlen($this->realm)) $credentials["realm"]=$this->realm; if(strlen($this->workstation)) $credentials["workstation"]=$this->workstation; $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism); if(!$success && !strcmp($mechanism,"PLAIN")) { /* * Author: Russell Robinson, 25 May 2003, http://www.tectite.com/ * Purpose: Try various AUTH PLAIN authentication methods. */ $mechanisms=array("PLAIN"); $credentials=array( "user"=>$this->user, "password"=>$this->password ); if(strlen($this->realm)) { /* * According to: http://www.sendmail.org/~ca/email/authrealms.html#authpwcheck_method * some sendmails won't accept the realm, so try again without it */ $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism); } if(!$success) { /* * It was seen an EXIM configuration like this: * user^password^unused */ $credentials["mode"]=SASL_PLAIN_EXIM_DOCUMENTATION_MODE; $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism); } if(!$success) { /* * ... though: http://exim.work.de/exim-html-3.20/doc/html/spec_36.html * specifies: ^user^password */ $credentials["mode"]=SASL_PLAIN_EXIM_MODE; $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism); } } if($success && strlen($mechanism)==0) { $this->error=gettext("it is not supported any of the authentication mechanisms required by the server"); $success=0; } } } } if($success) { $this->state="Connected"; $this->connected_domain=$domain; } else { fclose($this->connection); $this->connection=0; } return($success); } Function hostname() { if(!strcmp($localhost=$this->localhost,"") && !strcmp($localhost=getenv("SERVER_NAME"),"") && !strcmp($localhost=getenv("HOST"),"") && !strcmp($localhost=getenv("HOSTNAME"),"") && !strcmp($localhost=exec("/bin/hostname"),"")) $localhost="localhost"; return $localhost; } Function hello() { $success = 0; $fallback = 1; if ($this->esmtp || strlen($this->user)) { if ($this->PutLine("EHLO ".$this->hostname())) { if (($success_code = $this->VerifyResultLines("250",$responses)) > 0) { $this->esmtp_host = $this->Tokenize($responses[0]," "); for($response=1;$responseTokenize($responses[$response]," ")); $this->esmtp_extensions[$extension]=$this->Tokenize(""); } $success = 1; $fallback = 0; } else { if ($success_code == 0) { $code = $this->Tokenize($this->error," -"); switch($code) { case "421": $fallback=0; break; } } } } else $fallback=0; } if ($fallback) { if ($this->PutLine("HELO $localhost") && $this->VerifyResultLines("250",$responses)>0) $success=1; } return $success; } Function startTLS() { if ($this->PutLine("STARTTLS") && $this->VerifyResultLines("220",$responses)>0) { if (!stream_socket_enable_crypto($this->connection,true,STREAM_CRYPTO_METHOD_TLS_CLIENT)) { return false; } else { // Resend HELO since session has been reset return $this->hello($this->hostname); } } else return false; } Function MailFrom($sender) { if($this->direct_delivery) { switch($this->state) { case "Disconnected": $this->direct_sender=$sender; return(1); case "Connected": $sender=$this->direct_sender; break; default: $this->error=gettext("direct delivery connection is already established and sender is already set"); return(0); } } else { if(strcmp($this->state,"Connected")) { $this->error=gettext("connection is not in the initial state"); return(0); } } $this->error=""; if(!$this->PutLine("MAIL FROM:<$sender>")) return(0); if(!IsSet($this->esmtp_extensions["PIPELINING"]) && $this->VerifyResultLines("250",$responses)<=0) return(0); $this->state="SenderSet"; if(IsSet($this->esmtp_extensions["PIPELINING"])) $this->pending_sender=1; $this->pending_recipients=0; return(1); } Function SetRecipient($recipient) { if($this->direct_delivery) { if(GetType($at=strrpos($recipient,"@"))!="integer") return(gettext("it was not specified a valid direct recipient")); $domain=substr($recipient,$at+1); switch($this->state) { case "Disconnected": if(!$this->Connect($domain)) return(0); if(!$this->MailFrom("")) { $error=$this->error; $this->Disconnect(); $this->error=$error; return(0); } break; case "SenderSet": case "RecipientSet": if(strcmp($this->connected_domain,$domain)) { $this->error=gettext("it is not possible to deliver directly to recipients of different domains"); return(0); } break; default: $this->error=gettext("connection is already established and the recipient is already set"); return(0); } } else { switch($this->state) { case "SenderSet": case "RecipientSet": break; default: $this->error=gettext("connection is not in the recipient setting state"); return(0); } } $this->error=""; if(!$this->PutLine("RCPT TO:<$recipient>")) return(0); if(IsSet($this->esmtp_extensions["PIPELINING"])) { $this->pending_recipients++; if($this->pending_recipients>=$this->maximum_piped_recipients) { if(!$this->FlushRecipients()) return(0); } } else { if($this->VerifyResultLines(array("250","251"),$responses)<=0) return(0); } $this->state="RecipientSet"; return(1); } Function StartData() { if(strcmp($this->state,"RecipientSet")) { $this->error=gettext("connection is not in the start sending data state"); return(0); } $this->error=""; if(!$this->PutLine("DATA")) return(0); if($this->pending_recipients) { if(!$this->FlushRecipients()) return(0); } if($this->VerifyResultLines("354",$responses)<=0) return(0); $this->state="SendingData"; return(1); } Function PrepareData(&$data,&$output,$preg=1) { if($preg && function_exists("preg_replace")) $output=preg_replace(array("/\n\n|\r\r/","/(^|[^\r])\n/","/\r([^\n]|\$)/D","/(^|\n)\\./"),array("\r\n\r\n","\\1\r\n","\r\n\\1","\\1.."),$data); else $output=ereg_replace("(^|\n)\\.","\\1..",ereg_replace("\r([^\n]|\$)","\r\n\\1",ereg_replace("(^|[^\r])\n","\\1\r\n",ereg_replace("\n\n|\r\r","\r\n\r\n",$data)))); } Function SendData($data) { if(strcmp($this->state,"SendingData")) { $this->error=gettext("connection is not in the sending data state"); return(0); } $this->error=""; return($this->PutData($data)); } Function EndSendingData() { if(strcmp($this->state,"SendingData")) { $this->error=gettext("connection is not in the sending data state"); return(0); } $this->error=""; if(!$this->PutLine("\r\n.") || $this->VerifyResultLines("250",$responses)<=0) return(0); $this->state="Connected"; return(1); } Function ResetConnection() { switch($this->state) { case "Connected": return(1); case "SendingData": $this->error="can not reset the connection while sending data"; return(0); case "Disconnected": $this->error="can not reset the connection before it is established"; return(0); } $this->error=""; if(!$this->PutLine("RSET") || $this->VerifyResultLines("250",$responses)<=0) return(0); $this->state="Connected"; return(1); } Function Disconnect($quit=1) { if(!strcmp($this->state,"Disconnected")) { $this->error=gettext("it was not previously established a SMTP connection"); return(0); } $this->error=""; if(!strcmp($this->state,"Connected") && $quit && (!$this->PutLine("QUIT") || ($this->VerifyResultLines("221",$responses)<=0 && !$this->disconnected_error))) return(0); if($this->disconnected_error) $this->disconnected_error=0; else fclose($this->connection); $this->connection=0; $this->state="Disconnected"; if($this->debug) $this->OutputDebug("Disconnected."); return(1); } Function SendMessage($sender,$recipients,$headers,$body) { if(($success=$this->Connect())) { if(($success=$this->MailFrom($sender))) { for($recipient=0;$recipientSetRecipient($recipients[$recipient]))) break; } if($success && ($success=$this->StartData())) { for($header_data="",$header=0;$headerSendData($header_data."\r\n"))) { $this->PrepareData($body,$body_data); $success=$this->SendData($body_data); } if($success) $success=$this->EndSendingData(); } } $error=$this->error; $disconnect_success=$this->Disconnect($success); if($success) $success=$disconnect_success; else $this->error=$error; } return($success); } }; ?>