#!/usr/bin/perl
package IAMd;

# Name: IAM, Interactive Account Manager
# Description: Multi server Multi domain account manager
# Version: 0.8
# Authors: Jason Jorgensen <jasonj@innominatus.com>
# Copyright (C) 2003  Jason Jorgensen
#
# This program 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.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
###########################################################################



use IO::Socket;
use IO::Socket::INET;
use IO::Socket::SSL;
use strict;

my $configdir = '/etc/IAM';

BEGIN {
    require Exporter;
    use vars qw( $VERSION @ISA );
	$VERSION     = (split ' ', q!$Revision: 0.7 $!)[1];
    @ISA          =  qw( Exporter );
}

sub new {
  my $class = shift @_;
  my $self = {};
  my $hostname = shift @_;
  my $port = shift @_;
  my $method = shift @_;
  my $padding = shift @_;

  my @missingrequirements = ();
  
  if (! $hostname) { push @missingrequirements, "'hostname'"; }
  if (! $port) { push @missingrequirements, "'port'"; }
  if (! $method) { push @missingrequirements, "'method'"; }
  if (! $padding) { push @missingrequirements, "'secret passphrase'"; }
 
  if (@missingrequirements) { die "Missing requirements: @missingrequirements\n"; }
    
  $self->{'hostname'} = $hostname;
  $self->{'port'} = $port;
  $self->{'method'} = $method;
  $self->{'padding'} = $padding;

  bless ( $self, $class );
}

sub connect {
  use MD5;
  my $self = shift @_;
  
  print "DEBUG: Opening connection to $self->{'hostname'}:$self->{'port'}:$self->{'method'}\n" if $self->{'debug'};
  if ($self->{'method'} eq "plaintext") {
    $self->{'socket'} = IO::Socket::INET->new(PeerAddr => $self->{'hostname'},
                                               PeerPort => $self->{'port'},
                                               Proto    => 'tcp',
                                               Type     => SOCK_STREAM,
                                               Timeout  => 5,
                                               )
            || return "1 Couldnt connect to $self->{'hostname'}:$self->{'port'}:$self->{'method'} : $@\n";
  }
  elsif ($self->{'method'} eq "ssl") {
    $IO::Socket::SSL::DEBUG=1 if $self->{'debug'};
    #print "trying ssl connection\n";
    $self->{'socket'} = IO::Socket::SSL->new(PeerAddr => $self->{'hostname'},
                                               PeerPort => $self->{'port'},
                                               Proto    => 'tcp',
                                               Type     => SOCK_STREAM,
                                               Timeout  => 5,
                                               SSL_use_cert => 0,
                                               SSL_verify_mode => 0x00,
                                               )
            || return " 1 Couldnt connect to $self->{'hostname'}:$self->{'port'}:$self->{'method'} : $@\n";
#                                               SSL_use_cert => 0,
#                                               SSL_verify_mode => 0x01,
#                                               SSL_key_file    => "$configdir/certs/IAMd-client.key",
#                                               SSL_cert_file   => "$configdir/certs/IAMd-client.crt",
  }
  else { die "Invalid Method"; }
  my $socket = $self->{'socket'};
  $socket->autoflush(1);
  print "DEBUG: Getting OTP Challenge from $self->{'hostname'}\n" if $self->{'debug'};
  my $OTPchallenge = <$socket>;
  chomp $OTPchallenge;
  print "DEBUG: OTP Challenge = $OTPchallenge\n" if $self->{'debug'};

  my $OTPelement = new MD5;
  $OTPelement->add($self->{'padding'});
  $OTPelement->add($OTPchallenge);
  my $OTP = unpack("H*", $OTPelement->digest());
  
  print "DEBUG OTP = $OTP\n" if $self->{'debug'};
  print $socket "$OTP\n";
  my $authenticated = <$socket>;
  chomp $authenticated;
  if ($authenticated eq "Welcome") { print "DEBUG: Authenticated!\n" if $self->{'debug'}; return 0;}
  else {return "Could not authenticate with IAM daemon\n";}
}

