#!/usr/bin/perl -w

# mutt_ldap_query.pl version 3.0
# Written by Marc de Courville <marc@courville.org>
# The latest version of the code can be retrieved at 
#  ftp://ftp.mutt.org/pub/mutt/contrib
# This code is distributed under the GNU General Public License (GPL). See
# http://www.opensource.org/gpl-license.html and http://www.opensource.org/.

# mutt_ldap_query performs ldap queries using either ldapsearch command
# or the perl-ldap module and it outputs the required formatted data for
# feeding mutt when using its "External Address Query" feature.
# This perl script can be interfaced with mutt by defining in your .muttrc:
#   set query_command = "mutt_ldap_query.pl '%s'"
# Multiple requests are supported: the "Q" command of mutt accepts as argument
# a list of queries (e.g. "Gosse de\ Courville").

# References:
# - ldapsearch is a ldap server query tool present in ldap-3.3 distribution 
#   http://www.umich.edu/~rsug/ldap)
# - perl-ldap module 
#   http://www.perl.com/CPAN-local/authors/id/GBARR
# - mutt is the ultimate email client
#   http://www.mutt.org
# - historical Brandon Blong's "External Address Query" feature patch for mutt
#   http://www.fiction.net/blong/programs/mutt/#query

# Version History (major changes only)
# 3.0 (12/29/1999): 
#  implemented another query method using perl-ldap module enabled by 
#  the -p boolean flag
# 2.3 (12/28/1999): 
#  added better parsing of the options, a shortcut for avoiding
#  -s and -b options by using the script builtin table of common
#  servers and associated search bases performing a <server_nickname>
#  lookup (changes inspired from a patch sent by Adrian Likins
#  <alikins@redhat.com>), performed some Y2K cleanups ;-)
# 2.2 (11/02/1999): 
#  merged perl style fixes proposed by Warren Jones <wjones@tc.fluke.com>
# 2.1 (4/14/1998):
#  first public release

use strict;

use constant DEBUG => 0;
use constant ONLY_PERL => 0;

use Getopt::Std;
use vars qw($opt_p $opt_h $opt_s $opt_b $opt_n);
getopts('hpn:s:b:');

if ($opt_p)
{
  use Net::LDAP;
}

#----8<------8<------8<------8<---CONFIG AREA--->8------>8------>8------>8----
# Please change the following lines to match your site configuration
# These are my defaults...
my $ldap_server = "ldap.crm.mot.com";
my $search_base = "o=Motorola, c=US";           
my $LDAPSEARCH="/usr/bin/ldapsearch";
my @fields = qw(cn mail sn fn uid);
# list of the fields that will be used for composing the answer
my $expected_answers = "cn fn sn mail business_group telephonenumber";
#8<------8<------8<------8<------8<--------->8------>8------>8------>8------>8
my $ldap_server_nick;
my @results;

# database of the common server with default search starting points
# the format is: 'server nickname', 'full address of the server', 'search base'
my %server_db = (
  bigfoot	=> ['ldap.bigfoot.com', ''],
  novell	=> ['ldap.novell.com', ''],
  netscape	=> ['memberdir.netscape.com', 'ou=member_directory,o=netcenter.com'],
  infospace	=> ['ldap.infospace.com', 'c=US'],
  verisign	=> ['directory.verisign.com', ''],
  local		=> ['127.0.0.1', ''],
  four11	=> ['ldap.four11.com', ''],
  redhat	=> ['slag.support.redhat.com', 'dc=redhat,dc=com'],
  motorola	=> ['ldap.mot.com', 'o=Motorola,c=US'],
  crm		=> ['ldap.crm.mot.com', 'o=Motorola,c=US']
);

sub usage
{
<<EOF;
mutt_ldap_query performs ldap queries using either ldapsearch command
or the perl-ldap module and it outputs the required formatted data for
feeding mutt when using its "External Address Query" feature.

This perl script can be interfaced with mutt by defining in your .muttrc:
  set query_command = "mutt_ldap_query.pl '%s'"
Multiple requests are supported: the "Q" command of mutt accepts as argument
a list of queries (e.g. "Gosse de\ Courville").

usage: $0 [-p] -s <server_name> -b <search_base> -n <server_nickname> <name_to_query> [[<other_name_to_query>] ...]

-p use perl-ldap module instead of ldapsearch (which is the default)
-s query ldap server <server_name>
-b use <search_base> as the starting point for the search instead of the default
-n shortcut for avoiding -s and -b options by using the script builtin
   table of common servers and associated search bases performing a 
   <server_nickname> lookup

examples of queries:
  classical query:
    mutt_ldap_query.pl -s ldap.crm.mot.com -b 'o=Motorola,c=US' Gosse
  and its shortcut version using a nickname
    mutt_ldap_query.pl -n crm Gosse de\ Courville
EOF
}

