#!/usr/bin/perl =for commnet Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks Copyright (C) 2015 Conrad Lara See Contributors file for additional contributors Copyright (c) 2013 David Rivenburg et al. BroadBand-HamNet This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Additional Terms: Additional use restrictions exist on the AREDN(TM) trademark and logo. See AREDNLicense.txt for more info. Attributions to the AREDN Project must be retained in the source code. If importing this code into a new or existing project attribution to the AREDN project must be added to the source code. You must not misrepresent the origin of the material conained within. Modified versions must be modified to attribute to the original source and be marked in reasonable ways as differentiate it from the original version. =cut $debug = 0; BEGIN {push @INC, '/www/cgi-bin'}; use perlfunc; $config = nvram_get("config"); $node = nvram_get("node"); $node = "NOCALL" if $node eq ""; read_postdata(); if($config eq "" or -e "/tmp/reboot-required") { http_header(); html_header("$node setup", 1); print "
\n"; print "
\n"; alert_banner(); navbar("ports"); print "

"; if($config eq "") { print "This page is not available until the configuration has been set."; } else { print "The configuration has been changed.
This page will not be available until the node is rebooted.\n"; } print "
"; page_footer(); print "\n"; exit; } # check for dmz mode if(-e "/etc/config/dmz-mode") { chomp ($dmz_mode = `cat /etc/config/dmz-mode`) } else { $dmz_mode = 0 } # get the network details of the lan interface for dhcp calculations ($lanip, $lanmask, $junk, $lannet, $lancidr) = get_ip4_network(get_interface("lan")); $lannet_d = ip2decimal($lannet); $tmpdir = "/tmp/web/ports"; system "rm -rf $tmpdir" unless $parms{reload}; system "mkdir -p $tmpdir"; $portfile = "/etc/config.$config/_setup.ports"; $dhcpfile = "/etc/config.$config/_setup.dhcp"; $servfile = "/etc/config.$config/_setup.services"; if($config eq "mesh") { my $suffix = $dmz_mode ? ".dmz" : ".nat"; $portfile .= $suffix; $dhcpfile .= $suffix; $servfile .= $suffix; } # if a reset or a first time page load # read the data from the config files if($parms{button_reset} or not $parms{reload}) { $i = 0; foreach(`cat $portfile 2>/dev/null`) { next if /^\s*#/; next if /^\s*$/; chomp; # set parameters if(/(\S+)\s+=\s+(\S+)/) { $parms{$1} = $2; next; } # set port forwarding rules @parts = split /[:]/, $_; next unless scalar(@parts) == 6; ++$i; #foreach $var (qw(intf type out ip in enable adv link proto suffix name)) foreach $var (qw(intf type out ip in enable)) { $parms{"port${i}_$var"} = shift @parts; } } $parms{port_num} = $i; # set dhcp reservations # ip addresses are stored as offsets from the lan network address $i = 0; foreach(`cat $dhcpfile 2>/dev/null`) { next if /^\s*#/; next if /^\s*$/; chomp; @parts = split /\s+/, $_; next unless scalar(@parts) == 3; ++$i; $parms{"dhcp${i}_host"} = $parts[2]; $parms{"dhcp${i}_ip"} = add_ip_address($lannet, $parts[1]); $parms{"dhcp${i}_mac"} = $parts[0]; } $parms{dhcp_num} = $i; # services $i = 0; foreach(`cat $servfile 2>/dev/null`) { next if /^\s*#/; next if /^\s*$/; chomp; @parts = split /\|/, $_; $parts[5] = "" unless $parts[5]; next unless scalar(@parts) == 6; ++$i; foreach $var (qw(name link proto host port suffix)) { $parms{"serv${i}_$var"} = shift @parts; } } $parms{serv_num} = $i; # sanitize the "add" values $parms{port_add_intf} = $dmz_mode ? "wan" : "wifi"; $parms{port_add_type} = "tcp"; $parms{dmz_ip} = "" unless defined $parms{dmz_ip}; foreach $var (qw(port_add_out port_add_ip port_add_in dhcp_add_host dhcp_add_ip dhcp_add_mac serv_add_name serv_add_proto serv_add_host serv_add_port serv_add_suffix)) { $parms{$var} = ""; } } # get the dhcp range # assume that the lan setup is the only one that exists foreach(`cat /etc/config/dhcp`) { $dhcp_start = $1 if /option start\s+(\d+)/; $dhcp_limit = $1 if /option limit\s+(\d+)/; } $dhcp_end = $dhcp_start + $dhcp_limit - 1; # # load and validate the ports # for($i = 1, @list = (); $i <= $parms{port_num}; ++$i) { push @list, $i } push @list, "_add"; $port_num = 0; open(FILE, ">$tmpdir/ports"); foreach $val (@list) { # load strings foreach $var (qw(intf type out ip in)) { $varname = "port${val}_$var"; $parms{$varname} = "" unless $parms{$varname}; $parms{$varname} =~ s/^\s+//; $parms{$varname} =~ s/\s+$//; eval sprintf("\$%s = \$parms{%s}", $var, $varname); } # load bools foreach $var (qw(enable)) { $varname = "port${val}_$var"; $parms{$varname} = 0 unless $parms{$varname}; eval sprintf("\$%s = \$parms{%s}", $var, $varname); } $enable = 1 if $val eq "_add"; if($val eq "_add") { next unless ($out or $ip or $in) and ($parms{port_add} or $parms{button_save}) } else { next if $parms{"port${val}_del"} } if($val eq "_add" and $parms{button_save}) { push(@port_err, "$val this rule must be added or cleared out before saving changes"); next; } if($out =~ /-/) # this is a port range { if(validate_port_range($out)) { $out =~ s/\s+//; ($in) = $out =~ /^(\d+)/; # force inside to match outside } else { push(@port_err, "$val '$out' is not a valid port range"); } } else { if($out eq "") { push @port_err, "$val an outside port is required" } else { push(@port_err, "$val '$out' is not a valid port") unless validate_port($out) } } if($ip eq "") { push @port_err, "$val an address must be selected" } elsif(not validate_ip($ip)) { push @port_err, "$val '$ip' is not a valid address" } if($in eq "") { push @port_err, "$val a LAN port is required" } else { push(@port_err, "$val '$in' is not a valid port") unless validate_port($in) } next if $val eq "_add" and @port_err and $port_err[-1] =~ /^_add /; # commit the data for this rule ++$port_num; $usedports{$out} = 1; $type = "both" unless $type eq "tcp" or $type eq "udp"; print FILE "$intf:$type:$out:$ip:$in:$enable\n"; foreach $var (qw(intf type out ip in enable)) { eval sprintf("\$parms{port%d_%s} = \$%s", $port_num, $var, $var); } if($val eq "_add") { $parms{port_add_intf} = "wifi"; $parms{port_add_out} = ""; $parms{port_add_ip} = ""; $parms{port_add_in} = ""; } } if($parms{dmz_ip}) { print FILE "dmz_ip = $parms{dmz_ip}\n"; push(@dmz_err, "'$parms{dmz_ip}' is not a valid address") unless validate_ip($parms{dmz_ip}); } close(FILE); $parms{port_num} = $port_num; # # load and validate the dhcp reservations # for($i = 1, @list = (); $i <= $parms{dhcp_num}; ++$i) { push @list, $i } push @list, "_add"; $dhcp_num = 0; foreach $val (@list) { $host = $parms{"dhcp${val}_host"}; $ip = $parms{"dhcp${val}_ip"}; $mac = $parms{"dhcp${val}_mac"}; if($val eq "_add") { next unless ($host or $ip or $mac) and ($parms{dhcp_add} or $parms{button_save}); } else { next if $parms{"dhcp${val}_del"}; } if($val eq "_add" and $parms{button_save}) { push @dhcp_err, "$val this reservation must be added or cleared out before saving changes"; next; } if(validate_hostname($host)) { push(@dhcp_err, "$val hostname '$host' is already in use") if $hosts{$host}; } else { if($host) { push @dhcp_err, "$val '$host' is not a valid hostname" } else { push @dhcp_err, "$val a hostname is required" } } if(validate_ip($ip)) { if($addrs{$ip}) { push(@dhcp_err, "$val $ip is already in use"); } elsif($ip eq $lanip or not validate_same_subnet($ip, $lanip, $lanmask) or not validate_ip_netmask($ip, $lanmask)) { push @dhcp_err, "$val '$ip' is not a valid LAN address"; } } else { if($ip) { push @dhcp_err, "$val '$ip' is not a valid address" } else { push @dhcp_err, "$val an IP Address must be selected" } } if(validate_mac($mac)) { push(@dhcp_err, "$val MAC $mac is already in use") if $macs{$mac}; } else { if($mac) { push @dhcp_err, "$val '$mac' is not a valid mac address" } else { push @dhcp_err, "$val a MAC Address is required" } } next if $val eq "_add" and @dhcp_err and $dhcp_err[-1] =~ /^$val /; # commit the data for this reservation ++$dhcp_num; #print FILE "$mac $ip $host\n"; $parms{"dhcp${dhcp_num}_host"} = $host; $parms{"dhcp${dhcp_num}_ip"} = $ip; $parms{"dhcp${dhcp_num}_mac"} = $mac; $hosts{$host} = 1; $addrs{$ip} = 1; $macs{$mac} = 1; if($val eq "_add") { $parms{dhcp_add_host} = ""; $parms{dhcp_add_ip} = ""; $parms{dhcp_add_mac} = ""; } } # add existing leases foreach $lease (keys %parms) { #$hosts{$parms{$lease}} = 1 if $lease =~ /^lease\d+_host$/; next unless ($n) = $lease =~ /^lease(\d+)_add$/; next unless $parms{$lease}; # eliminate duplicate mac addresses $found = 0; foreach(keys %parms) { next unless /dhcp\d+_mac/; $found = 1 if $parms{$_} eq $parms{"lease${n}_mac"}; } next if $found; ++$dhcp_num; $host = $parms{"lease${n}_host"}; $ip = $parms{"lease${n}_ip"}; $mac = $parms{"lease${n}_mac"}; $parms{"dhcp${dhcp_num}_host"} = $host; $parms{"dhcp${dhcp_num}_ip"} = $ip; $parms{"dhcp${dhcp_num}_mac"} = $mac; push(@dhcp_err, "$dhcp_num hostname '$host' is already in use") if $hosts{$host}; push(@dhcp_err, "$dhcp_num $ip is already in use") if $addrs{$ip}; push(@dhcp_err, "$dhcp_num MAC $mac is already in use") if $macs{$mac}; last; } $parms{dhcp_num} = $dhcp_num; $dhcphosts{$lanip} = "localnode"; # replace "blank" dhcp hostnames and save the dhcp info into the tmpdir open(FILE, ">$tmpdir/dhcp"); for($i = $nn = 1; $i <= $parms{dhcp_num}; $i++) { if($parms{"dhcp${i}_host"} eq "*") { while(exists $hosts{"noname$nn"}) { $nn++ } $parms{"dhcp${i}_host"} = "noname$nn"; $hosts{"noname$nn"} = 1; } printf FILE "%s %d %s\n", $parms{"dhcp${i}_mac"}, ip2decimal($parms{"dhcp${i}_ip"}) - $lannet_d, $parms{"dhcp${i}_host"}; # save it in a lookup table $dhcphosts{$parms{"dhcp${i}_ip"}} = $parms{"dhcp${i}_host"} unless $dhcphosts{$parms{"dhcp${i}_ip"}}; } close(FILE); # # load and validate the services # for($i = 1, @list = (); $i <= $parms{serv_num}; ++$i) { push @list, $i } push @list, "_add"; $serv_num = 0; $hosts{""} = 1; $hosts{$node} = 1; $usedports{""} = 1; open(FILE, ">$tmpdir/services"); foreach $val (@list) { foreach $var (qw(name proto host port suffix)) { $varname = "serv${val}_$var"; $parms{$varname} = "" unless $parms{$varname}; $parms{$varname} =~ s/^\s+//; $parms{$varname} =~ s/\s+$//; eval sprintf("\$%s = \$parms{%s}", $var, $varname); } $host = $node unless $dmz_mode; # remove services that have had their host or port deleted #next if $val ne "_add" and not ($dmz_mode ? $hosts{$host} : $usedports{$port}); next if $val ne "_add" and not ($dmz_mode ? $hosts{$host} : 1 ); $link = $parms{"serv${val}_link"}; $link = 0 unless $link; if($val eq "_add") { next unless ($name or $proto or $port or $suffix) and ($parms{serv_add} or $parms{button_save}) } else { next if $parms{"serv${val}_del"} or not ($name or $proto or $port or $suffix); } if($val eq "_add" and $parms{button_save}) { push @serv_err, "$val this service must be added or cleared out before saving changes"; next; } if($name eq "") { push @serv_err, "$val a name is required"; } else { push(@serv_err, "$val '$name' is not a valid service name") unless validate_service_name($name); push(@serv_err, "$val the name '$name' is already in use") if $servicenames{$name}; } if($link) { $parms{"serv${val}_proto"} = $proto = "http" unless $proto; #$port = 80 if $proto eq "http" and not $port; push(@serv_err, "$val '$proto' is not a valid service protocol") unless validate_service_protocol($proto); if($port eq "") { push @serv_err, "$val a port number is required" } else { push(@serv_err, "$val '$port' is not a valid port") unless validate_port($port) } push(@serv_err, "$val '$suffix' is not a valid service suffix") unless validate_service_suffix($suffix); } elsif($val eq "_add") { $proto = $port = $suffix = ""; } next if $val eq "_add" and @serv_err and $serv_err[-1] =~ /^_add /; # commit the data for this service ++$serv_num; $servicenames{$name} = 1; print FILE "$name|$link|$proto|$host|$port|$suffix\n"; foreach $var (qw(name link proto host port suffix)) { eval sprintf("\$parms{serv%d_%s} = \$%s", $serv_num, $var, $var); } if($val eq "_add") { foreach(qw(name link proto host port suffix)) { $parms{"serv_add_$_"} = "" } } } close(FILE); $parms{serv_num} = $serv_num; # # save configuration # if($parms{button_save} and not (@port_err or @dhcp_err or @dmz_err or @serv_err)) { system "cp -f $tmpdir/ports $portfile"; system "cp -f $tmpdir/dhcp $dhcpfile"; system "cp -f $tmpdir/services $servfile"; push(@errors, "problem with configuration") if system "/usr/local/bin/node-setup -a -p $config"; unless($debug == 3) { push(@errors, "problem with dnsmasq") if system "/etc/init.d/dnsmasq reload >/dev/null 2>&1"; push(@errors, "problem with port setup") if system "/etc/init.d/firewall reload >/dev/null 2>&1"; if($config eq "mesh") { push(@errors, "problem with olsr setup") if system "/etc/init.d/olsrd restart >/dev/null 2>&1"; } } } # # generate the page # http_header() unless $debug == 2; html_header("$node setup", 1); print "
\n"; alert_banner(); print "
\n" unless $debug == 2; print "\n" if $debug == 2; print "\n"; print "\n"; # # control buttons # print "\n"; push @hidden, ""; # # messages # if($parms{button_save}) { if(@port_err or @dhcp_err or @dmz_err or @serv_err) { print "\n"; } elsif(@errors) { print "\n"; } else { print "\n"; } print "\n"; } # # everything else # if($dmz_mode) { print "\n"; print "\n"; print "\n"; print "\n"; } else { print "\n"; print "\n"; print "\n"; print "\n"; } print "
\n"; navbar("ports"); print "
Help          \n"; print "
 
