#!/usr/bin/perl

use strict;
use warnings;
use IO::Socket::SSL;
use Storable qw(nfreeze);
use Time::HiRes qw(sleep);
use MIME::Base64;
use File::Temp qw(tempfile);

### How to Use
#0.  **Generate certs** $ openssl req -x509 -newkey rsa:4096 -keyout server-key.pem -out server-cert.pem -sha256 -days 3650 -nodes -subj "/CN=connmap.server" (demo ones generated by me included, if not there server script auto generates new ones)
#1.  **Place the `server-cert.pem` file** in the same directory as the `connmapperl-client.pl` script on each client machine.
#3.  **Run the client**, providing the server's actual IP address as a command-line argument:
#    *   **LAN client:** `perl connmapperl-client.pl 192.168.1.100 your_password`
#    *   **Internet client:** `perl connmapperl-client.pl 203.0.113.50 your_password`

# --- Embedded SSL Certificate (Base64 encoded) ---
# Replace this with your actual server-cert.pem content encoded in base64
# To generate: base64 -w 0 server-cert.pem
my $sslcert_data_base64 = 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZMekNDQXhlZ0F3SUJBZ0lKQUxoZDl5Z3FpZlUvTUEwR0NTcUdTSWIzRFFFQkN3VUFNQmt4RnpBVkJnTlYKQkFNVERtTnZibTV0WVhBdWMyVnlkbVZ5TUI0WERUSTFNRGt3TkRBeE1qY3pOMW9YRFRNMU1Ea3dNakF4TWpjegpOMW93R1RFWE1CVUdBMVVFQXhNT1kyOXVibTFoY0M1elpYSjJaWEl3Z2dJaU1BMEdDU3FHU0liM0RRRUJBUVVBCkE0SUNEd0F3Z2dJS0FvSUNBUURCeTZ2UkFDYlc0M3daTjVaTytNNDR0ZnVDVTcyTnJRc0hnNlN3eC9PVW16SFcKZUptVFNiMmxpQjRJZjhmc25ZR0FxamRVTXRkT0VZb2lFOHFVYjliMlRUQWhTVUtYZXRUbWF4b0ZtN1ZPTW91OAp3K1Z1S3JiQmlnc3RNWCtLSks5bjlLcEMxcVA1OXZaV2tQbXVCRWhwL1dRcTJxLytCalpUOHZMWkJSd29TQkVmCmFHeEtvUkpvWTVpSks0TjZTV25YZTNGREhvQjh3a0cwWWU1VzF6cFhWeCtvUGRXUW5Ocy82K0E2ZlRpbWRwZ08KZVdTUmdGcjJEZUxIQWNBUVpMdGxyTTRXazkzWVpVWHNCdlA2S0Q1VnpibUY4WkZ4VXNwQjVGZXI3TnZtN2ZqZApyWlJ2MzFjMFMrUWEwY2J5UnJHQkY0bkxTYmY2d1VDT0pXeUhOMGhFYU0rT2hCRVFabWFHTnpKdjE3UmNNdldzCnpSTkJ3bWFId2tHTTNGL29uWXljVTUyT1ZPOWdMTEVkVmwvSHdhMUltaWxHVDI3eGZCcmxseUdFN2kwR000VmoKZkNpMmtua2NJamU1YS9sYUlncTV6bG1DUnJTSlU0bTdvTHZIZTIzUHZlSFlnaDc3SUIzQjIzYVprdUN3QlA2aApvRTdZQm1qM1N6YkkvN3R5T3dBcmVTbFZIdk5SeTEvcTE3ODh6WGcvclpDWjFvczMyZExuVFVYWld2Y0lQUTVFCjlCUzdpK2tXc1J4YlNzemRwN0RMcjRIZVNFbXpCMEZaTGc4UjVEWG5saUl0V2hINU9MQ1p5NmJaaXlqT0ZiQ0EKU0RSNERxQzkrVSszQzBTU214eFpzVDlwTzl0OVcxQVdPdzZ4WW5TaENlWGJJa25Pbk1XNWFNSUtJMEtqQ1FJRApBUUFCbzNvd2VEQWRCZ05WSFE0RUZnUVVTbGJ6S0lXdmxnREk2K3FLM04wQk1lcElWNk13U1FZRFZSMGpCRUl3ClFJQVVTbGJ6S0lXdmxnREk2K3FLM04wQk1lcElWNk9oSGFRYk1Ca3hGekFWQmdOVkJBTVREbU52Ym01dFlYQXUKYzJWeWRtVnlnZ2tBdUYzM0tDcUo5VDh3REFZRFZSMFRCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQwpBZ0VBdEJ6QUxtcGxJdXlrSkx6UWR0cFQ3ZnZjeThPc1dzWUVQUWVYMlNSOHVGK1plcUJ4ek9tUlFHWjNyME8zCmlxTEI4a2lQZCtRQnl4R3FCTkhIRHpsUkRvNXBnenkvZlR3bVN5SS82bXF4cnlKYmdwcFl1Q2diMUswQXVzZFYKR0ZHYkZ6bXcvTW1hcHdBSXFiOVJFcGdYWE11KzExZk1rOFB0aWxyQzdEUUMzUFBSaVJ2QTYvQkRIZDdxajBmYgorMXVhNWZndjdUSTFZdDhoWnZWL21zUXBoM1BrQjg3VnBLb3Brb0dQWHdKNElKU3lmYnc4OEh1UytzQXJFbzkwCmpUWjNDRnNPT3g3YTBCZ3FsOUJJa0JwOG5NUWMwYWhYUHNKTm1UQUNTT01WUHBkYzBQS1EyU3pzQ3F0WWlTNmMKUnpKZUMyVHZOTWlpSVdvZnljd05peUo5eUQwQWZIYVlUTmVFcnY4Z1F5cVVMeXB6WkVuek50WE43WmxwcGVBOApVVVYyYURhZTBPZWlDTlBja1R3azJWZGtMREMzbG55aFhUb2QxVkN0SnZKOVdsSWFGdDNvbDFZTTBWRit3YXRrCnNmNGlvc3ZRUTd0Y2tjN3lLb2piVXpBNW1YUC83S28zakpoaWxGMVE3TUV1Y0JlcldXbnRGSm8vNHdFWjN2cUYKSnIvRjU0amY4RG9BUTBhM1ZHc1hCOU5UM3U3US8wNytrZnFETncwb2RUbDZGK2JaK3VpL29kdWcraVJud0tFRgpveVBUc00vdXFXRTVnM2hiZDZSbTVGQW82bGN5elNxNndmTy95Sko5eEl2bGJEMWM1YlovdjFwbjJQbDBiYTB6Cjd2YXVkV2EyYjhvK1pVeE9lNExnT1MwUlJ5REdhZmN4UVoyYStTMnkwSmVlQ1l3PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==';