# print usage error
die usage if (! $ARGV[0] || $opt_h);

# define default $ldap_server
$ldap_server = $opt_s if $opt_s;
$search_base = $opt_b if $opt_b;
if ($opt_n) 
{
  $ldap_server_nick = $opt_n;
  my $option_array = $server_db{$ldap_server_nick};
  die print "$0 unknown server nickname:\n\t no server associated to the nickname $ldap_server_nick, please modify the internal database according your needs by editing the script $0\n" if ! $option_array;
  $ldap_server = $option_array->[0];
  $search_base = $option_array->[1];
}

print "DEBUG: ldap_server=$ldap_server search_base=$search_base\n" if (DEBUG);

$/ = '';	# Paragraph mode for input.

foreach my $askfor ( @ARGV )
{

# enable this if you want to include wildcard in your search with some huge 
# ldap databases you might want to avoid it
#  my $query = join '', map { "($_=$askfor*)" } @fields;
  my $query = join '', map { "($_=$askfor)" } @fields;
  $query = "(|" . $query . ")";
  
  my $command = "$LDAPSEARCH -L -h $ldap_server -b '$search_base' '(|$query)' $expected_answers";

  print "DEBUG: ldapsearch command is:\nDEBUG:   $command\n" if (DEBUG);

  if ($opt_p)
  {
    my $ldap = Net::LDAP->new($ldap_server, DN => "", Password => "", Port => 389, Debug => 3,) or die $@;
    $ldap->bind;
    my $mesg = $ldap->search( base => $search_base, filter => $query ) or die $@;
    $mesg->code && die $mesg->error;
    my @entries = $mesg->entries;
    map { $_->dump } $mesg->all_entries if (DEBUG);
    my $entry;
    foreach $entry (@entries)
    { 
      print "DEBUG processing $entry->dn\n" if (DEBUG);
# only keep the first email address, telephonenumber and businessgroup
      my $email = $entry->get('mail')->[0];
      my $phone = $entry->get('telephonenumber')->[0];
      my $sector = $entry->get('business_group')->[0];
      my $cn = $entry->get('cn');
      my @name = ($entry->get('fn'), $entry->get('sn'));
# for Motorola it is more convenient to have these information as output
      push @results, "<$email>\t@name\t($phone) $sector\n";
# this one works mostly for everyody
#      push @results, "<$email>\t@name\n";
    }
    $ldap->unbind;
  }
  else
  {
    open ( LDAPQUERY , "$command |") || die "LDAP query error: $!\n"; 
    while ( <LDAPQUERY> )
    {
      next if ! /^mail: (.*)$/im;

      my $email = $1;
      my $phone = /^telephonenumber: (.*)$/im ? $1 : '';
      my $sector = /^business_group: (.*)$/im ? $1 : '';
      my $cn = /^cn: (.*)/im ? $1 : ' ';
      my @name = ( /^fn: (.*)$/im, /^sn: (.*)$/im );

# for Motorola it is more convinient to have these information as output
      push @results, "<$email>\t@name\t($phone) $sector\n";
# this one works mostly for everyody
#      push @results, "<$email>\t@name\n";

## for pretty printing...
#      my $name = join ' ', ( /^fn=(.*)$/im, /^sn=(.*)$/im );
# use one of these two next lines
#      my $answer = sprintf ("<%s>\t%-17.17s\t(%s) %s\n", $email, $name , $phone, $sector);
#      my $answer = sprintf ("<%s>\t%-20.20s\n", $email, $cn );
#      push(@results, $answer);
    }
    close(LDAPQUERY) || die "ldapsearch failed: $!\n";
  }
}

print "LDAP query: found ", scalar(@results), "\n", @results;
exit 1 if ! @results;
