[ Index ]

PHP Cross Reference of phpBB 3.0 Beta 3

title

Body

[close]

/includes/ -> functions_messenger.php (source)

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


Generated: Wed Nov 22 00:35:05 2006 Cross-referenced by PHPXref 0.6