#!/usr/bin/env perl
# $Id: muxcat.pl,v 1.7 2012/08/12 21:33:19 ksb Exp $
# Service to receive data from a TCPMUX provider		(ksb/pv)
# Reach out to a tcpmux service on a (maybe) remote host.
use lib  '/usr/local/lib/sac/perl'.join('.', unpack('c*', $^V)),
	'/usr/local/lib/sac';
use Getopt::Std;
use IO::Handle qw(autoflush);
use Socket;
use strict;
require 'sysexits.ph';

my($progname, %opts);
my($host, $muxservice, $ipout, $sockaddr, $reply, $port, $proto, $file);

$progname = $0;
$progname =~ s/.*\///;
getopts("VhFp:o:ux", \%opts);

if ($opts{'V'}) {
	print "$progname: ", '$Id: muxcat.pl,v 1.7 2012/08/12 21:33:19 ksb Exp $', "\n";
	exit EX_OK();
}

my($usage) = 'usage [-Fux] [-o file] [-p port] host [services]';
if ($opts{'h'}) {
	print "$progname: $usage\n",
		"$progname: usage -h\n",
		"$progname: usage -V\n",
		"F       final service doesn't offer any acknowledgement\n",
		"h       show this help message\n",
		"o file  perl output file specification (defaults to >&STDOUT)\n",
		"p port  tcpmux port (default 1)\n",
		"u       use unbuffered output to file\n",
		"V       show only version information\n",
		"x       show remote replies on stderr\n",
		"host    host running tcpmux\n",
		"service connect to the named tcpmux service\n";
	exit EX_OK();
}

$host = shift @ARGV;
if (!defined($host)) {
	print STDERR "$progname: $usage\n";
	exit EX_USAGE();
}
push(@ARGV, $progname) if (0 == scalar(@ARGV));
$file = $opts{'o'};
$file ||= '-';
$opts{'p'} ||= 1;

redirect:
(undef,undef,$port,undef) = getservbyname($opts{'p'}, 'tcp');
(undef,undef,$port,undef) = getservbyport($opts{'p'}, 'tcp')
	unless defined($port);
if (!defined($port) && $opts{'p'} =~ m/^(\d+)$/o) {
	$port = $1;
}
if (!defined($port)) {
	print STDERR "$progname: $opts{'p'}: invalid port\n";
	exit EX_DATAERR();
}
if (! ($ipout = inet_aton($host))) {
	print STDERR "$progname: $host: invalid hostname\n";
	exit EX_NOHOST();
}
$sockaddr = sockaddr_in($port, $ipout) || die "$progname: sockaddr_in";
$proto = getprotobyname('tcp') || die "$progname: getproto";
if (! socket(SOCKET, PF_INET, SOCK_STREAM, $proto)) {
	print STDERR "$progname: socket: $!\n";
	exit EX_OSERR();
}

if (! connect(SOCKET, $sockaddr)) {
	print STDERR "$progname: connect: $host: $!\n";
	exit EX_OSERR();
}
autoflush SOCKET 1;

# negotiate with the mux to get to our service
while (defined($muxservice = shift @ARGV)) {
	print SOCKET "$muxservice\r\n";
	next if ('help' eq $muxservice);
	last if ($opts{'F'} && 0 == scalar(@ARGV));
	$reply = <SOCKET>;
	$reply =~ s/[\r\n]*$//g;
	print STDERR "$reply\n" if $opts{'x'};
	# MUX redirected us with @[host]:[port]
	if ($reply =~ m/^@([^:]*):*([^:]*)/o) {
		my($looph, $loopp) = ($host, $opts{'p'});
		$opts{'p'} = $2 if (defined($2) and '' ne $2);
		$host = $1 if (defined($1) and '' ne $1);
		close SOCKET;
		unshift @ARGV, $muxservice;
		if ($host eq $looph and $loopp eq $opts{'p'}) {
			print STDERR "$progname: redirection loop: $muxservice on $looph:$loopp\n";
			exit EX_SOFTWARE();
		}
		goto redirect;
	}
	next if ($reply =~ m/^\+/o);
	print STDERR "$progname: $host: $muxservice: $reply\n";
	exit EX_PROTOCOL();
}
shutdown(SOCKET, 1);

# read from the socket to file our output (-o)
$file = ">&STDOUT" if $file eq '-';
$file =~ s/^([^|>])/>$1/o;
open(OUT, "$file") || do {
	print STDERR "$progname: open: $file: $!\n";
	exit EX_CANTCREAT();
};
autoflush OUT if ($opts{'u'});
while(<SOCKET>) {
	print OUT;
}
close(SOCKET);
close(OUT);
exit EX_OK();
