Project

General

Profile

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

    
22
// Functions to support firewall_nat.php and firewall_nat_edit.php
23

    
24
require_once("config.gui.inc");
25
require_once("interfaces.inc");
26
require_once("util.inc");
27
require_once("pfsense-utils.inc");
28
require_once("ipsec.inc");
29
require_once("wg.inc");
30
require_once("filter.inc");
31
require_once("itemid.inc");
32

    
33
$specialsrcdst = explode(" ", "any pptp pppoe l2tp openvpn");
34
$ifdisp = get_configured_interface_with_descr();
35
foreach ($ifdisp as $kif => $kdescr) {
36
	$specialsrcdst[] = "{$kif}";
37
	$specialsrcdst[] = "{$kif}ip";
38
}
39

    
40
function build_localtype_list($json = false) {
41
	global $pconfig, $ifdisp;
42

    
43
	$list = array('single' => gettext('Single host'));
44

    
45
	foreach ($ifdisp as $ifent => $ifdesc) {
46
		if ($json || have_ruleint_access($ifent)) {
47
			$list[$ifent . 'ip'] = $ifdesc . ' address';
48
		}
49
	}
50

    
51
	if ($json) {
52
		$rv = array();
53

    
54
		foreach ($list as $ifent => $ifname) {
55
			$rv[] = array("text" => $ifname, "value" => $ifent);
56
		}
57

    
58
		return(json_encode($rv));
59
	} else {
60
		return($list);
61
	}
62
}
63

    
64
function build_dsttype_list($json = false) {
65
	global $config, $ifdisp;
66

    
67
	$list = array('any' => gettext('Any'), 'single' => gettext('Single host or alias'), 'network' => gettext('Network'), '(self)' => gettext('This Firewall (self)'));
68

    
69
	if ($json || have_ruleint_access("pppoe")) {
70
		$list['pppoe'] = gettext('PPPoE clients');
71
	}
72

    
73
	if ($json || have_ruleint_access("l2tp")) {
74
		$list['l2tp'] = gettext('L2TP clients');
75
	}
76

    
77
	foreach ($ifdisp as $ifent => $ifdesc) {
78
		if ($json || have_ruleint_access($ifent)) {
79
			$list[$ifent] = $ifdesc . ' net';
80
			$list[$ifent . 'ip'] = $ifdesc . ' address';
81
		}
82
	}
83

    
84
	//Temporary array so we can sort IPs
85
	$templist = array();
86
	if (is_array($config['virtualip']['vip'])) {
87
		foreach ($config['virtualip']['vip'] as $sn) {
88
			if ((($sn['mode'] == "proxyarp") || ($sn['mode'] == "other")) &&
89
			    ($sn['type'] == "network") && is_subnetv4($sn['subnet'])) {
90
				$templist[$sn['subnet'] . '/' . $sn['subnet_bits']] = 'Subnet: ' . $sn['subnet'] . '/' . $sn['subnet_bits'] . ' (' . $sn['descr'] . ')';
91
				if (isset($sn['noexpand'])) {
92
					continue;
93
				}
94
				$start = ip2long32(gen_subnet($sn['subnet'], $sn['subnet_bits']));
95
				$end = ip2long32(gen_subnet_max($sn['subnet'], $sn['subnet_bits']));
96
				$len = $end - $start;
97

    
98
				for ($i = 0; $i <= $len; $i++) {
99
					$snip = long2ip32($start+$i);
100

    
101
					$templist[$snip] = $snip . ' (' . $sn['descr'] . ')';
102
				}
103
			} else {
104
				$templist[$sn['subnet']] = $sn['subnet'] . ' (' . $sn['descr'] . ')';
105
			}
106
		}
107
	}
108

    
109
	//Sort temp IP array and append onto main array
110
	asort($templist);
111
	$list = array_merge($list, $templist);
112
	unset($templist);
113

    
114
	if ($json) {
115
		$rv = array();
116

    
117
		foreach ($list as $ifent => $ifname) {
118
			$rv[] = array("text" => $ifname, "value" => $ifent);
119
		}
120

    
121
		return(json_encode($rv));
122
	} else {
123
		return($list);
124
	}
125
}
126

    
127
function build_srctype_list($json = false) {
128
	global $ifdisp, $config;
129

    
130
	$list = array('any' => gettext('Any'), 'single' => gettext('Single host or alias'), 'network' => gettext('Network'));
131

    
132
	if ($json || have_ruleint_access("pppoe")) {
133
		$list['pppoe'] = gettext('PPPoE clients');
134
	}
135

    
136
	if ($json || have_ruleint_access("l2tp")) {
137
		$list['l2tp'] = gettext('L2TP clients');
138
	}
139

    
140
	foreach ($ifdisp as $ifent => $ifdesc) {
141
		if ($json || have_ruleint_access($ifent)) {
142
			$list[$ifent] = $ifdesc . ' net';
143
			$list[$ifent . 'ip'] = $ifdesc . ' address';
144
		}
145
	}
146

    
147
	if ($json) {
148
		$rv = array();
149

    
150
		foreach ($list as $ifent => $ifname) {
151
			$rv[] = array("text" => $ifname, "value" => $ifent);
152
		}
153

    
154
		return(json_encode($rv));
155
	} else {
156
		return($list);
157
	}
158
}
159

    
160
function saveNATrule($post, $id, $json = false) {
161
	global $config;
162

    
163
	init_config_arr(array('filter', 'rule'));
164
	init_config_arr(array('nat', 'separator'));
165
	init_config_arr(array('nat', 'rule'));
166
	$a_nat = &$config['nat']['rule'];
167
	$a_separators = &$config['nat']['separator'];
168

    
169
	$input_errors = array();
170

    
171
	if (strtoupper($post['proto']) == "TCP" || strtoupper($post['proto']) == "UDP" || strtoupper($post['proto']) == "TCP/UDP") {
172
		if ($post['srcbeginport_cust'] && !$post['srcbeginport']) {
173
			$post['srcbeginport'] = trim($post['srcbeginport_cust']);
174
		}
175
		if ($post['srcendport_cust'] && !$post['srcendport']) {
176
			$post['srcendport'] = trim($post['srcendport_cust']);
177
		}
178

    
179
		if ($post['srcbeginport'] == "any") {
180
			$post['srcbeginport'] = 0;
181
			$post['srcendport'] = 0;
182
		} else {
183
			if (!$post['srcendport']) {
184
				$post['srcendport'] = $post['srcbeginport'];
185
			}
186
		}
187
		if ($post['srcendport'] == "any") {
188
			$post['srcendport'] = $post['srcbeginport'];
189
		}
190

    
191
		if ($post['dstbeginport_cust'] && !$post['dstbeginport']) {
192
			$post['dstbeginport'] = trim($post['dstbeginport_cust']);
193
		}
194
		if ($post['dstendport_cust'] && !$post['dstendport']) {
195
			$post['dstendport'] = trim($post['dstendport_cust']);
196
		}
197

    
198
		if ($post['dstbeginport'] == "any") {
199
			$post['dstbeginport'] = "1";
200
			$post['dstendport'] = "65535";
201
			$post['localbeginport'] = "1";
202
		} else {
203
			if (!$post['dstendport']) {
204
				$post['dstendport'] = $post['dstbeginport'];
205
			}
206
		}
207
		if ($post['dstendport'] == "any") {
208
			$post['dstendport'] = $post['dstbeginport'];
209
		}
210

    
211
		if ($post['localbeginport_cust'] && !$post['localbeginport']) {
212
			$post['localbeginport'] = trim($post['localbeginport_cust']);
213
		}
214

    
215
		/* Make beginning port end port if not defined and endport is */
216
		if (!$post['srcbeginport'] && $post['srcendport']) {
217
			$post['srcbeginport'] = $post['srcendport'];
218
		}
219
		if (!$post['dstbeginport'] && $post['dstendport']) {
220
			$post['dstbeginport'] = $post['dstendport'];
221
		}
222
	} else {
223
		$post['srcbeginport'] = 0;
224
		$post['srcendport'] = 0;
225
		$post['dstbeginport'] = 0;
226
		$post['dstendport'] = 0;
227
	}
228

    
229
	if (is_specialnet($post['srctype'])) {
230
		$post['src'] = $post['srctype'];
231
		$post['srcmask'] = 0;
232
	} elseif ($post['srctype'] == "single") {
233
		if (is_ipaddrv6($post['src'])) {
234
			$post['srcmask'] = 128;
235
		} else {
236
			$post['srcmask'] = 32;
237
		}
238
	}
239

    
240
	if (is_specialnet($post['dsttype'])) {
241
		$post['dst'] = $post['dsttype'];
242
		$post['dstmask'] = 0;
243
	} elseif ($post['dsttype'] == "single") {
244
		if (is_ipaddrv6($post['dst'])) {
245
			$post['dstmask'] = 128;
246
		} else {
247
			$post['dstmask'] = 32;
248
		}
249
	} elseif (is_ipaddr($post['dsttype'])) {
250
		$post['dst'] = $post['dsttype'];
251
		$post['dsttype'] = "single";
252
		if (is_ipaddrv6($post['dst'])) {
253
			$post['dstmask'] = 128;
254
		} else {
255
			$post['dstmask'] = 32;
256
		}
257
	}
258

    
259
	if (is_specialnet($post['localtype'])) {
260
		$post['localip'] = $post['localtype'];
261
	}
262

    
263
	$pconfig = $post;
264

    
265
	/* input validation */
266
	if (strtoupper($post['proto']) == "TCP" or strtoupper($post['proto']) == "UDP" or strtoupper($post['proto']) == "TCP/UDP") {
267
		$reqdfields = explode(" ", "interface proto dstbeginport dstendport");
268
		$reqdfieldsn = array(gettext("Interface"), gettext("Protocol"), gettext("Destination port from"), gettext("Destination port to"));
269
	} else {
270
		$reqdfields = explode(" ", "interface proto");
271
		$reqdfieldsn = array(gettext("Interface"), gettext("Protocol"));
272
	}
273

    
274
	if ($post['srctype'] == "single" || $post['srctype'] == "network") {
275
		$reqdfields[] = "src";
276
		$reqdfieldsn[] = gettext("Source address");
277
	}
278

    
279
	if ($post['dsttype'] == "single" || $post['dsttype'] == "network") {
280
		$reqdfields[] = "dst";
281
		$reqdfieldsn[] = gettext("Destination address");
282
	}
283

    
284
	if (!isset($post['nordr'])) {
285
		$reqdfields[] = "localip";
286
		$reqdfieldsn[] = gettext("Redirect target IP");
287
	}
288

    
289
	if (!$json) {
290
		do_input_validation($post, $reqdfields, $reqdfieldsn, $input_errors);
291
	}
292

    
293
	if (!$post['srcbeginport']) {
294
		$post['srcbeginport'] = 0;
295
		$post['srcendport'] = 0;
296
	}
297

    
298
	if (!$post['dstbeginport']) {
299
		$post['dstbeginport'] = 0;
300
		$post['dstendport'] = 0;
301
	}
302

    
303
	if ($post['src']) {
304
		$post['src'] = trim($post['src']);
305
	}
306
	if ($post['dst']) {
307
		$post['dst'] = trim($post['dst']);
308
	}
309
	if ($post['localip']) {
310
		$post['localip'] = trim($post['localip']);
311
	}
312

    
313
	if (!array_key_exists($post['interface'], create_interface_list())) {
314
		$input_errors[] = gettext("The submitted interface does not exist.");
315
	}
316

    
317
	if (!isset($post['nordr']) && $post['localip'] &&
318
	    !is_ipaddroralias($post['localip']) && !is_specialnet($post['localtype'])) {
319
		$input_errors[] = sprintf(gettext("\"%s\" is not a valid redirect target IP address or host alias."), $post['localip']);
320
	}
321

    
322
	if ($post['localip']) {
323
		if (is_specialnet($post['localtype'])) {
324
			foreach ($ifdisp as $kif => $kdescr) {
325
				if ($post['localtype'] == "{$kif}ip") {
326
					if (($post['ipprotocol'] == 'inet') && !get_interface_ip($kif)) {
327
						$input_errors[] = sprintf(gettext("Redirect interface must have IPv4 address."));
328
						break;
329
					} elseif (($post['ipprotocol'] == 'inet6') && !get_interface_ipv6($kif)) {
330
						$input_errors[] = sprintf(gettext("Redirect interface must have IPv6 address."));
331
						break;
332
					}
333
				}
334
			}
335
		} elseif (($post['ipprotocol'] == 'inet') && is_ipaddrv6($post['localip'])) {
336
			$input_errors[] = sprintf(gettext("Redirect target IP must be IPv4."));
337
		} elseif (($post['ipprotocol'] == 'inet6') && is_ipaddrv4($post['localip'])) {
338
			$input_errors[] = sprintf(gettext("Redirect target IP must be IPv6."));
339
		}
340
	}
341

    
342
	if ($post['srcbeginport'] && !is_port_or_alias($post['srcbeginport'])) {
343
		$input_errors[] = sprintf(gettext("%s is not a valid start source port. It must be a port alias or integer between 1 and 65535."), $post['srcbeginport']);
344
	}
345
	if ($post['srcendport'] && !is_port_or_alias($post['srcendport'])) {
346
		$input_errors[] = sprintf(gettext("%s is not a valid end source port. It must be a port alias or integer between 1 and 65535."), $post['srcendport']);
347
	}
348
	if ($post['dstbeginport'] && !is_port_or_alias($post['dstbeginport'])) {
349
		$input_errors[] = sprintf(gettext("%s is not a valid start destination port. It must be a port alias or integer between 1 and 65535."), $post['dstbeginport']);
350
	}
351
	if ($post['dstendport'] && !is_port_or_alias($post['dstendport'])) {
352
		$input_errors[] = sprintf(gettext("%s is not a valid end destination port. It must be a port alias or integer between 1 and 65535."), $post['dstendport']);
353
	}
354

    
355
	if ((strtoupper($post['proto']) == "TCP" || strtoupper($post['proto']) == "UDP" || strtoupper($post['proto']) == "TCP/UDP") && (!isset($post['nordr']) && !is_port_or_alias($post['localbeginport']))) {
356
		$input_errors[] = sprintf(gettext("Redirect target port %s is not valid. It must be a port alias or integer between 1 and 65535."), $post['localbeginport']);
357
	}
358

    
359
	/* if user enters an alias and selects "network" then disallow. */
360
	if (($post['srctype'] == "network" && is_alias($post['src'])) ||
361
	    ($post['dsttype'] == "network" && is_alias($post['dst']))) {
362
		$input_errors[] = gettext("Alias entries must specify a single host or alias.");
363
	}
364

    
365
	if (!is_specialnet($post['srctype'])) {
366
		if (($post['src'] && !is_ipaddroralias($post['src']))) {
367
			$input_errors[] = sprintf(gettext("%s is not a valid source IP address or alias."), $post['src']);
368
		}
369
		if ($post['src']) {
370
			if (($post['ipprotocol'] == 'inet') && is_ipaddrv6($post['src'])) {
371
				$input_errors[] = sprintf(gettext("Source must be IPv4."));
372
			} elseif (($post['ipprotocol'] == 'inet6') && is_ipaddrv4($post['src'])) {
373
				$input_errors[] = sprintf(gettext("Source must be IPv6."));
374
			}
375
		}
376
		if (is_ipaddr($post['src']) && !is_subnet($post['src'] . '/' . $post['srcmask'])) {
377
			$input_errors[] = gettext("A valid source bit count must be specified.");
378
		}
379
	}
380

    
381
	if (!is_specialnet($post['dsttype'])) {
382
		if (($post['dst'] && !is_ipaddroralias($post['dst']))) {
383
			$input_errors[] = sprintf(gettext("%s is not a valid destination IP address or alias."), $post['dst']);
384
		}
385
		if ($post['dst']) {
386
			if (($post['ipprotocol'] == 'inet') && is_ipaddrv6($post['dst'])) {
387
				$input_errors[] = sprintf(gettext("Destination must be IPv4."));
388
			} elseif (($post['ipprotocol'] == 'inet6') && is_ipaddrv4($post['dst'])) {
389
				$input_errors[] = sprintf(gettext("Destination must be IPv6."));
390
			}
391
		}
392
		if (is_ipaddr($post['dst']) && !is_subnet($post['dst'] . '/' . $post['dstmask'])) {
393
			$input_errors[] = gettext("A valid destination bit count must be specified.");
394
		}
395
	}
396

    
397
	if ($post['srcbeginport'] > $post['srcendport']) {
398
		/* swap */
399
		$tmp = $post['srcendport'];
400
		$post['srcendport'] = $post['srcbeginport'];
401
		$post['srcbeginport'] = $tmp;
402
	}
403

    
404
	if ($post['dstbeginport'] > $post['dstendport']) {
405
		/* swap */
406
		$tmp = $post['dstendport'];
407
		$post['dstendport'] = $post['dstbeginport'];
408
		$post['dstbeginport'] = $tmp;
409
	}
410

    
411
	if (!$input_errors) {
412
		if (!isset($post['nordr']) && ((int) $post['dstendport'] - (int) $post['dstbeginport'] + (int) $post['localbeginport']) > 65535) {
413
			$input_errors[] = gettext("The target port range must be an integer between 1 and 65535.");
414
		}
415
	}
416

    
417
	/* check for overlaps */
418
	foreach ($a_nat as $natent) {
419
		if (isset($id) && ($a_nat[$id]) && ($a_nat[$id] === $natent)) {
420
			continue;
421
		}
422
		if ($natent['interface'] != $post['interface']) {
423
			continue;
424
		}
425
		if ($natent['destination']['address'] != $post['dst']) {
426
			continue;
427
		}
428
		if (($natent['proto'] != $post['proto']) && ($natent['proto'] != "tcp/udp") && ($post['proto'] != "tcp/udp")) {
429
			continue;
430
		}
431

    
432
		list($begp, $endp) = explode("-", $natent['destination']['port']);
433
		if (!$endp) {
434
			$endp = $begp;
435
		}
436

    
437
		if (!((($post['dstbeginport'] < $begp) && ($post['dstendport'] < $begp)) ||
438
		      (($post['dstbeginport'] > $endp) && ($post['dstendport'] > $endp)))) {
439
			$input_errors[] = gettext("The destination port range overlaps with an existing entry.");
440
			break;
441
		}
442
	}
443

    
444
	if (!$input_errors) {
445

    
446
		$natent = array();
447

    
448
		if (isset($post['disabled'])) {
449
			$natent['disabled'] = true;
450
		}
451

    
452
		if (isset($post['nordr'])) {
453
			$natent['nordr'] = true;
454
		}
455

    
456
		if ($natent['nordr']) {
457
			$post['associated-rule-id'] = '';
458
			$post['filter-rule-association'] = '';
459
		}
460

    
461
		pconfig_to_address($natent['source'], $post['src'],
462
			$post['srcmask'], $post['srcnot'],
463
			$post['srcbeginport'], $post['srcendport']);
464

    
465
		pconfig_to_address($natent['destination'], $post['dst'],
466
			$post['dstmask'], $post['dstnot'],
467
			$post['dstbeginport'], $post['dstendport']);
468

    
469
		$natent['ipprotocol'] = $post['ipprotocol'];
470
		$natent['protocol'] = $post['proto'];
471

    
472
		if (!isset($natent['nordr'])) {
473
			$natent['target'] = $post['localip'];
474
			$natent['local-port'] = $post['localbeginport'];
475
		}
476

    
477
		$natent['interface'] = $post['interface'];
478
		$natent['descr'] = $post['descr'];
479
		$natent['associated-rule-id'] = $post['associated-rule-id'];
480

    
481
		if ($post['filter-rule-association'] == "pass") {
482
			$natent['associated-rule-id'] = "pass";
483
		}
484

    
485
		if ($post['nosync'] == "yes") {
486
			$natent['nosync'] = true;
487
		} else {
488
			unset($natent['nosync']);
489
		}
490

    
491
		if ($post['natreflection'] == "enable" || $post['natreflection'] == "purenat" || $post['natreflection'] == "disable") {
492
			$natent['natreflection'] = $post['natreflection'];
493
		} else {
494
			unset($natent['natreflection']);
495
		}
496

    
497
		// If we used to have an associated filter rule, but no-longer should have one
498
		if (!empty($a_nat[$id]) && (empty($natent['associated-rule-id']) || $natent['associated-rule-id'] != $a_nat[$id]['associated-rule-id'])) {
499
			// Delete the previous rule
500
			delete_id($a_nat[$id]['associated-rule-id'], $config['filter']['rule']);
501
			if (!$json) {
502
				mark_subsystem_dirty('filter');
503
			}
504
		}
505

    
506
		$need_filter_rule = false;
507
		// Updating a rule with a filter rule associated
508
		if (!empty($natent['associated-rule-id'])) {
509
			$need_filter_rule = true;
510
		}
511
		// Create a rule or if we want to create a new one
512
		if ($natent['associated-rule-id'] == 'new') {
513
			$need_filter_rule = true;
514
			unset($natent['associated-rule-id']);
515
			$post['filter-rule-association']='add-associated';
516
		}
517
		// If creating a new rule, where we want to add the filter rule, associated or not
518
		else if (isset($post['filter-rule-association']) &&
519
		    ($post['filter-rule-association'] == 'add-associated' ||
520
		     $post['filter-rule-association'] == 'add-unassociated')) {
521
			$need_filter_rule = true;
522
		}
523

    
524
		if ($need_filter_rule == true) {
525
			/* auto-generate a matching firewall rule */
526
			$filterent = array();
527
			unset($filterentid);
528

    
529
			// If a rule already exists, load it
530
			if (!empty($natent['associated-rule-id'])) {
531
				$filterentid = get_id($natent['associated-rule-id'], $config['filter']['rule']);
532
				if ($filterentid === false) {
533
					$filterent['associated-rule-id'] = $natent['associated-rule-id'];
534
				} else {
535
					$filterent =& $config['filter']['rule'][$filterentid];
536
				}
537
			}
538

    
539
			pconfig_to_address($filterent['source'], $post['src'],
540
				$post['srcmask'], $post['srcnot'],
541
				$post['srcbeginport'], $post['srcendport']);
542

    
543
			// Update interface, protocol and destination
544
			$filterent['interface'] = $post['interface'];
545
			$filterent['ipprotocol'] = $post['ipprotocol'];
546
			$filterent['protocol'] = $post['proto'];
547
			if (is_specialnet($post['localtype'])) {
548
				$filterent['destination']['network'] = $post['localtype'];
549
			} else {
550
				$filterent['destination']['address'] = $post['localip'];
551
			}
552

    
553
			if (isset($post['disabled'])) {
554
				$filterent['disabled'] = true;
555
			}
556

    
557
			$dstpfrom = $post['localbeginport'];
558
			$dstpto = (int) $dstpfrom + (int) $post['dstendport'] - (int) $post['dstbeginport'];
559

    
560
			if ($dstpfrom == $dstpto) {
561
				$filterent['destination']['port'] = $dstpfrom;
562
			} else {
563
				$filterent['destination']['port'] = $dstpfrom . "-" . $dstpto;
564
			}
565

    
566
			/*
567
			 * Our firewall filter description may be no longer than
568
			 * 63 characters, so don't let it be.
569
			 */
570
			$filterent['descr'] = substr("NAT " . $post['descr'], 0, 62);
571

    
572
			// If this is a new rule, create an ID and add the rule
573
			if ($post['filter-rule-association'] == 'add-associated') {
574
				$filterent['associated-rule-id'] = $natent['associated-rule-id'] = get_unique_id();
575
				$filterent['tracker'] = (int)microtime(true);
576
				$filterent['created'] = make_config_revision_entry(null, gettext("NAT Port Forward"));
577
				$config['filter']['rule'][] = $filterent;
578
			}
579

    
580
			if (!$json) {
581
				mark_subsystem_dirty('filter');
582
			}
583
		}
584

    
585
		if (isset($a_nat[$id]['created']) && is_array($a_nat[$id]['created'])) {
586
			$natent['created'] = $a_nat[$id]['created'];
587
		}
588

    
589
		$natent['updated'] = make_config_revision_entry();
590

    
591
		if (!$json) {
592
			// Allow extending of the firewall edit page and include custom input validation
593
			pfSense_handle_custom_code("/usr/local/pkg/firewall_nat/pre_write_config");
594
		}
595

    
596
		// Update the NAT entry now
597
		if (isset($id) && $a_nat[$id]) {
598

    
599
			if (isset($natent['associated-rule-id']) &&
600
			    (isset($a_nat[$id]['disabled']) !== isset($natent['disabled']))) {
601
				// Check for filter rule associations
602
				toggle_id($natent['associated-rule-id'],
603
				    $config['filter']['rule'],
604
				    !isset($natent['disabled']));
605
				
606
				if (!$json) {
607
					mark_subsystem_dirty('filter');
608
				}
609
			}
610
			$a_nat[$id] = $natent;
611
		} else {
612
			$natent['created'] = make_config_revision_entry();
613
			if (is_numeric($after)) {
614
				array_splice($a_nat, $after+1, 0, array($natent));
615

    
616
				// Update the separators
617
				$ridx = $after;
618
				$mvnrows = +1;
619
				move_separators($a_separators, $ridx, $mvnrows);
620
			} else {
621
				$a_nat[] = $natent;
622
			}
623
		}
624

    
625
		if (write_config(gettext("Firewall: NAT: Port Forward - saved/edited a port forward rule."))) {
626
			if (!$json) {
627
				mark_subsystem_dirty('natconf');
628
			} else {
629
				filter_configure();
630
			}
631
		}
632
	}
633

    
634
	$rv = array();
635
	$rv['input_errors'] = $input_errors;
636
	$rv['pconfig'] = $pconfig;
637

    
638
	return $json ? json_encode($rv) : $rv;
639
}
640

    
641
function toggleNATrule($post, $json = false) {
642
	global $config;
643

    
644
	init_config_arr(array('nat', 'rule'));
645
	$a_nat = &$config['nat']['rule'];
646
	init_config_arr(array('nat', 'separator'));
647
	$a_separators = &$config['nat']['separator'];
648

    
649
	if (isset($a_nat[$post['id']]['disabled'])) {
650
		unset($a_nat[$post['id']]['disabled']);
651
		$rule_status = true;
652
	} else {
653
		$a_nat[$post['id']]['disabled'] = true;
654
		$rule_status = false;
655
	}
656

    
657
	// Check for filter rule associations
658
	if (isset($a_nat[$post['id']]['associated-rule-id'])) {
659
		toggle_id($a_nat[$post['id']]['associated-rule-id'],
660
		    $config['filter']['rule'], $rule_status);
661
		unset($rule_status);
662

    
663
		if(!$json) {
664
			mark_subsystem_dirty('filter');
665
		}
666
	}
667

    
668
	if (write_config(gettext("Firewall: NAT: Port forward, enable/disable NAT rule"))) {
669
		if (!$json) {
670
			mark_subsystem_dirty('natconf');
671
		}
672
	}
673

    
674
	if(!$json) {
675
		header("Location: firewall_nat.php");
676
		exit;
677
	}
678
}
679

    
680
function deleteMultipleNATrules($post, $json = false) {
681
	global $config;
682

    
683
	init_config_arr(array('nat', 'rule'));
684
	$a_nat = &$config['nat']['rule'];
685
	init_config_arr(array('nat', 'separator'));
686
	$a_separators = &$config['nat']['separator'];
687

    
688
	$first_idx = 0;
689
	$num_deleted = 0;
690

    
691
	foreach ($post['rule'] as $rulei) {
692
		// Check for filter rule associations
693
		if (isset($a_nat[$rulei]['associated-rule-id'])) {
694
			delete_id($a_nat[$rulei]['associated-rule-id'], $config['filter']['rule']);
695
			if (!$json) {
696
				mark_subsystem_dirty('filter');
697
			}
698
		}
699

    
700
		unset($a_nat[$rulei]);
701

    
702
		// Capture first changed filter index for later separator shifting
703
		if (!$first_idx) {
704
			$first_idx = $rulei;
705
		}
706

    
707
		$num_deleted++;
708
	}
709

    
710
	if ($num_deleted) {
711
		move_separators($a_separators, $first_idx, -$num_deleted);
712
		if (write_config("NAT: Rule deleted")) {
713
			if ($json) {
714
				filter_configure();
715
			} else {
716
				mark_subsystem_dirty('natconf');
717
			}
718
		}
719
	}
720

    
721
	if(!$json) {
722
		header("Location: firewall_nat.php");
723
		exit;
724
	}
725
}
726

    
727
function deleteNATrule($post, $json = false) {
728
	global $config;
729

    
730
	init_config_arr(array('nat', 'rule'));
731
	$a_nat = &$config['nat']['rule'];
732
	init_config_arr(array('nat', 'separator'));
733
	$a_separators = &$config['nat']['separator'];
734

    
735
	if (isset($a_nat[$post['id']]['associated-rule-id'])) {
736
		delete_id($a_nat[$post['id']]['associated-rule-id'], $config['filter']['rule']);
737
		$want_dirty_filter = true;
738
	}
739

    
740
	unset($a_nat[$post['id']]);
741

    
742
	// Update the separators
743
	$ridx = $post['id'];
744
	$mvnrows = -1;
745
	move_separators($a_separators, $ridx, $mvnrows);
746

    
747
	if (write_config("NAT: Rule deleted")) {
748
		if ($json) {
749
			filter_configure();
750
		} else {
751
			mark_subsystem_dirty('natconf');
752
			if ($want_dirty_filter) {
753
				mark_subsystem_dirty('filter');
754
			}
755
		}
756
	}
757

    
758
	if(!$json) {
759
		header("Location: firewall_nat.php");
760
		exit;
761
	}
762
}
763

    
764
function applyNATrules() {
765
	$retval = 0;
766

    
767
	$retval |= filter_configure();
768

    
769
	pfSense_handle_custom_code("/usr/local/pkg/firewall_nat/apply");
770

    
771
	if ($retval == 0) {
772
		clear_subsystem_dirty('natconf');
773
		clear_subsystem_dirty('filter');
774
	}
775

    
776
	return $retval;
777
}
778

    
779
// Re-order the NAT rules per the array of iindicies passed in $post
780
function reorderNATrules($post, $json = false) {
781
	global $config;
782

    
783
	$updated = false;
784
	$dirty = false;
785

    
786
	init_config_arr(array('nat', 'separator'));
787
	init_config_arr(array('nat', 'rule'));
788
	$a_nat = &$config['nat']['rule'];
789
	$a_separators = &$config['nat']['separator'];
790

    
791
	/* update rule order, POST[rule] is an array of ordered IDs */
792
	if (is_array($post['rule']) && !empty($post['rule'])) {
793
		$a_nat_new = array();
794

    
795
		// if a rule is not in POST[rule], it has been deleted by the user
796
		foreach ($post['rule'] as $id) {
797
			$a_nat_new[] = $a_nat[$id];
798
		}
799

    
800
		if ($a_nat !== $a_nat_new) {
801
			$a_nat = $a_nat_new;
802
			$dirty = true;
803
		}
804
	}
805

    
806
	/* update separator order, POST[separator] is an array of ordered IDs */
807
	if (is_array($post['separator']) && !empty($post['separator'])) {
808
		$new_separator = array();
809
		$idx = 0;
810

    
811
		foreach ($post['separator'] as $separator) {
812
			$new_separator['sep' . $idx++] = $separator;
813
		}
814

    
815
		if ($a_separators !== $new_separator) {
816
			$a_separators = $new_separator;
817
			$updated = true;
818
		}
819
	} else if (!empty($a_separators)) {
820
		$a_separators = "";
821
		$updated = true;
822
	}
823

    
824
	if ($updated || $dirty) {
825
		if (write_config("NAT: Rule order changed")) {
826
			if ($json) {
827
				filter_configure();
828
			} else if ($dirty) {
829
				mark_subsystem_dirty('natconf');
830
			}
831
		}
832
	}
833

    
834
	if(!$json) {
835
		header("Location: firewall_nat.php");
836
		exit;
837
	}
838
}
839

    
840
function MVC_filter_get_interface_list() {
841
	$iflist = MVC_create_interface_list();
842
	$filter_ifs = array();
843

    
844
	foreach ($iflist as $ifent => $ifname) {
845
		$filter_ifs[] = array("text" => $ifname, "value" => $ifent);
846
	}
847

    
848
	return json_encode($filter_ifs);
849
}
850

    
851
function MVC_create_interface_list() {
852
	global $config;
853

    
854
	$iflist = array();
855

    
856
	// add group interfaces
857
	if (isset($config['ifgroups']['ifgroupentry']) && is_array($config['ifgroups']['ifgroupentry'])) {
858
		foreach ($config['ifgroups']['ifgroupentry'] as $ifgen) {
859
			$iflist[$ifgen['ifname']] = $ifgen['ifname'];
860
		}
861
	}
862

    
863
	foreach (get_configured_interface_with_descr() as $ifent => $ifdesc) {
864
		$iflist[$ifent] = $ifdesc;
865
	}
866

    
867
	if ($config['l2tp']['mode'] == "server") {
868
		$iflist['l2tp'] = gettext('L2TP VPN');
869
	}
870

    
871
	if (is_pppoe_server_enabled()) {
872
		$iflist['pppoe'] = gettext("PPPoE Server");
873
	}
874

    
875
	// add ipsec interfaces
876
	if (ipsec_enabled()) {
877
		$iflist["enc0"] = gettext("IPsec");
878
	}
879

    
880
	// add openvpn/tun interfaces
881
	if ($config['openvpn']["openvpn-server"] || $config['openvpn']["openvpn-client"]) {
882
		$iflist["openvpn"] = gettext("OpenVPN");
883
	}
884

    
885
	if (is_wg_enabled()) {
886
		$iflist['wireguard'] = 'WireGuard';
887
	}
888

    
889
	return($iflist);
890
}
891

    
892
function getNATRule($id, $json = false) {
893
	global $config;
894

    
895
	init_config_arr(array('nat', 'rule'));
896
	$a_nat = &$config['nat']['rule'];
897

    
898
	if (isset($id) && $a_nat[$id]) {
899
		if (isset($a_nat[$id]['created']) && is_array($a_nat[$id]['created'])) {
900
			$pconfig['created'] = $a_nat[$id]['created'];
901
		}
902

    
903
		if (isset($a_nat[$id]['updated']) && is_array($a_nat[$id]['updated'])) {
904
			$pconfig['updated'] = $a_nat[$id]['updated'];
905
		}
906

    
907
		$pconfig['disabled'] = isset($a_nat[$id]['disabled']);
908
		$pconfig['nordr'] = isset($a_nat[$id]['nordr']);
909

    
910
		address_to_pconfig($a_nat[$id]['source'], $pconfig['src'],
911
			$pconfig['srcmask'], $pconfig['srcnot'],
912
			$pconfig['srcbeginport'], $pconfig['srcendport']);
913

    
914
		address_to_pconfig($a_nat[$id]['destination'], $pconfig['dst'],
915
			$pconfig['dstmask'], $pconfig['dstnot'],
916
			$pconfig['dstbeginport'], $pconfig['dstendport']);
917

    
918
		if (($pconfig['dstbeginport'] == 1) && ($pconfig['dstendport'] == 65535)) {
919
			$pconfig['dstbeginport'] = "any";
920
			$pconfig['dstendport'] = "any";
921
		}
922

    
923
		$pconfig['ipprotocol'] = $a_nat[$id]['ipprotocol'];
924
		$pconfig['proto'] = $a_nat[$id]['protocol'];
925
		$pconfig['localip'] = $a_nat[$id]['target'];
926
		$pconfig['localbeginport'] = $a_nat[$id]['local-port'];
927
		$pconfig['descr'] = $a_nat[$id]['descr'];
928
		$pconfig['interface'] = $a_nat[$id]['interface'];
929
		$pconfig['associated-rule-id'] = $a_nat[$id]['associated-rule-id'];
930
		$pconfig['nosync'] = isset($a_nat[$id]['nosync']);
931
		$pconfig['natreflection'] = $a_nat[$id]['natreflection'];
932

    
933
		if (!$pconfig['interface']) {
934
			$pconfig['interface'] = "wan";
935
		}
936
	} else {
937
		$pconfig['interface'] = "wan";
938
		$pconfig['src'] = "any";
939
		$pconfig['srcbeginport'] = "any";
940
		$pconfig['srcendport'] = "any";
941
	}
942

    
943
	return $json ? json_encode($pconfig):$pconfig;
944
}
945
?>
(5-5/13)