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.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

functions_messenger.php

Zuletzt modifiziert: 02.04.2025, 15:01 - Dateigröße: 51.84 KiB


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