PageRenderTime 84ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 1ms

/includes/functions_messenger.php

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