#!/usr/bin/perl
# NcFTPd MySQL Authentication Daemon
# Author: Martin May
# Date: August 18, 2000
# License: GNU GPL http://www.fsf.org/copyleft/gpl.html
#    Copyright (C) 2000 Martin May
#
#    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
#
#
# Version: 1.0
#
# TODO: Implement logging
#       Implement IP address restrictions
#

use strict;
use IO::Socket;
use DBI;


#-----------------------------------------#
#                                         #
#  Configuration starts here              #
#                                         #
#-----------------------------------------#

my $PORTNO = 9723;
my $KEY    = "\177\377\376\377";
my $DEBUG  = 0;

my $DBNAME = 'ServiceAuth';
my $DBUSER = 'root';
my $DBPASS = '';

my $DBTABLE = 'tblUser';

my $DBUSERFIELD = 'idUser';
my $DBPASSWORDFIELD = 'dtPassword';
my $DBUIDFIELD = 'dtUID';
my $DBGIDFIELD = 'dtGID';
my $DBHOMEDIRFIELD = 'dtHomedir';
my $DBRESTRICTEDFIELD = 'dtRestricted';
my $DBQUOTAFILESFIELD = 'dtQuotaFiles';
my $DBQUOTAKBFIELD = 'dtQuotaKB'; 

#-----------------------------------------#
#                                         #
#  Configuration ends here                #
#                                         #
#-----------------------------------------#


my $MAXLEN = 1900;
my($sock, $oldmsg, $newmsg, $hisaddr, $hishost, $decoded, $reply);
my($mType,$state,$duplicate,$rAddr,$hostname,$ipstr,$lAddr,$cfname,$connectTime,$username,$passPrompt,$passwd,$uid,@gids,$ngids,$restricteduser,$homedir,$denied,$allowed,$quotaHardkB,$quotaSoftkB,$quotaHardNumFiles,$quotaSoftNumFiles,$quotaFlags,$ncftpdPrivate,$authdPrivate);
my($rPort,$rAddrIP,$lPort,$lAddrIP);

$sock = IO::Socket::INET->new(LocalPort => $PORTNO, Proto => 'udp')
        or die "socket: $@";

print "Awaiting NcFTPd AUTH messages on port $PORTNO\n";

while ($sock->recv($newmsg, $MAXLEN)) {
    my($port, $ipaddr) = sockaddr_in($sock->peername);
    $hishost = gethostbyaddr($ipaddr, AF_INET);
    
    # Decode the message with XOR
    $decoded = scramble($newmsg);

    #my @asciichars = unpack("C*", $decoded);
    #print "Decoded: @asciichars\n\n"; 
    
    # Unpack the authentication message
    ($mType,$state,$duplicate,$rAddr,$hostname,$ipstr,$lAddr,$cfname,$connectTime,$username,$passPrompt,$passwd,$uid,$gids[0],$gids[1],$gids[2],$gids[3],$gids[4],$gids[5],$gids[6],$gids[7],$gids[8],$gids[9],$gids[10],$gids[11],$gids[12],$gids[13],$gids[14],$gids[15],$gids[16],$gids[17],$gids[18],$gids[19],$gids[20],$gids[21],$gids[22],$gids[23],$gids[24],$gids[25],$gids[26],$gids[27],$gids[28],$gids[29],$gids[30],$gids[31],$gids[32],$ngids,$restricteduser,$homedir,$denied,$allowed,$quotaHardkB,$quotaSoftkB,$quotaHardNumFiles,$quotaSoftNumFiles,$quotaFlags,$ncftpdPrivate,$authdPrivate) = unpack('N N N a16 a64 a32 a16 a64 N a64 a256 a64 N N33 N N  a128 N N N N N N N a256 a256',$decoded);

    # Unpack sockaddr_in structures
    #($rPort, $rAddrIP) = unpack_sockaddr_in($rAddr);
    #$rAddrIP = inet_ntoa($rAddrIP);
    #($lPort, $lAddrIP) = unpack_sockaddr_in($lAddr);
    #$lAddrIP = inet_ntoa($lAddrIP);

    if ($DEBUG == 1) {
      print "Processing AUTH Message\n---------------------\n\n";
      print "MType:      $mType\n";
      print "State:      $state\n";
      print "Duplicate:  $duplicate\n";
      #print "Remote IP:  $rAddrIP:$rPort\n";
      print "Hostname:   $hostname\n";
      print "IPstr:      $ipstr\n";
      #print "Local IP:   $lAddrIP:$lPort\n";
      print "CFname:     $cfname\n";
      print "ConnectT:   " . localtime($connectTime) . "\n";
      print "Username:   $username\n";
      print "Passprompt: $passPrompt\n";
      print "Password:   $passwd\n";
      print "UID:        $uid\n";
      print "GIDS:       @gids\n";
      print "nGIDS:      $ngids\n";
      print "Restricted: $restricteduser\n";
      print "Homedir:    $homedir\n\n";
    }  

    # Authenticate the user
    ($allowed,$homedir,$uid,$gids[0],$restricteduser,$quotaHardNumFiles,$quotaHardkB) = authenticate($username,$passwd) or die "Could not authenticate";;

    $state = 6;
    $ngids = 1;

    if ($DEBUG == 1) {
      print "Response:\n\n";
      print "MType:      $mType\n";
      print "State:      $state\n";
      print "Duplicate:  $duplicate\n";
      #print "Remote IP:  $rAddrIP:$rPort\n";
      print "Hostname:   $hostname\n";
      print "IPstr:      $ipstr\n";
      #print "Local IP:   $lAddrIP:$lPort\n";
      print "CFname:     $cfname\n";
      print "ConnectT:   " . localtime($connectTime) . "\n";
      print "Username:   $username\n";
      print "Passprompt: $passPrompt\n";
      print "Password:   $passwd\n";
      print "UID:        $uid\n";
      print "GIDS:       @gids\n";
      print "nGIDS:      $ngids\n";
      print "Restricted: $restricteduser\n";
      print "Homedir:    $homedir\n\n";
    }  

    # Pack the authentication message
    $reply = pack 'N N N a16 a64 a32 a16 a64 N a64 a256 a64 N N33 N N  a128 N N N N N N N a256 a256', ($mType,$state,$duplicate,$rAddr,$hostname,$ipstr,$lAddr,$cfname,$connectTime,$username,$passPrompt,$passwd,$uid,$gids[0],$gids[1],$gids[2],$gids[3],$gids[4],$gids[5],$gids[6],$gids[7],$gids[8],$gids[9],$gids[10],$gids[11],$gids[12],$gids[13],$gids[14],$gids[15],$gids[16],$gids[17],$gids[18],$gids[19],$gids[20],$gids[21],$gids[22],$gids[23],$gids[24],$gids[25],$gids[26],$gids[27],$gids[28],$gids[29],$gids[30],$gids[31],$gids[32],$ngids,$restricteduser,$homedir,$denied,$allowed,$quotaHardkB,$quotaSoftkB,$quotaHardNumFiles,$quotaSoftNumFiles,$quotaFlags,$ncftpdPrivate,$authdPrivate);

    #@asciichars = unpack("C*", $reply);
    #print "Decoded: @asciichars\n\n"; 

    # Code the message with XOR
    $reply = scramble($reply);

    # Send the reply
    $sock->send($reply);
} 

