1: | <?php
|
2: | |
3: | |
4: | |
5: | |
6: | |
7: | |
8: | |
9: | |
10: | |
11: | |
12: | |
13: | |
14: | |
15: | |
16: | |
17: | |
18: |
|
19: |
|
20: | |
21: | |
22: | |
23: | |
24: | |
25: | |
26: |
|
27: | class SMTP
|
28: | {
|
29: | |
30: | |
31: | |
32: |
|
33: | const VERSION = '5.2.28';
|
34: |
|
35: | |
36: | |
37: | |
38: |
|
39: | const CRLF = "\r\n";
|
40: |
|
41: | |
42: | |
43: | |
44: |
|
45: | const DEFAULT_SMTP_PORT = 25;
|
46: |
|
47: | |
48: | |
49: | |
50: |
|
51: | const MAX_LINE_LENGTH = 998;
|
52: |
|
53: | |
54: | |
55: |
|
56: | const DEBUG_OFF = 0;
|
57: |
|
58: | |
59: | |
60: |
|
61: | const DEBUG_CLIENT = 1;
|
62: |
|
63: | |
64: | |
65: |
|
66: | const DEBUG_SERVER = 2;
|
67: |
|
68: | |
69: | |
70: |
|
71: | const DEBUG_CONNECTION = 3;
|
72: |
|
73: | |
74: | |
75: |
|
76: | const DEBUG_LOWLEVEL = 4;
|
77: |
|
78: | |
79: | |
80: | |
81: | |
82: | |
83: |
|
84: | public $Version = '5.2.28';
|
85: |
|
86: | |
87: | |
88: | |
89: | |
90: | |
91: |
|
92: | public $SMTP_PORT = 25;
|
93: |
|
94: | |
95: | |
96: | |
97: | |
98: | |
99: |
|
100: | public $CRLF = "\r\n";
|
101: |
|
102: | |
103: | |
104: | |
105: | |
106: | |
107: | |
108: | |
109: | |
110: | |
111: |
|
112: | public $do_debug = self::DEBUG_OFF;
|
113: |
|
114: | |
115: | |
116: | |
117: | |
118: | |
119: | |
120: | |
121: | |
122: | |
123: | |
124: | |
125: | |
126: |
|
127: | public $Debugoutput = 'echo';
|
128: |
|
129: | |
130: | |
131: | |
132: | |
133: | |
134: |
|
135: | public $do_verp = false;
|
136: |
|
137: | |
138: | |
139: | |
140: | |
141: | |
142: | |
143: |
|
144: | public $Timeout = 300;
|
145: |
|
146: | |
147: | |
148: | |
149: | |
150: |
|
151: | public $Timelimit = 300;
|
152: |
|
153: | |
154: | |
155: | |
156: |
|
157: | protected $smtp_transaction_id_patterns = array(
|
158: | 'exim' => '/[0-9]{3} OK id=(.*)/',
|
159: | 'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
|
160: | 'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
|
161: | );
|
162: |
|
163: | |
164: | |
165: | |
166: |
|
167: | protected $last_smtp_transaction_id;
|
168: |
|
169: | |
170: | |
171: | |
172: |
|
173: | protected $smtp_conn;
|
174: |
|
175: | |
176: | |
177: | |
178: |
|
179: | protected $error = array(
|
180: | 'error' => '',
|
181: | 'detail' => '',
|
182: | 'smtp_code' => '',
|
183: | 'smtp_code_ex' => ''
|
184: | );
|
185: |
|
186: | |
187: | |
188: | |
189: | |
190: |
|
191: | protected $helo_rply = null;
|
192: |
|
193: | |
194: | |
195: | |
196: | |
197: | |
198: | |
199: | |
200: | |
201: |
|
202: | protected $server_caps = null;
|
203: |
|
204: | |
205: | |
206: | |
207: |
|
208: | protected $last_reply = '';
|
209: |
|
210: | |
211: | |
212: | |
213: | |
214: | |
215: | |
216: | |
217: |
|
218: | protected function edebug($str, $level = 0)
|
219: | {
|
220: | if ($level > $this->do_debug) {
|
221: | return;
|
222: | }
|
223: |
|
224: | if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
|
225: | call_user_func($this->Debugoutput, $str, $level);
|
226: | return;
|
227: | }
|
228: | switch ($this->Debugoutput) {
|
229: | case 'error_log':
|
230: |
|
231: | error_log($str);
|
232: | break;
|
233: | case 'html':
|
234: |
|
235: | echo gmdate('Y-m-d H:i:s') . ' ' . htmlentities(
|
236: | preg_replace('/[\r\n]+/', '', $str),
|
237: | ENT_QUOTES,
|
238: | 'UTF-8'
|
239: | ) . "<br>\n";
|
240: | break;
|
241: | case 'echo':
|
242: | default:
|
243: |
|
244: | $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
|
245: | echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
|
246: | "\n",
|
247: | "\n \t ",
|
248: | trim($str)
|
249: | ) . "\n";
|
250: | }
|
251: | }
|
252: |
|
253: | |
254: | |
255: | |
256: | |
257: | |
258: | |
259: | |
260: | |
261: |
|
262: | public function connect($host, $port = null, $timeout = 30, $options = array())
|
263: | {
|
264: | static $streamok;
|
265: |
|
266: |
|
267: | if (is_null($streamok)) {
|
268: | $streamok = function_exists('stream_socket_client');
|
269: | }
|
270: |
|
271: | $this->setError('');
|
272: |
|
273: | if ($this->connected()) {
|
274: |
|
275: | $this->setError('Already connected to a server');
|
276: | return false;
|
277: | }
|
278: | if (empty($port)) {
|
279: | $port = self::DEFAULT_SMTP_PORT;
|
280: | }
|
281: |
|
282: | $this->edebug(
|
283: | "Connection: opening to $host:$port, timeout=$timeout, options=" .
|
284: | var_export($options, true),
|
285: | self::DEBUG_CONNECTION
|
286: | );
|
287: | $errno = 0;
|
288: | $errstr = '';
|
289: | if ($streamok) {
|
290: | $socket_context = stream_context_create($options);
|
291: | set_error_handler(array($this, 'errorHandler'));
|
292: | $this->smtp_conn = stream_socket_client(
|
293: | $host . ":" . $port,
|
294: | $errno,
|
295: | $errstr,
|
296: | $timeout,
|
297: | STREAM_CLIENT_CONNECT,
|
298: | $socket_context
|
299: | );
|
300: | restore_error_handler();
|
301: | } else {
|
302: |
|
303: | $this->edebug(
|
304: | "Connection: stream_socket_client not available, falling back to fsockopen",
|
305: | self::DEBUG_CONNECTION
|
306: | );
|
307: | set_error_handler(array($this, 'errorHandler'));
|
308: | $this->smtp_conn = fsockopen(
|
309: | $host,
|
310: | $port,
|
311: | $errno,
|
312: | $errstr,
|
313: | $timeout
|
314: | );
|
315: | restore_error_handler();
|
316: | }
|
317: |
|
318: | if (!is_resource($this->smtp_conn)) {
|
319: | $this->setError(
|
320: | 'Failed to connect to server',
|
321: | $errno,
|
322: | $errstr
|
323: | );
|
324: | $this->edebug(
|
325: | 'SMTP ERROR: ' . $this->error['error']
|
326: | . ": $errstr ($errno)",
|
327: | self::DEBUG_CLIENT
|
328: | );
|
329: | return false;
|
330: | }
|
331: | $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
|
332: |
|
333: |
|
334: | if (substr(PHP_OS, 0, 3) != 'WIN') {
|
335: | $max = ini_get('max_execution_time');
|
336: |
|
337: | if ($max != 0 && $timeout > $max) {
|
338: | @set_time_limit($timeout);
|
339: | }
|
340: | stream_set_timeout($this->smtp_conn, $timeout, 0);
|
341: | }
|
342: |
|
343: | $announce = $this->get_lines();
|
344: | $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
|
345: | return true;
|
346: | }
|
347: |
|
348: | |
349: | |
350: | |
351: | |
352: |
|
353: | public function startTLS()
|
354: | {
|
355: | if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
|
356: | return false;
|
357: | }
|
358: |
|
359: |
|
360: | $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
|
361: |
|
362: |
|
363: |
|
364: | if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
|
365: | $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
|
366: | $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
|
367: | }
|
368: |
|
369: |
|
370: | set_error_handler(array($this, 'errorHandler'));
|
371: | $crypto_ok = stream_socket_enable_crypto(
|
372: | $this->smtp_conn,
|
373: | true,
|
374: | $crypto_method
|
375: | );
|
376: | restore_error_handler();
|
377: | return $crypto_ok;
|
378: | }
|
379: |
|
380: | |
381: | |
382: | |
383: | |
384: | |
385: | |
386: | |
387: | |
388: | |
389: | |
390: | |
391: |
|
392: | public function authenticate(
|
393: | $username,
|
394: | $password,
|
395: | $authtype = null,
|
396: | $realm = '',
|
397: | $workstation = '',
|
398: | $OAuth = null
|
399: | ) {
|
400: | if (!$this->server_caps) {
|
401: | $this->setError('Authentication is not allowed before HELO/EHLO');
|
402: | return false;
|
403: | }
|
404: |
|
405: | if (array_key_exists('EHLO', $this->server_caps)) {
|
406: |
|
407: | if (!array_key_exists('AUTH', $this->server_caps)) {
|
408: | $this->setError('Authentication is not allowed at this stage');
|
409: |
|
410: |
|
411: | return false;
|
412: | }
|
413: |
|
414: | self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL);
|
415: | self::edebug(
|
416: | 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
|
417: | self::DEBUG_LOWLEVEL
|
418: | );
|
419: |
|
420: | if (empty($authtype)) {
|
421: | foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN', 'NTLM', 'XOAUTH2') as $method) {
|
422: | if (in_array($method, $this->server_caps['AUTH'])) {
|
423: | $authtype = $method;
|
424: | break;
|
425: | }
|
426: | }
|
427: | if (empty($authtype)) {
|
428: | $this->setError('No supported authentication methods found');
|
429: | return false;
|
430: | }
|
431: | self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
|
432: | }
|
433: |
|
434: | if (!in_array($authtype, $this->server_caps['AUTH'])) {
|
435: | $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
|
436: | return false;
|
437: | }
|
438: | } elseif (empty($authtype)) {
|
439: | $authtype = 'LOGIN';
|
440: | }
|
441: | switch ($authtype) {
|
442: | case 'PLAIN':
|
443: |
|
444: | if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
|
445: | return false;
|
446: | }
|
447: |
|
448: | if (!$this->sendCommand(
|
449: | 'User & Password',
|
450: | base64_encode("\0" . $username . "\0" . $password),
|
451: | 235
|
452: | )
|
453: | ) {
|
454: | return false;
|
455: | }
|
456: | break;
|
457: | case 'LOGIN':
|
458: |
|
459: | if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
|
460: | return false;
|
461: | }
|
462: | if (!$this->sendCommand("Username", base64_encode($username), 334)) {
|
463: | return false;
|
464: | }
|
465: | if (!$this->sendCommand("Password", base64_encode($password), 235)) {
|
466: | return false;
|
467: | }
|
468: | break;
|
469: | case 'XOAUTH2':
|
470: |
|
471: |
|
472: | if (is_null($OAuth)) {
|
473: | return false;
|
474: | }
|
475: | $oauth = $OAuth->getOauth64();
|
476: |
|
477: |
|
478: | if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
|
479: | return false;
|
480: | }
|
481: | break;
|
482: | case 'NTLM':
|
483: | |
484: | |
485: | |
486: | |
487: | |
488: | |
489: | |
490: |
|
491: | require_once 'extras/ntlm_sasl_client.php';
|
492: | $temp = new stdClass;
|
493: | $ntlm_client = new ntlm_sasl_client_class;
|
494: |
|
495: | if (!$ntlm_client->initialize($temp)) {
|
496: | $this->setError($temp->error);
|
497: | $this->edebug(
|
498: | 'You need to enable some modules in your php.ini file: '
|
499: | . $this->error['error'],
|
500: | self::DEBUG_CLIENT
|
501: | );
|
502: | return false;
|
503: | }
|
504: |
|
505: | $msg1 = $ntlm_client->typeMsg1($realm, $workstation);
|
506: |
|
507: | if (!$this->sendCommand(
|
508: | 'AUTH NTLM',
|
509: | 'AUTH NTLM ' . base64_encode($msg1),
|
510: | 334
|
511: | )
|
512: | ) {
|
513: | return false;
|
514: | }
|
515: |
|
516: |
|
517: | $challenge = substr($this->last_reply, 3);
|
518: | $challenge = base64_decode($challenge);
|
519: | $ntlm_res = $ntlm_client->NTLMResponse(
|
520: | substr($challenge, 24, 8),
|
521: | $password
|
522: | );
|
523: |
|
524: | $msg3 = $ntlm_client->typeMsg3(
|
525: | $ntlm_res,
|
526: | $username,
|
527: | $realm,
|
528: | $workstation
|
529: | );
|
530: |
|
531: | return $this->sendCommand('Username', base64_encode($msg3), 235);
|
532: | case 'CRAM-MD5':
|
533: |
|
534: | if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
|
535: | return false;
|
536: | }
|
537: |
|
538: | $challenge = base64_decode(substr($this->last_reply, 4));
|
539: |
|
540: |
|
541: | $response = $username . ' ' . $this->hmac($challenge, $password);
|
542: |
|
543: |
|
544: | return $this->sendCommand('Username', base64_encode($response), 235);
|
545: | default:
|
546: | $this->setError("Authentication method \"$authtype\" is not supported");
|
547: | return false;
|
548: | }
|
549: | return true;
|
550: | }
|
551: |
|
552: | |
553: | |
554: | |
555: | |
556: | |
557: | |
558: | |
559: | |
560: |
|
561: | protected function hmac($data, $key)
|
562: | {
|
563: | if (function_exists('hash_hmac')) {
|
564: | return hash_hmac('md5', $data, $key);
|
565: | }
|
566: |
|
567: |
|
568: |
|
569: |
|
570: |
|
571: |
|
572: |
|
573: |
|
574: |
|
575: | $bytelen = 64;
|
576: | if (strlen($key) > $bytelen) {
|
577: | $key = pack('H*', md5($key));
|
578: | }
|
579: | $key = str_pad($key, $bytelen, chr(0x00));
|
580: | $ipad = str_pad('', $bytelen, chr(0x36));
|
581: | $opad = str_pad('', $bytelen, chr(0x5c));
|
582: | $k_ipad = $key ^ $ipad;
|
583: | $k_opad = $key ^ $opad;
|
584: |
|
585: | return md5($k_opad . pack('H*', md5($k_ipad . $data)));
|
586: | }
|
587: |
|
588: | |
589: | |
590: | |
591: | |
592: |
|
593: | public function connected()
|
594: | {
|
595: | if (is_resource($this->smtp_conn)) {
|
596: | $sock_status = stream_get_meta_data($this->smtp_conn);
|
597: | if ($sock_status['eof']) {
|
598: |
|
599: | $this->edebug(
|
600: | 'SMTP NOTICE: EOF caught while checking if connected',
|
601: | self::DEBUG_CLIENT
|
602: | );
|
603: | $this->close();
|
604: | return false;
|
605: | }
|
606: | return true;
|
607: | }
|
608: | return false;
|
609: | }
|
610: |
|
611: | |
612: | |
613: | |
614: | |
615: | |
616: | |
617: |
|
618: | public function close()
|
619: | {
|
620: | $this->setError('');
|
621: | $this->server_caps = null;
|
622: | $this->helo_rply = null;
|
623: | if (is_resource($this->smtp_conn)) {
|
624: |
|
625: | fclose($this->smtp_conn);
|
626: | $this->smtp_conn = null;
|
627: | $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
|
628: | }
|
629: | }
|
630: |
|
631: | |
632: | |
633: | |
634: | |
635: | |
636: | |
637: | |
638: | |
639: | |
640: | |
641: | |
642: |
|
643: | public function data($msg_data)
|
644: | {
|
645: |
|
646: | if (!$this->sendCommand('DATA', 'DATA', 354)) {
|
647: | return false;
|
648: | }
|
649: |
|
650: | |
651: | |
652: | |
653: | |
654: | |
655: | |
656: |
|
657: |
|
658: |
|
659: | $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data));
|
660: |
|
661: | |
662: | |
663: | |
664: |
|
665: |
|
666: | $field = substr($lines[0], 0, strpos($lines[0], ':'));
|
667: | $in_headers = false;
|
668: | if (!empty($field) && strpos($field, ' ') === false) {
|
669: | $in_headers = true;
|
670: | }
|
671: |
|
672: | foreach ($lines as $line) {
|
673: | $lines_out = array();
|
674: | if ($in_headers and $line == '') {
|
675: | $in_headers = false;
|
676: | }
|
677: |
|
678: |
|
679: | while (isset($line[self::MAX_LINE_LENGTH])) {
|
680: |
|
681: |
|
682: | $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
|
683: |
|
684: | if (!$pos) {
|
685: |
|
686: | $pos = self::MAX_LINE_LENGTH - 1;
|
687: | $lines_out[] = substr($line, 0, $pos);
|
688: | $line = substr($line, $pos);
|
689: | } else {
|
690: |
|
691: | $lines_out[] = substr($line, 0, $pos);
|
692: |
|
693: | $line = substr($line, $pos + 1);
|
694: | }
|
695: |
|
696: | if ($in_headers) {
|
697: | $line = "\t" . $line;
|
698: | }
|
699: | }
|
700: | $lines_out[] = $line;
|
701: |
|
702: |
|
703: | foreach ($lines_out as $line_out) {
|
704: |
|
705: | if (!empty($line_out) and $line_out[0] == '.') {
|
706: | $line_out = '.' . $line_out;
|
707: | }
|
708: | $this->client_send($line_out . self::CRLF);
|
709: | }
|
710: | }
|
711: |
|
712: |
|
713: |
|
714: | $savetimelimit = $this->Timelimit;
|
715: | $this->Timelimit = $this->Timelimit * 2;
|
716: | $result = $this->sendCommand('DATA END', '.', 250);
|
717: | $this->recordLastTransactionID();
|
718: |
|
719: | $this->Timelimit = $savetimelimit;
|
720: | return $result;
|
721: | }
|
722: |
|
723: | |
724: | |
725: | |
726: | |
727: | |
728: | |
729: | |
730: | |
731: | |
732: |
|
733: | public function hello($host = '')
|
734: | {
|
735: |
|
736: | return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
|
737: | }
|
738: |
|
739: | |
740: | |
741: | |
742: | |
743: | |
744: | |
745: | |
746: | |
747: |
|
748: | protected function sendHello($hello, $host)
|
749: | {
|
750: | $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
|
751: | $this->helo_rply = $this->last_reply;
|
752: | if ($noerror) {
|
753: | $this->parseHelloFields($hello);
|
754: | } else {
|
755: | $this->server_caps = null;
|
756: | }
|
757: | return $noerror;
|
758: | }
|
759: |
|
760: | |
761: | |
762: | |
763: | |
764: | |
765: |
|
766: | protected function parseHelloFields($type)
|
767: | {
|
768: | $this->server_caps = array();
|
769: | $lines = explode("\n", $this->helo_rply);
|
770: |
|
771: | foreach ($lines as $n => $s) {
|
772: |
|
773: | $s = trim(substr($s, 4));
|
774: | if (empty($s)) {
|
775: | continue;
|
776: | }
|
777: | $fields = explode(' ', $s);
|
778: | if (!empty($fields)) {
|
779: | if (!$n) {
|
780: | $name = $type;
|
781: | $fields = $fields[0];
|
782: | } else {
|
783: | $name = array_shift($fields);
|
784: | switch ($name) {
|
785: | case 'SIZE':
|
786: | $fields = ($fields ? $fields[0] : 0);
|
787: | break;
|
788: | case 'AUTH':
|
789: | if (!is_array($fields)) {
|
790: | $fields = array();
|
791: | }
|
792: | break;
|
793: | default:
|
794: | $fields = true;
|
795: | }
|
796: | }
|
797: | $this->server_caps[$name] = $fields;
|
798: | }
|
799: | }
|
800: | }
|
801: |
|
802: | |
803: | |
804: | |
805: | |
806: | |
807: | |
808: | |
809: | |
810: | |
811: | |
812: |
|
813: | public function mail($from)
|
814: | {
|
815: | $useVerp = ($this->do_verp ? ' XVERP' : '');
|
816: | return $this->sendCommand(
|
817: | 'MAIL FROM',
|
818: | 'MAIL FROM:<' . $from . '>' . $useVerp,
|
819: | 250
|
820: | );
|
821: | }
|
822: |
|
823: | |
824: | |
825: | |
826: | |
827: | |
828: | |
829: | |
830: |
|
831: | public function quit($close_on_error = true)
|
832: | {
|
833: | $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
|
834: | $err = $this->error;
|
835: | if ($noerror or $close_on_error) {
|
836: | $this->close();
|
837: | $this->error = $err;
|
838: | }
|
839: | return $noerror;
|
840: | }
|
841: |
|
842: | |
843: | |
844: | |
845: | |
846: | |
847: | |
848: | |
849: | |
850: |
|
851: | public function recipient($address)
|
852: | {
|
853: | return $this->sendCommand(
|
854: | 'RCPT TO',
|
855: | 'RCPT TO:<' . $address . '>',
|
856: | array(250, 251)
|
857: | );
|
858: | }
|
859: |
|
860: | |
861: | |
862: | |
863: | |
864: | |
865: | |
866: |
|
867: | public function reset()
|
868: | {
|
869: | return $this->sendCommand('RSET', 'RSET', 250);
|
870: | }
|
871: |
|
872: | |
873: | |
874: | |
875: | |
876: | |
877: | |
878: | |
879: |
|
880: | protected function sendCommand($command, $commandstring, $expect)
|
881: | {
|
882: | if (!$this->connected()) {
|
883: | $this->setError("Called $command without being connected");
|
884: | return false;
|
885: | }
|
886: |
|
887: | if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
|
888: | $this->setError("Command '$command' contained line breaks");
|
889: | return false;
|
890: | }
|
891: | $this->client_send($commandstring . self::CRLF);
|
892: |
|
893: | $this->last_reply = $this->get_lines();
|
894: |
|
895: | $matches = array();
|
896: | if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
|
897: | $code = $matches[1];
|
898: | $code_ex = (count($matches) > 2 ? $matches[2] : null);
|
899: |
|
900: | $detail = preg_replace(
|
901: | "/{$code}[ -]" .
|
902: | ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . "/m",
|
903: | '',
|
904: | $this->last_reply
|
905: | );
|
906: | } else {
|
907: |
|
908: | $code = substr($this->last_reply, 0, 3);
|
909: | $code_ex = null;
|
910: | $detail = substr($this->last_reply, 4);
|
911: | }
|
912: |
|
913: | $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
|
914: |
|
915: | if (!in_array($code, (array)$expect)) {
|
916: | $this->setError(
|
917: | "$command command failed",
|
918: | $detail,
|
919: | $code,
|
920: | $code_ex
|
921: | );
|
922: | $this->edebug(
|
923: | 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
|
924: | self::DEBUG_CLIENT
|
925: | );
|
926: | return false;
|
927: | }
|
928: |
|
929: | $this->setError('');
|
930: | return true;
|
931: | }
|
932: |
|
933: | |
934: | |
935: | |
936: | |
937: | |
938: | |
939: | |
940: | |
941: | |
942: | |
943: | |
944: | |
945: |
|
946: | public function sendAndMail($from)
|
947: | {
|
948: | return $this->sendCommand('SAML', "SAML FROM:$from", 250);
|
949: | }
|
950: |
|
951: | |
952: | |
953: | |
954: | |
955: | |
956: |
|
957: | public function verify($name)
|
958: | {
|
959: | return $this->sendCommand('VRFY', "VRFY $name", array(250, 251));
|
960: | }
|
961: |
|
962: | |
963: | |
964: | |
965: | |
966: | |
967: |
|
968: | public function noop()
|
969: | {
|
970: | return $this->sendCommand('NOOP', 'NOOP', 250);
|
971: | }
|
972: |
|
973: | |
974: | |
975: | |
976: | |
977: | |
978: | |
979: | |
980: | |
981: |
|
982: | public function turn()
|
983: | {
|
984: | $this->setError('The SMTP TURN command is not implemented');
|
985: | $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
|
986: | return false;
|
987: | }
|
988: |
|
989: | |
990: | |
991: | |
992: | |
993: | |
994: |
|
995: | public function client_send($data)
|
996: | {
|
997: | $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
|
998: | set_error_handler(array($this, 'errorHandler'));
|
999: | $result = fwrite($this->smtp_conn, $data);
|
1000: | restore_error_handler();
|
1001: | return $result;
|
1002: | }
|
1003: |
|
1004: | |
1005: | |
1006: | |
1007: | |
1008: |
|
1009: | public function getError()
|
1010: | {
|
1011: | return $this->error;
|
1012: | }
|
1013: |
|
1014: | |
1015: | |
1016: | |
1017: | |
1018: |
|
1019: | public function getServerExtList()
|
1020: | {
|
1021: | return $this->server_caps;
|
1022: | }
|
1023: |
|
1024: | |
1025: | |
1026: | |
1027: | |
1028: | |
1029: | |
1030: | |
1031: | |
1032: | |
1033: | |
1034: | |
1035: | |
1036: | |
1037: | |
1038: | |
1039: | |
1040: | |
1041: | |
1042: |
|
1043: | public function getServerExt($name)
|
1044: | {
|
1045: | if (!$this->server_caps) {
|
1046: | $this->setError('No HELO/EHLO was sent');
|
1047: | return null;
|
1048: | }
|
1049: |
|
1050: |
|
1051: | if (!array_key_exists($name, $this->server_caps)) {
|
1052: | if ($name == 'HELO') {
|
1053: | return $this->server_caps['EHLO'];
|
1054: | }
|
1055: | if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) {
|
1056: | return false;
|
1057: | }
|
1058: | $this->setError('HELO handshake was used. Client knows nothing about server extensions');
|
1059: | return null;
|
1060: | }
|
1061: |
|
1062: | return $this->server_caps[$name];
|
1063: | }
|
1064: |
|
1065: | |
1066: | |
1067: | |
1068: | |
1069: |
|
1070: | public function getLastReply()
|
1071: | {
|
1072: | return $this->last_reply;
|
1073: | }
|
1074: |
|
1075: | |
1076: | |
1077: | |
1078: | |
1079: | |
1080: | |
1081: | |
1082: | |
1083: |
|
1084: | protected function get_lines()
|
1085: | {
|
1086: |
|
1087: | if (!is_resource($this->smtp_conn)) {
|
1088: | return '';
|
1089: | }
|
1090: | $data = '';
|
1091: | $endtime = 0;
|
1092: | stream_set_timeout($this->smtp_conn, $this->Timeout);
|
1093: | if ($this->Timelimit > 0) {
|
1094: | $endtime = time() + $this->Timelimit;
|
1095: | }
|
1096: | while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
|
1097: | $str = @fgets($this->smtp_conn, 515);
|
1098: | $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
|
1099: | $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL);
|
1100: | $data .= $str;
|
1101: |
|
1102: |
|
1103: |
|
1104: | if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) {
|
1105: | break;
|
1106: | }
|
1107: |
|
1108: | $info = stream_get_meta_data($this->smtp_conn);
|
1109: | if ($info['timed_out']) {
|
1110: | $this->edebug(
|
1111: | 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
|
1112: | self::DEBUG_LOWLEVEL
|
1113: | );
|
1114: | break;
|
1115: | }
|
1116: |
|
1117: | if ($endtime and time() > $endtime) {
|
1118: | $this->edebug(
|
1119: | 'SMTP -> get_lines(): timelimit reached (' .
|
1120: | $this->Timelimit . ' sec)',
|
1121: | self::DEBUG_LOWLEVEL
|
1122: | );
|
1123: | break;
|
1124: | }
|
1125: | }
|
1126: | return $data;
|
1127: | }
|
1128: |
|
1129: | |
1130: | |
1131: | |
1132: |
|
1133: | public function setVerp($enabled = false)
|
1134: | {
|
1135: | $this->do_verp = $enabled;
|
1136: | }
|
1137: |
|
1138: | |
1139: | |
1140: | |
1141: |
|
1142: | public function getVerp()
|
1143: | {
|
1144: | return $this->do_verp;
|
1145: | }
|
1146: |
|
1147: | |
1148: | |
1149: | |
1150: | |
1151: | |
1152: | |
1153: |
|
1154: | protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
|
1155: | {
|
1156: | $this->error = array(
|
1157: | 'error' => $message,
|
1158: | 'detail' => $detail,
|
1159: | 'smtp_code' => $smtp_code,
|
1160: | 'smtp_code_ex' => $smtp_code_ex
|
1161: | );
|
1162: | }
|
1163: |
|
1164: | |
1165: | |
1166: | |
1167: |
|
1168: | public function setDebugOutput($method = 'echo')
|
1169: | {
|
1170: | $this->Debugoutput = $method;
|
1171: | }
|
1172: |
|
1173: | |
1174: | |
1175: | |
1176: |
|
1177: | public function getDebugOutput()
|
1178: | {
|
1179: | return $this->Debugoutput;
|
1180: | }
|
1181: |
|
1182: | |
1183: | |
1184: | |
1185: |
|
1186: | public function setDebugLevel($level = 0)
|
1187: | {
|
1188: | $this->do_debug = $level;
|
1189: | }
|
1190: |
|
1191: | |
1192: | |
1193: | |
1194: |
|
1195: | public function getDebugLevel()
|
1196: | {
|
1197: | return $this->do_debug;
|
1198: | }
|
1199: |
|
1200: | |
1201: | |
1202: | |
1203: |
|
1204: | public function setTimeout($timeout = 0)
|
1205: | {
|
1206: | $this->Timeout = $timeout;
|
1207: | }
|
1208: |
|
1209: | |
1210: | |
1211: | |
1212: |
|
1213: | public function getTimeout()
|
1214: | {
|
1215: | return $this->Timeout;
|
1216: | }
|
1217: |
|
1218: | |
1219: | |
1220: | |
1221: | |
1222: | |
1223: | |
1224: |
|
1225: | protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
|
1226: | {
|
1227: | $notice = 'Connection failed.';
|
1228: | $this->setError(
|
1229: | $notice,
|
1230: | $errno,
|
1231: | $errmsg
|
1232: | );
|
1233: | $this->edebug(
|
1234: | $notice . ' Error #' . $errno . ': ' . $errmsg . " [$errfile line $errline]",
|
1235: | self::DEBUG_CONNECTION
|
1236: | );
|
1237: | }
|
1238: |
|
1239: | |
1240: | |
1241: | |
1242: | |
1243: | |
1244: | |
1245: | |
1246: |
|
1247: | protected function recordLastTransactionID()
|
1248: | {
|
1249: | $reply = $this->getLastReply();
|
1250: |
|
1251: | if (empty($reply)) {
|
1252: | $this->last_smtp_transaction_id = null;
|
1253: | } else {
|
1254: | $this->last_smtp_transaction_id = false;
|
1255: | foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
|
1256: | if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
|
1257: | $this->last_smtp_transaction_id = $matches[1];
|
1258: | }
|
1259: | }
|
1260: | }
|
1261: |
|
1262: | return $this->last_smtp_transaction_id;
|
1263: | }
|
1264: |
|
1265: | |
1266: | |
1267: | |
1268: | |
1269: | |
1270: | |
1271: |
|
1272: | public function getLastTransactionID()
|
1273: | {
|
1274: | return $this->last_smtp_transaction_id;
|
1275: | }
|
1276: | }
|
1277: | |