#!/usr/bin/perl -w ############################################################################## # # Script to export a list of all email addresses from Active Directory # Brian Landers <brian@packetslave.com> # # This code is in the public domain. Your use of this code is at your own # risk, and no warranty is implied. The author accepts no liability for any # damages or risks incurred by its use. # ############################################################################## # This script would be most useful for generating an access.db file on a # sendmail gateway server. You would run it to generate a list of all # valid email addresses, then insert those addresses into access.db as # follows: # # To:bob@example.com RELAY # To:jim@example.com RELAY # To:joe@example.com RELAY # # Then, you'd create a default entry for the domain that rejects all other # recipients (since if they're not in the list, they're by definition invalid). # # To:example.com ERROR:"User unknown" # # For this to work, you need to have "example.com" in your relay-domains # file (normally /etc/mail/relay-domains), and you need to enable the # "blacklist_recipients" FEATURE in your sendmail.mc file. # # FEATURE(`blacklist_recipients') # # See also my genaccessdb script at packetslave.com for ideas on how to # generate the access.db file from this list of addresses # ############################################################################## # $Id: adexport,v 1.2 2011/08/20 23:30:52 blanders Exp $ use strict; $|++; use Net::LDAP; use Net::LDAP::Control::Paged; use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); #our ($cn,$passwd,$base); #($cn,$passwd,$base)=@_ARGV; #print "$cn \n $passwd \n $base"; #exit; # ---- Constants ---- our $bind = $ARGV[2].','.$ARGV[1]; # AD account our $passwd = $ARGV[3]; # AD password our $base = $ARGV[1]; # Start from root our @servers; push (@servers,$ARGV[0]); our $filter = '(|(objectClass=publicFolder)(&(sAMAccountName=*)(mail=*)))'; # ------------------- # We use this to keep track of addresses we've seen my %gSeen; # Connect to the server, try each one until we succeed my $ldap = undef; foreach( @servers ) { $ldap = Net::LDAP->new( $_ ); last if $ldap; # If we get here, we didn't connect die "Unable to connect to any LDAP servers!\n"; } # Create our paging control. Exchange has a maximum recordset size of # 1000 records by default. We have to use paging to get the full list. my $page = Net::LDAP::Control::Paged->new( size => 100 ); # Try to bind (login) to the server now that we're connected my $msg = $ldap->bind( dn => $bind, password => $passwd ); # If we can't bind, we can't continue if( $msg->code() ) { die( "error while binding:", $msg->error_text(), "\n" ); } # Build the args for the search my @args = ( base => $base, scope => "subtree", filter => $filter, attrs => [ "proxyAddresses" ], callback => \&handle_object, control => [ $page ], ); # Now run the search in a loop until we run out of results. This code # is taken pretty much directly from the example code in the perldoc # page for Net::LDAP::Control::Paged my $cookie; while(1) { # Perform search my $mesg = $ldap->search( @args ); # Only continue on LDAP_SUCCESS $mesg->code and last; # Get cookie from paged control my($resp) = $mesg->control( LDAP_CONTROL_PAGED ) or last; $cookie = $resp->cookie or last; # Set cookie in paged control $page->cookie($cookie); } if( $cookie ) { # We had an abnormal exit, so let the server know we do not want any more $page->cookie($cookie); $page->size(0); $ldap->search( @args ); } # Finally, unbind from the server $ldap->unbind; # ------------------------------------------------------------------------ # Callback function that gets called for each record we get from the server # as we get it. We look at the type of object and call the appropriate # handler function # sub handle_object { my $msg = shift; # Net::LDAP::Message object my $data = shift; # May be Net::LDAP::Entry or Net::LDAP::Reference # Only process if we actually got data return unless $data; return handle_entry( $msg, $data ) if $data->isa("Net::LDAP::Entry"); return handle_reference( $msg, $data ) if $data->isa("Net::LDAP::Reference"); # If we get here, it was something we're not prepared to handle, # so just return silently. return; } # ------------------------------------------------------------------------ # Handler for a Net::LDAP::Entry object. This is an actual record. We # extract all email addresses from the record and output only the SMTP # ones we haven't seen before. sub handle_entry { my $msg = shift; my $data = shift; # Extract the email addressess, selecting only the SMTP ones, and # filter them so that we only get unique addresses my @mails = grep { /^smtp:/i && !$gSeen{$_}++ } $data->get_value( "proxyAddresses" ); # If we found any, strip off the SMTP: identifier and print them out if( @mails ) { print map { s/^smtp:(.+)$/\L$1\n/i; $_ } @mails; } } # ------------------------------------------------------------------------ # Handler for a Net::LDAP::Reference object. This is a 'redirect' to # another portion of the directory. We simply extract the references # from the object and resubmit them to the handle_object function for # processing. sub handle_reference { my $msg = shift; my $data = shift; foreach my $obj( $data->references() ) { # Oooh, recursion! Might be a reference to another reference, after all return handle_object( $msg, $obj ); } }