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
|
?>
|