#!/usr/bin/perl
package xtradius;


# 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
###########################################################################
# This module is a plugin for IAM to handle xtradius
#

## User defined variables
#############################################################
my $driver = "mysql";
my $database = "Radius";
my $hostname = "localhost";
my $port = "3306";
my $user = "xtadmin";
my $password = 'mysqluserpass';
my $table = "auth";
my $uniqueidfield = "uniqueid";
my $usernamefield = "user";
my $passwordfield = "pass";
my $servicefield = "service_type";
my $staticipfield = "ip";
my $staticnetmaskfield = "netmask";
my $protocolfield = "proto";
my $numberofportsfield = "ports";
my $inactivetimeoutfield = "timeout";
my $extraattrfield = "radius_attr";
#my $calledfromfield = "called";
my $nasoveridefield = "override";
my $activefield = "active";
my $strictfield = "phfrom_strict";
my $callfromlistfield = "phfrom_list";

my @fields = ($usernamefield,$passwordfield,$servicefield,$staticipfield,$staticnetmaskfield,$protocolfield,$numberofportsfield,$inactivetimeoutfield,$extraattrfield,$nasoveridefield,$activefield,$strictfield,$callfromlistfield);
#############################################################

use DBIx::Broker;
#use strict;

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

sub new {
  my $class = shift @_;
  my $self = {};
  
  $self->{'authdb'} = eval {DBIx::Broker->new("$driver", "$database", "$hostname", "$port", "$user", "$password")};
  if ( ! $self->{'authdb'} ) { return "0 Could not open database $database!\n";
  }    
  bless ( $self, $class );
}

sub close {
  my $self = shift @_;

  eval { $self->{'authdb'}->disconnect(); }
}

sub debug {
  my $self = shift @_;

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

sub activate_fields {
  my $self = shift @_;

  my $requiredfields = "name=>uniqueid,desc=>Unique Identifier,minlen=>0,maxlen=>64,optional=>0
                        name=>username,desc=>Username,minlen=>4,maxlen=>64,optional=>0";

  return "0 ".$requiredfields;
}

sub activate {
  my $self = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;

  my @errors;

  if (! $uniqueid) { push @errors, 100; }
  if (! $username) { push @errors, 101; }

  if (! $self->{'authdb'}) { push @errors, 202; }

  if ($#errors < 0) {
    my %data;
    $data{"$activefield"} = 1;
    if (%data) { eval {$self->{'authdb'}->update("$table", \%data, "WHERE $uniqueidfield='$uniqueid' AND $usernamefield='$username'")}; }
    return &check_for_errors($self, $@);
  }
  else {
    return &display_errors($self, \@errors);
  }
}

sub add_fields {
  my $self = shift @_;
  
  my $requiredfields = "name=>uniqueid,desc=>Unique Identifier,minlen=>0,maxlen=>64,optional=>0
                        name=>username,desc=>Username,minlen=>4,maxlen=>64,optional=>0
                        name=>password,desc=>User Password,minlen=>4,maxlen=>64,optional=>1
                        name=>cryptedpassword,desc=>Already Crypted User Password,minlen=>4,maxlen=>64,optional=>1
                        name=>service,desc=>Service Type,minlen=>0,maxlen=>256,optional=>0
                        name=>ip,desc=>Static IP,minlen=>7,maxlen=>15,optional=>1
                        name=>netmask,desc=>Netmask for Static IP,minlen=>7,maxlen=>15,optional=>1
                        name=>proto,desc=>Protocol IE: PPP MPP etc.,minlen=>0,maxlen=>64,optional=>1
                        name=>ports,desc=>Number of ports user can use at once IE: 2 for ISDN,minlen=>0,maxlen=>64,optional=>1
                        name=>timeout,desc=>Inactivity Timeout time,minlen=>0,maxlen=>64,optional=>1
                        name=>genattr,desc=>Extra attributes radius will send to the RAS,minlen=>0,maxlen=>64,optional=>1
                        name=>nasovr,desc=>Override naslist and allow user to connect anywhere (0 or 1),minlen=>0,maxlen=>1,optional=>1
                        name=>active,desc=>Is the user active? (0 or 1),minlen=>0,maxlen=>1,optional=>1
                        name=>strict,desc=>Restrict the user to a list of phone numbers they can call from? (0 or 1),minlen=>0,maxlen=>1,optional=>1
                        name=>phlist,desc=>Comma sep list of phone numbers user can dial in from (no dashes in number),minlen=>0,maxlen=>64,optional=>1\n";
  return "0 ".$requiredfields;
}

sub add {
  my $self = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;
  my $password = shift @_;
  print "DEBUG: received password: $password\n" if $self->{'debug'};
  my $cryptedpassword = shift @_;
  print "DEBUG: received cryptedpassword: $cryptedpassword\n" if $self->{'debug'};
  my $service = shift @_;
  my $ip = shift @_;
  my $netmask = shift @_;
  my $proto = shift @_;
  my $ports = shift @_;
  my $timeout = shift @_;
  my $genattr = shift @_;
  my $nasovr = shift @_;
  my $active = shift @_;
  my $strict = shift @_;
  my $phlist = shift @_;

  my @errors;
  
  
  if (! $uniqueid) { push @errors, 100; }
  if (! $username) { push @errors, 101; }
  if (! $password && ! $cryptedpassword) { push @errors, 102; }

  @allusernames = eval {$self->{'authdb'}->select_one_column($usernamefield, $table, "WHERE $servicefield='$service'", 0)};
  if (grep /^$username$/, @allusernames) { push @errors, 200; }

  if (! $nasovr) { $nasovr = 0; }
  if (! $active) { $active = 1; }
  if (! $strict) { $strict = 0; }

  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; }

    my %data = ("$uniqueidfield", $uniqueid,
                "$usernamefield", $username,
                "$passwordfield", $finalpassword,
                "$servicefield", $service,
                "$staticipfield", $ip,
                "$staticnetmaskfield", $netmask,
                "$protocolfield", $proto,
                "$numberofportsfield", $ports,
                "$inactivetimeoutfield", $timeout,
                "$extraattrfield", $genattr,
                "$nasoveridefield", $nasovr,
                "$activefield", $active,
                "$strictfield", $strict,
                "$callfromlistfield", $phlist,
               );
    eval {$self->{'authdb'}->insert("$table", \%data)};
    return &check_for_errors($self, $@);
    #return 0;
  }
  else {
    return &display_errors($self, \@errors);
  }
}