# --- Configuration ---
my $server_ip   = '192.168.1.121'; # Default to localhost
my $server_port = 6789;
my $password    = "password"; # Default password

# --- Check for command-line arguments ---
if (defined $ARGV[0]) {
    $server_ip = $ARGV[0];
    print "Server IP set from command line: $server_ip\n";
}
if (defined $ARGV[1]) {
    $password = $ARGV[1];
    print "Password set from command line.\n";
}

# --- Reconnection Policy ---
my $initial_retry_delay = 2;
my $max_retry_delay     = 300;
my $current_retry_delay = $initial_retry_delay;

# --- Handle SIGPIPE ---
$SIG{PIPE} = 'IGNORE';

# --- SSL Certificate Handler ---
my $ssl_ca_file_path;
my $temp_cert_file;

sub get_ssl_cert_path {
    if (-f 'server-cert.pem') {
        print "Using server-cert.pem file from current directory\n";
        return 'server-cert.pem';
    }
    
    print "No server-cert.pem found, using embedded certificate\n";
    my $sslcert_data = decode_base64($sslcert_data_base64);
    
    ($temp_cert_file, $ssl_ca_file_path) = tempfile(
        TEMPLATE => 'ssl_cert_XXXXXX',
        SUFFIX   => '.pem',
        UNLINK   => 1
    );
    
    print $temp_cert_file $sslcert_data;
    close $temp_cert_file;
    
    return $ssl_ca_file_path;
}

