15 var $authentication_mechanism="";
22 var $direct_delivery=0;
28 var $esmtp_extensions=array();
29 var $maximum_piped_recipients=100;
30 var $exclude_address="";
31 var $getmxrr="GetMXRR";
32 var $pop3_auth_host="";
33 var $pop3_auth_port=110;
35 /* private variables - DO NOT ACCESS */
37 var $state="Disconnected";
39 var $pending_recipients=0;
41 var $direct_sender="";
42 var $connected_domain="";
44 var $disconnected_error=0;
46 /* Private methods - DO NOT CALL */
48 Function Tokenize($string,$separator="")
50 if(!strcmp($separator,""))
53 $string=$this->next_token
;
55 for($character=0;$character<strlen($separator);$character++
)
57 if(GetType($position=strpos($string,$separator[$character]))=="integer")
58 $found=(IsSet($found) ?
min($found,$position) : $position);
62 $this->next_token
=substr($string,$found+
1);
63 return(substr($string,0,$found));
72 Function OutputDebug($message)
76 $message=str_replace("\n","<br />\n",HtmlEntities($message));
81 Function SetDataAccessError($error)
84 if(function_exists("socket_get_status"))
86 $status=socket_get_status($this->connection
);
87 if($status["timed_out"])
88 $this->error
.=": data access time out";
89 elseif($status["eof"])
91 $this->error
.=": the server disconnected";
92 $this->disconnected_error
=1;
101 if(feof($this->connection
))
103 $this->error
="reached the end of data while reading from the SMTP server conection";
106 if(GetType($data=@fgets
($this->connection
,100))!="string"
109 $this->SetDataAccessError("it was not possible to read line from the SMTP server");
113 $length=strlen($line);
115 && substr($line,$length-2,2)=="\r\n")
117 $line=substr($line,0,$length-2);
119 $this->OutputDebug("S $line");
125 Function PutLine($line)
128 $this->OutputDebug("C $line");
129 if(!@fputs
($this->connection
,"$line\r\n"))
131 $this->SetDataAccessError("it was not possible to send a line to the SMTP server");
137 Function PutData(&$data)
142 $this->OutputDebug("C $data");
143 if(!@fputs
($this->connection
,$data))
145 $this->SetDataAccessError("it was not possible to send data to the SMTP server");
152 Function VerifyResultLines($code,&$responses)
155 Unset($this->result_code
);
156 while(strlen($line=$this->GetLine($this->connection
)))
158 if(IsSet($this->result_code
))
160 if(strcmp($this->Tokenize($line," -"),$this->result_code
))
168 $this->result_code
=$this->Tokenize($line," -");
169 if(GetType($code)=="array")
171 for($codes=0;$codes<count($code) && strcmp($this->result_code
,$code[$codes]);$codes++
);
172 if($codes>=count($code))
180 if(strcmp($this->result_code
,$code))
187 $responses[]=$this->Tokenize("");
188 if(!strcmp($this->result_code
,$this->Tokenize($line," ")))
194 Function FlushRecipients()
196 if($this->pending_sender
)
198 if($this->VerifyResultLines("250",$responses)<=0)
200 $this->pending_sender
=0;
202 for(;$this->pending_recipients
;$this->pending_recipients
--)
204 if($this->VerifyResultLines(array("250","251"),$responses)<=0)
210 Function ConnectToHost($domain, $port, $resolve_message)
214 $version=explode(".",function_exists("phpversion") ?
phpversion() : "3.0.7");
215 $php_version=intval($version[0])*1000000+
intval($version[1])*1000+
intval($version[2]);
216 if($php_version<4003000)
217 return("establishing SSL connections requires at least PHP version 4.3.0");
218 if(!function_exists("extension_loaded")
219 ||
!extension_loaded("openssl"))
220 return("establishing SSL connections requires the OpenSSL extension enabled");
222 if(ereg('^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$',$domain))
227 $this->OutputDebug($resolve_message);
228 if(!strcmp($ip=@gethostbyname
($domain),$domain))
229 return("could not resolve host \"".$domain."\"");
231 if(strlen($this->exclude_address
)
232 && !strcmp(@gethostbyname
($this->exclude_address
),$ip))
233 return("domain \"".$domain."\" resolved to an address excluded to be valid");
235 $this->OutputDebug("Connecting to host address \"".$ip."\" port ".$port."...");
236 if(($this->connection
=($this->timeout ? @fsockopen
(($this->ssl ?
"ssl://" : "").$ip,$port,$errno,$error,$this->timeout
) : @fsockopen
(($this->ssl ?
"ssl://" : "").$ip,$port))))
238 $error=($this->timeout ?
strval($error) : "??");
242 return("-3 socket could not be created");
244 return("-4 dns lookup on hostname \"".$domain."\" failed");
246 return("-5 connection refused or timed out");
248 return("-6 fdopen() call failed");
250 return("-7 setvbuf() call failed");
252 return("could not connect to the host \"".$domain."\": ".$error);
255 Function SASLAuthenticate($mechanisms, $credentials, &$authenticated, &$mechanism)
258 if(!function_exists("class_exists")
259 ||
!class_exists("sasl_client_class"))
261 $this->error
="it is not possible to authenticate using the specified mechanism because the SASL library class is not loaded";
264 $sasl=new sasl_client_class
;
265 $sasl->SetCredential("user",$credentials["user"]);
266 $sasl->SetCredential("password",$credentials["password"]);
267 if(IsSet($credentials["realm"]))
268 $sasl->SetCredential("realm",$credentials["realm"]);
269 if(IsSet($credentials["workstation"]))
270 $sasl->SetCredential("workstation",$credentials["workstation"]);
271 if(IsSet($credentials["mode"]))
272 $sasl->SetCredential("mode",$credentials["mode"]);
275 $status=$sasl->Start($mechanisms,$message,$interactions);
277 while($status==SASL_INTERACT
);
283 if(strlen($this->authentication_mechanism
))
285 $this->error
="authenticated mechanism ".$this->authentication_mechanism
." may not be used: ".$sasl->error
;
290 $this->error
="Could not start the SASL authentication client: ".$sasl->error
;
293 if(strlen($mechanism=$sasl->mechanism
))
295 if($this->PutLine("AUTH ".$sasl->mechanism
.(IsSet($message) ?
" ".base64_encode($message) : ""))==0)
297 $this->error
="Could not send the AUTH command";
300 if(!$this->VerifyResultLines(array("235","334"),$responses))
302 switch($this->result_code
)
309 $response=base64_decode($responses[0]);
312 $this->error
="Authentication error: ".$responses[0];
315 for(;!$authenticated;)
319 $status=$sasl->Step($response,$message,$interactions);
321 while($status==SASL_INTERACT
);
325 if($this->PutLine(base64_encode($message))==0)
327 $this->error
="Could not send the authentication step message";
330 if(!$this->VerifyResultLines(array("235","334"),$responses))
332 switch($this->result_code
)
339 $response=base64_decode($responses[0]);
342 $this->error
="Authentication error: ".$responses[0];
347 $this->error
="Could not process the SASL authentication step: ".$sasl->error
;
357 Function Connect($domain="")
359 if(strcmp($this->state
,"Disconnected"))
361 $this->error
="connection is already established";
364 $this->disconnected_error
=0;
365 $this->error
=$error="";
366 $this->esmtp_host
="";
367 $this->esmtp_extensions
=array();
369 if($this->direct_delivery
)
371 if(strlen($domain)==0)
373 $hosts=$weights=$mxhosts=array();
374 $getmxrr=$this->getmxrr
;
375 if(function_exists($getmxrr)
376 && $getmxrr($domain,$hosts,$weights))
378 for($host=0;$host<count($hosts);$host++
)
379 $mxhosts[$weights[$host]]=$hosts[$host];
381 for(Reset($mxhosts),$host=0;$host<count($mxhosts);Next($mxhosts),$host++
)
382 $hosts[$host]=$mxhosts[Key($mxhosts)];
386 if(strcmp(@gethostbyname
($domain),$domain)!=0)
392 if(strlen($this->host_name
))
393 $hosts[]=$this->host_name
;
394 if(strlen($this->pop3_auth_host
))
399 $this->error
="it was not specified the POP3 authentication user";
402 $password=$this->password
;
403 if(strlen($password)==0)
405 $this->error
="it was not specified the POP3 authentication password";
408 $domain=$this->pop3_auth_host
;
409 $this->error
=$this->ConnectToHost($domain, $this->pop3_auth_port
, "Resolving POP3 authentication host \"".$domain."\"...");
410 if(strlen($this->error
))
412 if(strlen($response=$this->GetLine())==0)
414 if(strcmp($this->Tokenize($response," "),"+OK"))
416 $this->error
="POP3 authentication server greeting was not found";
419 if(!$this->PutLine("USER ".$this->user
)
420 ||
strlen($response=$this->GetLine())==0)
422 if(strcmp($this->Tokenize($response," "),"+OK"))
424 $this->error
="POP3 authentication user was not accepted: ".$this->Tokenize("\r\n");
427 if(!$this->PutLine("PASS ".$password)
428 ||
strlen($response=$this->GetLine())==0)
430 if(strcmp($this->Tokenize($response," "),"+OK"))
432 $this->error
="POP3 authentication password was not accepted: ".$this->Tokenize("\r\n");
435 fclose($this->connection
);
441 $this->error
="could not determine the SMTP to connect";
444 for($host=0, $error="not connected";strlen($error) && $host<count($hosts);$host++
)
446 $domain=$hosts[$host];
447 $error=$this->ConnectToHost($domain, $this->host_port
, "Resolving SMTP server domain \"$domain\"...");
454 $timeout=($this->data_timeout ?
$this->data_timeout
: $this->timeout
);
456 && function_exists("socket_set_timeout"))
457 socket_set_timeout($this->connection
,$timeout,0);
459 $this->OutputDebug("Connected to SMTP server \"".$domain."\".");
460 if(!strcmp($localhost=$this->localhost
,"")
461 && !strcmp($localhost=getenv("SERVER_NAME"),"")
462 && !strcmp($localhost=getenv("HOST"),""))
463 $localhost="localhost";
465 if($this->VerifyResultLines("220",$responses)>0)
469 ||
strlen($this->user
))
471 if($this->PutLine("EHLO $localhost"))
473 if(($success_code=$this->VerifyResultLines("250",$responses))>0)
475 $this->esmtp_host
=$this->Tokenize($responses[0]," ");
476 for($response=1;$response<count($responses);$response++
)
478 $extension=strtoupper($this->Tokenize($responses[$response]," "));
479 $this->esmtp_extensions
[$extension]=$this->Tokenize("");
488 $code=$this->Tokenize($this->error
," -");
503 if($this->PutLine("HELO $localhost")
504 && $this->VerifyResultLines("250",$responses)>0)
508 && strlen($this->user
)
509 && strlen($this->pop3_auth_host
)==0)
511 if(!IsSet($this->esmtp_extensions
["AUTH"]))
513 $this->error
="server does not require authentication";
518 if(strlen($this->authentication_mechanism
))
519 $mechanisms=array($this->authentication_mechanism
);
523 for($authentication=$this->Tokenize($this->esmtp_extensions
["AUTH"]," ");strlen($authentication);$authentication=$this->Tokenize(" "))
524 $mechanisms[]=$authentication;
528 "password"=>$this->password
530 if(strlen($this->realm
))
531 $credentials["realm"]=$this->realm
;
532 if(strlen($this->workstation
))
533 $credentials["workstation"]=$this->workstation
;
534 $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism);
536 && !strcmp($mechanism,"PLAIN"))
539 * Author: Russell Robinson, 25 May 2003, http://www.tectite.com/
540 * Purpose: Try various AUTH PLAIN authentication methods.
542 $mechanisms=array("PLAIN");
545 "password"=>$this->password
547 if(strlen($this->realm
))
550 * According to: http://www.sendmail.org/~ca/email/authrealms.html#authpwcheck_method
551 * some sendmails won't accept the realm, so try again without it
553 $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism);
558 * It was seen an EXIM configuration like this:
559 * user^password^unused
561 $credentials["mode"]=SASL_PLAIN_EXIM_DOCUMENTATION_MODE
;
562 $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism);
567 * ... though: http://exim.work.de/exim-html-3.20/doc/html/spec_36.html
568 * specifies: ^user^password
570 $credentials["mode"]=SASL_PLAIN_EXIM_MODE
;
571 $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism);
575 && strlen($mechanism)==0)
577 $this->error
="it is not supported any of the authentication mechanisms required by the server";
585 $this->state
="Connected";
586 $this->connected_domain
=$domain;
590 fclose($this->connection
);
596 Function MailFrom($sender)
598 if($this->direct_delivery
)
603 $this->direct_sender
=$sender;
606 $sender=$this->direct_sender
;
609 $this->error
="direct delivery connection is already established and sender is already set";
615 if(strcmp($this->state
,"Connected"))
617 $this->error
="connection is not in the initial state";
622 if(!$this->PutLine("MAIL FROM:<$sender>"))
624 if(!IsSet($this->esmtp_extensions
["PIPELINING"])
625 && $this->VerifyResultLines("250",$responses)<=0)
627 $this->state
="SenderSet";
628 if(IsSet($this->esmtp_extensions
["PIPELINING"]))
629 $this->pending_sender
=1;
630 $this->pending_recipients
=0;
634 Function SetRecipient($recipient)
636 if($this->direct_delivery
)
638 if(GetType($at=strrpos($recipient,"@"))!="integer")
639 return("it was not specified a valid direct recipient");
640 $domain=substr($recipient,$at+
1);
644 if(!$this->Connect($domain))
646 if(!$this->MailFrom(""))
656 if(strcmp($this->connected_domain
,$domain))
658 $this->error
="it is not possible to deliver directly to recipients of different domains";
663 $this->error
="connection is already established and the recipient is already set";
675 $this->error
="connection is not in the recipient setting state";
680 if(!$this->PutLine("RCPT TO:<$recipient>"))
682 if(IsSet($this->esmtp_extensions
["PIPELINING"]))
684 $this->pending_recipients++
;
685 if($this->pending_recipients
>=$this->maximum_piped_recipients
)
687 if(!$this->FlushRecipients())
693 if($this->VerifyResultLines(array("250","251"),$responses)<=0)
696 $this->state
="RecipientSet";
702 if(strcmp($this->state
,"RecipientSet"))
704 $this->error
="connection is not in the start sending data state";
708 if(!$this->PutLine("DATA"))
710 if($this->pending_recipients
)
712 if(!$this->FlushRecipients())
715 if($this->VerifyResultLines("354",$responses)<=0)
717 $this->state
="SendingData";
721 Function PrepareData(&$data,&$output,$preg=1)
724 && function_exists("preg_replace"))
725 $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);
727 $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))));
730 Function SendData($data)
732 if(strcmp($this->state
,"SendingData"))
734 $this->error
="connection is not in the sending data state";
738 return($this->PutData($data));
741 Function EndSendingData()
743 if(strcmp($this->state
,"SendingData"))
745 $this->error
="connection is not in the sending data state";
749 if(!$this->PutLine("\r\n.")
750 ||
$this->VerifyResultLines("250",$responses)<=0)
752 $this->state
="Connected";
756 Function ResetConnection()
763 $this->error
="can not reset the connection while sending data";
766 $this->error
="can not reset the connection before it is established";
770 if(!$this->PutLine("RSET")
771 ||
$this->VerifyResultLines("250",$responses)<=0)
773 $this->state
="Connected";
777 Function Disconnect($quit=1)
779 if(!strcmp($this->state
,"Disconnected"))
781 $this->error
="it was not previously established a SMTP connection";
785 if(!strcmp($this->state
,"Connected")
787 && (!$this->PutLine("QUIT")
788 ||
($this->VerifyResultLines("221",$responses)<=0
789 && !$this->disconnected_error
)))
791 if($this->disconnected_error
)
792 $this->disconnected_error
=0;
794 fclose($this->connection
);
796 $this->state
="Disconnected";
798 $this->OutputDebug("Disconnected.");
802 Function SendMessage($sender,$recipients,$headers,$body)
804 if(($success=$this->Connect()))
806 if(($success=$this->MailFrom($sender)))
808 for($recipient=0;$recipient<count($recipients);$recipient++
)
810 if(!($success=$this->SetRecipient($recipients[$recipient])))
814 && ($success=$this->StartData()))
816 for($header_data="",$header=0;$header<count($headers);$header++
)
817 $header_data.=$headers[$header]."\r\n";
818 if(($success=$this->SendData($header_data."\r\n")))
820 $this->PrepareData($body,$body_data);
821 $success=$this->SendData($body_data);
824 $success=$this->EndSendingData();
828 $disconnect_success=$this->Disconnect($success);
830 $success=$disconnect_success;