[enh] adding DKIM and AUTODISCOVER management in SUB_DOMAINES, finishing BIND update_domains.php

This commit is contained in:
Benjamin Sonntag 2018-07-08 22:03:27 +02:00
parent ed7aaa3151
commit 9b7332f354
7 changed files with 407 additions and 509 deletions

View File

@ -1245,3 +1245,54 @@ function _dovecot_hash($password) {
$hash = _sha512cr($password); $hash = _sha512cr($password);
return '{SHA512-CRYPT}' . $hash; return '{SHA512-CRYPT}' . $hash;
} }
// ------------------------------------------------------------
/**
* Add the line of text $line into file $file.
* do not duplicate (check)
* @param $file string the full path to the file where we should add the line
* @param $line string the line to add (without the termination \n, WILL BE ADDED)
* @return boolean TRUE if the line has been added, or FALSE if the line ALREADY EXISTED
*/
function add_line_to_file($file,$line) {
$f=fopen($file,"rb");
$found=false;
while($s=fgets($f,1024)) {
if (trim($s)==$line) {
$found=true;
return false;
}
}
fclose($f);
$f=fopen($file,"ab");
fputs($f,trim($line)."\n");
fclose($f);
return true;
}
// ------------------------------------------------------------
/**
* Remove the line of text $line from file $file.
* @param $file string the full path to the file where we should remove the line
* @param $line string the line to add (without the termination \n, WILL BE REMOVED)
* @return boolean TRUE if the line has been found and removed, or FALSE if the line DIDN'T EXIST
*/
function del_line_from_file($file,$line) {
$f=fopen($file,"rb");
$g=fopen($file.".new","wb");
$found=false;
while($s=fgets($f,1024)) {
if (trim($s)!=$line) {
fputs($g,$s);
} else {
$found=true;
}
}
fclose($f);
fclose($g);
rename($file.".new",$file); // overwrite atomically
return $found;
}

View File