# scramble -- scramble a message by XOR'ing it with $KEY
sub scramble {
  my ($orig) = @_;
  my @array = unpack("C*", $orig);
  my @key = unpack("C*", $KEY);
  my $j = 0;
  my $i;
  for ($i = 0;$i < scalar(@array);$i++) {
    $array[$i] = $array[$i] ^ $key[$j];
      
    if ($j < (scalar(@key)-1)) {
      $j++;
    } else {
      $j = 0;
    }
  }
  my $decoded = pack("C*", @array);
  return $decoded;
}

# authenticate -- authenticate the user via a mysql database
sub authenticate {
  my ($user,$pass) = @_;

  $user =~ s/\x00+$//;
  $pass =~ s/\x00+$//;

  my ($allowed,$uid,$gid,$homedir,$restricteduser,$quotaHardNumFiles,$quotaHardkB);
  $allowed = 0;

  my $dbh = sqlconnect($DBNAME,$DBUSER,$DBPASS);

  my $query = "SELECT $DBUIDFIELD,$DBGIDFIELD,$DBHOMEDIRFIELD,$DBRESTRICTEDFIELD,$DBQUOTAFILESFIELD,$DBQUOTAKBFIELD FROM $DBTABLE WHERE $DBUSERFIELD = '" . $user . "' AND $DBPASSWORDFIELD = '" . $pass . "'";

  my $sth = $dbh->prepare($query);

  my $results = $sth->execute;
          
  if ($results == 1) {
    $allowed = 1;
    ($uid,$gid,$homedir,$restricteduser,$quotaHardNumFiles,$quotaHardkB) = $sth->fetchrow;
  }

  $sth->finish;

  sqldisconnect($dbh);
  return ($allowed,$homedir,$uid,$gid,$restricteduser,$quotaHardNumFiles,$quotaHardkB);
}

# sqlconnect -- connect to the database
sub sqlconnect {
   my ($database,$user,$pass) = @_;
   my $dbh = DBI->connect("DBI:mysql:$database", "$user", "$pass");

   if ( !defined $dbh ) {
      error("Cannot do \$dbh->connect: $DBI::errstr\n");
   }
   return $dbh;
}

# sqldisconnect -- disconnect from the database
sub sqldisconnect {
   my ($dbh) = @_;
   $dbh->disconnect;
   return $dbh;
}
