#!/usr/bin/perl -w
# -------------------------------------------------------------------------
#
# tumbler - client program used to open a door on a machine running
#           tumblerd
#
# Copyright (c) 2004 John Graham-Cumming
#
#   This file is part of tumbler
#
#   tumbler is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   tumbler 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 tumbler; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
#
# -------------------------------------------------------------------------

use strict;
use Getopt::Long;
use IO::Socket::INET;
use Digest::SHA qw(sha256_hex);
use Term::ReadKey;

# The version number of this program

my $version = '0.1.0';

# This is the list of URLs that were specified on the command line
# for each tumbler URL a single UDP packet will be sent to the appropriate
# machine.

my @urls;

# This is the address we are sending from which is set with the --from
# address.  This is needed if tumbler is being run behind a NAT device
# and the local address on this machine is not the address that will
# be seen by the tumblerd

my $from = '';

# -----------------------------------------------------------------------
#
# parse_command_line
#
# Parses the command line options and returns 1 if successful
#
# -----------------------------------------------------------------------
sub parse_command_line
{
    my $help = 0;

    if ( !GetOptions( 'open=s' => \@urls,
                      'from=s' => \$from,
                      'help'   => \$help ) ) {
        return 0;
    }

    # Handle getting help

    if ( $help ) {
        print "tumbler v$version - client to open a door on a tumblerd machine\n";
        print "\nUsage: tumbler --open tumbler://secret\@host:port/\n";
        print "If secret is omitted then tumbler will prompt for it\n";
        print "\nYou can optionally add --from IP if you are behind a ";
        print "NAT device giving the IP address of the external interface ";
        print "as the parameter to --from\n";
        exit 0;
    }

    # Check that the URLs look good

    if ( $#urls == -1 ) {
        print STDERR "Must specify at least one --open option\n";
        return 0;
    }

    foreach my $url (@urls) {
        if ( $url !~ /tumbler:\/\/(.+@)?[^:]+:\d+\// ) {
            print STDERR "Don't understand the --open URL '$url'\n";
            print STDERR "URLs must be in the form ";
            print STDERR "tumbler://secret\@host:port/\n";
            print STDERR "If secret is omitted then tumbler will prompt for it\n";
            return 0;
        }
    }

    return 1;
}

# MAIN

if ( parse_command_line() ) {
    foreach my $url (@urls) {
        $url =~ /tumbler:\/\/((.+)@)?([^:]+):(\d+)\//;

        # $2 = secret or undef
        # $3 = host
        # $4 = port

        my ( $secret, $host, $port ) = ( $2, $3, $4 );

        # If the secret is omitted then prompt for it

        if ( !defined( $secret ) ) {
            print "Enter secret for $host:$port: ";
            ReadMode( 'noecho' );
            $secret = ReadLine(0);
            chomp $secret;
            ReadMode( 'normal' );
            print "\n";
	}

        my $socket = new IO::Socket::INET( PeerHost => $host,
                                           PeerPort => $port,
                                           Proto    => 'udp' );

        if ( !defined( $socket ) ) {
            print STDERR "Failed to create a UDP socket for $host:$port\n";
            exit 0;
        }

        my ($sec,$min,$hour,$mday,$mon,$year) = gmtime(time);
        my ( $my_port, $my_ip ) = sockaddr_in( $socket->sockname );
        my $me = ($from ne '')?$from:inet_ntoa( $my_ip );
        my $hash = sha256_hex( "$year$mon$mday$hour$min:$me:$secret" );

        $socket->send( "TUMBLER1: $hash" );
    }
}

exit 1;