@ -28,40 +28,131 @@ class m_bind {
var $shouldreload; var $shouldreload;
var $shouldreconfig; var $shouldreconfig;
var $ZONE_TEMPLATE ="/etc/alternc/templates/bind/templates/zone.template"; var $ZONE_TEMPLATE ="/etc/alternc/templates/bind/templates/zone.template";
var $NAMED_TEMPLATE ="/etc/alternc/templates/bind/templates/named.template"; var $NAMED_TEMPLATE ="/etc/alternc/templates/bind/templates/named.template";
var $NAMED_CONF ="/var/lib/alternc/bind/automatic.conf"; var $NAMED_CONF ="/var/lib/alternc/bind/automatic.conf";
var $RNDC ="/usr/sbin/rndc"; var $RNDC ="/usr/sbin/rndc";
var $dkim_trusted_host_file = "/etc/opendkim/TrustedHosts"; var $zone_file_directory = '/var/lib/alternc/bind/zones';
var $dkim_keytable_file = "/etc/opendkim/KeyTable";
var $dkim_signingtable_file = "/etc/opendkim/SigningTable";
var $cache_conf_db = array(); // ------------------------------------------------------------
var $cache_get_persistent = array(); /** Hook launched before any action by updatedomains
var $cache_zone_file = array(); * initialize the reload/reconfig flags used by POST
var $cache_domain_summary = array(); * @NOTE launched as ROOT
var $zone_file_directory = '/var/lib/alternc/bind/zones/'; */
// launched before any action by updatedomains
function hook_updatedomains_dns_pre() { function hook_updatedomains_dns_pre() {
$this->shouldreload=false; $this->shouldreload=false;
$this->shouldreconfig=false; $this->shouldreconfig=false;
} }
// launched for each ZONE for which we want a zone update (or create)
function hook_updatedomains_dns_add($domain) {
// ------------------------------------------------------------
/**
* Hook launched for each ZONE for which we want a zone update (or create)
* update the zone, create it if necessary,
* and ask for reload or reconfig of bind9 depending on what happened
* @NOTE launched as ROOT
*/
function hook_updatedomains_dns_add($dominfo) {
global $L_FQDN,$L_NS1_HOSTNAME,$L_NS2_HOSTNAME,$L_DEFAULT_MX,$L_DEFAULT_SECONDARY_MX,$L_PUBLIC_IP,$L_PUBLIC_IPV6;
$domain = $dominfo["domaine"];
$ttl = $dominfo["zonettl"];
// does it already exist?
if (file_exists($this->zone_file_directory."/".$domain)) {
list($islocked,$serial,$more)=$this->read_zone($domain);
$serial++; // only increment serial for new zones
} else {
$more="";
$serial=date("Ymd")."00";
$islocked=false;
}
if ($islocked) return;
// Prepare a new zonefile from a template
$zone = file_get_contents($this->ZONE_TEMPLATE);
// substitute ALTERNC & domain variables
$zone = strtr($zone, array(
"%%fqdn%%" => "$L_FQDN",
"%%ns1%%" => "$L_NS1_HOSTNAME",
"%%ns2%%" => "$L_NS2_HOSTNAME",
"%%DEFAULT_MX%%" => "$L_DEFAULT_MX",
"%%DEFAULT_SECONDARY_MX%%" => "$L_DEFAULT_SECONDARY_MX",
"@@fqdn@@" => "$L_FQDN",
"@@ns1@@" => "$L_NS1_HOSTNAME",
"@@ns2@@" => "$L_NS2_HOSTNAME",
"@@DEFAULT_MX@@" => "$L_DEFAULT_MX",
"@@DEFAULT_SECONDARY_MX@@" => "$L_DEFAULT_SECONDARY_MX",
"@@DOMAINE@@" => $domain,
"@@SERIAL@@" => $serial,
"@@PUBLIC_IP@@" => "$L_PUBLIC_IP",
"@@PUBLIC_IPV6@@" => "$L_PUBLIC_IPV6",
"@@ZONETTL@@" => $ttl,
));
// add the SUBDOMAIN entries
$zone .= $this->conf_from_db($domain);
// add the "END ALTERNC CONF line";
$zone .= ";;; END ALTERNC AUTOGENERATE CONFIGURATION\n";
// add the manually entered info:
$zone .= $more;
file_put_contents($this->zone_file_directory."/".$domain,$zone);
// add the line into bind9 conf:
if (add_line_to_file(
$this->NAMED_CONF,
trim(strtr(
file_get_contents($this->NAMED_TEMPLATE),
array(
"@@DOMAIN@@" => $DOMAIN,
"@@ZONE_FILE@@" => $this->zone_file_directory."/".$domain
)
)))
) {
$this->shouldreconfig=true;
} else {
$this->shouldreload=true;
}
} }
// launched for each ZONE for which we want a zone DELETE
function hook_updatedomains_dns_del($domain) {
// ------------------------------------------------------------
/**
* Hook launched for each ZONE for which we want a zone DELETE
* remove the zone and its file,
* and if any action happened, ask for bind RECONFIG at posttime
* @NOTE launched as ROOT
*/
function hook_updatedomains_dns_del($dominfo) {
$domain = $dominfo["domaine"];
if (remove_line_from_file(
$this->NAMED_CONF,
trim(strtr(
file_get_contents($this->NAMED_TEMPLATE),
array(
"@@DOMAIN@@" => $domain,
"@@ZONE_FILE@@" => $this->zone_file_directory."/".$domain
)
)))
) {
$this->shouldreconfig=true;
} else {
return;
}
@unlink($this->zone_file_directory."/".$domain);
} }
// launched at the very end of updatedomains
// ------------------------------------------------------------
/**
* Hook function launched at the very end of updatedomains
* here, we just reload OR reconfig (or both) bind9 depending
* on what happened before.
* @NOTE launched as ROOT
*/
function hook_updatedomains_dns_post() { function hook_updatedomains_dns_post() {
global $msg; global $msg;
if ($this->shouldreload) { if ($this->shouldreload) {
@ -85,487 +176,61 @@ class m_bind {
} }
// ------------------------------------------------------------
/** /**
* Return the part of the conf we got from the database * read a zone file for $domain,
* * @param $domain string the domain name
* @return array with 3 informations:
* is the domain locked? (boolean), what's the current serial (integer), the data after alternc conf (string of lines)
*/
function read_zone($domain) {
$f=fopen($this->zone_file_directory."/".$domain,"rb");
$islocked=false;
$more="";
$serial=date("Ymd")."00";
while ($s=fgets($f,4096)) {
if (preg_match("#\;\s*LOCKED:YES#i",$s)) {
$islocked=true;
}
if (preg_match("/\s*(\d{10})\s+\;\sserial\s?/", $s,$mat)) {
$serial=$mat[1];
}
if (preg_match('/\;\s*END\sALTERNC\sAUTOGENERATE\sCONFIGURATION(.*)/s', $s)) {
break;
}
}
while ($s=fgets($f,4096)) {
$more.=$s;
}
return array($islocked,$serial,$more);
}
// ------------------------------------------------------------
/**
* Return the part of the conf we got from the sub_domaines table
* @global m_mysql $db * @global m_mysql $db
* @param string $domain * @param string $domain
* @return array $this->cache_conf_db * @return string a zonefile excerpt
*/ */
function conf_from_db($domain=false) { function conf_from_db($domain) {
global $db; global $db;
// Use cache, fill cache if empty
if (empty($this->cache_conf_db)) {
$db->query(" $db->query("
select SELECT
sd.domaine, REPLACE(REPLACE(dt.entry,'%TARGET%',sd.valeur), '%SUB%', if(length(sd.sub)>0,sd.sub,'@')) AS ENTRY
replace(replace(dt.entry,'%TARGET%',sd.valeur), '%SUB%', if(length(sd.sub)>0,sd.sub,'@')) as entry FROM
from
sub_domaines sd, sub_domaines sd,
domaines_type dt domaines_type dt
where WHERE
sd.type=dt.name sd.type=dt.name
and sd.enable in ('ENABLE', 'ENABLED') AND sd.enable IN ('ENABLE', 'ENABLED')
order by entry ;"); ORDER BY ENTRY ;");
$t=array(); $t=array();
while ($db->next_record()) { while ($db->next_record()) {
$t[$db->f('domaine')][] = $db->f('entry'); $t.= $db->f('entry')."\n";
} }
$this->cache_conf_db = $t; return $t;
} }
if ($domain) {
if (isset($this->cache_conf_db[$domain])) {
return $this->cache_conf_db[$domain];
} else {
return array();
}
} // if domain
return $this->cache_conf_db;
}
/**
* Return full path of the zone configuration file
*
* @param string $domain
* @return string
*/
function get_zone_file_uri($domain) {
return $this->zone_file_directory.$domain;
}
/**
*
* @param string $domain
* @return string zone file path
*/
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));
} else {
$this->cache_zone_file[$domain] = false;
}
}
return $this->cache_zone_file[$domain] ;
}
/**
*
* @param string $domain
* @return string
*/
function get_serial($domain) {
// Return the next serial the domain must have.
// Choose between a generated and an incremented.
// Calculated :
$calc = date('Ymd').'00'."\n";
// Old one :
$old=$calc; // default value
$file = $this->get_zone_file($domain);
preg_match_all("/\s*(\d{10})\s+\;\sserial\s?/", $file, $output_array);
if (isset($output_array[1][0]) && !empty($output_array[1][0])) {
$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
*
* @param string $domain
* @return string
*/
function get_persistent($domain) {
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];
}
/**
*
* @return string
*/
function get_zone_header() {
return file_get_contents($this->ZONE_TEMPLATE);
}
/**
*
* @global m_dom $dom
* @param string $domain
* @return array Retourne un tableau
*/
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;
}
/**
*
* @param string $domain
* @return boolean
*/
function dkim_delete($domain) {
$target_dir = "/etc/opendkim/keys/$domain";
if (file_exists($target_dir)) {
@unlink("$target_dir/alternc_private");
@unlink("$target_dir/alternc.txt");
@rmdir($target_dir);
}
return true;
}
/**
* Generate the domain DKIM key
*
* @param string $domain
* @return null|boolean
*/
function dkim_generate_key($domain) {
// Stop here if we do not manage the mail
$domainInfo = $this->get_domain_summary($domain);
if ( ! $domainInfo['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\n";
$keytable_new .="# END AUTOMATIC FILE. ADD YOUR CHANGES AFTER THIS LINE\n";
$signingtable_new.="# END AUTOMATIC FILE. ADD YOUR CHANGES AFTER THIS LINE\n";
# 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);
}
}
/**
*
* @param string $domain
* @return string
*/
function dkim_entry($domain) {
$keyfile="/etc/opendkim/keys/$domain/alternc.txt";
$domainInfo = $this->get_domain_summary($domain);
if (! file_exists($keyfile) && $domainInfo['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.
*
* @param string $domain
* @return string
*/
function mail_autoconfig_entry($domain) {
$zone= implode("\n",$this->conf_from_db($domain))."\n".$this->get_persistent($domain);
$entry='';
$domainInfo = $this->get_domain_summary($domain);
if ( $domainInfo['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
*
* @global string $L_FQDN
* @global string $L_NS1_HOSTNAME
* @global string $L_NS2_HOSTNAME
* @global string $L_DEFAULT_MX
* @global string $L_DEFAULT_SECONDARY_MX
* @global string $L_PUBLIC_IP
* @param string $domain
* @return string
*/
function get_zone($domain) {
global $L_FQDN, $L_NS1_HOSTNAME, $L_NS2_HOSTNAME, $L_DEFAULT_MX, $L_DEFAULT_SECONDARY_MX, $L_PUBLIC_IP;
$zone =$this->get_zone_header();
$zone.=implode("\n",$this->conf_from_db($domain));
$zone.="\n;;;HOOKED ENTRY\n";
$zone.= $this->dkim_entry($domain);
$zone.= $this->mail_autoconfig_entry($domain);
$zone.="\n;;; END ALTERNC AUTOGENERATE CONFIGURATION\n";
$zone.=$this->get_persistent($domain);
$domainInfo = $this->get_domain_summary($domain);
// FIXME check those vars
$zone = strtr($zone, array(
"%%fqdn%%"=>"$L_FQDN",
"%%ns1%%"=>"$L_NS1_HOSTNAME",
"%%ns2%%"=>"$L_NS2_HOSTNAME",
"%%DEFAULT_MX%%"=>"$L_DEFAULT_MX",
"%%DEFAULT_SECONDARY_MX%%"=>"$L_DEFAULT_SECONDARY_MX",
"@@fqdn@@"=>"$L_FQDN",
"@@ns1@@"=>"$L_NS1_HOSTNAME",
"@@ns2@@"=>"$L_NS2_HOSTNAME",
"@@DEFAULT_MX@@"=>"$L_DEFAULT_MX",
"@@DEFAULT_SECONDARY_MX@@"=>"$L_DEFAULT_SECONDARY_MX",
"@@DOMAINE@@"=>"$domain",
"@@SERIAL@@"=>$this->get_serial($domain),
"@@PUBLIC_IP@@"=>"$L_PUBLIC_IP",
"@@ZONETTL@@"=> $domainInfo['zonettl'],
));
return $zone;
}
/**
*
* @param string $domain
*/
function reload_zone($domain) {
exec($this->RNDC." reload ".escapeshellarg($domain), $output, $return_value);
if ($return_value != 0 ) {
echo "ERROR: Reload zone failed for zone $domain\n";
}
}
/**
* return true if zone is locked
*
* @param string $domain
* @return boolean
*/
function is_locked($domain) {
preg_match_all("/(\;\s*LOCKED:YES)/i", $this->get_zone_file($domain), $output_array);
if (isset($output_array[1][0]) && !empty($output_array[1][0])) {
return true;
}
return false;
}
/**
*
* @global m_mysql $db
* @global m_dom $dom
* @param string $domain
* @return boolean
*/
function save_zone($domain) {
global $db, $dom;
// Do not save if the zone is LOCKED
if ( $this->is_locked($domain)) {
$dom->set_dns_result($domain, "The zone file of this domain is locked. Contact your administrator."); // If edit, change dummy_for_translation
$dom->set_dns_action($domain, 'OK');
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, 0640);
$dom->set_dns_action($domain, 'OK');
return true; // fixme add tests
}
/**
* Delete the zone configuration file
*
* @param string $domain
* @return boolean
*/
function delete_zone($domain) {
$file=$this->get_zone_file_uri($domain);
if (file_exists($file)) {
unlink($file);
}
$this->dkim_delete($domain);
return true;
}
/**
*
* @global m_hooks $hooks
* @return boolean
*/
function reload_named() {
global $hooks;
// 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');
chmod($this->NAMED_CONF, 0640);
exec($this->RNDC." reconfig");
$hooks->invoke_scripts("/usr/lib/alternc/reload.d", array('dns_reconfig') );
}
return true;
}
/**
* Regenerate bind configuration and load it
*
* @global m_hooks $hooks
* @param boolean $all
* @return boolean
*/
function regenerate_conf($all=false) {
global $hooks;
foreach ($this->get_domain_summary() as $domain => $ds ) {
if ( ! $ds['gesdns'] && strtoupper($ds['dns_action']) == 'OK' ) continue; // Skip if we do not manage DNS and is up-to-date for this domain
if ( (strtoupper($ds['dns_action']) == 'DELETE' ) ||
(strtoupper($ds['dns_action']) == 'UPDATE' && $ds['gesdns']==false ) // in case we update the zone to disable DNS management
) {
$this->delete_zone($domain);
continue;
}
if ( ( $all || strtoupper($ds['dns_action']) == 'UPDATE' ) && $ds['gesdns'] ) {
$this->save_zone($domain);
$this->reload_zone($domain);
$hooks->invoke_scripts("/usr/lib/alternc/reload.d", array('dns_reload_zone', $domain) );
}
} // end foreach domain
$this->dkim_refresh_list();
$this->reload_named();
return true;
}
/**
*
*/
private function dummy_for_translation() {
_("The zone file of this domain is locked. Contact your administrator.");
}
} // m_bind } // m_bind

View File

@ -1442,7 +1442,7 @@ class m_dom {
* TRUE sinon. * TRUE sinon.
* *
*/ */
function edit_domain($dom, $dns, $gesmx, $force = false, $ttl = 86400) { function edit_domain($dom, $dns, $gesmx, $force = false, $ttl = 3600) {
global $db, $msg, $hooks; global $db, $msg, $hooks;
$msg->log("dom", "edit_domain", $dom . "/" . $dns . "/" . $gesmx); $msg->log("dom", "edit_domain", $dom . "/" . $dns . "/" . $gesmx);
// Locked ? // Locked ?

View File

@ -912,6 +912,7 @@ ORDER BY
} }
// ------------------------------------------------------------
/** /**
* hook function called by AlternC when a domain is created for * hook function called by AlternC when a domain is created for
* the current user account using the SLAVE DOMAIN feature * the current user account using the SLAVE DOMAIN feature
@ -928,6 +929,7 @@ ORDER BY
} }
// ------------------------------------------------------------
/** /**
* hook function called by AlternC when a domain is created for * hook function called by AlternC when a domain is created for
* the current user account * the current user account
@ -937,7 +939,7 @@ ORDER BY
* @access private * @access private
*/ */
function hook_dom_add_mx_domain($domain_id) { function hook_dom_add_mx_domain($domain_id) {
global $msg, $mem, $db; global $msg, $mem, $db, $L_FQDN;
$msg->log("mail", "hook_dom_add_mx_domain", $domain_id); $msg->log("mail", "hook_dom_add_mx_domain", $domain_id);
$db->query("SELECT value FROM variable where name='mailname_bounce';"); $db->query("SELECT value FROM variable where name='mailname_bounce';");
@ -947,8 +949,9 @@ ORDER BY
} }
$mailname = $db->f("value"); $mailname = $db->f("value");
// set spf & dmarc for this domain // set spf & dmarc for this domain
$db->query("SELECT domaine FROM domaines WHERE id= ?;", array($domain_id)); $db->query("SELECT domaine,compte FROM domaines WHERE id= ?;", array($domain_id));
if ($db->next_record()) { if ($db->next_record()) {
$this->set_dns_autoconf($db->Record["domaine"],$db->Record["compte"]);
if ($spf = variable_get("default_spf_value")) { if ($spf = variable_get("default_spf_value")) {
$this->set_dns_spf($db->Record["domaine"], $spf); $this->set_dns_spf($db->Record["domaine"], $spf);
} }
@ -960,6 +963,7 @@ ORDER BY
} }
// ------------------------------------------------------------
/** /**
* hook function called by variables when a variable is changed * hook function called by variables when a variable is changed
* @access private * @access private
@ -992,6 +996,33 @@ ORDER BY
} }
// ------------------------------------------------------------
/**
* Add dns entries for autodiscover / autoconf on the domain
*/
function set_dns_autoconf($domain,$uid=-1) {
global $db, $L_FQDN, $cuid;
$changed=false;
if ($uid==-1) $uid=$cuid;
$db->query("SELECT domaine,sub,type,valeur FROM sub_domaines WHERE domaine=? AND sub='autodiscover' AND type='autodiscover';",array($domain));
if (!$db->next_record()) {
$db->query("INSERT INTO sub_domaines SET domaine=?, compte=?, sub='autodiscover', type='autodiscover';",array($domain,$uid));
$changed=true;
}
$db->query("SELECT domaine,sub,type,valeur FROM sub_domaines WHERE domaine=? AND sub='autoconfig' AND type='autodiscover';",array($domain));
if (!$db->next_record()) {
$db->query("INSERT INTO sub_domaines SET domaine=?, compte=?, sub='autoconfig', type='autodiscover';",array($domain,$uid));
$changed=true;
}
if ($changed) {
$db->query("UPDATE domaines SET dns_action='UPDATE' WHERE domaine= ?;", array($domain));
}
return $changed;
}
// ------------------------------------------------------------
/** /**
* Set or UPDATE the DNS record for the domain $dom(str) to be $spf * Set or UPDATE the DNS record for the domain $dom(str) to be $spf
* account's login is current and if not it's $login. * account's login is current and if not it's $login.
@ -1022,6 +1053,7 @@ ORDER BY
} }
// ------------------------------------------------------------
/** /**
* Set or UPDATE the DNS record for the domain $dom(str) to be $dmarc * Set or UPDATE the DNS record for the domain $dom(str) to be $dmarc
* account's login is current and if not it's $login. * account's login is current and if not it's $login.
@ -1055,5 +1087,110 @@ ORDER BY
} }
/** Manage DKIM when adding / removing a domain MX management */
var $shouldreloaddkim;
// ------------------------------------------------------------
/**
* Hook launched before doing anything dns-related
*/
function hook_updatedomains_dns_pre() {
global $db;
// for each domain where we don't have the MX or the DNS, remove the DKIM setup
$this->shouldreloaddkim=false;
$db->query("SELECT domaine,gesdns,gesmx FROM domaines WHERE dns_action!='OK';");
$add=array();
$del=array();
while ($db->next_record()) {
if ($db->Record["gesdns"]==0 || $db->Record["gesmx"]==0) {
$del[]=$db->Record["domaine"];
} else {
$add[]=$db->Record["domaine"];
}
}
foreach($add as $domain) {
$this->dkim_add($domain);
}
foreach($del as $domain) {
$this->dkim_del($domain);
}
}
// ------------------------------------------------------------
/**
* Hook launched after doing anything dns-related
*/
function hook_updatedomains_dns_post() {
if ($this->shouldreloaddkim) {
exec("service opendkim reload");
$this->shouldreloaddkim=false;
}
}
// ------------------------------------------------------------
/**
* Add a domain into OpenDKIM configuration
*/
function dkim_add($domain) {
global $db;
$target_dir = "/etc/opendkim/keys/$domain";
if (file_exists($target_dir.'/alternc.txt')) return; // Do not generate if exist
$this->shouldreloaddkim=true;
if (! is_dir($target_dir)) mkdir($target_dir); // create dir
// Generate the key
$old_dir=getcwd();
chdir($target_dir);
exec('opendkim-genkey -b 1200 -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');
// Add line into files:
add_line_to_file("/etc/opendkim/KeyTable","alternc._domainkey.".$domain." ".$domain.":alternc:/etc/opendkim/keys/".$domain."/alternc.private");
add_line_to_file("/etc/opendkim/SigningTable",$domain." alternc._domainkey.".$domain);
// Add subdomaine entry
$dkim_key=$this->dkim_get_entry($domain);
$db->query("INSERT INTO sub_domaines SET domaine=?, compte=?, sub='', type='dkim', valeur=?;",array($uid,$domain,$dkim_key));
// no need to do DNS_ACTION="UPDATE" => we are in the middle of a HOOK, so dns WILL BE reloaded for this domain
}
// ------------------------------------------------------------
/**
* Delete a domain from OpenDKIM configuration
*/
function dkim_del($domain) {
$target_dir = "/etc/opendkim/keys/$domain";
if (file_exists($target_dir)) {
$this->shouldreloaddkim=true;
@unlink("$target_dir/alternc_private");
@unlink("$target_dir/alternc.txt");
@rmdir($target_dir);
del_line_from_file("/etc/opendkim/KeyTable","alternc._domainkey.".$domain." ".$domain.":alternc:/etc/opendkim/keys/".$domain."/alternc.private");
del_line_from_file("/etc/opendkim/SigningTable",$domain." alternc._domainkey.".$domain);
}
}
// ------------------------------------------------------------
/**
* return the content of the TXT information to be added into the DB for DKIM subdomains
* @param $domain string the name of the domain name
* @return string the TXT entry (without quotes)
* or false if an error occurred
**/
function dkim_get_entry($domain) {
$key=file_get_contents("/etc/opendkim/keys/".$domain."/alternc.txt");
if (preg_match('#alternc._domainkey IN TXT "(.*)"#',$key,$mat)) {
return $mat[1];
}
return false;
}
// @TODO hook after reloading DNS zones => if necessary, restart opendkim
} /* Class m_mail */ } /* Class m_mail */

View File

@ -115,7 +115,7 @@ CREATE TABLE IF NOT EXISTS domaines (
noerase tinyint(4) NOT NULL default '0', noerase tinyint(4) NOT NULL default '0',
dns_action enum ('OK','UPDATE','DELETE') NOT NULL default 'UPDATE', dns_action enum ('OK','UPDATE','DELETE') NOT NULL default 'UPDATE',
dns_result varchar(255) not null default '', dns_result varchar(255) not null default '',
zonettl int(10) unsigned NOT NULL default '86400', zonettl int(10) unsigned NOT NULL default '3600',
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE KEY (domaine) UNIQUE KEY (domaine)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
@ -479,7 +479,9 @@ CREATE TABLE IF NOT EXISTS `domaines_type` (
PRIMARY KEY ( `name` ) PRIMARY KEY ( `name` )
) ENGINE=InnoDB COMMENT = 'Type of domains allowed'; ) ENGINE=InnoDB COMMENT = 'Type of domains allowed';
INSERT IGNORE INTO `domaines_type` (name, description, target, entry, compatibility, only_dns, need_dns, advanced, enable) values INSERT IGNORE INTO `domaines_type` (name, description, target, entry, compatibility, only_dns, need_dns, advanced, enable) VALUES
('dkim', 'DKIM Key', 'NONE', '%SUB% IN TXT "%TARGET%"', 'txt,defmx,defmx2,mx,mx2,url,ip,ipv6', true, true, true, 'ADMIN'),
('autodiscover', 'Autodiscover and autoconf for email', 'NONE', '%SUB% IN A @@PUBLIC_IP@@', 'txt,defmx,defmx2,mx,mx2', false, true, true, 'ADMIN'),
('vhost', 'Locally hosted', 'DIRECTORY', '%SUB% IN A @@PUBLIC_IP@@', 'txt,defmx,defmx2,mx,mx2', false, false, false, 'ALL'), ('vhost', 'Locally hosted', 'DIRECTORY', '%SUB% IN A @@PUBLIC_IP@@', 'txt,defmx,defmx2,mx,mx2', false, false, false, 'ALL'),
('url', 'URL redirection', 'URL', '%SUB% IN A @@PUBLIC_IP@@', 'txt,defmx,defmx2', false, false, false, 'ALL'), ('url', 'URL redirection', 'URL', '%SUB% IN A @@PUBLIC_IP@@', 'txt,defmx,defmx2', false, false, false, 'ALL'),
('ip', 'IPv4 redirect', 'IP', '%SUB% IN A %TARGET%', 'url,ip,ipv6,txt,mx,mx2,defmx,defmx2', true, true, false, 'ALL'), ('ip', 'IPv4 redirect', 'IP', '%SUB% IN A %TARGET%', 'url,ip,ipv6,txt,mx,mx2,defmx,defmx2', true, true, false, 'ALL'),

View File

@ -1,13 +1,22 @@
-- upgrade from 3.4.10 and 3.4.11 (a bug prevented them to be inserted :/ )
-- migrating DKIM to be inside sub_domaines table
INSERT IGNORE INTO `domaines_type` (name, description, target, entry, compatibility, only_dns, need_dns, advanced, enable) VALUES
('dkim', 'DKIM Key', 'NONE', '%SUB% IN TXT "%TARGET%"', 'txt,defmx,defmx2,mx,mx2,url,ip,ipv6', true, true, true, 'ADMIN');
-- migrating AUTODISCOVER / AUTOCONF to be inside sub_domaines table
INSERT IGNORE INTO `domaines_type` (name, description, target, entry, compatibility, only_dns, need_dns, advanced, enable) VALUES
('autodiscover', 'Autodiscover and autoconf for email', 'NONE', '%SUB% IN A @@PUBLIC_IP@@', 'txt,defmx,defmx2,mx,mx2', false, true, true, 'ADMIN');
-- upgrade from 3.4.10 and 3.4.11 (a bug prevented them to be inserted :/ )
ALTER TABLE mailbox MODIFY `lastlogin` DATETIME NOT NULL DEFAULT 0; ALTER TABLE mailbox MODIFY `lastlogin` DATETIME NOT NULL DEFAULT 0;
ALTER TABLE mailbox ADD `lastloginsasl` DATETIME NOT NULL DEFAULT 0 AFTER `lastlogin`; ALTER TABLE mailbox ADD `lastloginsasl` DATETIME NOT NULL DEFAULT 0 AFTER `lastlogin`;
ALTER TABLE `domaines` MODIFY `zonettl` INT(10) UNSIGNED NOT NULL default '3600';
ALTER TABLE `membres` MODIFY `pass` varchar(255); -- upgrade to better hashes ($6$, 20000 loops) in membres and ftpusers
ALTER TABLE `ftpusers` MODIFY `encrypted_password` varchar(255); ALTER TABLE `membres` MODIFY `pass` VARCHAR(255);
ALTER TABLE `ftpusers` MODIFY `encrypted_password` VARCHAR(255);
-- upgrade to merge alternc-ssl into alternc + change the way we work on SSL -- upgrade to merge alternc-ssl into alternc + change the way we work on SSL
DROP TABLE IF EXISTS `certif_alias`; DROP TABLE IF EXISTS `certif_alias`;
ALTER TABLE `certificates` ALTER TABLE `certificates`
@ -28,7 +37,8 @@ ALTER TABLE `domaines_type`
UPDATE `domaines_type` SET `has_https_option`=1 WHERE name='vhost'; UPDATE `domaines_type` SET `has_https_option`=1 WHERE name='vhost';
-- Backport old certif_hosts data to sub_domaines -- Backport old certif_hosts data to sub_domaines
UPDATE `sub_domaines` LEFT JOIN `certif_hosts` ON `sub_domaines`.`id` = `certif_hosts`.`sub` SET `sub_domaines`.`certificate_id` = `certif_hosts`.`certif` WHERE 1; UPDATE `sub_domaines` LEFT JOIN `certif_hosts` ON `sub_domaines`.`id` = `certif_hosts`.`sub`
SET `sub_domaines`.`certificate_id` = `certif_hosts`.`certif`;
DROP TABLE IF EXISTS `certif_hosts`; DROP TABLE IF EXISTS `certif_hosts`;
-- Set https status (http,https,both) -- Set https status (http,https,both)
@ -37,21 +47,22 @@ UPDATE `sub_domaines` SET `https` = "both" WHERE `type` LIKE '%-mixssl' AND http
UPDATE `sub_domaines` SET `https` = "http" WHERE https = ''; UPDATE `sub_domaines` SET `https` = "http" WHERE https = '';
UPDATE `sub_domaines` SET `type` = REPLACE(`type`,'-ssl',''); UPDATE `sub_domaines` SET `type` = REPLACE(`type`,'-ssl','');
UPDATE `sub_domaines` SET `type` = REPLACE(`type`,'-mixssl',''); UPDATE `sub_domaines` SET `type` = REPLACE(`type`,'-mixssl','');
-- Disable https status when domains_type don't provide this -- Disable https status when domains_type don't use it
UPDATE `sub_domaines` SET `https` = '' WHERE type IN (select name FROM domaines_type WHERE has_https_option = 0); UPDATE `sub_domaines` SET `https` = '' WHERE type IN (SELECT name FROM domaines_type WHERE has_https_option = 0);
-- When two sudomain exists, we consider sub_domains with http and https feature -- When two subdomain exists, we consider sub_domains with http and https feature
UPDATE sub_domaines AS sd INNER JOIN UPDATE sub_domaines AS sd INNER JOIN
(SELECT MIN(id) id FROM `sub_domaines` GROUP BY domaine,sub,type HAVING count(id) > 1) sd1 (SELECT MIN(id) id FROM `sub_domaines` GROUP BY domaine,sub,type HAVING count(id) > 1) sd1
ON sd.id = sd1.id ON sd.id = sd1.id
SET `https` = "both"; SET `https` = "both";
-- Delete duplicate lines -- Delete duplicate lines
DELETE sd1 FROM sub_domaines sd1, sub_domaines sd2 WHERE sd1.id > sd2.id AND sd1.domaine = sd2.domaine AND sd1.sub = sd2.sub AND sd1.type = sd2.type AND sd1.https <> '' AND sd2.https <> ''; DELETE sd1 FROM sub_domaines sd1, sub_domaines sd2
WHERE sd1.id > sd2.id AND sd1.domaine = sd2.domaine AND sd1.sub = sd2.sub AND sd1.type = sd2.type
AND sd1.https <> '' AND sd2.https <> '';
-- we need to regenerate all vhost, they will be by AlternC.install -- we need to regenerate all vhost, they will be by AlternC.install
-- UPDATE `sub_domaines` SET `web_action` = 'UPDATE'; -- UPDATE `sub_domaines` SET `web_action` = 'UPDATE';
-- change some variable names : -- change some variable names :
UPDATE variable UPDATE variable
@ -72,6 +83,6 @@ DELETE FROM variable WHERE name IN (
'ftp_human_name' 'ftp_human_name'
); );
-- we'd like to prepare IPv6 ;) -- we'd like to prepare for IPv6 ;)
ALTER TABLE `domaines_type` CHANGE `entry` `entry` TEXT DEFAULT ''; ALTER TABLE `domaines_type` CHANGE `entry` `entry` TEXT DEFAULT '';

View File

@ -0,0 +1,32 @@
<?php
// we don't check our AlternC session
if(!chdir("/usr/share/alternc/panel"))
exit(1);
require("/usr/share/alternc/panel/class/config_nochk.php");
$db->query("SELECT * FROM domaines WHERE gesdns=1 AND gesmx=1;");
$add=array();
while ($db->next_record()) {
$add[$db->Record["domaine"]]=$db->Record["compte"];
}
foreach($add as $domain => $id) {
// Convert DKIM keys into SUB_DOMAINES table
if (file_exists("/etc/opendkim/keys/".$domain."/alternc.txt")) {
$dkim_key = $mail->dkim_get_entry($domain);
if ($dkim_key) {
// Add subdomain dkim entry
$db->query("INSERT INTO sub_domaines
SET compte=?, domaine=?, sub='@', valeur=?, type='dkim', web_action='OK', web_result=0, enable='ENABLED';",
array($id, $domain, $dkim_key)
);
// Alternc.INSTALL WILL reload DNS zones anyway, so fear not we don't set dns_action="RELOAD" here.
}
}
// Convert autodiscover into SUB_DOMAINES table
$db->query("INSERT INTO sub_domaines
SET compte=?, domaine=?, sub='@', valeur='', type='autodiscover', web_action='UPDATE', web_result=0, enable='ENABLED';",
array($id, $domain)
);
}