Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
functions_messenger.php
0001 <?php
0002 /**
0003 *
0004 * This file is part of the phpBB Forum Software package.
0005 *
0006 * @copyright (c) phpBB Limited <https://www.phpbb.com>
0007 * @license GNU General Public License, version 2 (GPL-2.0)
0008 *
0009 * For full copyright and license information, please see
0010 * the docs/CREDITS.txt file.
0011 *
0012 */
0013
0014 /**
0015 * @ignore
0016 */
0017 if (!defined('IN_PHPBB'))
0018 {
0019 exit;
0020 }
0021
0022 /**
0023 * Messenger
0024 */
0025 class messenger
0026 {
0027 var $msg, $replyto, $from, $subject;
0028 var $addresses = array();
0029 var $extra_headers = array();
0030
0031 var $mail_priority = MAIL_NORMAL_PRIORITY;
0032 var $use_queue = true;
0033
0034 /** @var \phpbb\template\template */
0035 protected $template;
0036
0037 /**
0038 * Constructor
0039 */
0040 function __construct($use_queue = true)
0041 {
0042 global $config;
0043
0044 $this->use_queue = (!$config['email_package_size']) ? false : $use_queue;
0045 $this->subject = '';
0046 }
0047
0048 /**
0049 * Resets all the data (address, template file, etc etc) to default
0050 */
0051 function reset()
0052 {
0053 $this->addresses = $this->extra_headers = array();
0054 $this->msg = $this->replyto = $this->from = '';
0055 $this->mail_priority = MAIL_NORMAL_PRIORITY;
0056 }
0057
0058 /**
0059 * Set addresses for to/im as available
0060 *
0061 * @param array $user User row
0062 */
0063 function set_addresses($user)
0064 {
0065 if (isset($user['user_email']) && $user['user_email'])
0066 {
0067 $this->to($user['user_email'], (isset($user['username']) ? $user['username'] : ''));
0068 }
0069
0070 if (isset($user['user_jabber']) && $user['user_jabber'])
0071 {
0072 $this->im($user['user_jabber'], (isset($user['username']) ? $user['username'] : ''));
0073 }
0074 }
0075
0076 /**
0077 * Sets an email address to send to
0078 */
0079 function to($address, $realname = '')
0080 {
0081 global $config;
0082
0083 if (!trim($address))
0084 {
0085 return;
0086 }
0087
0088 $pos = isset($this->addresses['to']) ? count($this->addresses['to']) : 0;
0089
0090 $this->addresses['to'][$pos]['email'] = trim($address);
0091
0092 // If empty sendmail_path on windows, PHP changes the to line
0093 if (!$config['smtp_delivery'] && DIRECTORY_SEPARATOR == '\\')
0094 {
0095 $this->addresses['to'][$pos]['name'] = '';
0096 }
0097 else
0098 {
0099 $this->addresses['to'][$pos]['name'] = trim($realname);
0100 }
0101 }
0102
0103 /**
0104 * Sets an cc address to send to
0105 */
0106 function cc($address, $realname = '')
0107 {
0108 if (!trim($address))
0109 {
0110 return;
0111 }
0112
0113 $pos = isset($this->addresses['cc']) ? count($this->addresses['cc']) : 0;
0114 $this->addresses['cc'][$pos]['email'] = trim($address);
0115 $this->addresses['cc'][$pos]['name'] = trim($realname);
0116 }
0117
0118 /**
0119 * Sets an bcc address to send to
0120 */
0121 function bcc($address, $realname = '')
0122 {
0123 if (!trim($address))
0124 {
0125 return;
0126 }
0127
0128 $pos = isset($this->addresses['bcc']) ? count($this->addresses['bcc']) : 0;
0129 $this->addresses['bcc'][$pos]['email'] = trim($address);
0130 $this->addresses['bcc'][$pos]['name'] = trim($realname);
0131 }
0132
0133 /**
0134 * Sets a im contact to send to
0135 */
0136 function im($address, $realname = '')
0137 {
0138 // IM-Addresses could be empty
0139 if (!trim($address))
0140 {
0141 return;
0142 }
0143
0144 $pos = isset($this->addresses['im']) ? count($this->addresses['im']) : 0;
0145 $this->addresses['im'][$pos]['uid'] = trim($address);
0146 $this->addresses['im'][$pos]['name'] = trim($realname);
0147 }
0148
0149 /**
0150 * Set the reply to address
0151 */
0152 function replyto($address)
0153 {
0154 $this->replyto = trim($address);
0155 }
0156
0157 /**
0158 * Set the from address
0159 */
0160 function from($address)
0161 {
0162 $this->from = trim($address);
0163 }
0164
0165 /**
0166 * set up subject for mail
0167 */
0168 function subject($subject = '')
0169 {
0170 $this->subject = trim($subject);
0171 }
0172
0173 /**
0174 * set up extra mail headers
0175 */
0176 function headers($headers)
0177 {
0178 $this->extra_headers[] = trim($headers);
0179 }
0180
0181 /**
0182 * Adds X-AntiAbuse headers
0183 *
0184 * @param \phpbb\config\config $config Config object
0185 * @param \phpbb\user $user User object
0186 * @return void
0187 */
0188 function anti_abuse_headers($config, $user)
0189 {
0190 $this->headers('X-AntiAbuse: Board servername - ' . mail_encode($config['server_name']));
0191 $this->headers('X-AntiAbuse: User_id - ' . $user->data['user_id']);
0192 $this->headers('X-AntiAbuse: Username - ' . mail_encode($user->data['username']));
0193 $this->headers('X-AntiAbuse: User IP - ' . $user->ip);
0194 }
0195
0196 /**
0197 * Set the email priority
0198 */
0199 function set_mail_priority($priority = MAIL_NORMAL_PRIORITY)
0200 {
0201 $this->mail_priority = $priority;
0202 }
0203
0204 /**
0205 * Set email template to use
0206 */
0207 function template($template_file, $template_lang = '', $template_path = '', $template_dir_prefix = '')
0208 {
0209 global $config, $phpbb_root_path, $user;
0210
0211 $template_dir_prefix = (!$template_dir_prefix || $template_dir_prefix[0] === '/') ? $template_dir_prefix : '/' . $template_dir_prefix;
0212
0213 $this->setup_template();
0214
0215 if (!trim($template_file))
0216 {
0217 trigger_error('No template file for emailing set.', E_USER_ERROR);
0218 }
0219
0220 if (!trim($template_lang))
0221 {
0222 // fall back to board default language if the user's language is
0223 // missing $template_file. If this does not exist either,
0224 // $this->template->set_filenames will do a trigger_error
0225 $template_lang = basename($config['default_lang']);
0226 }
0227
0228 $ext_template_paths = array(
0229 array(
0230 'name' => $template_lang . '_email',
0231 'ext_path' => 'language/' . $template_lang . '/email' . $template_dir_prefix,
0232 ),
0233 );
0234
0235 if ($template_path)
0236 {
0237 $template_paths = array(
0238 $template_path . $template_dir_prefix,
0239 );
0240 }
0241 else
0242 {
0243 $template_path = (!empty($user->lang_path)) ? $user->lang_path : $phpbb_root_path . 'language/';
0244 $template_path .= $template_lang . '/email';
0245
0246 $template_paths = array(
0247 $template_path . $template_dir_prefix,
0248 );
0249
0250 $board_language = basename($config['default_lang']);
0251
0252 // we can only specify default language fallback when the path is not a custom one for which we
0253 // do not know the default language alternative
0254 if ($template_lang !== $board_language)
0255 {
0256 $fallback_template_path = (!empty($user->lang_path)) ? $user->lang_path : $phpbb_root_path . 'language/';
0257 $fallback_template_path .= $board_language . '/email';
0258
0259 $template_paths[] = $fallback_template_path . $template_dir_prefix;
0260
0261 $ext_template_paths[] = array(
0262 'name' => $board_language . '_email',
0263 'ext_path' => 'language/' . $board_language . '/email' . $template_dir_prefix,
0264 );
0265 }
0266 // If everything fails just fall back to en template
0267 if ($template_lang !== 'en' && $board_language !== 'en')
0268 {
0269 $fallback_template_path = (!empty($user->lang_path)) ? $user->lang_path : $phpbb_root_path . 'language/';
0270 $fallback_template_path .= 'en/email';
0271
0272 $template_paths[] = $fallback_template_path . $template_dir_prefix;
0273
0274 $ext_template_paths[] = array(
0275 'name' => 'en_email',
0276 'ext_path' => 'language/en/email' . $template_dir_prefix,
0277 );
0278 }
0279 }
0280
0281 $this->set_template_paths($ext_template_paths, $template_paths);
0282
0283 $this->template->set_filenames(array(
0284 'body' => $template_file . '.txt',
0285 ));
0286
0287 return true;
0288 }
0289
0290 /**
0291 * assign variables to email template
0292 */
0293 function assign_vars($vars)
0294 {
0295 $this->setup_template();
0296
0297 $this->template->assign_vars($vars);
0298 }
0299
0300 function assign_block_vars($blockname, $vars)
0301 {
0302 $this->setup_template();
0303
0304 $this->template->assign_block_vars($blockname, $vars);
0305 }
0306
0307 /**
0308 * Send the mail out to the recipients set previously in var $this->addresses
0309 *
0310 * @param int $method User notification method NOTIFY_EMAIL|NOTIFY_IM|NOTIFY_BOTH
0311 * @param bool $break Flag indicating if the function only formats the subject
0312 * and the message without sending it
0313 *
0314 * @return bool
0315 */
0316 function send($method = NOTIFY_EMAIL, $break = false)
0317 {
0318 global $config, $user, $phpbb_dispatcher;
0319
0320 // We add some standard variables we always use, no need to specify them always
0321 $this->assign_vars(array(
0322 'U_BOARD' => generate_board_url(),
0323 'EMAIL_SIG' => str_replace('<br />', "\n", "-- \n" . html_entity_decode($config['board_email_sig'], ENT_COMPAT)),
0324 'SITENAME' => html_entity_decode($config['sitename'], ENT_COMPAT),
0325 ));
0326
0327 $subject = $this->subject;
0328 $template = $this->template;
0329 /**
0330 * Event to modify the template before parsing
0331 *
0332 * @event core.modify_notification_template
0333 * @var int method User notification method NOTIFY_EMAIL|NOTIFY_IM|NOTIFY_BOTH
0334 * @var bool break Flag indicating if the function only formats the subject
0335 * and the message without sending it
0336 * @var string subject The message subject
0337 * @var \phpbb\template\template template The (readonly) template object
0338 * @since 3.2.4-RC1
0339 */
0340 $vars = array('method', 'break', 'subject', 'template');
0341 extract($phpbb_dispatcher->trigger_event('core.modify_notification_template', compact($vars)));
0342
0343 // Parse message through template
0344 $message = trim($this->template->assign_display('body'));
0345
0346 /**
0347 * Event to modify notification message text after parsing
0348 *
0349 * @event core.modify_notification_message
0350 * @var int method User notification method NOTIFY_EMAIL|NOTIFY_IM|NOTIFY_BOTH
0351 * @var bool break Flag indicating if the function only formats the subject
0352 * and the message without sending it
0353 * @var string subject The message subject
0354 * @var string message The message text
0355 * @since 3.1.11-RC1
0356 */
0357 $vars = array('method', 'break', 'subject', 'message');
0358 extract($phpbb_dispatcher->trigger_event('core.modify_notification_message', compact($vars)));
0359
0360 $this->subject = $subject;
0361 $this->msg = $message;
0362 unset($subject, $message, $template);
0363
0364 // 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
0365 $this->msg = str_replace("\r\n", "\n", $this->msg);
0366
0367 // We now try and pull a subject from the email body ... if it exists,
0368 // do this here because the subject may contain a variable
0369 $drop_header = '';
0370 $match = array();
0371 if (preg_match('#^(Subject:(.*?))$#m', $this->msg, $match))
0372 {
0373 $this->subject = (trim($match[2]) != '') ? trim($match[2]) : (($this->subject != '') ? $this->subject : $user->lang['NO_EMAIL_SUBJECT']);
0374 $drop_header .= '[\r\n]*?' . preg_quote($match[1], '#');
0375 }
0376 else
0377 {
0378 $this->subject = (($this->subject != '') ? $this->subject : $user->lang['NO_EMAIL_SUBJECT']);
0379 }
0380
0381 if (preg_match('#^(List-Unsubscribe:(.*?))$#m', $this->msg, $match))
0382 {
0383 $this->extra_headers[] = $match[1];
0384 $drop_header .= '[\r\n]*?' . preg_quote($match[1], '#');
0385 }
0386
0387 if ($drop_header)
0388 {
0389 $this->msg = trim(preg_replace('#' . $drop_header . '#s', '', $this->msg));
0390 }
0391
0392 if ($break)
0393 {
0394 return true;
0395 }
0396
0397 switch ($method)
0398 {
0399 case NOTIFY_EMAIL:
0400 $result = $this->msg_email();
0401 break;
0402
0403 case NOTIFY_IM:
0404 $result = $this->msg_jabber();
0405 break;
0406
0407 case NOTIFY_BOTH:
0408 $result = $this->msg_email();
0409 $this->msg_jabber();
0410 break;
0411 }
0412
0413 $this->reset();
0414 return $result;
0415 }
0416
0417 /**
0418 * Add error message to log
0419 */
0420 function error($type, $msg)
0421 {
0422 global $user, $config, $request, $phpbb_log;
0423
0424 // Session doesn't exist, create it
0425 if (!isset($user->session_id) || $user->session_id === '')
0426 {
0427 $user->session_begin();
0428 }
0429
0430 $calling_page = html_entity_decode($request->server('REQUEST_URI'), ENT_COMPAT);
0431
0432 switch ($type)
0433 {
0434 case 'EMAIL':
0435 $message = '<strong>EMAIL/' . (($config['smtp_delivery']) ? 'SMTP' : 'PHP/mail()') . '</strong>';
0436 break;
0437
0438 default:
0439 $message = "<strong>$type</strong>";
0440 break;
0441 }
0442
0443 $message .= '<br /><em>' . htmlspecialchars($calling_page, ENT_COMPAT) . '</em><br /><br />' . $msg . '<br />';
0444 $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_ERROR_' . $type, false, array($message));
0445 }
0446
0447 /**
0448 * Save to queue
0449 */
0450 function save_queue()
0451 {
0452 global $config;
0453
0454 if ($config['email_package_size'] && $this->use_queue && !empty($this->queue))
0455 {
0456 $this->queue->save();
0457 return;
0458 }
0459 }
0460
0461 /**
0462 * Generates a valid message id to be used in emails
0463 *
0464 * @return string message id
0465 */
0466 function generate_message_id()
0467 {
0468 global $config, $request;
0469
0470 $domain = ($config['server_name']) ?: $request->server('SERVER_NAME', 'phpbb.generated');
0471
0472 return md5(unique_id(time())) . '@' . $domain;
0473 }
0474
0475 /**
0476 * Return email header
0477 */
0478 function build_header($to, $cc, $bcc)
0479 {
0480 global $config, $phpbb_dispatcher;
0481
0482 // We could use keys here, but we won't do this for 3.0.x to retain backwards compatibility
0483 $headers = array();
0484
0485 $headers[] = 'From: ' . $this->from;
0486
0487 if ($cc)
0488 {
0489 $headers[] = 'Cc: ' . $cc;
0490 }
0491
0492 if ($bcc)
0493 {
0494 $headers[] = 'Bcc: ' . $bcc;
0495 }
0496
0497 $headers[] = 'Reply-To: ' . $this->replyto;
0498 $headers[] = 'Return-Path: <' . $config['board_email'] . '>';
0499 $headers[] = 'Sender: <' . $config['board_email'] . '>';
0500 $headers[] = 'MIME-Version: 1.0';
0501 $headers[] = 'Message-ID: <' . $this->generate_message_id() . '>';
0502 $headers[] = 'Date: ' . date('r', time());
0503 $headers[] = 'Content-Type: text/plain; charset=UTF-8'; // format=flowed
0504 $headers[] = 'Content-Transfer-Encoding: 8bit'; // 7bit
0505
0506 $headers[] = 'X-Priority: ' . $this->mail_priority;
0507 $headers[] = 'X-MSMail-Priority: ' . (($this->mail_priority == MAIL_LOW_PRIORITY) ? 'Low' : (($this->mail_priority == MAIL_NORMAL_PRIORITY) ? 'Normal' : 'High'));
0508 $headers[] = 'X-Mailer: phpBB3';
0509 $headers[] = 'X-MimeOLE: phpBB3';
0510 $headers[] = 'X-phpBB-Origin: phpbb://' . str_replace(array('http://', 'https://'), array('', ''), generate_board_url());
0511
0512 /**
0513 * Event to modify email header entries
0514 *
0515 * @event core.modify_email_headers
0516 * @var array headers Array containing email header entries
0517 * @since 3.1.11-RC1
0518 */
0519 $vars = array('headers');
0520 extract($phpbb_dispatcher->trigger_event('core.modify_email_headers', compact($vars)));
0521
0522 if (count($this->extra_headers))
0523 {
0524 $headers = array_merge($headers, $this->extra_headers);
0525 }
0526
0527 return $headers;
0528 }
0529
0530 /**
0531 * Send out emails
0532 */
0533 function msg_email()
0534 {
0535 global $config, $phpbb_dispatcher;
0536
0537 if (empty($config['email_enable']))
0538 {
0539 return false;
0540 }
0541
0542 // Addresses to send to?
0543 if (empty($this->addresses) || (empty($this->addresses['to']) && empty($this->addresses['cc']) && empty($this->addresses['bcc'])))
0544 {
0545 // Send was successful. ;)
0546 return true;
0547 }
0548
0549 $use_queue = false;
0550 if ($config['email_package_size'] && $this->use_queue)
0551 {
0552 if (empty($this->queue))
0553 {
0554 $this->queue = new queue();
0555 $this->queue->init('email', $config['email_package_size']);
0556 }
0557 $use_queue = true;
0558 }
0559
0560 $contact_name = html_entity_decode($config['board_contact_name'], ENT_COMPAT);
0561 $board_contact = (($contact_name !== '') ? '"' . mail_encode($contact_name) . '" ' : '') . '<' . $config['board_contact'] . '>';
0562
0563 $break = false;
0564 $addresses = $this->addresses;
0565 $subject = $this->subject;
0566 $msg = $this->msg;
0567 /**
0568 * Event to send message via external transport
0569 *
0570 * @event core.notification_message_email
0571 * @var bool break Flag indicating if the function return after hook
0572 * @var array addresses The message recipients
0573 * @var string subject The message subject
0574 * @var string msg The message text
0575 * @since 3.2.4-RC1
0576 */
0577 $vars = array(
0578 'break',
0579 'addresses',
0580 'subject',
0581 'msg',
0582 );
0583 extract($phpbb_dispatcher->trigger_event('core.notification_message_email', compact($vars)));
0584
0585 $this->addresses = $addresses;
0586 $this->subject = $subject;
0587 $this->msg = $msg;
0588 unset($addresses, $subject, $msg);
0589
0590 if ($break)
0591 {
0592 return true;
0593 }
0594
0595 if (empty($this->replyto))
0596 {
0597 $this->replyto = $board_contact;
0598 }
0599
0600 if (empty($this->from))
0601 {
0602 $this->from = $board_contact;
0603 }
0604
0605 $encode_eol = $config['smtp_delivery'] || PHP_VERSION_ID >= 80000 ? "\r\n" : PHP_EOL;
0606
0607 // Build to, cc and bcc strings
0608 $to = $cc = $bcc = '';
0609 foreach ($this->addresses as $type => $address_ary)
0610 {
0611 if ($type == 'im')
0612 {
0613 continue;
0614 }
0615
0616 foreach ($address_ary as $which_ary)
0617 {
0618 ${$type} .= ((${$type} != '') ? ', ' : '') . (($which_ary['name'] != '') ? mail_encode($which_ary['name'], $encode_eol) . ' <' . $which_ary['email'] . '>' : $which_ary['email']);
0619 }
0620 }
0621
0622 // Build header
0623 $headers = $this->build_header($to, $cc, $bcc);
0624
0625 // Send message ...
0626 if (!$use_queue)
0627 {
0628 $mail_to = ($to == '') ? 'undisclosed-recipients:;' : $to;
0629 $err_msg = '';
0630
0631 if ($config['smtp_delivery'])
0632 {
0633 $result = smtpmail($this->addresses, mail_encode($this->subject), wordwrap(utf8_wordwrap($this->msg), 997, "\n", true), $err_msg, $headers);
0634 }
0635 else
0636 {
0637 $result = phpbb_mail($mail_to, $this->subject, $this->msg, $headers, $encode_eol, $err_msg);
0638 }
0639
0640 if (!$result)
0641 {
0642 $this->error('EMAIL', $err_msg);
0643 return false;
0644 }
0645 }
0646 else
0647 {
0648 $this->queue->put('email', array(
0649 'to' => $to,
0650 'addresses' => $this->addresses,
0651 'subject' => $this->subject,
0652 'msg' => $this->msg,
0653 'headers' => $headers)
0654 );
0655 }
0656
0657 return true;
0658 }
0659
0660 /**
0661 * Send jabber message out
0662 */
0663 function msg_jabber()
0664 {
0665 global $config, $user, $phpbb_root_path, $phpEx;
0666
0667 if (empty($config['jab_enable']) || empty($config['jab_host']) || empty($config['jab_username']) || empty($config['jab_password']))
0668 {
0669 return false;
0670 }
0671
0672 if (empty($this->addresses['im']))
0673 {
0674 // Send was successful. ;)
0675 return true;
0676 }
0677
0678 $use_queue = false;
0679 if ($config['jab_package_size'] && $this->use_queue)
0680 {
0681 if (empty($this->queue))
0682 {
0683 $this->queue = new queue();
0684 $this->queue->init('jabber', $config['jab_package_size']);
0685 }
0686 $use_queue = true;
0687 }
0688
0689 $addresses = array();
0690 foreach ($this->addresses['im'] as $type => $uid_ary)
0691 {
0692 $addresses[] = $uid_ary['uid'];
0693 }
0694 $addresses = array_unique($addresses);
0695
0696 if (!$use_queue)
0697 {
0698 include_once($phpbb_root_path . 'includes/functions_jabber.' . $phpEx);
0699 $this->jabber = new jabber($config['jab_host'], $config['jab_port'], $config['jab_username'], html_entity_decode($config['jab_password'], ENT_COMPAT), $config['jab_use_ssl'], $config['jab_verify_peer'], $config['jab_verify_peer_name'], $config['jab_allow_self_signed']);
0700
0701 if (!$this->jabber->connect())
0702 {
0703 $this->error('JABBER', $user->lang['ERR_JAB_CONNECT'] . '<br />' . $this->jabber->get_log());
0704 return false;
0705 }
0706
0707 if (!$this->jabber->login())
0708 {
0709 $this->error('JABBER', $user->lang['ERR_JAB_AUTH'] . '<br />' . $this->jabber->get_log());
0710 return false;
0711 }
0712
0713 foreach ($addresses as $address)
0714 {
0715 $this->jabber->send_message($address, $this->msg, $this->subject);
0716 }
0717
0718 $this->jabber->disconnect();
0719 }
0720 else
0721 {
0722 $this->queue->put('jabber', array(
0723 'addresses' => $addresses,
0724 'subject' => $this->subject,
0725 'msg' => $this->msg)
0726 );
0727 }
0728 unset($addresses);
0729 return true;
0730 }
0731
0732 /**
0733 * Setup template engine
0734 */
0735 protected function setup_template()
0736 {
0737 global $phpbb_container, $phpbb_dispatcher;
0738
0739 if ($this->template instanceof \phpbb\template\template)
0740 {
0741 return;
0742 }
0743
0744 $template_environment = new \phpbb\template\twig\environment(
0745 $phpbb_container->get('config'),
0746 $phpbb_container->get('filesystem'),
0747 $phpbb_container->get('path_helper'),
0748 $phpbb_container->getParameter('core.template.cache_path'),
0749 $phpbb_container->get('ext.manager'),
0750 new \phpbb\template\twig\loader(
0751 $phpbb_container->get('filesystem')
0752 ),
0753 $phpbb_dispatcher,
0754 array()
0755 );
0756 $template_environment->setLexer($phpbb_container->get('template.twig.lexer'));
0757
0758 $this->template = new \phpbb\template\twig\twig(
0759 $phpbb_container->get('path_helper'),
0760 $phpbb_container->get('config'),
0761 new \phpbb\template\context(),
0762 $template_environment,
0763 $phpbb_container->getParameter('core.template.cache_path'),
0764 $phpbb_container->get('user'),
0765 $phpbb_container->get('template.twig.extensions.collection'),
0766 $phpbb_container->get('ext.manager')
0767 );
0768 }
0769
0770 /**
0771 * Set template paths to load
0772 */
0773 protected function set_template_paths($path_name, $paths)
0774 {
0775 $this->setup_template();
0776
0777 $this->template->set_custom_style($path_name, $paths);
0778 }
0779 }
0780
0781 /**
0782 * handling email and jabber queue
0783 */
0784 class queue
0785 {
0786 var $data = array();
0787 var $queue_data = array();
0788 var $package_size = 0;
0789 var $cache_file = '';
0790 var $eol = "\n";
0791
0792 /**
0793 * @var \phpbb\filesystem\filesystem_interface
0794 */
0795 protected $filesystem;
0796
0797 /**
0798 * constructor
0799 */
0800 function __construct()
0801 {
0802 global $phpEx, $phpbb_root_path, $phpbb_filesystem, $phpbb_container;
0803
0804 $this->data = array();
0805 $this->cache_file = $phpbb_container->getParameter('core.cache_dir') . "queue.$phpEx";
0806 $this->filesystem = $phpbb_filesystem;
0807 }
0808
0809 /**
0810 * Init a queue object
0811 */
0812 function init($object, $package_size)
0813 {
0814 $this->data[$object] = array();
0815 $this->data[$object]['package_size'] = $package_size;
0816 $this->data[$object]['data'] = array();
0817 }
0818
0819 /**
0820 * Put object in queue
0821 */
0822 function put($object, $scope)
0823 {
0824 $this->data[$object]['data'][] = $scope;
0825 }
0826
0827 /**
0828 * Process queue
0829 * Using lock file
0830 */
0831 function process()
0832 {
0833 global $config, $phpEx, $phpbb_root_path, $user, $phpbb_dispatcher;
0834
0835 $lock = new \phpbb\lock\flock($this->cache_file);
0836 $lock->acquire();
0837
0838 // avoid races, check file existence once
0839 $have_cache_file = file_exists($this->cache_file);
0840 if (!$have_cache_file || $config['last_queue_run'] > time() - $config['queue_interval'])
0841 {
0842 if (!$have_cache_file)
0843 {
0844 $config->set('last_queue_run', time(), false);
0845 }
0846
0847 $lock->release();
0848 return;
0849 }
0850
0851 $config->set('last_queue_run', time(), false);
0852
0853 include($this->cache_file);
0854
0855 foreach ($this->queue_data as $object => $data_ary)
0856 {
0857 @set_time_limit(0);
0858
0859 if (!isset($data_ary['package_size']))
0860 {
0861 $data_ary['package_size'] = 0;
0862 }
0863
0864 $package_size = $data_ary['package_size'];
0865 $num_items = (!$package_size || count($data_ary['data']) < $package_size) ? count($data_ary['data']) : $package_size;
0866
0867 /*
0868 * This code is commented out because it causes problems on some web hosts.
0869 * The core problem is rather restrictive email sending limits.
0870 * This code is nly useful if you have no such restrictions from the
0871 * web host and the package size setting is wrong.
0872
0873 // If the amount of emails to be sent is way more than package_size than we need to increase it to prevent backlogs...
0874 if (count($data_ary['data']) > $package_size * 2.5)
0875 {
0876 $num_items = count($data_ary['data']);
0877 }
0878 */
0879
0880 switch ($object)
0881 {
0882 case 'email':
0883 // Delete the email queued objects if mailing is disabled
0884 if (!$config['email_enable'])
0885 {
0886 unset($this->queue_data['email']);
0887 continue 2;
0888 }
0889 break;
0890
0891 case 'jabber':
0892 if (!$config['jab_enable'])
0893 {
0894 unset($this->queue_data['jabber']);
0895 continue 2;
0896 }
0897
0898 include_once($phpbb_root_path . 'includes/functions_jabber.' . $phpEx);
0899 $this->jabber = new jabber($config['jab_host'], $config['jab_port'], $config['jab_username'], html_entity_decode($config['jab_password'], ENT_COMPAT), $config['jab_use_ssl'], $config['jab_verify_peer'], $config['jab_verify_peer_name'], $config['jab_allow_self_signed']);
0900
0901 if (!$this->jabber->connect())
0902 {
0903 $messenger = new messenger();
0904 $messenger->error('JABBER', $user->lang['ERR_JAB_CONNECT']);
0905 continue 2;
0906 }
0907
0908 if (!$this->jabber->login())
0909 {
0910 $messenger = new messenger();
0911 $messenger->error('JABBER', $user->lang['ERR_JAB_AUTH']);
0912 continue 2;
0913 }
0914
0915 break;
0916
0917 default:
0918 $lock->release();
0919 return;
0920 }
0921
0922 for ($i = 0; $i < $num_items; $i++)
0923 {
0924 // Make variables available...
0925 extract(array_shift($this->queue_data[$object]['data']));
0926
0927 switch ($object)
0928 {
0929 case 'email':
0930 $break = false;
0931 /**
0932 * Event to send message via external transport
0933 *
0934 * @event core.notification_message_process
0935 * @var bool break Flag indicating if the function return after hook
0936 * @var array addresses The message recipients
0937 * @var string subject The message subject
0938 * @var string msg The message text
0939 * @since 3.2.4-RC1
0940 */
0941 $vars = array(
0942 'break',
0943 'addresses',
0944 'subject',
0945 'msg',
0946 );
0947 extract($phpbb_dispatcher->trigger_event('core.notification_message_process', compact($vars)));
0948
0949 if (!$break)
0950 {
0951 $err_msg = '';
0952 $to = (!$to) ? 'undisclosed-recipients:;' : $to;
0953
0954 if ($config['smtp_delivery'])
0955 {
0956 $result = smtpmail($addresses, mail_encode($subject), wordwrap(utf8_wordwrap($msg), 997, "\n", true), $err_msg, $headers);
0957 }
0958 else
0959 {
0960 $encode_eol = $config['smtp_delivery'] || PHP_VERSION_ID >= 80000 ? "\r\n" : PHP_EOL;
0961 $result = phpbb_mail($to, $subject, $msg, $headers, $encode_eol, $err_msg);
0962 }
0963
0964 if (!$result)
0965 {
0966 $messenger = new messenger();
0967 $messenger->error('EMAIL', $err_msg);
0968 continue 2;
0969 }
0970 }
0971 break;
0972
0973 case 'jabber':
0974 foreach ($addresses as $address)
0975 {
0976 if ($this->jabber->send_message($address, $msg, $subject) === false)
0977 {
0978 $messenger = new messenger();
0979 $messenger->error('JABBER', $this->jabber->get_log());
0980 continue 3;
0981 }
0982 }
0983 break;
0984 }
0985 }
0986
0987 // No more data for this object? Unset it
0988 if (!count($this->queue_data[$object]['data']))
0989 {
0990 unset($this->queue_data[$object]);
0991 }
0992
0993 // Post-object processing
0994 switch ($object)
0995 {
0996 case 'jabber':
0997 // Hang about a couple of secs to ensure the messages are
0998 // handled, then disconnect
0999 $this->jabber->disconnect();
1000 break;
1001 }
1002 }
1003
1004 if (!count($this->queue_data))
1005 {
1006 @unlink($this->cache_file);
1007 }
1008 else
1009 {
1010 if ($fp = @fopen($this->cache_file, 'wb'))
1011 {
1012 fwrite($fp, "<?php\nif (!defined('IN_PHPBB')) exit;\n\$this->queue_data = unserialize(" . var_export(serialize($this->queue_data), true) . ");\n\n?>");
1013 fclose($fp);
1014
1015 if (function_exists('opcache_invalidate'))
1016 {
1017 @opcache_invalidate($this->cache_file);
1018 }
1019
1020 try
1021 {
1022 $this->filesystem->phpbb_chmod($this->cache_file, \phpbb\filesystem\filesystem_interface::CHMOD_READ | \phpbb\filesystem\filesystem_interface::CHMOD_WRITE);
1023 }
1024 catch (\phpbb\filesystem\exception\filesystem_exception $e)
1025 {
1026 // Do nothing
1027 }
1028 }
1029 }
1030
1031 $lock->release();
1032 }
1033
1034 /**
1035 * Save queue
1036 */
1037 function save()
1038 {
1039 if (!count($this->data))
1040 {
1041 return;
1042 }
1043
1044 $lock = new \phpbb\lock\flock($this->cache_file);
1045 $lock->acquire();
1046
1047 if (file_exists($this->cache_file))
1048 {
1049 include($this->cache_file);
1050
1051 foreach ($this->queue_data as $object => $data_ary)
1052 {
1053 if (isset($this->data[$object]) && count($this->data[$object]))
1054 {
1055 $this->data[$object]['data'] = array_merge($data_ary['data'], $this->data[$object]['data']);
1056 }
1057 else
1058 {
1059 $this->data[$object]['data'] = $data_ary['data'];
1060 }
1061 }
1062 }
1063
1064 if ($fp = @fopen($this->cache_file, 'w'))
1065 {
1066 fwrite($fp, "<?php\nif (!defined('IN_PHPBB')) exit;\n\$this->queue_data = unserialize(" . var_export(serialize($this->data), true) . ");\n\n?>");
1067 fclose($fp);
1068
1069 if (function_exists('opcache_invalidate'))
1070 {
1071 @opcache_invalidate($this->cache_file);
1072 }
1073
1074 try
1075 {
1076 $this->filesystem->phpbb_chmod($this->cache_file, \phpbb\filesystem\filesystem_interface::CHMOD_READ | \phpbb\filesystem\filesystem_interface::CHMOD_WRITE);
1077 }
1078 catch (\phpbb\filesystem\exception\filesystem_exception $e)
1079 {
1080 // Do nothing
1081 }
1082
1083 $this->data = array();
1084 }
1085
1086 $lock->release();
1087 }
1088 }
1089
1090 /**
1091 * Replacement or substitute for PHP's mail command
1092 */
1093 function smtpmail($addresses, $subject, $message, &$err_msg, $headers = false)
1094 {
1095 global $config, $user;
1096
1097 // Fix any bare linefeeds in the message to make it RFC821 Compliant.
1098 $message = preg_replace("#(?<!\r)\n#si", "\r\n", $message);
1099
1100 if ($headers !== false)
1101 {
1102 if (!is_array($headers))
1103 {
1104 // Make sure there are no bare linefeeds in the headers
1105 $headers = preg_replace('#(?<!\r)\n#si', "\n", $headers);
1106 $headers = explode("\n", $headers);
1107 }
1108
1109 // Ok this is rather confusing all things considered,
1110 // but we have to grab bcc and cc headers and treat them differently
1111 // Something we really didn't take into consideration originally
1112 $headers_used = array();
1113
1114 foreach ($headers as $header)
1115 {
1116 if (strpos(strtolower($header), 'cc:') === 0 || strpos(strtolower($header), 'bcc:') === 0)
1117 {
1118 continue;
1119 }
1120 $headers_used[] = trim($header);
1121 }
1122
1123 $headers = chop(implode("\r\n", $headers_used));
1124 }
1125
1126 if (trim($subject) == '')
1127 {
1128 $err_msg = (isset($user->lang['NO_EMAIL_SUBJECT'])) ? $user->lang['NO_EMAIL_SUBJECT'] : 'No email subject specified';
1129 return false;
1130 }
1131
1132 if (trim($message) == '')
1133 {
1134 $err_msg = (isset($user->lang['NO_EMAIL_MESSAGE'])) ? $user->lang['NO_EMAIL_MESSAGE'] : 'Email message was blank';
1135 return false;
1136 }
1137
1138 $mail_rcpt = $mail_to = $mail_cc = array();
1139
1140 // Build correct addresses for RCPT TO command and the client side display (TO, CC)
1141 if (isset($addresses['to']) && count($addresses['to']))
1142 {
1143 foreach ($addresses['to'] as $which_ary)
1144 {
1145 $mail_to[] = ($which_ary['name'] != '') ? mail_encode(trim($which_ary['name'])) . ' <' . trim($which_ary['email']) . '>' : '<' . trim($which_ary['email']) . '>';
1146 $mail_rcpt['to'][] = '<' . trim($which_ary['email']) . '>';
1147 }
1148 }
1149
1150 if (isset($addresses['bcc']) && count($addresses['bcc']))
1151 {
1152 foreach ($addresses['bcc'] as $which_ary)
1153 {
1154 $mail_rcpt['bcc'][] = '<' . trim($which_ary['email']) . '>';
1155 }
1156 }
1157
1158 if (isset($addresses['cc']) && count($addresses['cc']))
1159 {
1160 foreach ($addresses['cc'] as $which_ary)
1161 {
1162 $mail_cc[] = ($which_ary['name'] != '') ? mail_encode(trim($which_ary['name'])) . ' <' . trim($which_ary['email']) . '>' : '<' . trim($which_ary['email']) . '>';
1163 $mail_rcpt['cc'][] = '<' . trim($which_ary['email']) . '>';
1164 }
1165 }
1166
1167 $smtp = new smtp_class();
1168
1169 $errno = 0;
1170 $errstr = '';
1171
1172 $smtp->add_backtrace('Connecting to ' . $config['smtp_host'] . ':' . $config['smtp_port']);
1173
1174 // Ok we have error checked as much as we can to this point let's get on it already.
1175 if (!class_exists('\phpbb\error_collector'))
1176 {
1177 global $phpbb_root_path, $phpEx;
1178 include($phpbb_root_path . 'includes/error_collector.' . $phpEx);
1179 }
1180 $collector = new \phpbb\error_collector;
1181 $collector->install();
1182
1183 $options = array();
1184 $verify_peer = (bool) $config['smtp_verify_peer'];
1185 $verify_peer_name = (bool) $config['smtp_verify_peer_name'];
1186 $allow_self_signed = (bool) $config['smtp_allow_self_signed'];
1187 $remote_socket = $config['smtp_host'] . ':' . $config['smtp_port'];
1188
1189 // Set ssl context options, see http://php.net/manual/en/context.ssl.php
1190 $options['ssl'] = array('verify_peer' => $verify_peer, 'verify_peer_name' => $verify_peer_name, 'allow_self_signed' => $allow_self_signed);
1191 $socket_context = stream_context_create($options);
1192
1193 $smtp->socket = @stream_socket_client($remote_socket, $errno, $errstr, 20, STREAM_CLIENT_CONNECT, $socket_context);
1194 $collector->uninstall();
1195 $error_contents = $collector->format_errors();
1196
1197 if (!$smtp->socket)
1198 {
1199 if ($errstr)
1200 {
1201 $errstr = utf8_convert_message($errstr);
1202 }
1203
1204 $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";
1205 $err_msg .= ($error_contents) ? '<br /><br />' . htmlspecialchars($error_contents, ENT_COMPAT) : '';
1206 return false;
1207 }
1208
1209 // Wait for reply
1210 if ($err_msg = $smtp->server_parse('220', __LINE__))
1211 {
1212 $smtp->close_session($err_msg);
1213 return false;
1214 }
1215
1216 // Let me in. This function handles the complete authentication process
1217 if ($err_msg = $smtp->log_into_server($config['smtp_host'], $config['smtp_username'], html_entity_decode($config['smtp_password'], ENT_COMPAT), $config['smtp_auth_method']))
1218 {
1219 $smtp->close_session($err_msg);
1220 return false;
1221 }
1222
1223 // From this point onward most server response codes should be 250
1224 // Specify who the mail is from....
1225 $smtp->server_send('MAIL FROM:<' . $config['board_email'] . '>');
1226 if ($err_msg = $smtp->server_parse('250', __LINE__))
1227 {
1228 $smtp->close_session($err_msg);
1229 return false;
1230 }
1231
1232 // Specify each user to send to and build to header.
1233 $to_header = implode(', ', $mail_to);
1234 $cc_header = implode(', ', $mail_cc);
1235
1236 // Now tell the MTA to send the Message to the following people... [TO, BCC, CC]
1237 $rcpt = false;
1238 foreach ($mail_rcpt as $type => $mail_to_addresses)
1239 {
1240 foreach ($mail_to_addresses as $mail_to_address)
1241 {
1242 // Add an additional bit of error checking to the To field.
1243 if (preg_match('#[^ ]+\@[^ ]+#', $mail_to_address))
1244 {
1245 $smtp->server_send("RCPT TO:$mail_to_address");
1246 if ($err_msg = $smtp->server_parse('250', __LINE__))
1247 {
1248 // We continue... if users are not resolved we do not care
1249 if ($smtp->numeric_response_code != 550)
1250 {
1251 $smtp->close_session($err_msg);
1252 return false;
1253 }
1254 }
1255 else
1256 {
1257 $rcpt = true;
1258 }
1259 }
1260 }
1261 }
1262
1263 // 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.
1264 if (!$rcpt)
1265 {
1266 $user->session_begin();
1267 $err_msg .= '<br /><br />';
1268 $err_msg .= (isset($user->lang['INVALID_EMAIL_LOG'])) ? sprintf($user->lang['INVALID_EMAIL_LOG'], htmlspecialchars($mail_to_address, ENT_COMPAT)) : '<strong>' . htmlspecialchars($mail_to_address, ENT_COMPAT) . '</strong> possibly an invalid email address?';
1269 $smtp->close_session($err_msg);
1270 return false;
1271 }
1272
1273 // Ok now we tell the server we are ready to start sending data
1274 $smtp->server_send('DATA');
1275
1276 // This is the last response code we look for until the end of the message.
1277 if ($err_msg = $smtp->server_parse('354', __LINE__))
1278 {
1279 $smtp->close_session($err_msg);
1280 return false;
1281 }
1282
1283 // Send the Subject Line...
1284 $smtp->server_send("Subject: $subject");
1285
1286 // Now the To Header.
1287 $to_header = ($to_header == '') ? 'undisclosed-recipients:;' : $to_header;
1288 $smtp->server_send("To: $to_header");
1289
1290 // Now the CC Header.
1291 if ($cc_header != '')
1292 {
1293 $smtp->server_send("CC: $cc_header");
1294 }
1295
1296 // Now any custom headers....
1297 if ($headers !== false)
1298 {
1299 $smtp->server_send("$headers\r\n");
1300 }
1301
1302 // Ok now we are ready for the message...
1303 $smtp->server_send($message);
1304
1305 // Ok the all the ingredients are mixed in let's cook this puppy...
1306 $smtp->server_send('.');
1307 if ($err_msg = $smtp->server_parse('250', __LINE__))
1308 {
1309 $smtp->close_session($err_msg);
1310 return false;
1311 }
1312
1313 // Now tell the server we are done and close the socket...
1314 $smtp->server_send('QUIT');
1315 $smtp->close_session($err_msg);
1316
1317 return true;
1318 }
1319
1320 /**
1321 * SMTP Class
1322 * Auth Mechanisms originally taken from the AUTH Modules found within the PHP Extension and Application Repository (PEAR)
1323 * See docs/AUTHORS for more details
1324 */
1325 class smtp_class
1326 {
1327 var $server_response = '';
1328 var $socket = 0;
1329 protected $socket_tls = false;
1330 var $responses = array();
1331 var $commands = array();
1332 var $numeric_response_code = 0;
1333
1334 var $backtrace = false;
1335 var $backtrace_log = array();
1336
1337 function __construct()
1338 {
1339 // Always create a backtrace for admins to identify SMTP problems
1340 $this->backtrace = true;
1341 $this->backtrace_log = array();
1342 }
1343
1344 /**
1345 * Add backtrace message for debugging
1346 */
1347 function add_backtrace($message)
1348 {
1349 if ($this->backtrace)
1350 {
1351 $this->backtrace_log[] = utf8_htmlspecialchars($message, ENT_COMPAT);
1352 }
1353 }
1354
1355 /**
1356 * Send command to smtp server
1357 */
1358 function server_send($command, $private_info = false)
1359 {
1360 fputs($this->socket, $command . "\r\n");
1361
1362 (!$private_info) ? $this->add_backtrace("# $command") : $this->add_backtrace('# Omitting sensitive information');
1363
1364 // We could put additional code here
1365 }
1366
1367 /**
1368 * We use the line to give the support people an indication at which command the error occurred
1369 */
1370 function server_parse($response, $line)
1371 {
1372 global $user;
1373
1374 $this->server_response = '';
1375 $this->responses = array();
1376 $this->numeric_response_code = 0;
1377
1378 while (substr($this->server_response, 3, 1) != ' ')
1379 {
1380 if (!($this->server_response = fgets($this->socket, 256)))
1381 {
1382 return (isset($user->lang['NO_EMAIL_RESPONSE_CODE'])) ? $user->lang['NO_EMAIL_RESPONSE_CODE'] : 'Could not get mail server response codes';
1383 }
1384 $this->responses[] = substr(rtrim($this->server_response), 4);
1385 $this->numeric_response_code = (int) substr($this->server_response, 0, 3);
1386
1387 $this->add_backtrace("LINE: $line <- {$this->server_response}");
1388 }
1389
1390 if (!(substr($this->server_response, 0, 3) == $response))
1391 {
1392 $this->numeric_response_code = (int) substr($this->server_response, 0, 3);
1393 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";
1394 }
1395
1396 return 0;
1397 }
1398
1399 /**
1400 * Close session
1401 */
1402 function close_session(&$err_msg)
1403 {
1404 fclose($this->socket);
1405
1406 if ($this->backtrace)
1407 {
1408 $message = '<h1>Backtrace</h1><p>' . implode('<br />', $this->backtrace_log) . '</p>';
1409 $err_msg .= $message;
1410 }
1411 }
1412
1413 /**
1414 * Log into server and get possible auth codes if neccessary
1415 */
1416 function log_into_server($hostname, $username, $password, $default_auth_method)
1417 {
1418 global $user;
1419
1420 // Here we try to determine the *real* hostname (reverse DNS entry preferrably)
1421 if (function_exists('php_uname') && !empty($local_host = php_uname('n')))
1422 {
1423 // Able to resolve name to IP
1424 if (($addr = @gethostbyname($local_host)) !== $local_host)
1425 {
1426 // Able to resolve IP back to name
1427 if (!empty($name = @gethostbyaddr($addr)) && $name !== $addr)
1428 {
1429 $local_host = $name;
1430 }
1431 }
1432 }
1433 else
1434 {
1435 $local_host = $user->host;
1436 }
1437
1438 // If we are authenticating through pop-before-smtp, we
1439 // have to login ones before we get authenticated
1440 // NOTE: on some configurations the time between an update of the auth database takes so
1441 // long that the first email send does not work. This is not a biggie on a live board (only
1442 // the install mail will most likely fail) - but on a dynamic ip connection this might produce
1443 // severe problems and is not fixable!
1444 if ($default_auth_method == 'POP-BEFORE-SMTP' && $username && $password)
1445 {
1446 global $config;
1447
1448 $errno = 0;
1449 $errstr = '';
1450
1451 $this->server_send("QUIT");
1452 fclose($this->socket);
1453
1454 $this->pop_before_smtp($hostname, $username, $password);
1455 $username = $password = $default_auth_method = '';
1456
1457 // We need to close the previous session, else the server is not
1458 // able to get our ip for matching...
1459 if (!$this->socket = @fsockopen($config['smtp_host'], $config['smtp_port'], $errno, $errstr, 10))
1460 {
1461 if ($errstr)
1462 {
1463 $errstr = utf8_convert_message($errstr);
1464 }
1465
1466 $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";
1467 return $err_msg;
1468 }
1469
1470 // Wait for reply
1471 if ($err_msg = $this->server_parse('220', __LINE__))
1472 {
1473 $this->close_session($err_msg);
1474 return $err_msg;
1475 }
1476 }
1477
1478 $hello_result = $this->hello($local_host);
1479 if (!is_null($hello_result))
1480 {
1481 return $hello_result;
1482 }
1483
1484 // SMTP STARTTLS (RFC 3207)
1485 if (!$this->socket_tls)
1486 {
1487 $this->socket_tls = $this->starttls();
1488
1489 if ($this->socket_tls)
1490 {
1491 // Switched to TLS
1492 // RFC 3207: "The client MUST discard any knowledge obtained from the server, [...]"
1493 // So say hello again
1494 $hello_result = $this->hello($local_host);
1495
1496 if (!is_null($hello_result))
1497 {
1498 return $hello_result;
1499 }
1500 }
1501 }
1502
1503 // If we are not authenticated yet, something might be wrong if no username and passwd passed
1504 if (!$username || !$password)
1505 {
1506 return false;
1507 }
1508
1509 if (!isset($this->commands['AUTH']))
1510 {
1511 return (isset($user->lang['SMTP_NO_AUTH_SUPPORT'])) ? $user->lang['SMTP_NO_AUTH_SUPPORT'] : 'SMTP server does not support authentication';
1512 }
1513
1514 // Get best authentication method
1515 $available_methods = explode(' ', $this->commands['AUTH']);
1516
1517 // Define the auth ordering if the default auth method was not found
1518 $auth_methods = array('PLAIN', 'LOGIN', 'CRAM-MD5', 'DIGEST-MD5');
1519 $method = '';
1520
1521 if (in_array($default_auth_method, $available_methods))
1522 {
1523 $method = $default_auth_method;
1524 }
1525 else
1526 {
1527 foreach ($auth_methods as $_method)
1528 {
1529 if (in_array($_method, $available_methods))
1530 {
1531 $method = $_method;
1532 break;
1533 }
1534 }
1535 }
1536
1537 if (!$method)
1538 {
1539 return (isset($user->lang['NO_SUPPORTED_AUTH_METHODS'])) ? $user->lang['NO_SUPPORTED_AUTH_METHODS'] : 'No supported authentication methods';
1540 }
1541
1542 $method = strtolower(str_replace('-', '_', $method));
1543 return $this->$method($username, $password);
1544 }
1545
1546 /**
1547 * SMTP EHLO/HELO
1548 *
1549 * @return mixed Null if the authentication process is supposed to continue
1550 * False if already authenticated
1551 * Error message (string) otherwise
1552 */
1553 protected function hello($hostname)
1554 {
1555 // Try EHLO first
1556 $this->server_send("EHLO $hostname");
1557 if ($err_msg = $this->server_parse('250', __LINE__))
1558 {
1559 // a 503 response code means that we're already authenticated
1560 if ($this->numeric_response_code == 503)
1561 {
1562 return false;
1563 }
1564
1565 // If EHLO fails, we try HELO
1566 $this->server_send("HELO $hostname");
1567 if ($err_msg = $this->server_parse('250', __LINE__))
1568 {
1569 return ($this->numeric_response_code == 503) ? false : $err_msg;
1570 }
1571 }
1572
1573 foreach ($this->responses as $response)
1574 {
1575 $response = explode(' ', $response);
1576 $response_code = $response[0];
1577 unset($response[0]);
1578 $this->commands[$response_code] = implode(' ', $response);
1579 }
1580 }
1581
1582 /**
1583 * SMTP STARTTLS (RFC 3207)
1584 *
1585 * @return bool Returns true if TLS was started
1586 * Otherwise false
1587 */
1588 protected function starttls()
1589 {
1590 global $config;
1591
1592 // allow SMTPS (what was used by phpBB 3.0) if hostname is prefixed with tls:// or ssl://
1593 if (strpos($config['smtp_host'], 'tls://') === 0 || strpos($config['smtp_host'], 'ssl://') === 0)
1594 {
1595 return true;
1596 }
1597
1598 if (!function_exists('stream_socket_enable_crypto'))
1599 {
1600 return false;
1601 }
1602
1603 if (!isset($this->commands['STARTTLS']))
1604 {
1605 return false;
1606 }
1607
1608 $this->server_send('STARTTLS');
1609
1610 if ($err_msg = $this->server_parse('220', __LINE__))
1611 {
1612 return false;
1613 }
1614
1615 $result = false;
1616 $stream_meta = stream_get_meta_data($this->socket);
1617
1618 if (stream_set_blocking($this->socket, 1))
1619 {
1620 $result = stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
1621 stream_set_blocking($this->socket, (int) $stream_meta['blocked']);
1622 }
1623
1624 return $result;
1625 }
1626
1627 /**
1628 * Pop before smtp authentication
1629 */
1630 function pop_before_smtp($hostname, $username, $password)
1631 {
1632 global $user;
1633
1634 if (!$this->socket = @fsockopen($hostname, 110, $errno, $errstr, 10))
1635 {
1636 if ($errstr)
1637 {
1638 $errstr = utf8_convert_message($errstr);
1639 }
1640
1641 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";
1642 }
1643
1644 $this->server_send("USER $username", true);
1645 if ($err_msg = $this->server_parse('+OK', __LINE__))
1646 {
1647 return $err_msg;
1648 }
1649
1650 $this->server_send("PASS $password", true);
1651 if ($err_msg = $this->server_parse('+OK', __LINE__))
1652 {
1653 return $err_msg;
1654 }
1655
1656 $this->server_send('QUIT');
1657 fclose($this->socket);
1658
1659 return false;
1660 }
1661
1662 /**
1663 * Plain authentication method
1664 */
1665 function plain($username, $password)
1666 {
1667 $this->server_send('AUTH PLAIN');
1668 if ($err_msg = $this->server_parse('334', __LINE__))
1669 {
1670 return ($this->numeric_response_code == 503) ? false : $err_msg;
1671 }
1672
1673 $base64_method_plain = base64_encode("\0" . $username . "\0" . $password);
1674 $this->server_send($base64_method_plain, true);
1675 if ($err_msg = $this->server_parse('235', __LINE__))
1676 {
1677 return $err_msg;
1678 }
1679
1680 return false;
1681 }
1682
1683 /**
1684 * Login authentication method
1685 */
1686 function login($username, $password)
1687 {
1688 $this->server_send('AUTH LOGIN');
1689 if ($err_msg = $this->server_parse('334', __LINE__))
1690 {
1691 return ($this->numeric_response_code == 503) ? false : $err_msg;
1692 }
1693
1694 $this->server_send(base64_encode($username), true);
1695 if ($err_msg = $this->server_parse('334', __LINE__))
1696 {
1697 return $err_msg;
1698 }
1699
1700 $this->server_send(base64_encode($password), true);
1701 if ($err_msg = $this->server_parse('235', __LINE__))
1702 {
1703 return $err_msg;
1704 }
1705
1706 return false;
1707 }
1708
1709 /**
1710 * cram_md5 authentication method
1711 */
1712 function cram_md5($username, $password)
1713 {
1714 $this->server_send('AUTH CRAM-MD5');
1715 if ($err_msg = $this->server_parse('334', __LINE__))
1716 {
1717 return ($this->numeric_response_code == 503) ? false : $err_msg;
1718 }
1719
1720 $md5_challenge = base64_decode($this->responses[0]);
1721 $password = (strlen($password) > 64) ? pack('H32', md5($password)) : ((strlen($password) < 64) ? str_pad($password, 64, chr(0)) : $password);
1722 $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))));
1723
1724 $base64_method_cram_md5 = base64_encode($username . ' ' . $md5_digest);
1725
1726 $this->server_send($base64_method_cram_md5, true);
1727 if ($err_msg = $this->server_parse('235', __LINE__))
1728 {
1729 return $err_msg;
1730 }
1731
1732 return false;
1733 }
1734
1735 /**
1736 * digest_md5 authentication method
1737 * A real pain in the ***
1738 */
1739 function digest_md5($username, $password)
1740 {
1741 global $config, $user;
1742
1743 $this->server_send('AUTH DIGEST-MD5');
1744 if ($err_msg = $this->server_parse('334', __LINE__))
1745 {
1746 return ($this->numeric_response_code == 503) ? false : $err_msg;
1747 }
1748
1749 $md5_challenge = base64_decode($this->responses[0]);
1750
1751 // Parse the md5 challenge - from AUTH_SASL (PEAR)
1752 $tokens = array();
1753 while (preg_match('/^([a-z-]+)=("[^"]+(?<!\\\)"|[^,]+)/i', $md5_challenge, $matches))
1754 {
1755 // Ignore these as per rfc2831
1756 if ($matches[1] == 'opaque' || $matches[1] == 'domain')
1757 {
1758 $md5_challenge = substr($md5_challenge, strlen($matches[0]) + 1);
1759 continue;
1760 }
1761
1762 // Allowed multiple "realm" and "auth-param"
1763 if (!empty($tokens[$matches[1]]) && ($matches[1] == 'realm' || $matches[1] == 'auth-param'))
1764 {
1765 if (is_array($tokens[$matches[1]]))
1766 {
1767 $tokens[$matches[1]][] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
1768 }
1769 else
1770 {
1771 $tokens[$matches[1]] = array($tokens[$matches[1]], preg_replace('/^"(.*)"$/', '\\1', $matches[2]));
1772 }
1773 }
1774 else if (!empty($tokens[$matches[1]])) // Any other multiple instance = failure
1775 {
1776 $tokens = array();
1777 break;
1778 }
1779 else
1780 {
1781 $tokens[$matches[1]] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
1782 }
1783
1784 // Remove the just parsed directive from the challenge
1785 $md5_challenge = substr($md5_challenge, strlen($matches[0]) + 1);
1786 }
1787
1788 // Realm
1789 if (empty($tokens['realm']))
1790 {
1791 $tokens['realm'] = (function_exists('php_uname')) ? php_uname('n') : $user->host;
1792 }
1793
1794 // Maxbuf
1795 if (empty($tokens['maxbuf']))
1796 {
1797 $tokens['maxbuf'] = 65536;
1798 }
1799
1800 // Required: nonce, algorithm
1801 if (empty($tokens['nonce']) || empty($tokens['algorithm']))
1802 {
1803 $tokens = array();
1804 }
1805 $md5_challenge = $tokens;
1806
1807 if (!empty($md5_challenge))
1808 {
1809 $str = '';
1810 for ($i = 0; $i < 32; $i++)
1811 {
1812 $str .= chr(mt_rand(0, 255));
1813 }
1814 $cnonce = base64_encode($str);
1815
1816 $digest_uri = 'smtp/' . $config['smtp_host'];
1817
1818 $auth_1 = sprintf('%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $username, $md5_challenge['realm'], $password))), $md5_challenge['nonce'], $cnonce);
1819 $auth_2 = 'AUTHENTICATE:' . $digest_uri;
1820 $response_value = md5(sprintf('%s:%s:00000001:%s:auth:%s', md5($auth_1), $md5_challenge['nonce'], $cnonce, md5($auth_2)));
1821
1822 $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']);
1823 }
1824 else
1825 {
1826 return (isset($user->lang['INVALID_DIGEST_CHALLENGE'])) ? $user->lang['INVALID_DIGEST_CHALLENGE'] : 'Invalid digest challenge';
1827 }
1828
1829 $base64_method_digest_md5 = base64_encode($input_string);
1830 $this->server_send($base64_method_digest_md5, true);
1831 if ($err_msg = $this->server_parse('334', __LINE__))
1832 {
1833 return $err_msg;
1834 }
1835
1836 $this->server_send(' ');
1837 if ($err_msg = $this->server_parse('235', __LINE__))
1838 {
1839 return $err_msg;
1840 }
1841
1842 return false;
1843 }
1844 }
1845
1846 /**
1847 * Encodes the given string for proper display in UTF-8 or US-ASCII.
1848 *
1849 * This version is based on iconv_mime_encode() implementation
1850 * from symfomy/polyfill-iconv
1851 * https://github.com/symfony/polyfill-iconv/blob/fd324208ec59a39ebe776e6e9ec5540ad4f40aaa/Iconv.php#L355
1852 *
1853 * @param string $str
1854 * @param string $eol Lines delimiter (optional to be backwards compatible)
1855 *
1856 * @return string
1857 */
1858 function mail_encode($str, $eol = "\r\n")
1859 {
1860 // Check if string contains ASCII only characters
1861 $is_ascii = strlen($str) === utf8_strlen($str);
1862
1863 $scheme = $is_ascii ? 'Q' : 'B';
1864
1865 // Define start delimiter, end delimiter
1866 // Use the Quoted-Printable encoding for ASCII strings to avoid unnecessary encoding in Base64
1867 $start = '=?' . ($is_ascii ? 'US-ASCII' : 'UTF-8') . '?' . $scheme . '?';
1868 $end = '?=';
1869
1870 // Maximum encoded-word length is 75 as per RFC 2047 section 2.
1871 // $split_length *must* be a multiple of 4, but <= 75 - strlen($start . $eol . $end)!!!
1872 $split_length = 75 - strlen($start . $eol . $end);
1873 $split_length = $split_length - $split_length % 4;
1874
1875 $line_length = strlen($start) + strlen($end);
1876 $line_offset = strlen($start) + 1;
1877 $line_data = '';
1878
1879 $is_quoted_printable = 'Q' === $scheme;
1880
1881 preg_match_all('/./us', $str, $chars);
1882 $chars = $chars[0] ?? [];
1883
1884 $str = [];
1885 foreach ($chars as $char)
1886 {
1887 $encoded_char = $is_quoted_printable
1888 ? $char = preg_replace_callback(
1889 '/[()<>@,;:\\\\".\[\]=_?\x20\x00-\x1F\x80-\xFF]/',
1890 function ($matches)
1891 {
1892 $hex = dechex(ord($matches[0]));
1893 $hex = strlen($hex) == 1 ? "0$hex" : $hex;
1894 return '=' . strtoupper($hex);
1895 },
1896 $char
1897 )
1898 : base64_encode($line_data . $char);
1899
1900 if (isset($encoded_char[$split_length - $line_length]))
1901 {
1902 if (!$is_quoted_printable)
1903 {
1904 $line_data = base64_encode($line_data);
1905 }
1906 $str[] = $start . $line_data . $end;
1907 $line_length = $line_offset;
1908 $line_data = '';
1909 }
1910
1911 $line_data .= $char;
1912 $is_quoted_printable && $line_length += strlen($char);
1913 }
1914
1915 if ($line_data !== '')
1916 {
1917 if (!$is_quoted_printable)
1918 {
1919 $line_data = base64_encode($line_data);
1920 }
1921 $str[] = $start . $line_data . $end;
1922 }
1923
1924 return implode($eol . ' ', $str);
1925 }
1926
1927 /**
1928 * Wrapper for sending out emails with the PHP's mail function
1929 */
1930 function phpbb_mail($to, $subject, $msg, $headers, $eol, &$err_msg)
1931 {
1932 global $config, $phpbb_root_path, $phpEx, $phpbb_dispatcher;
1933
1934 // Convert Numeric Character References to UTF-8 chars (ie. Emojis)
1935 $subject = utf8_decode_ncr($subject);
1936 $msg = utf8_decode_ncr($msg);
1937
1938 /**
1939 * We use the EOL character for the OS here because the PHP mail function does not correctly transform line endings.
1940 * On Windows SMTP is used (SMTP is \r\n), on UNIX a command is used...
1941 * Reference: http://bugs.php.net/bug.php?id=15841
1942 */
1943 $headers = implode($eol, $headers);
1944
1945 if (!class_exists('\phpbb\error_collector'))
1946 {
1947 include($phpbb_root_path . 'includes/error_collector.' . $phpEx);
1948 }
1949
1950 $collector = new \phpbb\error_collector;
1951 $collector->install();
1952
1953 /**
1954 * On some PHP Versions mail() *may* fail if there are newlines within the subject.
1955 * Newlines are used as a delimiter for lines in mail_encode() according to RFC 2045 section 6.8.
1956 * Because PHP can't decide what is wanted we revert back to the non-RFC-compliant way of separating by one space
1957 * (Use '' as parameter to mail_encode() results in SPACE used)
1958 */
1959 $additional_parameters = $config['email_force_sender'] ? '-f' . $config['board_email'] : '';
1960
1961 /**
1962 * Modify data before sending out emails with PHP's mail function
1963 *
1964 * @event core.phpbb_mail_before
1965 * @var string to The message recipient
1966 * @var string subject The message subject
1967 * @var string msg The message text
1968 * @var string headers The email headers
1969 * @var string eol The endline character
1970 * @var string additional_parameters The additional parameters
1971 * @since 3.3.6-RC1
1972 */
1973 $vars = [
1974 'to',
1975 'subject',
1976 'msg',
1977 'headers',
1978 'eol',
1979 'additional_parameters',
1980 ];
1981 extract($phpbb_dispatcher->trigger_event('core.phpbb_mail_before', compact($vars)));
1982
1983 $result = mail($to, mail_encode($subject, ''), wordwrap(utf8_wordwrap($msg), 997, "\n", true), $headers, $additional_parameters);
1984
1985 /**
1986 * Execute code after sending out emails with PHP's mail function
1987 *
1988 * @event core.phpbb_mail_after
1989 * @var string to The message recipient
1990 * @var string subject The message subject
1991 * @var string msg The message text
1992 * @var string headers The email headers
1993 * @var string eol The endline character
1994 * @var string additional_parameters The additional parameters
1995 * @var bool result True if the email was sent, false otherwise
1996 * @since 3.3.6-RC1
1997 */
1998 $vars = [
1999 'to',
2000 'subject',
2001 'msg',
2002 'headers',
2003 'eol',
2004 'additional_parameters',
2005 'result',
2006 ];
2007 extract($phpbb_dispatcher->trigger_event('core.phpbb_mail_after', compact($vars)));
2008
2009 $collector->uninstall();
2010 $err_msg = $collector->format_errors();
2011
2012 return $result;
2013 }
2014