Configuration NOT saved!
Configuration saved, however:
"; foreach(@errors) { print "$_
" } print "
Configuration saved and is now active.
 
\n"; print "\n"; if($config eq "mesh") { print "\n"; } print "
\n"; &print_reservations(); print "     \n"; &print_services(); print "
 

\n"; &print_forwarding(); print "
\n"; print "\n"; if($config eq "mesh") { print "\n"; } print "
\n"; &print_forwarding(); print "     \n"; &print_services(); print "
 

\n"; &print_reservations(); print "
\n"; push @hidden, ""; push @hidden, ""; push @hidden, ""; foreach(@hidden) { print "$_\n" } print "
\n"; show_debug_info(); page_footer(); print "\n"; exit; # # page subsections # sub print_forwarding { print "\n"; print ""; print ""; print "\n"; for($i = 1, @list = (); $i <= $parms{port_num}; ++$i) { push @list, $i } push @list, "_add"; foreach $val (@list) { foreach $var (qw(intf type out ip in enable adv link proto suffix name)) { eval sprintf("\$%s = \$parms{port%s_%s}", $var, $val, $var); } print "\n" if $val eq "_add" and scalar(@list) > 1; # enable checkbox if(0)#$val ne "_add") { print "" : " rowspan=2>"; print "enable  "; } else { print ""; push @hidden, ""; } # port forwarding settings print ""; print ""; print "\n"; print "\n"; print "\n"; print "\n"; # display any errors while(@port_err and $port_err[0] =~ /^$val /) { $err = shift @port_err; $err =~ s/^\S+ //; print "\n"; } print "\n"; } # dmz server for nat mode unless($dmz_mode) { print "\n"; print ""; print "\n"; foreach(@dmz_err) { print "\n" } } print "
Port Forwarding
 InterfaceTypeOutside
