phpBB
Statistics
| Revision:

root / trunk / phpBB / includes / functions_messenger.php

History | View | Annotate | Download (43.2 kB)

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