├── class ├── tests │ ├── empty │ ├── bug17178.phpt │ ├── bug17317.phpt │ ├── 13659.phpt │ ├── smtp_error.phpt │ ├── 9137.phpt │ ├── 9137_2.phpt │ └── rfc822.phpt ├── Mail │ ├── package.xml │ ├── null.php │ ├── mock.php │ ├── mail.php │ ├── sendmail.php │ ├── smtp.php │ ├── smtpmx.php │ ├── Net │ │ ├── Net │ │ │ └── Socket.php │ │ └── SMTP.php │ └── RFC822.php └── Mail.php ├── _config.yml ├── INSTRUCTIONS.pdf ├── SCREEN_SHOT.jpg ├── images ├── logo.png ├── youtube.JPG ├── background.jpg ├── download.jpg └── SCREEN_SHOT.jpg ├── EVIL-EMAIL-SMS-BOMBER-master.zip ├── target_emails.txt ├── emails_to_send_from.csv ├── README.md ├── package.xml ├── index.html └── mailer_flooder.php /class/tests/empty: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-time-machine -------------------------------------------------------------------------------- /INSTRUCTIONS.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEODEMON/EVIL-EMAIL-SMS-BOMBER/HEAD/INSTRUCTIONS.pdf -------------------------------------------------------------------------------- /SCREEN_SHOT.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEODEMON/EVIL-EMAIL-SMS-BOMBER/HEAD/SCREEN_SHOT.jpg -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEODEMON/EVIL-EMAIL-SMS-BOMBER/HEAD/images/logo.png -------------------------------------------------------------------------------- /images/youtube.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEODEMON/EVIL-EMAIL-SMS-BOMBER/HEAD/images/youtube.JPG -------------------------------------------------------------------------------- /images/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEODEMON/EVIL-EMAIL-SMS-BOMBER/HEAD/images/background.jpg -------------------------------------------------------------------------------- /images/download.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEODEMON/EVIL-EMAIL-SMS-BOMBER/HEAD/images/download.jpg -------------------------------------------------------------------------------- /images/SCREEN_SHOT.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEODEMON/EVIL-EMAIL-SMS-BOMBER/HEAD/images/SCREEN_SHOT.jpg -------------------------------------------------------------------------------- /EVIL-EMAIL-SMS-BOMBER-master.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEODEMON/EVIL-EMAIL-SMS-BOMBER/HEAD/EVIL-EMAIL-SMS-BOMBER-master.zip -------------------------------------------------------------------------------- /target_emails.txt: -------------------------------------------------------------------------------- 1 | joeblow@thehatefactory.com 2 | samfish@gmail.com 3 | fredstoner@aol.com 4 | janedoe@facebook.com 5 | trollbitch@youwish.com 6 | -------------------------------------------------------------------------------- /emails_to_send_from.csv: -------------------------------------------------------------------------------- 1 | trollarmy@somedomain.com,trollarmy,PASSWORD,smtp.somedomain.com,587, 2 | brookelovesmith@aol.com,brookelovesmith,PASSWORD,smtp.aol.com,587, 3 | bobbyjackson@aol.com,bobbyjackson,PASSWORD,smtp.aol.com,587, 4 | markzuck@gmail.com,markzuck@gmail.com,PASSWORD,smtp.gmail.com,587, 5 | -------------------------------------------------------------------------------- /class/tests/bug17178.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Mail_RFC822::parseAddressList does not accept RFC-valid group syntax 3 | --FILE-- 4 | (test)'; 10 | $parser = new Mail_RFC822(); 11 | $result = $parser->parseAddressList($address, 'anydomain.com', TRUE); 12 | 13 | if (!PEAR::isError($result) && is_array($result) && is_object($result[0])) 14 | if ($result[0]->personal == '"Test Student"' && 15 | $result[0]->mailbox == "test" && 16 | $result[0]->host == "mydomain.com" && 17 | is_array($result[0]->comment) && $result[0]->comment[0] == 'test') 18 | { 19 | print("OK"); 20 | } 21 | 22 | 23 | ?> 24 | --EXPECT-- 25 | OK 26 | -------------------------------------------------------------------------------- /class/tests/smtp_error.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Mail: SMTP Error Reporting 3 | --SKIPIF-- 4 | packageExists('Net_SMTP')) die("skip\n"); 10 | --FILE-- 11 | 'bogus.host.tld'); 16 | 17 | /* Create our SMTP-based mailer object. */ 18 | $mailer = Mail::factory('smtp', $params); 19 | 20 | /* Attempt to send an empty message in order to trigger an error. */ 21 | $e = $mailer->send(array(), array(), ''); 22 | if (is_a($e, 'PEAR_Error')) { 23 | $err = $e->getMessage(); 24 | if (preg_match('/Failed to connect to bogus.host.tld:25 \[SMTP: Failed to connect socket:.*/i', $err)) { 25 | echo "OK"; 26 | } 27 | } 28 | 29 | --EXPECT-- 30 | OK -------------------------------------------------------------------------------- /class/tests/9137.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Mail: Test for bug #9137 3 | --FILE-- 4 | 'John Doe', 'email' => 'test@example.com'), 11 | array('name' => 'John Doe\\', 'email' => 'test@example.com'), 12 | array('name' => 'John "Doe', 'email' => 'test@example.com'), 13 | array('name' => 'John "Doe\\', 'email' => 'test@example.com'), 14 | ); 15 | 16 | for ($i = 0; $i < count($addresses); $i++) { 17 | // construct the address 18 | $address = "\"" . addslashes($addresses[$i]['name']) . "\" ". 19 | "<".$addresses[$i]['email'].">"; 20 | 21 | $parsedAddresses = Mail_RFC822::parseAddressList($address); 22 | if (is_a($parsedAddresses, 'PEAR_Error')) { 23 | echo $address." :: Failed to validate\n"; 24 | } else { 25 | echo $address." :: Parsed\n"; 26 | } 27 | } 28 | 29 | --EXPECT-- 30 | "John Doe" :: Parsed 31 | "John Doe\\" :: Parsed 32 | "John \"Doe" :: Parsed 33 | "John \"Doe\\" :: Parsed 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EVIL-EMAIL-SMS-BOMBER 2 | 3 | 4 | ![alt tag](https://github.com/SEODEMON/EVIL-EMAIL-SMS-BOMBER/blob/master/images/logo.png) 5 | 6 | ### By Jeff Childers 7 | 8 | #### The EVIL EMAIL/SMS BOMBER is made for research and testing purposes only. Prank your friends. EVIL EMAIL/SMS BOMBER can send bulk mail or SMS text messages. 9 | 10 | #### DISCLAIMER: Use at your own risk! I take no responsibility for your actions. This was made for pranks, testing and investigation purposes only. 11 | 12 | ### SMS is Only For USA and Canada Numbers 13 | 14 | 15 | ![alt tag](https://github.com/SEODEMON/EVIL-EMAIL-SMS-BOMBER/blob/master/images/SCREEN_SHOT.jpg) 16 | 17 | # Features: 18 | ### Send Bulk Emails – Mass Emailing – Email Bombing 19 | 20 | ### Send Bulk SMS – Mass SMS Sending – SMSBombing = Only For USA and Canada Numbers 21 | 22 | #### Unlimited SMTP Servers Using Free Email Providers 23 | #### Multiple SMTP Servers Using Free Email Providers 24 | 25 | # How To USE 26 | 27 | PLEASE READ THE INSTRUCTIONS IN THE PDF FILE FIRST 28 | 29 | https://github.com/SEODEMON/EVIL-EMAIL-SMS-BOMBER/blob/master/INSTRUCTIONS.pdf 30 | 31 | 32 | [![IMAGE ALT TEXT HERE](https://github.com/SEODEMON/EVIL-EMAIL-SMS-BOMBER/blob/master/images/youtube.JPG)](https://www.youtube.com/watch?v=saMag6sGbV0 33 | 34 | 35 | -------------------------------------------------------------------------------- /class/tests/9137_2.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Mail: Test for bug #9137, take 2 3 | --FILE-- 4 | '"John Doe" '), 11 | array('raw' => '"John Doe' . chr(92) . '" '), 12 | array('raw' => '"John Doe' . chr(92) . chr(92) . '" '), 13 | array('raw' => '"John Doe' . chr(92) . chr(92) . chr(92) . '" '), 14 | array('raw' => '"John Doe' . chr(92) . chr(92) . chr(92) . chr(92) . '" '), 15 | array('raw' => '"John Doe '), 16 | ); 17 | 18 | for ($i = 0; $i < count($addresses); $i++) { 19 | // construct the address 20 | $address = $addresses[$i]['raw']; 21 | $parsedAddresses = Mail_RFC822::parseAddressList($address); 22 | if (PEAR::isError($parsedAddresses)) { 23 | echo $address." :: Failed to validate\n"; 24 | } else { 25 | echo $address." :: Parsed\n"; 26 | } 27 | } 28 | 29 | --EXPECT-- 30 | "John Doe" :: Parsed 31 | "John Doe\" :: Failed to validate 32 | "John Doe\\" :: Parsed 33 | "John Doe\\\" :: Failed to validate 34 | "John Doe\\\\" :: Parsed 35 | "John Doe :: Failed to validate 36 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Net_Socket 4 | pear.php.net 5 | Network Socket Interface 6 | Net_Socket is a class interface to TCP sockets. It provides blocking 7 | and non-blocking operation, with different reading and writing modes 8 | (byte-wise, block-wise, line-wise and special formats like network 9 | byte-order ip addresses). 10 | 11 | Chuck Hagenbuch 12 | chagenbu 13 | chuck@horde.org 14 | yes 15 | 16 | 17 | Stig Bakken 18 | ssb 19 | stig@php.net 20 | no 21 | 22 | 23 | Aleksander Machniak 24 | alec 25 | alec@php.net 26 | yes 27 | 28 | 2013-05-24 29 | 30 | 31 | 1.0.14 32 | 1.0.10 33 | 34 | 35 | stable 36 | stable 37 | 38 | PHP License 39 | 40 | - Fix connecting when host is specified with protocol prefix e.g. ssl:// 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 4.3.0 51 | 52 | 53 | 1.4.0b1 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Evil Email/SMS Bomber - ANONYMOUS 5 | 6 | 7 | 8 | 9 | 10 | 26 | 27 | 28 |
29 | Logo 30 |

PLEASE READ THE INSTRUCTIONS FIRST

31 |
32 |

Mail Subject: 33 |

34 |

SUPPORTS SPINTAX

EXAMPLE: 35 |

DO NOT USE SPINTAX IN SUBJECT 36 |
37 |

38 |

Message: 39 |

40 |

Times To Send: 41 |

42 |

43 |
44 |
45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /class/Mail/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Net_Socket2 4 | pear.php.net 5 | Network Socket Interface 6 | Net_Socket2 is a class interface to TCP sockets. It provides blocking 7 | and non-blocking operation, with different reading and writing modes 8 | (byte-wise, block-wise, line-wise and special formats like network 9 | byte-order ip addresses). 10 | 11 | Daniel O'Connor 12 | doconnor 13 | daniel.oconnor@gmail.com 14 | yes 15 | 16 | 17 | Chuck Hagenbuch 18 | chagenbu 19 | chuck@horde.org 20 | yes 21 | 22 | 23 | Stig Bakken 24 | ssb 25 | stig@php.net 26 | no 27 | 28 | 29 | Aleksander Machniak 30 | alec 31 | alec@php.net 32 | yes 33 | 34 | 2015-03-23 35 | 36 | 37 | 0.1.1 38 | 0.1.0 39 | 40 | 41 | beta 42 | beta 43 | 44 | PHP License 45 | 46 | Bug #20434 constant OS_WINDOWS is not defined 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 5.0.0 58 | 59 | 60 | 1.5.6 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 0.1.0 69 | 0.1.0 70 | 71 | 72 | beta 73 | beta 74 | 75 | 2014-06-04 76 | PHP License 77 | 78 | Fork for PHP5+ 79 | QA release 80 | 81 | 82 | 83 | 84 | 0.1.1 85 | 0.1.0 86 | 87 | 88 | beta 89 | beta 90 | 91 | 2015-03-24 92 | PHP License 93 | 94 | Bug #20434 constant OS_WINDOWS is not defined 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /class/tests/rfc822.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Mail_RFC822: Address Parsing 3 | --FILE-- 4 | parseAddressList($address, null, true, true)); 12 | 13 | /* Address groups. */ 14 | $address = 'My Group: "Richard" (A comment), ted@example.com (Ted Bloggs), Barney;'; 15 | print_r($parser->parseAddressList($address, null, true, true)); 16 | 17 | /* A valid address with spaces in the local part. */ 18 | $address = '<"Jon Parise"@php.net>'; 19 | print_r($parser->parseAddressList($address, null, true, true)); 20 | 21 | /* An invalid address with spaces in the local part. */ 22 | $address = ''; 23 | $result = $parser->parseAddressList($address, null, true, true); 24 | if (is_a($result, 'PEAR_Error')) echo $result->getMessage() . "\n"; 25 | 26 | /* A valid address with an uncommon TLD. */ 27 | $address = 'jon@host.longtld'; 28 | $result = $parser->parseAddressList($address, null, true, true); 29 | if (is_a($result, 'PEAR_Error')) echo $result->getMessage() . "\n"; 30 | 31 | --EXPECT-- 32 | Array 33 | ( 34 | [0] => stdClass Object 35 | ( 36 | [personal] => 37 | [comment] => Array 38 | ( 39 | ) 40 | 41 | [mailbox] => user 42 | [host] => example.com 43 | ) 44 | 45 | ) 46 | Array 47 | ( 48 | [0] => stdClass Object 49 | ( 50 | [groupname] => My Group 51 | [addresses] => Array 52 | ( 53 | [0] => stdClass Object 54 | ( 55 | [personal] => "Richard" 56 | [comment] => Array 57 | ( 58 | [0] => A comment 59 | ) 60 | 61 | [mailbox] => richard 62 | [host] => localhost 63 | ) 64 | 65 | [1] => stdClass Object 66 | ( 67 | [personal] => 68 | [comment] => Array 69 | ( 70 | [0] => Ted Bloggs 71 | ) 72 | 73 | [mailbox] => ted 74 | [host] => example.com 75 | ) 76 | 77 | [2] => stdClass Object 78 | ( 79 | [personal] => 80 | [comment] => Array 81 | ( 82 | ) 83 | 84 | [mailbox] => Barney 85 | [host] => localhost 86 | ) 87 | 88 | ) 89 | 90 | ) 91 | 92 | ) 93 | Array 94 | ( 95 | [0] => stdClass Object 96 | ( 97 | [personal] => 98 | [comment] => Array 99 | ( 100 | ) 101 | 102 | [mailbox] => "Jon Parise" 103 | [host] => php.net 104 | ) 105 | 106 | ) 107 | Validation failed for: 108 | -------------------------------------------------------------------------------- /class/Mail/null.php: -------------------------------------------------------------------------------- 1 | 40 | * @copyright 2010 Phil Kernick 41 | * @license http://opensource.org/licenses/bsd-license.php New BSD License 42 | * @version CVS: $Id$ 43 | * @link http://pear.php.net/package/Mail/ 44 | */ 45 | 46 | /** 47 | * Null implementation of the PEAR Mail:: interface. 48 | * @access public 49 | * @package Mail 50 | * @version $Revision$ 51 | */ 52 | class Mail_null extends Mail { 53 | 54 | /** 55 | * Implements Mail_null::send() function. Silently discards all 56 | * mail. 57 | * 58 | * @param mixed $recipients Either a comma-seperated list of recipients 59 | * (RFC822 compliant), or an array of recipients, 60 | * each RFC822 valid. This may contain recipients not 61 | * specified in the headers, for Bcc:, resending 62 | * messages, etc. 63 | * 64 | * @param array $headers The array of headers to send with the mail, in an 65 | * associative array, where the array key is the 66 | * header name (ie, 'Subject'), and the array value 67 | * is the header value (ie, 'test'). The header 68 | * produced from those values would be 'Subject: 69 | * test'. 70 | * 71 | * @param string $body The full text of the message body, including any 72 | * Mime parts, etc. 73 | * 74 | * @return mixed Returns true on success, or a PEAR_Error 75 | * containing a descriptive error message on 76 | * failure. 77 | */ 78 | public function send($recipients, $headers, $body) 79 | { 80 | return true; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /class/Mail/mock.php: -------------------------------------------------------------------------------- 1 | 40 | * @copyright 2010 Chuck Hagenbuch 41 | * @license http://opensource.org/licenses/bsd-license.php New BSD License 42 | * @version CVS: $Id$ 43 | * @link http://pear.php.net/package/Mail/ 44 | */ 45 | 46 | /** 47 | * Mock implementation of the PEAR Mail:: interface for testing. 48 | * @access public 49 | * @package Mail 50 | * @version $Revision$ 51 | */ 52 | class Mail_mock extends Mail { 53 | 54 | /** 55 | * Array of messages that have been sent with the mock. 56 | * 57 | * @var array 58 | */ 59 | public $sentMessages = array(); 60 | 61 | /** 62 | * Callback before sending mail. 63 | * 64 | * @var callback 65 | */ 66 | protected $_preSendCallback; 67 | 68 | /** 69 | * Callback after sending mai. 70 | * 71 | * @var callback 72 | */ 73 | protected $_postSendCallback; 74 | 75 | /** 76 | * Constructor. 77 | * 78 | * Instantiates a new Mail_mock:: object based on the parameters 79 | * passed in. It looks for the following parameters, both optional: 80 | * preSendCallback Called before an email would be sent. 81 | * postSendCallback Called after an email would have been sent. 82 | * 83 | * @param array Hash containing any parameters. 84 | */ 85 | public function __construct($params) 86 | { 87 | if (isset($params['preSendCallback']) && 88 | is_callable($params['preSendCallback'])) { 89 | $this->_preSendCallback = $params['preSendCallback']; 90 | } 91 | 92 | if (isset($params['postSendCallback']) && 93 | is_callable($params['postSendCallback'])) { 94 | $this->_postSendCallback = $params['postSendCallback']; 95 | } 96 | } 97 | 98 | /** 99 | * Implements Mail_mock::send() function. Silently discards all 100 | * mail. 101 | * 102 | * @param mixed $recipients Either a comma-seperated list of recipients 103 | * (RFC822 compliant), or an array of recipients, 104 | * each RFC822 valid. This may contain recipients not 105 | * specified in the headers, for Bcc:, resending 106 | * messages, etc. 107 | * 108 | * @param array $headers The array of headers to send with the mail, in an 109 | * associative array, where the array key is the 110 | * header name (ie, 'Subject'), and the array value 111 | * is the header value (ie, 'test'). The header 112 | * produced from those values would be 'Subject: 113 | * test'. 114 | * 115 | * @param string $body The full text of the message body, including any 116 | * Mime parts, etc. 117 | * 118 | * @return mixed Returns true on success, or a PEAR_Error 119 | * containing a descriptive error message on 120 | * failure. 121 | */ 122 | public function send($recipients, $headers, $body) 123 | { 124 | if ($this->_preSendCallback) { 125 | call_user_func_array($this->_preSendCallback, 126 | array(&$this, $recipients, $headers, $body)); 127 | } 128 | 129 | $entry = array('recipients' => $recipients, 'headers' => $headers, 'body' => $body); 130 | $this->sentMessages[] = $entry; 131 | 132 | if ($this->_postSendCallback) { 133 | call_user_func_array($this->_postSendCallback, 134 | array(&$this, $recipients, $headers, $body)); 135 | } 136 | 137 | return true; 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /mailer_flooder.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Evil Email/SMS Bomber 5 | 6 | 7 | 8 | 9 | 62 | 63 | 64 | 65 | Logo 66 |
67 | $from, 160 | 'To' => $to, 161 | 'Subject' => $subject); 162 | $smtp = Mail::factory('smtp', 163 | array ('host' => $host, 164 | 'port' => $port, 165 | 'auth' => true, 166 | 'username' => $username, 167 | 'password' => $password)); 168 | 169 | 170 | 171 | 172 | $sendtimes = $_POST['sendtimes']; 173 | 174 | 175 | 176 | } 177 | } 178 | } 179 | } 180 | } 181 | } 182 | 183 | 184 | 185 | 186 | while ($number < $sendtimes) { 187 | $number++; 188 | 189 | 190 | $mail = $smtp->send($to, $headers, $body); 191 | sleep(2); 192 | } 193 | 194 | 195 | if (PEAR::isError($mail)) { 196 | 197 | echo nl2br(' \n
ERROR: '. $From_Email .' ('. $smtp_server_info .') ' . $mail->getMessage() . ' \n
'); 198 | 199 | } else { 200 | 201 | echo nl2br(' \n
From: '. $Column_one . ' Sent '. $sendtimes .' times to '. $emailtarget . '
\n '); 202 | } 203 | 204 | } 205 | 206 | } 207 | ob_flush(); 208 | flush(); 209 | ob_end_clean(); 210 | 211 | 212 | // BY PHP NINJA JEFF CHILDERS 213 | // for RESEARCH PURPOSES ONLY 214 | 215 | ?> 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /class/Mail/mail.php: -------------------------------------------------------------------------------- 1 | 40 | * @copyright 2010 Chuck Hagenbuch 41 | * @license http://opensource.org/licenses/bsd-license.php New BSD License 42 | * @version CVS: $Id$ 43 | * @link http://pear.php.net/package/Mail/ 44 | */ 45 | 46 | /** 47 | * internal PHP-mail() implementation of the PEAR Mail:: interface. 48 | * @package Mail 49 | * @version $Revision$ 50 | */ 51 | class Mail_mail extends Mail { 52 | 53 | /** 54 | * Any arguments to pass to the mail() function. 55 | * @var string 56 | */ 57 | var $_params = ''; 58 | 59 | /** 60 | * Constructor. 61 | * 62 | * Instantiates a new Mail_mail:: object based on the parameters 63 | * passed in. 64 | * 65 | * @param array $params Extra arguments for the mail() function. 66 | */ 67 | public function __construct($params = null) 68 | { 69 | // The other mail implementations accept parameters as arrays. 70 | // In the interest of being consistent, explode an array into 71 | // a string of parameter arguments. 72 | if (is_array($params)) { 73 | $this->_params = join(' ', $params); 74 | } else { 75 | $this->_params = $params; 76 | } 77 | 78 | /* Because the mail() function may pass headers as command 79 | * line arguments, we can't guarantee the use of the standard 80 | * "\r\n" separator. Instead, we use the system's native line 81 | * separator. */ 82 | if (defined('PHP_EOL')) { 83 | $this->sep = PHP_EOL; 84 | } else { 85 | $this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n"; 86 | } 87 | } 88 | 89 | /** 90 | * Implements Mail_mail::send() function using php's built-in mail() 91 | * command. 92 | * 93 | * @param mixed $recipients Either a comma-seperated list of recipients 94 | * (RFC822 compliant), or an array of recipients, 95 | * each RFC822 valid. This may contain recipients not 96 | * specified in the headers, for Bcc:, resending 97 | * messages, etc. 98 | * 99 | * @param array $headers The array of headers to send with the mail, in an 100 | * associative array, where the array key is the 101 | * header name (ie, 'Subject'), and the array value 102 | * is the header value (ie, 'test'). The header 103 | * produced from those values would be 'Subject: 104 | * test'. 105 | * 106 | * @param string $body The full text of the message body, including any 107 | * Mime parts, etc. 108 | * 109 | * @return mixed Returns true on success, or a PEAR_Error 110 | * containing a descriptive error message on 111 | * failure. 112 | */ 113 | public function send($recipients, $headers, $body) 114 | { 115 | if (!is_array($headers)) { 116 | return PEAR::raiseError('$headers must be an array'); 117 | } 118 | 119 | $result = $this->_sanitizeHeaders($headers); 120 | if (is_a($result, 'PEAR_Error')) { 121 | return $result; 122 | } 123 | 124 | // If we're passed an array of recipients, implode it. 125 | if (is_array($recipients)) { 126 | $recipients = implode(', ', $recipients); 127 | } 128 | 129 | // Get the Subject out of the headers array so that we can 130 | // pass it as a seperate argument to mail(). 131 | $subject = ''; 132 | if (isset($headers['Subject'])) { 133 | $subject = $headers['Subject']; 134 | unset($headers['Subject']); 135 | } 136 | 137 | // Also remove the To: header. The mail() function will add its own 138 | // To: header based on the contents of $recipients. 139 | unset($headers['To']); 140 | 141 | // Flatten the headers out. 142 | $headerElements = $this->prepareHeaders($headers); 143 | if (is_a($headerElements, 'PEAR_Error')) { 144 | return $headerElements; 145 | } 146 | list(, $text_headers) = $headerElements; 147 | 148 | // We only use mail()'s optional fifth parameter if the additional 149 | // parameters have been provided and we're not running in safe mode. 150 | if (empty($this->_params) || ini_get('safe_mode')) { 151 | $result = mail($recipients, $subject, $body, $text_headers); 152 | } else { 153 | $result = mail($recipients, $subject, $body, $text_headers, 154 | $this->_params); 155 | } 156 | 157 | // If the mail() function returned failure, we need to create a 158 | // PEAR_Error object and return it instead of the boolean result. 159 | if ($result === false) { 160 | $result = PEAR::raiseError('mail() returned failure'); 161 | } 162 | 163 | return $result; 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /class/Mail/sendmail.php: -------------------------------------------------------------------------------- 1 | | 17 | // +----------------------------------------------------------------------+ 18 | 19 | /** 20 | * Sendmail implementation of the PEAR Mail:: interface. 21 | * @access public 22 | * @package Mail 23 | * @version $Revision$ 24 | */ 25 | class Mail_sendmail extends Mail { 26 | 27 | /** 28 | * The location of the sendmail or sendmail wrapper binary on the 29 | * filesystem. 30 | * @var string 31 | */ 32 | var $sendmail_path = '/usr/sbin/sendmail'; 33 | 34 | /** 35 | * Any extra command-line parameters to pass to the sendmail or 36 | * sendmail wrapper binary. 37 | * @var string 38 | */ 39 | var $sendmail_args = '-i'; 40 | 41 | /** 42 | * Constructor. 43 | * 44 | * Instantiates a new Mail_sendmail:: object based on the parameters 45 | * passed in. It looks for the following parameters: 46 | * sendmail_path The location of the sendmail binary on the 47 | * filesystem. Defaults to '/usr/sbin/sendmail'. 48 | * 49 | * sendmail_args Any extra parameters to pass to the sendmail 50 | * or sendmail wrapper binary. 51 | * 52 | * If a parameter is present in the $params array, it replaces the 53 | * default. 54 | * 55 | * @param array $params Hash containing any parameters different from the 56 | * defaults. 57 | */ 58 | public function __construct($params) 59 | { 60 | if (isset($params['sendmail_path'])) { 61 | $this->sendmail_path = $params['sendmail_path']; 62 | } 63 | if (isset($params['sendmail_args'])) { 64 | $this->sendmail_args = $params['sendmail_args']; 65 | } 66 | 67 | /* 68 | * Because we need to pass message headers to the sendmail program on 69 | * the commandline, we can't guarantee the use of the standard "\r\n" 70 | * separator. Instead, we use the system's native line separator. 71 | */ 72 | if (defined('PHP_EOL')) { 73 | $this->sep = PHP_EOL; 74 | } else { 75 | $this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n"; 76 | } 77 | } 78 | 79 | /** 80 | * Implements Mail::send() function using the sendmail 81 | * command-line binary. 82 | * 83 | * @param mixed $recipients Either a comma-seperated list of recipients 84 | * (RFC822 compliant), or an array of recipients, 85 | * each RFC822 valid. This may contain recipients not 86 | * specified in the headers, for Bcc:, resending 87 | * messages, etc. 88 | * 89 | * @param array $headers The array of headers to send with the mail, in an 90 | * associative array, where the array key is the 91 | * header name (ie, 'Subject'), and the array value 92 | * is the header value (ie, 'test'). The header 93 | * produced from those values would be 'Subject: 94 | * test'. 95 | * 96 | * @param string $body The full text of the message body, including any 97 | * Mime parts, etc. 98 | * 99 | * @return mixed Returns true on success, or a PEAR_Error 100 | * containing a descriptive error message on 101 | * failure. 102 | */ 103 | public function send($recipients, $headers, $body) 104 | { 105 | if (!is_array($headers)) { 106 | return PEAR::raiseError('$headers must be an array'); 107 | } 108 | 109 | $result = $this->_sanitizeHeaders($headers); 110 | if (is_a($result, 'PEAR_Error')) { 111 | return $result; 112 | } 113 | 114 | $recipients = $this->parseRecipients($recipients); 115 | if (is_a($recipients, 'PEAR_Error')) { 116 | return $recipients; 117 | } 118 | $recipients = implode(' ', array_map('escapeshellarg', $recipients)); 119 | 120 | $headerElements = $this->prepareHeaders($headers); 121 | if (is_a($headerElements, 'PEAR_Error')) { 122 | return $headerElements; 123 | } 124 | list($from, $text_headers) = $headerElements; 125 | 126 | /* Since few MTAs are going to allow this header to be forged 127 | * unless it's in the MAIL FROM: exchange, we'll use 128 | * Return-Path instead of From: if it's set. */ 129 | if (!empty($headers['Return-Path'])) { 130 | $from = $headers['Return-Path']; 131 | } 132 | 133 | if (!isset($from)) { 134 | return PEAR::raiseError('No from address given.'); 135 | } elseif (strpos($from, ' ') !== false || 136 | strpos($from, ';') !== false || 137 | strpos($from, '&') !== false || 138 | strpos($from, '`') !== false) { 139 | return PEAR::raiseError('From address specified with dangerous characters.'); 140 | } 141 | 142 | $from = escapeshellarg($from); // Security bug #16200 143 | 144 | $mail = @popen($this->sendmail_path . (!empty($this->sendmail_args) ? ' ' . $this->sendmail_args : '') . " -f$from -- $recipients", 'w'); 145 | if (!$mail) { 146 | return PEAR::raiseError('Failed to open sendmail [' . $this->sendmail_path . '] for execution.'); 147 | } 148 | 149 | // Write the headers following by two newlines: one to end the headers 150 | // section and a second to separate the headers block from the body. 151 | fputs($mail, $text_headers . $this->sep . $this->sep); 152 | 153 | fputs($mail, $body); 154 | $result = pclose($mail); 155 | if (version_compare(phpversion(), '4.2.3') == -1) { 156 | // With older php versions, we need to shift the pclose 157 | // result to get the exit code. 158 | $result = $result >> 8 & 0xFF; 159 | } 160 | 161 | if ($result != 0) { 162 | return PEAR::raiseError('sendmail returned error code ' . $result, 163 | $result); 164 | } 165 | 166 | return true; 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /class/Mail.php: -------------------------------------------------------------------------------- 1 | 40 | * @copyright 1997-2010 Chuck Hagenbuch 41 | * @license http://opensource.org/licenses/bsd-license.php New BSD License 42 | * @version CVS: $Id$ 43 | * @link http://pear.php.net/package/Mail/ 44 | */ 45 | 46 | require_once 'PEAR.php'; 47 | 48 | /** 49 | * PEAR's Mail:: interface. Defines the interface for implementing 50 | * mailers under the PEAR hierarchy, and provides supporting functions 51 | * useful in multiple mailer backends. 52 | * 53 | * @version $Revision$ 54 | * @package Mail 55 | */ 56 | class Mail 57 | { 58 | /** 59 | * Line terminator used for separating header lines. 60 | * @var string 61 | */ 62 | public $sep = "\r\n"; 63 | 64 | /** 65 | * Provides an interface for generating Mail:: objects of various 66 | * types 67 | * 68 | * @param string $driver The kind of Mail:: object to instantiate. 69 | * @param array $params The parameters to pass to the Mail:: object. 70 | * 71 | * @return object Mail a instance of the driver class or if fails a PEAR Error 72 | */ 73 | public static function factory($driver, $params = array()) 74 | { 75 | $driver = strtolower($driver); 76 | @include_once 'Mail/' . $driver . '.php'; 77 | $class = 'Mail_' . $driver; 78 | if (class_exists($class)) { 79 | $mailer = new $class($params); 80 | return $mailer; 81 | } else { 82 | return PEAR::raiseError('Unable to find class for driver ' . $driver); 83 | } 84 | } 85 | 86 | /** 87 | * Implements Mail::send() function using php's built-in mail() 88 | * command. 89 | * 90 | * @param mixed $recipients Either a comma-seperated list of recipients 91 | * (RFC822 compliant), or an array of recipients, 92 | * each RFC822 valid. This may contain recipients not 93 | * specified in the headers, for Bcc:, resending 94 | * messages, etc. 95 | * 96 | * @param array $headers The array of headers to send with the mail, in an 97 | * associative array, where the array key is the 98 | * header name (ie, 'Subject'), and the array value 99 | * is the header value (ie, 'test'). The header 100 | * produced from those values would be 'Subject: 101 | * test'. 102 | * 103 | * @param string $body The full text of the message body, including any 104 | * Mime parts, etc. 105 | * 106 | * @return mixed Returns true on success, or a PEAR_Error 107 | * containing a descriptive error message on 108 | * failure. 109 | * 110 | * @deprecated use Mail_mail::send instead 111 | */ 112 | public function send($recipients, $headers, $body) 113 | { 114 | if (!is_array($headers)) { 115 | return PEAR::raiseError('$headers must be an array'); 116 | } 117 | 118 | $result = $this->_sanitizeHeaders($headers); 119 | if (is_a($result, 'PEAR_Error')) { 120 | return $result; 121 | } 122 | 123 | // if we're passed an array of recipients, implode it. 124 | if (is_array($recipients)) { 125 | $recipients = implode(', ', $recipients); 126 | } 127 | 128 | // get the Subject out of the headers array so that we can 129 | // pass it as a seperate argument to mail(). 130 | $subject = ''; 131 | if (isset($headers['Subject'])) { 132 | $subject = $headers['Subject']; 133 | unset($headers['Subject']); 134 | } 135 | 136 | // flatten the headers out. 137 | list(, $text_headers) = Mail::prepareHeaders($headers); 138 | 139 | return mail($recipients, $subject, $body, $text_headers); 140 | } 141 | 142 | /** 143 | * Sanitize an array of mail headers by removing any additional header 144 | * strings present in a legitimate header's value. The goal of this 145 | * filter is to prevent mail injection attacks. 146 | * 147 | * @param array $headers The associative array of headers to sanitize. 148 | */ 149 | protected function _sanitizeHeaders(&$headers) 150 | { 151 | foreach ($headers as $key => $value) { 152 | $headers[$key] = 153 | preg_replace('=((||0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', 154 | null, $value); 155 | } 156 | } 157 | 158 | /** 159 | * Take an array of mail headers and return a string containing 160 | * text usable in sending a message. 161 | * 162 | * @param array $headers The array of headers to prepare, in an associative 163 | * array, where the array key is the header name (ie, 164 | * 'Subject'), and the array value is the header 165 | * value (ie, 'test'). The header produced from those 166 | * values would be 'Subject: test'. 167 | * 168 | * @return mixed Returns false if it encounters a bad address, 169 | * otherwise returns an array containing two 170 | * elements: Any From: address found in the headers, 171 | * and the plain text version of the headers. 172 | */ 173 | protected function prepareHeaders($headers) 174 | { 175 | $lines = array(); 176 | $from = null; 177 | 178 | foreach ($headers as $key => $value) { 179 | if (strcasecmp($key, 'From') === 0) { 180 | include_once 'Mail/RFC822.php'; 181 | $parser = new Mail_RFC822(); 182 | $addresses = $parser->parseAddressList($value, 'localhost', false); 183 | if (is_a($addresses, 'PEAR_Error')) { 184 | return $addresses; 185 | } 186 | 187 | $from = $addresses[0]->mailbox . '@' . $addresses[0]->host; 188 | 189 | // Reject envelope From: addresses with spaces. 190 | if (strstr($from, ' ')) { 191 | return false; 192 | } 193 | 194 | $lines[] = $key . ': ' . $value; 195 | } elseif (strcasecmp($key, 'Received') === 0) { 196 | $received = array(); 197 | if (is_array($value)) { 198 | foreach ($value as $line) { 199 | $received[] = $key . ': ' . $line; 200 | } 201 | } 202 | else { 203 | $received[] = $key . ': ' . $value; 204 | } 205 | // Put Received: headers at the top. Spam detectors often 206 | // flag messages with Received: headers after the Subject: 207 | // as spam. 208 | $lines = array_merge($received, $lines); 209 | } else { 210 | // If $value is an array (i.e., a list of addresses), convert 211 | // it to a comma-delimited string of its elements (addresses). 212 | if (is_array($value)) { 213 | $value = implode(', ', $value); 214 | } 215 | $lines[] = $key . ': ' . $value; 216 | } 217 | } 218 | 219 | return array($from, join($this->sep, $lines)); 220 | } 221 | 222 | /** 223 | * Take a set of recipients and parse them, returning an array of 224 | * bare addresses (forward paths) that can be passed to sendmail 225 | * or an smtp server with the rcpt to: command. 226 | * 227 | * @param mixed Either a comma-seperated list of recipients 228 | * (RFC822 compliant), or an array of recipients, 229 | * each RFC822 valid. 230 | * 231 | * @return mixed An array of forward paths (bare addresses) or a PEAR_Error 232 | * object if the address list could not be parsed. 233 | */ 234 | protected function parseRecipients($recipients) 235 | { 236 | include_once 'Mail/RFC822.php'; 237 | 238 | // if we're passed an array, assume addresses are valid and 239 | // implode them before parsing. 240 | if (is_array($recipients)) { 241 | $recipients = implode(', ', $recipients); 242 | } 243 | 244 | // Parse recipients, leaving out all personal info. This is 245 | // for smtp recipients, etc. All relevant personal information 246 | // should already be in the headers. 247 | $Mail_RFC822 = new Mail_RFC822(); 248 | $addresses = $Mail_RFC822->parseAddressList($recipients, 'localhost', false); 249 | 250 | // If parseAddressList() returned a PEAR_Error object, just return it. 251 | if (is_a($addresses, 'PEAR_Error')) { 252 | return $addresses; 253 | } 254 | 255 | $recipients = array(); 256 | if (is_array($addresses)) { 257 | foreach ($addresses as $ob) { 258 | $recipients[] = $ob->mailbox . '@' . $ob->host; 259 | } 260 | } 261 | 262 | return $recipients; 263 | } 264 | 265 | } 266 | 267 | 268 | -------------------------------------------------------------------------------- /class/Mail/smtp.php: -------------------------------------------------------------------------------- 1 | 40 | * @author Chuck Hagenbuch 41 | * @copyright 2010 Chuck Hagenbuch 42 | * @license http://opensource.org/licenses/bsd-license.php New BSD License 43 | * @version CVS: $Id$ 44 | * @link http://pear.php.net/package/Mail/ 45 | */ 46 | 47 | /** Error: Failed to create a Net_SMTP object */ 48 | define('PEAR_MAIL_SMTP_ERROR_CREATE', 10000); 49 | 50 | /** Error: Failed to connect to SMTP server */ 51 | define('PEAR_MAIL_SMTP_ERROR_CONNECT', 10001); 52 | 53 | /** Error: SMTP authentication failure */ 54 | define('PEAR_MAIL_SMTP_ERROR_AUTH', 10002); 55 | 56 | /** Error: No From: address has been provided */ 57 | define('PEAR_MAIL_SMTP_ERROR_FROM', 10003); 58 | 59 | /** Error: Failed to set sender */ 60 | define('PEAR_MAIL_SMTP_ERROR_SENDER', 10004); 61 | 62 | /** Error: Failed to add recipient */ 63 | define('PEAR_MAIL_SMTP_ERROR_RECIPIENT', 10005); 64 | 65 | /** Error: Failed to send data */ 66 | define('PEAR_MAIL_SMTP_ERROR_DATA', 10006); 67 | 68 | /** 69 | * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class. 70 | * @access public 71 | * @package Mail 72 | * @version $Revision$ 73 | */ 74 | class Mail_smtp extends Mail { 75 | 76 | /** 77 | * SMTP connection object. 78 | * 79 | * @var object 80 | * @access private 81 | */ 82 | var $_smtp = null; 83 | 84 | /** 85 | * The list of service extension parameters to pass to the Net_SMTP 86 | * mailFrom() command. 87 | * @var array 88 | */ 89 | var $_extparams = array(); 90 | 91 | /** 92 | * The SMTP host to connect to. 93 | * @var string 94 | */ 95 | var $host = 'localhost'; 96 | 97 | /** 98 | * The port the SMTP server is on. 99 | * @var integer 100 | */ 101 | var $port = 25; 102 | 103 | /** 104 | * Should SMTP authentication be used? 105 | * 106 | * This value may be set to true, false or the name of a specific 107 | * authentication method. 108 | * 109 | * If the value is set to true, the Net_SMTP package will attempt to use 110 | * the best authentication method advertised by the remote SMTP server. 111 | * 112 | * @var mixed 113 | */ 114 | var $auth = false; 115 | 116 | /** 117 | * The username to use if the SMTP server requires authentication. 118 | * @var string 119 | */ 120 | var $username = ''; 121 | 122 | /** 123 | * The password to use if the SMTP server requires authentication. 124 | * @var string 125 | */ 126 | var $password = ''; 127 | 128 | /** 129 | * Hostname or domain that will be sent to the remote SMTP server in the 130 | * HELO / EHLO message. 131 | * 132 | * @var string 133 | */ 134 | var $localhost = 'localhost'; 135 | 136 | /** 137 | * SMTP connection timeout value. NULL indicates no timeout. 138 | * 139 | * @var integer 140 | */ 141 | var $timeout = null; 142 | 143 | /** 144 | * Turn on Net_SMTP debugging? 145 | * 146 | * @var boolean $debug 147 | */ 148 | var $debug = false; 149 | 150 | /** 151 | * Indicates whether or not the SMTP connection should persist over 152 | * multiple calls to the send() method. 153 | * 154 | * @var boolean 155 | */ 156 | var $persist = false; 157 | 158 | /** 159 | * Use SMTP command pipelining (specified in RFC 2920) if the SMTP server 160 | * supports it. This speeds up delivery over high-latency connections. By 161 | * default, use the default value supplied by Net_SMTP. 162 | * @var bool 163 | */ 164 | var $pipelining; 165 | 166 | var $socket_options = array(); 167 | 168 | /** 169 | * Constructor. 170 | * 171 | * Instantiates a new Mail_smtp:: object based on the parameters 172 | * passed in. It looks for the following parameters: 173 | * host The server to connect to. Defaults to localhost. 174 | * port The port to connect to. Defaults to 25. 175 | * auth SMTP authentication. Defaults to none. 176 | * username The username to use for SMTP auth. No default. 177 | * password The password to use for SMTP auth. No default. 178 | * localhost The local hostname / domain. Defaults to localhost. 179 | * timeout The SMTP connection timeout. Defaults to none. 180 | * verp Whether to use VERP or not. Defaults to false. 181 | * DEPRECATED as of 1.2.0 (use setMailParams()). 182 | * debug Activate SMTP debug mode? Defaults to false. 183 | * persist Should the SMTP connection persist? 184 | * pipelining Use SMTP command pipelining 185 | * 186 | * If a parameter is present in the $params array, it replaces the 187 | * default. 188 | * 189 | * @param array Hash containing any parameters different from the 190 | * defaults. 191 | */ 192 | public function __construct($params) 193 | { 194 | if (isset($params['host'])) $this->host = $params['host']; 195 | if (isset($params['port'])) $this->port = $params['port']; 196 | if (isset($params['auth'])) $this->auth = $params['auth']; 197 | if (isset($params['username'])) $this->username = $params['username']; 198 | if (isset($params['password'])) $this->password = $params['password']; 199 | if (isset($params['localhost'])) $this->localhost = $params['localhost']; 200 | if (isset($params['timeout'])) $this->timeout = $params['timeout']; 201 | if (isset($params['debug'])) $this->debug = (bool)$params['debug']; 202 | if (isset($params['persist'])) $this->persist = (bool)$params['persist']; 203 | if (isset($params['pipelining'])) $this->pipelining = (bool)$params['pipelining']; 204 | if (isset($params['socket_options'])) $this->socket_options = $params['socket_options']; 205 | // Deprecated options 206 | if (isset($params['verp'])) { 207 | $this->addServiceExtensionParameter('XVERP', is_bool($params['verp']) ? null : $params['verp']); 208 | } 209 | } 210 | 211 | /** 212 | * Destructor implementation to ensure that we disconnect from any 213 | * potentially-alive persistent SMTP connections. 214 | */ 215 | public function __destruct() 216 | { 217 | $this->disconnect(); 218 | } 219 | 220 | /** 221 | * Implements Mail::send() function using SMTP. 222 | * 223 | * @param mixed $recipients Either a comma-seperated list of recipients 224 | * (RFC822 compliant), or an array of recipients, 225 | * each RFC822 valid. This may contain recipients not 226 | * specified in the headers, for Bcc:, resending 227 | * messages, etc. 228 | * 229 | * @param array $headers The array of headers to send with the mail, in an 230 | * associative array, where the array key is the 231 | * header name (e.g., 'Subject'), and the array value 232 | * is the header value (e.g., 'test'). The header 233 | * produced from those values would be 'Subject: 234 | * test'. 235 | * 236 | * @param string $body The full text of the message body, including any 237 | * MIME parts, etc. 238 | * 239 | * @return mixed Returns true on success, or a PEAR_Error 240 | * containing a descriptive error message on 241 | * failure. 242 | */ 243 | public function send($recipients, $headers, $body) 244 | { 245 | /* If we don't already have an SMTP object, create one. */ 246 | $result = $this->getSMTPObject(); 247 | if (PEAR::isError($result)) { 248 | return $result; 249 | } 250 | 251 | if (!is_array($headers)) { 252 | return PEAR::raiseError('$headers must be an array'); 253 | } 254 | 255 | $this->_sanitizeHeaders($headers); 256 | 257 | $headerElements = $this->prepareHeaders($headers); 258 | if (is_a($headerElements, 'PEAR_Error')) { 259 | $this->_smtp->rset(); 260 | return $headerElements; 261 | } 262 | list($from, $textHeaders) = $headerElements; 263 | 264 | /* Since few MTAs are going to allow this header to be forged 265 | * unless it's in the MAIL FROM: exchange, we'll use 266 | * Return-Path instead of From: if it's set. */ 267 | if (!empty($headers['Return-Path'])) { 268 | $from = $headers['Return-Path']; 269 | } 270 | 271 | if (!isset($from)) { 272 | $this->_smtp->rset(); 273 | return PEAR::raiseError('No From: address has been provided', 274 | PEAR_MAIL_SMTP_ERROR_FROM); 275 | } 276 | 277 | $params = null; 278 | if (!empty($this->_extparams)) { 279 | foreach ($this->_extparams as $key => $val) { 280 | $params .= ' ' . $key . (is_null($val) ? '' : '=' . $val); 281 | } 282 | } 283 | if (PEAR::isError($res = $this->_smtp->mailFrom($from, ltrim($params)))) { 284 | $error = $this->_error("Failed to set sender: $from", $res); 285 | $this->_smtp->rset(); 286 | return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_SENDER); 287 | } 288 | 289 | $recipients = $this->parseRecipients($recipients); 290 | if (is_a($recipients, 'PEAR_Error')) { 291 | $this->_smtp->rset(); 292 | return $recipients; 293 | } 294 | 295 | foreach ($recipients as $recipient) { 296 | $res = $this->_smtp->rcptTo($recipient); 297 | if (is_a($res, 'PEAR_Error')) { 298 | $error = $this->_error("Failed to add recipient: $recipient", $res); 299 | $this->_smtp->rset(); 300 | return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_RECIPIENT); 301 | } 302 | } 303 | 304 | /* Send the message's headers and the body as SMTP data. */ 305 | $res = $this->_smtp->data($body, $textHeaders); 306 | list(,$args) = $this->_smtp->getResponse(); 307 | 308 | if (preg_match("/Ok: queued as (.*)/", $args, $queued)) { 309 | $this->queued_as = $queued[1]; 310 | } 311 | 312 | /* we need the greeting; from it we can extract the authorative name of the mail server we've really connected to. 313 | * ideal if we're connecting to a round-robin of relay servers and need to track which exact one took the email */ 314 | $this->greeting = $this->_smtp->getGreeting(); 315 | 316 | if (is_a($res, 'PEAR_Error')) { 317 | $error = $this->_error('Failed to send data', $res); 318 | $this->_smtp->rset(); 319 | return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_DATA); 320 | } 321 | 322 | /* If persistent connections are disabled, destroy our SMTP object. */ 323 | if ($this->persist === false) { 324 | $this->disconnect(); 325 | } 326 | 327 | return true; 328 | } 329 | 330 | /** 331 | * Connect to the SMTP server by instantiating a Net_SMTP object. 332 | * 333 | * @return mixed Returns a reference to the Net_SMTP object on success, or 334 | * a PEAR_Error containing a descriptive error message on 335 | * failure. 336 | * 337 | * @since 1.2.0 338 | */ 339 | public function getSMTPObject() 340 | { 341 | if (is_object($this->_smtp) !== false) { 342 | return $this->_smtp; 343 | } 344 | 345 | include_once 'Net/SMTP.php'; 346 | $this->_smtp = new Net_SMTP($this->host, 347 | $this->port, 348 | $this->localhost, 349 | $this->pipelining, 350 | 0, 351 | $this->socket_options); 352 | 353 | /* If we still don't have an SMTP object at this point, fail. */ 354 | if (is_object($this->_smtp) === false) { 355 | return PEAR::raiseError('Failed to create a Net_SMTP object', 356 | PEAR_MAIL_SMTP_ERROR_CREATE); 357 | } 358 | 359 | /* Configure the SMTP connection. */ 360 | if ($this->debug) { 361 | $this->_smtp->setDebug(true); 362 | } 363 | 364 | /* Attempt to connect to the configured SMTP server. */ 365 | if (PEAR::isError($res = $this->_smtp->connect($this->timeout))) { 366 | $error = $this->_error('Failed to connect to ' . 367 | $this->host . ':' . $this->port, 368 | $res); 369 | return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_CONNECT); 370 | } 371 | 372 | /* Attempt to authenticate if authentication has been enabled. */ 373 | if ($this->auth) { 374 | $method = is_string($this->auth) ? $this->auth : ''; 375 | 376 | if (PEAR::isError($res = $this->_smtp->auth($this->username, 377 | $this->password, 378 | $method))) { 379 | $error = $this->_error("$method authentication failure", 380 | $res); 381 | $this->_smtp->rset(); 382 | return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_AUTH); 383 | } 384 | } 385 | 386 | return $this->_smtp; 387 | } 388 | 389 | /** 390 | * Add parameter associated with a SMTP service extension. 391 | * 392 | * @param string Extension keyword. 393 | * @param string Any value the keyword needs. 394 | * 395 | * @since 1.2.0 396 | */ 397 | public function addServiceExtensionParameter($keyword, $value = null) 398 | { 399 | $this->_extparams[$keyword] = $value; 400 | } 401 | 402 | /** 403 | * Disconnect and destroy the current SMTP connection. 404 | * 405 | * @return boolean True if the SMTP connection no longer exists. 406 | * 407 | * @since 1.1.9 408 | */ 409 | public function disconnect() 410 | { 411 | /* If we have an SMTP object, disconnect and destroy it. */ 412 | if (is_object($this->_smtp) && $this->_smtp->disconnect()) { 413 | $this->_smtp = null; 414 | } 415 | 416 | /* We are disconnected if we no longer have an SMTP object. */ 417 | return ($this->_smtp === null); 418 | } 419 | 420 | /** 421 | * Build a standardized string describing the current SMTP error. 422 | * 423 | * @param string $text Custom string describing the error context. 424 | * @param object $error Reference to the current PEAR_Error object. 425 | * 426 | * @return string A string describing the current SMTP error. 427 | * 428 | * @since 1.1.7 429 | */ 430 | protected function _error($text, $error) 431 | { 432 | /* Split the SMTP response into a code and a response string. */ 433 | list($code, $response) = $this->_smtp->getResponse(); 434 | 435 | /* Build our standardized error string. */ 436 | return $text 437 | . ' [SMTP: ' . $error->getMessage() 438 | . " (code: $code, response: $response)]"; 439 | } 440 | 441 | 442 | 443 | } 444 | -------------------------------------------------------------------------------- /class/Mail/smtpmx.php: -------------------------------------------------------------------------------- 1 | 44 | * @copyright 2010 gERD Schaufelberger 45 | * @license http://opensource.org/licenses/bsd-license.php New BSD License 46 | * @version CVS: $Id$ 47 | * @link http://pear.php.net/package/Mail/ 48 | */ 49 | 50 | require_once 'Net/SMTP.php'; 51 | 52 | /** 53 | * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class. 54 | * 55 | * 56 | * @access public 57 | * @author gERD Schaufelberger 58 | * @package Mail 59 | * @version $Revision$ 60 | */ 61 | class Mail_smtpmx extends Mail { 62 | 63 | /** 64 | * SMTP connection object. 65 | * 66 | * @var object 67 | * @access private 68 | */ 69 | var $_smtp = null; 70 | 71 | /** 72 | * The port the SMTP server is on. 73 | * @var integer 74 | * @see getservicebyname() 75 | */ 76 | var $port = 25; 77 | 78 | /** 79 | * Hostname or domain that will be sent to the remote SMTP server in the 80 | * HELO / EHLO message. 81 | * 82 | * @var string 83 | * @see posix_uname() 84 | */ 85 | var $mailname = 'localhost'; 86 | 87 | /** 88 | * SMTP connection timeout value. NULL indicates no timeout. 89 | * 90 | * @var integer 91 | */ 92 | var $timeout = 10; 93 | 94 | /** 95 | * use either PEAR:Net_DNS or getmxrr 96 | * 97 | * @var boolean 98 | */ 99 | var $withNetDns = true; 100 | 101 | /** 102 | * PEAR:Net_DNS_Resolver 103 | * 104 | * @var object 105 | */ 106 | var $resolver; 107 | 108 | /** 109 | * Whether to use VERP or not. If not a boolean, the string value 110 | * will be used as the VERP separators. 111 | * 112 | * @var mixed boolean or string 113 | */ 114 | var $verp = false; 115 | 116 | /** 117 | * Whether to use VRFY or not. 118 | * 119 | * @var boolean $vrfy 120 | */ 121 | var $vrfy = false; 122 | 123 | /** 124 | * Switch to test mode - don't send emails for real 125 | * 126 | * @var boolean $debug 127 | */ 128 | var $test = false; 129 | 130 | /** 131 | * Turn on Net_SMTP debugging? 132 | * 133 | * @var boolean $peardebug 134 | */ 135 | var $debug = false; 136 | 137 | /** 138 | * internal error codes 139 | * 140 | * translate internal error identifier to PEAR-Error codes and human 141 | * readable messages. 142 | * 143 | * @var boolean $debug 144 | * @todo as I need unique error-codes to identify what exactly went wrond 145 | * I did not use intergers as it should be. Instead I added a "namespace" 146 | * for each code. This avoids conflicts with error codes from different 147 | * classes. How can I use unique error codes and stay conform with PEAR? 148 | */ 149 | var $errorCode = array( 150 | 'not_connected' => array( 151 | 'code' => 1, 152 | 'msg' => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.' 153 | ), 154 | 'failed_vrfy_rcpt' => array( 155 | 'code' => 2, 156 | 'msg' => 'Recipient "{RCPT}" could not be veryfied.' 157 | ), 158 | 'failed_set_from' => array( 159 | 'code' => 3, 160 | 'msg' => 'Failed to set sender: {FROM}.' 161 | ), 162 | 'failed_set_rcpt' => array( 163 | 'code' => 4, 164 | 'msg' => 'Failed to set recipient: {RCPT}.' 165 | ), 166 | 'failed_send_data' => array( 167 | 'code' => 5, 168 | 'msg' => 'Failed to send mail to: {RCPT}.' 169 | ), 170 | 'no_from' => array( 171 | 'code' => 5, 172 | 'msg' => 'No from address has be provided.' 173 | ), 174 | 'send_data' => array( 175 | 'code' => 7, 176 | 'msg' => 'Failed to create Net_SMTP object.' 177 | ), 178 | 'no_mx' => array( 179 | 'code' => 8, 180 | 'msg' => 'No MX-record for {RCPT} found.' 181 | ), 182 | 'no_resolver' => array( 183 | 'code' => 9, 184 | 'msg' => 'Could not start resolver! Install PEAR:Net_DNS or switch off "netdns"' 185 | ), 186 | 'failed_rset' => array( 187 | 'code' => 10, 188 | 'msg' => 'RSET command failed, SMTP-connection corrupt.' 189 | ), 190 | ); 191 | 192 | /** 193 | * Constructor. 194 | * 195 | * Instantiates a new Mail_smtp:: object based on the parameters 196 | * passed in. It looks for the following parameters: 197 | * mailname The name of the local mail system (a valid hostname which matches the reverse lookup) 198 | * port smtp-port - the default comes from getservicebyname() and should work fine 199 | * timeout The SMTP connection timeout. Defaults to 30 seconds. 200 | * vrfy Whether to use VRFY or not. Defaults to false. 201 | * verp Whether to use VERP or not. Defaults to false. 202 | * test Activate test mode? Defaults to false. 203 | * debug Activate SMTP and Net_DNS debug mode? Defaults to false. 204 | * netdns whether to use PEAR:Net_DNS or the PHP build in function getmxrr, default is true 205 | * 206 | * If a parameter is present in the $params array, it replaces the 207 | * default. 208 | * 209 | * @access public 210 | * @param array Hash containing any parameters different from the 211 | * defaults. 212 | * @see _Mail_smtpmx() 213 | */ 214 | function __construct($params) 215 | { 216 | if (isset($params['mailname'])) { 217 | $this->mailname = $params['mailname']; 218 | } else { 219 | // try to find a valid mailname 220 | if (function_exists('posix_uname')) { 221 | $uname = posix_uname(); 222 | $this->mailname = $uname['nodename']; 223 | } 224 | } 225 | 226 | // port number 227 | if (isset($params['port'])) { 228 | $this->_port = $params['port']; 229 | } else { 230 | $this->_port = getservbyname('smtp', 'tcp'); 231 | } 232 | 233 | if (isset($params['timeout'])) $this->timeout = $params['timeout']; 234 | if (isset($params['verp'])) $this->verp = $params['verp']; 235 | if (isset($params['test'])) $this->test = $params['test']; 236 | if (isset($params['peardebug'])) $this->test = $params['peardebug']; 237 | if (isset($params['netdns'])) $this->withNetDns = $params['netdns']; 238 | } 239 | 240 | /** 241 | * Constructor wrapper for PHP4 242 | * 243 | * @access public 244 | * @param array Hash containing any parameters different from the defaults 245 | * @see __construct() 246 | */ 247 | function Mail_smtpmx($params) 248 | { 249 | $this->__construct($params); 250 | register_shutdown_function(array(&$this, '__destruct')); 251 | } 252 | 253 | /** 254 | * Destructor implementation to ensure that we disconnect from any 255 | * potentially-alive persistent SMTP connections. 256 | */ 257 | function __destruct() 258 | { 259 | if (is_object($this->_smtp)) { 260 | $this->_smtp->disconnect(); 261 | $this->_smtp = null; 262 | } 263 | } 264 | 265 | /** 266 | * Implements Mail::send() function using SMTP direct delivery 267 | * 268 | * @access public 269 | * @param mixed $recipients in RFC822 style or array 270 | * @param array $headers The array of headers to send with the mail. 271 | * @param string $body The full text of the message body, 272 | * @return mixed Returns true on success, or a PEAR_Error 273 | */ 274 | function send($recipients, $headers, $body) 275 | { 276 | if (!is_array($headers)) { 277 | return PEAR::raiseError('$headers must be an array'); 278 | } 279 | 280 | $result = $this->_sanitizeHeaders($headers); 281 | if (is_a($result, 'PEAR_Error')) { 282 | return $result; 283 | } 284 | 285 | // Prepare headers 286 | $headerElements = $this->prepareHeaders($headers); 287 | if (is_a($headerElements, 'PEAR_Error')) { 288 | return $headerElements; 289 | } 290 | list($from, $textHeaders) = $headerElements; 291 | 292 | // use 'Return-Path' if possible 293 | if (!empty($headers['Return-Path'])) { 294 | $from = $headers['Return-Path']; 295 | } 296 | if (!isset($from)) { 297 | return $this->_raiseError('no_from'); 298 | } 299 | 300 | // Prepare recipients 301 | $recipients = $this->parseRecipients($recipients); 302 | if (is_a($recipients, 'PEAR_Error')) { 303 | return $recipients; 304 | } 305 | 306 | foreach ($recipients as $rcpt) { 307 | list($user, $host) = explode('@', $rcpt); 308 | 309 | $mx = $this->_getMx($host); 310 | if (is_a($mx, 'PEAR_Error')) { 311 | return $mx; 312 | } 313 | 314 | if (empty($mx)) { 315 | $info = array('rcpt' => $rcpt); 316 | return $this->_raiseError('no_mx', $info); 317 | } 318 | 319 | $connected = false; 320 | foreach ($mx as $mserver => $mpriority) { 321 | $this->_smtp = new Net_SMTP($mserver, $this->port, $this->mailname); 322 | 323 | // configure the SMTP connection. 324 | if ($this->debug) { 325 | $this->_smtp->setDebug(true); 326 | } 327 | 328 | // attempt to connect to the configured SMTP server. 329 | $res = $this->_smtp->connect($this->timeout); 330 | if (is_a($res, 'PEAR_Error')) { 331 | $this->_smtp = null; 332 | continue; 333 | } 334 | 335 | // connection established 336 | if ($res) { 337 | $connected = true; 338 | break; 339 | } 340 | } 341 | 342 | if (!$connected) { 343 | $info = array( 344 | 'host' => implode(', ', array_keys($mx)), 345 | 'port' => $this->port, 346 | 'rcpt' => $rcpt, 347 | ); 348 | return $this->_raiseError('not_connected', $info); 349 | } 350 | 351 | // Verify recipient 352 | if ($this->vrfy) { 353 | $res = $this->_smtp->vrfy($rcpt); 354 | if (is_a($res, 'PEAR_Error')) { 355 | $info = array('rcpt' => $rcpt); 356 | return $this->_raiseError('failed_vrfy_rcpt', $info); 357 | } 358 | } 359 | 360 | // mail from: 361 | $args['verp'] = $this->verp; 362 | $res = $this->_smtp->mailFrom($from, $args); 363 | if (is_a($res, 'PEAR_Error')) { 364 | $info = array('from' => $from); 365 | return $this->_raiseError('failed_set_from', $info); 366 | } 367 | 368 | // rcpt to: 369 | $res = $this->_smtp->rcptTo($rcpt); 370 | if (is_a($res, 'PEAR_Error')) { 371 | $info = array('rcpt' => $rcpt); 372 | return $this->_raiseError('failed_set_rcpt', $info); 373 | } 374 | 375 | // Don't send anything in test mode 376 | if ($this->test) { 377 | $result = $this->_smtp->rset(); 378 | $res = $this->_smtp->rset(); 379 | if (is_a($res, 'PEAR_Error')) { 380 | return $this->_raiseError('failed_rset'); 381 | } 382 | 383 | $this->_smtp->disconnect(); 384 | $this->_smtp = null; 385 | return true; 386 | } 387 | 388 | // Send data 389 | $res = $this->_smtp->data($body, $textHeaders); 390 | if (is_a($res, 'PEAR_Error')) { 391 | $info = array('rcpt' => $rcpt); 392 | return $this->_raiseError('failed_send_data', $info); 393 | } 394 | 395 | $this->_smtp->disconnect(); 396 | $this->_smtp = null; 397 | } 398 | 399 | return true; 400 | } 401 | 402 | /** 403 | * Recieve mx rexords for a spciefied host 404 | * 405 | * The MX records 406 | * 407 | * @access private 408 | * @param string $host mail host 409 | * @return mixed sorted 410 | */ 411 | function _getMx($host) 412 | { 413 | $mx = array(); 414 | 415 | if ($this->withNetDns) { 416 | $res = $this->_loadNetDns(); 417 | if (is_a($res, 'PEAR_Error')) { 418 | return $res; 419 | } 420 | 421 | $response = $this->resolver->query($host, 'MX'); 422 | if (!$response) { 423 | return false; 424 | } 425 | 426 | foreach ($response->answer as $rr) { 427 | if ($rr->type == 'MX') { 428 | $mx[$rr->exchange] = $rr->preference; 429 | } 430 | } 431 | } else { 432 | $mxHost = array(); 433 | $mxWeight = array(); 434 | 435 | if (!getmxrr($host, $mxHost, $mxWeight)) { 436 | return false; 437 | } 438 | for ($i = 0; $i < count($mxHost); ++$i) { 439 | $mx[$mxHost[$i]] = $mxWeight[$i]; 440 | } 441 | } 442 | 443 | asort($mx); 444 | return $mx; 445 | } 446 | 447 | /** 448 | * initialize PEAR:Net_DNS_Resolver 449 | * 450 | * @access private 451 | * @return boolean true on success 452 | */ 453 | function _loadNetDns() 454 | { 455 | if (is_object($this->resolver)) { 456 | return true; 457 | } 458 | 459 | if (!include_once 'Net/DNS.php') { 460 | return $this->_raiseError('no_resolver'); 461 | } 462 | 463 | $this->resolver = new Net_DNS_Resolver(); 464 | if ($this->debug) { 465 | $this->resolver->test = 1; 466 | } 467 | 468 | return true; 469 | } 470 | 471 | /** 472 | * raise standardized error 473 | * 474 | * include additional information in error message 475 | * 476 | * @access private 477 | * @param string $id maps error ids to codes and message 478 | * @param array $info optional information in associative array 479 | * @see _errorCode 480 | */ 481 | function _raiseError($id, $info = array()) 482 | { 483 | $code = $this->errorCode[$id]['code']; 484 | $msg = $this->errorCode[$id]['msg']; 485 | 486 | // include info to messages 487 | if (!empty($info)) { 488 | $search = array(); 489 | $replace = array(); 490 | 491 | foreach ($info as $key => $value) { 492 | array_push($search, '{' . strtoupper($key) . '}'); 493 | array_push($replace, $value); 494 | } 495 | 496 | $msg = str_replace($search, $replace, $msg); 497 | } 498 | 499 | return PEAR::raiseError($msg, $code); 500 | } 501 | 502 | } 503 | -------------------------------------------------------------------------------- /class/Mail/Net/Net/Socket.php: -------------------------------------------------------------------------------- 1 | 18 | * Chuck Hagenbuch 19 | * 20 | * @category Net 21 | * @package Net_Socket 22 | * @author Stig Bakken 23 | * @author Chuck Hagenbuch 24 | * @copyright 1997-2003 The PHP Group 25 | * @license http://www.php.net/license/2_02.txt PHP 2.02 26 | * @link http://pear.php.net/packages/Net_Socket 27 | */ 28 | 29 | require_once 'PEAR.php'; 30 | 31 | define('NET_SOCKET_READ', 1); 32 | define('NET_SOCKET_WRITE', 2); 33 | define('NET_SOCKET_ERROR', 4); 34 | 35 | /** 36 | * Generalized Socket class. 37 | * 38 | * @category Net 39 | * @package Net_Socket 40 | * @author Stig Bakken 41 | * @author Chuck Hagenbuch 42 | * @copyright 1997-2003 The PHP Group 43 | * @license http://www.php.net/license/2_02.txt PHP 2.02 44 | * @link http://pear.php.net/packages/Net_Socket 45 | */ 46 | class Net_Socket extends PEAR 47 | { 48 | /** 49 | * Socket file pointer. 50 | * @var resource $fp 51 | */ 52 | var $fp = null; 53 | 54 | /** 55 | * Whether the socket is blocking. Defaults to true. 56 | * @var boolean $blocking 57 | */ 58 | var $blocking = true; 59 | 60 | /** 61 | * Whether the socket is persistent. Defaults to false. 62 | * @var boolean $persistent 63 | */ 64 | var $persistent = false; 65 | 66 | /** 67 | * The IP address to connect to. 68 | * @var string $addr 69 | */ 70 | var $addr = ''; 71 | 72 | /** 73 | * The port number to connect to. 74 | * @var integer $port 75 | */ 76 | var $port = 0; 77 | 78 | /** 79 | * Number of seconds to wait on socket operations before assuming 80 | * there's no more data. Defaults to no timeout. 81 | * @var integer|float $timeout 82 | */ 83 | var $timeout = null; 84 | 85 | /** 86 | * Number of bytes to read at a time in readLine() and 87 | * readAll(). Defaults to 2048. 88 | * @var integer $lineLength 89 | */ 90 | var $lineLength = 2048; 91 | 92 | /** 93 | * The string to use as a newline terminator. Usually "\r\n" or "\n". 94 | * @var string $newline 95 | */ 96 | var $newline = "\r\n"; 97 | 98 | /** 99 | * Connect to the specified port. If called when the socket is 100 | * already connected, it disconnects and connects again. 101 | * 102 | * @param string $addr IP address or host name (may be with protocol prefix). 103 | * @param integer $port TCP port number. 104 | * @param boolean $persistent (optional) Whether the connection is 105 | * persistent (kept open between requests 106 | * by the web server). 107 | * @param integer $timeout (optional) Connection socket timeout. 108 | * @param array $options See options for stream_context_create. 109 | * 110 | * @access public 111 | * 112 | * @return boolean|PEAR_Error True on success or a PEAR_Error on failure. 113 | */ 114 | function connect($addr, $port = 0, $persistent = null, 115 | $timeout = null, $options = null) 116 | { 117 | if (is_resource($this->fp)) { 118 | @fclose($this->fp); 119 | $this->fp = null; 120 | } 121 | 122 | if (!$addr) { 123 | return $this->raiseError('$addr cannot be empty'); 124 | } else if (strspn($addr, ':.0123456789') == strlen($addr)) { 125 | $this->addr = strpos($addr, ':') !== false ? '['.$addr.']' : $addr; 126 | } else { 127 | $this->addr = $addr; 128 | } 129 | 130 | $this->port = $port % 65536; 131 | 132 | if ($persistent !== null) { 133 | $this->persistent = $persistent; 134 | } 135 | 136 | $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen'; 137 | $errno = 0; 138 | $errstr = ''; 139 | 140 | $old_track_errors = @ini_set('track_errors', 1); 141 | 142 | if ($timeout <= 0) { 143 | $timeout = @ini_get('default_socket_timeout'); 144 | } 145 | 146 | if ($options && function_exists('stream_context_create')) { 147 | $context = stream_context_create($options); 148 | 149 | // Since PHP 5 fsockopen doesn't allow context specification 150 | if (function_exists('stream_socket_client')) { 151 | $flags = STREAM_CLIENT_CONNECT; 152 | 153 | if ($this->persistent) { 154 | $flags = STREAM_CLIENT_PERSISTENT; 155 | } 156 | 157 | $addr = $this->addr . ':' . $this->port; 158 | $fp = stream_socket_client($addr, $errno, $errstr, 159 | $timeout, $flags, $context); 160 | } else { 161 | $fp = @$openfunc($this->addr, $this->port, $errno, 162 | $errstr, $timeout, $context); 163 | } 164 | } else { 165 | $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout); 166 | } 167 | 168 | if (!$fp) { 169 | if ($errno == 0 && !strlen($errstr) && isset($php_errormsg)) { 170 | $errstr = $php_errormsg; 171 | } 172 | @ini_set('track_errors', $old_track_errors); 173 | return $this->raiseError($errstr, $errno); 174 | } 175 | 176 | @ini_set('track_errors', $old_track_errors); 177 | $this->fp = $fp; 178 | $this->setTimeout(); 179 | return $this->setBlocking($this->blocking); 180 | } 181 | 182 | /** 183 | * Disconnects from the peer, closes the socket. 184 | * 185 | * @access public 186 | * @return mixed true on success or a PEAR_Error instance otherwise 187 | */ 188 | function disconnect() 189 | { 190 | if (!is_resource($this->fp)) { 191 | return $this->raiseError('not connected'); 192 | } 193 | 194 | @fclose($this->fp); 195 | $this->fp = null; 196 | return true; 197 | } 198 | 199 | /** 200 | * Set the newline character/sequence to use. 201 | * 202 | * @param string $newline Newline character(s) 203 | * @return boolean True 204 | */ 205 | function setNewline($newline) 206 | { 207 | $this->newline = $newline; 208 | return true; 209 | } 210 | 211 | /** 212 | * Find out if the socket is in blocking mode. 213 | * 214 | * @access public 215 | * @return boolean The current blocking mode. 216 | */ 217 | function isBlocking() 218 | { 219 | return $this->blocking; 220 | } 221 | 222 | /** 223 | * Sets whether the socket connection should be blocking or 224 | * not. A read call to a non-blocking socket will return immediately 225 | * if there is no data available, whereas it will block until there 226 | * is data for blocking sockets. 227 | * 228 | * @param boolean $mode True for blocking sockets, false for nonblocking. 229 | * 230 | * @access public 231 | * @return mixed true on success or a PEAR_Error instance otherwise 232 | */ 233 | function setBlocking($mode) 234 | { 235 | if (!is_resource($this->fp)) { 236 | return $this->raiseError('not connected'); 237 | } 238 | 239 | $this->blocking = $mode; 240 | stream_set_blocking($this->fp, (int)$this->blocking); 241 | return true; 242 | } 243 | 244 | /** 245 | * Sets the timeout value on socket descriptor, 246 | * expressed in the sum of seconds and microseconds 247 | * 248 | * @param integer $seconds Seconds. 249 | * @param integer $microseconds Microseconds, optional. 250 | * 251 | * @access public 252 | * @return mixed True on success or false on failure or 253 | * a PEAR_Error instance when not connected 254 | */ 255 | function setTimeout($seconds = null, $microseconds = null) 256 | { 257 | if (!is_resource($this->fp)) { 258 | return $this->raiseError('not connected'); 259 | } 260 | 261 | if ($seconds === null && $microseconds === null) { 262 | $seconds = (int) $this->timeout; 263 | $microseconds = (int) (($this->timeout - $seconds) * 1000000); 264 | } else { 265 | $this->timeout = $seconds + $microseconds/1000000; 266 | } 267 | 268 | if ($this->timeout > 0) { 269 | return stream_set_timeout($this->fp, (int) $seconds, (int) $microseconds); 270 | } 271 | else { 272 | return false; 273 | } 274 | } 275 | 276 | /** 277 | * Sets the file buffering size on the stream. 278 | * See php's stream_set_write_buffer for more information. 279 | * 280 | * @param integer $size Write buffer size. 281 | * 282 | * @access public 283 | * @return mixed on success or an PEAR_Error object otherwise 284 | */ 285 | function setWriteBuffer($size) 286 | { 287 | if (!is_resource($this->fp)) { 288 | return $this->raiseError('not connected'); 289 | } 290 | 291 | $returned = stream_set_write_buffer($this->fp, $size); 292 | if ($returned == 0) { 293 | return true; 294 | } 295 | return $this->raiseError('Cannot set write buffer.'); 296 | } 297 | 298 | /** 299 | * Returns information about an existing socket resource. 300 | * Currently returns four entries in the result array: 301 | * 302 | *

303 | * timed_out (bool) - The socket timed out waiting for data
304 | * blocked (bool) - The socket was blocked
305 | * eof (bool) - Indicates EOF event
306 | * unread_bytes (int) - Number of bytes left in the socket buffer
307 | *

308 | * 309 | * @access public 310 | * @return mixed Array containing information about existing socket 311 | * resource or a PEAR_Error instance otherwise 312 | */ 313 | function getStatus() 314 | { 315 | if (!is_resource($this->fp)) { 316 | return $this->raiseError('not connected'); 317 | } 318 | 319 | return stream_get_meta_data($this->fp); 320 | } 321 | 322 | /** 323 | * Get a specified line of data 324 | * 325 | * @param int $size Reading ends when size - 1 bytes have been read, 326 | * or a newline or an EOF (whichever comes first). 327 | * If no size is specified, it will keep reading from 328 | * the stream until it reaches the end of the line. 329 | * 330 | * @access public 331 | * @return mixed $size bytes of data from the socket, or a PEAR_Error if 332 | * not connected. If an error occurs, FALSE is returned. 333 | */ 334 | function gets($size = null) 335 | { 336 | if (!is_resource($this->fp)) { 337 | return $this->raiseError('not connected'); 338 | } 339 | 340 | if (is_null($size)) { 341 | return @fgets($this->fp); 342 | } else { 343 | return @fgets($this->fp, $size); 344 | } 345 | } 346 | 347 | /** 348 | * Read a specified amount of data. This is guaranteed to return, 349 | * and has the added benefit of getting everything in one fread() 350 | * chunk; if you know the size of the data you're getting 351 | * beforehand, this is definitely the way to go. 352 | * 353 | * @param integer $size The number of bytes to read from the socket. 354 | * 355 | * @access public 356 | * @return $size bytes of data from the socket, or a PEAR_Error if 357 | * not connected. 358 | */ 359 | function read($size) 360 | { 361 | if (!is_resource($this->fp)) { 362 | return $this->raiseError('not connected'); 363 | } 364 | 365 | return @fread($this->fp, $size); 366 | } 367 | 368 | /** 369 | * Write a specified amount of data. 370 | * 371 | * @param string $data Data to write. 372 | * @param integer $blocksize Amount of data to write at once. 373 | * NULL means all at once. 374 | * 375 | * @access public 376 | * @return mixed If the socket is not connected, returns an instance of 377 | * PEAR_Error. 378 | * If the write succeeds, returns the number of bytes written. 379 | * If the write fails, returns false. 380 | * If the socket times out, returns an instance of PEAR_Error. 381 | */ 382 | function write($data, $blocksize = null) 383 | { 384 | if (!is_resource($this->fp)) { 385 | return $this->raiseError('not connected'); 386 | } 387 | 388 | if (is_null($blocksize) && !OS_WINDOWS) { 389 | $written = @fwrite($this->fp, $data); 390 | 391 | // Check for timeout or lost connection 392 | if (!$written) { 393 | $meta_data = $this->getStatus(); 394 | 395 | if (!is_array($meta_data)) { 396 | return $meta_data; // PEAR_Error 397 | } 398 | 399 | if (!empty($meta_data['timed_out'])) { 400 | return $this->raiseError('timed out'); 401 | } 402 | } 403 | 404 | return $written; 405 | } else { 406 | if (is_null($blocksize)) { 407 | $blocksize = 1024; 408 | } 409 | 410 | $pos = 0; 411 | $size = strlen($data); 412 | while ($pos < $size) { 413 | $written = @fwrite($this->fp, substr($data, $pos, $blocksize)); 414 | 415 | // Check for timeout or lost connection 416 | if (!$written) { 417 | $meta_data = $this->getStatus(); 418 | 419 | if (!is_array($meta_data)) { 420 | return $meta_data; // PEAR_Error 421 | } 422 | 423 | if (!empty($meta_data['timed_out'])) { 424 | return $this->raiseError('timed out'); 425 | } 426 | 427 | return $written; 428 | } 429 | 430 | $pos += $written; 431 | } 432 | 433 | return $pos; 434 | } 435 | } 436 | 437 | /** 438 | * Write a line of data to the socket, followed by a trailing newline. 439 | * 440 | * @param string $data Data to write 441 | * 442 | * @access public 443 | * @return mixed fwrite() result, or PEAR_Error when not connected 444 | */ 445 | function writeLine($data) 446 | { 447 | if (!is_resource($this->fp)) { 448 | return $this->raiseError('not connected'); 449 | } 450 | 451 | return fwrite($this->fp, $data . $this->newline); 452 | } 453 | 454 | /** 455 | * Tests for end-of-file on a socket descriptor. 456 | * 457 | * Also returns true if the socket is disconnected. 458 | * 459 | * @access public 460 | * @return bool 461 | */ 462 | function eof() 463 | { 464 | return (!is_resource($this->fp) || feof($this->fp)); 465 | } 466 | 467 | /** 468 | * Reads a byte of data 469 | * 470 | * @access public 471 | * @return 1 byte of data from the socket, or a PEAR_Error if 472 | * not connected. 473 | */ 474 | function readByte() 475 | { 476 | if (!is_resource($this->fp)) { 477 | return $this->raiseError('not connected'); 478 | } 479 | 480 | return ord(@fread($this->fp, 1)); 481 | } 482 | 483 | /** 484 | * Reads a word of data 485 | * 486 | * @access public 487 | * @return 1 word of data from the socket, or a PEAR_Error if 488 | * not connected. 489 | */ 490 | function readWord() 491 | { 492 | if (!is_resource($this->fp)) { 493 | return $this->raiseError('not connected'); 494 | } 495 | 496 | $buf = @fread($this->fp, 2); 497 | return (ord($buf[0]) + (ord($buf[1]) << 8)); 498 | } 499 | 500 | /** 501 | * Reads an int of data 502 | * 503 | * @access public 504 | * @return integer 1 int of data from the socket, or a PEAR_Error if 505 | * not connected. 506 | */ 507 | function readInt() 508 | { 509 | if (!is_resource($this->fp)) { 510 | return $this->raiseError('not connected'); 511 | } 512 | 513 | $buf = @fread($this->fp, 4); 514 | return (ord($buf[0]) + (ord($buf[1]) << 8) + 515 | (ord($buf[2]) << 16) + (ord($buf[3]) << 24)); 516 | } 517 | 518 | /** 519 | * Reads a zero-terminated string of data 520 | * 521 | * @access public 522 | * @return string, or a PEAR_Error if 523 | * not connected. 524 | */ 525 | function readString() 526 | { 527 | if (!is_resource($this->fp)) { 528 | return $this->raiseError('not connected'); 529 | } 530 | 531 | $string = ''; 532 | while (($char = @fread($this->fp, 1)) != "\x00") { 533 | $string .= $char; 534 | } 535 | return $string; 536 | } 537 | 538 | /** 539 | * Reads an IP Address and returns it in a dot formatted string 540 | * 541 | * @access public 542 | * @return Dot formatted string, or a PEAR_Error if 543 | * not connected. 544 | */ 545 | function readIPAddress() 546 | { 547 | if (!is_resource($this->fp)) { 548 | return $this->raiseError('not connected'); 549 | } 550 | 551 | $buf = @fread($this->fp, 4); 552 | return sprintf('%d.%d.%d.%d', ord($buf[0]), ord($buf[1]), 553 | ord($buf[2]), ord($buf[3])); 554 | } 555 | 556 | /** 557 | * Read until either the end of the socket or a newline, whichever 558 | * comes first. Strips the trailing newline from the returned data. 559 | * 560 | * @access public 561 | * @return All available data up to a newline, without that 562 | * newline, or until the end of the socket, or a PEAR_Error if 563 | * not connected. 564 | */ 565 | function readLine() 566 | { 567 | if (!is_resource($this->fp)) { 568 | return $this->raiseError('not connected'); 569 | } 570 | 571 | $line = ''; 572 | 573 | $timeout = time() + $this->timeout; 574 | 575 | while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) { 576 | $line .= @fgets($this->fp, $this->lineLength); 577 | if (substr($line, -1) == "\n") { 578 | return rtrim($line, $this->newline); 579 | } 580 | } 581 | return $line; 582 | } 583 | 584 | /** 585 | * Read until the socket closes, or until there is no more data in 586 | * the inner PHP buffer. If the inner buffer is empty, in blocking 587 | * mode we wait for at least 1 byte of data. Therefore, in 588 | * blocking mode, if there is no data at all to be read, this 589 | * function will never exit (unless the socket is closed on the 590 | * remote end). 591 | * 592 | * @access public 593 | * 594 | * @return string All data until the socket closes, or a PEAR_Error if 595 | * not connected. 596 | */ 597 | function readAll() 598 | { 599 | if (!is_resource($this->fp)) { 600 | return $this->raiseError('not connected'); 601 | } 602 | 603 | $data = ''; 604 | while (!feof($this->fp)) { 605 | $data .= @fread($this->fp, $this->lineLength); 606 | } 607 | return $data; 608 | } 609 | 610 | /** 611 | * Runs the equivalent of the select() system call on the socket 612 | * with a timeout specified by tv_sec and tv_usec. 613 | * 614 | * @param integer $state Which of read/write/error to check for. 615 | * @param integer $tv_sec Number of seconds for timeout. 616 | * @param integer $tv_usec Number of microseconds for timeout. 617 | * 618 | * @access public 619 | * @return False if select fails, integer describing which of read/write/error 620 | * are ready, or PEAR_Error if not connected. 621 | */ 622 | function select($state, $tv_sec, $tv_usec = 0) 623 | { 624 | if (!is_resource($this->fp)) { 625 | return $this->raiseError('not connected'); 626 | } 627 | 628 | $read = null; 629 | $write = null; 630 | $except = null; 631 | if ($state & NET_SOCKET_READ) { 632 | $read[] = $this->fp; 633 | } 634 | if ($state & NET_SOCKET_WRITE) { 635 | $write[] = $this->fp; 636 | } 637 | if ($state & NET_SOCKET_ERROR) { 638 | $except[] = $this->fp; 639 | } 640 | if (false === ($sr = stream_select($read, $write, $except, 641 | $tv_sec, $tv_usec))) { 642 | return false; 643 | } 644 | 645 | $result = 0; 646 | if (count($read)) { 647 | $result |= NET_SOCKET_READ; 648 | } 649 | if (count($write)) { 650 | $result |= NET_SOCKET_WRITE; 651 | } 652 | if (count($except)) { 653 | $result |= NET_SOCKET_ERROR; 654 | } 655 | return $result; 656 | } 657 | 658 | /** 659 | * Turns encryption on/off on a connected socket. 660 | * 661 | * @param bool $enabled Set this parameter to true to enable encryption 662 | * and false to disable encryption. 663 | * @param integer $type Type of encryption. See stream_socket_enable_crypto() 664 | * for values. 665 | * 666 | * @see http://se.php.net/manual/en/function.stream-socket-enable-crypto.php 667 | * @access public 668 | * @return false on error, true on success and 0 if there isn't enough data 669 | * and the user should try again (non-blocking sockets only). 670 | * A PEAR_Error object is returned if the socket is not 671 | * connected 672 | */ 673 | function enableCrypto($enabled, $type) 674 | { 675 | if (version_compare(phpversion(), "5.1.0", ">=")) { 676 | if (!is_resource($this->fp)) { 677 | return $this->raiseError('not connected'); 678 | } 679 | return @stream_socket_enable_crypto($this->fp, $enabled, $type); 680 | } else { 681 | $msg = 'Net_Socket::enableCrypto() requires php version >= 5.1.0'; 682 | return $this->raiseError($msg); 683 | } 684 | } 685 | 686 | } 687 | -------------------------------------------------------------------------------- /class/Mail/RFC822.php: -------------------------------------------------------------------------------- 1 | 40 | * @author Chuck Hagenbuch 65 | * @author Chuck Hagenbuch 66 | * @version $Revision$ 67 | * @license BSD 68 | * @package Mail 69 | */ 70 | class Mail_RFC822 { 71 | 72 | /** 73 | * The address being parsed by the RFC822 object. 74 | * @var string $address 75 | */ 76 | var $address = ''; 77 | 78 | /** 79 | * The default domain to use for unqualified addresses. 80 | * @var string $default_domain 81 | */ 82 | var $default_domain = 'localhost'; 83 | 84 | /** 85 | * Should we return a nested array showing groups, or flatten everything? 86 | * @var boolean $nestGroups 87 | */ 88 | var $nestGroups = true; 89 | 90 | /** 91 | * Whether or not to validate atoms for non-ascii characters. 92 | * @var boolean $validate 93 | */ 94 | var $validate = true; 95 | 96 | /** 97 | * The array of raw addresses built up as we parse. 98 | * @var array $addresses 99 | */ 100 | var $addresses = array(); 101 | 102 | /** 103 | * The final array of parsed address information that we build up. 104 | * @var array $structure 105 | */ 106 | var $structure = array(); 107 | 108 | /** 109 | * The current error message, if any. 110 | * @var string $error 111 | */ 112 | var $error = null; 113 | 114 | /** 115 | * An internal counter/pointer. 116 | * @var integer $index 117 | */ 118 | var $index = null; 119 | 120 | /** 121 | * The number of groups that have been found in the address list. 122 | * @var integer $num_groups 123 | * @access public 124 | */ 125 | var $num_groups = 0; 126 | 127 | /** 128 | * A variable so that we can tell whether or not we're inside a 129 | * Mail_RFC822 object. 130 | * @var boolean $mailRFC822 131 | */ 132 | var $mailRFC822 = true; 133 | 134 | /** 135 | * A limit after which processing stops 136 | * @var int $limit 137 | */ 138 | var $limit = null; 139 | 140 | /** 141 | * Sets up the object. The address must either be set here or when 142 | * calling parseAddressList(). One or the other. 143 | * 144 | * @param string $address The address(es) to validate. 145 | * @param string $default_domain Default domain/host etc. If not supplied, will be set to localhost. 146 | * @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing. 147 | * @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance. 148 | * 149 | * @return object Mail_RFC822 A new Mail_RFC822 object. 150 | */ 151 | public function __construct($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null) 152 | { 153 | if (isset($address)) $this->address = $address; 154 | if (isset($default_domain)) $this->default_domain = $default_domain; 155 | if (isset($nest_groups)) $this->nestGroups = $nest_groups; 156 | if (isset($validate)) $this->validate = $validate; 157 | if (isset($limit)) $this->limit = $limit; 158 | } 159 | 160 | /** 161 | * Starts the whole process. The address must either be set here 162 | * or when creating the object. One or the other. 163 | * 164 | * @param string $address The address(es) to validate. 165 | * @param string $default_domain Default domain/host etc. 166 | * @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing. 167 | * @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance. 168 | * 169 | * @return array A structured array of addresses. 170 | */ 171 | public function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null) 172 | { 173 | if (!isset($this) || !isset($this->mailRFC822)) { 174 | $obj = new Mail_RFC822($address, $default_domain, $nest_groups, $validate, $limit); 175 | return $obj->parseAddressList(); 176 | } 177 | 178 | if (isset($address)) $this->address = $address; 179 | if (isset($default_domain)) $this->default_domain = $default_domain; 180 | if (isset($nest_groups)) $this->nestGroups = $nest_groups; 181 | if (isset($validate)) $this->validate = $validate; 182 | if (isset($limit)) $this->limit = $limit; 183 | 184 | $this->structure = array(); 185 | $this->addresses = array(); 186 | $this->error = null; 187 | $this->index = null; 188 | 189 | // Unfold any long lines in $this->address. 190 | $this->address = preg_replace('/\r?\n/', "\r\n", $this->address); 191 | $this->address = preg_replace('/\r\n(\t| )+/', ' ', $this->address); 192 | 193 | while ($this->address = $this->_splitAddresses($this->address)); 194 | 195 | if ($this->address === false || isset($this->error)) { 196 | require_once 'PEAR.php'; 197 | return PEAR::raiseError($this->error); 198 | } 199 | 200 | // Validate each address individually. If we encounter an invalid 201 | // address, stop iterating and return an error immediately. 202 | foreach ($this->addresses as $address) { 203 | $valid = $this->_validateAddress($address); 204 | 205 | if ($valid === false || isset($this->error)) { 206 | require_once 'PEAR.php'; 207 | return PEAR::raiseError($this->error); 208 | } 209 | 210 | if (!$this->nestGroups) { 211 | $this->structure = array_merge($this->structure, $valid); 212 | } else { 213 | $this->structure[] = $valid; 214 | } 215 | } 216 | 217 | return $this->structure; 218 | } 219 | 220 | /** 221 | * Splits an address into separate addresses. 222 | * 223 | * @param string $address The addresses to split. 224 | * @return boolean Success or failure. 225 | */ 226 | protected function _splitAddresses($address) 227 | { 228 | if (!empty($this->limit) && count($this->addresses) == $this->limit) { 229 | return ''; 230 | } 231 | 232 | if ($this->_isGroup($address) && !isset($this->error)) { 233 | $split_char = ';'; 234 | $is_group = true; 235 | } elseif (!isset($this->error)) { 236 | $split_char = ','; 237 | $is_group = false; 238 | } elseif (isset($this->error)) { 239 | return false; 240 | } 241 | 242 | // Split the string based on the above ten or so lines. 243 | $parts = explode($split_char, $address); 244 | $string = $this->_splitCheck($parts, $split_char); 245 | 246 | // If a group... 247 | if ($is_group) { 248 | // If $string does not contain a colon outside of 249 | // brackets/quotes etc then something's fubar. 250 | 251 | // First check there's a colon at all: 252 | if (strpos($string, ':') === false) { 253 | $this->error = 'Invalid address: ' . $string; 254 | return false; 255 | } 256 | 257 | // Now check it's outside of brackets/quotes: 258 | if (!$this->_splitCheck(explode(':', $string), ':')) { 259 | return false; 260 | } 261 | 262 | // We must have a group at this point, so increase the counter: 263 | $this->num_groups++; 264 | } 265 | 266 | // $string now contains the first full address/group. 267 | // Add to the addresses array. 268 | $this->addresses[] = array( 269 | 'address' => trim($string), 270 | 'group' => $is_group 271 | ); 272 | 273 | // Remove the now stored address from the initial line, the +1 274 | // is to account for the explode character. 275 | $address = trim(substr($address, strlen($string) + 1)); 276 | 277 | // If the next char is a comma and this was a group, then 278 | // there are more addresses, otherwise, if there are any more 279 | // chars, then there is another address. 280 | if ($is_group && substr($address, 0, 1) == ','){ 281 | $address = trim(substr($address, 1)); 282 | return $address; 283 | 284 | } elseif (strlen($address) > 0) { 285 | return $address; 286 | 287 | } else { 288 | return ''; 289 | } 290 | 291 | // If you got here then something's off 292 | return false; 293 | } 294 | 295 | /** 296 | * Checks for a group at the start of the string. 297 | * 298 | * @param string $address The address to check. 299 | * @return boolean Whether or not there is a group at the start of the string. 300 | */ 301 | protected function _isGroup($address) 302 | { 303 | // First comma not in quotes, angles or escaped: 304 | $parts = explode(',', $address); 305 | $string = $this->_splitCheck($parts, ','); 306 | 307 | // Now we have the first address, we can reliably check for a 308 | // group by searching for a colon that's not escaped or in 309 | // quotes or angle brackets. 310 | if (count($parts = explode(':', $string)) > 1) { 311 | $string2 = $this->_splitCheck($parts, ':'); 312 | return ($string2 !== $string); 313 | } else { 314 | return false; 315 | } 316 | } 317 | 318 | /** 319 | * A common function that will check an exploded string. 320 | * 321 | * @param array $parts The exloded string. 322 | * @param string $char The char that was exploded on. 323 | * @return mixed False if the string contains unclosed quotes/brackets, or the string on success. 324 | */ 325 | protected function _splitCheck($parts, $char) 326 | { 327 | $string = $parts[0]; 328 | 329 | for ($i = 0; $i < count($parts); $i++) { 330 | if ($this->_hasUnclosedQuotes($string) 331 | || $this->_hasUnclosedBrackets($string, '<>') 332 | || $this->_hasUnclosedBrackets($string, '[]') 333 | || $this->_hasUnclosedBrackets($string, '()') 334 | || substr($string, -1) == '\\') { 335 | if (isset($parts[$i + 1])) { 336 | $string = $string . $char . $parts[$i + 1]; 337 | } else { 338 | $this->error = 'Invalid address spec. Unclosed bracket or quotes'; 339 | return false; 340 | } 341 | } else { 342 | $this->index = $i; 343 | break; 344 | } 345 | } 346 | 347 | return $string; 348 | } 349 | 350 | /** 351 | * Checks if a string has unclosed quotes or not. 352 | * 353 | * @param string $string The string to check. 354 | * @return boolean True if there are unclosed quotes inside the string, 355 | * false otherwise. 356 | */ 357 | protected function _hasUnclosedQuotes($string) 358 | { 359 | $string = trim($string); 360 | $iMax = strlen($string); 361 | $in_quote = false; 362 | $i = $slashes = 0; 363 | 364 | for (; $i < $iMax; ++$i) { 365 | switch ($string[$i]) { 366 | case '\\': 367 | ++$slashes; 368 | break; 369 | 370 | case '"': 371 | if ($slashes % 2 == 0) { 372 | $in_quote = !$in_quote; 373 | } 374 | // Fall through to default action below. 375 | 376 | default: 377 | $slashes = 0; 378 | break; 379 | } 380 | } 381 | 382 | return $in_quote; 383 | } 384 | 385 | /** 386 | * Checks if a string has an unclosed brackets or not. IMPORTANT: 387 | * This function handles both angle brackets and square brackets; 388 | * 389 | * @param string $string The string to check. 390 | * @param string $chars The characters to check for. 391 | * @return boolean True if there are unclosed brackets inside the string, false otherwise. 392 | */ 393 | protected function _hasUnclosedBrackets($string, $chars) 394 | { 395 | $num_angle_start = substr_count($string, $chars[0]); 396 | $num_angle_end = substr_count($string, $chars[1]); 397 | 398 | $this->_hasUnclosedBracketsSub($string, $num_angle_start, $chars[0]); 399 | $this->_hasUnclosedBracketsSub($string, $num_angle_end, $chars[1]); 400 | 401 | if ($num_angle_start < $num_angle_end) { 402 | $this->error = 'Invalid address spec. Unmatched quote or bracket (' . $chars . ')'; 403 | return false; 404 | } else { 405 | return ($num_angle_start > $num_angle_end); 406 | } 407 | } 408 | 409 | /** 410 | * Sub function that is used only by hasUnclosedBrackets(). 411 | * 412 | * @param string $string The string to check. 413 | * @param integer &$num The number of occurences. 414 | * @param string $char The character to count. 415 | * @return integer The number of occurences of $char in $string, adjusted for backslashes. 416 | */ 417 | protected function _hasUnclosedBracketsSub($string, &$num, $char) 418 | { 419 | $parts = explode($char, $string); 420 | for ($i = 0; $i < count($parts); $i++){ 421 | if (substr($parts[$i], -1) == '\\' || $this->_hasUnclosedQuotes($parts[$i])) 422 | $num--; 423 | if (isset($parts[$i + 1])) 424 | $parts[$i + 1] = $parts[$i] . $char . $parts[$i + 1]; 425 | } 426 | 427 | return $num; 428 | } 429 | 430 | /** 431 | * Function to begin checking the address. 432 | * 433 | * @param string $address The address to validate. 434 | * @return mixed False on failure, or a structured array of address information on success. 435 | */ 436 | protected function _validateAddress($address) 437 | { 438 | $is_group = false; 439 | $addresses = array(); 440 | 441 | if ($address['group']) { 442 | $is_group = true; 443 | 444 | // Get the group part of the name 445 | $parts = explode(':', $address['address']); 446 | $groupname = $this->_splitCheck($parts, ':'); 447 | $structure = array(); 448 | 449 | // And validate the group part of the name. 450 | if (!$this->_validatePhrase($groupname)){ 451 | $this->error = 'Group name did not validate.'; 452 | return false; 453 | } else { 454 | // Don't include groups if we are not nesting 455 | // them. This avoids returning invalid addresses. 456 | if ($this->nestGroups) { 457 | $structure = new stdClass; 458 | $structure->groupname = $groupname; 459 | } 460 | } 461 | 462 | $address['address'] = ltrim(substr($address['address'], strlen($groupname . ':'))); 463 | } 464 | 465 | // If a group then split on comma and put into an array. 466 | // Otherwise, Just put the whole address in an array. 467 | if ($is_group) { 468 | while (strlen($address['address']) > 0) { 469 | $parts = explode(',', $address['address']); 470 | $addresses[] = $this->_splitCheck($parts, ','); 471 | $address['address'] = trim(substr($address['address'], strlen(end($addresses) . ','))); 472 | } 473 | } else { 474 | $addresses[] = $address['address']; 475 | } 476 | 477 | // Trim the whitespace from all of the address strings. 478 | array_map('trim', $addresses); 479 | 480 | // Validate each mailbox. 481 | // Format could be one of: name 482 | // geezer@domain.com 483 | // geezer 484 | // ... or any other format valid by RFC 822. 485 | for ($i = 0; $i < count($addresses); $i++) { 486 | if (!$this->validateMailbox($addresses[$i])) { 487 | if (empty($this->error)) { 488 | $this->error = 'Validation failed for: ' . $addresses[$i]; 489 | } 490 | return false; 491 | } 492 | } 493 | 494 | // Nested format 495 | if ($this->nestGroups) { 496 | if ($is_group) { 497 | $structure->addresses = $addresses; 498 | } else { 499 | $structure = $addresses[0]; 500 | } 501 | 502 | // Flat format 503 | } else { 504 | if ($is_group) { 505 | $structure = array_merge($structure, $addresses); 506 | } else { 507 | $structure = $addresses; 508 | } 509 | } 510 | 511 | return $structure; 512 | } 513 | 514 | /** 515 | * Function to validate a phrase. 516 | * 517 | * @param string $phrase The phrase to check. 518 | * @return boolean Success or failure. 519 | */ 520 | protected function _validatePhrase($phrase) 521 | { 522 | // Splits on one or more Tab or space. 523 | $parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY); 524 | 525 | $phrase_parts = array(); 526 | while (count($parts) > 0){ 527 | $phrase_parts[] = $this->_splitCheck($parts, ' '); 528 | for ($i = 0; $i < $this->index + 1; $i++) 529 | array_shift($parts); 530 | } 531 | 532 | foreach ($phrase_parts as $part) { 533 | // If quoted string: 534 | if (substr($part, 0, 1) == '"') { 535 | if (!$this->_validateQuotedString($part)) { 536 | return false; 537 | } 538 | continue; 539 | } 540 | 541 | // Otherwise it's an atom: 542 | if (!$this->_validateAtom($part)) return false; 543 | } 544 | 545 | return true; 546 | } 547 | 548 | /** 549 | * Function to validate an atom which from rfc822 is: 550 | * atom = 1* 551 | * 552 | * If validation ($this->validate) has been turned off, then 553 | * validateAtom() doesn't actually check anything. This is so that you 554 | * can split a list of addresses up before encoding personal names 555 | * (umlauts, etc.), for example. 556 | * 557 | * @param string $atom The string to check. 558 | * @return boolean Success or failure. 559 | */ 560 | protected function _validateAtom($atom) 561 | { 562 | if (!$this->validate) { 563 | // Validation has been turned off; assume the atom is okay. 564 | return true; 565 | } 566 | 567 | // Check for any char from ASCII 0 - ASCII 127 568 | if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) { 569 | return false; 570 | } 571 | 572 | // Check for specials: 573 | if (preg_match('/[][()<>@,;\\:". ]/', $atom)) { 574 | return false; 575 | } 576 | 577 | // Check for control characters (ASCII 0-31): 578 | if (preg_match('/[\\x00-\\x1F]+/', $atom)) { 579 | return false; 580 | } 581 | 582 | return true; 583 | } 584 | 585 | /** 586 | * Function to validate quoted string, which is: 587 | * quoted-string = <"> *(qtext/quoted-pair) <"> 588 | * 589 | * @param string $qstring The string to check 590 | * @return boolean Success or failure. 591 | */ 592 | protected function _validateQuotedString($qstring) 593 | { 594 | // Leading and trailing " 595 | $qstring = substr($qstring, 1, -1); 596 | 597 | // Perform check, removing quoted characters first. 598 | return !preg_match('/[\x0D\\\\"]/', preg_replace('/\\\\./', '', $qstring)); 599 | } 600 | 601 | /** 602 | * Function to validate a mailbox, which is: 603 | * mailbox = addr-spec ; simple address 604 | * / phrase route-addr ; name and route-addr 605 | * 606 | * @param string &$mailbox The string to check. 607 | * @return boolean Success or failure. 608 | */ 609 | public function validateMailbox(&$mailbox) 610 | { 611 | // A couple of defaults. 612 | $phrase = ''; 613 | $comment = ''; 614 | $comments = array(); 615 | 616 | // Catch any RFC822 comments and store them separately. 617 | $_mailbox = $mailbox; 618 | while (strlen(trim($_mailbox)) > 0) { 619 | $parts = explode('(', $_mailbox); 620 | $before_comment = $this->_splitCheck($parts, '('); 621 | if ($before_comment != $_mailbox) { 622 | // First char should be a (. 623 | $comment = substr(str_replace($before_comment, '', $_mailbox), 1); 624 | $parts = explode(')', $comment); 625 | $comment = $this->_splitCheck($parts, ')'); 626 | $comments[] = $comment; 627 | 628 | // +2 is for the brackets 629 | $_mailbox = substr($_mailbox, strpos($_mailbox, '('.$comment)+strlen($comment)+2); 630 | } else { 631 | break; 632 | } 633 | } 634 | 635 | foreach ($comments as $comment) { 636 | $mailbox = str_replace("($comment)", '', $mailbox); 637 | } 638 | 639 | $mailbox = trim($mailbox); 640 | 641 | // Check for name + route-addr 642 | if (substr($mailbox, -1) == '>' && substr($mailbox, 0, 1) != '<') { 643 | $parts = explode('<', $mailbox); 644 | $name = $this->_splitCheck($parts, '<'); 645 | 646 | $phrase = trim($name); 647 | $route_addr = trim(substr($mailbox, strlen($name.'<'), -1)); 648 | 649 | if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false) { 650 | return false; 651 | } 652 | 653 | // Only got addr-spec 654 | } else { 655 | // First snip angle brackets if present. 656 | if (substr($mailbox, 0, 1) == '<' && substr($mailbox, -1) == '>') { 657 | $addr_spec = substr($mailbox, 1, -1); 658 | } else { 659 | $addr_spec = $mailbox; 660 | } 661 | 662 | if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) { 663 | return false; 664 | } 665 | } 666 | 667 | // Construct the object that will be returned. 668 | $mbox = new stdClass(); 669 | 670 | // Add the phrase (even if empty) and comments 671 | $mbox->personal = $phrase; 672 | $mbox->comment = isset($comments) ? $comments : array(); 673 | 674 | if (isset($route_addr)) { 675 | $mbox->mailbox = $route_addr['local_part']; 676 | $mbox->host = $route_addr['domain']; 677 | $route_addr['adl'] !== '' ? $mbox->adl = $route_addr['adl'] : ''; 678 | } else { 679 | $mbox->mailbox = $addr_spec['local_part']; 680 | $mbox->host = $addr_spec['domain']; 681 | } 682 | 683 | $mailbox = $mbox; 684 | return true; 685 | } 686 | 687 | /** 688 | * This function validates a route-addr which is: 689 | * route-addr = "<" [route] addr-spec ">" 690 | * 691 | * Angle brackets have already been removed at the point of 692 | * getting to this function. 693 | * 694 | * @param string $route_addr The string to check. 695 | * @return mixed False on failure, or an array containing validated address/route information on success. 696 | */ 697 | protected function _validateRouteAddr($route_addr) 698 | { 699 | // Check for colon. 700 | if (strpos($route_addr, ':') !== false) { 701 | $parts = explode(':', $route_addr); 702 | $route = $this->_splitCheck($parts, ':'); 703 | } else { 704 | $route = $route_addr; 705 | } 706 | 707 | // If $route is same as $route_addr then the colon was in 708 | // quotes or brackets or, of course, non existent. 709 | if ($route === $route_addr){ 710 | unset($route); 711 | $addr_spec = $route_addr; 712 | if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) { 713 | return false; 714 | } 715 | } else { 716 | // Validate route part. 717 | if (($route = $this->_validateRoute($route)) === false) { 718 | return false; 719 | } 720 | 721 | $addr_spec = substr($route_addr, strlen($route . ':')); 722 | 723 | // Validate addr-spec part. 724 | if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) { 725 | return false; 726 | } 727 | } 728 | 729 | if (isset($route)) { 730 | $return['adl'] = $route; 731 | } else { 732 | $return['adl'] = ''; 733 | } 734 | 735 | $return = array_merge($return, $addr_spec); 736 | return $return; 737 | } 738 | 739 | /** 740 | * Function to validate a route, which is: 741 | * route = 1#("@" domain) ":" 742 | * 743 | * @param string $route The string to check. 744 | * @return mixed False on failure, or the validated $route on success. 745 | */ 746 | protected function _validateRoute($route) 747 | { 748 | // Split on comma. 749 | $domains = explode(',', trim($route)); 750 | 751 | foreach ($domains as $domain) { 752 | $domain = str_replace('@', '', trim($domain)); 753 | if (!$this->_validateDomain($domain)) return false; 754 | } 755 | 756 | return $route; 757 | } 758 | 759 | /** 760 | * Function to validate a domain, though this is not quite what 761 | * you expect of a strict internet domain. 762 | * 763 | * domain = sub-domain *("." sub-domain) 764 | * 765 | * @param string $domain The string to check. 766 | * @return mixed False on failure, or the validated domain on success. 767 | */ 768 | protected function _validateDomain($domain) 769 | { 770 | // Note the different use of $subdomains and $sub_domains 771 | $subdomains = explode('.', $domain); 772 | 773 | while (count($subdomains) > 0) { 774 | $sub_domains[] = $this->_splitCheck($subdomains, '.'); 775 | for ($i = 0; $i < $this->index + 1; $i++) 776 | array_shift($subdomains); 777 | } 778 | 779 | foreach ($sub_domains as $sub_domain) { 780 | if (!$this->_validateSubdomain(trim($sub_domain))) 781 | return false; 782 | } 783 | 784 | // Managed to get here, so return input. 785 | return $domain; 786 | } 787 | 788 | /** 789 | * Function to validate a subdomain: 790 | * subdomain = domain-ref / domain-literal 791 | * 792 | * @param string $subdomain The string to check. 793 | * @return boolean Success or failure. 794 | */ 795 | protected function _validateSubdomain($subdomain) 796 | { 797 | if (preg_match('|^\[(.*)]$|', $subdomain, $arr)){ 798 | if (!$this->_validateDliteral($arr[1])) return false; 799 | } else { 800 | if (!$this->_validateAtom($subdomain)) return false; 801 | } 802 | 803 | // Got here, so return successful. 804 | return true; 805 | } 806 | 807 | /** 808 | * Function to validate a domain literal: 809 | * domain-literal = "[" *(dtext / quoted-pair) "]" 810 | * 811 | * @param string $dliteral The string to check. 812 | * @return boolean Success or failure. 813 | */ 814 | protected function _validateDliteral($dliteral) 815 | { 816 | return !preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) && $matches[1] != '\\'; 817 | } 818 | 819 | /** 820 | * Function to validate an addr-spec. 821 | * 822 | * addr-spec = local-part "@" domain 823 | * 824 | * @param string $addr_spec The string to check. 825 | * @return mixed False on failure, or the validated addr-spec on success. 826 | */ 827 | protected function _validateAddrSpec($addr_spec) 828 | { 829 | $addr_spec = trim($addr_spec); 830 | 831 | // Split on @ sign if there is one. 832 | if (strpos($addr_spec, '@') !== false) { 833 | $parts = explode('@', $addr_spec); 834 | $local_part = $this->_splitCheck($parts, '@'); 835 | $domain = substr($addr_spec, strlen($local_part . '@')); 836 | 837 | // No @ sign so assume the default domain. 838 | } else { 839 | $local_part = $addr_spec; 840 | $domain = $this->default_domain; 841 | } 842 | 843 | if (($local_part = $this->_validateLocalPart($local_part)) === false) return false; 844 | if (($domain = $this->_validateDomain($domain)) === false) return false; 845 | 846 | // Got here so return successful. 847 | return array('local_part' => $local_part, 'domain' => $domain); 848 | } 849 | 850 | /** 851 | * Function to validate the local part of an address: 852 | * local-part = word *("." word) 853 | * 854 | * @param string $local_part 855 | * @return mixed False on failure, or the validated local part on success. 856 | */ 857 | protected function _validateLocalPart($local_part) 858 | { 859 | $parts = explode('.', $local_part); 860 | $words = array(); 861 | 862 | // Split the local_part into words. 863 | while (count($parts) > 0) { 864 | $words[] = $this->_splitCheck($parts, '.'); 865 | for ($i = 0; $i < $this->index + 1; $i++) { 866 | array_shift($parts); 867 | } 868 | } 869 | 870 | // Validate each word. 871 | foreach ($words as $word) { 872 | // word cannot be empty (#17317) 873 | if ($word === '') { 874 | return false; 875 | } 876 | // If this word contains an unquoted space, it is invalid. (6.2.4) 877 | if (strpos($word, ' ') && $word[0] !== '"') 878 | { 879 | return false; 880 | } 881 | 882 | if ($this->_validatePhrase(trim($word)) === false) return false; 883 | } 884 | 885 | // Managed to get here, so return the input. 886 | return $local_part; 887 | } 888 | 889 | /** 890 | * Returns an approximate count of how many addresses are in the 891 | * given string. This is APPROXIMATE as it only splits based on a 892 | * comma which has no preceding backslash. Could be useful as 893 | * large amounts of addresses will end up producing *large* 894 | * structures when used with parseAddressList(). 895 | * 896 | * @param string $data Addresses to count 897 | * @return int Approximate count 898 | */ 899 | public function approximateCount($data) 900 | { 901 | return count(preg_split('/(?@. This can be sufficient for most 908 | * people. Optional stricter mode can be utilised which restricts 909 | * mailbox characters allowed to alphanumeric, full stop, hyphen 910 | * and underscore. 911 | * 912 | * @param string $data Address to check 913 | * @param boolean $strict Optional stricter mode 914 | * @return mixed False if it fails, an indexed array 915 | * username/domain if it matches 916 | */ 917 | public function isValidInetAddress($data, $strict = false) 918 | { 919 | $regex = $strict ? '/^([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i' : '/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i'; 920 | if (preg_match($regex, trim($data), $matches)) { 921 | return array($matches[1], $matches[2]); 922 | } else { 923 | return false; 924 | } 925 | } 926 | 927 | } 928 | -------------------------------------------------------------------------------- /class/Mail/Net/SMTP.php: -------------------------------------------------------------------------------- 1 | | 17 | // | Jon Parise | 18 | // | Damian Alejandro Fernandez Sosa | 19 | // +----------------------------------------------------------------------+ 20 | 21 | require_once 'PEAR.php'; 22 | require_once 'Net/Socket.php'; 23 | 24 | /** 25 | * Provides an implementation of the SMTP protocol using PEAR's 26 | * Net_Socket class. 27 | * 28 | * @package Net_SMTP 29 | * @author Chuck Hagenbuch 30 | * @author Jon Parise 31 | * @author Damian Alejandro Fernandez Sosa 32 | * 33 | * @example basic.php A basic implementation of the Net_SMTP package. 34 | */ 35 | class Net_SMTP 36 | { 37 | /** 38 | * The server to connect to. 39 | * @var string 40 | */ 41 | public $host = 'localhost'; 42 | 43 | /** 44 | * The port to connect to. 45 | * @var int 46 | */ 47 | public $port = 25; 48 | 49 | /** 50 | * The value to give when sending EHLO or HELO. 51 | * @var string 52 | */ 53 | public $localhost = 'localhost'; 54 | 55 | /** 56 | * List of supported authentication methods, in preferential order. 57 | * @var array 58 | */ 59 | public $auth_methods = array(); 60 | 61 | /** 62 | * Use SMTP command pipelining (specified in RFC 2920) if the SMTP 63 | * server supports it. 64 | * 65 | * When pipeling is enabled, rcptTo(), mailFrom(), sendFrom(), 66 | * somlFrom() and samlFrom() do not wait for a response from the 67 | * SMTP server but return immediately. 68 | * 69 | * @var bool 70 | */ 71 | public $pipelining = false; 72 | 73 | /** 74 | * Number of pipelined commands. 75 | * @var int 76 | */ 77 | protected $pipelined_commands = 0; 78 | 79 | /** 80 | * Should debugging output be enabled? 81 | * @var boolean 82 | */ 83 | protected $debug = false; 84 | 85 | /** 86 | * Debug output handler. 87 | * @var callback 88 | */ 89 | protected $debug_handler = null; 90 | 91 | /** 92 | * The socket resource being used to connect to the SMTP server. 93 | * @var resource 94 | */ 95 | protected $socket = null; 96 | 97 | /** 98 | * Array of socket options that will be passed to Net_Socket::connect(). 99 | * @see stream_context_create() 100 | * @var array 101 | */ 102 | protected $socket_options = null; 103 | 104 | /** 105 | * The socket I/O timeout value in seconds. 106 | * @var int 107 | */ 108 | protected $timeout = 0; 109 | 110 | /** 111 | * The most recent server response code. 112 | * @var int 113 | */ 114 | protected $code = -1; 115 | 116 | /** 117 | * The most recent server response arguments. 118 | * @var array 119 | */ 120 | protected $arguments = array(); 121 | 122 | /** 123 | * Stores the SMTP server's greeting string. 124 | * @var string 125 | */ 126 | protected $greeting = null; 127 | 128 | /** 129 | * Stores detected features of the SMTP server. 130 | * @var array 131 | */ 132 | protected $esmtp = array(); 133 | 134 | /** 135 | * Instantiates a new Net_SMTP object, overriding any defaults 136 | * with parameters that are passed in. 137 | * 138 | * If you have SSL support in PHP, you can connect to a server 139 | * over SSL using an 'ssl://' prefix: 140 | * 141 | * // 465 is a common smtps port. 142 | * $smtp = new Net_SMTP('ssl://mail.host.com', 465); 143 | * $smtp->connect(); 144 | * 145 | * @param string $host The server to connect to. 146 | * @param integer $port The port to connect to. 147 | * @param string $localhost The value to give when sending EHLO or HELO. 148 | * @param boolean $pipelining Use SMTP command pipelining 149 | * @param integer $timeout Socket I/O timeout in seconds. 150 | * @param array $socket_options Socket stream_context_create() options. 151 | * 152 | * @since 1.0 153 | */ 154 | public function __construct($host = null, $port = null, $localhost = null, 155 | $pipelining = false, $timeout = 0, $socket_options = null 156 | ) { 157 | if (isset($host)) { 158 | $this->host = $host; 159 | } 160 | if (isset($port)) { 161 | $this->port = $port; 162 | } 163 | if (isset($localhost)) { 164 | $this->localhost = $localhost; 165 | } 166 | 167 | $this->pipelining = $pipelining; 168 | $this->socket = new Net_Socket(); 169 | $this->socket_options = $socket_options; 170 | $this->timeout = $timeout; 171 | 172 | /* Include the Auth_SASL package. If the package is available, we 173 | * enable the authentication methods that depend upon it. */ 174 | if (@include_once 'Auth/SASL.php') { 175 | $this->setAuthMethod('CRAM-MD5', array($this, 'authCramMD5')); 176 | $this->setAuthMethod('DIGEST-MD5', array($this, 'authDigestMD5')); 177 | } 178 | 179 | /* These standard authentication methods are always available. */ 180 | $this->setAuthMethod('LOGIN', array($this, 'authLogin'), false); 181 | $this->setAuthMethod('PLAIN', array($this, 'authPlain'), false); 182 | } 183 | 184 | /** 185 | * Set the socket I/O timeout value in seconds plus microseconds. 186 | * 187 | * @param integer $seconds Timeout value in seconds. 188 | * @param integer $microseconds Additional value in microseconds. 189 | * 190 | * @since 1.5.0 191 | */ 192 | public function setTimeout($seconds, $microseconds = 0) 193 | { 194 | return $this->socket->setTimeout($seconds, $microseconds); 195 | } 196 | 197 | /** 198 | * Set the value of the debugging flag. 199 | * 200 | * @param boolean $debug New value for the debugging flag. 201 | * @param callback $handler Debug handler callback 202 | * 203 | * @since 1.1.0 204 | */ 205 | public function setDebug($debug, $handler = null) 206 | { 207 | $this->debug = $debug; 208 | $this->debug_handler = $handler; 209 | } 210 | 211 | /** 212 | * Write the given debug text to the current debug output handler. 213 | * 214 | * @param string $message Debug mesage text. 215 | * 216 | * @since 1.3.3 217 | */ 218 | protected function debug($message) 219 | { 220 | if ($this->debug) { 221 | if ($this->debug_handler) { 222 | call_user_func_array( 223 | $this->debug_handler, array(&$this, $message) 224 | ); 225 | } else { 226 | echo "DEBUG: $message\n"; 227 | } 228 | } 229 | } 230 | 231 | /** 232 | * Send the given string of data to the server. 233 | * 234 | * @param string $data The string of data to send. 235 | * 236 | * @return mixed The number of bytes that were actually written, 237 | * or a PEAR_Error object on failure. 238 | * 239 | * @since 1.1.0 240 | */ 241 | protected function send($data) 242 | { 243 | $this->debug("Send: $data"); 244 | 245 | $result = $this->socket->write($data); 246 | if (!$result || PEAR::isError($result)) { 247 | $msg = $result ? $result->getMessage() : "unknown error"; 248 | return PEAR::raiseError("Failed to write to socket: $msg"); 249 | } 250 | 251 | return $result; 252 | } 253 | 254 | /** 255 | * Send a command to the server with an optional string of 256 | * arguments. A carriage return / linefeed (CRLF) sequence will 257 | * be appended to each command string before it is sent to the 258 | * SMTP server - an error will be thrown if the command string 259 | * already contains any newline characters. Use send() for 260 | * commands that must contain newlines. 261 | * 262 | * @param string $command The SMTP command to send to the server. 263 | * @param string $args A string of optional arguments to append 264 | * to the command. 265 | * 266 | * @return mixed The result of the send() call. 267 | * 268 | * @since 1.1.0 269 | */ 270 | protected function put($command, $args = '') 271 | { 272 | if (!empty($args)) { 273 | $command .= ' ' . $args; 274 | } 275 | 276 | if (strcspn($command, "\r\n") !== strlen($command)) { 277 | return PEAR::raiseError('Commands cannot contain newlines'); 278 | } 279 | 280 | return $this->send($command . "\r\n"); 281 | } 282 | 283 | /** 284 | * Read a reply from the SMTP server. The reply consists of a response 285 | * code and a response message. 286 | * 287 | * @param mixed $valid The set of valid response codes. These 288 | * may be specified as an array of integer 289 | * values or as a single integer value. 290 | * @param bool $later Do not parse the response now, but wait 291 | * until the last command in the pipelined 292 | * command group 293 | * 294 | * @return mixed True if the server returned a valid response code or 295 | * a PEAR_Error object is an error condition is reached. 296 | * 297 | * @since 1.1.0 298 | * 299 | * @see getResponse 300 | */ 301 | protected function parseResponse($valid, $later = false) 302 | { 303 | $this->code = -1; 304 | $this->arguments = array(); 305 | 306 | if ($later) { 307 | $this->pipelined_commands++; 308 | return true; 309 | } 310 | 311 | for ($i = 0; $i <= $this->pipelined_commands; $i++) { 312 | while ($line = $this->socket->readLine()) { 313 | $this->debug("Recv: $line"); 314 | 315 | /* If we receive an empty line, the connection was closed. */ 316 | if (empty($line)) { 317 | $this->disconnect(); 318 | return PEAR::raiseError('Connection was closed'); 319 | } 320 | 321 | /* Read the code and store the rest in the arguments array. */ 322 | $code = substr($line, 0, 3); 323 | $this->arguments[] = trim(substr($line, 4)); 324 | 325 | /* Check the syntax of the response code. */ 326 | if (is_numeric($code)) { 327 | $this->code = (int)$code; 328 | } else { 329 | $this->code = -1; 330 | break; 331 | } 332 | 333 | /* If this is not a multiline response, we're done. */ 334 | if (substr($line, 3, 1) != '-') { 335 | break; 336 | } 337 | } 338 | } 339 | 340 | $this->pipelined_commands = 0; 341 | 342 | /* Compare the server's response code with the valid code/codes. */ 343 | if (is_int($valid) && ($this->code === $valid)) { 344 | return true; 345 | } elseif (is_array($valid) && in_array($this->code, $valid, true)) { 346 | return true; 347 | } 348 | 349 | return PEAR::raiseError('Invalid response code received from server', $this->code); 350 | } 351 | 352 | /** 353 | * Issue an SMTP command and verify its response. 354 | * 355 | * @param string $command The SMTP command string or data. 356 | * @param mixed $valid The set of valid response codes. These 357 | * may be specified as an array of integer 358 | * values or as a single integer value. 359 | * 360 | * @return mixed True on success or a PEAR_Error object on failure. 361 | * 362 | * @since 1.6.0 363 | */ 364 | public function command($command, $valid) 365 | { 366 | if (PEAR::isError($error = $this->put($command))) { 367 | return $error; 368 | } 369 | if (PEAR::isError($error = $this->parseResponse($valid))) { 370 | return $error; 371 | } 372 | 373 | return true; 374 | } 375 | 376 | /** 377 | * Return a 2-tuple containing the last response from the SMTP server. 378 | * 379 | * @return array A two-element array: the first element contains the 380 | * response code as an integer and the second element 381 | * contains the response's arguments as a string. 382 | * 383 | * @since 1.1.0 384 | */ 385 | public function getResponse() 386 | { 387 | return array($this->code, join("\n", $this->arguments)); 388 | } 389 | 390 | /** 391 | * Return the SMTP server's greeting string. 392 | * 393 | * @return string A string containing the greeting string, or null if 394 | * a greeting has not been received. 395 | * 396 | * @since 1.3.3 397 | */ 398 | public function getGreeting() 399 | { 400 | return $this->greeting; 401 | } 402 | 403 | /** 404 | * Attempt to connect to the SMTP server. 405 | * 406 | * @param int $timeout The timeout value (in seconds) for the 407 | * socket connection attempt. 408 | * @param bool $persistent Should a persistent socket connection 409 | * be used? 410 | * 411 | * @return mixed Returns a PEAR_Error with an error message on any 412 | * kind of failure, or true on success. 413 | * @since 1.0 414 | */ 415 | public function connect($timeout = null, $persistent = false) 416 | { 417 | $this->greeting = null; 418 | 419 | $result = $this->socket->connect( 420 | $this->host, $this->port, $persistent, $timeout, $this->socket_options 421 | ); 422 | 423 | if (PEAR::isError($result)) { 424 | return PEAR::raiseError( 425 | 'Failed to connect socket: ' . $result->getMessage() 426 | ); 427 | } 428 | 429 | /* 430 | * Now that we're connected, reset the socket's timeout value for 431 | * future I/O operations. This allows us to have different socket 432 | * timeout values for the initial connection (our $timeout parameter) 433 | * and all other socket operations. 434 | */ 435 | if ($this->timeout > 0) { 436 | if (PEAR::isError($error = $this->setTimeout($this->timeout))) { 437 | return $error; 438 | } 439 | } 440 | 441 | if (PEAR::isError($error = $this->parseResponse(220))) { 442 | return $error; 443 | } 444 | 445 | /* Extract and store a copy of the server's greeting string. */ 446 | list(, $this->greeting) = $this->getResponse(); 447 | 448 | if (PEAR::isError($error = $this->negotiate())) { 449 | return $error; 450 | } 451 | 452 | return true; 453 | } 454 | 455 | /** 456 | * Attempt to disconnect from the SMTP server. 457 | * 458 | * @return mixed Returns a PEAR_Error with an error message on any 459 | * kind of failure, or true on success. 460 | * @since 1.0 461 | */ 462 | public function disconnect() 463 | { 464 | if (PEAR::isError($error = $this->put('QUIT'))) { 465 | return $error; 466 | } 467 | if (PEAR::isError($error = $this->parseResponse(221))) { 468 | return $error; 469 | } 470 | if (PEAR::isError($error = $this->socket->disconnect())) { 471 | return PEAR::raiseError( 472 | 'Failed to disconnect socket: ' . $error->getMessage() 473 | ); 474 | } 475 | 476 | return true; 477 | } 478 | 479 | /** 480 | * Attempt to send the EHLO command and obtain a list of ESMTP 481 | * extensions available, and failing that just send HELO. 482 | * 483 | * @return mixed Returns a PEAR_Error with an error message on any 484 | * kind of failure, or true on success. 485 | * 486 | * @since 1.1.0 487 | */ 488 | protected function negotiate() 489 | { 490 | if (PEAR::isError($error = $this->put('EHLO', $this->localhost))) { 491 | return $error; 492 | } 493 | 494 | if (PEAR::isError($this->parseResponse(250))) { 495 | /* If the EHLO failed, try the simpler HELO command. */ 496 | if (PEAR::isError($error = $this->put('HELO', $this->localhost))) { 497 | return $error; 498 | } 499 | if (PEAR::isError($this->parseResponse(250))) { 500 | return PEAR::raiseError('HELO was not accepted', $this->code); 501 | } 502 | 503 | return true; 504 | } 505 | 506 | foreach ($this->arguments as $argument) { 507 | $verb = strtok($argument, ' '); 508 | $len = strlen($verb); 509 | $arguments = substr($argument, $len + 1, strlen($argument) - $len - 1); 510 | $this->esmtp[$verb] = $arguments; 511 | } 512 | 513 | if (!isset($this->esmtp['PIPELINING'])) { 514 | $this->pipelining = false; 515 | } 516 | 517 | return true; 518 | } 519 | 520 | /** 521 | * Returns the name of the best authentication method that the server 522 | * has advertised. 523 | * 524 | * @return mixed Returns a string containing the name of the best 525 | * supported authentication method or a PEAR_Error object 526 | * if a failure condition is encountered. 527 | * @since 1.1.0 528 | */ 529 | protected function getBestAuthMethod() 530 | { 531 | $available_methods = explode(' ', $this->esmtp['AUTH']); 532 | 533 | foreach ($this->auth_methods as $method => $callback) { 534 | if (in_array($method, $available_methods)) { 535 | return $method; 536 | } 537 | } 538 | 539 | return PEAR::raiseError('No supported authentication methods'); 540 | } 541 | 542 | /** 543 | * Attempt to do SMTP authentication. 544 | * 545 | * @param string $uid The userid to authenticate as. 546 | * @param string $pwd The password to authenticate with. 547 | * @param string $method The requested authentication method. If none is 548 | * specified, the best supported method will be used. 549 | * @param bool $tls Flag indicating whether or not TLS should be attempted. 550 | * @param string $authz An optional authorization identifier. If specified, this 551 | * identifier will be used as the authorization proxy. 552 | * 553 | * @return mixed Returns a PEAR_Error with an error message on any 554 | * kind of failure, or true on success. 555 | * @since 1.0 556 | */ 557 | public function auth($uid, $pwd , $method = '', $tls = true, $authz = '') 558 | { 559 | /* We can only attempt a TLS connection if one has been requested, 560 | * we're running PHP 5.1.0 or later, have access to the OpenSSL 561 | * extension, are connected to an SMTP server which supports the 562 | * STARTTLS extension, and aren't already connected over a secure 563 | * (SSL) socket connection. */ 564 | if ($tls && version_compare(PHP_VERSION, '5.1.0', '>=') 565 | && extension_loaded('openssl') && isset($this->esmtp['STARTTLS']) 566 | && strncasecmp($this->host, 'ssl://', 6) !== 0 567 | ) { 568 | /* Start the TLS connection attempt. */ 569 | if (PEAR::isError($result = $this->put('STARTTLS'))) { 570 | return $result; 571 | } 572 | if (PEAR::isError($result = $this->parseResponse(220))) { 573 | return $result; 574 | } 575 | if (isset($this->socket_options['ssl']['crypto_method'])) { 576 | $crypto_method = $this->socket_options['ssl']['crypto_method']; 577 | } else { 578 | /* STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT constant does not exist 579 | * and STREAM_CRYPTO_METHOD_SSLv23_CLIENT constant is 580 | * inconsistent across PHP versions. */ 581 | $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT 582 | | @STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT 583 | | @STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; 584 | } 585 | if (PEAR::isError($result = $this->socket->enableCrypto(true, $crypto_method))) { 586 | return $result; 587 | } elseif ($result !== true) { 588 | return PEAR::raiseError('STARTTLS failed'); 589 | } 590 | 591 | /* Send EHLO again to recieve the AUTH string from the 592 | * SMTP server. */ 593 | $this->negotiate(); 594 | } 595 | 596 | if (empty($this->esmtp['AUTH'])) { 597 | return PEAR::raiseError('SMTP server does not support authentication'); 598 | } 599 | 600 | /* If no method has been specified, get the name of the best 601 | * supported method advertised by the SMTP server. */ 602 | if (empty($method)) { 603 | if (PEAR::isError($method = $this->getBestAuthMethod())) { 604 | /* Return the PEAR_Error object from _getBestAuthMethod(). */ 605 | return $method; 606 | } 607 | } else { 608 | $method = strtoupper($method); 609 | if (!array_key_exists($method, $this->auth_methods)) { 610 | return PEAR::raiseError("$method is not a supported authentication method"); 611 | } 612 | } 613 | 614 | if (!isset($this->auth_methods[$method])) { 615 | return PEAR::raiseError("$method is not a supported authentication method"); 616 | } 617 | 618 | if (!is_callable($this->auth_methods[$method], false)) { 619 | return PEAR::raiseError("$method authentication method cannot be called"); 620 | } 621 | 622 | if (is_array($this->auth_methods[$method])) { 623 | list($object, $method) = $this->auth_methods[$method]; 624 | $result = $object->{$method}($uid, $pwd, $authz, $this); 625 | } else { 626 | $func = $this->auth_methods[$method]; 627 | $result = $func($uid, $pwd, $authz, $this); 628 | } 629 | 630 | /* If an error was encountered, return the PEAR_Error object. */ 631 | if (PEAR::isError($result)) { 632 | return $result; 633 | } 634 | 635 | return true; 636 | } 637 | 638 | /** 639 | * Add a new authentication method. 640 | * 641 | * @param string $name The authentication method name (e.g. 'PLAIN') 642 | * @param mixed $callback The authentication callback (given as the name of a 643 | * function or as an (object, method name) array). 644 | * @param bool $prepend Should the new method be prepended to the list of 645 | * available methods? This is the default behavior, 646 | * giving the new method the highest priority. 647 | * 648 | * @return mixed True on success or a PEAR_Error object on failure. 649 | * 650 | * @since 1.6.0 651 | */ 652 | public function setAuthMethod($name, $callback, $prepend = true) 653 | { 654 | if (!is_string($name)) { 655 | return PEAR::raiseError('Method name is not a string'); 656 | } 657 | 658 | if (!is_string($callback) && !is_array($callback)) { 659 | return PEAR::raiseError('Method callback must be string or array'); 660 | } 661 | 662 | if (is_array($callback)) { 663 | if (!is_object($callback[0]) || !is_string($callback[1])) { 664 | return PEAR::raiseError('Bad mMethod callback array'); 665 | } 666 | } 667 | 668 | if ($prepend) { 669 | $this->auth_methods = array_merge( 670 | array($name => $callback), $this->auth_methods 671 | ); 672 | } else { 673 | $this->auth_methods[$name] = $callback; 674 | } 675 | 676 | return true; 677 | } 678 | 679 | /** 680 | * Authenticates the user using the DIGEST-MD5 method. 681 | * 682 | * @param string $uid The userid to authenticate as. 683 | * @param string $pwd The password to authenticate with. 684 | * @param string $authz The optional authorization proxy identifier. 685 | * 686 | * @return mixed Returns a PEAR_Error with an error message on any 687 | * kind of failure, or true on success. 688 | * @since 1.1.0 689 | */ 690 | protected function authDigestMD5($uid, $pwd, $authz = '') 691 | { 692 | if (PEAR::isError($error = $this->put('AUTH', 'DIGEST-MD5'))) { 693 | return $error; 694 | } 695 | /* 334: Continue authentication request */ 696 | if (PEAR::isError($error = $this->parseResponse(334))) { 697 | /* 503: Error: already authenticated */ 698 | if ($this->code === 503) { 699 | return true; 700 | } 701 | return $error; 702 | } 703 | 704 | $auth_sasl = new Auth_SASL; 705 | $digest = $auth_sasl->factory('digest-md5'); 706 | $challenge = base64_decode($this->arguments[0]); 707 | $auth_str = base64_encode( 708 | $digest->getResponse($uid, $pwd, $challenge, $this->host, "smtp", $authz) 709 | ); 710 | 711 | if (PEAR::isError($error = $this->put($auth_str))) { 712 | return $error; 713 | } 714 | /* 334: Continue authentication request */ 715 | if (PEAR::isError($error = $this->parseResponse(334))) { 716 | return $error; 717 | } 718 | 719 | /* We don't use the protocol's third step because SMTP doesn't 720 | * allow subsequent authentication, so we just silently ignore 721 | * it. */ 722 | if (PEAR::isError($error = $this->put(''))) { 723 | return $error; 724 | } 725 | /* 235: Authentication successful */ 726 | if (PEAR::isError($error = $this->parseResponse(235))) { 727 | return $error; 728 | } 729 | } 730 | 731 | /** 732 | * Authenticates the user using the CRAM-MD5 method. 733 | * 734 | * @param string $uid The userid to authenticate as. 735 | * @param string $pwd The password to authenticate with. 736 | * @param string $authz The optional authorization proxy identifier. 737 | * 738 | * @return mixed Returns a PEAR_Error with an error message on any 739 | * kind of failure, or true on success. 740 | * @since 1.1.0 741 | */ 742 | protected function authCRAMMD5($uid, $pwd, $authz = '') 743 | { 744 | if (PEAR::isError($error = $this->put('AUTH', 'CRAM-MD5'))) { 745 | return $error; 746 | } 747 | /* 334: Continue authentication request */ 748 | if (PEAR::isError($error = $this->parseResponse(334))) { 749 | /* 503: Error: already authenticated */ 750 | if ($this->code === 503) { 751 | return true; 752 | } 753 | return $error; 754 | } 755 | 756 | $auth_sasl = new Auth_SASL; 757 | $challenge = base64_decode($this->arguments[0]); 758 | $cram = $auth_sasl->factory('cram-md5'); 759 | $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge)); 760 | 761 | if (PEAR::isError($error = $this->put($auth_str))) { 762 | return $error; 763 | } 764 | 765 | /* 235: Authentication successful */ 766 | if (PEAR::isError($error = $this->parseResponse(235))) { 767 | return $error; 768 | } 769 | } 770 | 771 | /** 772 | * Authenticates the user using the LOGIN method. 773 | * 774 | * @param string $uid The userid to authenticate as. 775 | * @param string $pwd The password to authenticate with. 776 | * @param string $authz The optional authorization proxy identifier. 777 | * 778 | * @return mixed Returns a PEAR_Error with an error message on any 779 | * kind of failure, or true on success. 780 | * @since 1.1.0 781 | */ 782 | protected function authLogin($uid, $pwd, $authz = '') 783 | { 784 | if (PEAR::isError($error = $this->put('AUTH', 'LOGIN'))) { 785 | return $error; 786 | } 787 | /* 334: Continue authentication request */ 788 | if (PEAR::isError($error = $this->parseResponse(334))) { 789 | /* 503: Error: already authenticated */ 790 | if ($this->code === 503) { 791 | return true; 792 | } 793 | return $error; 794 | } 795 | 796 | if (PEAR::isError($error = $this->put(base64_encode($uid)))) { 797 | return $error; 798 | } 799 | /* 334: Continue authentication request */ 800 | if (PEAR::isError($error = $this->parseResponse(334))) { 801 | return $error; 802 | } 803 | 804 | if (PEAR::isError($error = $this->put(base64_encode($pwd)))) { 805 | return $error; 806 | } 807 | 808 | /* 235: Authentication successful */ 809 | if (PEAR::isError($error = $this->parseResponse(235))) { 810 | return $error; 811 | } 812 | 813 | return true; 814 | } 815 | 816 | /** 817 | * Authenticates the user using the PLAIN method. 818 | * 819 | * @param string $uid The userid to authenticate as. 820 | * @param string $pwd The password to authenticate with. 821 | * @param string $authz The optional authorization proxy identifier. 822 | * 823 | * @return mixed Returns a PEAR_Error with an error message on any 824 | * kind of failure, or true on success. 825 | * @since 1.1.0 826 | */ 827 | protected function authPlain($uid, $pwd, $authz = '') 828 | { 829 | if (PEAR::isError($error = $this->put('AUTH', 'PLAIN'))) { 830 | return $error; 831 | } 832 | /* 334: Continue authentication request */ 833 | if (PEAR::isError($error = $this->parseResponse(334))) { 834 | /* 503: Error: already authenticated */ 835 | if ($this->code === 503) { 836 | return true; 837 | } 838 | return $error; 839 | } 840 | 841 | $auth_str = base64_encode($authz . chr(0) . $uid . chr(0) . $pwd); 842 | 843 | if (PEAR::isError($error = $this->put($auth_str))) { 844 | return $error; 845 | } 846 | 847 | /* 235: Authentication successful */ 848 | if (PEAR::isError($error = $this->parseResponse(235))) { 849 | return $error; 850 | } 851 | 852 | return true; 853 | } 854 | 855 | /** 856 | * Send the HELO command. 857 | * 858 | * @param string $domain The domain name to say we are. 859 | * 860 | * @return mixed Returns a PEAR_Error with an error message on any 861 | * kind of failure, or true on success. 862 | * @since 1.0 863 | */ 864 | public function helo($domain) 865 | { 866 | if (PEAR::isError($error = $this->put('HELO', $domain))) { 867 | return $error; 868 | } 869 | if (PEAR::isError($error = $this->parseResponse(250))) { 870 | return $error; 871 | } 872 | 873 | return true; 874 | } 875 | 876 | /** 877 | * Return the list of SMTP service extensions advertised by the server. 878 | * 879 | * @return array The list of SMTP service extensions. 880 | * @since 1.3 881 | */ 882 | public function getServiceExtensions() 883 | { 884 | return $this->esmtp; 885 | } 886 | 887 | /** 888 | * Send the MAIL FROM: command. 889 | * 890 | * @param string $sender The sender (reverse path) to set. 891 | * @param string $params String containing additional MAIL parameters, 892 | * such as the NOTIFY flags defined by RFC 1891 893 | * or the VERP protocol. 894 | * 895 | * If $params is an array, only the 'verp' option 896 | * is supported. If 'verp' is true, the XVERP 897 | * parameter is appended to the MAIL command. 898 | * If the 'verp' value is a string, the full 899 | * XVERP=value parameter is appended. 900 | * 901 | * @return mixed Returns a PEAR_Error with an error message on any 902 | * kind of failure, or true on success. 903 | * @since 1.0 904 | */ 905 | public function mailFrom($sender, $params = null) 906 | { 907 | $args = "FROM:<$sender>"; 908 | 909 | /* Support the deprecated array form of $params. */ 910 | if (is_array($params) && isset($params['verp'])) { 911 | if ($params['verp'] === true) { 912 | $args .= ' XVERP'; 913 | } elseif (trim($params['verp'])) { 914 | $args .= ' XVERP=' . $params['verp']; 915 | } 916 | } elseif (is_string($params) && !empty($params)) { 917 | $args .= ' ' . $params; 918 | } 919 | 920 | if (PEAR::isError($error = $this->put('MAIL', $args))) { 921 | return $error; 922 | } 923 | if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { 924 | return $error; 925 | } 926 | 927 | return true; 928 | } 929 | 930 | /** 931 | * Send the RCPT TO: command. 932 | * 933 | * @param string $recipient The recipient (forward path) to add. 934 | * @param string $params String containing additional RCPT parameters, 935 | * such as the NOTIFY flags defined by RFC 1891. 936 | * 937 | * @return mixed Returns a PEAR_Error with an error message on any 938 | * kind of failure, or true on success. 939 | * 940 | * @since 1.0 941 | */ 942 | public function rcptTo($recipient, $params = null) 943 | { 944 | $args = "TO:<$recipient>"; 945 | if (is_string($params)) { 946 | $args .= ' ' . $params; 947 | } 948 | 949 | if (PEAR::isError($error = $this->put('RCPT', $args))) { 950 | return $error; 951 | } 952 | if (PEAR::isError($error = $this->parseResponse(array(250, 251), $this->pipelining))) { 953 | return $error; 954 | } 955 | 956 | return true; 957 | } 958 | 959 | /** 960 | * Quote the data so that it meets SMTP standards. 961 | * 962 | * This is provided as a separate public function to facilitate 963 | * easier overloading for the cases where it is desirable to 964 | * customize the quoting behavior. 965 | * 966 | * @param string &$data The message text to quote. The string must be passed 967 | * by reference, and the text will be modified in place. 968 | * 969 | * @since 1.2 970 | */ 971 | public function quotedata(&$data) 972 | { 973 | /* Because a single leading period (.) signifies an end to the 974 | * data, legitimate leading periods need to be "doubled" ('..'). */ 975 | $data = preg_replace('/^\./m', '..', $data); 976 | 977 | /* Change Unix (\n) and Mac (\r) linefeeds into CRLF's (\r\n). */ 978 | $data = preg_replace('/(?:\r\n|\n|\r(?!\n))/', "\r\n", $data); 979 | } 980 | 981 | /** 982 | * Send the DATA command. 983 | * 984 | * @param mixed $data The message data, either as a string or an open 985 | * file resource. 986 | * @param string $headers The message headers. If $headers is provided, 987 | * $data is assumed to contain only body data. 988 | * 989 | * @return mixed Returns a PEAR_Error with an error message on any 990 | * kind of failure, or true on success. 991 | * @since 1.0 992 | */ 993 | public function data($data, $headers = null) 994 | { 995 | /* Verify that $data is a supported type. */ 996 | if (!is_string($data) && !is_resource($data)) { 997 | return PEAR::raiseError('Expected a string or file resource'); 998 | } 999 | 1000 | /* Start by considering the size of the optional headers string. We 1001 | * also account for the addition 4 character "\r\n\r\n" separator 1002 | * sequence. */ 1003 | $size = $headers_size = (is_null($headers)) ? 0 : strlen($headers) + 4; 1004 | 1005 | if (is_resource($data)) { 1006 | $stat = fstat($data); 1007 | if ($stat === false) { 1008 | return PEAR::raiseError('Failed to get file size'); 1009 | } 1010 | $size += $stat['size']; 1011 | } else { 1012 | $size += strlen($data); 1013 | } 1014 | 1015 | /* RFC 1870, section 3, subsection 3 states "a value of zero indicates 1016 | * that no fixed maximum message size is in force". Furthermore, it 1017 | * says that if "the parameter is omitted no information is conveyed 1018 | * about the server's fixed maximum message size". */ 1019 | $limit = (isset($this->esmtp['SIZE'])) ? $this->esmtp['SIZE'] : 0; 1020 | if ($limit > 0 && $size >= $limit) { 1021 | $this->disconnect(); 1022 | return PEAR::raiseError('Message size exceeds server limit'); 1023 | } 1024 | 1025 | /* Initiate the DATA command. */ 1026 | if (PEAR::isError($error = $this->put('DATA'))) { 1027 | return $error; 1028 | } 1029 | if (PEAR::isError($error = $this->parseResponse(354))) { 1030 | return $error; 1031 | } 1032 | 1033 | /* If we have a separate headers string, send it first. */ 1034 | if (!is_null($headers)) { 1035 | $this->quotedata($headers); 1036 | if (PEAR::isError($result = $this->send($headers . "\r\n\r\n"))) { 1037 | return $result; 1038 | } 1039 | 1040 | /* Subtract the headers size now that they've been sent. */ 1041 | $size -= $headers_size; 1042 | } 1043 | 1044 | /* Now we can send the message body data. */ 1045 | if (is_resource($data)) { 1046 | /* Stream the contents of the file resource out over our socket 1047 | * connection, line by line. Each line must be run through the 1048 | * quoting routine. */ 1049 | while (strlen($line = fread($data, 8192)) > 0) { 1050 | /* If the last character is an newline, we need to grab the 1051 | * next character to check to see if it is a period. */ 1052 | while (!feof($data)) { 1053 | $char = fread($data, 1); 1054 | $line .= $char; 1055 | if ($char != "\n") { 1056 | break; 1057 | } 1058 | } 1059 | $this->quotedata($line); 1060 | if (PEAR::isError($result = $this->send($line))) { 1061 | return $result; 1062 | } 1063 | } 1064 | 1065 | $last = $line; 1066 | } else { 1067 | /* 1068 | * Break up the data by sending one chunk (up to 512k) at a time. 1069 | * This approach reduces our peak memory usage. 1070 | */ 1071 | for ($offset = 0; $offset < $size;) { 1072 | $end = $offset + 512000; 1073 | 1074 | /* 1075 | * Ensure we don't read beyond our data size or span multiple 1076 | * lines. quotedata() can't properly handle character data 1077 | * that's split across two line break boundaries. 1078 | */ 1079 | if ($end >= $size) { 1080 | $end = $size; 1081 | } else { 1082 | for (; $end < $size; $end++) { 1083 | if ($data[$end] != "\n") { 1084 | break; 1085 | } 1086 | } 1087 | } 1088 | 1089 | /* Extract our chunk and run it through the quoting routine. */ 1090 | $chunk = substr($data, $offset, $end - $offset); 1091 | $this->quotedata($chunk); 1092 | 1093 | /* If we run into a problem along the way, abort. */ 1094 | if (PEAR::isError($result = $this->send($chunk))) { 1095 | return $result; 1096 | } 1097 | 1098 | /* Advance the offset to the end of this chunk. */ 1099 | $offset = $end; 1100 | } 1101 | 1102 | $last = $chunk; 1103 | } 1104 | 1105 | /* Don't add another CRLF sequence if it's already in the data */ 1106 | $terminator = (substr($last, -2) == "\r\n" ? '' : "\r\n") . ".\r\n"; 1107 | 1108 | /* Finally, send the DATA terminator sequence. */ 1109 | if (PEAR::isError($result = $this->send($terminator))) { 1110 | return $result; 1111 | } 1112 | 1113 | /* Verify that the data was successfully received by the server. */ 1114 | if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { 1115 | return $error; 1116 | } 1117 | 1118 | return true; 1119 | } 1120 | 1121 | /** 1122 | * Send the SEND FROM: command. 1123 | * 1124 | * @param string $path The reverse path to send. 1125 | * 1126 | * @return mixed Returns a PEAR_Error with an error message on any 1127 | * kind of failure, or true on success. 1128 | * @since 1.2.6 1129 | */ 1130 | public function sendFrom($path) 1131 | { 1132 | if (PEAR::isError($error = $this->put('SEND', "FROM:<$path>"))) { 1133 | return $error; 1134 | } 1135 | if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { 1136 | return $error; 1137 | } 1138 | 1139 | return true; 1140 | } 1141 | 1142 | /** 1143 | * Send the SOML FROM: command. 1144 | * 1145 | * @param string $path The reverse path to send. 1146 | * 1147 | * @return mixed Returns a PEAR_Error with an error message on any 1148 | * kind of failure, or true on success. 1149 | * @since 1.2.6 1150 | */ 1151 | public function somlFrom($path) 1152 | { 1153 | if (PEAR::isError($error = $this->put('SOML', "FROM:<$path>"))) { 1154 | return $error; 1155 | } 1156 | if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { 1157 | return $error; 1158 | } 1159 | 1160 | return true; 1161 | } 1162 | 1163 | /** 1164 | * Send the SAML FROM: command. 1165 | * 1166 | * @param string $path The reverse path to send. 1167 | * 1168 | * @return mixed Returns a PEAR_Error with an error message on any 1169 | * kind of failure, or true on success. 1170 | * @since 1.2.6 1171 | */ 1172 | public function samlFrom($path) 1173 | { 1174 | if (PEAR::isError($error = $this->put('SAML', "FROM:<$path>"))) { 1175 | return $error; 1176 | } 1177 | if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { 1178 | return $error; 1179 | } 1180 | 1181 | return true; 1182 | } 1183 | 1184 | /** 1185 | * Send the RSET command. 1186 | * 1187 | * @return mixed Returns a PEAR_Error with an error message on any 1188 | * kind of failure, or true on success. 1189 | * @since 1.0 1190 | */ 1191 | public function rset() 1192 | { 1193 | if (PEAR::isError($error = $this->put('RSET'))) { 1194 | return $error; 1195 | } 1196 | if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { 1197 | return $error; 1198 | } 1199 | 1200 | return true; 1201 | } 1202 | 1203 | /** 1204 | * Send the VRFY command. 1205 | * 1206 | * @param string $string The string to verify 1207 | * 1208 | * @return mixed Returns a PEAR_Error with an error message on any 1209 | * kind of failure, or true on success. 1210 | * @since 1.0 1211 | */ 1212 | public function vrfy($string) 1213 | { 1214 | /* Note: 251 is also a valid response code */ 1215 | if (PEAR::isError($error = $this->put('VRFY', $string))) { 1216 | return $error; 1217 | } 1218 | if (PEAR::isError($error = $this->parseResponse(array(250, 252)))) { 1219 | return $error; 1220 | } 1221 | 1222 | return true; 1223 | } 1224 | 1225 | /** 1226 | * Send the NOOP command. 1227 | * 1228 | * @return mixed Returns a PEAR_Error with an error message on any 1229 | * kind of failure, or true on success. 1230 | * @since 1.0 1231 | */ 1232 | public function noop() 1233 | { 1234 | if (PEAR::isError($error = $this->put('NOOP'))) { 1235 | return $error; 1236 | } 1237 | if (PEAR::isError($error = $this->parseResponse(250))) { 1238 | return $error; 1239 | } 1240 | 1241 | return true; 1242 | } 1243 | 1244 | /** 1245 | * Backwards-compatibility method. identifySender()'s functionality is 1246 | * now handled internally. 1247 | * 1248 | * @return boolean This method always return true. 1249 | * 1250 | * @since 1.0 1251 | */ 1252 | public function identifySender() 1253 | { 1254 | return true; 1255 | } 1256 | } 1257 | --------------------------------------------------------------------------------