#!/usr/bin/perl -w # cgate_account_settings.pl - reads CommuniGate server account.settings # file, extracts user information and updates it in the OpenLDAP tree. # # Copyright (C) 2005, Rene Pfeiffer # # 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. # # Thu Nov 24 23:27:32 CET 2005 Created script. # Fri Nov 25 18:10:55 CET 2005 Added LDAP functionality. # Thu Dec 15 14:58:47 CET 2005 uid debugging. # # Update/synchronisation strategy: # # - Read files to be processed from path in ARGV # - Aquire list of file to open # - Open file from list # - Parse it and extract MaxAccountSize, any active mail forwarding rules # and possibly the real name # - Write LDIF file or update via TCP connection to LDAP server # use Carp; use DBI; use English; use Fcntl 'O_RDONLY'; use Getopt::Long; use Net::LDAP; use Net::LDAP::Entry; use Net::LDAP::Filter; use Pod::Usage; use strict; use Term::ANSIColor qw(:constants); use Tie::File; use Unicode::String qw(latin1 utf8); # Getopt::Long my $binddn = 'cn=ldapadmin,dc=example,dc=net'; my $debug = 0; my $help; my $passwd = '123456'; my $size = 5; my $target = 'ldapmaster.example.net'; my $tport = 389; my $tree = 'ou=users,ou=accounts,dc=example,dc=net'; my $verbose= 0; # File handling my $account; my @accountfiles; my $actual_uid; my $line; my $regexpline; my $maxfiles; my @settings; # Account data my $Forward; my $MaxAccountSize; my $RealName; my $Quota; # LDAP my $attribute; my $binddn_target; my $entry; my $filter; my $filterobj; my $ldap_target; my $lres; my $maxentries; my $mesg; my $sizelimit = 0; my $tres; my $update_cn; my $update_dn; my $update_mailForwardingAddress; my $update_mailQuotaSize; my $update_uid; # Program logic my $errors = 0; my $i; my $inserted = 0; my $ldap_errors = 0; my $processed = 0; my $skip = 0; my $updated = 0; # -------------------------------------------------------------------- # Let's start # Print out help message in case the user didn't supply a file # with the list of account.settings files to process pod2usage(-exitval => 2, -verbose => 2) if ( $#ARGV < 2 ); GetOptions( 'binddn=s' => \$binddn, 'debug=i' => \$debug, 'help' => \$help, 'passwd=s' => \$passwd, 'sizelimit=i' => \$size, 'target=s' => \$target, 'tport=i' => \$tport, 'tree=s' => \$tree, 'verbose=i' => \$verbose ); $binddn_target = $binddn; # Print out help message in case the user didn't supply all # mandatory parameters pod2usage(-exitval => 2, -verbose => 2) if $help; # Let's rock! # # Connect to LDAP server $ldap_target = Net::LDAP->new( $target, port => $tport, version => 3, async => 1 ) or die "$@"; if ( $debug > 0 ) { print WHITE "Connected to $target.",RESET,"\n"; # Limit results in debugging mode $sizelimit = 7; if ( $size > $sizelimit ) { $sizelimit = $size; } } else { print WHITE "Connected to target server : ",YELLOW,$target,RESET,"\n"; } # Bind to LDAP server $mesg = $ldap_target->bind( $binddn_target, password => $passwd ); $mesg->code && die $mesg->error; # Tie file with paths to an array if ( $debug > 0 ) { print WHITE "Opening file >",$ARGV[$#ARGV-1],"<",RESET,"\n"; } tie @accountfiles, 'Tie::File', $ARGV[$#ARGV-1], autochomp => 1, mode => O_RDONLY or die "Could not open file of files!"; $maxfiles = $#accountfiles; # Walk through the array of files and process every single one of them foreach $account (@accountfiles) { # Watch out for the debug mode if ( ($debug > 0) and ($processed > $sizelimit) ) { next; } # Open file and get contents $skip = 0; tie @settings, 'Tie::File', $account, autochomp => 1, mode => O_RDONLY or $skip = 1; # Clear variables $Forward = 'x'; $MaxAccountSize = 0; $RealName = 'x'; if ( $skip == 0 ) { # Go through options line by line if ( $debug > 5 ) { print WHITE "Processing file $account.",RESET,"\n"; } foreach $line (@settings) { if ( $debug > 15 ) { print RED "Current line: <",$line,">",RESET,"\n"; } # We don't want to modify the original file, therefore we # copy the actua line to a seperate variable. $regexpline = $line; # Look for presence of quota setting if ( $regexpline =~ m/.*MaxAccountSize.*/is ) { $regexpline =~ m/\W*MaxAccountSize\W*=\W*(.*);/g; $Quota = $1; $MaxAccountSize = int(substr($Quota,0,length($Quota)-1)); if ( substr($Quota,length($Quota)-1,1) eq 'M' ) { $MaxAccountSize = $MaxAccountSize * 1024 * 1024; } if ( substr($Quota,length($Quota)-1,1) eq 'K' ) { $MaxAccountSize = $MaxAccountSize * 1024; } if ( $debug > 10 ) { print YELLOW "Found quota size : ",$Quota," / ",$MaxAccountSize," bytes",RESET,"\n"; } } else { if ( $debug > 15 ) { print YELLOW "No match for MaxAccountSize.",RESET,"\n"; } } # Look for presence of real name setting if ( $regexpline =~ m/.*RealName.*/is ) { $regexpline =~ m/\W*RealName\W*=\W*"(.*)";/g; $RealName = $1; if ( $debug > 10 ) { print YELLOW "Found real name : <",$RealName,">",RESET,"\n"; } } else { if ( $debug > 15 ) { print YELLOW "No match for RealName.",RESET,"\n"; } } # Look for any rules in order to extract forwarding addresses if ( $regexpline =~ m/.*Rules.*/is ) { # Check if rule is active if ( $regexpline =~ m/Rules\W*=\W*\(\(1/g ) { $regexpline =~ s/\)/ /g; if ( $regexpline =~ m/.*"Mirror to",(.*)\W,/g ) { $Forward = substr($1,0,255); } if ( $regexpline =~ m/.*"Redirect to",(.*)\W,/g ) { $Forward = substr($1,0,255); } if ( $debug > 10 ) { print YELLOW "Found mailforward: ",$Forward,RESET,"\n"; } } } else { if ( $debug > 15 ) { print YELLOW "No match for Rules.",RESET,"\n"; } } } # Close file untie @settings; # See if we find a similar entry in the LDAP tree. We extract the uid # from the filename. $actual_uid= $account; $actual_uid=~ s/\.macnt//g; $filter = sprintf("(uid=%s)",$actual_uid); $filterobj = Net::LDAP::Filter->new($filter); $lres = $ldap_target->search( base => $tree, scope => 'sub', sizelimit => 1, timelimit => 900, attrs => ['uid','cn','mailQuotaSize','mailForwardingAddress'], filter => $filter ); if ( $lres->count > 0 ) { # We found something $entry = $lres->entry(0); $update_uid = $entry->get_value("uid"); $update_cn = $entry->get_value("cn"); $update_mailForwardingAddress = $entry->get_value("mailForwardingAddress"); $update_mailQuotaSize = $entry->get_value("mailQuotaSize"); # Free search result $mesg = $ldap_target->abandon($lres); $mesg->code && print 'Couldn\'t abandon search result used for uid detection.\n'; # Send updates if we have enough data to send to # the LDAP server if ( ($Forward ne 'x') or ($MaxAccountSize > 0) ) { $update_dn = sprintf("uid=%s,%s",$update_uid,$tree); if ( $debug > 0 ) { print YELLOW "Building LDAP update object for uid <",$update_uid,">",RESET,"\n"; print YELLOW "Building LDAP update object for dn <",$update_dn,">",RESET,"\n"; } if ( $Forward ne 'x' ) { if ( $debug > 0 ) { print RED "Sending: replace 'mailForwardingAddress' => $Forward",RESET,"\n"; $updated++; } else { $mesg = $ldap_target->modify( $update_dn, replace => { 'mailForwardingAddress' => $Forward } ); if ( ! $mesg->code ) { $updated++; } else { print RED "Error: ",$mesg->error,"(",$mesg->error_name,")",RESET,"\n"; $ldap_errors++; } } } if ( $MaxAccountSize > 0 ) { if ( $debug > 0 ) { print RED "Sending: replace 'mailQuotaSize' => $MaxAccountSize",RESET,"\n"; $updated++; } else { $mesg = $ldap_target->modify( $update_dn, replace => { 'mailQuotaSize' => $MaxAccountSize } ); if ( ! $mesg->code ) { $updated++; } else { print RED "Error: ",$mesg->error,"(",$mesg->error_name,")",RESET,"\n"; $ldap_errors++; } } } } } $processed = $processed + 1; } else { $errors = $errors + 1; } } continue { $processed = $processed + 1; } # Show statistics print GREEN "Processed files : ",$maxfiles,RESET,"\n"; print GREEN "Updated attributes : ",$updated,RESET,"\n"; print GREEN "Errors while reading : ",$errors,RESET,"\n"; print GREEN "Errors while updating : ",$ldap_errors,RESET,"\n"; untie @accountfiles; $ldap_target->unbind; exit; __END__ =head1 NAME cgate_account_settings.pl - reads account.settings files from a CommuniGate server, extracts some user information and writes it to a LDAP tree =head1 SYNOPSIS cgate_account_settings.pl [OPTIONS] FILE =head1 DESCRIPTION This script reads a variable amount of CommuniGate account.settings files, extracts information and writes it to an LDAP server containing user structures. The individual account.settings must be stored in a single file, one path per line. The file with the list of files is given as the first argument. =head1 OPTIONS =over 8 =item B<--binddn dn> The DN to use when binding to the target server. =item B<--debug n> Set the debug level. 0 is for production use. =item B<--help> Prints this text. =item B<--passwd password> The password to use when binding to the target server. =item B<--size n> Limit processed entries to n when in debug mode. Defaults to 5, but the default number of processed entries is 7. n needs to be bigger than 7 to have any effect. =item B<--target server> Target server. Defaults to ldapmaster.example.net. =item B<--tport port> Indicates the target port for talking to the target server. Defaults to 389. =item B<--tree dntree> The DN of the subtree on the target server where the data should be synced to. =item B<--verbose n> A verbose level greater than 0 makes the program print more status messages. Defaults to 0. =head1 BUGS The script does not use TLS when talking to the LDAP server. You can work around this limitation by creating SSH tunnels and using localhost with a different port. =head1 AUTHOR R. Pfeiffer