Port
LAN IPLAN
Port
 
  
$err
DMZ Server  
$_
\n"; } sub print_reservations { print "\n"; print "\n"; print "\n"; print "\n"; for($i = 1, @list = (); $i <= $parms{dhcp_num}; ++$i) { push @list, $i } push @list, "_add"; foreach $val (@list) { $host = $parms{"dhcp${val}_host"}; $ip = $parms{"dhcp${val}_ip"}; $mac = lc $parms{"dhcp${val}_mac"}; print "\n" if $val eq "_add" and scalar(@list) > 1; print "\n"; print "\n"; print "\n"; print "\n"; # display any errors while(@dhcp_err and $dhcp_err[0] =~ /^$val /) { $err = shift @dhcp_err; $err =~ s/^\S+ //; print "\n"; } print "\n"; } print "\n"; print "\n"; $i = 0; foreach(`cat /tmp/dhcp.leases 2>/dev/null`) { ++$i; ($junk, $mac, $ip, $host) = split /\s+/, $_; print "\n"; print ""; print "\n"; push @hidden, ""; push @hidden, ""; push @hidden, ""; } print "\n" unless $i; print "
DHCP Address Reservations
HostnameIP AddressMAC Address
 
$err
 
Current DHCP Leases
$host$ip$mac "; print "
there are no active leases
\n"; } sub print_services { print "\n"; unless($dmz_mode or $parms{port_num} or $parms{dmz_ip}) { if($dmz_mode) { print "\n" } else { print "\n" } print "\n"; print "
Advertised Services
 
 

", "
none
\n"; return; } print "\n" if $dmz_mode; print "NameLinkURL", $dmz_mode ? "" : "

", "\n"; print "\n" if $dmz_mode; for($i = 1, @list = (); $i <= $parms{serv_num}; ++$i) { push @list, $i } push @list, "_add"; foreach $val (@list) { foreach $var (qw(name link proto host port suffix)) { eval sprintf("\$%s = \$parms{serv%s_%s}", $var, $val, $var); } unless($dmz_mode) { $parms{"serv${val}_host"} = $host = $node } #unless($link) { $proto = $port = $suffix = "" } print "\n" if $val eq "_add" and scalar(@list) > 1; print ""; print ""; print "
"; print ""; if($dmz_mode) { print "://\n"; } else { print "://$host"; } print ": / "; print " \n"; # display any errors while(@serv_err and $serv_err[0] =~ /^$val /) { $err = shift @serv_err; $err =~ s/^\S+ //; print "$err\n"; } unless($link or $val eq "_add") { push @hidden, ""; push @hidden, ""; push @hidden, ""; push @hidden, ""; } print "\n"; } print "\n"; }