#!/usr/bin/perl
package IAM;

# 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 DBIx::Broker;
#use strict;

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

sub new {
  my $class = shift @_;
  my $self = {};
  my $plugindir = shift @_;
  
  $self->{'plugindir'} = $plugindir;
  
  bless ( $self, $class );
}

sub connect {
  my $self = shift @_;

  if ( @_ ) {
    my ( $driver, $masterdatabase, $remotesdatabase, $hostname, $port, $user, $password) = @_;
    $self->{'masterdb'} = eval {DBIx::Broker->new("$driver", "$masterdatabase", "$hostname", "$port", "$user", "$password")};
    &check_for_errors($self, $@);
    if ( ! $self->{'masterdb'} ) { print "1 Could not open database $database!\n";
    }
  }
}

sub disconnect {
  my $self = shift @_;

  eval { $self->{'masterdb'}->disconnect(); }
  &check_for_errors($self, $@);
}

sub debug_on {
  my $self = shift @_;

  eval {$self->{'masterdb'}->debug_on( \*STDERR )};  # put DBIx::Broker into DEBUG mode
  &check_for_errors($self, $@);
  $self->{'debug'} = 1;
}

sub debug_off {
  my $self = shift @_;

  eval {$self->{'masterdb'}->debug_off( \*STDERR )};  # put DBIx::Broker out of  DEBUG mode
  &check_for_errors($self, $@);
  $self->{'debug'} = 0;
}

sub dbtypes {
  my $self = shift @_;

  my @dbtypes = eval {$self->{'masterdb'}->select_one_column('DISTINCT TYPE', 'DOMAINDBS', "ORDER BY TYPE", 0)};
  &check_for_errors($self, $@);
  $self->{'dbtypes'} = join (' ', @dbtypes );

  return @dbtypes;
}

sub domains {
  print "DEBUG: sub domains called\n" if $self->{'debug'};
  my $self = shift @_;

  my @domains = eval {$self->{'masterdb'}->select_one_column('DISTINCT DOMAIN', 'DOMAINOPTIONS', "ORDER BY DOMAIN", 0)};
  &check_for_errors($self, $@);
  $self->{'domainlist'} = join (' ', @domains );

  return \@domains;
}

sub domaindbs {
  my $self = shift @_;

  my @domaindbs = eval {$self->{'masterdb'}->select_all('DOMAINDBS', "ORDER BY DBFQDN", 1)};
  &check_for_errors($self, $@);
  $self->{'domaindbs'} = 1;

  return @domaindbs;
}

sub singledb {
  my $self = shift @_;
  my $domain = shift @_;
  my $type = shift @_;
  
  my @errors;
  
  if (! $domain) { push @errors, 1500 }
  if (! $type) { push @errors, 1501 }
  
  if ($#errors <0) {
    my @singledb = eval {$self->{'masterdb'}->select_all('DOMAINDBS', "WHERE DOMAIN='$domain' AND TYPE='$type'", 1)};
    &check_for_errors($self, $@);
    return @singledb[0];
  }
  else { return &display_errors(\@errors); }

}

sub remote_iamd_connect {
  use IAMd;
  
  my $self = shift @_;
  my $domain = shift @_;
  my $type = shift @_;

  if (! $domain) { push @errors,  1500}
  if (! $type) { push @errors,  1501}

  my $info = &singledb($self, $domain, $type);  # grab the name of the required plugin
  if (! $info) { return "1 not enough info to connect to remote IAMd\n"; }

  print "DEBUG: attempting connection to remote IAMd: $info->{'HOSTNAME'}, $info->{'PORT'}, $info->{'METHOD'}, $info->{'PASSWORD'}\n" if $self->{'debug'};
  my $remote_iamd = eval {IAMd->new($info->{'HOSTNAME'}, $info->{'PORT'}, $info->{'METHOD'}, $info->{'PASSWORD'})};
  &check_for_errors($self,$@);
  if ($remote_iamd) { 
    $result = $remote_iamd->connect;
  }
  else { return "1 Couldnt not establish remote IAMd communications information\n"; }
  if ($remote_iamd->connect) { 
    print "DEBUG: could NOT connect to remote IAMd\n" if $self->{'debug'};
    return "1 Could not connect to remove IAMd\n";
  }
  
  print "DEBUG: current plugin: $info->{'PLUGIN'}\n" if $self->{'debug'};

  return $remote_iamd, $info->{'PLUGIN'};

  #my $usestatement = "use $pluginname->{'PLUGIN'}"; # setup the 'use' statement to be eval'd
  #eval $usestatement;
  #&check_for_errors($self, $@);
  #
  #my $plugin = eval { $pluginname->new(); };  # calling new method on the dynamicly loaded object "plugin"
  #&check_for_errors($self, $@)ug
  #
  #@requiredfunctions = ('add', 'delete', 'close');
  #foreach $function (@requiredfunctions) {
  #  *fullfunction = $pluginname."::".$function;
  #  if (! defined &fullfunction) { return 1; }
  #}  
  #
  #return $plugin;
}  

