summaryrefslogtreecommitdiffstats
path: root/emergencyd.pl
diff options
context:
space:
mode:
Diffstat (limited to 'emergencyd.pl')
-rwxr-xr-xemergencyd.pl59
1 files changed, 57 insertions, 2 deletions
diff --git a/emergencyd.pl b/emergencyd.pl
index e9bd8f3..6920e95 100755
--- a/emergencyd.pl
+++ b/emergencyd.pl
@@ -55,6 +55,12 @@
#
# * SYRQ: this command takes a mandatory argument; the server writes
# this argument to /proc/sysrq-trigger and responds with DONE.
+#
+# * SHEL: this command takes a mandatory numeric argument, which is
+# interpreted as a TCP port number on the computer at the source of
+# the datagram; the server tries to connect to that port and spawn a
+# shell attached to it (beware, though: this shell will have no
+# terminal).
# *** The emergency daemon itself ***
#
@@ -78,6 +84,9 @@ use Digest::SHA qw(hmac_sha256_hex);
use Socket;
use Socket6;
use POSIX ();
+use Fcntl;
+use Errno qw(EINPROGRESS);
+use IO::Poll;
use Sys::Syslog qw(:standard :macros);
use Getopt::Std;
@@ -121,7 +130,7 @@ if ( defined(*IPV6_V6ONLY{CODE}) ) {
setsockopt $socket, IPPROTO_IPV6, IPV6_V6ONLY, 0
or die "Can't set IPV6_V6ONLY option to 0: $!";
}
-bind $socket, sockaddr_in6($port, in6addr_any) or die "Can't bind socket: $!";
+bind $socket, pack_sockaddr_in6($port, in6addr_any) or die "Can't bind socket: $!";
openlog(SYSLOG_IDENT, "ndelay,pid", LOG_DAEMON);
@@ -140,6 +149,7 @@ if ( $opts{f} ) {
close STDERR;
POSIX::setsid;
}
+$SIG{CLD} = "IGNORE"; # F*ck archaic unices where this produces zombies.
sub curtime {
my $fiddle = shift // 0;
@@ -228,6 +238,50 @@ sub cmd_syrq {
return "DONE\n";
}
+sub cmd_shel {
+ my $port = shift or die [ "!BAD", "Missing argument to SHEL" ];
+ die [ "!BAD", "Invalid port number (SHEL command) $port" ]
+ unless $port =~ /^\d+$/;
+ my $proto = getprotobyname("tcp") or die "Can't resolve tcp protocol: $!";
+ socket my $socket, PF_INET6, SOCK_STREAM, $proto
+ or die "Can't create socket: $!";
+ my $flags;
+ $flags = fcntl ($socket, F_GETFL, 0);
+ fcntl ($socket, F_SETFL, $flags|O_NONBLOCK) if defined($flags);
+ my $sender = shift;
+ my (undef, undef, $saddr, $scopeid) = unpack_sockaddr_in6_all $sender;
+ my $target = pack_sockaddr_in6_all($port, 0, $saddr, $scopeid);
+ unless ( connect $socket, $target) {
+ # This is so GROSS! :-(((
+ die "Can't connect to port $port: $!" unless $! == EINPROGRESS;
+ my $poll = new IO::Poll;
+ $poll->mask($socket, POLLOUT);
+ my $ev = $poll->poll(15);
+ die "Failed to poll: $!" unless defined($ev);
+ die "Timed out connecting to port $port" unless $ev == 1;
+ my $err = getsockopt $socket, SOL_SOCKET, SO_ERROR;
+ die "Failed to retrieve socket error: $!" unless defined($err);
+ $err = unpack("I",$err);
+ if ( $err ) {
+ $! = $err;
+ die "Can't connect to port $port: $!";
+ }
+ }
+ my $v = fork;
+ die "Can't fork: $!" unless defined($v);
+ if ( $v ) {
+ close $socket;
+ return [ "DONE", $v ];
+ } else {
+ fcntl ($socket, F_SETFL, $flags&~O_NONBLOCK) if defined($flags);
+ open STDIN, "<&", $socket;
+ open STDOUT, ">&", $socket;
+ open STDERR, ">&", $socket;
+ exec "/bin/sh" or print STDERR "exec failed: $!";
+ exit -1;
+ }
+}
+
my %dispatch = (
"PING" => \&cmd_ping,
"DATE" => \&cmd_date,
@@ -237,6 +291,7 @@ my %dispatch = (
"RKEY" => \&cmd_rkey,
"LOGM" => \&cmd_logm,
"SYRQ" => \&cmd_syrq,
+ "SHEL" => \&cmd_shel,
);
PACKET:
@@ -261,7 +316,7 @@ while ( $running ) {
}
my $sub = $dispatch{$command};
die "!UNK\n" unless defined($sub);
- $resp = &{$sub}($arg);
+ $resp = &{$sub}($arg, $sender);
};
if ( $@ ) {
$resp = $@;