Project

General

Profile

Download (16.4 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-2024 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
function easyrule_find_rule_interface($int) {
33
	/* Borrowed from firewall_rules.php */
34
	$iflist = get_configured_interface_with_descr(true);
35

    
36
	/* Add interface groups */
37
	foreach (config_get_path('ifgroups/ifgroupentry', []) as $ifgen) {
38
		$iflist[$ifgen['ifname']] = $ifgen['ifname'];
39
	}
40

    
41
	if (is_pppoe_server_enabled()) {
42
		$iflist['pppoe'] = gettext("PPPoE Server");
43
	}
44

    
45
	if (config_get_path('l2tp/mode') == "server") {
46
		$iflist['l2tp'] = gettext("L2TP VPN");
47
	}
48

    
49
	/* Add IPsec tunnel interface */
50
	if (ipsec_enabled()) {
51
		$iflist["enc0"] = gettext("IPsec");
52
	}
53

    
54
	if (count(config_get_path('openvpn/openvpn-server', [])) ||
55
	    count(config_get_path('openvpn/openvpn-client', []))) {
56
		$iflist["openvpn"] = gettext("OpenVPN");
57
	}
58

    
59
	/* Check if the given name matches a known assigned interface id or
60
	 * common group name */
61
	if (array_key_exists($int, $iflist)) {
62
		return $int;
63
	}
64

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

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

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

    
89
	return false;
90
}
91

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

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

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

    
122
	/* If the rule already exists, no need to do it again */
123
	if (easyrule_block_rule_exists($int, $ipproto)) {
124
		return true;
125
	}
126

    
127
	filter_rules_sort();
128
	$a_filter = config_get_path('filter/rule', []);
129

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

    
143
	// place the rule on top
144
	$ridx = get_interface_ruleindex($int);
145
	array_splice($a_filter, $ridx['first'], 0, array($filterent));
146
	config_set_path('filter/rule', $a_filter);
147

    
148
	// shift the separators
149
	$a_separators = config_get_path('filter/separator/' . strtolower($int), []);
150
	shift_separators($a_separators, -1);
151
	config_set_path('filter/separator/' . strtolower($int), $a_separators);
152

    
153
	return true;
154
}
155

    
156
function easyrule_block_alias_getid($int = 'wan') {
157
	global $blockaliasname;
158

    
159
	/* Hunt down an alias with the name we want, return its id */
160
	foreach (config_get_path('aliases/alias', []) as $aliasid => $alias) {
161
		if ($alias['name'] == $blockaliasname . strtoupper($int)) {
162
			return $aliasid;
163
		}
164
	}
165

    
166
	return false;
167
}
168

    
169
function easyrule_block_alias_add($host, $int = 'wan') {
170
	global $blockaliasname;
171
	$easyrule_nettype_flags = [SPECIALNET_ANY, SPECIALNET_SELF, SPECIALNET_CLIENTS];
172
	/* If the host isn't a valid IP address, bail */
173
	$host = trim($host, "[]");
174
	if (!is_ipaddr($host) && !is_subnet($host)) {
175
		return "invalid";
176
	}
177

    
178
	$a_aliases = config_get_path('aliases/alias', []);
179

    
180
	/* Try to get the ID if the alias already exists */
181
	$id = easyrule_block_alias_getid($int);
182
	if ($id === false) {
183
		unset($id);
184
	}
185

    
186
	$alias = array();
187

    
188
	if (is_subnet($host)) {
189
		list($host, $mask) = explode("/", $host);
190
	} elseif (get_specialnet($host, $easyrule_nettype_flags)) {
191
		$mask = 0;
192
	} elseif (is_ipaddrv6($host)) {
193
		$mask = 128;
194
	} else {
195
		$mask = 32;
196
	}
197

    
198
	if (isset($id) &&
199
	    array_key_exists($id, $a_aliases) &&
200
	    is_array($a_aliases[$id])) {
201

    
202
		// Catch case when the list is empty
203
		if (empty(array_get_path($a_aliases, "{$id}/address", ""))) {
204
			$a_address = array();
205
			$a_detail = array();
206
		} else {
207
			$a_address = explode(" ", array_get_path($a_aliases, "{$id}/address", ""));
208

    
209
			/* Make sure this IP address isn't already in the list. */
210
			if (in_array($host.'/'.$mask, $a_address)) {
211
				return "exists";
212
			}
213
			$a_detail = explode("||", array_get_path($a_aliases, "{$id}/detail"));
214
		}
215

    
216
		/* Since the alias already exists, just add to it. */
217
		$alias['name']    = array_get_path($a_aliases, "{$id}/name");
218
		$alias['type']    = array_get_path($a_aliases, "{$id}/type");
219
		$alias['descr']   = array_get_path($a_aliases, "{$id}/descr");
220

    
221
		$a_address[] = $host.'/'.$mask;
222
		$a_detail[] = gettext('Entry added') . ' ' . date('r');
223

    
224
		$alias['address'] = join(" ", $a_address);
225
		$alias['detail']  = join("||", $a_detail);
226

    
227
	} else {
228
		/* Create a new alias with all the proper information */
229
		$alias['name']    = $blockaliasname . strtoupper($int);
230
		$alias['type']    = 'network';
231
		$alias['descr']   = gettext("Blocked via EasyRule");
232

    
233
		$alias['address'] = $host . '/' . $mask;
234
		$alias['detail']  = gettext('Entry added') . ' ' . date('r') . '||';
235
	}
236

    
237
	/* Replace the old alias if needed, otherwise tack it on the end */
238
	if (isset($id) && $a_aliases[$id]) {
239
		$a_aliases[$id] = $alias;
240
	} else {
241
		$a_aliases[] = $alias;
242
	}
243

    
244
	// Sort list
245
	$a_aliases = msort($a_aliases, "name");
246

    
247
	config_set_path('aliases/alias', $a_aliases);
248
	return true;
249
}
250

    
251
function easyrule_block_host_add($host, $int = 'wan') {
252
	global $retval;
253
	/* Bail if the supplied host is not a valid IP address */
254
	$host = trim($host, "[]");
255
	if (!is_ipaddr($host) && !is_subnet($host)) {
256
		return "invalid";
257
	}
258

    
259
	if (is_v6($host)) {
260
		$ipproto = 'inet6';
261
	} else {
262
		$ipproto = 'inet';
263
	}
264

    
265
	/* Flag whether or not we need to reload the filter */
266
	$dirty = false;
267

    
268
	/* Attempt to add this host to the alias */
269
	$alias_add_result = easyrule_block_alias_add($host, $int);
270
	if ($alias_add_result === true) {
271
		$dirty = true;
272
	} else {
273
		/* Couldn't add the alias, or adding the host failed. */
274
		return $alias_add_result;
275
	}
276

    
277
	/* Attempt to add the firewall rule if it doesn't exist.
278
	 * Failing to add the rule isn't necessarily an error, it may
279
	 * have been modified by the user in some way. Adding to the
280
	 * Alias is what's important.
281
	 */
282
	if (!easyrule_block_rule_exists($int, $ipproto)) {
283
		$rule_create_result = easyrule_block_rule_create($int, $ipproto);
284
		if ($rule_create_result === true) {
285
			$dirty = true;
286
		} else {
287
			return $rule_create_result;
288
		}
289
	}
290

    
291
	/* If needed, write the config and reload the filter */
292
	if ($dirty) {
293
		write_config(sprintf(gettext("Blocked %s via EasyRule"), $host));
294
		$retval = filter_configure();
295
		if (!empty($_SERVER['DOCUMENT_ROOT'])) {
296
			header("Location: firewall_aliases.php");
297
			exit;
298
		} else {
299
			return true;
300
		}
301
	} else {
302
		return false;
303
	}
304
}
305

    
306
function easyrule_pass_rule_add($int, $proto, $srchost, $dsthost, $dstport, $ipproto) {
307
	$easyrule_nettype_flags = [SPECIALNET_ANY, SPECIALNET_SELF, SPECIALNET_CLIENTS];
308

    
309
	filter_rules_sort();
310

    
311
	/* Make up a new rule */
312
	$filterent = array();
313
	$filterent['type'] = 'pass';
314
	$filterent['interface'] = $int;
315
	$filterent['ipprotocol'] = $ipproto;
316
	$filterent['descr'] = gettext("Passed via EasyRule");
317

    
318
	if ($proto != "any") {
319
		$filterent['protocol'] = $proto;
320
	} else {
321
		unset($filterent['protocol']);
322
	}
323

    
324
	if ((strtolower($proto) == "icmp6") || (strtolower($proto) == "icmpv6")) {
325
		$filterent['protocol'] = "icmp";
326
	}
327

    
328
	/* Default to only allow echo requests, since that's what most people want and
329
	 *  it should be a safe choice. */
330
	if ($proto == "icmp") {
331
		$filterent['icmptype'] = 'echoreq';
332
	}
333

    
334
	if (is_subnet($srchost)) {
335
		list($srchost, $srcmask) = explode("/", $srchost);
336
	} elseif (get_specialnet($srchost, $easyrule_nettype_flags)) {
337
		$srcmask = 0;
338
	} elseif (is_ipaddrv6($srchost)) {
339
		$srcmask = 128;
340
	} else {
341
		$srcmask = 32;
342
	}
343

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

    
354
	pconfig_to_address($filterent['source'], $srchost, $srcmask, false, 0, 0, false, $easyrule_nettype_flags);
355
	pconfig_to_address($filterent['destination'], $dsthost, $dstmask, '', $dstport, $dstport, false, $easyrule_nettype_flags);
356

    
357
	$filterent['created'] = make_config_revision_entry(null, "EasyRule");
358
	$filterent['tracker'] = (int)microtime(true);
359
	config_set_path('filter/rule/', $filterent);
360

    
361
	write_config($filterent['descr']);
362
	$retval = filter_configure();
363
	if (!empty($_SERVER['DOCUMENT_ROOT'])) {
364
		header("Location: firewall_rules.php?if={$int}");
365
		exit;
366
	} else {
367
		return true;
368
	}
369
}
370

    
371
function easyrule_parse_block($int, $src) {
372
	if (!empty($src) && !empty($int)) {
373
		$src = trim($src, "[]");
374
		if (!is_ipaddr($src) && !is_subnet($src)) {
375
			return gettext("Tried to block invalid address:") . ' ' . htmlspecialchars($src);
376
		}
377
		$int = easyrule_find_rule_interface($int);
378
		if ($int === false) {
379
			return gettext("Invalid interface for block rule.");
380
		}
381
		switch ((string)easyrule_block_host_add($src, $int)) {
382
			case "exists":
383
				return gettext("Block entry already exists.");
384
				break;
385
			case "invalid":
386
				return gettext("Invalid address.");
387
				break;
388
			case "1":
389
				return gettext("Block added successfully");
390
				break;
391
			case "":
392
			default:
393
				return gettext("Failed to create block rule, alias, or add entry.");
394
				break;
395
		}
396
	} else {
397
		return gettext("Tried to block but had no address or interface");
398
	}
399
	return gettext("Unknown block error.");
400
}
401

    
402
function easyrule_parse_unblock($int, $host) {
403
	global $blockaliasname;
404
	$easyrule_nettype_flags = [SPECIALNET_ANY, SPECIALNET_SELF, SPECIALNET_CLIENTS];
405

    
406
	if (!empty($host) && !empty($int)) {
407
		$host = trim($host, "[]");
408
		if (!is_ipaddr($host) && !is_subnet($host)) {
409
			return gettext("Tried to unblock invalid address:") . ' ' . htmlspecialchars($host);
410
		}
411
		$real_int = easyrule_find_rule_interface($int);
412
		if ($real_int === false) {
413
			return gettext("Invalid interface for block rule:") . ' ' . htmlspecialchars($int);
414
		}
415

    
416
		/* Try to get the ID - will fail if there are no rules/alias on this interface */
417
		$id = easyrule_block_alias_getid($real_int);
418
		if ($id === false ||
419
		    empty(config_get_path("aliases/alias/{$id}", [])) ||
420
		    empty(config_get_path("aliases/alias/{$id}/address"))) {
421
			return gettext("No entries are blocked on interface:") . ' ' . htmlspecialchars($int);
422
		}
423

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

    
426
		if (is_subnet($host)) {
427
			list($host, $mask) = explode("/", $host);
428
		} elseif (get_specialnet($host, $easyrule_nettype_flags)) {
429
			$mask = 0;
430
		} elseif (is_ipaddrv6($host)) {
431
			$mask = 128;
432
		} else {
433
			$mask = 32;
434
		}
435

    
436
		// Create the expected string representation
437
		$unblock = $host.'/'.$mask;
438

    
439
		$a_address = explode(" ", $alias['address']);
440
		$a_detail = explode("||", $alias['detail']);
441

    
442
		if (($key = array_search($unblock, $a_address)) !== false) {
443
			unset($a_address[$key]);
444
			unset($a_detail[$key]);
445
			// Write back the result to the config array
446
			$alias['address'] = join(" ", $a_address);
447
			$alias['detail'] = join("||", $a_detail);
448
			config_set_path("aliases/alias/{$id}", $alias);
449

    
450
			// Update config
451
			write_config(sprintf(gettext("Unblocked %s via EasyRule"), $host));
452
			$retval = filter_configure();
453
			if (!empty($_SERVER['DOCUMENT_ROOT'])) {
454
				header("Location: firewall_aliases.php");
455
				exit;
456
			} else {
457
				return gettext("Entry unblocked successfully");
458
			}
459
		} else {
460
			return gettext("Entry is not on block list: " . $host);
461
		}
462
	}
463

    
464
	return gettext("Tried to unblock but had no address or interface");
465

    
466
}
467

    
468
function easyrule_parse_getblock($int = 'wan', $sep = "\n") {
469
	global $blockaliasname;
470

    
471
	$real_int = easyrule_find_rule_interface($int);
472
	if ($real_int === false) {
473
		return gettext("Invalid interface for block rule:") . ' ' . htmlspecialchars($int);
474
	}
475

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

    
479
	if ($id === false ||
480
	    empty(config_get_path("aliases/alias/{$id}", [])) ||
481
	    empty(config_get_path("aliases/alias/{$id}/address"))) {
482
		return gettext("No entries are blocked on interface:") . ' ' . htmlspecialchars($int);
483
	}
484
	return join($sep, explode(" ", config_get_path("aliases/alias/{$id}/address", '')));
485
}
486

    
487
function easyrule_parse_pass($int, $proto, $src, $dst, $dstport = 0, $ipproto = "inet") {
488
	/* Check for valid int, srchost, dsthost, dstport, and proto */
489
	global $protocols_with_ports;
490
	$easyrule_nettype_flags = [SPECIALNET_ANY, SPECIALNET_SELF, SPECIALNET_CLIENTS];
491
	$src = trim($src, "[]");
492
	$dst = trim($dst, "[]");
493

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

    
548
?>
(16-16/61)