~azzar1/unity/add-show-desktop-key

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