sub remote_iamd_disconnect {
  my $self = shift @_;
  my $iamd = shift @_;

  $iamd->disconnect;
  $iamd=undef;

  return;
}

sub update_currentuser {
  my $self = shift @_;
  my $user = shift @_;
  my $userip = shift @_;
  
  my @errors;
  
  if ($user) { $self->{'user'} = $user; } else { push @errors, 1700; }
  if ($userip) { $self->{'userip'} = $userip; } else { push @errors, 1701; }

  print "DEBUG: updating currentuser with: user=$self->{'user'}, userip=$self->{'userip'}\n" if $self->{'debug'};
  print "DEBUG: $self\n" if $self->{'debug'};
  if ($#errors <0) { return 0; }
  else { return &display_errors($self,\@errors); }
}


sub update_translog {
  my $self = shift @_;
  my $uniqueid = shift @_;
  my $action = shift @_;
  my $args = shift @_;
  my $result = shift @_;
  
  print "DEBUG: updated translog called with ($self->{'user'}, $self->{'userip'}, $uniqueid, $action, $args, $self->{'timestamp'}, $result)\n" if $self->{'debug'};
  
  my @errors;
  
  if (! $self->{'user'} ) { print "DEBUG: update_translog: no IAM user\n" if $self->{'debug'};push @errors, 1004; }
  if (! $self->{'userip'} ) { print "DEBUG: update_translog: no IAM userip\n" if $self->{'debug'}; push @errors, 1005; }
  if (! $uniqueid) { print "DEBUG: update_translog: no uniqueid\n" if $self->{'debug'}; push @errors, 1101; }
  if (! $action) { print "DEBUG: update_translog: no action\n" if $self->{'debug'}; push @errors, 1600; }
  
  if ($#errors < 0) {
    my %logdata = (
                "MODUSER", $self->{'user'},
                "MODIP", $self->{'userip'},
                "UNIQUEID", $uniqueid,
                "ACTION", $action,
                "ARGS", $args,
                "TIMESTAMP", $self->{'timestamp'},
                "RESULT", $result,
               );
    eval {$self->{'masterdb'}->insert('TRANSACTIONLOG', \%logdata)};
    print "DEBUG: updated translog with ($self->{'user'}, $self->{'userip'}, $uniqueid, $action, $args, $self->{'timestamp'}, $result)\n" if $self->{'debug'};
    &check_for_errors($self,$@);
    my %lastmoddata = (
                "LAST_MOD_BY_USER", $self->{'user'},
                "LAST_MOD_TIMESTAMP", $self->{'timestamp'},
                "LAST_MOD_BY_IP", $self->{'userip'},
                "LAST_MOD_ACTION", $action,
                "LAST_MOD_ARGS", $args,
                "LAST_MOD_RESULT", $result,
               );
    eval {$self->{'masterdb'}->update('USERDATA', \%lastmoddata, "WHERE UNIQUEID='$uniqueid'")};
    &check_for_errors($self,$@);
    return 0;
  }
  else { return &display_errors($self,\@errors); }

}

sub check_for_errors {
  my $self = shift @_;
  $error = shift @_;

  if ($error) {
    print STDERR "Error: $error\n";
  }
  return $error;
}

sub display_errors {
  my $self = shift @_;
  my @errors = @{shift @_};
  my $errmsg;

  foreach $error (@errors) {
    $errmsg .= "$error ";
    if ($error == 666) { print "Yet to be classified error"; }
    
    elsif ($error == 104) { $errmsg .= "Must supply a uniqueid"; }
  
    elsif ($error == 400) { $errmsg .= "User does not have the specified account"; }
  
    elsif ($error == 1000) { $errmsg .= "An account with the Username already exists for account type. Please choose another"; }
    elsif ($error == 1001) { $errmsg .= "Must specify a first name."; }
    elsif ($error == 1002) { $errmsg .= "Must specify a last name."; }
    elsif ($error == 1003) { $errmsg .= "Chosen UniqueID not unique."; }
    elsif ($error == 1004) { $errmsg .= "You are not an authenticated user."; }
    elsif ($error == 1005) { $errmsg .= "No established IP address."; }

    elsif ($error == 1100) { $errmsg .= "domain=? No domain specified"; }
    elsif ($error == 1101) { $errmsg .= "uniqueid=? No uniqueid specified"; }
    elsif ($error == 1102) { $errmsg .= "uniqueid=? That uniqueid does not exist"; }
    elsif ($error == 1103) { $errmsg .= "Cyrus error trying to delete the account. Possibly did not exist."; }
    elsif ($error == 1104) { $errmsg .= "username=? No username specified"; }
    elsif ($error == 1105) { $errmsg .= "password=? No password specified"; }
    elsif ($error == 1106) { $errmsg .= "username=? That username does not exist"; }

    elsif ($error == 1200) { $errmsg .= "An account with the Username already exists for account type. Please choose another"; }
    elsif ($error == 1201) { $errmsg .= "There were errors in the submitted data."; }
    elsif ($error == 1202) { $errmsg .= "There were errors in the username or password."; }

    elsif ($error == 1300) { $errmsg .= "An account with the Username already exists for account type. Please choose another"; }
    elsif ($error == 1301) { $errmsg .= "There were errors in the submitted form."; }
    elsif ($error == 1302) { $errmsg .= "There were errors in the username or password."; }
    elsif ($error == 1303) { $errmsg .= "Chosen UniqueID not unique."; }
    elsif ($error == 1304) { $errmsg .= "Cyrus error trying to delete the account. Possibly did not exist."; }

    elsif ($error == 1400) { $errmsg .= "Please re-enter a Username with ONLY [A-Z a-z 0-9 _]"; } 
    elsif ($error == 1401) { $errmsg .= "Please enter a Plain Password longer than 4 characters"; }
    elsif ($error == 1403) { $errmsg .= "Please enter a unix crypted password with exactly 13 characters or an md5 crypted password"; }
    
    elsif ($error == 1500) { $errmsg .= "Must specify a domain"; }
    elsif ($error == 1501) { $errmsg .= "Must specify a type"; }

    elsif ($error == 1600) { $errmsg .= "No 'actoin' specified"; }
    
    elsif ($error == 1700) { $errmsg .= "Starting number must be a number!"; }
    elsif ($error == 1701) { $errmsg .= "Value must be ASC or DESC"; }
    elsif ($error == 1700) { $errmsg .= "Return number must be a number!"; }
    
    else { $errmsg .= "Unknown error"; }
    $errmsg .= "\n";
  }
  return $errmsg;
}

sub remove_dups {
  # takes multiple lists and returns a list with no duplicates
  my @dups = @_;
  my %nodups;

  @nodups{@dups} = (1) x @dups;
  print "DEBUG: Removing Duplicates: dups: ".join(',',@dups)." no dups: ".join(',',keys(%nodups))."<br>\n" if $self->{'debug'};
  @nodups=keys(%nodups);
      
  return @nodups;
}

###### all sub's below are user subs

sub add_user {
  my $self = shift @_;
  my $args = join(',', map { "'$_'"} @_ );
  my $firstname = shift @_;
  my $lastname = shift @_;
  my $uniqueid = shift @_;
  my $age = shift @_;
  my $street = shift @_;
  my $city = shift @_;
  my $zip = shift @_;
  my $phonenumber = shift @_;

  my @errors;
  my $error;
  
  $self->{'timestamp'} = time;
  
  if (! $firstname) { push @errors, 1001; }
  if (! $lastname) { push @errors, 1002; }
  
  @alluniqueids = eval {$self->{'masterdb'}->select_one_column('DISTINCT UNIQUEID', 'USERDATA', "", 0)};
  
  if ($uniqueid) {              ## specified uniqueid, make sure its unique
    if (grep /^$uniqueid$/, @alluniqueids) { push @errors, 1003; }
  }
  else {                        ## if they didnt enter a uniqueid then make one up.
    $uniqueid = $firstname.$lastname;
    my $uniqueidcounter = 0;
    while ( grep /^$uniqueid$/, @alluniqueids ) {
      $uniqueid = $firstname.$lastname.$uniqueidcounter;
      $uniqueidcounter++;
      print "DEBUG uniqueid: $uniqueid\n" if $debug;
    }
  }

  if ($#errors < 0) {
    my %data = (
                "USERFIRSTNAME", $firstname,
                "USERLASTNAME", $lastname,
                "UNIQUEID", $uniqueid,
                "USERAGE", $age,
                "USERSTREET", $street,
                "USERCITY", $city,
                "USERSTATE", $state,
                "USERZIP", $zip,
                "USERPHONENUMBER", $phonenumber,
                );
    eval {$self->{'masterdb'}->insert('USERDATA', \%data)};
    $error .= &check_for_errors($self,$@);
    my %createdata = (
                "CREATED_BY_USER", $self->{'user'},
                "TIMESTAMP", $self->{'timestamp'},
                "CREATED_BY_IP", $self->{'userip'},
               );
    eval {$self->{'masterdb'}->update('USERDATA', \%createdata, "WHERE UNIQUEID='$uniqueid'")};
    &check_for_errors($self,$@);
  }
  else { return &display_errors($self,\@errors); }
  
  if (! $error ) { &update_translog($self, $uniqueid, 'add_user', $args, 'success'); return 0; }
  else { &update_translog($self, $uniqueid, 'add_user', $args, "failure: $error"); return $error; }

}

sub delete_user {
  my $self = shift @_;
  my $args = join(',', map { "'$_'"} @_ );
  my $uniqueid = shift @_;

  my @errors;
  my $error;
  $self->{'timestamp'} = time;
  
  if (! $uniqueid) { push @errors, 1101; }
  
  if (! eval {$self->{'masterdb'}->select_one_value('UNIQUEID', 'USERDATA', "WHERE UNIQUEID='$uniqueid'", 0)}) { push @errors, 1102; }  ## make sure the uniqueid exists

  if ($#errors < 0) {
    @alluseraccounts = eval {$self->{'masterdb'}->select_all('AUTHDATA', "WHERE UNIQUEID='$uniqueid'", 1)};
    $error .= &check_for_errors($self,$@);
    if (! $error) {
      foreach $account (@alluseraccounts) {
        if (&delete_account($self, $account->{'DOMAIN'}, $account->{'TYPE'}, $account->{'UNIQUEID'}, $account->{'USERNAME'})) { $error .= $_; }
      }
      eval {$self->{'masterdb'}->delete('USERDATA', "WHERE UNIQUEID='$uniqueid'")};
      $error .= &check_for_errors($self,$@);
      eval {$self->{'masterdb'}->delete('AUTHDATA', "WHERE UNIQUEID='$uniqueid'")};
      $error .= &check_for_errors($self,$@);
    }
  }
  else { return &display_errors($self,\@errors); }
  
  if (! $error ) { &update_translog($self, $uniqueid, 'delete_user', $args, 'success'); return 0; }
  else { &update_translog($self, $uniqueid, 'delete_user', $args, "failure: $error"); return $error; }
  
}

sub display_user {
  my $self = shift @_;
  my $args = join(',', map { "'$_'"} @_ );
  my $uniqueid = shift @_;

  my @errors;
  my $error;
  my $result;
  my $subresult;
  
  if (! $uniqueid) { push @errors, 1101; }
  
  if (! eval {$self->{'masterdb'}->select_one_value('UNIQUEID', 'USERDATA', "WHERE UNIQUEID='$uniqueid'", 0)}) { push @errors, 1102; }  ## make sure the uniqueid exists

  if ($#errors < 0) {
    my @fields = (
                "USERFIRSTNAME",
                "USERLASTNAME",
                "UNIQUEID",
                "USERAGE",
                "USERSTREET",
                "USERCITY",
                "USERSTATE",
                "USERZIP",
                "USERPHONENUMBER",
                "CREATED_BY_USER",
                "CREATED_BY_IP",
                "TIMESTAMP",
                "LAST_MOD_BY_USER",
                "LAST_MOD_TIMESTAMP",
                "LAST_MOD_BY_IP",
                "LAST_MOD_ACTION",
                "LAST_MOD_RESULT",
                );
    $userinfo = eval {$self->{'masterdb'}->select_one_row(\@fields, 'USERDATA', "WHERE UNIQUEID='$uniqueid'", 1)};
    $error .= &check_for_errors($self,$@);
    foreach $field (@fields) { $result .= "$field=>$userinfo->{$field},"; }
    $result .= "\n\0\n";   ## add a null line as a delimiter
    @alluseraccounts = eval {$self->{'masterdb'}->select_all('AUTHDATA', "WHERE UNIQUEID='$uniqueid'", 1)};
    $error .= &check_for_errors($self,$@);
    if (! $error) {
      $accountcounter = 1;
      foreach $account (@alluseraccounts) {
        print "DEBUG: calling display_account with ($account->{'DOMAIN'}, $account->{'TYPE'}, $account->{'UNIQUEID'}, $account->{'USERNAME'})\n" if $self->{'DEBUG'};
        $subresult = "AccountNumber=>$accountcounter,Domain=>$account->{'DOMAIN'},Type=>$account->{'TYPE'},AuthenticationUsername=>$account->{'USERNAME'},";
        $subresult .= &display_account($self, $account->{'DOMAIN'}, $account->{'TYPE'}, $account->{'UNIQUEID'}, $account->{'USERNAME'})."\n\0\n";
	print "DEBUG: subresult for account #$accountcounter: $subresult\n" if $self->{'debug'};
	$result .= $subresult;
#        if ($subresult !~ /^0.*/) { $error .= $result; } else { $result .= $subresult; }
        $accountcounter++;
      }
    }
  print "DEBUG: final output of display user: $result\n" if $self->{'debug'};
  }
  else { return &display_errors($self,\@errors); }
  
  if (! $error ) { return "0 ".$result; }
  else { return $error; }
}

sub display_users {
  my $self = shift @_;
  my $args = join(',', map { "'$_'"} @_ );
  my $searchpattern = shift @_;
  my $returnnumber = shift @_;
  my $startingnumber = shift @_;
  my $sort = shift @_;
  my $sortascdesc = shift @_;

  my @errors;
  my $error;
  my $result;
  my $subresult;
  my %accounts;
  my $userstipulations;
  my $authstipulations;
  
  if (! $returnnumber) { $returnnumber = 25; }
  elsif ($returnnumber && $returnnumber !~ /\d+/) { push @errors, 1702 }
  if (! $startingnumber) { $startingnumber = 0; }
  if ($startingnumber && $startingnumber !~ /\d+/) { push @errors, 1700; }
  if (! $sortascdesc) { $sortascdesc = "ASC"; }
  if ($sortascdesc && $sortascdesc ne "ASC" && $sortascdesc ne "DESC") { push @errors, 1701; }
  if ($sort) { $sort =~ tr/a-z/A-Z/; } 

  if ($#errors < 0) {
    my @userfields = (
                "USERFIRSTNAME",
                "USERLASTNAME",
                "UNIQUEID",
                "USERAGE",
                "USERSTREET",
                "USERCITY",
                "USERSTATE",
                "USERZIP",
                "USERPHONENUMBER",
                "CREATED_BY_USER",
                "CREATED_BY_IP",
                "TIMESTAMP",
                "LAST_MOD_BY_USER",
                "LAST_MOD_TIMESTAMP",
                "LAST_MOD_BY_IP",
                "LAST_MOD_ACTION",
                "LAST_MOD_RESULT",
                );
    my @authfields = (
                "USERNAME",
                "USERPASSWORD",
                "TYPE",
		"DOMAIN",
                "UNIQUEID",
		);
    ## generate sql stipulations string
    if ($searchpattern) {
      $userstipulations = "WHERE USERFIRSTNAME LIKE '%$searchpattern%' OR USERLASTNAME LIKE '%$searchpattern%' OR UNIQUEID LIKE '%$searchpattern%' OR USERAGE LIKE '%$searchpattern%' OR USERSTREET LIKE '%$searchpattern%' OR USERCITY LIKE '%$searchpattern%' OR USERSTATE LIKE '%$searchpattern%' OR USERZIP LIKE '%$searchpattern%' OR USERPHONENUMBER LIKE '%$searchpattern%' ";
      $authstipulations = "WHERE USERNAME LIKE '%$searchpattern%' OR UNIQUEID LIKE '%$searchpattern%' OR TYPE LIKE '%$searchpattern%' ";
      $error .= &check_for_errors($self,$@);
    }
    if ($sort) {
      if (grep /$sort/, @userfields) { $usersort .= "ORDER BY $sort $sortascdesc "; }
      if (grep /$sort/, @authfields) { $authsort .= "ORDER BY $sort $sortascdesc "; }
      $userstipulations .= $usersort; 
      $authstipulations .= $authsort; 
    }
    else { 
      ## always sort by UNIQUEID if client doesnt request anything special
      $usersort .= "ORDER BY UNIQUEID $sortascdesc "; 
      $authsort .= "ORDER BY UNIQUEID $sortascdesc "; 
      $userstipulations .= $usersort; 
      $authstipulations .= $authsort; 
    }
    ## ALWAYS limit the sql search with the default values or client requested values
    $userlimit .= "LIMIT $startingnumber, $returnnumber";
    $authlimit .= "LIMIT $startingnumber, $returnnumber";
#    $userstipulations .= $userlimit; 
#    $authstipulations .= $authlimit; 

    print "DEBUG: user stipulations: $userstipulations\n" if $self->{'debug'};
    print "DEBUG: auth stipulations: $authstipulations\n" if $self->{'debug'};

    ## get all the uniqueids matching the stipulations
    @userids = eval {$self->{'masterdb'}->select_one_column('UNIQUEID', 'USERDATA', "$userstipulations", 0)};
    $error .= &check_for_errors($self,$@);
    @authids = eval {$self->{'masterdb'}->select_one_column('UNIQUEID', 'AUTHDATA', "$authstipulations", 0)};
    $error .= &check_for_errors($self,$@);

    ## remove duplicate uniqueids
    @uniqueids = &remove_dups(@userids, @authids);
    my $matchingusers = $#uniqueids + 1;

    $totalusers = eval {$self->{'masterdb'}->count('USERDATA', '')};
    $totalaccounts = eval {$self->{'masterdb'}->count('AUTHDATA', '')};
    ## turn the list of ids into a string of ids
    $uniqueidstring = join(',', map { "'$_'"} @uniqueids );

    print "DEBUG: uniqueidstring: $uniqueidstring\n" if $self->{'debug'};
    
    ## retrieve all the records for the matching users
    @userinfo = eval {$self->{'masterdb'}->select_all('USERDATA', "WHERE UNIQUEID in ($uniqueidstring) $usersort $userlimit", 1)};
    $error .= &check_for_errors($self,$@);

    foreach $user (@userinfo) {
      push @authuniqueids, $user->{'UNIQUEID'};
    }
    $authuniqueidstring = join(',', map { "'$_'"} @authuniqueids );
    @authinfo = eval {$self->{'masterdb'}->select_all('AUTHDATA', "WHERE UNIQUEID in ($authuniqueidstring)", 1)};
    $error .= &check_for_errors($self,$@);

    $displayingusers = $#userinfo + 1;
    $displayingaccounts = $#authinfo + 1;
    
    ## start making the final result with some var's the client might find useful
    $result = "totalaccounts=>$totalaccounts,totalusers=>$totalusers,matchingusers=>$matchingusers,displayingusers=>$displayingusers,displayingaccounts=>$displayingaccounts,startingusernumber=>".($startingnumber+1).",userreturnlimit=>$returnnumber\n\0\n";
    
    ## use the UNIQUEID to sort the accounts 
    foreach $auth (@authinfo) {
      foreach $field (@authfields) { $accounts{$auth->{'UNIQUEID'}}  .= "$field=>$auth->{$field},"; }
      $accounts{$auth->{'UNIQUEID'}} .= "\0\n";
      #print "DEBUG: accounts{$auth->{'UNIQUEID'}}: $accounts{$auth->{'UNIQUEID'}}\n" if $self->{'debug'};
    }
    foreach $user (@userinfo) {
      my $subresult = '';
      foreach $field (@userfields) { $subresult .= "$field=>$user->{$field},"; }
      $subresult .= "\0\n";        ## add a null to seperate accounts
      $subresult .= "$accounts{$user->{'UNIQUEID'}}\n,\0\n"; ## add a null line as a delimiter between users
      #print "DEBUG: subresult{$user->{'UNIQUEID'}}: $subresult\n" if $self->{'debug'};
      $result .= $subresult;
    }
  print "DEBUG: final output of display users: $result\n" if $self->{'debug'};
  }
  else { return &display_errors($self,\@errors); }
  
  if (! $error ) { return "0 ".$result; }
  else { return $error; }
}

sub fields {
  my $self = shift @_;
  my $method = shift @_;
  my $domain = shift @_;
  my $type = shift @_;

  my @errors; # record any setup errors here
  my $error;  # record any errors during execution here
  
  if (! $method) { push @errors, 10002; }
  if (! $domain) { push @errors, 1500; }
  if (! $type) { push @errors, 1501; }

  if ($#errors < 0) {
    print "DEBUG: about to open connection to remote IAMd\n" if $self->{'debug'};
    ($iamd, $pluginname) = &remote_iamd_connect($self, $domain, $type);
    if ($iamd) {
      print "DEBUG: about to make a call to a remote IAMd with password: $password\n" if $self->{'debug'};
      $result = eval {$iamd->request("remote_".$method."_fields", $pluginname)};
      $error .= &check_for_errors($self,$@);
      &remote_iamd_disconnect($self, $iamd);   ## Disconnect from remote IAMd
    }
  }
  else { return &display_errors($self,\@errors); }

  if (! $error ) { return $result; }
  else { return $error; }
    
}


## subroutines that match the sub's in all the plugins, keeps info like last updated timestamps and logging of activity.
sub activate_account {
  my $self = shift @_;
  my $args = join(',', map { "'$_'"} @_ );
  my $domain = shift @_;
  my $type = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;

  my @optionalargs = @_;
  
  my @errors;
  my $error;
  my %data;
  $self->{'timestamp'} = time;
  
  if (! $domain) { push @errors, 1500; }
  if (! $type) { push @errors, 1501; }
  if (! $uniqueid) { push @errors, 1101; }
  if (! $username) { push @errors, 1104; }

  if (! eval {$self->{'masterdb'}->select_one_value('DISTINCT UNIQUEID', 'USERDATA', "WHERE UNIQUEID='$uniqueid'", 0)}) { push @errors, 1102; }  ## make sure the uniqueid exists
  if (! eval {$self->{'masterdb'}->select_one_column('DISTINCT USERNAME', 'AUTHDATA', "WHERE DOMAIN='$domain' AND TYPE='$type' AND USERNAME='$username'", 0)}) { push @errors, 1106; }  ## make sure the username exists

  if ($#errors < 0) {
  ($iamd, $pluginname) = &remote_iamd_connect($self, $domain, $type);
    if ($iamd) {
      $result = eval {$iamd->request('remote_activate', $pluginname, $uniqueid, $username, @optionalargs)};
      if ($result !~ /^0.*$/) { $error .= $result; }
      $error .= &check_for_errors($self,$@);
      &remote_iamd_disconnect($self, $iamd);   ## Disconnect from remote IAMd
    }
  }
  else { return &display_errors($self,\@errors); }

  if (! $error ) { &update_translog($self, $uniqueid, 'activate_account', $args, 'success'); return $result; }
  else { &update_translog($self, $uniqueid, 'activate_account', $args, "failure: $error"); return $error; }
}

sub add_account {
  my $self = shift @_;
  my $args = join(',', map { "'$_'"} @_ );
  my $domain = shift @_;
  my $type = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;
  my $password = shift @_;
  my $cryptedpassword = shift @_;

  my @optionalargs = @_;
  
  my @errors; # record any setup errors here
  my $error;  # record any errors during execution here
  $self->{'timestamp'} = time;
  
  if (! $domain) { push @errors, 1500; }
  if (! $type) { push @errors, 1501; }
  if (! $uniqueid) { push @errors, 1101; }
  if (! $username) { push @errors, 1104; }
  if (! $password && ! $cryptedpassword) { push @errors, 1105; }

  if (! eval {$self->{'masterdb'}->select_one_value('DISTINCT UNIQUEID', 'USERDATA', "WHERE UNIQUEID='$uniqueid'", 0)}) { push @errors, 1102; }  ## make sure the uniqueid exists

  if ($#errors < 0) {
    if ($password) { 
      use Crypt::PasswdMD5;
      use String::Random;
      $rand = new String::Random;
      $salt = $rand->randpattern("ssssssss");

      $finalpassword = unix_md5_crypt($password, $salt); 
    }
    elsif ($cryptedpassword) { $finalpassword = $cryptedpassword; }

    print "DEBUG: about to open connection to remote IAMd\n" if $self->{'debug'};
    ($iamd, $pluginname) = &remote_iamd_connect($self, $domain, $type);
    print "DEBUG: result from remote iamd connection call: $iamd\n" if $self->{'debug'};
    if (($iamd) && ($iamd !~ /^1.*/)) {
      print "DEBUG: about to make a call to a remote IAMd with password: $password\n" if $self->{'debug'};
      $result = eval {$iamd->request('remote_add', $pluginname, $uniqueid, $username, $password, $cryptedpassword, @optionalargs)};
      $error .= &check_for_errors($self,$@);
      if ($result =~ /^0.*/) {
        my %data = (
                  "UNIQUEID", $uniqueid,
                  "USERNAME", $username,
                  "USERPASSWORD", $finalpassword,
                  "DOMAIN", $domain,
                  "TYPE", $type,
                 );
        print "DEBUG: about to make an AUTHDATA entry\n" if $self->{'debug'};
        eval {$self->{'masterdb'}->insert('AUTHDATA', \%data)};
        $error .= &check_for_errors($self,$@);
      }
      else { $error .= $result; }
      &remote_iamd_disconnect($self, $iamd);   ## Disconnect from remote IAMd
    }
  }
  else { return &display_errors($self,\@errors); }

  if (! $error ) { &update_translog($self, $uniqueid, 'add_account', $args, 'success'); return $result; }
  else { &update_translog($self, $uniqueid, 'add_account', $args, "failure: $error"); return $error; }

}

sub deactivate_account {
  my $self = shift @_;
  my $args = join(',', map { "'$_'"} @_ );
  my $domain = shift @_;
  my $type = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;

  my @optionalargs = @_;
  
  my @errors;
  my $error;
  my %data;
  $self->{'timestamp'} = time;
  
  if (! $domain) { push @errors, 1500; }
  if (! $type) { push @errors, 1501; }
  if (! $uniqueid) { push @errors, 1101; }
  if (! $username) { push @errors, 1104; }

  if (! eval {$self->{'masterdb'}->select_one_value('DISTINCT UNIQUEID', 'USERDATA', "WHERE UNIQUEID='$uniqueid'", 0)}) { push @errors, 1102; }  ## make sure the uniqueid exists
  if (! eval {$self->{'masterdb'}->select_one_column('DISTINCT USERNAME', 'AUTHDATA', "WHERE DOMAIN='$domain' AND TYPE='$type' AND USERNAME='$username'", 0)}) { push @errors, 1106; }  ## make sure the username exists

  if ($#errors < 0) {
  ($iamd, $pluginname) = &remote_iamd_connect($self, $domain, $type);
    if ($iamd) {
      $result = eval {$iamd->request('remote_deactivate', $pluginname, $uniqueid, $username, @optionalargs)};
      if ($result !~ /^0.*$/) { $error .= $result; }
      $error .= &check_for_errors($self,$@);
      &remote_iamd_disconnect($self, $iamd);   ## Disconnect from remote IAMd
    }
  }
  else { return &display_errors($self,\@errors); }

  if (! $error ) { &update_translog($self, $uniqueid, 'deactivate_account', $args, 'success'); return $result; }
  else { &update_translog($self, $uniqueid, 'deactivate_account', $args, "failure: $error"); return $error; }
}

sub delete_account {
  my $self = shift @_;
  my $args = join(',', map { "'$_'"} @_ );
  my $domain = shift @_;
  my $type = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;

  my @optionalargs = @_;
  
  my @errors;
  my $error;
  $self->{'timestamp'} = time;
  
  if (! $domain) { push @errors, 1500; }
  if (! $type) { push @errors, 1501; }
  if (! $uniqueid) { push @errors, 1101; }
  if (! $username) { push @errors, 1104; }

  if (! eval {$self->{'masterdb'}->select_one_value('DISTINCT UNIQUEID', 'USERDATA', "WHERE UNIQUEID='$uniqueid'", 0)}) { push @errors, 1102; }  ## make sure the uniqueid exists
  if (! eval {$self->{'masterdb'}->select_one_column('DISTINCT USERNAME', 'AUTHDATA', "WHERE DOMAIN='$domain' AND TYPE='$type' AND USERNAME='$username'", 0)}) { push @errors, 1106; }  ## make sure the username exists

  if ($#errors < 0) {
  ($iamd, $pluginname) = &remote_iamd_connect($self, $domain, $type);
    if ($iamd) {
      $result = eval {$iamd->request('remote_delete', $pluginname, $uniqueid, $username, @optionalargs)};
      $error .= &check_for_errors($self,$@);
      if ($result =~ /^0.*$/) {
        eval {$self->{'masterdb'}->delete('AUTHDATA', "WHERE UNIQUEID='$uniqueid' AND DOMAIN='$domain' AND TYPE='$type' AND USERNAME='$username'")};
        $error .= &check_for_errors($self,$@);
      }
      else { $error .= $result; }
      &remote_iamd_disconnect($self, $iamd);   ## Disconnect from remote IAMd
    }
  }
  else { return &display_errors($self,\@errors); }

  if (! $error ) { &update_translog($self, $uniqueid, 'delete_account', $args, 'success'); return $result; }
  else { &update_translog($self, $uniqueid, 'delete_account', $args, "failure: $error"); return $error; }
}

sub display_account {
  my $self = shift @_;
  my $domain = shift @_;
  my $type = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;

  my @optionalargs = @_;
      print "DEBUG: calling remote iamd: display: $pluginname, $uniqueid, $username, @optionalargs\n" if $self->{'debug'};
  
  my @errors;
  my $error;
  my $result;
  
  if (! $domain) { push @errors, 1500; }
  if (! $type) { push @errors, 1501; }
  if (! $uniqueid) { push @errors, 1101; }
  if (! $username) { push @errors, 1104; }

  if (! eval {$self->{'masterdb'}->select_one_value('DISTINCT UNIQUEID', 'USERDATA', "WHERE UNIQUEID='$uniqueid'", 0)}) { push @errors, 1102; }  ## make sure the uniqueid exists
  if (! eval {$self->{'masterdb'}->select_one_column('DISTINCT USERNAME', 'AUTHDATA', "WHERE DOMAIN='$domain' AND TYPE='$type' AND USERNAME='$username'", 0)}) { push @errors, 1106; }  ## make sure the username exists

  if ($#errors < 0) {
    ($iamd, $pluginname) = &remote_iamd_connect($self, $domain, $type);
    if ($iamd) {
      print "DEBUG: calling remote iamd: display: $pluginname, $uniqueid, $username, @optionalargs\n" if $self->{'debug'};
      $result = eval {$iamd->request('remote_display', $pluginname, $uniqueid, $username, @optionalargs)};
      $error .= &check_for_errors($self,$@);
      &remote_iamd_disconnect($self, $iamd);   ## Disconnect from remote IAMd
    }
  }
  else { return &display_errors($self,\@errors); }

  if (! $error ) { return $result; }
  else { return $error; }
}

sub modify_account {
  my $self = shift @_;
  my $args = join(',', map { "'$_'"} @_ );
  my $domain = shift @_;
  my $type = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;

  my @optionalargs = @_;
  
  my @errors;
  my $error;
  my %data;
  $self->{'timestamp'} = time;
  
  if (! $domain) { push @errors, 1500; }
  if (! $type) { push @errors, 1501; }
  if (! $uniqueid) { push @errors, 1101; }
  if (! $username) { push @errors, 1104; }

  if (! eval {$self->{'masterdb'}->select_one_value('DISTINCT UNIQUEID', 'USERDATA', "WHERE UNIQUEID='$uniqueid'", 0)}) { push @errors, 1102; }  ## make sure the uniqueid exists
  if (! eval {$self->{'masterdb'}->select_one_column('DISTINCT USERNAME', 'AUTHDATA', "WHERE DOMAIN='$domain' AND TYPE='$type' AND USERNAME='$username'", 0)}) { push @errors, 1106; }  ## make sure the username exists

  if ($#errors < 0) {
  ($iamd, $pluginname) = &remote_iamd_connect($self, $domain, $type);
    if ($iamd) {
      $result = eval {$iamd->request('remote_modify', $pluginname, $uniqueid, $username, @optionalargs)};
      $error .= &check_for_errors($self,$@);
      if ($result =~ /^0.*$/) {
        if ($optionalargs[0]) { $data{UNIQUEID} = $optionalargs[0]; }     # uniqueid change
        if ($optionalargs[1]) { $data{USERNAME} = $optionalargs[1]; }     # username change
        if ($optionalargs[2]) { 
          use Crypt::PasswdMD5;
          use String::Random;
          $rand = new String::Random;
          $salt = $rand->randpattern("ssssssss");

          $finalpassword = unix_md5_crypt($optionalargs[2], $salt); 
          $data{USERPASSWORD} = $finalpassword; }                             # plain password now crypted change
        elsif ($optionalargs[3]) { $data{PASSWORD} = $optionalargs[3]; }  # crypted password change
        if (%data) {eval {$self->{'masterdb'}->update('AUTHDATA', \%data, "WHERE UNIQUEID='$uniqueid' AND DOMAIN='$domain' AND TYPE='$type' AND USERNAME='$username'")};}
        $error .= &check_for_errors($self,$@);
      }
      else { $error .= $result; }
      &remote_iamd_disconnect($self, $iamd);   ## Disconnect from remote IAMd
    }
  }
  else { return &display_errors($self,\@errors); }

  if (! $error ) { &update_translog($self, $uniqueid, 'modify_account', $args, 'success'); return $result; }
  else { &update_translog($self, $uniqueid, 'modify_account', $args, "failure: $error"); return $error; }
}

1;

__END__