Wednesday, January 28, 2009

KOHA LDAP Authentication

Previously I make some troubleshooting for this 1 college. They are using KOHA as their Library System. My tasks were to established authentication connection between KOHA & LDAP server. The tricky part where the user account at LDAP server using different ou(Organizational Unit) some. Current LDAP module (Auth-with-ldap.PM) bundled with KOHA unable to counter this issue. So I come out with new checkpw class. That to the following process:
  • it connects to LDAP using admin account(or account that can search for userid availablein LDAP server)
  • search base on user uid
  • retrive cn
  • compare password
  • then get the LDAP entry
  • and calls the memberadd if necessary
and here the perl code:
# this checkpw is a LDAP based one
# it connects to LDAP using admin account
# search base on user uid
# retrive cn
# compare password
# then get the LDAP entry
# and calls the memberadd if necessary
sub checkpw {
 use Net::LDAP;
 my ($dbh, $userid, $password, $authtype) = @_;
 if ($userid eq C4::Context->config('user') && $password eq C4::Context->config('pass')) {
  # KOHA superuser account
  return 2;
 }
 ################################edited by mbek for KOHA@SOMEPROJECT###########################################
 ### LOCAL
 ### Change the code below to match your own LDAP server.
 ##################################################
 # LDAP connexion parameters
 # LDAP server
 my $ldapserver = '192.168.0.12'; #ur LDAP server's IP/hostname
 my $ldapadmin = 'ldapadminaccount';
 my $ldappassword = 'ldapadminpassword';
 # Base DN for users
 #my $name = "ou=users,dc=tow,dc=net";
 my $name = "cn=$ldapadmin,dc=sub,dc=domain,dc=edu,dc=my";
 my $searchbase = "dc=sub,dc=domain,dc=edu,dc=my";
 my $checkname = "ou=People,dc=sub,dc=domain,dc=edu,dc=my";
 # Bind uses the users full DN, if uid doesn't work try "cn"
 my $binddn = "$name";
 # my $binddn = "uid=$userid,$name";
 my $db = Net::LDAP->new( $ldapserver );
 # do bind
 my $res =$db->bind();
 # check connexion, anything other code than LDAP_SUCCESS (0)
 # is a problem
 if($res->code != 0 ) {
  # auth refused
  warn
  #die
  "LDAP Auth failed server not responding or wrong user password combination";
  return 0;
   
 }else {
  # search user
  my $userdnsearch = $db->search(
   base => "$searchbase",
   filter =>"(uid=$userid)",
  );
  my $userldapentry=$userdnsearch -> shift_entry;
  # build LDAP hash
  my %memberhash;
  my $x =$userldapentry->{asn}{attributes};
  my $key;
  foreach my $k ( @$x) {
   foreach my $k2 (keys %$k) {
    if ($k2 eq 'type') {
     $key = $$k{$k2};
    } else {
     my $a = @$k{$k2};
     foreach my $k3 (@$a) {
      $memberhash{$key} .= $k3."";
     }
    }
   }
  }
  $res = $db->unbind;
  my %borrower;
  if($memberhash{cn}){
   my $newldap = Net::LDAP->new( "$ldapserver" , timeout=>60) or die "$@";
   $checkname = "cn=$memberhash{cn}," . $checkname;
   my $msg = $newldap->bind($checkname, password => $password);
   my $status = $msg->code;
   
   if($msg->code != 0 ) {
    warn "LDAP Auth failed server not responding or wrong user password combination"; 
     return 0; 
   }else{
    $msg = $newldap->search(
    base => "$searchbase",
    filter =>"(uid=$userid)",
    );
    my $userldapentry=$msg -> shift_entry;
    # build LDAP hash
    my %validhash;
    my $x =$userldapentry->{asn}{attributes};
    my $key;
    foreach my $k ( @$x) {
     foreach my $k2 (keys %$k) {
     if ($k2 eq 'type') {
      $key = $$k{$k2};
     } else {
      my $a = @$k{$k2};
      foreach my $k3 (@$a) {
       $validhash{$key} .= $k3." ";
      }
      }
     }
    }
    #
    # BUILD %borrower to CREATE or MODIFY BORROWER
    # change $memberhash{'xxx'} to fit your ldap structure.
    # check twice that mandatory fields are correctly filled
    #
    $borrower{cardnumber} = $userid;
    $borrower{firstname} = $validhash{givenName}; # MANDATORY FIELD
    $borrower{surname} = $validhash{sn}; # MANDATORY FIELD
    $borrower{initials} = substr($borrower{firstname},0,1).substr($borrower{surname},0,1)." "; # MANDATORY FIELD
    $borrower{streetaddress} = $validhash{homePostalAddress}." "; # MANDATORY FIELD
    $borrower{city} = $validhash{l}." "; # MANDATORY FIELD
    $borrower{phone} = $validhash{homePhone}." "; # MANDATORY FIELD
    $borrower{branchcode} = $validhash{businessCategory}; # MANDATORY FIELD
    $borrower{emailaddress} = $validhash{mail};
    $borrower{categorycode} = $validhash{employeeType};
   }
  }else{
   warn "LDAP Auth failed server not responding or wrong user password combination"; 
    return 0;
  }
  ################################End edited by mbek for KOHA@SOMEPROJECT###########################################
  ##################################################
  ### /LOCAL
  ### No change needed after this line (unless there's a bug ;-) )
  ##################################################
  # check if borrower exists
  my $sth=$dbh->prepare("select password from borrowers where cardnumber=?");
  $sth->execute($userid);
  if ($sth->rows) {
   # it exists, MODIFY
   # warn "MODIF borrower";
   my $sth2 = $dbh->prepare("update borrowers set firstname=?,surname=?,initials=?,streetaddress=?,city=?,phone=?, categorycode=?,branchcode=?,emailaddress=?,sort1=? where cardnumber=?");
   $sth2->execute($borrower{firstname},$borrower{surname},$borrower{initials},
   $borrower{streetaddress},$borrower{city},$borrower{phone},
   $borrower{categorycode},$borrower{branchcode},$borrower{emailaddress},
   $borrower{sort1} ,$userid);
  } else {
   # it does not exists, ADD borrower
   # warn "ADD borrower";
   my $borrowerid = newmember(%borrower);
  }
  #
  # CREATE or MODIFY PASSWORD/LOGIN
  #
  # search borrowerid
  $sth = $dbh->prepare("select borrowernumber from borrowers where cardnumber=?");
  $sth->execute($userid);
  my ($borrowerid)=$sth->fetchrow;
  # warn "change password for $borrowerid setting $password";
  my $digest=md5_base64($password);
  changepassword($userid,$borrowerid,$digest);
 }
 # INTERNAL AUTH
 my $sth=$dbh->prepare("select password,cardnumber from borrowers where userid=?");
 $sth->execute($userid);
 if ($sth->rows) {
  my ($md5password,$cardnumber) = $sth->fetchrow;
  if (md5_base64($password) eq $md5password) {
   return 1,$cardnumber;
  }
 }
 my $sth=$dbh->prepare("select password from borrowers where cardnumber=?");
 $sth->execute($userid);
 if ($sth->rows) {
  my ($md5password) = $sth->fetchrow;
  if (md5_base64($password) eq $md5password) {
   return 1,$userid;
  }
 }
 return 0;
}