sub disconnect {
  my $self = shift @_;

  &request($self, 'quit');
  close($self->{'socket'});
  wait;
}

sub debug_on {
  my $self = shift @_;

  $self->{'debug'} = 1;
}

sub debug_off {
  my $self = shift @_;

  $self->{'debug'} = 0;
}

sub arrayofhashes {
  my $self = shift @_;
  my $result = shift @_;
  
  $result =~ s/^\d+\s+//;
  my @arrayofhashes;
  
  my @lines = split /\n/, $result;
  foreach my $line (@lines){
    my $hashref = {};
    my @pairs = split /,/, $line;
    foreach my $pair (@pairs) {
      (my $key, my $value) = split /\=\>/, $pair;
      $key =~ s/^\d+\s+//;
      $key =~ s/^\s*//;
      $key =~ s/\s*$//;
      $value =~ s/^\s*//;
      $value =~ s/\s*$//;
#      my $counter;
#      my $keycounter = $key.$counter;
#      print "DEBUG: keycounter: $keycounter\n" if $self->{'debug'};
#      while ($hashref->{$keycounter}) {
#        if (! $counter) { $counter = 1; }
#	else { $counter++; }
#	$keycounter = $key.$counter;
#	print "DEBUG: new keycounter: $keycounter\n" if $self->{'debug'};
#      }
      $hashref->{$key} = $value ;
      print "DEBUG: \$hasref->{$key} = $hashref->{$key}\n" if $self->{'debug'};
    }
    push @arrayofhashes, $hashref;
  }

  if ($self->{'DEBUG'}) {
    my $hashrefp;
    my $keyp;
    foreach $hashrefp (@arrayofhashes) {
      foreach $keyp (keys %{$hashrefp}) {
        print "DEBUG: $keyp => $hashrefp->{$keyp}\n";
      }
    }
  }
  
  return @arrayofhashes;
}

sub checkforspecialdata {
  my $self =  shift @_;
  my $result = shift @_;  
  
  print "DEBUG: checking for special data in: $result\n" if $self->{'debug'};    
  if ($result =~ /^ARRAYOFHASHES/) { print "DEBUG: array of hashes\n" if $self->{'debug'}; return &arrayofhashes($self, $result); }
  if ($result =~ /^smelly/) { print "DEBUG: array of hashes\n" if $self->{'debug'}; return &arrayofhashes($self, $result); }

  return;
}


sub request {
  my $self = shift @_;
  my $request = join '::', @_;

  my $result = "";
  my $line = "";
  my $socket = $self->{'socket'};
  
  print $socket "$request\n";
  print "DEBUG: sent request: $request\n" if $self->{'debug'};
#  while (($line !~ /^_ENDRESULT_\n$/) && ($socket->connected)) {
  $line = <$socket>;
  while (($line !~ /^_ENDRESULT_\n$/) && ! $@ && $self->{'socket'}->connected) {
    print "DEBUG: waiting for response: $result\n" if $self->{'debug'};
    $result .= $line;
    $line = <$socket>;
    eval { ($self->{'socket'}->connected); };
    if ($line eq '') {
      print "DEBUG: connection fubar'd shutdown socket\n" if $self->{'debug'};
      eval { $self->{'socket'}->(2) };
    }
  }
  print "DEBUG: final result: $result\n" if $self->{'debug'};
  print "DEBUG: received a end of result code, continuing\n" if $self->{'debug'};
  $result =~ s/_ENDRESULT_\n//g; # Remove the end of result message \n used 
  chomp $result;

  return $result;
}

sub waitforprompt {
  my $self = shift @_;
  my $socket = $self->{'socket'};
  
  while (<$socket> !~ /^\>\n$/){
  }

}


1;

__END__