sub deactivate_fields {
  my $self = shift @_;

  my $requiredfields = "name=>uniqueid,desc=>Unique Identifier,minlen=>0,maxlen=>64,optional=>0
                        name=>username,desc=>Username,minlen=>4,maxlen=>64,optional=>0";

  return "0 ".$requiredfields;
}

sub deactivate {
  my $self = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;

  my @errors;

  if (! $uniqueid) { push @errors, 100; }
  if (! $username) { push @errors, 101; }

  if (! $self->{'authdb'}) { push @errors, 202; }

  if ($#errors < 0) {
    my %data;
    $data{"$activefield"} = 0;
    if (%data) { eval {$self->{'authdb'}->update("$table", \%data, "WHERE $uniqueidfield='$uniqueid' AND $usernamefield='$username'")}; }
    return &check_for_errors($self, $@);
  }
  else {
    return &display_errors($self, \@errors);
  }
}

sub modify_fields {
  my $self = shift @_;
  
  my $requiredfields = "name=>uniqueid,desc=>Unique Identifier,minlen=>0,maxlen=>64,optional=>1
                        name=>username,desc=>Username,minlen=>4,maxlen=>64,optional=>1
                        name=>newuniqueid,desc=>New Unique Identifier,minlen=>0,maxlen=>64,optional=>1
                        name=>newusername,desc=>New Username,minlen=>4,maxlen=>64,optional=>1
                        name=>newpassword,desc=>New Plain Text User Password,minlen=>4,maxlen=>64,optional=>1
                        name=>newcryptedpassword,desc=>New Crypted User Password,minlen=>4,maxlen=>64,optional=>1
                        name=>service,desc=>Service Type,minlen=>0,maxlen=>256,optional=>0
                        name=>ip,desc=>Static IP,minlen=>7,maxlen=>15,optional=>1
                        name=>netmask,desc=>Netmask for Static IP,minlen=>7,maxlen=>15,optional=>1
                        name=>proto,desc=>Protocol IE: PPP MPP etc.,minlen=>0,maxlen=>64,optional=>1
                        name=>ports,desc=>Number of ports user can use at once IE: 2 for ISDN,minlen=>0,maxlen=>64,optional=>1
                        name=>timeout,desc=>Inactivity Timeout time,minlen=>0,maxlen=>64,optional=>1
                        name=>genattr,desc=>Extra attributes radius will send to the RAS,minlen=>0,maxlen=>64,optional=>1
                        name=>nasovr,desc=>Override naslist and allow user to connect anywhere (0 or 1),minlen=>0,maxlen=>1,optional=>1
                        name=>active,desc=>Is the user active? (0 or 1),minlen=>0,maxlen=>1,optional=>1
                        name=>strict,desc=>Restrict the user to a list of phone numbers they can call from? (0 or 1),minlen=>0,maxlen=>1,optional=>1
                        name=>phlist,desc=>Comma sep list of phone numbers user can dial in from (no dashes in number),minlen=>0,maxlen=>64,optional=>1\n";
  return "0 ".$requiredfields;
}

