firewall: Write correct rules bound to interface for routes IPsec tunnels
[ipfire-2.x.git] / config / firewall / firewall-lib.pl
1 #!/usr/bin/perl
2 ###############################################################################
3 #                                                                             #
4 # IPFire.org - A linux based firewall                                         #
5 # Copyright (C) 2013 Alexander Marx <amarx@ipfire.org>                        #
6 #                                                                             #
7 # This program is free software: you can redistribute it and/or modify        #
8 # it under the terms of the GNU General Public License as published by        #
9 # the Free Software Foundation, either version 3 of the License, or           #
10 # (at your option) any later version.                                         #
11 #                                                                             #
12 # This program is distributed in the hope that it will be useful,             #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of              #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
15 # GNU General Public License for more details.                                #
16 #                                                                             #
17 # You should have received a copy of the GNU General Public License           #
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
19 #                                                                             #
20 ###############################################################################
21
22 use strict;
23 no warnings 'uninitialized';
24
25 package fwlib;
26
27 my %customnetwork=();
28 my %customhost=();
29 my %customgrp=();
30 my %customgeoipgrp=();
31 my %customservice=();
32 my %customservicegrp=();
33 my %ccdnet=();
34 my %ccdhost=();
35 my %ipsecconf=();
36 my %ipsecsettings=();
37 my %netsettings=();
38 my %ovpnsettings=();
39 my %aliases=();
40
41 require '/var/ipfire/general-functions.pl';
42 require '/var/ipfire/geoip-functions.pl';
43
44 my $confignet           = "${General::swroot}/fwhosts/customnetworks";
45 my $confighost          = "${General::swroot}/fwhosts/customhosts";
46 my $configgrp           = "${General::swroot}/fwhosts/customgroups";
47 my $configgeoipgrp      = "${General::swroot}/fwhosts/customgeoipgrp";
48 my $configsrv           = "${General::swroot}/fwhosts/customservices";
49 my $configsrvgrp        = "${General::swroot}/fwhosts/customservicegrp";
50 my $configccdnet        = "${General::swroot}/ovpn/ccd.conf";
51 my $configccdhost       = "${General::swroot}/ovpn/ovpnconfig";
52 my $configipsec         = "${General::swroot}/vpn/config";
53 my $configovpn          = "${General::swroot}/ovpn/settings";
54 my $val;
55 my $field;
56 my $netsettings         = "${General::swroot}/ethernet/settings";
57
58 &General::readhash("/var/ipfire/ethernet/settings", \%netsettings);
59 &General::readhash("${General::swroot}/ovpn/settings", \%ovpnsettings);
60 &General::readhash("${General::swroot}/vpn/settings", \%ipsecsettings);
61
62 &General::readhasharray("$confignet", \%customnetwork);
63 &General::readhasharray("$confighost", \%customhost);
64 &General::readhasharray("$configgrp", \%customgrp);
65 &General::readhasharray("$configgeoipgrp", \%customgeoipgrp);
66 &General::readhasharray("$configccdnet", \%ccdnet);
67 &General::readhasharray("$configccdhost", \%ccdhost);
68 &General::readhasharray("$configipsec", \%ipsecconf);
69 &General::readhasharray("$configsrv", \%customservice);
70 &General::readhasharray("$configsrvgrp", \%customservicegrp);
71 &General::get_aliases(\%aliases);
72
73 sub get_srv_prot
74 {
75         my $val=shift;
76         foreach my $key (sort {$a <=> $b} keys %customservice){
77                 if($customservice{$key}[0] eq $val){
78                         if ($customservice{$key}[0] eq $val){
79                                 return $customservice{$key}[2];
80                         }
81                 }
82         }
83 }
84 sub get_srvgrp_prot
85 {
86         my $val=shift;
87         my @ips=();
88         my $tcp;
89         my $udp;
90         my $icmp;
91         foreach my $key (sort {$a <=> $b} keys %customservicegrp){
92                 if($customservicegrp{$key}[0] eq $val){
93                         if (&get_srv_prot($customservicegrp{$key}[2]) eq 'TCP'){ 
94                                 $tcp=1;
95                         }elsif(&get_srv_prot($customservicegrp{$key}[2]) eq 'UDP'){ 
96                                 $udp=1;
97                         }elsif(&get_srv_prot($customservicegrp{$key}[2]) eq 'ICMP'){
98                                 $icmp=1;
99                         }else{
100                                 #Protocols used in servicegroups
101                                 push (@ips,$customservicegrp{$key}[2]);
102                         }
103                 }
104         }
105         if ($tcp eq '1'){push (@ips,'TCP');}
106         if ($udp eq '1'){push (@ips,'UDP');}
107         if ($icmp eq '1'){push (@ips,'ICMP');}
108         my $back=join(",",@ips);
109         return $back;
110         
111 }
112 sub get_srv_port
113 {
114         my $val=shift;
115         my $field=shift;
116         my $prot=shift;
117         foreach my $key (sort {$a <=> $b} keys %customservice){
118                 if($customservice{$key}[0] eq $val && $customservice{$key}[2] eq $prot){
119                         return $customservice{$key}[$field];
120                 }
121         }
122 }
123 sub get_srvgrp_port
124 {
125         my $val=shift;
126         my $prot=shift;
127         my $back;
128         my $value;
129         my @ips=();
130         foreach my $key (sort {$a <=> $b} keys %customservicegrp){
131                 if($customservicegrp{$key}[0] eq $val){
132                         if ($prot ne 'ICMP'){
133                                 $value=&get_srv_port($customservicegrp{$key}[2],1,$prot);
134                         }elsif ($prot eq 'ICMP'){
135                                 $value=&get_srv_port($customservicegrp{$key}[2],3,$prot);
136                         }
137                         push (@ips,$value) if ($value ne '') ;
138                 }
139         }
140         if($prot ne 'ICMP'){
141                 if ($#ips gt 0){$back="-m multiport --dports ";}else{$back="--dport ";}
142         }elsif ($prot eq 'ICMP'){
143                 $back="--icmp-type ";
144         }
145         
146         $back.=join(",",@ips);
147         return $back;
148 }
149 sub get_ipsec_net_ip
150 {
151         my $val=shift;
152         my $field=shift;
153         foreach my $key (sort {$a <=> $b} keys %ipsecconf){
154                 #adapt $val to reflect real name without subnet (if rule with only one ipsec subnet is created)
155                 my @tmpval = split (/\|/, $val);
156                 $val = $tmpval[0];
157                 if($ipsecconf{$key}[1] eq $val){
158                         return $ipsecconf{$key}[$field];
159                 }
160         }
161 }
162 sub get_ipsec_host_ip
163 {
164         my $val=shift;
165         my $field=shift;
166         foreach my $key (sort {$a <=> $b} keys %ipsecconf){
167                 if($ipsecconf{$key}[1] eq $val){
168                         return $ipsecconf{$key}[$field];
169                 }
170         }
171 }
172 sub get_ipsec_id {
173         my $val = shift;
174
175         foreach my $key (keys %ipsecconf) {
176                 if ($ipsecconf{$key}[1] eq $val) {
177                         return $key;
178                 }
179         }
180 }
181 sub get_ovpn_n2n_ip
182 {
183         my $val=shift;
184         my $field=shift;
185         foreach my $key (sort {$a <=> $b} keys %ccdhost){
186                 if($ccdhost{$key}[1] eq $val){
187                         return $ccdhost{$key}[$field];
188                 }
189         }
190 }
191 sub get_ovpn_host_ip
192 {
193         my $val=shift;
194         my $field=shift;
195         foreach my $key (sort {$a <=> $b} keys %ccdhost){
196                 if($ccdhost{$key}[1] eq $val){
197                         return $ccdhost{$key}[$field];
198                 }
199         }
200 }
201 sub get_ovpn_net_ip
202 {
203         
204         my $val=shift;
205         my $field=shift;
206         foreach my $key (sort {$a <=> $b} keys %ccdnet){
207                 if($ccdnet{$key}[0] eq $val){
208                         return $ccdnet{$key}[$field];
209                 }
210         }
211 }
212 sub get_grp_ip
213 {
214         my $val=shift;
215         my $src=shift;
216         foreach my $key (sort {$a <=> $b} keys %customgrp){
217                 if ($customgrp{$key}[0] eq $val){
218                         &get_address($customgrp{$key}[3],$src);
219                 }
220         }               
221         
222 }
223 sub get_std_net_ip
224 {
225         my $val=shift;
226         my $con=shift;
227         if ($val eq 'ALL'){
228                 return "0.0.0.0/0.0.0.0";
229         }elsif($val eq 'GREEN'){
230                 return "$netsettings{'GREEN_NETADDRESS'}/$netsettings{'GREEN_NETMASK'}";
231         }elsif($val eq 'ORANGE'){
232                 return "$netsettings{'ORANGE_NETADDRESS'}/$netsettings{'ORANGE_NETMASK'}";
233         }elsif($val eq 'BLUE'){
234                 return "$netsettings{'BLUE_NETADDRESS'}/$netsettings{'BLUE_NETMASK'}";
235         }elsif($val eq 'RED'){
236                 return "0.0.0.0/0";
237         }elsif($val =~ /OpenVPN/i){
238                 return "$ovpnsettings{'DOVPN_SUBNET'}";
239         }elsif($val =~ /IPsec/i){
240                 return "$ipsecsettings{'RW_NET'}";
241         }elsif($val eq 'IPFire'){
242                 return ;
243         }
244 }
245 sub get_interface
246 {
247         my $net=shift;
248         if($net eq "$netsettings{'GREEN_NETADDRESS'}/$netsettings{'GREEN_NETMASK'}"){
249                 return "$netsettings{'GREEN_DEV'}";
250         }
251         if($net eq "$netsettings{'ORANGE_NETADDRESS'}/$netsettings{'ORANGE_NETMASK'}"){
252                 return "$netsettings{'ORANGE_DEV'}";
253         }
254         if($net eq "$netsettings{'BLUE_NETADDRESS'}/$netsettings{'BLUE_NETMASK'}"){
255                 return "$netsettings{'BLUE_DEV'}";
256         }
257         if($net eq "0.0.0.0/0") {
258                 return &get_external_interface();
259         }
260         return "";
261 }
262 sub get_net_ip
263 {
264         my $val=shift;
265         foreach my $key (sort {$a <=> $b} keys %customnetwork){
266                 if($customnetwork{$key}[0] eq $val){
267                         return "$customnetwork{$key}[1]/$customnetwork{$key}[2]";
268                 }  
269         }
270 }
271 sub get_host_ip
272 {
273         my $val=shift;
274         my $src=shift;
275         foreach my $key (sort {$a <=> $b} keys %customhost){
276                 if($customhost{$key}[0] eq $val){
277                         if ($customhost{$key}[1] eq 'mac' && $src eq 'src'){
278                         return "-m mac --mac-source $customhost{$key}[2]";
279                         }elsif($customhost{$key}[1] eq 'ip' && $src eq 'src'){
280                                 return "$customhost{$key}[2]";
281                         }elsif($customhost{$key}[1] eq 'ip' && $src eq 'tgt'){
282                                 return "$customhost{$key}[2]";
283                         }elsif($customhost{$key}[1] eq 'mac' && $src eq 'tgt'){
284                                 return "none";
285                         }
286                 }  
287         }
288 }
289 sub get_addresses
290 {
291         my $hash = shift;
292         my $key  = shift;
293         my $type = shift;
294
295         my @addresses = ();
296         my $addr_type;
297         my $value;
298         my $group_name;
299
300         if ($type eq "src") {
301                 $addr_type = $$hash{$key}[3];
302                 $value = $$hash{$key}[4];
303
304         } elsif ($type eq "tgt") {
305                 $addr_type = $$hash{$key}[5];
306                 $value = $$hash{$key}[6];
307         }
308
309         if ($addr_type ~~ ["cust_grp_src", "cust_grp_tgt"]) {
310                 foreach my $grp (sort {$a <=> $b} keys %customgrp) {
311                         if ($customgrp{$grp}[0] eq $value) {
312                                 my @address = &get_address($customgrp{$grp}[3], $customgrp{$grp}[2], $type);
313
314                                 if (@address) {
315                                         push(@addresses, @address);
316                                 }
317                         }
318                 }
319         }elsif ($addr_type ~~ ["cust_geoip_src", "cust_geoip_tgt"] && $value =~ "group:") {
320                 $value=substr($value,6);
321                 foreach my $grp (sort {$a <=> $b} keys %customgeoipgrp) {
322                         if ($customgeoipgrp{$grp}[0] eq $value) {
323                                 my @address = &get_address($addr_type, $customgeoipgrp{$grp}[2], $type);
324
325                                 if (@address) {
326                                         push(@addresses, @address);
327                                 }
328                         }
329                 }
330         } else {
331                 my @address = &get_address($addr_type, $value, $type);
332
333                 if (@address) {
334                         push(@addresses, @address);
335                 }
336         }
337
338         return @addresses;
339 }
340 sub get_address
341 {
342         my $key   = shift;
343         my $value = shift;
344         my $type  = shift;
345
346         my @ret = ();
347
348         # If the user manually typed an address, we just check if it is a MAC
349         # address. Otherwise, we assume that it is an IP address.
350         if ($key ~~ ["src_addr", "tgt_addr"]) {
351                 if (&General::validmac($value)) {
352                         push(@ret, ["-m mac --mac-source $value", ""]);
353                 } else {
354                         push(@ret, [$value, ""]);
355                 }
356
357         # If a default network interface (GREEN, BLUE, etc.) is selected, we
358         # try to get the corresponding address of the network.
359         } elsif ($key ~~ ["std_net_src", "std_net_tgt", "Standard Network"]) {
360                 my $external_interface = &get_external_interface();
361
362                 my $network_address = &get_std_net_ip($value, $external_interface);
363
364                 if ($network_address) {
365                         my $interface = &get_interface($network_address);
366                         push(@ret, [$network_address, $interface]);
367                 }
368
369         # Custom networks.
370         } elsif ($key ~~ ["cust_net_src", "cust_net_tgt", "Custom Network"]) {
371                 my $network_address = &get_net_ip($value);
372                 if ($network_address) {
373                         push(@ret, [$network_address, ""]);
374                 }
375
376         # Custom hosts.
377         } elsif ($key ~~ ["cust_host_src", "cust_host_tgt", "Custom Host"]) {
378                 my $host_address = &get_host_ip($value, $type);
379                 if ($host_address) {
380                         push(@ret, [$host_address, ""]);
381                 }
382
383         # OpenVPN networks.
384         } elsif ($key ~~ ["ovpn_net_src", "ovpn_net_tgt", "OpenVPN static network"]) {
385                 my $network_address = &get_ovpn_net_ip($value, 1);
386                 if ($network_address) {
387                         push(@ret, [$network_address, ""]);
388                 }
389
390         # OpenVPN hosts.
391         } elsif ($key ~~ ["ovpn_host_src", "ovpn_host_tgt", "OpenVPN static host"]) {
392                 my $host_address = &get_ovpn_host_ip($value, 33);
393                 if ($host_address) {
394                         push(@ret, [$host_address, ""]);
395                 }
396
397         # OpenVPN N2N.
398         } elsif ($key ~~ ["ovpn_n2n_src", "ovpn_n2n_tgt", "OpenVPN N-2-N"]) {
399                 my $network_address = &get_ovpn_n2n_ip($value, 11);
400                 if ($network_address) {
401                         push(@ret, [$network_address, ""]);
402                 }
403
404         # IPsec networks.
405         } elsif ($key ~~ ["ipsec_net_src", "ipsec_net_tgt", "IpSec Network"]) {
406                 #Check if we have multiple subnets and only want one of them
407                 if ( $value =~ /\|/ ){
408                         my @parts = split(/\|/, $value);
409                         push(@ret, [$parts[1], ""]);
410                 }else{
411                         my $interface_mode = &get_ipsec_net_ip($value, 36);
412                         if ($interface_mode ~~ ["gre", "vti"]) {
413                                 my $id = &get_ipsec_id($value);
414                                 push(@ret, ["0.0.0.0/0", "${interface_mode}${id}"]);
415                         } else {
416                                 my $network_address = &get_ipsec_net_ip($value, 11);
417                                 my @nets = split(/\|/, $network_address);
418                                 foreach my $net (@nets) {
419                                         push(@ret, [$net, ""]);
420                                 }
421                         }
422                 }
423
424         # The firewall's own IP addresses.
425         } elsif ($key ~~ ["ipfire", "ipfire_src"]) {
426                 # ALL
427                 if ($value eq "ALL") {
428                         push(@ret, ["0/0", ""]);
429
430                 # GREEN
431                 } elsif ($value eq "GREEN") {
432                         push(@ret, [$netsettings{"GREEN_ADDRESS"}, ""]);
433
434                 # BLUE
435                 } elsif ($value eq "BLUE") {
436                         push(@ret, [$netsettings{"BLUE_ADDRESS"}, ""]);
437
438                 # ORANGE
439                 } elsif ($value eq "ORANGE") {
440                         push(@ret, [$netsettings{"ORANGE_ADDRESS"}, ""]);
441
442                 # RED
443                 } elsif ($value ~~ ["RED", "RED1"]) {
444                         my $address = &get_external_address();
445                         if ($address) {
446                                 push(@ret, [$address, ""]);
447                         }
448
449                 # Aliases
450                 } else {
451                         my $alias = &get_alias($value);
452                         if ($alias) {
453                                 push(@ret, [$alias, ""]);
454                         }
455                 }
456
457         # Handle rule options with GeoIP as source.
458         } elsif ($key eq "cust_geoip_src") {
459                 # Get external interface.
460                 my $external_interface = &get_external_interface();
461
462                 push(@ret, ["-m geoip --src-cc $value", "$external_interface"]);
463
464         # Handle rule options with GeoIP as target.
465         } elsif ($key eq "cust_geoip_tgt") {
466                 # Get external interface.
467                 my $external_interface = &get_external_interface();
468
469                 push(@ret, ["-m geoip --dst-cc $value", "$external_interface"]);
470
471         # If nothing was selected, we assume "any".
472         } else {
473                 push(@ret, ["0/0", ""]);
474         }
475
476         return @ret;
477 }
478 sub get_external_interface()
479 {
480         open(IFACE, "/var/ipfire/red/iface") or return "";
481         my $iface = <IFACE>;
482         close(IFACE);
483
484         return $iface;
485 }
486 sub get_external_address()
487 {
488         open(ADDR, "/var/ipfire/red/local-ipaddress") or return "";
489         my $address = <ADDR>;
490         close(ADDR);
491
492         return $address;
493 }
494 sub get_alias
495 {
496         my $id = shift;
497
498         foreach my $alias (sort keys %aliases) {
499                 if ($id eq $alias) {
500                         return $aliases{$alias}{"IPT"};
501                 }
502         }
503 }
504
505 sub get_nat_address {
506         my $zone = shift;
507         my $source = shift;
508
509         # Any static address of any zone.
510         if ($zone eq "AUTO") {
511                 if ($source && ($source !~ m/mac/i )) {
512                         my $firewall_ip = &get_internal_firewall_ip_address($source, 1);
513                         if ($firewall_ip) {
514                                 return $firewall_ip;
515                         }
516
517                         $firewall_ip = &get_matching_firewall_address($source, 1);
518                         if ($firewall_ip) {
519                                 return $firewall_ip;
520                         }
521                 }
522
523                 return &get_external_address();
524
525         } elsif ($zone eq "RED" || $zone eq "GREEN" || $zone eq "ORANGE" || $zone eq "BLUE") {
526                 return $netsettings{$zone . "_ADDRESS"};
527
528         } elsif ($zone ~~ ["Default IP", "ALL"]) {
529                 return &get_external_address();
530
531         } else {
532                 my $alias = &get_alias($zone);
533                 unless ($alias) {
534                         $alias = &get_external_address();
535                 }
536                 return $alias;
537         }
538
539         print_error("Could not find NAT address");
540 }
541
542 sub get_internal_firewall_ip_addresses
543 {
544         my $use_orange = shift;
545
546         my @zones = ("GREEN", "BLUE");
547         if ($use_orange) {
548                 push(@zones, "ORANGE");
549         }
550
551         my @addresses = ();
552         for my $zone (@zones) {
553                 next unless (exists $netsettings{$zone . "_ADDRESS"});
554
555                 my $zone_address = $netsettings{$zone . "_ADDRESS"};
556                 push(@addresses, $zone_address);
557         }
558
559         return @addresses;
560 }
561 sub get_matching_firewall_address
562 {
563         my $addr = shift;
564         my $use_orange = shift;
565
566         my ($address, $netmask) = split("/", $addr);
567
568         my @zones = ("GREEN", "BLUE");
569         if ($use_orange) {
570                 push(@zones, "ORANGE");
571         }
572
573         foreach my $zone (@zones) {
574                 next unless (exists $netsettings{$zone . "_ADDRESS"});
575
576                 my $zone_subnet = $netsettings{$zone . "_NETADDRESS"};
577                 my $zone_mask   = $netsettings{$zone . "_NETMASK"};
578
579                 if (&General::IpInSubnet($address, $zone_subnet, $zone_mask)) {
580                         return $netsettings{$zone . "_ADDRESS"};
581                 }
582         }
583
584         return 0;
585 }
586 sub get_internal_firewall_ip_address
587 {
588         my $subnet = shift;
589         my $use_orange = shift;
590
591         my ($net_address, $net_mask) = split("/", $subnet);
592         if ((!$net_mask) || ($net_mask ~~ ["32", "255.255.255.255"])) {
593                 return 0;
594         }
595
596         # Convert net mask into correct format for &General::IpInSubnet().
597         $net_mask = &General::iporsubtodec($net_mask);
598
599         my @addresses = &get_internal_firewall_ip_addresses($use_orange);
600         foreach my $zone_address (@addresses) {
601                 if (&General::IpInSubnet($zone_address, $net_address, $net_mask)) {
602                         return $zone_address;
603                 }
604         }
605
606         return 0;
607 }
608
609 sub get_geoip_locations() {
610         return &GeoIP::get_geoip_locations();
611 }
612
613 return 1;