happy coding

**Gong Xi Fa Cai

Friday, January 9, 2009

Joomla replication script

back to 2006/2007, 
again, my colleage handle this 1 project that require him to do more than 100 site using joomla.
our server spec:
OS : windows
Web Server : Sambar Server 7.0
Perl : 5.6
so i come out with this script.
#!/usr/bin/perl
$| = 1;
########################################
##use at ur own risk
##mbek@tuk_kambing
##me@mbek.net
##www.mbek.net, hairulmoktar.blogspot.com
########################################
use CGI::Carp qw(fatalsToBrowser set_message);
BEGIN {sub handle_errors { my $msg = shift;
print qq|<html><head><title>Server Error</title></head></html><body bgcolor=#ffffff leftmargin=8 topmargin=8 marginwidth=8 marginheight=8><font face=verdana color=#336699>
<h1><font color=red>Opss!! Server Error!</font></h1><div style="border: 1px solid #666666;background-color: #ffff00;padding: 10px;">$msg<p>Please inform <A HREF=mailto:my\@email.com>mbek</A> (my\@email.com)detail about about this error.</div></body></html>
<!--powered by mbek@tuk_kambing-->
\n|;exit;}
set_message(\&handle_errors);}
########################################
use CGI;
use File::Copy;
use DBI;
my $req = new CGI;
$vhostsloc = "/path/to/sambar70/config/vhosts.ini";
$rootdir = "/path/to/mainfolder";
$rooturl = "http://$ENV{HTTP_HOST}";
$adminemail = "admin\@somehost.com";
$db_user = "mysqlusername";
$db_password = "mysqlpassword";
$dsn = "DBI:mysql:database=joomla;host=localhost;port=3306";
print "Content-type : text/html\n\n";
print qq~
<html>
<style>
body,td,p,th,input,select{
font-family: verdana;
font-size: 10px;
}
</style>
<body>~;
if($req->param('do') eq 'docopy'){
&docopy();
}elsif($req->param('do') eq 'joomla'){
&joomla();
}elsif($req->param('do') eq 'dump'){
&dumps();
}else{
&main();
}
print qq~
</body>
</html>~;
sub main{
my %VHOSTSEXIST;
open(VHOSTS,"$vhostsloc");
my @vhostslines = <VHOSTS>;
close VHOSTS;
#print "<ol>";
foreach(@vhostslines){
$_ =~ s/[\n\r]//g;
if($_ =~ /^\[(.*?)\]/ && $_ !~ /^\[\*/){
my $hosts = $1;
if($hosts =~ /my1stopweb.com/i){
# print "<li>$hosts";
# print " : <font color=\"#ff0000\">more than 1</font>" if $VHOSTSEXIST{$hosts};
$VHOSTSEXIST{$hosts} = "$hosts";
}
}
}
#print "</ol>";
my $listofdatabases = '';
$dbh = DBI->connect($dsn,$db_user,$db_password) or die $!;
my $sth = $dbh->prepare("show databases");
$sth->execute() or die qq|Database Error:  $DBI::errstr $query|;
while (my @dbrows =  $sth->fetchrow_array ){
push @databases,"$dbrows[0]";
$listofdatabases .= qq~<option value="$dbrows[0]">$dbrows[0]</option>~ if $dbrows[0] =~ /^joomla_/i;
}
$sth->finish;
$dbh->disconnect;
my $select_sources = '';
opendir SUBDIR, "$rootdir" or die "$rootdir doesnt exist";
my @sourceslist = grep !/^\.\.?$/, readdir SUBDIR;
foreach (@sourceslist){
if(-d "$rootdir/$_"){
$select_sources .= qq~<option value="$_">$_</option>~;
if($_ !~ /old/i && $_ !~ /joomla/i){
$listeddir .= qq~<li>$_~;
$listeddir_subdomain .= qq~<li><a href="http://$_\.my1stopweb.com" target="_blank">http://$_\.my1stopweb.com</a>~;
$listeddir_email .= qq~<li>$_\@my1stopweb.com ~;
$listeddir_subdomain_vhost .= qq~[$_.my1stopweb.com]
Automatic Log mailto = 
CGI Directory = /cgi-bin/
CGI Run As = 
Default Page = index.php index.htm index.html index.stm index.asp index.pl index.cgi
Documents Directory = f:/site/my1stopweb/$_/
Log File = vhost-$_.my1stopweb.com.log
Messages Directory = messages
Tmp Directory = tmp
WEB-INF Directory = 
WinCGI Directory = /cgi-win/
~ if !$VHOSTSEXIST{$_.".my1stopweb.com"};
}
}
}
closedir SUBDIR;
print qq~
<form>
<table>
<tr>
<td>
<li>source : <select name="sources">$select_sources</select>
<li>destination : <input type="text" name="destination" value="" onkeyup="this.form.mosConfig_db.value='joomla_' + this.value;">
<li><input type="checkbox" name="replacefolder" value="1"> continue if <b>Folder</b> Exist ??
<ul>
<li><input type="checkbox" name="replacefile" value="1"> Replace <b>Existing</b> files ?? (only valid if <i>Replace <b>Folder</b> Exist</i> checked)
</ul>
</td>
</tr><tr>
<td>
** <b>optional</b>
<table border=1>
<tr>
<td><input type="checkbox" name="doconfig" value="1"></td>
<td><b>Joomla General Configuration</b></td>
</tr>
<tr>
<td> </td>
<td>
<li>Joomla Name : <input type="text" name="mosConfig_sitename" value="New Website | SMEXchange" size="50">
<li>Installation Folder : <input type="text" name="installfolder" value="installation">
<p>
<li>DB Server : <input type="text" name="mosConfig_host" value="localhost">
<li>username : <input type="text" name="mosConfig_user" value="my1stopweb">
<li>pasword : <input type="text" name="mosConfig_password" value="">
<li>Database : <input type="text" name="mosConfig_db" value="joomla_">
</td>
</tr>
<tr>
<td> </td>
<td>
<table border=0>
<tr>
<td><input type="radio" name="dbused" value="joomla" checked></td>
<td>Joomla</td>
</tr>
<tr>
<td><input type="radio" name="dbused" value="copy"></td>
<td>Copy Existing Databse</td>
</tr>
<tr>
<td> </td>
<td><select name="copyfromdb">$listofdatabases</select></td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr><tr>
<td >
<input type="checkbox" name="showprogress" value=""> Show Progress (<i>leave uncheck for faster loading...</i>)
</td>
</tr><tr>
<td >
<input type="submit" value="Copy Directory >>">
<input type="hidden" name="do" value="docopy">
</td>
</tr>
</table>
<p>
<table>
<tr>
<td>dir</td>
<td>subdomain</td>
<td>email</td>
</tr><tr>
<td>$listeddir</td>
<td>$listeddir_subdomain</td>
<td>$listeddir_email</td>
</tr><tr>
<td colspan=3><textarea rows=10 cols=100>$listeddir_subdomain_vhost</textarea></td>
</tr>
</table>
</form>
~;
exit;
}
sub docopy{
my $sourcefolder = $req->param('sources');
my $destinationfolder = $req->param('destination');
my $replacefolder = $req->param('replacefolder');
my $replacefile = $req->param('replacefile');
my $showprogress = $req->param('showprogress');
my $doconfig = $req->param('doconfig');
my $mosConfig_host = $req->param('mosConfig_host');
my $mosConfig_sitename = $req->param('mosConfig_sitename');
my $mosConfig_user = $req->param('mosConfig_user');
my $mosConfig_password = $req->param('mosConfig_password');
my $mosConfig_db = $req->param('mosConfig_db');
my $installfolder = $req->param('installfolder');
my $dbused = $req->param('dbused');
my $copyfromdb = $req->param('copyfromdb');
if($destinationfolder && $sourcefolder){
$sourcedir = $rootdir ."/$sourcefolder";
$destinationdir = $rootdir . "/$destinationfolder";
if(!-e "$destinationdir" || $replacefolder){
$installfolder =~ s/\\/\//g;
my $locationofinstallationfolder;
if($installfolder =~ /\//i){
$locationofinstallationfolder = "$installfolder";
}else{
$configdir = $rootdir . "/$sourcefolder";
$locationofinstallationfolder = "$configdir/$installfolder";
}
if(-e "$locationofinstallationfolder"){
print "<p>Start Copy.........";
$cnt = 322;
$printfile = &getfolder($sourcedir);
print "<li>Total File : $totalfile\n";
print "<li>Total Folder : $totalfolder\n";
print "<p>Copy dirs...\n";
mkdir($destinationdir);
#foreach(@folders){
for(my $d=$#folders;$d>=0;$d--){
my $newdir =  $folders[$d];
$newdir =~ s/$sourcedir/$destinationdir/g;
print "<li>Copy $_ to $newdir...\n" if $showprogress;
if(!-e "$newdir"){
mkdir($newdir) or die "cannot make dir";
print " Done\n" if $showprogress;
}else{
print " Unchanged\n" if $showprogress;
}
}
print "<p>Copy Files....\n";
foreach(@files){
my $newfile =  $_;
$newfile =~ s/$sourcedir/$destinationdir/g;
print "<li>Copy $_ to $newfile ...\n" if $showprogress;
if((-e "$newfile" && $replacefile)||(!-e "$newfile")){
copy("$_","$newfile") or die "cannot copy file from $_ to $newfile";
print " Done\n" if $showprogress;
}else{
print " Unchanged\n" if $showprogress;
}
}
print "<p>Done";
if($doconfig){
print qq~
<p>Continue with Jooml Configuration 
<SCRIPT language=javascript>
location.href='?do=joomla&destinationfolder=$destinationfolder&dbused=$dbused&copyfromdb=$copyfromdb&mosConfig_host=$mosConfig_host&mosConfig_user=$mosConfig_user&mosConfig_password=$mosConfig_password&mosConfig_db=$mosConfig_db&installfolder=$locationofinstallationfolder&mosConfig_sitename=$mosConfig_sitename';
</SCRIPT>
~;
}
exit;
}else{
print qq~
<li>Installation Folder <b>$locationofinstallationfolder</b> doesn\'t exist
~;
}
}else{
print qq~
<li>Folder <b>$destinationdir</b> Already exist
~;
}
}else{
print qq~
<li>missing required fields
~;
}
exit;
}
sub getfolder{
$counter++;
my ($name) = @_;
my ${$counter};
opendir SUBDIR, "$name" or die "$name no exist";
@{$counter} = grep !/^\.\.?$/, readdir SUBDIR;
foreach (@{$counter}){
${$name} .= "\t$name/$_";
if(-d "$name/$_"){
${$name} .= " -> folder\n\t";
${$name} .= &getfolder("$name/$_");
$totalfolder = $totalfolder + 1;
push @folders,"$name/$_";
}
else{
if(-e "$name/$_"){
$cnt++;
#copy("$name/$_","$pubdir/$_");
my @filenames = split(/\./,$_);
my $justnames = $_;
$justnames =~ s/\.$filenames[$#filenames]//g;
#print "<li>$name/$_";
push @files,"$name/$_";
$totalfile = $totalfile + 1;
}
}
}
closedir SUBDIR;
return(${$name});
}
sub joomla{
my $destinationfolder = $req->param('destinationfolder');
$mosConfig_host = $req->param('mosConfig_host');
$mosConfig_sitename = $req->param('mosConfig_sitename');
$mosConfig_user = $req->param('mosConfig_user');
$mosConfig_password = $req->param('mosConfig_password');
$installfolder = $req->param('installfolder');
$mosConfig_db = $req->param('mosConfig_db');
$dbused = $req->param('dbused');
$copyfromdb = $req->param('copyfromdb');
if($destinationfolder){
$configdir = $rootdir . "/$destinationfolder";
$mosConfig_absolute_path = $configdir;
$mosConfig_cachepath = $rootdir . "/$destinationfolder/cache";
$mosConfig_live_site = "$rooturl/$destinationfolder";
$mosConfig_dbprefix = "jos_";
$mosConfig_mailfrom = $adminemail;
print "<p>Do Config..";
open(CONFIG,"$configdir/configuration.php");
my @configlines = <CONFIG>;
close CONFIG;
open(NEWCONFIG,">$configdir/configuration.php");
foreach(@configlines){
$_ =~ s/[\n\r]//g;
my ($var,$val) = split(/\=/,$_);
$var =~ s/\ //g;
$var =~ s/\$//g;
if(${$var}){
${$var} =~ s/\'/\\\'/g;
#print qq~<li>$var => ${$var}~;
print NEWCONFIG qq~\$$var = '${$var}';\n~;
}else{
#print qq~<li>Unchanged~;
print NEWCONFIG qq~$_\n~;
}
}
close NEWCONFIG;
print "..done";
if($dbused eq 'copy'){
print "<p>Copy Database from $copyfromdb to $mosConfig_db..";
my $new_db = "$mosConfig_db";
my $old_db = "$copyfromdb";
$dsn = "DBI:mysql:database=$old_db;host=localhost;port=3306";
$dbh = DBI->connect($dsn,$db_user,$db_password) or die $!;
my $sth = $dbh->prepare("show tables");
$sth->execute() or die qq|Database Error:  $DBI::errstr $query|;
while (my @rows =  $sth->fetchrow_array ){
push @tables,"$rows[0]";
}
$sth->finish;
$dbh->disconnect;
&doQuery("DROP DATABASE IF EXISTS  `$new_db` ;");
&doQuery("CREATE DATABASE `$new_db` ;");
$dsn = "DBI:mysql:database=$new_db;host=localhost;port=3306";
foreach(@tables){
if($_){
&doQuery("CREATE TABLE $new_db.$_ SELECT * FROM $old_db.$_");
}
}
}else{
print "<p>Do Primary Sql..";
open(SQL,"$installfolder/sql/joomla.sql");
my @sqllines = <SQL>;
close SQL;
my $joomlanewsql = '';
my $allsql = join('',@sqllines);
my @sqlcommand = split(/\;/,$allsql);
foreach(@sqlcommand){
#$_ =~ s/[\n\r]//g;
$_ =~ s/\#\_\_/$mosConfig_dbprefix/g;
push @joomlanewsql, "$_ ; ";
}
&doQuery("DROP DATABASE IF EXISTS  `$mosConfig_db` ;");
&doQuery("CREATE DATABASE `$mosConfig_db` ;");
$dsn = "DBI:mysql:database=$mosConfig_db;host=localhost;port=3306";
foreach(0..$#joomlanewsql - 1){
&doQuery("$joomlanewsql[$_]") if $joomlanewsql[$_];
}
my $usertable = $mosConfig_dbprefix.'users';
&doQuery("INSERT INTO `$usertable` VALUES (62, 'Administrator', 'admin', 'hairul\@onestop.com.my', '74b87337454200d4d33f80c4663dc5e5', 'Super Administrator', 0, 1, 25, '2007-05-07 11:25:36', '2007-05-07 11:26:21', '', 'expired=\nexpired_time=');");
print "..done";
print "<p>Do Samplae Data Sql..";
open(SQL,"$installfolder/sql/sample_data.sql");
my @sqllines = <SQL>;
close SQL;
my $joomlanewsql = '';
my $allsql = join('',@sqllines);
my @sqlcommand = split(/\;/,$allsql);
my @joomlanewsql;
foreach(@sqlcommand){
#$_ =~ s/[\n\r]//g;
$_ =~ s/\#\_\_/$mosConfig_dbprefix/g;
push @joomlanewsql, "$_ ; ";
}
foreach(0..$#joomlanewsql - 1){
&doQuery("$joomlanewsql[$_]") if $joomlanewsql[$_];
}
}
print "..done";
if(-e "$installfolder"){
rename ("$installfolder","$installfolder\-old") if $installfolder =~ /installation/i;
}
print qq~
<h1>Configuration Completed.</h1>
<a href="$mosConfig_live_site">click here to view ur website</a>
~;
}else{
print qq~
<li>missing folder
~;
}
exit;
}
sub dumps(){
my $new_db = "new_ajib";
my $old_db = "ajib";
$dsn = "DBI:mysql:database=$old_db;host=localhost;port=3306";
$dbh = DBI->connect($dsn,$db_user,$db_password) or die $!;
my $sth = $dbh->prepare("show tables");
$sth->execute() or die qq|Database Error:  $DBI::errstr $query|;
while (my @rows =  $sth->fetchrow_array ){
push @tables,"$rows[0]";
}
$sth->finish;
$dbh->disconnect;
&doQuery("DROP DATABASE IF EXISTS  `$new_db` ;");
&doQuery("CREATE DATABASE `$new_db` ;");
$dsn = "DBI:mysql:database=$new_db;host=localhost;port=3306";
print "<p>start ";
foreach(@tables){
if($_){
&doQuery("CREATE TABLE $new_db.$_ SELECT * FROM $old_db.$_");
print "<li>copy $new_db.$_ FROM $old_db.$_ ";
}
}
print "<p>done ";
}
sub doQuery{
my ($query) = @_;
$dbh = DBI->connect($dsn,$db_user,$db_password) or die $!;
# &sqlQuery($inserter);
$sth = $dbh->prepare($query) or die qq|Database Error: $DBI::errstr $query|;
# return &sqlerr("$DBI::errstr. <P>Query: $query");
  $sth->execute() or die qq|Database Error:  $DBI::errstr $query|;
$dbh->disconnect;
}
exit;
this is web base script. so just put this to ur cgi enable folder with 755 permission. 
u can simply modify certain area to suit ur preferences
**use at ur own risk.
happy coding.