diff options
author | Renato Botelho <renato@netgate.com> | 2015-08-25 08:08:24 -0300 |
---|---|---|
committer | Renato Botelho <renato@netgate.com> | 2015-08-25 14:49:54 -0300 |
commit | 46bc6e545a17e77202aaf01ec0cd8d5a46567525 (patch) | |
tree | 32d18dda436ec739c67c489ceb771e8629cd926f /src/etc/inc/smtp.inc | |
parent | 4d9801c2dbd2b3e54a39578ee62b93af66607227 (diff) | |
download | pfsense-46bc6e545a17e77202aaf01ec0cd8d5a46567525.zip pfsense-46bc6e545a17e77202aaf01ec0cd8d5a46567525.tar.gz |
Move main pfSense content to src/
Diffstat (limited to 'src/etc/inc/smtp.inc')
-rw-r--r-- | src/etc/inc/smtp.inc | 862 |
1 files changed, 862 insertions, 0 deletions
diff --git a/src/etc/inc/smtp.inc b/src/etc/inc/smtp.inc new file mode 100644 index 0000000..035a30a --- /dev/null +++ b/src/etc/inc/smtp.inc @@ -0,0 +1,862 @@ +<?php +/* + * smtp.php + * + * @(#) $Header$ + * + */ + +/* + pfSense_MODULE: notifications +*/ + +class smtp_class +{ + var $user=""; + var $realm=""; + var $password=""; + var $workstation=""; + var $authentication_mechanism=""; + var $host_name=""; + var $host_port=25; + var $ssl=0; + var $tls=0; + var $localhost=""; + var $timeout=0; + var $data_timeout=0; + var $direct_delivery=0; + var $error=""; + var $debug=0; + var $html_debug=0; + var $esmtp=1; + var $esmtp_host=""; + var $esmtp_extensions=array(); + var $maximum_piped_recipients=100; + var $exclude_address=""; + var $getmxrr="GetMXRR"; + var $pop3_auth_host=""; + var $pop3_auth_port=110; + + /* private variables - DO NOT ACCESS */ + + var $state="Disconnected"; + var $connection=0; + var $pending_recipients=0; + var $next_token=""; + var $direct_sender=""; + var $connected_domain=""; + var $result_code; + var $disconnected_error=0; + + /* Private methods - DO NOT CALL */ + + Function Tokenize($string,$separator="") + { + if(!strcmp($separator,"")) + { + $separator=$string; + $string=$this->next_token; + } + for($character=0;$character<strlen($separator);$character++) + { + if(GetType($position=strpos($string,$separator[$character]))=="integer") + $found=(IsSet($found) ? min($found,$position) : $position); + } + if(IsSet($found)) + { + $this->next_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","<br />\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 connection"); + 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;$codes<count($code) && strcmp($this->result_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;$host<count($hosts);$host++) + $mxhosts[$weights[$host]]=$hosts[$host]; + KSort($mxhosts); + for(Reset($mxhosts),$host=0;$host<count($mxhosts);Next($mxhosts),$host++) + $hosts[$host]=$mxhosts[Key($mxhosts)]; + } + else + { + if(strcmp(@gethostbyname($domain),$domain)!=0) + $hosts[]=$domain; + } + } + else + { + if(strlen($this->host_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) && $host<count($hosts);$host++) + { + $domain=$hosts[$host]; + $error=$this->ConnectToHost($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=gethostname(),"")) + $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;$response<count($responses);$response++) { + $extension = strtoupper($this->Tokenize($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;$recipient<count($recipients);$recipient++) + { + if(!($success=$this->SetRecipient($recipients[$recipient]))) + break; + } + if($success + && ($success=$this->StartData())) + { + for($header_data="",$header=0;$header<count($headers);$header++) + $header_data.=$headers[$header]."\r\n"; + if(($success=$this->SendData($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); + } + +}; + +?> |