Project

General

Profile

Download (17 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * notices.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2005 Colin Smith (ethethlay@gmail.com)
7
 * Copyright (c) 2005-2013 BSD Perimeter
8
 * Copyright (c) 2013-2016 Electric Sheep Fencing
9
 * Copyright (c) 2014-2023 Rubicon Communications, LLC (Netgate)
10
 * All rights reserved.
11
 *
12
 * Licensed under the Apache License, Version 2.0 (the "License");
13
 * you may not use this file except in compliance with the License.
14
 * You may obtain a copy of the License at
15
 *
16
 * http://www.apache.org/licenses/LICENSE-2.0
17
 *
18
 * Unless required by applicable law or agreed to in writing, software
19
 * distributed under the License is distributed on an "AS IS" BASIS,
20
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
 * See the License for the specific language governing permissions and
22
 * limitations under the License.
23
 */
24

    
25
require_once("globals.inc");
26
require_once("functions.inc");
27
require_once("led.inc");
28

    
29
$notice_path = g_get('tmp_path') . '/notices';
30
global $smtp_authentication_mechanisms;
31
$smtp_authentication_mechanisms = array(
32
	'PLAIN' => 'PLAIN',
33
	'LOGIN' => 'LOGIN');
34
/* Other SMTP Authentication Mechanisms that could be supported.
35
 * Note that MD5 is no longer considered secure.
36
 *	'GSSAPI' => 'GSSAPI ' . gettext("Generic Security Services Application Program Interface")
37
 *	'DIGEST-MD5' => 'DIGEST-MD5 ' . gettext("Digest access authentication")
38
 *	'MD5' => 'MD5'
39
 *	'CRAM-MD5' => 'CRAM-MD5'
40
*/
41
global $pushover_sounds;
42
$pushover_sounds = array(
43
	'devicedefault' => 'Device Default',
44
	'pushover' => 'Pushover',
45
	'bike' => 'Bike',
46
	'bugle' => 'Bugle',
47
	'cashregister' => 'Cash Register',
48
	'classical' => 'Classical',
49
	'cosmic' => 'Cosmic',
50
	'falling' => 'Falling',
51
	'gamelan' => 'Gamelan',
52
	'incoming' => 'Incoming',
53
	'intermission' => 'Intermission',
54
	'magic' => 'Magic',
55
	'mechanical' => 'Mechanical',
56
	'pianobar' => 'Piano Bar',
57
	'siren' => 'Siren',
58
	'spacealarm' => 'Space Alarm',
59
	'tugboat' => 'Tug Boat',
60
	'alien' => 'Alien (long)',
61
	'climb' => 'Climb (long)',
62
	'persistent' => 'Persistent (long)',
63
	'echo' => 'Echo (long)',
64
	'updown' => 'Up Down (long)',
65
	'vibrate' => 'Vibrate only',
66
	'none' => 'None (silent)');
67

    
68
/****f* notices/file_notice
69
 * NAME
70
 *   file_notice
71
 * INPUTS
72
 *	 $id, $notice, $category, $url, $priority, $local_only
73
 * RESULT
74
 *   Files a notice and kicks off the various alerts, smtp, telegram, pushover, system log, LED's, etc.
75
 *   If $local_only is true then the notice is not sent to external places (smtp, telegram, pushover)
76
 ******/
77
function file_notice($id, $notice, $category = "General", $url = "", $priority = 1, $local_only = false) {
78
	/*
79
	 * $category - Category that this notice should be displayed under. This can be arbitrary,
80
	 * 	       but a page must be set to receive this messages for it to be displayed.
81
	 *
82
	 * $priority - A notice's priority. Higher numbers indicate greater severity.
83
	 *	       0 = informational, 1 = warning, 2 = error, etc. This may also be arbitrary,
84
	 */
85
	global $notice_path;
86
	if (!$queue = get_notices()) {
87
		$queue = array();
88
	}
89
	$queuekey = time();
90
	$toqueue = array(
91
				'id'		=> htmlentities($id),
92
				'notice'	=> htmlentities($notice),
93
				'url'		=> htmlentities($url),
94
				'category'	=> htmlentities($category),
95
				'priority'	=> htmlentities($priority),
96
			);
97
	while (isset($queue[$queuekey])) {
98
		$queuekey++;
99
	}
100
	$queue[$queuekey] = $toqueue;
101
	$queueout = fopen($notice_path, "w");
102
	if (!$queueout) {
103
		log_error(sprintf(gettext("Could not open %s for writing"), $notice_path));
104
		return;
105
	}
106
	fwrite($queueout, serialize($queue));
107
	fclose($queueout);
108
	log_error(sprintf(gettext("New alert found: %s"), $notice));
109
	/* soekris */
110
	if (file_exists("/dev/led/error")) {
111
		exec("/bin/echo 1 > /dev/led/error");
112
	}
113
	/* wrap & alix */
114
	led_normalize();
115
	led_morse(1, 'sos');
116
	if (!$local_only) {
117
		notify_all_remote($notice);
118
	}
119
	return $queuekey;
120
}
121

    
122
/****f* notices/get_notices
123
 * NAME
124
 *   get_notices
125
 * INPUTS
126
 *	 $category
127
 * RESULT
128
 *   Returns a specific notices text
129
 ******/
130
function get_notices($category = "all") {
131
	global $g;
132

    
133
	if (file_exists("{$g['tmp_path']}/notices")) {
134
		$queue = unserialize(file_get_contents("{$g['tmp_path']}/notices"));
135
		if (!$queue) {
136
			return false;
137
		}
138
		if ($category != 'all') {
139
			foreach ($queue as $time => $notice) {
140
				if (strtolower($notice['category']) == strtolower($category)) {
141
					$toreturn[$time] = $notice;
142
				}
143
			}
144
			return $toreturn;
145
		} else {
146
			return $queue;
147
		}
148
	} else {
149
		return false;
150
	}
151
}
152

    
153
/****f* notices/close_notice
154
 * NAME
155
 *   close_notice
156
 * INPUTS
157
 *	 $id
158
 * RESULT
159
 *   Removes a notice from the list
160
 ******/
161
function close_notice($id) {
162
	global $notice_path;
163
	require_once("util.inc");
164
	/* soekris */
165
	if (file_exists("/dev/led/error")) {
166
		exec("/bin/echo 0 > /dev/led/error");
167
	}
168
	/* wrap & alix */
169
	led_normalize();
170
	$ids = array();
171
	if (!$notices = get_notices()) {
172
		return;
173
	}
174
	if ($id == "all") {
175
		unlink_if_exists($notice_path);
176
		return;
177
	}
178
	foreach (array_keys($notices) as $time) {
179
		if ($id == $time) {
180
			unset($notices[$id]);
181
			break;
182
		}
183
	}
184
	foreach ($notices as $key => $notice) {
185
		$ids[$key] = $notice['id'];
186
	}
187
	foreach ($ids as $time => $tocheck) {
188
		if ($id == $tocheck) {
189
			unset($notices[$time]);
190
			break;
191
		}
192
	}
193
	if (count($notices) != 0) {
194
		$queueout = fopen($notice_path, "w");
195
		fwrite($queueout, serialize($notices));
196
		fclose($queueout);
197
	} else {
198
		unlink_if_exists($notice_path);
199
	}
200

    
201
	return;
202
}
203

    
204
/****f* notices/dump_xml_notices
205
 * NAME
206
 *   dump_xml_notices
207
 * INPUTS
208
 *	 NONE
209
 * RESULT
210
 *   Outputs notices in XML formatted text
211
 ******/
212
function dump_xml_notices() {
213
	require_once("xmlparse.inc");
214
	global $notice_path, $listtags;
215
	$listtags[] = 'notice';
216
	if (!$notices = get_notices()) {
217
		return;
218
	}
219
	foreach ($notices as $time => $notice) {
220
		$notice['time'] = $time;
221
		$toput['notice'][] = $notice;
222
	}
223
	$xml = dump_xml_config($toput, 'notices');
224
	return $xml;
225
}
226

    
227
/****f* notices/are_notices_pending
228
 * NAME
229
 *   are_notices_pending
230
 * INPUTS
231
 *	 $category to check
232
 * RESULT
233
 *   returns true if notices are pending, false if they are not
234
 ******/
235
function are_notices_pending($category = "all") {
236
	global $notice_path;
237
	if (file_exists($notice_path)) {
238
		return true;
239
	}
240
	return false;
241
}
242

    
243
function notices_sendqueue() {
244
	global $g;
245
	$nothing_done_count = 0;
246
	$messagequeue = array();
247

    
248
	while(true) {
249
		$nothing_done_count++;
250
		$smtpcount = 0;
251
		$messages = array();
252
		if (file_exists("{$g['vardb_path']}/notifyqueue.messages") &&
253
		    is_writable("{$g['vardb_path']}/notifyqueue.messages")) {
254
			$notifyqueue_lck = lock("notifyqueue", LOCK_EX);
255
			$messages = unserialize(file_get_contents("{$g['vardb_path']}/notifyqueue.messages"));
256
			$messagequeue = $messages;
257
			$messages['mails']['item'] = array(); // clear all items to be send
258
			$ret = file_put_contents("{$g['vardb_path']}/notifyqueue.messages", serialize($messages));
259
			if ($ret === false) {
260
				log_error("ERROR: Failed to write notify message queue!");
261
				return;
262
			}
263
			unset($messages);
264
		} else {
265
			/* Queue does not exist or is not writable, so no action can be taken
266
			 * https://redmine.pfsense.org/issues/14031
267
			 */
268
			return;
269
		}
270
		// clear lock before trying to send messages, so new one's can be added
271
		unlock($notifyqueue_lck);
272

    
273
		if (is_array($messagequeue['mails']['item'])) {
274
			$smtpmessage = "";
275
			foreach($messagequeue['mails']['item'] as $mail) {
276
				switch ($mail['type']) {
277
					case 'mail':
278
						$smtpcount++;
279
						$smtpmessage .= "\r\n" . date("G:i:s",$mail['time']) . " " . $mail['msg'];
280
						break;
281
					default:
282
						break;
283
				}
284
			}
285
			if (!empty($smtpmessage)) {
286
				$smtpmessageheader = sprintf(gettext("Notifications in this message: %s"), $smtpcount);
287
				$smtpmessageheader .= "\n" . str_repeat('=', strlen($smtpmessageheader)) . "\n";
288
				$nothing_done_count = 0;
289
				notify_via_smtp($smtpmessageheader . $smtpmessage, true);
290
			}
291
		}
292
		if ($nothing_done_count > 6) {
293
			break;
294
		} else {
295
			sleep(10);
296
		}
297
	}
298
}
299

    
300
function notify_via_queue_add($message, $type='mail') {
301
	global $g;
302
	$mail = array();
303
	$mail['time'] = time();
304
	$mail['type'] = $type;
305
	$mail['msg'] = $message;
306
	$notifyqueue_lck = lock("notifyqueue", LOCK_EX);
307
	$messages = array();
308
	if (file_exists("{$g['vardb_path']}/notifyqueue.messages")) {
309
		if (!is_writable("{$g['vardb_path']}/notifyqueue.messages")) {
310
			/* Cannot write to notify queue, so exit early
311
			 * https://redmine.pfsense.org/issues/14031
312
			 */
313
			return;
314
		}
315
		$messages = unserialize(file_get_contents("{$g['vardb_path']}/notifyqueue.messages"));
316
	}
317
	if (is_array($messages)) {
318
		$messages['mails']['item'][] = $mail;
319
		$ret = file_put_contents("{$g['vardb_path']}/notifyqueue.messages", serialize($messages));
320
		if ($ret === false) {
321
			log_error("ERROR: Failed to write notify message queue!");
322
			return;
323
		}
324
	}
325
	unset($messages);
326

    
327
	mwexec_bg('/usr/local/bin/notify_monitor.php');
328
	unlock($notifyqueue_lck);
329
}
330

    
331
/****f* notices/notify_via_smtp
332
 * NAME
333
 *   notify_via_smtp
334
 * INPUTS
335
 *	 notification string to send as an email
336
 * RESULT
337
 *   returns true if message was sent
338
 ******/
339
function notify_via_smtp($message, $force = false) {
340
	global $config, $g;
341
	if (platform_booting()) {
342
		return;
343
	}
344

    
345
	if (isset($config['notifications']['smtp']['disable']) && !$force) {
346
		return;
347
	}
348

    
349
	/* Do NOT send the same message twice, except if $force is true */
350
	if (!$force && file_exists("/var/db/notices_lastmsg.txt")) {
351
		$lastmsg = trim(file_get_contents("/var/db/notices_lastmsg.txt"));
352
		if ($lastmsg == $message) {
353
			return;
354
		}
355
	}
356

    
357
	/* Store last message sent to avoid spamming */
358
	@file_put_contents("/var/db/notices_lastmsg.txt", $message);
359
	if (!$force) {
360
		notify_via_queue_add($message, 'mail');
361
		$ret = true;
362
	} else {
363
		$ret = send_smtp_message($message, "{$config['system']['hostname']}.{$config['system']['domain']} - Notification", $force);
364
	}
365

    
366
	return $ret;
367
}
368

    
369
function send_smtp_message($message, $subject = "(no subject)", $force = false) {
370
	global $config, $g;
371
	require_once("Mail.php");
372

    
373
	if (isset($config['notifications']['smtp']['disable']) && !$force) {
374
		return;
375
	}
376

    
377
	if (!$config['notifications']['smtp']['ipaddress']) {
378
		return;
379
	}
380

    
381
	if (!$config['notifications']['smtp']['notifyemailaddress']) {
382
		return;
383
	}
384

    
385
	$to = config_get_path('notifications/smtp/notifyemailaddress');
386

    
387
	if (empty($config['notifications']['smtp']['username']) ||
388
	    empty($config['notifications']['smtp']['password'])) {
389
		$auth = false;
390
		$username = '';
391
		$password = '';
392
	} else {
393
		$auth = isset($config['notifications']['smtp']['authentication_mechanism'])
394
		    ? $config['notifications']['smtp']['authentication_mechanism']
395
		    : 'PLAIN';
396
		$username = config_get_path('notifications/smtp/username');
397
		$password = config_get_path('notifications/smtp/password');
398
	}
399

    
400
	$params = array(
401
		'host' => (isset($config['notifications']['smtp']['ssl'])
402
		    ? 'ssl://'
403
		    : '')
404
		    . $config['notifications']['smtp']['ipaddress'],
405
		'port' => empty($config['notifications']['smtp']['port'])
406
		    ? 25
407
		    : $config['notifications']['smtp']['port'],
408
		'auth' => $auth,
409
		'username' => $username,
410
		'password' => $password,
411
		'localhost' => $config['system']['hostname'] . "." .
412
		    $config['system']['domain'],
413
		'timeout' => !empty($config['notifications']['smtp']['timeout'])
414
		    ? $config['notifications']['smtp']['timeout']
415
		    : 20,
416
		'debug' => false,
417
		'persist' => false
418
	);
419

    
420
	if ($config['notifications']['smtp']['sslvalidate'] == "disabled") {
421
		$params['socket_options'] = array(
422
			'ssl' => array(
423
				'verify_peer_name' => false,
424
				'verify_peer' => false
425
		));
426
	}
427

    
428
	if ($config['notifications']['smtp']['fromaddress']) {
429
		$from = config_get_path('notifications/smtp/fromaddress');
430
	} else {
431
		$from = "pfsense@{$config['system']['hostname']}.{$config['system']['domain']}";
432
	}
433

    
434
	$headers = array(
435
		"From"    => $from,
436
		"To"      => $to,
437
		"Subject" => $subject,
438
		"Date"    => date("r")
439
	);
440

    
441
	$error_text = 'Could not send the message to %1$s -- Error: %2$s';
442
	try {
443
		$smtp =& Mail::factory('smtp', $params);
444
		$mail = @$smtp->send($to, $headers, $message);
445

    
446
		if (PEAR::isError($mail)) {
447
			$err_msg = sprintf(gettext($error_text),
448
			    $to, $mail->getMessage());
449
		}
450
	} catch (Exception $e) {
451
		$err_msg = sprintf(gettext($error_text), $to, $e->getMessage());
452
	}
453

    
454
	if (!empty($err_msg)) {
455
		log_error($err_msg);
456
		return($err_msg);
457
	}
458

    
459
	log_error(sprintf(gettext("Message sent to %s OK"), $to));
460
	return;
461
}
462
/****f* notices/notify_via_telegram
463
 * NAME
464
 *   notify_via_telegram
465
 * INPUTS
466
 *	 notification string to send to Telegram via API
467
 * RESULT
468
 *   returns NULL if message was sent
469
 ******/
470

    
471
function notify_via_telegram($message, $force = false) {
472
	global $config;
473
	if ((!isset($config['notifications']['telegram']['enabled']) && (!$force))
474
	    || !$config['notifications']['telegram']['api']
475
	    || !$config['notifications']['telegram']['chatid']) {
476
		if ($force) {
477
			return gettext("Unable to test Telegram notification without both API Key & Chat ID set");
478
		}
479
		return;
480
	}
481

    
482
	$url = "https://api.telegram.org/bot{$config['notifications']['telegram']['api']}/sendMessage?";
483
	$data = array(
484
		"chat_id" => $config['notifications']['telegram']['chatid'],
485
		"text" => "{$config['system']['hostname']}.{$config['system']['domain']}\n{$message}"
486
	);
487
	$result = json_decode(curl_post_notification($url . http_build_query($data)), true);
488
	if (is_array($result)) {
489
		if ($result['ok']) {
490
			unset($err_msg);
491
		} else {
492
			$err_msg = sprintf(gettext("Failed to send Telegram notification. Error received was :{$result['error_code']}: {$result['description']}"));
493
			log_error($err_msg);
494
		}
495
	} else {
496
		$err_msg = gettext("API to Telegram did not return data in expected format!");
497
		log_error($err_msg);
498
	}
499
	return $err_msg;
500
}
501

    
502
/****f* notices/notify_via_pushover
503
 * NAME
504
 *   notify_via_pushover
505
 * INPUTS
506
 *	 notification string to send to Pushover via API
507
 * RESULT
508
 *   returns NULL if message was sent
509
 ******/
510

    
511
function notify_via_pushover($message, $force = false) {
512
	global $config;
513
	if ((!isset($config['notifications']['pushover']['enabled']) && (!$force))
514
	    || !$config['notifications']['pushover']['apikey']
515
	    || !$config['notifications']['pushover']['userkey']) {
516
		if ($force) {
517
			return gettext("Unable to test Pushover notification without both API Key & User Key set");
518
		}
519
		return;
520
	}
521
	if (strcasecmp($config['notifications']['pushover']['sound'], 'devicedefault') == 0) {
522
		config_del_path('notifications/pushover/sound');
523
	}
524

    
525
	$url = "https://api.pushover.net/1/messages.json";
526
	$data = array(
527
		"token" => $config['notifications']['pushover']['apikey'],
528
		"user" => $config['notifications']['pushover']['userkey'],
529
		"sound" => $config['notifications']['pushover']['sound'],
530
		"priority" => $config['notifications']['pushover']['priority'],
531
		"retry" => $config['notifications']['pushover']['retry'],
532
		"expire" => $config['notifications']['pushover']['expire'],
533
		"message" => "{$config['system']['hostname']}.{$config['system']['domain']}\n{$message}"
534
	);
535
	$result = json_decode(curl_post_notification($url, $data), true);
536
	if (is_array($result)) {
537
		if ($result['status']) {
538
			unset($err_msg);
539
		} else {
540
			$err_msg = sprintf(gettext("Failed to send Pushover notification. Error received was: %s"), $result['errors']['0']);
541
			log_error($err_msg);
542
		}
543
	} else {
544
		$err_msg = gettext("Pushover API server did not return data in expected format!");
545
		log_error($err_msg);
546
	}
547
	return $err_msg;
548
}
549

    
550
/****f* notices/notify_via_slack
551
 * NAME
552
 *   notify_via_slack
553
 * INPUTS
554
 *	 notification string to send to Slack via API
555
 * RESULT
556
 *   returns NULL if message was sent
557
 ******/
558

    
559
function notify_via_slack($message, $force = false) {
560
	global $config;
561
	if ((!isset($config['notifications']['slack']['enabled']) && (!$force))
562
	    || !$config['notifications']['slack']['api']
563
	    || !$config['notifications']['slack']['channel']) {
564
		if ($force) {
565
			return gettext("Unable to test Slack notification without both API Key & Channel set");
566
		}
567
		return;
568
	}
569

    
570
	$url = "https://slack.com/api/chat.postMessage";
571
	$data = array(
572
		"token" => $config['notifications']['slack']['api'],
573
		"channel" => "#" . $config['notifications']['slack']['channel'],
574
		"text" => $message,
575
		"username" => "{$config['system']['hostname']}.{$config['system']['domain']}"
576
	);
577
	$result = json_decode(curl_post_notification($url, $data), true);
578
	if (is_array($result)) {
579
		if ($result['ok']) {
580
			unset($err_msg);
581
		} else {
582
			$err_msg = sprintf(gettext("Failed to send Slack notification. Error received was: %s"), $result['error']);
583
			log_error($err_msg);
584
		}
585
	} else {
586
		$err_msg = gettext("Slack API server did not return data in expected format!");
587
		log_error($err_msg);
588
	}
589
	return $err_msg;
590
}
591

    
592
function curl_post_notification($url, $data = array()) {
593
	$conn = curl_init($url);
594
	if (!empty($data)) {
595
		curl_setopt($conn, CURLOPT_POSTFIELDS, $data);
596
	}
597
	curl_setopt($conn, CURLOPT_SSL_VERIFYPEER, true);
598
	curl_setopt($conn, CURLOPT_FRESH_CONNECT,  true);
599
	curl_setopt($conn, CURLOPT_RETURNTRANSFER, true);
600
	set_curlproxy($conn);
601
	$curl_post_result = curl_exec($conn);
602
	curl_close($conn);
603
	return $curl_post_result;	//json encoded
604
}
605

    
606
/* Notify via remote methods only - not via GUI. */
607
function notify_all_remote($msg) {
608
	notify_via_smtp($msg);
609
	notify_via_telegram($msg);
610
	notify_via_pushover($msg);
611
	notify_via_slack($msg);
612
}
613

    
614
?>
(32-32/62)