From 77b22d46dd1da1156c71d45177a97c3d96364e13 Mon Sep 17 00:00:00 2001 From: Alan Garcia Date: Tue, 4 Feb 2014 16:14:01 +0000 Subject: [PATCH] Support de dkim et de autoconfig/autodiscover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reste la retrocompatibilité des hooks a faire --- src/generate_bind_conf.php | 181 ++++++++++++++++++++++++++++++++++--- 1 file changed, 167 insertions(+), 14 deletions(-) diff --git a/src/generate_bind_conf.php b/src/generate_bind_conf.php index 59ce248e..18a7b059 100755 --- a/src/generate_bind_conf.php +++ b/src/generate_bind_conf.php @@ -20,7 +20,12 @@ class m_bind_regenerate { var $NAMED_CONF ="/var/lib/alternc/bind/automatic.conf"; var $RNDC ="/usr/sbin/rndc"; + var $dkim_trusted_host_file = "/etc/opendkim/TrustedHosts"; + var $dkim_keytable_file = "/etc/opendkim/KeyTable"; + var $dkim_signingtable_file = "/etc/opendkim/SigningTable"; + var $cache_conf_db = array(); + var $cache_get_persistent = array(); var $cache_zone_file = array(); var $cache_domain_summary = array(); var $zone_file_directory = '/var/lib/alternc/bind/zones/'; @@ -32,6 +37,7 @@ class m_bind_regenerate { // Return the part of the conf we got from the database function conf_from_db($domain=false) { global $db; + // Use cache, fill cache if empty if (empty($this->cache_conf_db)) { $db->query(" select @@ -59,11 +65,13 @@ class m_bind_regenerate { return $this->cache_conf_db; } + // Return full path of the zone configuration file function get_zone_file_uri($domain) { return $this->zone_file_directory.$domain; } function get_zone_file($domain) { + // Use cache, fill cache if empty if (!isset($this->cache_zone_file[$domain]) ) { if (file_exists($this->get_zone_file_uri($domain))) { $this->cache_zone_file[$domain] = @file_get_contents($this->get_zone_file_uri($domain)); @@ -89,16 +97,21 @@ class m_bind_regenerate { $old = $output_array[1][0]; } + // Return max between newly calculated, and old one incremented return max(array($calc,$old)) + 1 ; } // Return lines that are after ;;;END ALTERNC AUTOGENERATE CONFIGURATION function get_persistent($domain) { - preg_match_all('/\;\sEND\sALTERNC\sAUTOGENERATE\sCONFIGURATION(.*)/s', $this->get_zone_file($domain), $output_array); - if (isset($output_array[1][0]) && !empty($output_array[1][0])) { - return $output_array[1][0]; - } - return; + if ( ! isset($this->cache_get_persistent[$domain] )) { + preg_match_all('/\;\s*END\sALTERNC\sAUTOGENERATE\sCONFIGURATION(.*)/s', $this->get_zone_file($domain), $output_array); + if (isset($output_array[1][0]) && !empty($output_array[1][0])) { + $this->cache_get_persistent[$domain] = $output_array[1][0]; + } else { + $this->cache_get_persistent[$domain] = false; + } + } // isset + return $this->cache_get_persistent[$domain]; } function get_zone_header($domain) { @@ -107,23 +120,144 @@ class m_bind_regenerate { function get_domain_summary($domain=false) { global $dom; + + // Use cache if is filled, if not, fill it if (empty($this->cache_domain_summary)) { $this->cache_domain_summary = $dom->get_domain_all_summary(); } + if ($domain) return $this->cache_domain_summary[$domain]; else return $this->cache_domain_summary; } + + function dkim_delete($domain) { + $target_dir = "/etc/opendkim/keys/$domain"; + @unlink("$target_dir/alternc_private"); + @unlink("$target_dir/alternc.txt"); + @rmdir($target_dir); + return true; + } + + // Generate the domain DKIM key + function dkim_generate_key($domain) { + // Stop here if we do not manage the mail + if ( ! $this->get_domain_summary($domain)['gesmx'] ) return; + + $target_dir = "/etc/opendkim/keys/$domain"; + + if (file_exists($target_dir.'/alternc.txt')) return; // Do not generate if exist + + if (! is_dir($target_dir)) mkdir($target_dir); // create dir + + // Generate the key + $old_dir=getcwd(); + chdir($target_dir); + exec('opendkim-genkey -r -d "'.escapeshellarg($domain).'" -s "alternc" '); + chdir($old_dir); + + // opendkim must be owner of the key + chown("$target_dir/alternc.private", 'opendkim'); + chgrp("$target_dir/alternc.private", 'opendkim'); + + return true; // FIXME handle error + } + + // Refresh DKIM configuration: be sure to list the domain having a private key (and only them) + function dkim_refresh_list() { // so ugly... but there is only 1 pass, not 3. Still ugly. + $trusted_host_new = "# WARNING: this file is auto generated by AlternC.\n# Add your changes after the last line\n"; + $keytable_new = "# WARNING: this file is auto generated by AlternC.\n# Add your changes after the last line\n"; + $signingtable_new = "# WARNING: this file is auto generated by AlternC.\n# Add your changes after the last line\n"; + + # Generate automatic entry + foreach ($this->get_domain_summary() as $domain => $ds ) { + // Skip if delete in progress, or if we do not manage dns or mail + if ( ! $ds['gesdns'] || ! $ds['gesmx'] || strtoupper($ds['dns_action']) == 'DELETE' ) continue; + + // Skip if there is no key generated + if (! file_exists("/etc/opendkim/keys/$domain/alternc.txt")) continue; + + // Modif the files. + $trusted_host_new.="$domain\n"; + $keytable_new .="alternc._domainkey.$domain $domain:alternc:/etc/opendkim/keys/$domain/alternc.private\n"; + $signingtable_new.="$domain alternc._domainkey.$domain\n"; + } + $trusted_host_new.="# END AUTOMATIC FILE. ADD YOUR CHANGES AFTER THIS LINE"; + $keytable_new .="# END AUTOMATIC FILE. ADD YOUR CHANGES AFTER THIS LINE"; + $signingtable_new.="# END AUTOMATIC FILE. ADD YOUR CHANGES AFTER THIS LINE"; + + # Get old files + $trusted_host_old=@file_get_contents($this->dkim_trusted_host_file); + $keytable_old =@file_get_contents($this->dkim_keytable_file); + $signingtable_old=@file_get_contents($this->dkim_signingtable_file); + + # Keep manuel entry + preg_match_all('/\#\s*END\ AUTOMATIC\ FILE\.\ ADD\ YOUR\ CHANGES\ AFTER\ THIS\ LINE(.*)/s', $trusted_host_old, $output_array); + if (isset($output_array[1][0]) && !empty($output_array[1][0])) { + $trusted_host_new.=$output_array[1][0]; + } + preg_match_all('/\#\s*END\ AUTOMATIC\ FILE\.\ ADD\ YOUR\ CHANGES\ AFTER\ THIS\ LINE(.*)/s', $keytable_old, $output_array); + if (isset($output_array[1][0]) && !empty($output_array[1][0])) { + $keytable_new.=$output_array[1][0]; + } + preg_match_all('/\#\s*END\ AUTOMATIC\ FILE\.\ ADD\ YOUR\ CHANGES\ AFTER\ THIS\ LINE(.*)/s', $signingtable_old, $output_array); + if (isset($output_array[1][0]) && !empty($output_array[1][0])) { + $signingtable_new.=$output_array[1][0]; + } + + // Save if there are some diff + if ( $trusted_host_new != $trusted_host_old ) { + file_put_contents($this->dkim_trusted_host_file, $trusted_host_new); + } + if ( $keytable_new != $keytable_old ) { + file_put_contents($this->dkim_keytable_file, $keytable_new); + } + if ( $signingtable_new != $signingtable_old ) { + file_put_contents($this->dkim_signingtable_file, $signingtable_new); + } + + } + + function dkim_entry($domain) { + $keyfile="/etc/opendkim/keys/$domain/alternc.txt"; + if (! file_exists($keyfile) && $this->get_domain_summary($domain)['gesmx'] ) { + $this->dkim_generate_key($domain); + } + return @file_get_contents($keyfile); + } + + // Conditionnal generation autoconfig entry for outlook / thunderbird + // If entry with the same name allready exist, skip it. + function mail_autoconfig_entry($domain) { + $zone= implode("\n",$this->conf_from_db($domain))."\n".$this->get_persistent($domain); + + $entry=''; + if ( $this->get_domain_summary($domain)['gesmx'] ) { + // If we manage the mail + + // Check if there is no the same entry (defined or manual) + // can be toto IN A or toto.fqdn.tld. IN A + if (! preg_match("/autoconfig(\s|\.".str_replace('.','\.',$domain)."\.)/", $zone )) { + $entry.="autoconfig IN CNAME %%fqdn%%.\n"; + } + if (! preg_match("/autodiscover(\s|\.".str_replace('.','\.',$domain)."\.)/", $zone )) { + $entry.="autodiscover IN CNAME %%fqdn%%.\n"; + } + } // if gesmx + return $entry; + } // Return a fully generated zone function get_zone($domain) { global $L_FQDN, $L_NS1_HOSTNAME, $L_NS2_HOSTNAME, $L_DEFAULT_MX, $L_DEFAULT_SECONDARY_MX, $L_PUBLIC_IP; - $zone=''; - $zone.=$this->get_zone_header($domain); + $zone =$this->get_zone_header($domain); $zone.=implode("\n",$this->conf_from_db($domain)); $zone.="\n;;;HOOKED ENTRY\n"; - // FIXME ADD HOOKS opendkim, autoconfig toussa.... - $zone.="\n;;;END ALTERNC AUTOGENERATE CONFIGURATION\n"; + + $zone.= $this->dkim_entry($domain); + $zone.= $this->mail_autoconfig_entry($domain); + + $zone.="\n;;;END ALTERNC AUTOGENERATE CONFIGURATION"; $zone.=$this->get_persistent($domain); // FIXME check those vars @@ -161,32 +295,44 @@ class m_bind_regenerate { } function save_zone($domain) { + global $db; + + // Do not save if the zone is LOCKED if ( $this->is_locked($domain)) return false; + // Save file, and apply chmod/chown $file=$this->get_zone_file_uri($domain); file_put_contents($file, $this->get_zone($domain)); chown($file, 'bind'); chmod($file, 640); + + $db->query("UPDATE domaines SET dns_action = 'OK' WHERE domaine = '".mysql_escape_string($domain)."';"); return true; // fixme add tests } + // Delete the zone configuration file function delete_zone($domain) { $file=$this->get_zone_file_uri($domain); if (file_exists($file)) { unlink($file); } + $this->dkim_delete($domain); return; } function reload_named() { + // Generate the new conf file $new_named_conf="// DO NOT EDIT\n// This file is generated by Alternc.\n// Every changes you'll make will be overwrited.\n"; $tpl=file_get_contents($this->NAMED_TEMPLATE); foreach ($this->get_domain_summary() as $domain => $ds ) { if ( ! $ds['gesdns'] || strtoupper($ds['dns_action']) == 'DELETE' ) continue; $new_named_conf.=strtr($tpl, array("@@DOMAINE@@"=>$domain, "@@ZONE_FILE@@"=>$this->get_zone_file_uri($domain))); } + + // Get the actual conf file $old_named_conf = @file_get_contents($this->NAMED_CONF); + // Apply new configuration only if there are some differences if ($old_named_conf != $new_named_conf ) { file_put_contents($this->NAMED_CONF,$new_named_conf); chown($this->NAMED_CONF, 'bind'); @@ -195,28 +341,35 @@ class m_bind_regenerate { } } + // Regenerate bind configuration and load it function regenerate_conf($all=false) { foreach ($this->get_domain_summary() as $domain => $ds ) { - if ( ! $ds['gesdns'] ) continue; - if ( strtoupper($ds['dns_action']) == 'DELETE' ) { + if ( ! $ds['gesdns'] ) continue; // Skip if we do not manage DNS for this domain + + if ( strtoupper($ds['dns_action']) == 'DELETE' ) { $this->delete_zone($domain); } + if ($all || strtoupper($ds['dns_action']) == 'UPDATE' ) { $this->save_zone($domain); $this->reload_zone($domain); // FIXME reload zone hooks } - } + } // end foreach domain + + $this->dkim_refresh_list(); $this->reload_named(); // FIXME reload named hooks + + return; } } // class $bind = new m_bind_regenerate(); -#echo $bind->get_zone('coin.fr'); +$bind->regenerate_conf(true); -echo $bind->regenerate_conf(true); +echo "Forced regeneration of DNS: done.";