From: mivirl <> Date: Sun, 2 Jun 2024 17:55:26 +0000 (-0500) Subject: server,client: Implement ssl/tls encryption X-Git-Url: http://mivirl.dev/git/?a=commitdiff_plain;h=HEAD;p=remote-logging.git server,client: Implement ssl/tls encryption The server and client now use ssl to communicate, with certificates generated by `cert-server.pl`. Clients connect to the cert-server to request a certificate using a password. After receiving a certificate they can connect to the server and start sending logs. Rewrote the server in perl to facilitate use of encryption. Removed use of actually portable perl due to the prebuilt binary not including IO::Socket::SSL and Net::SSLeay. Rebuilding the perl binary would be required to use encryption, so the system perl will be used instead. --- diff --git a/README.md b/README.md index e7a90af..6288569 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ To deploy on the server, you'll need to transfer the `build/_output/server` directory to the remote system, then `cd` to that directory and run: ```sh -./busybox sh start_server.sh +sh start_server.sh ``` By default the server uses the ports 46515-46550. This can be changed in @@ -52,7 +52,7 @@ To deploy on the client, you'll need to transfer the `build/_output/client` directory to the remote system, then `cd` to that directory and run: ```sh -./perl.com client.pl +perl client.pl ``` ## Monitoring diff --git a/build.sh b/build.sh index b0a25b7..222beaa 100644 --- a/build.sh +++ b/build.sh @@ -28,10 +28,9 @@ if ! sha256sum -c ../checksums >/dev/null 2>&1; then wget https://github.com/inotify-tools/inotify-tools/archive/refs/tags/4.23.9.0.tar.gz mv 4.23.9.0.tar.gz inotify-tools-4.23.9.0.tar.gz wget https://github.com/tstack/lnav/releases/download/v0.11.2/lnav-0.11.2-x86_64-linux-musl.zip - wget https://github.com/G4Vi/Perl-Dist-APPerl/releases/download/v0.3.0/perl.com fi -sha256sum -c ../checksums +sha256sum -c ../checksums || exit 1 # Compile busybox # Config file for busybox uses default settings except: @@ -65,10 +64,10 @@ cp busybox-1.36.1/busybox _output/server/ cp busybox-1.36.1/busybox _output/client/ cp inotify-tools-4.23.9.0/src/inotifywait _output/client/ cp pspy64 _output/client/ -cp perl.com _output/client/ cp lnav-0.11.2/lnav _output/server/ -chmod u+x _output/server/* -chmod u+x _output/client/* cp ../src/*server* _output/server/ cp ../src/*client* _output/client/ + +chmod u+x _output/server/* +chmod u+x _output/client/* diff --git a/checksums b/checksums index 05bc84d..331886d 100644 --- a/checksums +++ b/checksums @@ -1,5 +1,4 @@ b8cc24c9574d809e7279c3be349795c5d5ceb6fdf19ca709f80cde50e47de314 busybox-1.36.1.tar.bz2 1dfa33f80b6797ce2f6c01f454fd486d30be4dca1b0c5c2ea9ba3c30a5c39855 inotify-tools-4.23.9.0.tar.gz 6515598ca4985ec42daeafbae7eb5c7c6d7e94a11f88666d157b3d363aa391ff lnav-0.11.2-x86_64-linux-musl.zip -b5997a2683ea993aaa3a0bac08c2172afd94785b22219fddc6876f3740364d59 perl.com c93f29a5cc1347bdb90e14a12424e6469c8cfea9a20b800bc249755f0043a3bb pspy64 diff --git a/src/cert-server.pl b/src/cert-server.pl new file mode 100644 index 0000000..36af4a4 --- /dev/null +++ b/src/cert-server.pl @@ -0,0 +1,229 @@ +#!/usr/bin/perl + +# Clients connect to this server over ssl to request a certificate +# The provided certificate is signed by a CA ca cert to allow using +# client certificates for authentication + +use strict; +use warnings; +use IO::Socket::INET; +use IO::Socket::SSL; +use Digest::SHA "sha256"; +use POSIX "strftime"; + +umask 0077; + +$ENV{PATH} = '/bin:/usr/bin'; + +my %hostnames; + +my ($clientname, $clientip); +open(my $logfh, '>>', '_cert_provider_log.txt'); +sub log_client { + my ($message) = @_; + $clientname = 'unknown' if ! defined $clientname; + $clientip = 'unknown' if ! defined $clientip; + $message =~ s/"/\\"/g; + my $timestamp = strftime "%Y-%m-%d %H:%M:%S", localtime; + my $logmsg = "ts=\"$timestamp\" client=\"$clientname\" ip=\"$clientip\" message=\"$message\"\n"; + print $logmsg; + print $logfh $logmsg; +} + +sub log_client_die { + my ($message) = @_; + log_client $message; + exit 1; +} + +sub find_existing_names { + opendir(my $dir, '.'); + while (readdir($dir)) { + my ($name) = $_ =~ /client-([a-zA-Z0-9-]+)-cert.pem/; + $hostnames{"$name.c.logs"}++ if defined $name; + } +} + +sub generate_ca_cert { + print "Generating CA private key and certificate...\n"; + my $pid = fork(); + if (!$pid) { + open STDOUT, '>', undef; + open STDERR, '>', undef; + exec 'openssl', 'req', '-x509', '-new', '-noenc', '-newkey', 'rsa:4096', + '-sha256', '-days', '3650', '-subj', + '/C=AU/ST=none/L=none/O=none/OU=none/CN=ca.logs', '-keyout', + 'ca-key.pem', '-out', 'ca-cert.pem' || die "Failed to exec openssl"; + } + waitpid ($pid, 0); + + chmod 0644, 'ca-cert.pem'; + + return ('ca-key.pem', 'ca-cert.pem'); +} + +sub generate_client_cert { + my ($cakey, $cacert, $name, $postfix) = @_; + + $clientname = "${name}${postfix}.logs"; + my $fn; + if ($postfix ne '') { + $fn = "client-${name}"; + } else { + $fn = "${name}"; + } + + if (exists $hostnames{$clientname}) { + return (undef, undef); + } + $hostnames{$clientname}++; + + log_client "Generating private key and certificate for ${clientname}"; + my $pid = fork(); + if (!$pid) { + # Don't show output + open STDOUT, '>', undef; + open STDERR, '>', undef; + exec 'openssl', 'req', '-x509', '-noenc', '-newkey', 'rsa:4096', '-sha256', + '-days', '3650', '-subj', "/C=AU/ST=none/L=none/O=none/OU=none/CN=${clientname}", + '-CA', $cacert, '-CAkey', $cakey, '-keyout', "${fn}-key.pem", + '-out', "${fn}-cert.pem" || die "Failed to exec openssl"; + } + waitpid($pid, 0); + + return ("${fn}-key.pem", "${fn}-cert.pem", $clientname), +}; + +sub fingerprint_cert { + my ($cert) = @_; + print "$cert : "; + my $pid = fork(); + if (!$pid) { + exec 'openssl', 'x509', '-in', $cert, '-noout', '-fingerprint', '-sha256' + || die "Failed to exec openssl"; + } + waitpid ($pid, 0); + + return; +} + +sub handle_client { + my ($srvkey, $srvcert, $cakey, $cacert, $pw, $client) = @_; + my $pid = fork(); + return if $pid; + + $clientip = $client->peerhost; + log_client "Client connected"; + + # Upgrade to SSL after fork to not slow down accepting connections + IO::Socket::SSL->start_SSL( + $client, + SSL_server => 1, + SSL_cert_file => 'server-cert.pem', + SSL_key_file => 'server-key.pem', + ) || log_client_die "Failed ssl handshake: $SSL_ERROR"; + + my $pw_attempt = ""; + my $char = ""; + while ($char ne "\n") { + $client->sysread($char, 1) || log_client_die "Client disconnected while providing password"; + $pw_attempt = "$pw_attempt$char"; + } + chomp $pw_attempt; + + my $sha_pw = sha256($pw); + my $sha_pw_attempt = sha256($pw_attempt); + + if ($sha_pw ne $sha_pw_attempt) { + log_client_die "Authentication failure"; + } + log_client "Authentication success"; + + # Read client name (will be used for the certificate) + my $name = ""; + $char = ""; + while ($char ne "\n") { + $client->sysread($char, 1) || log_client_die "Client disconnected while providing name"; + if ($char =~ /[a-zA-Z0-9-]/) { + $name = "$name$char"; + } + } + chomp $name; + + if ($name eq '') { + log_client_die "Client provided blank name, no certificate generated"; + } + + my ($clientkey, $clientcert, $hn) = generate_client_cert($cakey, $cacert, $name, '.c'); + if (!defined $clientkey || !defined $clientcert) { + log_client_die "Client [$clientip] requested existing certificate, no certificate generated"; + } + + # Send certificate and key to client + log_client "Sending client private key and certificate for ${hn}"; + open(my $ckfh, '<', $clientkey); + while (<$ckfh>) { + $client->print($_); + } + open(my $ccfh, '<', $clientcert); + while (<$ccfh>) { + $client->print($_); + } + close($ckfh); + close($ccfh); + + # Deleting the client's private key since the server doesn't need it + unlink($clientkey); + exit; +} + +sub help { + print STDERR "Usage: $0 port [ca_key ca_certificate [server_key server_certificate]]\n"; + exit 1; +} + +sub main { + my $argcount = scalar @ARGV; + if (($argcount != 1 && $argcount != 3 && $argcount != 5) || $ARGV[0] eq '-h') { + help; + } + my ($cakey, $cacert, $srvkey, $srvcert); + if (scalar @ARGV == 1) { + ($cakey, $cacert) = generate_ca_cert(); + ($srvkey, $srvcert) = generate_client_cert($cakey, $cacert, 'server', ''); + } elsif (scalar @ARGV == 1) { + $cakey = $ARGV[1]; + $cacert = $ARGV[2]; + ($srvkey, $srvcert) = generate_client_cert($cakey, $cacert, 'server', ''); + } else { + $cakey = $ARGV[1]; + $cacert = $ARGV[2]; + $srvkey = $ARGV[3]; + $srvcert = $ARGV[4]; + } + + print "\n"; + fingerprint_cert($srvcert); + + print "\nCreating password. Clients will need to provide this to request a certificate\n"; + print "Set password: "; + my $password = ; + chomp $password; + + find_existing_names; + + my $srv = IO::Socket::INET->new( + LocalAddr => '0.0.0.0', + LocalPort => $ARGV[0], + Listen => 1024, + ) || die "Failed to listen: $!"; + + $clientname = undef; + $clientip = undef; + while (1) { + my $client = $srv->accept; + handle_client($srvkey, $srvcert, $cakey, $cacert, $password, $client); + } +} + +main; diff --git a/src/client.pl b/src/client.pl index 27b4cac..8b165ae 100644 --- a/src/client.pl +++ b/src/client.pl @@ -1,18 +1,31 @@ #!/usr/bin/env perl + +# Client connects to certificate provider to request a client certificate +# Sends logs and changed files to the server over ssl using the provided +# client certificate to authenticate + use strict; use warnings; use IO::Socket::INET; +use IO::Socket::SSL; use POSIX "strftime"; +umask 0077; + # Change this to the IP of the server my $server_ip = "127.0.0.1"; my $server_port = 46515; +my $cert_provider_ip = $server_ip; +my $cert_provider_port = 46516; + +my $server_fingerprint; # Trust on first use if not defined. Will be saved to server_fingerprint.txt # See what's sent and monitored at the bottom of the script # Handle SIGINT my @child_processes; sub stop_child_processes { + return if scalar(@child_processes) == 0; kill 'INT', @child_processes; } $SIG{'INT'} = 'stop_child_processes'; @@ -20,8 +33,14 @@ $SIG{'INT'} = 'stop_child_processes'; # Register client with server my ($hostname) = ns_system('./busybox', 'hostname'); -my ($clientName, $clientKey) = register($hostname); +get_server_fingerprint(); +if (! -f 'client-cert.pem') { + printf "Enter password for certificate: "; + my $password = ; + chomp $password; + request_certificate($cert_provider_ip, $cert_provider_port, $password, $hostname); +} # ------------------------------------------------------------------------------ @@ -69,13 +88,56 @@ sub get_files_recursively { return @files; } +sub request_certificate { + my ($cert_provider_ip, $cert_provider_port, $password, $name) = @_; + return if (-f 'client-cert.pem'); + + my $socket = IO::Socket::SSL->new( + PeerAddr => $cert_provider_ip, + PeerPort => $cert_provider_port, + SSL_fingerprint => $server_fingerprint, + ) || die "Failed to connect to certificate provider: $SSL_ERROR"; + $socket->write("$password\n$name\n"); + open(my $fh, '>', 'client-cert.pem'); + my $buffer = ""; + while (1) { + $socket->sysread($buffer, 1024) || last; + print $fh $buffer; + } + $socket->shutdown(SHUT_RD); + close $fh; +} + +sub get_server_fingerprint { + if (-e 'server_fingerprint.txt') { + open(my $fh, '<', 'server_fingerprint.txt'); + $server_fingerprint = <$fh>; + close $fh; + return; + } + my $socket = IO::Socket::SSL->new( + PeerAddr => $server_ip, + PeerPort => $server_port, + SSL_verify_mode => SSL_VERIFY_NONE + ); + $server_fingerprint = $socket->get_fingerprint('sha256'); + print "Trusting server fingerprint: ", $server_fingerprint, "\n"; + open(my $fh, '>', 'server_fingerprint.txt'); + print $fh $server_fingerprint; + close $fh; +} + sub connect_to_server { - my ($port) = @_; - $port = $server_port if (!defined $port); - my $socket = IO::Socket::INET->new( + + get_server_fingerprint if !defined $server_fingerprint; + + my $socket = IO::Socket::SSL->new( PeerAddr => $server_ip, - PeerPort => $port, - Proto => 'tcp' + PeerPort => $server_port, + SSL_cert_file => 'client-cert.pem', + SSL_key_file => 'client-cert.pem', + SSL_fingerprint => $server_fingerprint, + SSL_fast_shutdown => 0, ); my $wait = 1; while ((! $socket) && $wait < 16) { @@ -83,67 +145,27 @@ sub connect_to_server { $wait *= 2; } if ($wait >= 16) { - die "Failed to connect to $server_ip:$port : $!"; + die "Failed to connect to $server_ip:$server_port : $!"; } $socket->autoflush(1); return $socket; } -sub register { - my ($hostname) = @_; - my $socket = connect_to_server; - $socket->send("register\n"); - $socket->send("$hostname\n"); - - # Wait for connection to be established, try up to 5 times - my $response; - foreach (1..5) { - sleep $_; - $socket->recv($response, 80); - last if ($response =~ m/Key/); - } - (my $clientName, my $clientKey) = $response =~ m/Name: (client_[a-zA-Z]+_\d+)\nKey: (\d+)\n/; - - if (defined $clientName && defined $clientKey) { - print_log "Register: success"; - } else { - print_log "Register: failure"; +sub send_info { + my $pid = fork; + if ($pid) { + push @child_processes, $pid; + return; } - $socket->close(); - return ($clientName, $clientKey); -} -sub login { my $socket = connect_to_server; - - # Wait for connection to be established, try up to 5 times - my $response; - $socket->send("login\n"); - $socket->send("$clientName\n"); - $socket->send("$clientKey\n"); - foreach (1..5) { - sleep $_; - $socket->recv($response, 80); - last if ($response =~ m/Auth/); - } - - if ($response =~ m/Auth success/) { - print_log "Login: success"; - } elsif ($response =~ m/Auth failure/) { - print_log "Login: failure"; - } - return $socket; -} - -sub send_info { - my $socket = login($clientName, $clientKey); - my $info = join "", ns_system('./busybox', 'sh', '-c', 'hostname; date; uname -a; cat /etc/os-release; lspci; lsusb; ifconfig'); - $socket->send("info\n"); - $socket->send($info); - $socket->send("⟃---EOF---⟄\n"); - return; + $socket->write("info\n...\n"); + $socket->write($info); + + $socket->shutdown(SHUT_WR); + exit; } sub send_log { @@ -157,23 +179,20 @@ sub send_log { # Check that log exists and is readable by current user exit if (! -e $file || ! -r _); - my $socket = login($clientName, $clientKey); - - # Replace / character with similar-looking character that is valid - # for filenames. Used to show full path to file - my $fileName = $file =~ s/\////gr; + my $socket = connect_to_server; # Upload tailed log continuously - $socket->send("log\n"); - $socket->send("$fileName\n"); + $socket->write("logs\n"); + $socket->write("$file\n"); print_log "Log: Uploading $file"; my $tailLog = ns_systemFH('./busybox', './busybox', 'tail', '-F', "$file"); while (<$tailLog>) { - $socket->send($_); + $socket->write($_); } print_log "Log: Closing $file"; close($tailLog); - $socket->send("⟃---EOF---⟄\n"); + + $socket->shutdown(SHUT_WR); exit; } @@ -184,19 +203,8 @@ sub send_processes { return; } - my $socket = login($clientName, $clientKey); - - # Upload process log continuously - $socket->send("processes\n"); - print_log "Processes: Started"; - my $commandLog = ns_systemFH('./pspy64', '-f'); - while (<$commandLog>) { - $socket->send($_); - } - print_log "Processes: Finished"; - close($commandLog); - $socket->send("⟃---EOF---⟄\n"); - exit; + my $socket = connect_to_server; + ... } sub send_command_output { @@ -207,20 +215,21 @@ sub send_command_output { return; } - my $socket = login($clientName, $clientKey); + my $socket = connect_to_server; # Upload command output continously with provided filename my ($fileName) = $name; - $socket->send("command\n"); - $socket->send("$fileName\n"); + $socket->write("cmds\n"); + $socket->write("$fileName\n"); print_log "Command: Started @command"; my $commandLog = ns_systemFH(@command); while (<$commandLog>) { - $socket->send($_); + $socket->write($_); } print_log "Command: Completed @command"; close($commandLog); - $socket->send("⟃---EOF---⟄\n"); + + $socket->shutdown(SHUT_WR); exit; } @@ -235,56 +244,24 @@ sub send_file { # Check that log exists and is readable by current user exit if (! -e $file || ! -r _); - # Replace / character with similar-looking character that is valid - # for filenames. Used to show full path to file - my $fileName = $file =~ s/\////gr; - my ($fileHash) = ns_system('./busybox', 'md5sum', "$file"); - chomp $fileName; chomp $fileHash; - ($fileHash) = $fileHash =~ m/([0-9a-f]+)/; - - my $socket = login($clientName, $clientKey); + my $socket = connect_to_server; # Send filename and hash to server, wait for a response with the port to # upload the file to - $socket->send("file\n"); - $socket->send("$fileName\n"); - $socket->send("$fileHash\n"); - $socket->recv(my $ignored, 128); - my $port = undef; - my $r; - my $sleeptime = 2; - my $attemptcount = 0; - while (! defined $port) { - sleep $sleeptime; - $sleeptime *= 2; - $sleeptime = 10 if ($sleeptime > 10); - $socket->recv($r, 128); - ($port) = $r =~ m/(\d+)/; - $attemptcount += 1; - if ($attemptcount >= 5) { - print_log "File: upload failure ($file)"; - exit; - } - } + $socket->write("file\n"); + $socket->write("$file\n"); - # Send file once - print_log "File: upload port is $port ($file)"; + ## Send file once + print_log "File: Uploading $file"; open(my $fileFH, '<', "$file") || die "Failed to open $file"; - my $fileSocket = connect_to_server $port; while (<$fileFH>) { - $fileSocket->send($_); + $socket->write($_); } + print_log "File: Closing $file"; close($fileFH); - close($fileSocket); + $socket->flush; - # Server checks that the uploaded hash matches and informs on error - # No retry is attempted on failed upload - $socket->recv(my $response, 128); - if ($response =~ m/Transfer success/) { - print_log "File: upload success ($file)"; - } else { - print_log "File: upload failure ($file)"; - } + $socket->shutdown(SHUT_WR); exit; } diff --git a/src/server.pl b/src/server.pl new file mode 100644 index 0000000..e9454c6 --- /dev/null +++ b/src/server.pl @@ -0,0 +1,221 @@ +#!/usr/bin/perl + +# Client connects to this server over ssl and sends logs, files, and command +# output which are stored under the following directories according to their +# type: +# clients +# └── development.c.logs +# ├── _clientinfo.txt +# ├── _serverlog +# ├── commands +# ├── files +# └── logs + +use strict; +use warnings; +use IO::Socket::INET; +use IO::Socket::SSL; +use POSIX "strftime"; + +my $perl_binary = '/usr/bin/perl'; + +umask 0077; + +binmode(STDOUT, ":encoding(UTF-8)"); + +my ($clientname, $clientip); +my $clientlogfh; +sub log_client { + my ($message) = @_; + $clientname = 'unknown' if ! defined $clientname; + $clientip = 'unknown' if ! defined $clientip; + $message =~ s/"/\\"/g; + my $timestamp = strftime "%Y-%m-%d %H:%M:%S", localtime; + my $logmsg = "ts=\"$timestamp\" client=\"$clientname\" ip=\"$clientip\" message=\"$message\"\n"; + print $logmsg; + print $clientlogfh $logmsg if defined $clientlogfh; +} + +sub log_client_die { + my ($message) = @_; + log_client $message; + exit 1; +} + +sub write_to_file { + my ($client, $filetype, $filename) = @_; + + # Add suffix if file already exists + if ($filetype eq 'file' || $filetype eq 'info') { + if (-e $filename) { + my $suffix = 1; + while (-e "${filename}_${suffix}") { + ++$suffix; + } + $filename = "${filename}_${suffix}"; + } + } + + log_client "Started writing to $filename"; + + open(my $fh, '>>', $filename); + select $fh; + $| = 1; # flush stream automatically + select STDOUT; + + my $char = ""; + while (1) { + $client->sysread($char, 1024) || last; + print $fh $char; + } + log_client "Finished writing to $filename"; + $fh->flush; + close($fh); + $client->shutdown(SHUT_RD); + exit; +} + +sub handle_client { + my ($client) = @_; + if (fork()) { + $client->close; + return; + } + + # Upgrade to SSL after fork to not slow down accepting connections + IO::Socket::SSL->start_SSL( + $client, + SSL_server => 1, + SSL_cert_file => 'server-cert.pem', + SSL_key_file => 'server-key.pem', + SSL_verify_mode => SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + SSL_ca_file => 'ca-cert.pem', + SSL_fast_shutdown => 0, + ) || log_client_die "Failed ssl handshake: $SSL_ERROR"; + + $clientname = $client->peer_certificate('commonName'); + $clientip = $client->peerhost; + + my $dirname = "clients/$clientname"; + + # Create skeleton directories if not present + if (! -d $dirname) { + mkdir($dirname, 0700); + mkdir("$dirname/clients", 0700); + mkdir("$dirname/files", 0700); + mkdir("$dirname/commands", 0700); + } + open($clientlogfh, '>>:encoding(UTF-8)', "$dirname/_serverlog"); + select $clientlogfh; + $| = 1; # flush stream automatically + select STDOUT; + + log_client 'connected'; + + # Read source of data, choose filename prefix accordingly + my $filename; + my $filetype; + my $buffer = ""; + $client->read($buffer, 5); + $buffer =~ s/\n//g; + + if ($buffer eq 'info') { + $filetype = 'info'; + $filename = "$dirname/_clientinfo.txt"; + } elsif ($buffer eq 'logs') { + $filetype = 'logs'; + $filename = "$dirname/logs/L__"; + } elsif ($buffer eq 'cmds') { + $filetype = 'cmds'; + $filename = "$dirname/commands/C__"; + } elsif ($buffer eq 'file') { + $filetype = 'file'; + $filename = "$dirname/files/F__"; + } else { + log_client "provided invalid filetype ($buffer), ignoring"; + exit; + } + + # Read name for logfile, strip illegal characters for windows filenames + my $name = ""; + my $char = ""; + while ($char ne "\n") { + $client->read($char, 1) || log_client_die 'disconnected'; + if ($char =~ /[^<>:"\/\\|?*]/) { + $name = "$name$char"; + } else { + if ($char ne '/') { + # Use unicode replacement character + $name = "$name\N{REPLACEMENT CHARACTER}"; + } else { + # Use unicode lookalike for / + $name = "$name\N{BIG SOLIDUS}"; + } + } + } + chomp $name; + + if ($name eq '') { + log_client 'provided blank filename, ignoring'; + exit; + } + + if ($filetype ne 'info') { + $filename = "$filename$name"; + } + + write_to_file($client, $filetype, $filename); + + print $buffer; +} + +sub help { + print STDERR "Usage: $0 port [-c port [-s]]\n"; + print STDERR "\t-c port\tStart cert-server on port\n"; + print STDERR "\t-s\tUse saved certificates for cert-server if they exist\n"; + exit 1; +} + +sub main { + my $argcount = scalar @ARGV; + if (($argcount != 1 && $argcount != 3 && $argcount != 4) + || $ARGV[0] eq '-h' + || ($argcount >= 3 && $ARGV[1] ne '-c') + || ($argcount == 4 && $ARGV[3] ne '-s')) { + help; + } + + if ($argcount >= 3) { + my @cert_server_args = ('../cert-server.pl', $ARGV[2]); + if ($argcount == 4 && (-f 'ca-key.pem' && -f 'ca-cert.pem')) { + push @cert_server_args, 'ca-key.pem', 'ca-cert.pem'; + if (-f 'server-key.pem' && -f 'server-cert.pem') { + push @cert_server_args, 'server-key.pem', 'server-cert.pem'; + } + } + if (!fork()) { + exec $perl_binary, @cert_server_args; + } + + while (!(-f 'server-key.pem' && -f 'server-cert.pem')) { + sleep 1; + } + } + + mkdir('clients', 0700); + + my $srv = IO::Socket::INET->new( + LocalAddr => '0.0.0.0', + LocalPort => $ARGV[0], + Listen => 16384, + ) || die "Failed to listen: $!"; + + print "Server is listening for clients\n"; + while (1) { + my $client = $srv->accept; + handle_client($client); + $client->close(); + } +} + +main; diff --git a/src/server.sh b/src/server.sh deleted file mode 100644 index 7fbd00e..0000000 --- a/src/server.sh +++ /dev/null @@ -1,188 +0,0 @@ -#!./busybox sh - -if [ $# -ne 2 ]; then - echo "Usage: $0 file_port_start file_port_num" - exit 1 -fi - -FILE_PORT_START=$1 -FILE_PORT_NUM=$2 - -SRVDIR=$(pwd) - -verify_input() { - case "$@" in - "") echo "Input error" - exit - ;; - *"/"*) echo "Input error" - exit - ;; - esac -} - -write_to_file() { - if [ -n "$1" ]; then - touch "$1" - while IFS= read -r INPUT; do - if [ "$INPUT" != "⟃---EOF---⟄" ]; then - printf "%s" "$INPUT" >> "$1" - else - break - fi - done - fi -} - -print_status() { - echo "Acknowledged" - "$@" - echo "Done." -} - - -# ----------------------------------------------------------------------------- -# Identify/authenticate client - -read -r COMMAND -echo "$COMMAND" >&2 - -# Existing client -if [ "$COMMAND" = "login" ]; then - echo -n "Client name: " - read -r TMPNAME - echo -n "Client key: " - read -r TMPKEY - - verify_input "$TMPNAME" - - # Check if provided credentials are accurate - if [ -e "$SRVDIR/clients/$TMPNAME" ]; then - KEY=$(cat "$SRVDIR/clients/$TMPNAME/_auth-key") - if [ "$TMPKEY" = "$KEY" ]; then - CLIENTNAME="$TMPNAME" - CLIENTDIR="$SRVDIR/clients/$TMPNAME" - echo "Auth success" - else - echo "Auth failure"; exit - fi - else - echo "Auth failure"; exit - fi - unset TMPNAME - unset TMPKEY - -# New client -elif [ "$COMMAND" = "register" ]; then - echo -n "Hostname: " - read -r CLIENTHOSTNAME - # Use shortened hostname and add random suffix to prevent collisions - CLIENTHOSTNAME=$(echo "$CLIENTHOSTNAME" | sed 's/[^a-zA-Z]//g' | cut -c -16) - CLIENTNAME="client_${CLIENTHOSTNAME}_$(( RANDOM * 2**30 + RANDOM * 2**15 + RANDOM ))" - CLIENTDIR="$SRVDIR/clients/$CLIENTNAME" - CLIENTKEY=$(( RANDOM * 2**30 + RANDOM * 2**15 + RANDOM )) - mkdir -p "$CLIENTDIR" - echo "$CLIENTKEY" > "$CLIENTDIR/_auth-key" - echo "Name: $CLIENTNAME" - echo "Key: $CLIENTKEY" - unset CLIENTKEY -else - echo "Command not found." - exit -fi - -cd "$CLIENTDIR" - - -# ----------------------------------------------------------------------------- -# Client communication - -while read -r COMMAND; do - echo "$CLIENTNAME: $COMMAND" >&2 - - if [ "$COMMAND" = "info" ]; then - print_status write_to_file _info.txt - - - elif [ "$COMMAND" = "processes" ]; then - print_status write_to_file _processes.log - - - elif [ "$COMMAND" = "file" ]; then - echo -n "Filename: " - read -r TMPFILENAME - verify_input "F__${TMPFILENAME}" - echo -n "Hash: " - read -r TMPHASH - - FILENAME="F__${TMPFILENAME}" - - # Add suffix if file already exists - if [ -e "$FILENAME" ]; then - SUFFIX=1 - while [ -e "${FILENAME}_${SUFFIX}" ]; do - SUFFIX=$(( SUFFIX + 1 )) - done - FILENAME="${FILENAME}_${SUFFIX}" - fi - - # Grab an open port and listen for file. Use netcat since sh can't - # handle binary data well - ATTEMPTCOUNT=0 - SLEEPTIME=0 - while true; do - sleep $(( RANDOM % (SLEEPTIME + 5) + 1 )) - PORT=$(( (RANDOM * 2 + RANDOM % 2) % FILE_PORT_NUM + FILE_PORT_START )) - nc -w 7 -l -p "$PORT" > "$FILENAME" 2>/dev/null & - NC_PID=$! - # Wait for nc to fail. There seems to be a bug with busybox sh where - # using the builtin sleep when following the backgrounded nc - # doesn't sleep, so calling the binary again - ../../../busybox sleep 1 - if ps -o pid | grep -q -e $NC_PID ; then - break - fi - ATTEMPTCOUNT=$(( ATTEMPTCOUNT + 1 )) - SLEEPTIME=$(( SLEEPTIME + 2 )) - if [ $ATTEMPTCOUNT -gt 5 ]; then - echo "$CLIENTNAME: ($FILENAME) Failed to bind open port for file transfer, giving up" >&2 - echo "[${FILERECVTIME}] File transfer failed: ${FILENAME}" >> _files.log - exit - fi - done - echo "$PORT" - wait $NC_PID - - FILERECVTIME="$(date '+%Y-%m-%d %H:%M:%S')" - echo "[${FILERECVTIME}] File received: ${FILENAME}" >> _files.log - - HASH=$(md5sum "$FILENAME" | cut -d' ' -f1) - if [ "$HASH" != "$TMPHASH" ]; then - echo "Checksum error" - else - echo "Transfer success" - fi - - unset TMPFILENAME - - - elif [ "$COMMAND" = "log" ]; then - echo -n "Filename: " - read -r TMPFILENAME - verify_input "L__${TMPFILENAME}" - print_status write_to_file "L__${TMPFILENAME}" - unset TMPFILENAME - - - elif [ "$COMMAND" = "command" ]; then - echo -n "Filename: " - read -r TMPFILENAME - verify_input "C__${TMPFILENAME}" - print_status write_to_file "C__${TMPFILENAME}" - unset TMPFILENAME - - - else - echo "Command not found." - fi -done diff --git a/src/start-server.sh b/src/start-server.sh index b150210..2745eb3 100644 --- a/src/start-server.sh +++ b/src/start-server.sh @@ -1,13 +1,12 @@ #!./busybox sh SRVPORT=46515 -FILE_PORT_START=46516 -FILE_PORT_NUM=45 +CRTPORT=46516 CWD=$(pwd) # ----------------------------------------------------------------------------- # Start listening for clients -mkdir -p "$CWD/srv/clients" +mkdir -p "$CWD/srv" cd "$CWD/srv" -tcpsvd -c 4096 0.0.0.0 "$SRVPORT" ../busybox sh ../server.sh "$FILE_PORT_START" "$FILE_PORT_NUM" +perl ../server.pl "$SRVPORT" -c "$CRTPORT" -s