# Initialize SSL certificate path
$ssl_ca_file_path = get_ssl_cert_path();

#======================================================================
# Data Gathering Functions (REPLACED WITH NEW FAST/CORRECT VERSION)
#======================================================================
sub get_connections_with_progs {
    my $ss_cmd = 'ss -t -n -p -o -4 state established';
    my $output = qx($ss_cmd);
    
    my @connections;
    my @lines = split /\n/, $output;
    shift @lines; # Remove header line

    for my $line (@lines) {
        my @fields = split /\s+/, $line, 5;
        next unless @fields >= 4;

        my ($local_ip, $local_port) = ($fields[2] =~ /^(.+):(\d+)$/);
        my ($peer_ip,  $peer_port)  = ($fields[3] =~ /^(.+):(\d+)$/);
        
        if ($local_ip and $local_port and $peer_ip and $peer_port) {
            my $prog = '';
            if (defined $fields[4] and $fields[4] =~ /users:\(\("([^"]+)"/) {
                $prog = $1;
            }

            push @connections, {
                local_ip    => $local_ip,
                local_port  => $local_port,
                ip          => $peer_ip,
                port        => $peer_port,
                programname => $prog,
            };
        }
    }
    return \@connections; # Return as a reference
}

#======================================================================
# Networking Functions (ADDED send_message and MODIFIED connect_to_server)
#======================================================================

sub connect_to_server {
    my $socket = IO::Socket::SSL->new(
        PeerAddr => $server_ip,
        PeerPort => $server_port,
        Proto    => 'tcp',
        Timeout  => 5,
        SSL_version => 'TLSv1',
        SSL_verify_mode => 1, 
        SSL_ca_file     => $ssl_ca_file_path,
        SSL_verify_hostname_scheme => 'http',
        SSL_hostname => 'connmap.server',
    );

    unless ($socket) {
        warn "SSL Connection failed: $! , $@, " . IO::Socket::SSL::errstr();
        return undef;
    }

    print "SSL connection established to $server_ip. Authenticating...\n";
    
    # Use the robust send_message function for authentication
    if (!send_message($socket, \$password)) {
        warn "Failed to send authentication packet.\n";
        $socket->close();
        return undef;
    }
    
    print "Authentication successful.\n";
    return $socket;
}

# --- Robust Message Sending Function ---
sub send_message {
    my ($socket, $data_ref) = @_;

    my $payload = nfreeze($data_ref);
    my $message = pack('N', length($payload)) . $payload;
    my $total_len = length($message);
    my $sent_len = 0;

    while ($sent_len < $total_len) {
        my $bytes_sent = $socket->syswrite($message, $total_len - $sent_len, $sent_len);
        
        if (!defined $bytes_sent) {
            warn "syswrite error: $!";
            return 0; # Failure
        }
        
        $sent_len += $bytes_sent;
    }
    return 1; # Success
}

#======================================================================
# Main Client Logic (MODIFIED TO USE ROBUST NETWORKING)
#======================================================================
my $socket;
while (1) {
    unless (defined $socket && $socket->connected) {
        while (1) {
            print "Attempting to connect to server at $server_ip...\n";
            $socket = connect_to_server();

            if (defined $socket) {
                print "Connection to server successful.\n";
                $current_retry_delay = $initial_retry_delay;
                last;
            }

            print "Connection failed. Retrying in $current_retry_delay seconds...\n";
            sleep $current_retry_delay;
            
            $current_retry_delay *= 2;
            if ($current_retry_delay > $max_retry_delay) {
                $current_retry_delay = $max_retry_delay;
            }
        }
    }

    # Data gathering now returns a reference
    my $connections_ref = get_connections_with_progs();
    # Dereference the ref for counting
    my $count_with_progs = grep { $_->{programname} ne '' } @$connections_ref;
    print "Found " . scalar(@$connections_ref) . " established connections. ($count_with_progs with program names)\n";

    # Use the new robust send_message function
    unless (send_message($socket, $connections_ref)) {
        print "Failed to send data. Server has disconnected.\n";
        $socket->close();
        $socket = undef; # This will trigger the reconnection logic
    }
    
    sleep 1;
}
