Project

General

Profile

Download (16.2 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * easyrule.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2009-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2022 Rubicon Communications, LLC (Netgate)
9
 * Originally Sponsored By Anathematic @ pfSense Forums
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
$blockaliasname = 'EasyRuleBlockHosts';
26
$protocols_with_ports = array('tcp', 'udp');
27
require_once("functions.inc");
28
require_once("util.inc");
29
require_once("ipsec.inc");
30
require_once("config.inc");
31

    
32
global $specialsrcdst;
33
$specialsrcdst = explode(" ", "any (self) pppoe l2tp openvpn");
34

    
35
function easyrule_find_rule_interface($int) {
36
	global $config;
37
	/* Borrowed from firewall_rules.php */
38
	$iflist = get_configured_interface_with_descr(true);
39

    
40
	/* Add interface groups */
41
	foreach (config_get_path('ifgroups/ifgroupentry', []) as $ifgen) {
42
		$iflist[$ifgen['ifname']] = $ifgen['ifname'];
43
	}
44

    
45
	if (is_pppoe_server_enabled()) {
46
		$iflist['pppoe'] = gettext("PPPoE Server");
47
	}
48

    
49
	if (config_get_path('l2tp/mode') == "server") {
50
		$iflist['l2tp'] = gettext("L2TP VPN");
51
	}
52

    
53
	/* Add IPsec tunnel interface */
54
	if (ipsec_enabled()) {
55
		$iflist["enc0"] = gettext("IPsec");
56
	}
57

    
58
	if (count(config_get_path('openvpn/openvpn-server', [])) ||
59
	    count(config_get_path('openvpn/openvpn-client', []))) {
60
		$iflist["openvpn"] = gettext("OpenVPN");
61
	}
62

    
63
	/* Check if the given name matches a known assigned interface id or
64
	 * common group name */
65
	if (array_key_exists($int, $iflist)) {
66
		return $int;
67
	}
68

    
69
	/* Check if the user passed an interface description name instead of the
70
	 * internal name. */
71
	foreach ($iflist as $if => $ifd) {
72
		if (strtolower($int) == strtolower($ifd)) {
73
			return $if;
74
		}
75
	}
76

    
77
	/* Check for unassigned OpenVPN or IPsec and return the associated
78
	 * group name. */
79
	if (substr($int, 0, 4) == "ovpn") {
80
		return "openvpn";
81
	}
82
	if (substr($int, 0, 5) == "ipsec") {
83
		return "ipsec";
84
	}
85

    
86
	/* If the user passed a real interface name, attempt to map it to an
87
	 * assigned interface */
88
	$iff = convert_real_interface_to_friendly_interface_name($int);
89
	if (($iff !== NULL) && ($iff != $int)) {
90
		return $iff;
91
	}
92

    
93
	return false;
94
}
95

    
96
function easyrule_block_rule_exists($int = 'wan', $ipproto = "inet") {
97
	global $blockaliasname, $config;
98
	/* No rules, we we know it doesn't exist */
99
	if (empty(config_get_path('filter/rule', []))) {
100
		return false;
101
	}
102

    
103
	/* Search through the rules for one referencing our alias */
104
	foreach (config_get_path('filter/rule', []) as $rule) {
105
		if (!is_array($rule) || !is_array($rule['source'])) {
106
			continue;
107
		}
108
		$checkproto = isset($rule['ipprotocol']) ? $rule['ipprotocol'] : "inet";
109
		if ((array_get_path($rule, 'source/address') == $blockaliasname . strtoupper($int)) &&
110
		    ($rule['interface'] == $int) &&
111
		    ($checkproto == $ipproto)) {
112
			return true;
113
		}
114
	}
115
	return false;
116
}
117

    
118
function easyrule_block_rule_create($int = 'wan', $ipproto = "inet") {
119
	global $blockaliasname, $config;
120
	/* If the alias doesn't exist, exit.
121
	 * Can't create an empty alias, and we don't know a host */
122
	if (easyrule_block_alias_getid($int) === false) {
123
		return "noalias";
124
	}
125

    
126
	/* If the rule already exists, no need to do it again */
127
	if (easyrule_block_rule_exists($int, $ipproto)) {
128
		return true;
129
	}
130

    
131
	filter_rules_sort();
132
	$a_filter = config_get_path('filter/rule', []);
133

    
134
	/* Make up a new rule */
135
	$filterent = array();
136
	$filterent['type'] = 'block';
137
	$filterent['interface'] = $int;
138
	$filterent['ipprotocol'] = $ipproto;
139
	$filterent['source'] = [];
140
	$filterent['source']['address'] = $blockaliasname . strtoupper($int);
141
	$filterent['destination'] = [];
142
	$filterent['destination']['any'] = '';
143
	$filterent['descr'] = gettext("Blocked via EasyRule");
144
	$filterent['created'] = make_config_revision_entry(null, "EasyRule");
145
	$filterent['tracker'] = (int)microtime(true);
146

    
147
	// Refer to firewall_rules_edit.php separators updating code.
148
	// Using same code, variables, and techniques here.
149
	$after = -1;	// Place rule at top and move all separators.
150
	array_splice($a_filter, $after+1, 0, array($filterent));
151
	config_set_path('filter/rule', $a_filter);
152

    
153
	$tmpif = $int;
154

    
155
	// Update the separators
156
	$a_separators = config_get_path('filter/separator/' . strtolower($tmpif), []);
157
	$ridx = ifridx($tmpif, $after);	// get rule index within interface
158
	$mvnrows = +1;
159
	move_separators($a_separators, $ridx, $mvnrows);
160
	config_set_path('filter/separator/' . strtolower($tmpif), $a_separators);
161

    
162
	return true;
163
}
164

    
165
function easyrule_block_alias_getid($int = 'wan') {
166
	global $blockaliasname;
167

    
168
	/* Hunt down an alias with the name we want, return its id */
169
	foreach (config_get_path('aliases/alias', []) as $aliasid => $alias) {
170
		if ($alias['name'] == $blockaliasname . strtoupper($int)) {
171
			return $aliasid;
172
		}
173
	}
174

    
175
	return false;
176
}
177

    
178
function easyrule_block_alias_add($host, $int = 'wan') {
179
	global $blockaliasname, $config;
180
	/* If the host isn't a valid IP address, bail */
181
	$host = trim($host, "[]");
182
	if (!is_ipaddr($host) && !is_subnet($host)) {
183
		return "invalid";
184
	}
185

    
186
	$a_aliases = config_get_path('aliases/alias', []);
187

    
188
	/* Try to get the ID if the alias already exists */
189
	$id = easyrule_block_alias_getid($int);
190
	if ($id === false) {
191
		unset($id);
192
	}
193

    
194
	$alias = array();
195

    
196
	if (is_subnet($host)) {
197
		list($host, $mask) = explode("/", $host);
198
	} elseif (is_specialnet($host)) {
199
		$mask = 0;
200
	} elseif (is_ipaddrv6($host)) {
201
		$mask = 128;
202
	} else {
203
		$mask = 32;
204
	}
205

    
206
	if (isset($id) &&
207
	    array_key_exists($id, $a_aliases) &&
208
	    is_array($a_aliases[$id])) {
209

    
210
		// Catch case when the list is empty
211
		if (empty(array_get_path($a_aliases, "{$id}/address", ""))) {
212
			$a_address = array();
213
			$a_detail = array();
214
		} else {
215
			$a_address = explode(" ", array_get_path($a_aliases, "{$id}/address", ""));
216

    
217
			/* Make sure this IP address isn't already in the list. */
218
			if (in_array($host.'/'.$mask, $a_address)) {
219
				return "exists";
220
			}
221
			$a_detail = explode("||", array_get_path($a_aliases, "{$id}/detail"));
222
		}
223

    
224
		/* Since the alias already exists, just add to it. */
225
		$alias['name']    = array_get_path($a_aliases, "{$id}/name");
226
		$alias['type']    = array_get_path($a_aliases, "{$id}/type");
227
		$alias['descr']   = array_get_path($a_aliases, "{$id}/descr");
228

    
229
		$a_address[] = $host.'/'.$mask;
230
		$a_detail[] = gettext('Entry added') . ' ' . date('r');
231

    
232
		$alias['address'] = join(" ", $a_address);
233
		$alias['detail']  = join("||", $a_detail);
234

    
235
	} else {
236
		/* Create a new alias with all the proper information */
237
		$alias['name']    = $blockaliasname . strtoupper($int);
238
		$alias['type']    = 'network';
239
		$alias['descr']   = gettext("Blocked via EasyRule");
240

    
241
		$alias['address'] = $host . '/' . $mask;
242
		$alias['detail']  = gettext('Entry added') . ' ' . date('r') . '||';
243
	}
244

    
245
	/* Replace the old alias if needed, otherwise tack it on the end */
246
	if (isset($id) && $a_aliases[$id]) {
247
		$a_aliases[$id] = $alias;
248
	} else {
249
		$a_aliases[] = $alias;
250
	}
251

    
252
	// Sort list
253
	$a_aliases = msort($a_aliases, "name");
254

    
255
	config_set_path('aliases/alias', $a_aliases);
256
	return true;
257
}
258

    
259
function easyrule_block_host_add($host, $int = 'wan') {
260
	global $retval;
261
	/* Bail if the supplied host is not a valid IP address */
262
	$host = trim($host, "[]");
263
	if (!is_ipaddr($host) && !is_subnet($host)) {
264
		return "invalid";
265
	}
266

    
267
	if (is_v6($host)) {
268
		$ipproto = 'inet6';
269
	} else {
270
		$ipproto = 'inet';
271
	}
272

    
273
	/* Flag whether or not we need to reload the filter */
274
	$dirty = false;
275

    
276
	/* Attempt to add this host to the alias */
277
	$alias_add_result = easyrule_block_alias_add($host, $int);
278
	if ($alias_add_result === true) {
279
		$dirty = true;
280
	} else {
281
		/* Couldn't add the alias, or adding the host failed. */
282
		return $alias_add_result;
283
	}
284

    
285
	/* Attempt to add the firewall rule if it doesn't exist.
286
	 * Failing to add the rule isn't necessarily an error, it may
287
	 * have been modified by the user in some way. Adding to the
288
	 * Alias is what's important.
289
	 */
290
	if (!easyrule_block_rule_exists($int, $ipproto)) {
291
		$rule_create_result = easyrule_block_rule_create($int, $ipproto);
292
		if ($rule_create_result === true) {
293
			$dirty = true;
294
		} else {
295
			return $rule_create_result;
296
		}
297
	}
298

    
299
	/* If needed, write the config and reload the filter */
300
	if ($dirty) {
301
		write_config(sprintf(gettext("Blocked %s via EasyRule"), $host));
302
		$retval = filter_configure();
303
		if (!empty($_SERVER['DOCUMENT_ROOT'])) {
304
			header("Location: firewall_aliases.php");
305
			exit;
306
		} else {
307
			return true;
308
		}
309
	} else {
310
		return false;
311
	}
312
}
313

    
314
function easyrule_pass_rule_add($int, $proto, $srchost, $dsthost, $dstport, $ipproto) {
315
	global $config;
316

    
317
	init_config_arr(array('filter', 'rule'));
318
	filter_rules_sort();
319
	$a_filter = config_get_path('filter/rule', []);
320

    
321
	/* Make up a new rule */
322
	$filterent = array();
323
	$filterent['type'] = 'pass';
324
	$filterent['interface'] = $int;
325
	$filterent['ipprotocol'] = $ipproto;
326
	$filterent['descr'] = gettext("Passed via EasyRule");
327

    
328
	if ($proto != "any") {
329
		$filterent['protocol'] = $proto;
330
	} else {
331
		unset($filterent['protocol']);
332
	}
333

    
334
	/* Default to only allow echo requests, since that's what most people want and
335
	 *  it should be a safe choice. */
336
	if ($proto == "icmp") {
337
		$filterent['icmptype'] = 'echoreq';
338
	}
339

    
340
	if ((strtolower($proto) == "icmp6") || (strtolower($proto) == "icmpv6")) {
341
		$filterent['protocol'] = "icmp";
342
	}
343

    
344
	if (is_subnet($srchost)) {
345
		list($srchost, $srcmask) = explode("/", $srchost);
346
	} elseif (is_specialnet($srchost)) {
347
		$srcmask = 0;
348
	} elseif (is_ipaddrv6($srchost)) {
349
		$srcmask = 128;
350
	} else {
351
		$srcmask = 32;
352
	}
353

    
354
	if (is_subnet($dsthost)) {
355
		list($dsthost, $dstmask) = explode("/", $dsthost);
356
	} elseif (is_specialnet($dsthost)) {
357
		$dstmask = 0;
358
	} elseif (is_ipaddrv6($dsthost)) {
359
		$dstmask = 128;
360
	} else {
361
		$dstmask = 32;
362
	}
363

    
364
	pconfig_to_address($filterent['source'], $srchost, $srcmask);
365
	pconfig_to_address($filterent['destination'], $dsthost, $dstmask, '', $dstport, $dstport);
366

    
367
	$filterent['created'] = make_config_revision_entry(null, "EasyRule");
368
	$filterent['tracker'] = (int)microtime(true);
369
	$a_filter[] = $filterent;
370
	config_set_path('filter/rule', $a_filter);
371

    
372
	write_config($filterent['descr']);
373
	$retval = filter_configure();
374
	if (!empty($_SERVER['DOCUMENT_ROOT'])) {
375
		header("Location: firewall_rules.php?if={$int}");
376
		exit;
377
	} else {
378
		return true;
379
	}
380
}
381

    
382
function easyrule_parse_block($int, $src) {
383
	if (!empty($src) && !empty($int)) {
384
		$src = trim($src, "[]");
385
		if (!is_ipaddr($src) && !is_subnet($src)) {
386
			return gettext("Tried to block invalid address:") . ' ' . htmlspecialchars($src);
387
		}
388
		$int = easyrule_find_rule_interface($int);
389
		if ($int === false) {
390
			return gettext("Invalid interface for block rule.");
391
		}
392
		switch ((string)easyrule_block_host_add($src, $int)) {
393
			case "exists":
394
				return gettext("Block entry already exists.");
395
				break;
396
			case "invalid":
397
				return gettext("Invalid address.");
398
				break;
399
			case "1":
400
				return gettext("Block added successfully");
401
				break;
402
			case "":
403
			default:
404
				return gettext("Failed to create block rule, alias, or add entry.");
405
				break;
406
		}
407
	} else {
408
		return gettext("Tried to block but had no address or interface");
409
	}
410
	return gettext("Unknown block error.");
411
}
412

    
413
function easyrule_parse_unblock($int, $host) {
414
	global $blockaliasname, $config;
415

    
416
	if (!empty($host) && !empty($int)) {
417
		$host = trim($host, "[]");
418
		if (!is_ipaddr($host) && !is_subnet($host)) {
419
			return gettext("Tried to unblock invalid address:") . ' ' . htmlspecialchars($host);
420
		}
421
		$real_int = easyrule_find_rule_interface($int);
422
		if ($real_int === false) {
423
			return gettext("Invalid interface for block rule:") . ' ' . htmlspecialchars($int);
424
		}
425

    
426
		/* Try to get the ID - will fail if there are no rules/alias on this interface */
427
		$id = easyrule_block_alias_getid($real_int);
428
		if ($id === false ||
429
		    empty(config_get_path("aliases/alias/{$id}", [])) ||
430
		    empty(config_get_path("aliases/alias/{$id}")['address'])) {
431
			return gettext("No entries are blocked on interface:") . ' ' . htmlspecialchars($int);
432
		}
433

    
434
		$alias = config_get_path("aliases/alias/{$id}", []);
435

    
436
		if (is_subnet($host)) {
437
			list($host, $mask) = explode("/", $host);
438
		} elseif (is_specialnet($host)) {
439
			$mask = 0;
440
		} elseif (is_ipaddrv6($host)) {
441
			$mask = 128;
442
		} else {
443
			$mask = 32;
444
		}
445

    
446
		// Create the expected string representation
447
		$unblock = $host.'/'.$mask;
448

    
449
		$a_address = explode(" ", $alias['address']);
450
		$a_detail = explode("||", $alias['detail']);
451

    
452
		if (($key = array_search($unblock, $a_address)) !== false) {
453
			unset($a_address[$key]);
454
			unset($a_detail[$key]);
455
			// Write back the result to the config array
456
			$alias['address'] = join(" ", $a_address);
457
			$alias['detail'] = join("||", $a_detail);
458
			config_set_path("aliases/alias/{$id}", $alias);
459

    
460
			// Update config
461
			write_config(sprintf(gettext("Unblocked %s via EasyRule"), $host));
462
			$retval = filter_configure();
463
			if (!empty($_SERVER['DOCUMENT_ROOT'])) {
464
				header("Location: firewall_aliases.php");
465
				exit;
466
			} else {
467
				return gettext("Entry unblocked successfully");
468
			}
469
		} else {
470
			return gettext("Entry is not on block list: " . $host);
471
		}
472
	}
473

    
474
	return gettext("Tried to unblock but had no address or interface");
475

    
476
}
477

    
478
function easyrule_parse_getblock($int = 'wan', $sep = "\n") {
479
	global $blockaliasname, $config;
480

    
481
	$real_int = easyrule_find_rule_interface($int);
482
	if ($real_int === false) {
483
		return gettext("Invalid interface for block rule:") . ' ' . htmlspecialchars($int);
484
	}
485

    
486
	/* Try to get the ID - will fail if there are no rules/alias on this interface */
487
	$id = easyrule_block_alias_getid($real_int);
488

    
489
	if ($id === false ||
490
	    empty(config_get_path("aliases/alias/{$id}", [])) ||
491
	    empty(config_get_path("aliases/alias/{$id}")['address'])) {
492
		return gettext("No entries are blocked on interface:") . ' ' . htmlspecialchars($int);
493
	}
494
	return join($sep, explode(" ", config_get_path("aliases/alias/{$id}")['address']));
495
}
496

    
497
function easyrule_parse_pass($int, $proto, $src, $dst, $dstport = 0, $ipproto = "inet") {
498
	/* Check for valid int, srchost, dsthost, dstport, and proto */
499
	global $protocols_with_ports;
500
	$src = trim($src, "[]");
501
	$dst = trim($dst, "[]");
502

    
503
	if (!empty($int) && !empty($proto) && !empty($src) && !empty($dst)) {
504
		$int = easyrule_find_rule_interface($int);
505
		if ($int === false) {
506
			return gettext("Invalid interface for pass rule:") . ' ' . htmlspecialchars($int);
507
		}
508
		if (($proto != 'any') &&
509
		    (getprotobyname($proto) === false) &&
510
		    (getprotobynumber($proto) === false)) {
511
			return gettext("Invalid protocol for pass rule:") . ' ' . htmlspecialchars($proto);
512
		}
513
		if (!is_ipaddr($src) && !is_subnet($src) && !is_ipaddroralias($src) && !is_specialnet($src)) {
514
			return gettext("Tried to pass invalid source IP address:") . ' ' . htmlspecialchars($src);
515
		}
516
		if (!is_ipaddr($dst) && !is_subnet($dst) && !is_ipaddroralias($dst) && !is_specialnet($dst)) {
517
			return gettext("Tried to pass invalid destination IP address:") . ' ' . htmlspecialchars($dst);
518
		}
519
		if ((is_v6($src) && is_v4($dst)) || (is_v4($src) && is_v6($dst))) {
520
			return gettext("The source IP address family has to match the family of the destination IP address.");
521
		}
522
		if (is_v6($src)) {
523
			$ipproto = 'inet6';
524
		} else {
525
			$ipproto = 'inet';
526
		}
527
		/* If the protocol is by number, change it to a name */
528
		if (($proto != 'any') &&
529
		    (getprotobyname($proto) === false)) {
530
			$proto = getprotobynumber($proto);
531
		}
532
		if (in_array($proto, $protocols_with_ports)) {
533
			if (empty($dstport)) {
534
				return gettext("Missing destination port.");
535
			}
536
			if (!is_port($dstport) && ($dstport != "any")) {
537
				return gettext("Tried to pass invalid destination port:") . ' ' . htmlspecialchars($dstport);
538
			}
539
		} else {
540
			$dstport = 0;
541
		}
542
		/* Should have valid input... */
543
		if (easyrule_pass_rule_add($int, $proto, $src, $dst, $dstport, $ipproto)) {
544
			return gettext("Successfully added pass rule!");
545
		} else {
546
			return gettext("Failed to add pass rule.");
547
		}
548
	} else {
549
		return gettext("Missing parameters for pass rule.");
550
	}
551
	return gettext("Unknown pass error.");
552
}
553

    
554
?>
(17-17/61)