sub modify {
  my $self = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;
  my $newuniqueid = shift @_;
  my $newusername = shift @_;
  my $newpassword = shift @_;
  my $newcryptedpassword = shift @_;
  my $service = shift @_;
  my $ip = shift @_;
  my $netmask = shift @_;
  my $proto = shift @_;
  my $ports = shift @_;
  my $timeout = shift @_;
  my $genattr = shift @_;
  my $nasovr = shift @_;
  my $active = shift @_;
  my $strict = shift @_;
  my $phlist = shift @_;

  my @errors;
  
  if (! $uniqueid) { push @errors, 100; }
  if (! $username) { push @errors, 101; }
  
  if (($newusername) && ($newusername ne $username)) {
    @allusernames = eval {$self->{'authdb'}->select_one_column($usernamefield, $table, "", 0)};
    if (! grep /^$username$/, @allusernames) { push @errors, 201; }
    if (grep /^$newusername$/, @allusernames) { push @errors, 200; }
  }
  else { undef $newusername; }
  
  if ($#errors < 0) {
    if ($newpassword) { 
      use Crypt::PasswdMD5;
      use String::Random;
      $rand = new String::Random;
      $salt = $rand->randpattern("ssssssss");
      
      $finalpassword = unix_md5_crypt($newpassword, $salt); 
    }
    elsif ($newcryptedpassword) { $finalpassword = $newcryptedpassword; }

    my %data;
    if ($newuniqueid) { $data{"$uniqueidfield"} = $newuniqueid; }
    if ($newusername) { $data{"$usernamefield"} = $newusername; }
    if ($newpassword) { $data{"$passwordfield"} = $finalpassword; }
    if ($service) { $data{"$servicefield"} = $service; }
    if ($ip) { $data{"$staticipfield"} = $ip; }
    if ($netmask) { $data{"$staticnetmaskfield"} = $netmask; }
    if ($proto) { $data{"$protocolfield"} = $proto; }
    if ($ports) { $data{"$numberofportsfield"} = $ports; }
    if ($timeout) { $data{"$inactivetimeoutfield"} = $timeout; }
    if ($genattr) { $data{"$extraattrfield"} = $genattr; }
    if ($nasovr) { $data{"$nasoveridefield"} = $nasovr; }
    if ($active) { $data{"$strictfield"} = $active; }
    if ($phlist) { $data{"$callfromlistfield"} = $phlist; }

    if (%data) { eval {$self->{'authdb'}->update("$table", \%data, "WHERE $uniqueidfield='$uniqueid' AND $usernamefield='$username'")}; }
    return &check_for_errors($self, $@);
    #return 0;
  }
  else {
    return &display_errors($self, \@errors);
  }
}

sub delete_fields {
  my $self = shift @_;
  
  my $requiredfields = "name=>uniqueid,desc=>Unique Identifier,minlen=>0,maxlen=>64,optional=>1
                        name=>username,desc=>Username,minlen=>4,maxlen=>64,optional=>1\n";
  return "0 ".$requiredfields;
}

sub delete {
  my $self = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;
  
  my @errors;

  if (! $uniqueid) { push @errors, 100; }
  if (! $username) { push @errors, 101; }

  @allusernames = eval {$self->{'authdb'}->select_one_column($usernamefield, $table, "", 0)};
  if (! grep /^$username$/, @allusernames) { push @errors, 201; }

  if ($#errors < 0) {
    eval {$self->{'authdb'}->delete("$table", "WHERE $uniqueidfield='$uniqueid' AND $usernamefield='$username'")};
    return &check_for_errors($self, $@);
  }
  else {
    return &display_errors($self, \@errors);
  }
  
}

sub display {
  my $self = shift @_;
  my $uniqueid = shift @_;
  my $username = shift @_;
  
  my @errors;

  if (! $uniqueid) { push @errors, 100; }
  if (! $username) { push @errors, 101; }

  $accountinfo = eval {$self->{'authdb'}->select_one_row(\@fields, $table, "WHERE $uniqueidfield='$uniqueid' AND $usernamefield='$username'", 1)};
  if (&check_for_errors($self, $@)) { push @errors, 301; }
  my $result = '';
  foreach $field (@fields) { $result .= "$field=>$accountinfo->{$field},"; }
  $result =~ s/\,$//;
  #my $result = "Username=>$accountinfo->{$usernamefield},Password=>$accountinfo->{$passwordfield},Used Quota=>$quota[1],Quota=>$quota[2]";

  if ($#errors < 0) {
    return "0 ".$result;
  }
  else {
    return &display_errors($self, \@errors);
  }
}

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

  if ($error) {
    print STDERR "Error: $error\n";
    return "1 Unknown error occured, inform administrator\n";
  }                                                                                                                                        
  else { return 0; }
}

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 == 100) { $errmsg .= "Must specify a 'uniqueid'"; }
    elsif ($error == 101) { $errmsg .= "Must specify a 'username'"; }
    elsif ($error == 102) { $errmsg .= "Must specify a 'password'"; }
    
    elsif ($error == 200) { $errmsg .= "Duplicate Username!"; }
    elsif ($error == 201) { $errmsg .= "Username doesnt exist!"; }

    elsif ($error == 300) { $errmsg .= "Couldnt retreive current password!"; }
    elsif ($error == 301) { $errmsg .= "Couldnt retreive user info!"; }
    
    else { $errmsg .= "Unknown error"; }
    $errmsg .= "\n";
  }
  return $errmsg;
}


1;

__END__
