<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Le coin du sysadmin &#187; Scripting</title>
	<atom:link href="http://laurent.cligny.net/index.php/tag/scripting/feed/" rel="self" type="application/rss+xml" />
	<link>http://laurent.cligny.net</link>
	<description>La connaissance s&#039;accroit en la partagant</description>
	<lastBuildDate>Tue, 15 Feb 2011 08:45:02 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Extraire des données Active Directory pour Postfix</title>
		<link>http://laurent.cligny.net/index.php/2008/09/09/extraire-des-donnees-active-directory-pour-postfix/</link>
		<comments>http://laurent.cligny.net/index.php/2008/09/09/extraire-des-donnees-active-directory-pour-postfix/#comments</comments>
		<pubDate>Tue, 09 Sep 2008 10:06:25 +0000</pubDate>
		<dc:creator>laurent</dc:creator>
				<category><![CDATA[Scripting]]></category>

		<guid isPermaLink="false">http://laurent.cligny.net/?p=7</guid>
		<description><![CDATA[Ou comment extraire les mails valides d&#8217;un domaine Active Directory pour les fournir à Postfix. &#8220;Pourquoi faire ?&#8221; Me direz-vous. Et bien c&#8217;est simple, refuser un mail envoyé à une adresse mail qui n&#8217;existe pas, avant de la relayer à votre infrastructure Exchange, c&#8217;est chouette. Un peu comme un retour de la poste &#8220;N&#8217;habite plus [...]]]></description>
			<content:encoded><![CDATA[<p>Ou comment extraire les mails valides d&#8217;un domaine Active Directory pour les fournir à Postfix.</p>
<p>&#8220;Pourquoi faire ?&#8221; Me direz-vous. Et bien c&#8217;est simple, refuser un mail envoyé à une adresse mail qui n&#8217;existe pas, avant de la relayer à votre infrastructure Exchange, c&#8217;est chouette. Un peu comme un retour de la poste &#8220;N&#8217;habite plus à l&#8217;adresse indiquée&#8221; en somme. <span id="more-7"></span></p>
<p><strong> 1. Configuration de Postfix</strong></p>
<p>Pour demander à Postfix d&#8217;y regarder à deux fois avant d&#8217;accepter un mail, il faut lui dire dans quelle liste il va devoir chercher une occurrence. Pour ça on ajoute la ligne suivante dans <em>main.cf</em>:</p>
<p><span style="color: #0000ff;">relay_recipient_maps = hash:/usr/local/etc/postfix/relay_recipients</span></p>
<p>Ce qui à pour effet de demander à Postfix de regarder dans ce fichier à chaque fin de commande RCPT To: qu&#8217;il reçoit et de répondre par une erreur s&#8217;il ne trouve rien qui corresponde.</p>
<p>Formidable n&#8217;est-ce pas ? Mais maintenant il faut le remplir ce fichier (sous la forme mailaddress@example.com OK pour un fichier de type &#8220;hash&#8221;). Donc soit on embauche quelqu&#8217;un pour réactualiser ce fichier à chaque nouvelle adresse-mail (sans oublier le &#8220;<em>postmap /usr/local/etc/postfix/relay_recipients</em>&#8221; après chaque modif), soit on demande (gentiment) à son serveur de le faire à notre place <img src='http://laurent.cligny.net/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Mais juste avant ça, regardons les différents options (non exhaustives, cf &#8220;<em>man 5 postconf</em>&#8221; pour les curieux) qui s&#8217;offrent à nous pour stocker nos belles adresses e-mails valides dans notre infrasctructure Exchange.</p>
<ul>
<li>Soit on les met tel quels dans notre fichier plat avec un &#8220;<em>postmap &lt;relay_recipients&gt;</em>&#8221; qui va bien pour en faire un &#8220;&lt;relay_recipients.db&gt;&#8221; au format BerkeleyDB.</li>
<li>Soit on met nos jolies adresses dans une base de donnée type MySQL ou PostgreSQL.</li>
<li>Soit on est un bourrin et on fait directement des requêtes LDAP dans Active Directory pour chaque vérification d&#8217;adresse (et à plusieurs par seconde sur un serveur mail d&#8217;entreprise moyen, vos serveurs Active Directory vont faire une drôle de tronche).</li>
</ul>
<p>Etant de nature mi-figue mi-raisin, et puisque mon serveur dispose déjà du moteur de base de donnée MySQL, j&#8217;opte pour la deuxième option. <strong></strong></p>
<p><strong> 2. Ca va scripter chérie !</strong></p>
<p>Résumons ! Nous voulons demander des infos à <strong>ActiveDirectory</strong> donc <strong>LDAP</strong>, les mettre sous la forme &#8220;mailaddress@example.com&#8221; et les écrire dans une base <strong>MySQL </strong>(qu&#8217;on appellera <em>postfix</em>). Et pour ça le moteur de script <strong>Perl</strong> c&#8217;est super ! Pourquoi ? Parcequ&#8217;il existe une foultitude de modules Perl pour moult moteurs de base de données, protocoles réseaux, serveurs, j&#8217;en passe et des meilleures. Ce qui fait qu&#8217;en un script, on peut faire tout ce qu&#8217;il nous faut et même plus (par contre pour faire café, voyez plutôt avec Java).</p>
<p><strong>2.1 Avant de &#8220;coder&#8221;</strong></p>
<p>Avant de vous lancer à corps perdu dans le copier-col^WWécriture de ce script, veuillez noter les pré requis dont vous aurez besoin en matière de modules Perl (attention ce sont les noms des ports FreeBSD):</p>
<ul>
<li>perl 5.8.x (Le moteur de script mondialement connu)</li>
<li>p5-DBD-mysql50 (Le module pour parler à sa base de donnée)</li>
<li>p5-perl-ldap (Le module pour parler LDAP dans le texte)</li>
<li>p5-DBI (Le module dont dépend p5-DBD pour fonctionner)</li>
<li>p5-Storable (Le module dont dépend p5-DBI pour fonctionner)</li>
</ul>
<p>Assurez-vous aussi de créer une table dans votre base de donnée  avec un index pour accueillir vos adresses e-mails. Ce n&#8217;est pas l&#8217;objet de ce (long) billet mais voici comment qu&#8217;on fait (on part du principe d&#8217;être déjà connecté à MySQL):</p>
<pre><em>mysql&gt; CREATE DATABASE postfix;</em></pre>
<pre><em>mysql&gt; CREATE TABLE valid_emails (mail_addresses VARCHAR(128) NOT NULL) type=MyISAM;</em></pre>
<pre><em>mysql&gt; CREATE UNIQUE INDEX id_valid_emails_01 ON valid_emails(mail_addresses);</em></pre>
<p><strong>2.2 Le script</strong></p>
<p>Evidement (quoique&#8230;) le script va se partager en trois actions, la récolte d&#8217;infos, leur mise en forme et leur mise à jours dans la base de donnée. Et comme un exemple vaut mieux qu&#8217;un long discours:</p>
<pre><span style="color: #0000ff;">#!/usr/bin/perl
</span><span><span style="color: #008000;"># On déclare ci-dessus que c'est un script perl

</span></span><span style="color: #0000ff;"><span style="color: #008000;"># On charge nos modules Perl. Notez le "Paged" du module LDAP, il va nous permettre
# d'extraire les données d'Active Directory en contournant la limite de 100 résultats
# par requêtes de celui-ci</span>
</span></pre>
<pre><span style="color: #0000ff;">use DBI();
use Net::LDAP;
use Net::LDAP::Control::Paged;
use Net::LDAP::Constant ( "LDAP_CONTROL_PAGED" );
</span></pre>
<pre><span style="color: #0000ff;"><span style="color: #008000;"># On (est un con, je sais) déclare les constantes pour nos contrôleurs de domaine</span>
$dc1="nameserver1";
$dc2="nameserver2";</span><span style="color: #0000ff;">
$hqbase="dc=example,dc=com"; <span style="color: #008000;"># Votre nom de domain complet</span>

</span><span style="color: #0000ff;"><span style="color: #008000;"># Les login et mot de passe d'un compte ayant les droits en lecture (et seulement en lecture)
# dans Active Directory.</span>
$ldapuser="user";
$ldappasswd="passwd";</span></pre>
<pre><span style="color: #0000ff;"><span style="color: #008000;"># Le nom d'hôte abritant MySQL, le type de base de donnée, le nom de la base et
# de la table à remplir et un login/mot de passe d'un utilisateur MySQL ayant
# les droits INSERT et DELETE sur votre table et <span style="text-decoration: underline;">seulement</span> celle-ci, histoire
# d'éviter les bêtises.</span>
$dbhost="localhost";</span><span style="color: #0000ff;">
$dbtype="mysql";</span><span style="color: #0000ff;">
$dbname="postfix";</span><span style="color: #0000ff;">
$dbtable="valid_emails";
$dbcolumn="mail_addresses";
$dbuser="user";
$dbpasswd="passwd";</span></pre>
<pre><span style="color: #0000ff;"><span style="color: #008000;"># On se connecte à Active Directory avec une gestion d'erreur en cas de problème</span>
$noldapserver=0;
$ldap = Net::LDAP-&gt;new($dc1) or
   $noldapserver=1;
if ($noldapserver == 1)  {
   $ldap = Net::LDAP-&gt;new($dc2) or
      die "Error connecting to specified domain controllers $@ \n";
}

$mesg = $ldap-&gt;bind ( dn =&gt; $ldapuser,
                     password =&gt;$ldappasswd);
if ( $mesg-&gt;code()) {
    die ("error:", $mesg-&gt;code(),"\n","error name: ",$mesg-&gt;error_name(),
        "\n", "error text: ",$mesg-&gt;error_text(),"\n");
}

<span style="color: #008000;"># On se connecte au moteur de base de donnée MySQL</span>
my $dbh = DBI-&gt;connect("DBI:$dbtype:database=$dbname;host=$dbhost","$dbuser","$dbpasswd");

<span style="color: #008000;"># On prépare notre requête LDAP en prenant bien soin de ne pas dépasser une taille de 1000
# réponses par mise en cache de la requête.
<span style="color: #0000ff;">$page = Net::LDAP::Control::Paged-&gt;new( size =&gt; 990 );
<span style="color: #008000;">
# On applique un filtrage des résultats LDAP pour ne garder que ce qui nous intéresse
# (voir aussi RCF 4515, </span></span></span></span><span style="color: #008000;">LDAP:String Representation of Search Filters</span><span style="color: #0000ff;"><span style="color: #008000;"><span style="color: #0000ff;"><span style="color: #008000;">)
</span></span></span></span><span><span><span><span><span style="color: #0000ff;"><span style="color: #008000;"><span style="color: #0000ff;"><span style="color: #008000;"># Et on recherche l'attribut proxyAddresses qui contient les adresses e-mails sous forme
# multivaluée</span></span></span></span></span></span></span></span>

<span style="color: #0000ff;"><span style="color: #008000;"><span style="color: #0000ff;">@args = ( base     =&gt; $hqbase,
          filter =&gt; "(&amp; (mailnickname=*)(| (&amp;(objectCategory=person)
                     (objectClass=user)(!(homeMDB=*))(!(msExchHomeServerName=*)))
                     (&amp;(objectCategory=person)(objectClass=user)(|(homeMDB=*)
                     (msExchHomeServerName=*)))
                     (objectCategory=group)(objectCategory=publicFolder)))",
          control  =&gt; [ $page ],
          attrs  =&gt; "proxyAddresses",
);</span>

# On commence la recherche pour de vrai
<span style="color: #0000ff;">my $cookie;
while(1) {
  my $mesg = $ldap-&gt;search( @args );

<span style="color: #008000;"># Et on filtre les résulats</span>
  foreach my $entry ( $mesg-&gt;entries ) {

<span style="color: #008000;">    # En ne gardant que les lignes contenant "proxyAddresses".</span>
     foreach my $mail ( $entry-&gt;get_value( "proxyAddresses" ) ) {
     <span style="color: #008000;"># On dégage les vilains </span></span></span></span><span style="color: #008000;"><span><span>"smtp:" et "SMTP:"</span></span> qui se trouvent devant nos adresses e-mails</span><span style="color: #0000ff;"><span style="color: #008000;"><span style="color: #0000ff;">
      if ( $mail =~ s/^(smtp|SMTP)://gs ) {
	 <span style="color: #008000;"># On passe tout en minuscule pour Postfix</span>
	 $mail = lc($mail);
         <span style="color: #008000;"># On écrit le tout dans notre base de donnée</span>
         <span style="color: #008000;"># Notez que REPLACE ici est très pratique car il permet à la fois de mettre à jour
         # les données lorsqu'elles existent, grâce à l'index, et de les INSERT-er si elles
         # n'existent pas encore. Voyez aussi le (</span></span></span></span><span style="color: #008000;"><span><span>" . $dbh-&gt;quote("$mail") . "</span></span>) qui permet
         # d'insérer nos donnéesau format caractère.</span>
<span style="color: #0000ff;"><span style="color: #008000;"><span style="color: #0000ff;">	 $dbh-&gt;do("REPLACE INTO $dbtable ($dbcolumn) VALUES (" . $dbh-&gt;quote("$mail") . ")");
        }
     }
  }

  <span style="color: #008000;"># J'avoue le morceau qui suit n'est pas de moi, je laisse donc les commentaires d'origine
  # En gros on s'assure que les requêtes LDAP mises en cache se passent bien
  # Only continue on LDAP_SUCCESS</span>
  $mesg-&gt;code and last;

  <span style="color: #008000;"># Get cookie from paged control</span>
  my($resp)  = $mesg-&gt;control( LDAP_CONTROL_PAGED ) or last;
  $cookie    = $resp-&gt;cookie or last;

  <span style="color: #008000;"># Set cookie in paged control</span>
  $page-&gt;cookie($cookie);
}

if ($cookie) {
  <span style="color: #008000;"># We had an abnormal exit, so let the server know we do not want any more</span>
  $page-&gt;cookie($cookie);
  $page-&gt;size(0);
  $ldap-&gt;search( @args );
  <span style="color: #008000;"># Also would be a good idea to die unhappily and inform OP at this point</span>
     die("LDAP query unsuccessful");
}

</span></span></span></pre>
<p><span style="color: #0000ff;"><span style="color: #008000;"><span style="color: #0000ff;"><span style="color: #000000;">Rendez votre script exécutable avec <em>chmod +x</em> et voilà ! Evidemment, faites un petit <em>SELECT</em> sur votre table afin de vérifier que les mails sont bien dans la table <em>valid_emails</em>, et que ces valeurs ne se retouvent pas en double quand on lance deux fois le script coup sur coup.<br />
</span></span></span></span></p>
<p><strong><span style="color: #000000;">3. Postfix, le retour</span></strong></p>
<p>Tout ça c&#8217;est bien gentil, mais à la base nous voulons permettre à Postfix de faire une requête dans notre table toute chaude, afin d&#8217;y vérifier que les destinataires existent bien.</p>
<p>Pour ça il faut modifier un chouilla notre ligne <em>relay_recipient_maps</em> dans <em>main.cf</em>:</p>
<p><span style="color: #0000ff;">relay_recipient_maps = <span style="text-decoration: underline;">mysql</span>:/usr/local/etc/postfix/relay_recipients</span></p>
<p>Puis modifier le contenu de ce fichier pour qu&#8217;il ressemble à ça:</p>
<pre><span style="color: #008000;"># Autant dans le script il nous fallait un utilisateur MySQL ayant des droits d'écriture sur
# la table "valid_emails", autant ici un utilisateur MySQL en lecture seule fera l'affaire.</span>
<span style="color: #0000ff;">user = dbuser
password = dbpasswd
dbname = postfix
query = SELECT mail_addresses FROM valid_emails WHERE mail_addresses='%s'</span></pre>
<p>N&#8217;oubliez pas de recharger la conf de Postfix:</p>
<p><em># /usr/local/etc/rc.d/postfix reload</em></p>
<p>Le tout suivi d&#8217;un:</p>
<p><em>% postmap /usr/local/etc/postfix/relay_recipients</em></p>
<p>Puis on teste notre requête via postmap:</p>
<p><em>% postmap -q &#8220;mailaddress@example.com&#8221; mysql:/usr/local/etc/postfix/relay_recipients</em></p>
<p>Si tout fonctionne, l&#8217;email demandé est affiché sur la sortie standard, sinon rien n&#8217;est affiché et il faut tout vous retaper du début <img src='http://laurent.cligny.net/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> .</p>
<p>Voilà ! Postfix rejette maintenant tous les mails envoyés à des adresses mails non valides sur notre domaine Active Directory, c&#8217;est déjà ça en moins à traiter pour votre serveur mail adoré.</p>
]]></content:encoded>
			<wfw:commentRss>http://laurent.cligny.net/index.php/2008/09/09/extraire-des-donnees-active-directory-pour-postfix/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

