PageRenderTime 57ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/includes/emailer.php

http://github.com/MightyGorgon/icy_phoenix
PHP | 1670 lines | 1147 code | 253 blank | 270 comment | 186 complexity | 83bf1018056fc5b917f2e9e36e290e5a MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /**
  3. *
  4. * @package Icy Phoenix
  5. * @version $Id$
  6. * @copyright (c) 2008 Icy Phoenix
  7. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  8. *
  9. */
  10. /**
  11. *
  12. * @Icy Phoenix is based on phpBB
  13. * @copyright (c) 2008 phpBB Group
  14. *
  15. */
  16. /*
  17. * Emailer class
  18. */
  19. class emailer
  20. {
  21. var $vars;
  22. var $from;
  23. var $replyto;
  24. var $subject;
  25. var $msg;
  26. var $addresses = array();
  27. var $extra_headers = array();
  28. var $mail_priority = MAIL_NORMAL_PRIORITY;
  29. var $use_queue = true;
  30. var $use_smtp = false;
  31. var $tpl_obj = NULL;
  32. var $tpl_msg = array();
  33. var $eol = "\n";
  34. /**
  35. * Initialize the class
  36. */
  37. function __construct($use_queue = true)
  38. {
  39. $this->use_queue = (empty($config['email_package_size'])) ? false : $use_queue;
  40. $this->reset();
  41. // Determine EOL character (\n for UNIX, \r\n for Windows and \r for Mac)
  42. $this->eol = (!defined('PHP_EOL')) ? (($eol = strtolower(substr(PHP_OS, 0, 3))) == 'win') ? "\r\n" : (($eol == 'mac') ? "\r" : "\n") : PHP_EOL;
  43. $this->eol = (!$this->eol) ? "\n" : $this->eol;
  44. }
  45. /**
  46. * Resets all the data (address, template file, etc etc) to default
  47. */
  48. function reset()
  49. {
  50. global $config;
  51. $this->use_smtp = $config['smtp_delivery'];
  52. $this->addresses = array();
  53. $this->extra_headers = array();
  54. $this->vars = '';
  55. $this->from = '';
  56. $this->replyto = '';
  57. $this->mail_priority = MAIL_NORMAL_PRIORITY;
  58. $this->msg = '';
  59. }
  60. /**
  61. * Sets an email address to send to
  62. */
  63. function to($address, $realname = '')
  64. {
  65. $address = trim($address);
  66. $realname = trim($realname);
  67. if (empty($address))
  68. {
  69. return;
  70. }
  71. $pos = isset($this->addresses['to']) ? sizeof($this->addresses['to']) : 0;
  72. $this->addresses['to'][$pos]['email'] = $address;
  73. // If empty sendmail_path on windows, PHP changes the to line
  74. if (!$this->use_smtp && (DIRECTORY_SEPARATOR == '\\'))
  75. {
  76. $this->addresses['to'][$pos]['name'] = '';
  77. }
  78. else
  79. {
  80. $this->addresses['to'][$pos]['name'] = $realname;
  81. }
  82. }
  83. /**
  84. * Sets an email address to send to
  85. */
  86. function email_address($address)
  87. {
  88. $this->to($address);
  89. }
  90. /**
  91. * Sets a cc address to send to
  92. */
  93. function cc($address, $realname = '')
  94. {
  95. $address = trim($address);
  96. $realname = trim($realname);
  97. if (empty($address))
  98. {
  99. return;
  100. }
  101. $pos = isset($this->addresses['cc']) ? sizeof($this->addresses['cc']) : 0;
  102. $this->addresses['cc'][$pos]['email'] = $address;
  103. $this->addresses['cc'][$pos]['name'] = $realname;
  104. }
  105. /**
  106. * Sets a bcc address to send to
  107. */
  108. function bcc($address, $realname = '')
  109. {
  110. $address = trim($address);
  111. $realname = trim($realname);
  112. if (empty($address))
  113. {
  114. return;
  115. }
  116. $pos = isset($this->addresses['bcc']) ? sizeof($this->addresses['bcc']) : 0;
  117. $this->addresses['bcc'][$pos]['email'] = $address;
  118. $this->addresses['bcc'][$pos]['name'] = $realname;
  119. }
  120. /**
  121. * Sets a im contact to send to
  122. */
  123. function im($address, $realname = '')
  124. {
  125. // IM-Addresses could be empty
  126. $address = trim($address);
  127. $realname = trim($realname);
  128. if (empty($address))
  129. {
  130. return;
  131. }
  132. $pos = isset($this->addresses['im']) ? sizeof($this->addresses['im']) : 0;
  133. $this->addresses['im'][$pos]['uid'] = $address;
  134. $this->addresses['im'][$pos]['name'] = $realname;
  135. }
  136. /**
  137. * Set the reply to address
  138. */
  139. function replyto($address)
  140. {
  141. $this->replyto = trim($address);
  142. }
  143. /**
  144. * Set the from address
  145. */
  146. function from($address)
  147. {
  148. $this->from = trim($address);
  149. }
  150. /**
  151. * set up subject for mail
  152. */
  153. function set_subject($subject = '')
  154. {
  155. $this->subject = trim(preg_replace('#[\n\r]+#s', '', $subject));
  156. }
  157. /**
  158. * Set the email priority
  159. */
  160. function set_mail_priority($priority = MAIL_NORMAL_PRIORITY)
  161. {
  162. $this->mail_priority = $priority;
  163. }
  164. /**
  165. * set up extra mail headers
  166. */
  167. function headers($headers)
  168. {
  169. $this->extra_headers[] = trim($headers);
  170. }
  171. /**
  172. * Return email header
  173. */
  174. function build_header($to = '', $cc = '', $bcc = '')
  175. {
  176. global $config, $lang;
  177. // Force to UTF-8
  178. //$encoding_charset = !empty($lang['ENCODING']) ? $lang['ENCODING'] : 'UTF-8';
  179. $encoding_charset = 'UTF-8';
  180. // We could use keys here, but we won't do this for 3.0.x to retain backwards compatibility
  181. $headers = array();
  182. $this->from = !empty($this->from) ? $this->from : '<' . trim($config['board_email']) . '>';
  183. $this->replyto = !empty($this->replyto) ? $this->replyto : '<' . trim($config['board_email']) . '>';
  184. $this->mail_priority = !empty($this->mail_priority) ? $this->mail_priority : MAIL_LOW_PRIORITY;
  185. $headers[] = 'From: ' . $this->from;
  186. if (!empty($cc))
  187. {
  188. $headers[] = 'Cc: ' . $cc;
  189. }
  190. if (!empty($bcc))
  191. {
  192. $headers[] = 'Bcc: ' . $bcc;
  193. }
  194. $headers[] = 'Reply-To: ' . $this->replyto;
  195. $headers[] = 'Return-Path: <' . $config['board_email'] . '>';
  196. $headers[] = 'Sender: <' . $config['board_email'] . '>';
  197. $headers[] = 'MIME-Version: 1.0';
  198. $headers[] = 'Message-ID: <' . md5(unique_id(time())) . '@' . $config['server_name'] . '>';
  199. $headers[] = 'Date: ' . date('r', time());
  200. $headers[] = 'Content-Type: text/' . (!empty($config['html_email']) ? 'html' : 'plain') . '; charset=' . $encoding_charset; // format=flowed
  201. $headers[] = 'Content-Transfer-Encoding: 8bit'; // 7bit
  202. $headers[] = 'X-Priority: ' . $this->mail_priority;
  203. $headers[] = 'X-MSMail-Priority: ' . (($this->mail_priority == MAIL_LOW_PRIORITY) ? 'Low' : (($this->mail_priority == MAIL_NORMAL_PRIORITY) ? 'Normal' : 'High'));
  204. $headers[] = 'X-Mailer: Icy Phoenix';
  205. $headers[] = 'X-MimeOLE: Icy Phoenix';
  206. $headers[] = 'X-Icy-Phoenix-Origin: icyphoenix://' . str_replace(array('http://', 'https://'), array('', ''), create_server_url());
  207. if (sizeof($this->extra_headers))
  208. {
  209. $headers = array_merge($headers, $this->extra_headers);
  210. }
  211. return $headers;
  212. }
  213. /**
  214. * Assigns vars
  215. */
  216. function assign_vars($vars)
  217. {
  218. if (empty($vars) || !is_array($vars))
  219. {
  220. return;
  221. }
  222. $this->vars = (empty($this->vars)) ? $vars : array_merge($this->vars, $vars);
  223. }
  224. /**
  225. * Send the email
  226. */
  227. function send($method = NOTIFY_EMAIL, $break = false)
  228. {
  229. global $db, $config, $lang;
  230. if (defined('EMAILER_DISABLED') && EMAILER_DISABLED)
  231. {
  232. return false;
  233. }
  234. /*
  235. if (empty($config['email_enable']))
  236. {
  237. return false;
  238. }
  239. */
  240. // Addresses to send to?
  241. if (empty($this->addresses) || (empty($this->addresses['to']) && empty($this->addresses['cc']) && empty($this->addresses['bcc'])))
  242. {
  243. // Send was successful. ;)
  244. return true;
  245. }
  246. // Mighty Gorgon: force queue to false until we are sure that everything is working fine
  247. $use_queue = false;
  248. /*
  249. if ($config['email_package_size'] && $this->use_queue)
  250. {
  251. if (empty($this->queue))
  252. {
  253. $this->queue = new queue();
  254. $this->queue->init('email', $config['email_package_size']);
  255. }
  256. $use_queue = true;
  257. }
  258. */
  259. $encode_eol = !empty($config['smtp_delivery']) ? "\r\n" : $this->eol;
  260. // Old Icy Phoenix Code - BEGIN
  261. // Note: this is for {} parsing
  262. // Escape all quotes, else the eval will fail.
  263. $this->msg = str_replace("'", "\'", $this->msg);
  264. $this->msg = preg_replace('#\{([a-z0-9\-_]*?)\}#is', "' . $\\1 . '", $this->msg);
  265. // Set vars
  266. reset($this->vars);
  267. while(list($key, $val) = each($this->vars))
  268. {
  269. ${$key} = $val;
  270. }
  271. eval("\$this->msg = '$this->msg';");
  272. // Clear vars
  273. reset($this->vars);
  274. while(list($key, $val) = each($this->vars))
  275. {
  276. unset(${$key});
  277. }
  278. // We now try and pull a subject from the email body ... if it exists do this here because the subject may contain a variable
  279. $drop_header = '';
  280. $this->subject = (($this->subject != '') ? $this->subject : 'No Subject');
  281. $match = array();
  282. if (preg_match('#^(Subject:(.*?))$#m', $this->msg, $match))
  283. {
  284. $this->subject = (trim($match[2]) != '') ? trim($match[2]) : $this->subject;
  285. $drop_header .= '[\r\n]*?' . preg_quote($match[1], '#');
  286. }
  287. // Force to UTF-8
  288. //$encoding_charset = !empty($lang['ENCODING']) ? trim($lang['ENCODING']) : 'UTF-8';
  289. $encoding_charset = 'UTF-8';
  290. $this->encoding = $encoding_charset;
  291. if (preg_match('#^(Charset:(.*?))$#m', $this->msg, $match))
  292. {
  293. $this->encoding = (trim($match[2]) != '') ? trim($match[2]) : $encoding_charset;
  294. $drop_header .= '[\r\n]*?' . preg_quote($match[1], '#');
  295. }
  296. if (!empty($drop_header))
  297. {
  298. $this->msg = trim(preg_replace('#' . $drop_header . '#s', '', $this->msg));
  299. }
  300. // Old Icy Phoenix Code - END
  301. // Build to, cc and bcc strings
  302. $to = '';
  303. $cc = '';
  304. $bcc = '';
  305. foreach ($this->addresses as $type => $address_ary)
  306. {
  307. if ($type == 'im')
  308. {
  309. continue;
  310. }
  311. foreach ($address_ary as $which_ary)
  312. {
  313. ${$type} .= ((${$type} != '') ? ', ' : '') . (!empty($which_ary['name']) ? (mail_encode($which_ary['name'], $encode_eol) . ' <' . $which_ary['email'] . '>') : $which_ary['email']);
  314. }
  315. }
  316. // Build header
  317. $headers = $this->build_header($to, $cc, $bcc);
  318. // Send message ...
  319. if (!$use_queue)
  320. {
  321. $empty_to_header = empty($to) ? true : false;
  322. $mail_to = $empty_to_header ? ($config['sendmail_fix'] ? ' ' : 'Undisclosed-recipients:;') : $to;
  323. $err_msg = '';
  324. if ($this->use_smtp)
  325. {
  326. $result = smtpmail($this->addresses, mail_encode($this->subject), wordwrap(utf8_wordwrap($this->msg), 997, "\n", true), $err_msg, $headers);
  327. // Old Version
  328. //$result = smtpmail($to, $this->subject, $this->msg, $this->extra_headers);
  329. }
  330. else
  331. {
  332. $result = phpbb_mail($mail_to, $this->subject, $this->msg, $headers, $this->eol, $err_msg);
  333. // Old Version
  334. /*
  335. $result = @mail($mail_to, $this->subject, preg_replace("#(?<!\r)\n#s", "\n", $this->msg), $this->extra_headers);
  336. if (!$result && !$config['sendmail_fix'] && $empty_to_header)
  337. {
  338. $to = ' ';
  339. set_config('sendmail_fix', 1);
  340. $result = @mail($mail_to, $this->subject, preg_replace("#(?<!\r)\n#s", "\n", $this->msg), $this->extra_headers);
  341. }
  342. */
  343. }
  344. // Did it work?
  345. if (!$result && empty($config['disable_email_error']))
  346. {
  347. $this->error('EMAIL', $err_msg);
  348. return false;
  349. }
  350. }
  351. else
  352. {
  353. $this->queue->put('email', array(
  354. 'to' => $to,
  355. 'addresses' => $this->addresses,
  356. 'subject' => $this->subject,
  357. 'msg' => $this->msg,
  358. 'headers' => $headers
  359. )
  360. );
  361. }
  362. return true;
  363. }
  364. /**
  365. * Selects which template to use
  366. */
  367. function use_template($template_file, $template_lang = '', $no_template = false, $plugin_path = '')
  368. {
  369. global $config;
  370. if (trim($template_file) == '')
  371. {
  372. message_die(GENERAL_ERROR, 'No template file set', '', __LINE__, __FILE__);
  373. }
  374. if (trim($template_lang) == '')
  375. {
  376. $template_lang = $config['default_lang'];
  377. }
  378. $email_template_path = IP_ROOT_PATH . (!empty($plugin_path) ? $plugin_path : '');
  379. $email_lang_folder = 'language/lang_' . $template_lang . '/';
  380. $email_format_folder = 'email/' . (!empty($config['html_email']) ? 'html/' : 'txt/');
  381. if (empty($this->tpl_msg[$template_lang . $template_file]))
  382. {
  383. $tpl_file = $email_template_path . $email_lang_folder . $email_format_folder . $template_file . '.tpl';
  384. if (!@file_exists(@phpbb_realpath($tpl_file)))
  385. {
  386. // Try to force English!
  387. $email_lang_folder = 'language/lang_english/';
  388. $tpl_file = $email_template_path . $email_lang_folder . $email_format_folder . $template_file . '.tpl';
  389. if (!@file_exists(@phpbb_realpath($tpl_file)))
  390. {
  391. message_die(GENERAL_ERROR, 'Could not find email template file :: ' . $template_file, '', __LINE__, __FILE__);
  392. }
  393. }
  394. if (!($fd = @fopen($tpl_file, 'r')))
  395. {
  396. message_die(GENERAL_ERROR, 'Failed opening template file :: ' . $tpl_file, '', __LINE__, __FILE__);
  397. }
  398. $this->tpl_msg[$template_lang . $template_file] = @fread($fd, @filesize($tpl_file));
  399. @fclose($fd);
  400. }
  401. if (!empty($config['html_email']))
  402. {
  403. $mail_header = '';
  404. $mail_footer = '';
  405. if (!$no_template)
  406. {
  407. // We don't check here if the file exists for the same lang, because we already checked above and switched to English if needed
  408. // Also we use here IP_ROOT_PATH and not the full path, since header is only in root
  409. $tpl_header = IP_ROOT_PATH . $email_lang_folder . $email_format_folder . 'html_mail_header.tpl';
  410. if (!($fd = @fopen($tpl_header, 'r')))
  411. {
  412. message_die(GENERAL_ERROR, 'Failed opening template file :: ' . $tpl_header, '', __LINE__, __FILE__);
  413. }
  414. $mail_header = fread($fd, filesize($tpl_header));
  415. fclose($fd);
  416. // Mighty Gorgon - Add Server URL - BEGIN
  417. $server_url = create_server_url();
  418. $mail_header = str_replace('{ROOT}', $server_url, $mail_header);
  419. $mail_header = str_replace('{SITENAME}', $config['sitename'], $mail_header);
  420. // Mighty Gorgon - Add Server URL - END
  421. $tpl_footer = IP_ROOT_PATH . $email_lang_folder . $email_format_folder . 'html_mail_footer.tpl';
  422. if (!($fd = @fopen($tpl_footer, 'r')))
  423. {
  424. message_die(GENERAL_ERROR, 'Failed opening template file :: ' . $tpl_footer, '', __LINE__, __FILE__);
  425. }
  426. $mail_footer = @fread($fd, @filesize($tpl_footer));
  427. @fclose($fd);
  428. }
  429. $this->msg = $mail_header . $this->tpl_msg[$template_lang . $template_file] . $mail_footer;
  430. }
  431. else
  432. {
  433. $this->msg = $this->tpl_msg[$template_lang . $template_file];
  434. }
  435. return true;
  436. }
  437. /*
  438. * Encodes the given string for proper display for this encoding... nabbed from php.net and modified. There is an alternative encoding method which
  439. * may produce lesd output but it's questionable as to its worth in this scenario IMO
  440. */
  441. function encode($str)
  442. {
  443. if ($this->encoding == '')
  444. {
  445. return $str;
  446. }
  447. // define start delimimter, end delimiter and spacer
  448. $end = "?=";
  449. $start = "=?$this->encoding?B?";
  450. $spacer = "$end\r\n $start";
  451. // determine length of encoded text within chunks and ensure length is even
  452. $length = 75 - strlen($start) - strlen($end);
  453. $length = floor($length / 2) * 2;
  454. // encode the string and split it into chunks with spacers after each chunk
  455. $str = chunk_split(base64_encode($str), $length, $spacer);
  456. // remove trailing spacer and add start and end delimiters
  457. $str = preg_replace('#' . preg_quote($spacer, '#') . '$#', '', $str);
  458. return $start . $str . $end;
  459. }
  460. /**
  461. * Save to queue
  462. */
  463. function save_queue()
  464. {
  465. global $config;
  466. if ($config['email_package_size'] && $this->use_queue && !empty($this->queue))
  467. {
  468. $this->queue->save();
  469. return;
  470. }
  471. }
  472. /**
  473. * Add error message to log
  474. */
  475. function error($type, $msg)
  476. {
  477. global $config, $user, $lang;
  478. /*
  479. // Session doesn't exist, create it
  480. if (!isset($user->session_id) || $user->session_id === '')
  481. {
  482. $user->session_begin();
  483. }
  484. */
  485. $calling_page = (!empty($_SERVER['SCRIPT_NAME'])) ? $_SERVER['SCRIPT_NAME'] : $_ENV['SCRIPT_NAME'];
  486. $message = '';
  487. switch ($type)
  488. {
  489. case 'EMAIL':
  490. $message = '<strong>EMAIL/' . (($config['smtp_delivery']) ? 'SMTP' : 'PHP/' . $config['email_function_name'] . '()') . '</strong>';
  491. break;
  492. default:
  493. $message = "<strong>$type</strong>";
  494. break;
  495. }
  496. $message .= '<br /><em>' . htmlspecialchars($calling_page) . '</em><br /><br />' . $msg . '<br />';
  497. //add_log('critical', 'LOG_ERROR_' . $type, $message);
  498. message_die(GENERAL_ERROR, '<strong>Failed sending email</strong><br />' . $message, '', __LINE__, __FILE__);
  499. }
  500. // Attach files via MIME.
  501. function attachFile($filename, $mimetype = "application/octet-stream", $szFromAddress, $szFilenameToDisplay)
  502. {
  503. global $lang;
  504. // Force to UTF-8
  505. //$encoding_charset = !empty($lang['ENCODING']) ? $lang['ENCODING'] : 'UTF-8';
  506. $encoding_charset = 'UTF-8';
  507. $mime_boundary = "--==================_846811060==_";
  508. $this->msg = '--' . $mime_boundary . "\nContent-Type: text/html;\n\tcharset=\"" . $encoding_charset . "\"\n\n" . $this->msg;
  509. if ($mime_filename)
  510. {
  511. $filename = $mime_filename;
  512. $encoded = $this->encode_file($filename);
  513. }
  514. $fd = fopen($filename, "r");
  515. $contents = fread($fd, filesize($filename));
  516. $this->mimeOut = "--" . $mime_boundary . "\n";
  517. $this->mimeOut .= "Content-Type: " . $mimetype . ";\n\tname=\"$szFilenameToDisplay\"\n";
  518. $this->mimeOut .= "Content-Transfer-Encoding: quoted-printable\n";
  519. $this->mimeOut .= "Content-Disposition: attachment;\n\tfilename=\"$szFilenameToDisplay\"\n\n";
  520. if ( $mimetype == "message/rfc822" )
  521. {
  522. $this->mimeOut .= "From: ".$szFromAddress."\n";
  523. $this->mimeOut .= "To: ".$this->emailAddress."\n";
  524. $this->mimeOut .= "Date: " . gmdate("D, d M Y H:i:s") . " UT\n";
  525. $this->mimeOut .= "Reply-To:".$szFromAddress."\n";
  526. $this->mimeOut .= "Subject: ".$this->mailSubject."\n";
  527. $this->mimeOut .= "X-Mailer: PHP/" . phpversion()."\n";
  528. $this->mimeOut .= "MIME-Version: 1.0\n";
  529. }
  530. $this->mimeOut .= $contents."\n";
  531. $this->mimeOut .= "--" . $mime_boundary . "--" . "\n";
  532. return $out;
  533. // added -- to notify email client attachment is done
  534. }
  535. function getMimeHeaders($filename, $mime_filename="")
  536. {
  537. $mime_boundary = "--==================_846811060==_";
  538. if ($mime_filename)
  539. {
  540. $filename = $mime_filename;
  541. }
  542. $out = "MIME-Version: 1.0\n";
  543. $out .= "Content-Type: multipart/mixed;\n\tboundary=\"$mime_boundary\"\n\n";
  544. $out .= "This message is in MIME format. Since your mail reader does not understand\n";
  545. $out .= "this format, some or all of this message may not be legible.";
  546. return $out;
  547. }
  548. //
  549. // Split string by RFC 2045 semantics (76 chars per line, end with \r\n).
  550. //
  551. function myChunkSplit($str)
  552. {
  553. $stmp = $str;
  554. $len = strlen($stmp);
  555. $out = "";
  556. while ($len > 0)
  557. {
  558. if ($len >= 76)
  559. {
  560. $out .= substr($stmp, 0, 76) . "\r\n";
  561. $stmp = substr($stmp, 76);
  562. $len = $len - 76;
  563. }
  564. else
  565. {
  566. $out .= $stmp . "\r\n";
  567. $stmp = "";
  568. $len = 0;
  569. }
  570. }
  571. return $out;
  572. }
  573. // Split the specified file up into a string and return it
  574. function encode_file($sourcefile)
  575. {
  576. if (is_readable(@phpbb_realpath($sourcefile)))
  577. {
  578. $fd = fopen($sourcefile, "r");
  579. $contents = fread($fd, filesize($sourcefile));
  580. $encoded = $this->myChunkSplit(base64_encode($contents));
  581. fclose($fd);
  582. }
  583. return $encoded;
  584. }
  585. } // class emailer
  586. /**
  587. * handling email and jabber queue
  588. * @package phpBB3
  589. */
  590. class queue
  591. {
  592. var $data = array();
  593. var $queue_data = array();
  594. var $package_size = 0;
  595. var $cache_file = '';
  596. var $eol = "\n";
  597. /**
  598. * constructor
  599. */
  600. function __construct()
  601. {
  602. $this->data = array();
  603. $this->cache_file = IP_ROOT_PATH . 'cache/queue.' . PHP_EXT;
  604. // Determine EOL character (\n for UNIX, \r\n for Windows and \r for Mac)
  605. $this->eol = (!defined('PHP_EOL')) ? (($eol = strtolower(substr(PHP_OS, 0, 3))) == 'win') ? "\r\n" : (($eol == 'mac') ? "\r" : "\n") : PHP_EOL;
  606. $this->eol = (!$this->eol) ? "\n" : $this->eol;
  607. }
  608. /**
  609. * Init a queue object
  610. */
  611. function init($object, $package_size)
  612. {
  613. $this->data[$object] = array();
  614. $this->data[$object]['package_size'] = $package_size;
  615. $this->data[$object]['data'] = array();
  616. }
  617. /**
  618. * Put object in queue
  619. */
  620. function put($object, $scope)
  621. {
  622. $this->data[$object]['data'][] = $scope;
  623. }
  624. /**
  625. * Process queue
  626. * Using lock file
  627. */
  628. function process()
  629. {
  630. global $db, $config, $lang;
  631. set_config('last_queue_run', time(), true);
  632. // Delete stale lock file
  633. if (file_exists($this->cache_file . '.lock') && !file_exists($this->cache_file))
  634. {
  635. @unlink($this->cache_file . '.lock');
  636. return;
  637. }
  638. if (!file_exists($this->cache_file) || (file_exists($this->cache_file . '.lock') && filemtime($this->cache_file) > time() - $config['queue_interval']))
  639. {
  640. return;
  641. }
  642. $fp = @fopen($this->cache_file . '.lock', 'wb');
  643. fclose($fp);
  644. @chmod($this->cache_file . '.lock', 0777);
  645. include($this->cache_file);
  646. foreach ($this->queue_data as $object => $data_ary)
  647. {
  648. @set_time_limit(0);
  649. if (!isset($data_ary['package_size']))
  650. {
  651. $data_ary['package_size'] = 0;
  652. }
  653. $package_size = $data_ary['package_size'];
  654. $num_items = (!$package_size || sizeof($data_ary['data']) < $package_size) ? sizeof($data_ary['data']) : $package_size;
  655. // If the amount of emails to be sent is way more than package_size than we need to increase it to prevent backlogs...
  656. if (sizeof($data_ary['data']) > $package_size * 2.5)
  657. {
  658. $num_items = sizeof($data_ary['data']);
  659. }
  660. switch ($object)
  661. {
  662. case 'email':
  663. // Delete the email queued objects if mailing is disabled
  664. if (!$config['email_enable'])
  665. {
  666. unset($this->queue_data['email']);
  667. continue 2;
  668. }
  669. break;
  670. case 'jabber':
  671. if (!$config['jab_enable'])
  672. {
  673. unset($this->queue_data['jabber']);
  674. continue 2;
  675. }
  676. include_once(IP_ROOT_PATH . 'includes/functions_jabber.' . PHP_EXT);
  677. $this->jabber = new jabber($config['jab_host'], $config['jab_port'], $config['jab_username'], $config['jab_password'], $config['jab_use_ssl']);
  678. if (!$this->jabber->connect())
  679. {
  680. messenger::error('JABBER', $lang['ERR_JAB_CONNECT']);
  681. continue 2;
  682. }
  683. if (!$this->jabber->login())
  684. {
  685. messenger::error('JABBER', $lang['ERR_JAB_AUTH']);
  686. continue 2;
  687. }
  688. break;
  689. default:
  690. return;
  691. }
  692. for ($i = 0; $i < $num_items; $i++)
  693. {
  694. // Make variables available...
  695. extract(array_shift($this->queue_data[$object]['data']));
  696. switch ($object)
  697. {
  698. case 'email':
  699. $err_msg = '';
  700. $to = (!$to) ? 'undisclosed-recipients:;' : $to;
  701. if ($config['smtp_delivery'])
  702. {
  703. $result = smtpmail($addresses, mail_encode($subject), wordwrap(utf8_wordwrap($msg), 997, "\n", true), $err_msg, $headers);
  704. }
  705. else
  706. {
  707. $result = phpbb_mail($to, $subject, $msg, $headers, $this->eol, $err_msg);
  708. }
  709. if (!$result)
  710. {
  711. @unlink($this->cache_file . '.lock');
  712. emailer::error('EMAIL', $err_msg);
  713. continue 2;
  714. }
  715. break;
  716. case 'jabber':
  717. foreach ($addresses as $address)
  718. {
  719. if ($this->jabber->send_message($address, $msg, $subject) === false)
  720. {
  721. emailer::error('JABBER', $this->jabber->get_log());
  722. continue 3;
  723. }
  724. }
  725. break;
  726. }
  727. }
  728. // No more data for this object? Unset it
  729. if (!sizeof($this->queue_data[$object]['data']))
  730. {
  731. unset($this->queue_data[$object]);
  732. }
  733. // Post-object processing
  734. switch ($object)
  735. {
  736. case 'jabber':
  737. // Hang about a couple of secs to ensure the messages are
  738. // handled, then disconnect
  739. $this->jabber->disconnect();
  740. break;
  741. }
  742. }
  743. if (!sizeof($this->queue_data))
  744. {
  745. @unlink($this->cache_file);
  746. }
  747. else
  748. {
  749. if ($fp = @fopen($this->cache_file, 'wb'))
  750. {
  751. @flock($fp, LOCK_EX);
  752. fwrite($fp, "<?php\nif (!defined('IN_ICYPHOENIX')) exit;\n\$this->queue_data = unserialize(" . var_export(serialize($this->queue_data), true) . ");\n\n?>");
  753. @flock($fp, LOCK_UN);
  754. fclose($fp);
  755. phpbb_chmod($this->cache_file, CHMOD_READ | CHMOD_WRITE);
  756. }
  757. }
  758. @unlink($this->cache_file . '.lock');
  759. }
  760. /**
  761. * Save queue
  762. */
  763. function save()
  764. {
  765. if (!sizeof($this->data))
  766. {
  767. return;
  768. }
  769. if (file_exists($this->cache_file))
  770. {
  771. include($this->cache_file);
  772. foreach ($this->queue_data as $object => $data_ary)
  773. {
  774. if (isset($this->data[$object]) && sizeof($this->data[$object]))
  775. {
  776. $this->data[$object]['data'] = array_merge($data_ary['data'], $this->data[$object]['data']);
  777. }
  778. else
  779. {
  780. $this->data[$object]['data'] = $data_ary['data'];
  781. }
  782. }
  783. }
  784. if ($fp = @fopen($this->cache_file, 'w'))
  785. {
  786. @flock($fp, LOCK_EX);
  787. fwrite($fp, "<?php\nif (!defined('IN_ICYPHOENIX')) exit;\n\$this->queue_data = unserialize(" . var_export(serialize($this->data), true) . ");\n\n?>");
  788. @flock($fp, LOCK_UN);
  789. fclose($fp);
  790. phpbb_chmod($this->cache_file, CHMOD_READ | CHMOD_WRITE);
  791. }
  792. }
  793. }
  794. /**
  795. * Replacement or substitute for PHP's mail command
  796. */
  797. function smtpmail($addresses, $subject, $message, &$err_msg, $headers = false)
  798. {
  799. global $config, $lang;
  800. // Fix any bare linefeeds in the message to make it RFC821 Compliant.
  801. $message = preg_replace("#(?<!\r)\n#si", "\r\n", $message);
  802. if ($headers !== false)
  803. {
  804. if (!is_array($headers))
  805. {
  806. // Make sure there are no bare linefeeds in the headers
  807. $headers = preg_replace('#(?<!\r)\n#si', "\n", $headers);
  808. $headers = explode("\n", $headers);
  809. }
  810. // Ok this is rather confusing all things considered, but we have to grab bcc and cc headers and treat them differently
  811. // Something we really didn't take into consideration originally
  812. $headers_used = array();
  813. foreach ($headers as $header)
  814. {
  815. if (strpos(strtolower($header), 'cc:') === 0 || strpos(strtolower($header), 'bcc:') === 0)
  816. {
  817. continue;
  818. }
  819. $headers_used[] = trim($header);
  820. }
  821. $headers = chop(implode("\r\n", $headers_used));
  822. }
  823. if (trim($subject) == '')
  824. {
  825. $err_msg = (isset($lang['NO_EMAIL_SUBJECT'])) ? $lang['NO_EMAIL_SUBJECT'] : 'No email subject specified';
  826. return false;
  827. }
  828. if (trim($message) == '')
  829. {
  830. $err_msg = (isset($lang['NO_EMAIL_MESSAGE'])) ? $lang['NO_EMAIL_MESSAGE'] : 'Email message was blank';
  831. return false;
  832. }
  833. $mail_rcpt = $mail_to = $mail_cc = array();
  834. // Build correct addresses for RCPT TO command and the client side display (TO, CC)
  835. if (isset($addresses['to']) && sizeof($addresses['to']))
  836. {
  837. foreach ($addresses['to'] as $which_ary)
  838. {
  839. $mail_to[] = ($which_ary['name'] != '') ? mail_encode(trim($which_ary['name'])) . ' <' . trim($which_ary['email']) . '>' : '<' . trim($which_ary['email']) . '>';
  840. $mail_rcpt['to'][] = '<' . trim($which_ary['email']) . '>';
  841. }
  842. }
  843. if (isset($addresses['bcc']) && sizeof($addresses['bcc']))
  844. {
  845. foreach ($addresses['bcc'] as $which_ary)
  846. {
  847. $mail_rcpt['bcc'][] = '<' . trim($which_ary['email']) . '>';
  848. }
  849. }
  850. if (isset($addresses['cc']) && sizeof($addresses['cc']))
  851. {
  852. foreach ($addresses['cc'] as $which_ary)
  853. {
  854. $mail_cc[] = ($which_ary['name'] != '') ? mail_encode(trim($which_ary['name'])) . ' <' . trim($which_ary['email']) . '>' : '<' . trim($which_ary['email']) . '>';
  855. $mail_rcpt['cc'][] = '<' . trim($which_ary['email']) . '>';
  856. }
  857. }
  858. $smtp = new smtp_class();
  859. $errno = 0;
  860. $errstr = '';
  861. $config['smtp_port'] = empty($config['smtp_port']) ? 25 : $config['smtp_port'];
  862. $smtp->add_backtrace('Connecting to ' . $config['smtp_host'] . ':' . $config['smtp_port']);
  863. // Ok we have error checked as much as we can to this point let's get on it already.
  864. ob_start();
  865. $smtp->socket = fsockopen($config['smtp_host'], $config['smtp_port'], $errno, $errstr, 20);
  866. $error_contents = ob_get_clean();
  867. if (!$smtp->socket)
  868. {
  869. if ($errstr)
  870. {
  871. $errstr = utf8_convert_message($errstr);
  872. }
  873. $err_msg = (isset($lang['NO_CONNECT_TO_SMTP_HOST'])) ? sprintf($lang['NO_CONNECT_TO_SMTP_HOST'], $errno, $errstr) : "Could not connect to smtp host : $errno : $errstr";
  874. $err_msg .= ($error_contents) ? '<br /><br />' . htmlspecialchars($error_contents) : '';
  875. return false;
  876. }
  877. // Wait for reply
  878. if ($err_msg = $smtp->server_parse('220', __LINE__))
  879. {
  880. $smtp->close_session($err_msg);
  881. return false;
  882. }
  883. // Let me in. This function handles the complete authentication process
  884. if ($err_msg = $smtp->log_into_server($config['smtp_host'], $config['smtp_username'], $config['smtp_password'], $config['smtp_auth_method']))
  885. {
  886. $smtp->close_session($err_msg);
  887. return false;
  888. }
  889. // From this point onward most server response codes should be 250
  890. // Specify who the mail is from....
  891. $smtp->server_send('MAIL FROM:<' . $config['board_email'] . '>');
  892. if ($err_msg = $smtp->server_parse('250', __LINE__))
  893. {
  894. $smtp->close_session($err_msg);
  895. return false;
  896. }
  897. // Specify each user to send to and build to header.
  898. $to_header = implode(', ', $mail_to);
  899. $cc_header = implode(', ', $mail_cc);
  900. // Now tell the MTA to send the Message to the following people... [TO, BCC, CC]
  901. $rcpt = false;
  902. foreach ($mail_rcpt as $type => $mail_to_addresses)
  903. {
  904. foreach ($mail_to_addresses as $mail_to_address)
  905. {
  906. // Add an additional bit of error checking to the To field.
  907. if (preg_match('#[^ ]+\@[^ ]+#', $mail_to_address))
  908. {
  909. $smtp->server_send("RCPT TO:$mail_to_address");
  910. if ($err_msg = $smtp->server_parse('250', __LINE__))
  911. {
  912. // We continue... if users are not resolved we do not care
  913. if ($smtp->numeric_response_code != 550)
  914. {
  915. $smtp->close_session($err_msg);
  916. return false;
  917. }
  918. }
  919. else
  920. {
  921. $rcpt = true;
  922. }
  923. }
  924. }
  925. }
  926. // We try to send messages even if a few people do not seem to have valid email addresses, but if no one has, we have to exit here.
  927. if (!$rcpt)
  928. {
  929. $err_msg .= '<br /><br />';
  930. $err_msg .= (isset($lang['INVALID_EMAIL_LOG'])) ? sprintf($lang['INVALID_EMAIL_LOG'], htmlspecialchars($mail_to_address)) : '<strong>' . htmlspecialchars($mail_to_address) . '</strong> possibly an invalid email address?';
  931. $smtp->close_session($err_msg);
  932. return false;
  933. }
  934. // Ok now we tell the server we are ready to start sending data
  935. $smtp->server_send('DATA');
  936. // This is the last response code we look for until the end of the message.
  937. if ($err_msg = $smtp->server_parse('354', __LINE__))
  938. {
  939. $smtp->close_session($err_msg);
  940. return false;
  941. }
  942. // Send the Subject Line...
  943. $smtp->server_send("Subject: $subject");
  944. // Now the To Header.
  945. $to_header = ($to_header == '') ? 'Undisclosed-recipients:;' : $to_header;
  946. $smtp->server_send("To: $to_header");
  947. // Now the CC Header.
  948. if ($cc_header != '')
  949. {
  950. $smtp->server_send("CC: $cc_header");
  951. }
  952. // Now any custom headers....
  953. if ($headers !== false)
  954. {
  955. $smtp->server_send("$headers\r\n");
  956. }
  957. // Ok now we are ready for the message...
  958. $smtp->server_send($message);
  959. // Ok the all the ingredients are mixed in let's cook this puppy...
  960. $smtp->server_send('.');
  961. if ($err_msg = $smtp->server_parse('250', __LINE__))
  962. {
  963. $smtp->close_session($err_msg);
  964. return false;
  965. }
  966. // Now tell the server we are done and close the socket...
  967. $smtp->server_send('QUIT');
  968. $smtp->close_session($err_msg);
  969. return true;
  970. }
  971. /**
  972. * SMTP Class
  973. * Auth Mechanisms originally taken from the AUTH Modules found within the PHP Extension and Application Repository (PEAR)
  974. * See docs/AUTHORS for more details
  975. * @package phpBB3
  976. */
  977. class smtp_class
  978. {
  979. var $server_response = '';
  980. var $socket = 0;
  981. var $responses = array();
  982. var $commands = array();
  983. var $numeric_response_code = 0;
  984. var $backtrace = false;
  985. var $backtrace_log = array();
  986. function __construct()
  987. {
  988. // Always create a backtrace for admins to identify SMTP problems
  989. $this->backtrace = true;
  990. $this->backtrace_log = array();
  991. }
  992. /**
  993. * Add backtrace message for debugging
  994. */
  995. function add_backtrace($message)
  996. {
  997. if ($this->backtrace)
  998. {
  999. $this->backtrace_log[] = utf8_htmlspecialchars($message);
  1000. }
  1001. }
  1002. /**
  1003. * Send command to smtp server
  1004. */
  1005. function server_send($command, $private_info = false)
  1006. {
  1007. fputs($this->socket, $command . "\r\n");
  1008. (!$private_info) ? $this->add_backtrace("# $command") : $this->add_backtrace('# Omitting sensitive information');
  1009. // We could put additional code here
  1010. }
  1011. /**
  1012. * We use the line to give the support people an indication at which command the error occurred
  1013. */
  1014. function server_parse($response, $line)
  1015. {
  1016. global $lang;
  1017. $this->server_response = '';
  1018. $this->responses = array();
  1019. $this->numeric_response_code = 0;
  1020. while (substr($this->server_response, 3, 1) != ' ')
  1021. {
  1022. if (!($this->server_response = fgets($this->socket, 256)))
  1023. {
  1024. return (isset($lang['NO_EMAIL_RESPONSE_CODE'])) ? $lang['NO_EMAIL_RESPONSE_CODE'] : 'Could not get mail server response codes';
  1025. }
  1026. $this->responses[] = substr(rtrim($this->server_response), 4);
  1027. $this->numeric_response_code = (int) substr($this->server_response, 0, 3);
  1028. $this->add_backtrace("LINE: $line <- {$this->server_response}");
  1029. }
  1030. if (!(substr($this->server_response, 0, 3) == $response))
  1031. {
  1032. $this->numeric_response_code = (int) substr($this->server_response, 0, 3);
  1033. return (isset($lang['EMAIL_SMTP_ERROR_RESPONSE'])) ? sprintf($lang['EMAIL_SMTP_ERROR_RESPONSE'], $line, $this->server_response) : "Ran into problems sending Mail at <strong>Line $line</strong>. Response: $this->server_response";
  1034. }
  1035. return 0;
  1036. }
  1037. /**
  1038. * Close session
  1039. */
  1040. function close_session(&$err_msg)
  1041. {
  1042. fclose($this->socket);
  1043. if ($this->backtrace)
  1044. {
  1045. $message = '<h1>Backtrace</h1><p>' . implode('<br />', $this->backtrace_log) . '</p>';
  1046. $err_msg .= $message;
  1047. }
  1048. }
  1049. /**
  1050. * Log into server and get possible auth codes if neccessary
  1051. */
  1052. function log_into_server($hostname, $username, $password, $default_auth_method)
  1053. {
  1054. global $lang;
  1055. $err_msg = '';
  1056. // Here we try to determine the *real* hostname (reverse DNS entry preferrably)
  1057. $local_host = extract_current_hostname();
  1058. if (function_exists('php_uname'))
  1059. {
  1060. $local_host = php_uname('n');
  1061. // Able to resolve name to IP
  1062. if (($addr = @gethostbyname($local_host)) !== $local_host)
  1063. {
  1064. // Able to resolve IP back to name
  1065. if (($name = @gethostbyaddr($addr)) !== $addr)
  1066. {
  1067. $local_host = $name;
  1068. }
  1069. }
  1070. }
  1071. // If we are authenticating through pop-before-smtp, we
  1072. // have to login ones before we get authenticated
  1073. // NOTE: on some configurations the time between an update of the auth database takes so
  1074. // long that the first email send does not work. This is not a biggie on a live board (only
  1075. // the install mail will most likely fail) - but on a dynamic ip connection this might produce
  1076. // severe problems and is not fixable!
  1077. if ($default_auth_method == 'POP-BEFORE-SMTP' && $username && $password)
  1078. {
  1079. global $config;
  1080. $errno = 0;
  1081. $errstr = '';
  1082. $this->server_send("QUIT");
  1083. fclose($this->socket);
  1084. $result = $this->pop_before_smtp($hostname, $username, $password);
  1085. $username = $password = $default_auth_method = '';
  1086. // We need to close the previous session, else the server is not able to get our ip for matching...
  1087. $config['smtp_port'] = empty($config['smtp_port']) ? 25 : $config['smtp_port'];
  1088. if (!$this->socket = @fsockopen($config['smtp_host'], $config['smtp_port'], $errno, $errstr, 10))
  1089. {
  1090. if ($errstr)
  1091. {
  1092. $errstr = utf8_convert_message($errstr);
  1093. }
  1094. $err_msg = (isset($lang['NO_CONNECT_TO_SMTP_HOST'])) ? sprintf($lang['NO_CONNECT_TO_SMTP_HOST'], $errno, $errstr) : "Could not connect to smtp host : $errno : $errstr";
  1095. return $err_msg;
  1096. }
  1097. // Wait for reply
  1098. if ($err_msg = $this->server_parse('220', __LINE__))
  1099. {
  1100. $this->close_session($err_msg);
  1101. return $err_msg;
  1102. }
  1103. }
  1104. // Try EHLO first
  1105. $this->server_send("EHLO {$local_host}");
  1106. if ($err_msg = $this->server_parse('250', __LINE__))
  1107. {
  1108. // a 503 response code means that we're already authenticated
  1109. if ($this->numeric_response_code == 503)
  1110. {
  1111. return false;
  1112. }
  1113. // If EHLO fails, we try HELO
  1114. $this->server_send("HELO {$local_host}");
  1115. if ($err_msg = $this->server_parse('250', __LINE__))
  1116. {
  1117. return ($this->numeric_response_code == 503) ? false : $err_msg;
  1118. }
  1119. }
  1120. foreach ($this->responses as $response)
  1121. {
  1122. $response = explode(' ', $response);
  1123. $response_code = $response[0];
  1124. unset($response[0]);
  1125. $this->commands[$response_code] = implode(' ', $response);
  1126. }
  1127. // If we are not authenticated yet, something might be wrong if no username and passwd passed
  1128. if (!$username || !$password)
  1129. {
  1130. return false;
  1131. }
  1132. if (!isset($this->commands['AUTH']))
  1133. {
  1134. return (isset($lang['SMTP_NO_AUTH_SUPPORT'])) ? $lang['SMTP_NO_AUTH_SUPPORT'] : 'SMTP server does not support authentication';
  1135. }
  1136. // Get best authentication method
  1137. $available_methods = explode(' ', $this->commands['AUTH']);
  1138. // Define the auth ordering if the default auth method was not found
  1139. $auth_methods = array('PLAIN', 'LOGIN', 'CRAM-MD5', 'DIGEST-MD5');
  1140. $method = '';
  1141. if (in_array($default_auth_method, $available_methods))
  1142. {
  1143. $method = $default_auth_method;
  1144. }
  1145. else
  1146. {
  1147. foreach ($auth_methods as $_method)
  1148. {
  1149. if (in_array($_method, $available_methods))
  1150. {
  1151. $method = $_method;
  1152. break;
  1153. }
  1154. }
  1155. }
  1156. if (!$method)
  1157. {
  1158. return (isset($lang['NO_SUPPORTED_AUTH_METHODS'])) ? $lang['NO_SUPPORTED_AUTH_METHODS'] : 'No supported authentication methods';
  1159. }
  1160. $method = strtolower(str_replace('-', '_', $method));
  1161. return $this->$method($username, $password);
  1162. }
  1163. /**
  1164. * Pop before smtp authentication
  1165. */
  1166. function pop_before_smtp($hostname, $username, $password)
  1167. {
  1168. global $lang;
  1169. if (!$this->socket = @fsockopen($hostname, 110, $errno, $errstr, 10))
  1170. {
  1171. if ($errstr)
  1172. {
  1173. $errstr = utf8_convert_message($errstr);
  1174. }
  1175. return (isset($lang['NO_CONNECT_TO_SMTP_HOST'])) ? sprintf($lang['NO_CONNECT_TO_SMTP_HOST'], $errno, $errstr) : "Could not connect to smtp host : $errno : $errstr";
  1176. }
  1177. $this->server_send("USER $username", true);
  1178. if ($err_msg = $this->server_parse('+OK', __LINE__))
  1179. {
  1180. return $err_msg;
  1181. }
  1182. $this->server_send("PASS $password", true);
  1183. if ($err_msg = $this->server_parse('+OK', __LINE__))
  1184. {
  1185. return $err_msg;
  1186. }
  1187. $this->server_send('QUIT');
  1188. fclose($this->socket);
  1189. return false;
  1190. }
  1191. /**
  1192. * Plain authentication method
  1193. */
  1194. function plain($username, $password)
  1195. {
  1196. $this->server_send('AUTH PLAIN');
  1197. if ($err_msg = $this->server_parse('334', __LINE__))
  1198. {
  1199. return ($this->numeric_response_code == 503) ? false : $err_msg;
  1200. }
  1201. $base64_method_plain = base64_encode("\0" . $username . "\0" . $password);
  1202. $this->server_send($base64_method_plain, true);
  1203. if ($err_msg = $this->server_parse('235', __LINE__))
  1204. {
  1205. return $err_msg;
  1206. }
  1207. return false;
  1208. }
  1209. /**
  1210. * Login authentication method
  1211. */
  1212. function login($username, $password)
  1213. {
  1214. $this->server_send('AUTH LOGIN');
  1215. if ($err_msg = $this->server_parse('334', __LINE__))
  1216. {
  1217. return ($this->numeric_response_code == 503) ? false : $err_msg;
  1218. }
  1219. $this->server_send(base64_encode($username), true);
  1220. if ($err_msg = $this->server_parse('334', __LINE__))
  1221. {
  1222. return $err_msg;
  1223. }
  1224. $this->server_send(base64_encode($password), true);
  1225. if ($err_msg = $this->server_parse('235', __LINE__))
  1226. {
  1227. return $err_msg;
  1228. }
  1229. return false;
  1230. }
  1231. /**
  1232. * cram_md5 authentication method
  1233. */
  1234. function cram_md5($username, $password)
  1235. {
  1236. $this->server_send('AUTH CRAM-MD5');
  1237. if ($err_msg = $this->server_parse('334', __LINE__))
  1238. {
  1239. return ($this->numeric_response_code == 503) ? false : $err_msg;
  1240. }
  1241. $md5_challenge = base64_decode($this->responses[0]);
  1242. $password = (strlen($password) > 64) ? pack('H32', md5($password)) : ((strlen($password) < 64) ? str_pad($password, 64, chr(0)) : $password);
  1243. $md5_digest = md5((substr($password, 0, 64) ^ str_repeat(chr(0x5C), 64)) . (pack('H32', md5((substr($password, 0, 64) ^ str_repeat(chr(0x36), 64)) . $md5_challenge))));
  1244. $base64_method_cram_md5 = base64_encode($username . ' ' . $md5_digest);
  1245. $this->server_send($base64_method_cram_md5, true);
  1246. if ($err_msg = $this->server_parse('235', __LINE__))
  1247. {
  1248. return $err_msg;
  1249. }
  1250. return false;
  1251. }
  1252. /**
  1253. * digest_md5 authentication method
  1254. * A real pain in the ***
  1255. */
  1256. function digest_md5($username, $password)
  1257. {
  1258. global $config, $lang;
  1259. $this->server_send('AUTH DIGEST-MD5');
  1260. if ($err_msg = $this->server_parse('334', __LINE__))
  1261. {
  1262. return ($this->numeric_response_code == 503) ? false : $err_msg;
  1263. }
  1264. $md5_challenge = base64_decode($this->responses[0]);
  1265. // Parse the md5 challenge - from AUTH_SASL (PEAR)
  1266. $tokens = array();
  1267. while (preg_match('/^([a-z-]+)=("[^"]+(?<!\\\)"|[^,]+)/i', $md5_challenge, $matches))
  1268. {
  1269. // Ignore these as per rfc2831
  1270. if ($matches[1] == 'opaque' || $matches[1] == 'domain')
  1271. {
  1272. $md5_challenge = substr($md5_challenge, strlen($matches[0]) + 1);
  1273. continue;
  1274. }
  1275. // Allowed multiple "realm" and "auth-param"
  1276. if (!empty($tokens[$matches[1]]) && ($matches[1] == 'realm' || $matches[1] == 'auth-param'))
  1277. {
  1278. if (is_array($tokens[$matches[1]]))
  1279. {
  1280. $tokens[$matches[1]][] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
  1281. }
  1282. else
  1283. {
  1284. $tokens[$matches[1]] = array($tokens[$matches[1]], preg_replace('/^"(.*)"$/', '\\1', $matches[2]));
  1285. }
  1286. }
  1287. elseif (!empty($tokens[$matches[1]])) // Any other multiple instance = failure
  1288. {
  1289. $tokens = array();
  1290. break;
  1291. }
  1292. else
  1293. {
  1294. $tokens[$matches[1]] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
  1295. }
  1296. // Remove the just parsed directive from the challenge
  1297. $md5_challenge = substr($md5_challenge, strlen($matches[0]) + 1);
  1298. }
  1299. // Realm
  1300. if (empty($tokens['realm']))
  1301. {
  1302. $tokens['realm'] = (function_exists('php_uname')) ? php_uname('n') : extract_current_hostname();
  1303. }
  1304. // Maxbuf
  1305. if (empty($tokens['maxbuf']))
  1306. {
  1307. $tokens['maxbuf'] = 65536;
  1308. }
  1309. // Required: nonce, algorithm
  1310. if (empty($tokens['nonce']) || empty($tokens['algorithm']))
  1311. {
  1312. $tokens = array();
  1313. }
  1314. $md5_challenge = $tokens;
  1315. if (!empty($md5_challenge))
  1316. {
  1317. $str = '';
  1318. for ($i = 0; $i < 32; $i++)
  1319. {
  1320. $str .= chr(mt_rand(0, 255));
  1321. }
  1322. $cnonce = base64_encode($str);
  1323. $digest_uri = 'smtp/' . $config['smtp_host'];
  1324. $auth_1 = sprintf('%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $username, $md5_challenge['realm'], $password))), $md5_challenge['nonce'], $cnonce);
  1325. $auth_2 = 'AUTHENTICATE:' . $digest_uri;
  1326. $response_value = md5(sprintf('%s:%s:00000001:%s:auth:%s', md5($auth_1), $md5_challenge['nonce'], $cnonce, md5($auth_2)));
  1327. $input_string = sprintf('username="%s",realm="%s",nonce="%s",cnonce="%s",nc="00000001",qop=auth,digest-uri="%s",response=%s,%d', $username, $md5_challenge['realm'], $md5_challenge['nonce'], $cnonce, $digest_uri, $response_value, $md5_challenge['maxbuf']);
  1328. }
  1329. else
  1330. {
  1331. return (isset($lang['INVALID_DIGEST_CHALLENGE'])) ? $lang['INVALID_DIGEST_CHALLENGE'] : 'Invalid digest challenge';
  1332. }
  1333. $base64_method_digest_md5 = base64_encode($input_string);
  1334. $this->server_send($base64_method_digest_md5, true);
  1335. if ($err_msg = $this->server_parse('334', __LINE__))
  1336. {
  1337. return $err_msg;
  1338. }
  1339. $this->server_send(' ');
  1340. if ($err_msg = $this->server_parse('235', __LINE__))
  1341. {
  1342. return $err_msg;
  1343. }
  1344. return false;
  1345. }
  1346. }
  1347. /**
  1348. * Encodes the given string for proper display in UTF-8.
  1349. *
  1350. * This version is using base64 encoded data. The downside of this is if the mail client does not understand this encoding the user is basically doomed with an unreadable subject.
  1351. *
  1352. * Please note that this version fully supports RFC 2045 section 6.8.
  1353. *
  1354. * @param string $eol End of line we are using (optional to be backwards compatible)
  1355. */
  1356. function mail_encode($str, $eol = "\r\n")
  1357. {
  1358. // define start delimimter, end delimiter and spacer
  1359. $start = "=?UTF-8?B?";
  1360. $end = "?=";
  1361. $delimiter = "$eol ";
  1362. // Maximum length is 75. $split_length *must* be a multiple of 4, but <= 75 - strlen($start . $delimiter . $end)!!!
  1363. $split_length = 60;
  1364. $encoded_str = base64_encode($str);
  1365. // If encoded string meets the limits, we just return with the correct data.
  1366. if (strlen($encoded_str) <= $split_length)
  1367. {
  1368. return $start . $encoded_str . $end;
  1369. }
  1370. // If there is only ASCII data, we just return what we want, correctly splitting the lines.
  1371. if (strlen($str) === utf8_strlen($str))
  1372. {
  1373. return $start . implode($end . $delimiter . $start, str_split($encoded_str, $split_length)) . $end;
  1374. }
  1375. // UTF-8 data, compose encoded lines
  1376. $array = utf8_str_split($str);
  1377. $str = '';
  1378. while (sizeof($array))
  1379. {
  1380. $text = '';
  1381. while (sizeof($array) && intval((strlen($text . $array[0]) + 2) / 3) << 2 <= $split_length)
  1382. {
  1383. $text .= array_shift($array);
  1384. }
  1385. $str .= $start . base64_encode($text) . $end . $delimiter;
  1386. }
  1387. return substr($str, 0, -strlen($delimiter));
  1388. }
  1389. /**
  1390. * Wrapper for sending out emails with the PHP's mail function
  1391. */
  1392. function phpbb_mail($to, $subject, $msg, $headers, $eol, &$err_msg)
  1393. {
  1394. global $config;
  1395. $config['email_function_name'] = !empty($config['email_function_name']) ? trim($config['email_function_name']) : 'mail';
  1396. // We use the EOL character for the OS here because the PHP mail function does not correctly transform line endings. On Windows SMTP is used (SMTP is \r\n), on UNIX a command is used...
  1397. // Reference: http://bugs.php.net/bug.php?id=15841
  1398. $headers = implode($eol, $headers);
  1399. ob_start();
  1400. // On some PHP Versions mail() *may* fail if there are newlines within the subject.
  1401. // Newlines are used as a delimiter for lines in mail_encode() according to RFC 2045 section 6.8.
  1402. // Because PHP can't decide what is wanted we revert back to the non-RFC-compliant way of separating by one space (Use '' as parameter to mail_encode() results in SPACE used)
  1403. $result = $config['email_function_name']($to, mail_encode($subject, ''), wordwrap(utf8_wordwrap($msg), 997, "\n", true), $headers);
  1404. $err_msg = ob_get_clean();
  1405. return $result;
  1406. }
  1407. ?>