#!/usr/bin/perl -w
#
############################################################################
#
# File: fwknop_serv
#
# Purpose: To provide a minimal TCP server over which the fwknop client can
#          connect to send the SPA packet.  This breaks the traditional SPA
#          model of only using a single packet to transmit desired access
#          modifications, but if you want to send SPA packets over the Tor
#          network then this server is necessary.  A circuit through the
#          Tor network is built up over successive TCP connections, and
#          there is no way to send packets through Tor without an
#          established circuit.
#
# Author: Michael Rash (mbr@cipherdyne.org)
#
# Version: 1.9.8
#
# Copyright (C) 2004-2008 Michael Rash (mbr@cipherdyne.org)
#
# License (GNU Public 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, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
#    USA
#
############################################################################
#
# $Id: fwknop_serv 1277 2008-10-01 02:37:05Z mbr $
#

use IO::Socket;
use POSIX;
use strict;

my $config_file = '/etc/fwknop/fwknop.conf';
my %config = ();

my @required_vars = qw(
    TCPSERV_PORT
    TCPSERV_PID_FILE
    LOCALE
);

&setup();

my $server = IO::Socket::INET->new(
    LocalPort => $config{'TCPSERV_PORT'},
    Type   => SOCK_STREAM,
    Reuse  => 1,
    Listen => 5
) or die $!;

&drop_privs();

### trivial loop; we just want the local TCP stack to accept connections;
### fwknopd gets data from pcap anyway
while (my $client = $server->accept()) {

    $client->recv(my $request, 1500, 0);
}

close $server;

exit 0;

#================================= end main ===============================

sub drop_privs() {

    my ($login, $pass, $uid, $gid) = getpwnam('nobody');
    unless ($uid and $gid) {
        warn "[-] Could not get UID and GID of user nobody";
    }

    ### drop privileges
    if ($uid and $gid) {
        POSIX::setuid($uid);
        POSIX::setgid($gid);
    }

    return;
}

sub setup() {

    ### import config
    &import_config();

    ### expand any embedded vars within config values
    &expand_vars();

    ### make sure all the vars we need are actually in the config file.
    &required_vars();

    ### validate config
    &validate_config();

    my $pid = fork();
    exit 0 if $pid;
    die "[*] $0: Couldn't fork: $!" unless defined $pid;
    POSIX::setsid() or die "[*] $0: Can't start a new session: $!";

    ### make sure there isn't another fwknop_serv process already running
    &uniquepid();

    ### write our pid out to disk
    &writepid();

    &handle_locale();

    return;
}

sub handle_locale() {
    if ($config{'LOCALE'} ne 'NONE') {
        ### set LC_ALL env variable
        $ENV{'LC_ALL'} = $config{'LOCALE'};
    }
    return;
}

sub validate_config() {
    unless ($config{'TCPSERV_PORT'} > 0
            and $config{'TCPSERV_PORT'} < 65535) {
        die "[*] TCPSERV_PORT must be between 1 and 65535";
    }
    return;
}

sub required_vars() {
    for my $var (@required_vars) {
        unless (defined $config{$var}) {
            die "[*] Variable $var is not defined in $config_file";
        }
    }
    return;
}

sub uniquepid() {
    if (-e $config{'TCPSERV_PID_FILE'}) {
        my $caller = $0;
        open PIDFILE, "< $config{'TCPSERV_PID_FILE'}";
        my $pid = <PIDFILE>;
        close PIDFILE;
        chomp $pid;
        if (kill 0, $pid) {  # fwknop_serv is already running
            die "[*] fwknop_serv (pid: $pid) is already running!  Exiting.\n";
        }
    }
    return;
}

sub writepid() {
    open P, "> $config{'TCPSERV_PID_FILE'}" or die "[*] Could not open ",
        "$config{'TCPSERV_PID_FILE'}: $!";
    print P $$, "\n";
    close P;
    chmod 0600, $config{'TCPSERV_PID_FILE'};
    return;
}

sub import_config() {
    open C, "< $config_file" or die "[*] Could not open ",
        "config file $config_file: $!";
    while (<C>) {
        next if /^\s*#/;
        if (/^\s*(\S+)\s+(\S+);/) {
	    $config{$1} = $2;
        }
    }
    close C;
    return;
}

sub expand_vars() {

    my $has_sub_var = 1;
    my $resolve_ctr = 0;

    while ($has_sub_var) {
        $resolve_ctr++;
        $has_sub_var = 0;
        if ($resolve_ctr >= 20) {
            die "[*] Exceeded maximum variable resolution counter.";
        }
	for my $var (keys %config) {
	    my $val = $config{$var};
	    if ($val =~ m|\$(\w+)|) {
		my $sub_var = $1;
		die "[*] sub-ver $sub_var not allowed within same ",
                    "variable $var" if $sub_var eq $var;
		if (defined $config{$sub_var}) {
		    $val =~ s|\$$sub_var|$config{$sub_var}|;
		    $config{$var} = $val;
		} else {
		    die "[*] sub-var \"$sub_var\" not defined in ",
		        "config for var: $var."
		}
		$has_sub_var = 1;
	    }
	}
    }
    return;
}
