phpBB
Statistics
| Revision:

root / branches / phpBB-3_0_0 / phpBB / includes / functions_messenger.php

History | View | Annotate | Download (41.5 kB)

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