#!/usr/local/bin/perl -w
# -------------------------------------------------------------------------
#
# shimmer - client program determine which port a service is currently
#           running on
#
# Copyright (c) 2007-2008 John Graham-Cumming
#
#   This file is part of shimmer
#
#   shimmer 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.
#
#   shimmer 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 shimmer; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
#
# -------------------------------------------------------------------------

use strict;
use Getopt::Long;
use Digest::SHA qw(sha256);
use Crypt::Rijndael;
use Term::ReadKey;

# The version number of this program

my $version = '0.1.0';

# The name of the mirage (service) to connect to on the remote
# machine.

my $mirage;

# The shared secret for the mirage

my $secret;

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

    if ( !GetOptions( 'open=s' => \$mirage,
                      'secret=s' => \$secret,
                      'help'     => \$help ) ) {
	$help = 1;
    }
    
    # Handle getting help
    
    if ( $help ) {
        print "shimmer v$version - client find the current safe port on a machine running shimmerd\n";
        print "\nUsage: shimmer --open name:low:high --secret secret\n";
        print "If secret is omitted then shimmer will prompt for it\n";
	
        return 0;
    }
    
    if ( ( !defined( $mirage ) ) || ( $mirage eq '' ) ||
	 ( $mirage !~ /^[^:]+:\d+:\d+$/ ) ) {
        print STDERR "Must specify the --open option with the name, low and high ports of the mirage (service)\n";
	
        return 0;
    }
    
    return 1;
}

# -----------------------------------------------------------------------
#
# get_current_minute
#
# Returns the number of minutes since the start of the Unix epoch as
# an integer
#
# -----------------------------------------------------------------------
sub get_current_minute
{
    return int( time / 60 );
}

# MAIN

if ( parse_command_line() ) {

    # If the secret is omitted then prompt for it

    if ( !defined( $secret ) ) {
	print "Enter secret for $mirage: ";
	ReadMode( 'noecho' );
	$secret = ReadLine(0);
	chomp $secret;
	ReadMode( 'normal' );
	print "\n";
    }
    
    $mirage =~ /^([^:]+):(\d+):(\d+)$/;
    my ( $mirage_name, $low, $high ) = ( $1, $2, $3 );
    
    my $minute = get_current_minute();
    my $key = sha256( "$mirage_name - $secret - $minute" );
    my $cipher = Crypt::Rijndael->new( $key,
				       Crypt::Rijndael::MODE_ECB() );
    my $range = $high - $low;
    
    my $plain = pack( "V4", ( 0, 0, 0, 0 ) );
    my $secret_text = $cipher->encrypt( $plain );
    $secret_text = unpack( "V", $secret_text );
    $secret_text %= $range;
    my $port = $secret_text + $low;
    
    print "$port\n";
    
    exit 0;
}

exit 1;
