[enh] first version of class-and-hooks-based update_domains.php, includes ssl certficate mechanism
This commit is contained in:
parent
a194cd80d0
commit
ed7aaa3151
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
----------------------------------------------------------------------
|
||||
LICENSE
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License (GPL)
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
To read the license please visit http://www.gnu.org/copyleft/gpl.html
|
||||
----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Manages APACHE 2.4+ vhosts templates in AlternC 3.5+
|
||||
*
|
||||
* @copyright AlternC-Team 2000-2018 https://alternc.com/
|
||||
*/
|
||||
class m_apache {
|
||||
|
||||
var $shouldreload;
|
||||
|
||||
// only values allowed for https in subdomaines table.
|
||||
var $httpsmodes=array("http","https","both");
|
||||
|
||||
// Slave AlternC instances can know the last reload time thanks to this
|
||||
var $reloadfile="/run/alternc/apache-reload";
|
||||
// Where do we find apache template files ?
|
||||
var $templatedir="/etc/alternc/templates/apache2";
|
||||
// Where do we store all Apache vhosts ?
|
||||
var $vhostroot="/var/lib/alternc/apache-vhost/";
|
||||
|
||||
// launched before any action by updatedomains
|
||||
function hook_updatedomains_web_pre() {
|
||||
$this->shouldreload=false;
|
||||
}
|
||||
|
||||
// launched for each FQDN for which we want a new vhost template
|
||||
function hook_updatedomains_web_add($subdomid) {
|
||||
global $msg,$db;
|
||||
|
||||
$db->query("SELECT sd.*, dt.only_dns, dt.has_https_option, m.login FROM domaines_type dt, sub_domaines sd LEFT JOIN membres m ON m.uid=sd.compte WHERE dt.name=sd.type AND sd.web_action!='OK' AND id=?;",array($subdomid));
|
||||
$db->next_record();
|
||||
$subdom=$db->Record;
|
||||
|
||||
// security : only AlternC account's UIDs
|
||||
if ($subdom["compte"]<1999) {
|
||||
$msg->raise("ERROR","apache","Subdom ".$subdom["id"]." for domain ".$subdom["sub"].".".$subdom["domaine"]." has id ".$subdom["compte"].". Skipped");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// search for the template file:
|
||||
$template = $this->templatedir."/".strtolower($subdom["type"]);
|
||||
if ($subdom["has_https_option"] && in_array($subdom["https"],$this->httpsmodes)) {
|
||||
$template.="-".$subdom["https"];
|
||||
}
|
||||
$template.=".conf";
|
||||
if (!is_file($template)) {
|
||||
$msg->raise("ERROR","apache","Template $template not found for subdom ".$subdom["id"]." for domain ".$subdom["sub"].".".$subdom["domaine"].". Skipped");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$subdom["fqdn"]=$subdom["sub"].(($subdom["sub"])?".":"").$subdom["domaine"];
|
||||
// SSL information $subdom["certificate_id"] may be ZERO => it means "take id 0 which is snakeoil cert"
|
||||
$cert = $ssl->get_certificate_path($subdom["certificate_id"]);
|
||||
if ($cert["chain"]) {
|
||||
$chainline="SSLCertificateChainFile ".$cert["chain"];
|
||||
} else {
|
||||
$chainline="";
|
||||
}
|
||||
// Replace needed vars in template file
|
||||
$tpl=file_get_contents($template);
|
||||
$tpl = strtr($tpl, array(
|
||||
"%%LOGIN%%" => $subdom['login'],
|
||||
"%%fqdn%%" => $subdom['fqdn'],
|
||||
"%%document_root%%" => getuserpath($subdom['login']) . $subdom['valeur'],
|
||||
"%%account_root%%" => getuserpath($subdom['login']),
|
||||
"%%redirect%%" => $subdom['valeur'],
|
||||
"%%UID%%" => $subdom['compte'],
|
||||
"%%GID%%" => $subdom['compte'],
|
||||
"%%mail_account%%" => $subdom['mail'],
|
||||
"%%user%%" => "FIXME",
|
||||
"%%CRT%%" => $cert["cert"],
|
||||
"%%KEY%%" => $cert["key"],
|
||||
"%%CHAINLINE%%" => $chainline,
|
||||
));
|
||||
// and write the template
|
||||
$confdir = $this->vhostroot."/".substr($subdom["compte"],-1)."/".$subdom["compte"];
|
||||
@mkdir($confdir,0755,true);
|
||||
file_put_contents($confdir."/".$subdom["fqdn"].".conf");
|
||||
$this->shouldreload=true;
|
||||
|
||||
return 0; // shell meaning => OK ;)
|
||||
} // hook_updatedomains_web_add
|
||||
|
||||
|
||||
// ------------------------------------------------------------
|
||||
/**
|
||||
* launched for each FQDN for which we want to delete a vhost template
|
||||
*/
|
||||
function hook_updatedomains_web_del($subdom) {
|
||||
$confdir = $this->vhostroot."/".substr($subdom["compte"],-1)."/".$subdom["compte"];
|
||||
@unlink($confdir."/".$subdom["fqdn"].".conf");
|
||||
$this->shouldreload=true;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------
|
||||
/**
|
||||
* launched at the very end of updatedomains
|
||||
*/
|
||||
function hook_updatedomains_web_post() {
|
||||
global $msg;
|
||||
if ($this->shouldreload) {
|
||||
|
||||
// concatenate all files into one
|
||||
$this->concat();
|
||||
|
||||
// reload apache
|
||||
$ret=0;
|
||||
exec("apache2ctl graceful 2>&1",$out,$ret);
|
||||
touch($this->reloadfile);
|
||||
if ($ret!=0) {
|
||||
$msg->raise("ERROR","apache","Error while reloading apache, error code is $ret\n".implode("\n",$out));
|
||||
} else {
|
||||
$msg->raise("INFO","apache","Apache reloaded");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
/**
|
||||
* Concatenate all files under $this->vhostroot
|
||||
* into one (mindepth=2 though),
|
||||
* this function is faster than any shell stuff :D
|
||||
*/
|
||||
private function concat() {
|
||||
global $msg;
|
||||
$d=opendir($this->vhostroot);
|
||||
$f=fopen($this->vhostroot."/vhosts_all.conf.new","wb");
|
||||
if (!$f) {
|
||||
$msg->raise("FATAL","apache","Can't write vhosts_all file");
|
||||
return false;
|
||||
}
|
||||
while (($c=readdir($d))!==false) {
|
||||
if (substr($c,0,1)!="." && is_dir($this->vhostroot."/".$c)) {
|
||||
$this->subconcat($f,$this->vhostroot."/".$c);
|
||||
}
|
||||
}
|
||||
closedir($d);
|
||||
fclose($f);
|
||||
}
|
||||
|
||||
private function subconcat($f,$root) {
|
||||
// recursive cat :)
|
||||
$d=opendir($root);
|
||||
while (($c=readdir($d))!==false) {
|
||||
if (substr($c,0,1)!=".") {
|
||||
if (is_dir($root."/".$c)) {
|
||||
$this->subconcat($f,$root."/".$c); // RECURSIVE CALL
|
||||
}
|
||||
if (is_file($root."/".$c)) {
|
||||
fputs($f,file_get_contents($root."/".$c)."\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($d);
|
||||
}
|
||||
|
||||
} // m_apache
|
||||
|
|
@ -19,11 +19,16 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* bind9 file management class
|
||||
* Manages BIND 9+ zone management templates in AlternC 3.5+
|
||||
*
|
||||
* @copyright AlternC-Team 2000-2017 https://alternc.com/
|
||||
* @copyright AlternC-Team 2000-2018 https://alternc.com/
|
||||
*/
|
||||
class system_bind {
|
||||
class m_bind {
|
||||
|
||||
var $shouldreload;
|
||||
var $shouldreconfig;
|
||||
|
||||
|
||||
var $ZONE_TEMPLATE ="/etc/alternc/templates/bind/templates/zone.template";
|
||||
var $NAMED_TEMPLATE ="/etc/alternc/templates/bind/templates/named.template";
|
||||
var $NAMED_CONF ="/var/lib/alternc/bind/automatic.conf";
|
||||
|
@ -39,6 +44,47 @@ class system_bind {
|
|||
var $cache_domain_summary = array();
|
||||
var $zone_file_directory = '/var/lib/alternc/bind/zones/';
|
||||
|
||||
|
||||
// launched before any action by updatedomains
|
||||
function hook_updatedomains_dns_pre() {
|
||||
$this->shouldreload=false;
|
||||
$this->shouldreconfig=false;
|
||||
}
|
||||
|
||||
// launched for each ZONE for which we want a zone update (or create)
|
||||
function hook_updatedomains_dns_add($domain) {
|
||||
|
||||
}
|
||||
|
||||
// launched for each ZONE for which we want a zone DELETE
|
||||
function hook_updatedomains_dns_del($domain) {
|
||||
|
||||
}
|
||||
|
||||
// launched at the very end of updatedomains
|
||||
function hook_updatedomains_dns_post() {
|
||||
global $msg;
|
||||
if ($this->shouldreload) {
|
||||
$ret=0;
|
||||
exec($this->rndc." reload 2>&1",$out,$ret);
|
||||
if ($ret!=0) {
|
||||
$msg->raise("ERROR","bind","Error while reloading bind, error code is $ret\n".implode("\n",$out));
|
||||
} else {
|
||||
$msg->raise("INFO","bind","Bind reloaded");
|
||||
}
|
||||
}
|
||||
if ($this->shouldreconfig) {
|
||||
$ret=0;
|
||||
exec($this->rndc." reload 2>&1",$out,$ret);
|
||||
if ($ret!=0) {
|
||||
$msg->raise("ERROR","bind","Error while reconfiguring bind, error code is $ret\n".implode("\n",$out));
|
||||
} else {
|
||||
$msg->raise("INFO","bind","Bind reconfigured");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return the part of the conf we got from the database
|
||||
|
@ -521,5 +567,6 @@ class system_bind {
|
|||
}
|
||||
|
||||
|
||||
} /* Class system_bind */
|
||||
|
||||
} // m_bind
|
||||
|
|
@ -1770,10 +1770,15 @@ class m_dom {
|
|||
if ($this->islocked) {
|
||||
$msg->raise("ERROR", "dom", _("--- Program error --- Lock already obtained!"));
|
||||
}
|
||||
while (file_exists($this->fic_lock_cron)) {
|
||||
// wait for the file to disappear, or at most 15min:
|
||||
while (file_exists($this->fic_lock_cron) && filemtime($this->fic_lock_cron)>(time()-900)) {
|
||||
clearstatcache();
|
||||
sleep(2);
|
||||
}
|
||||
@touch($this->fic_lock_cron);
|
||||
$this->islocked = true;
|
||||
// extra safe :
|
||||
register_shutdown_function(array("m_dom","unlock"),1);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1783,12 +1788,13 @@ class m_dom {
|
|||
* return true
|
||||
* @access private
|
||||
*/
|
||||
function unlock() {
|
||||
function unlock($isshutdown=0) {
|
||||
global $msg;
|
||||
$msg->debug("dom", "unlock");
|
||||
if (!$this->islocked) {
|
||||
if (!$isshutdown && !$this->islocked) {
|
||||
$msg->raise("ERROR", "dom", _("--- Program error --- No lock on the domains!"));
|
||||
}
|
||||
@unlink($this->fic_lock_cron);
|
||||
$this->islocked = false;
|
||||
return true;
|
||||
}
|
||||
|
@ -1891,6 +1897,95 @@ class m_dom {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* complex process to manage domain and subdomain updates
|
||||
* Launched every minute by a cron as root
|
||||
* should launch hooks for each domain or subdomain,
|
||||
* so that apache & bind could do their job
|
||||
*/
|
||||
function update_domains() {
|
||||
if (posix_getuid()!=0) {
|
||||
echo "FATAL: please lauch me as root\n";
|
||||
exit();
|
||||
}
|
||||
|
||||
$dom->lock();
|
||||
|
||||
// fix in case we forgot to delete SUBDOMAINS before deleting a DOMAIN
|
||||
$db->query("UPDATE sub_domaines sd, domaines d SET sd.web_action = 'DELETE' WHERE sd.domaine = d.domaine AND sd.compte=d.compte AND d.dns_action = 'DELETE';");
|
||||
|
||||
// Search for things to do on DOMAINS:
|
||||
$db->query("SELECT * FROM domaines WHERE dns_action!='OK';");
|
||||
$alldoms=array();
|
||||
while ($db->next_record()) {
|
||||
$alldoms[$db->Record["id"]]=$db->Record;
|
||||
}
|
||||
// now launch hooks
|
||||
if (count($alldoms)) {
|
||||
$hooks->invoke("hook_updatedomains_dns_pre");
|
||||
foreach($alldoms as $id=>$onedom) {
|
||||
if ($onedom["gesdns"]==0 || $onedom["dns_action"]=="DELETE") {
|
||||
$ret = $hooks->invoke("hook_updatedomains_dns_del",array(array($onedom)));
|
||||
} else {
|
||||
$ret = $hooks->invoke("hook_updatedomains_dns_add",array(array($onedom)));
|
||||
}
|
||||
|
||||
if ($onedom["dns_action"]=="DELETE") {
|
||||
$db->query("DELETE FROM domaines WHERE domaine=?;",array($onedom));
|
||||
} else {
|
||||
// we keep the highest result returned by hooks...
|
||||
rsort($ret,SORT_NUMERIC); $returncode=$ret[0];
|
||||
$db->query("UPDATE domaines SET dns_result=?, dns_action='OK' WHERE domaine=?;",array($returncode,$onedom));
|
||||
}
|
||||
}
|
||||
$hooks->invoke("hook_updatedomains_dns_post");
|
||||
}
|
||||
|
||||
|
||||
// Search for things to do on SUB-DOMAINS:
|
||||
$db->query("SELECT sd.*, dt.only_dns FROM domaines_type dt, sub_domaines sd WHERE dt.name=sd.type AND sd.web_action!='OK';");
|
||||
$alldoms=array();
|
||||
$ignore=array();
|
||||
while ($db->next_record()) {
|
||||
// only_dns=1 => weird, we should not have web_action SET to something else than OK ... anyway, skip it
|
||||
if ($db->Record["only_dns"]) {
|
||||
$ignore[]=$db->Record["id"];
|
||||
} else {
|
||||
$alldoms[$db->Record["id"]]=$db->Record;
|
||||
}
|
||||
}
|
||||
foreach($ignore as $id) {
|
||||
// @FIXME (unsure it's useful) maybe we could check that no file exist for this subdomain ?
|
||||
$db->query("UPDATE sub_domaines SET web_action='OK' WHERE id=?;",array($id));
|
||||
}
|
||||
// now launch hooks
|
||||
if (count($alldoms)) {
|
||||
$hooks->invoke("hook_updatedomains_web_pre");
|
||||
foreach($alldoms as $id=>$subdom) {
|
||||
// is it a delete (DISABLED or DELETE)
|
||||
if ($subdom["web_action"]=="DELETE" || strtoupper(substr($subdom["enable"],0,7))=="DISABLE") {
|
||||
$ret = $hooks->invoke("hook_updatedomains_web_del",array($subdom["id"]));
|
||||
} else {
|
||||
$hooks->invoke("hook_updatedomains_web_before",array($subdom["id"])); // give a chance to get SSL cert before ;)
|
||||
$ret = $hooks->invoke("hook_updatedomains_web_add",array($subdom["id"]));
|
||||
$hooks->invoke("hook_updatedomains_web_after",array($subdom["id"]));
|
||||
}
|
||||
|
||||
if ($subdom["web_action"]=="DELETE") {
|
||||
$db->query("DELETE FROM sub_domaines WHERE id=?;",array($id));
|
||||
} else {
|
||||
// we keep the highest result returned by hooks...
|
||||
rsort($ret,SORT_NUMERIC); $returncode=$ret[0];
|
||||
$db->query("UPDATE sub_domaines SET web_result=?, web_action='OK' WHERE id=?;",array($returncode,$id));
|
||||
}
|
||||
}
|
||||
$hooks->invoke("hook_updatedomains_web_post");
|
||||
}
|
||||
|
||||
$dom->unlock();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return an array with all the needed parameters to generate conf
|
||||
* of a vhost.
|
||||
|
|
|
@ -372,7 +372,34 @@ INSTR(CONCAT(sd.sub,IF(sd.sub!='','.',''),sd.domaine),'.')+1))=?
|
|||
return $db->Record;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Return paths to certificate, key, and chain for a certificate
|
||||
* given it's ID.
|
||||
* @param $id integer the certificate by id
|
||||
* @return array cert, key, chain (not mandatory) with full path.
|
||||
*/
|
||||
function get_certificate_path($id) {
|
||||
global $db, $msg, $cuid;
|
||||
$msg->log("ssl", "get_certificate_path",$id);
|
||||
$id = intval($id);
|
||||
$db->query("SELECT id FROM certificates WHERE id=?;",array($id));
|
||||
if (!$db->next_record()) {
|
||||
$msg->raise("ERROR","ssl", _("Can't find this Certificate"));
|
||||
// Return cert 0 info :)
|
||||
$id=0;
|
||||
}
|
||||
$chain=self::KEY_REPOSITORY."/".floor($id/1000)."/".$id.".chain";
|
||||
if (!file_exists($chain))
|
||||
$chain=false;
|
||||
|
||||
return array(
|
||||
"cert" => self::KEY_REPOSITORY."/".floor($id/1000)."/".$id.".cert",
|
||||
"key" => self::KEY_REPOSITORY."/".floor($id/1000)."/".$id.".key",
|
||||
"chain" => $chain
|
||||
);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Return all the valid certificates that can be used for a specific FQDN
|
||||
* return the list of certificates by order of preference
|
||||
|
@ -492,6 +519,12 @@ INSTR(CONCAT(sd.sub,IF(sd.sub!='','.',''),sd.domaine),'.')+1))=?
|
|||
$msg->raise("ERROR","ssl", _("Can't save the Key/Crt/Chain now. Please try later."));
|
||||
return false;
|
||||
}
|
||||
$this->write_cert_file(array(
|
||||
"id"=>$id,
|
||||
"sslcrt"=>$crt,
|
||||
"sslkey"=>$key,
|
||||
"sslchain"=>$chain
|
||||
));
|
||||
return $id;
|
||||
}
|
||||
|
||||
|
@ -612,6 +645,33 @@ SELECT ?,?,?, FROM_UNIXTIME(?), FROM_UNIXTIME(?), ?, ?, sslcsr FROM certificate
|
|||
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Launched by hosting_functions.sh launched by update_domaines.sh
|
||||
* Action may be create/postinst/delete/enable/disable
|
||||
* Change the template for this domain name to have the proper CERTIFICATE
|
||||
* An algorithm determine the best possible certificate, which may be a BAD one
|
||||
* (like a generic self-signed for localhost as a last chance)
|
||||
*/
|
||||
public function hook_updatedomains_web_before($subdomid) {
|
||||
global $db, $msg, $dom;
|
||||
$msg->log("ssl", "hook_updatedomains_web_before($subdomid)");
|
||||
|
||||
$db->query("SELECT sd.*, dt.only_dns, dt.has_https_option, m.login FROM domaines_type dt, sub_domaines sd LEFT JOIN membres m ON m.uid=sd.compte WHERE dt.name=sd.type AND sd.web_action!='OK' AND id=?;",array($subdomid));
|
||||
$db->next_record();
|
||||
$subdom=$db->Record;
|
||||
$domtype=$dom->domains_type_get($subdom["type"]);
|
||||
// the domain type must be a "dns_only=false" one:
|
||||
if ($domtype["only_dns"]==true) {
|
||||
return; // nothing to do : this domain type does not involve Vhosts
|
||||
}
|
||||
$subdom["fqdn"]=$subdom["sub"].(($subdom["sub"])?".":"").$subdom["domaine"];
|
||||
|
||||
list($cert) = $this->get_valid_certs($fqdn, $subdom["provider"]);
|
||||
// Edit certif_hosts:
|
||||
$db->query("UPDATE sub_domaines SET certificate_id=? WHERE id=?;",array($cert["id"], $subdom["id"]));
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
/** Search for the best certificate for a user and a fqdn
|
||||
|
@ -627,7 +687,25 @@ SELECT ?,?,?, FROM_UNIXTIME(?), FROM_UNIXTIME(?), ?, ?, sslcsr FROM certificate
|
|||
|
||||
// get the first good certificate:
|
||||
list($cert) = $this->get_valid_certs($fqdn, $subdom["provider"]);
|
||||
$this->write_cert_file($cert);
|
||||
// we have the files, let's fill the output array :
|
||||
$output=array(
|
||||
"id" => $cert["id"],
|
||||
"crt" => $CRTDIR . "/" . $cert["id"].".pem",
|
||||
"key" => $CRTDIR . "/" . $cert["id"].".key",
|
||||
);
|
||||
if (file_exists($CRTDIR . "/" . $cert["id"].".chain")) {
|
||||
$output["chain"] = $CRTDIR . "/" . $cert["id"].".chain";
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Write certificate file into KEY_REPOSITORY
|
||||
* @param $cert array an array with ID sslcrt sslkey sslchain
|
||||
*/
|
||||
function write_cert_file($cert) {
|
||||
// we split the certificates by 1000
|
||||
$CRTDIR = self::KEY_REPOSITORY . "/" . floor($cert["id"]/1000);
|
||||
@mkdir($CRTDIR,0750,true);
|
||||
|
@ -659,16 +737,6 @@ SELECT ?,?,?, FROM_UNIXTIME(?), FROM_UNIXTIME(?), ?, ?, sslcsr FROM certificate
|
|||
chmod($CRTDIR . "/" . $cert["id"].".chain",0640);
|
||||
}
|
||||
}
|
||||
// we have the files, let's fill the output array :
|
||||
$output=array(
|
||||
"id" => $cert["id"],
|
||||
"crt" => $CRTDIR . "/" . $cert["id"].".pem",
|
||||
"key" => $CRTDIR . "/" . $cert["id"].".key",
|
||||
);
|
||||
if (file_exists($CRTDIR . "/" . $cert["id"].".chain")) {
|
||||
$output["chain"] = $CRTDIR . "/" . $cert["id"].".chain";
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
#!/bin/bash
|
||||
# Update domain next-gen by fufroma
|
||||
# This is now done using PHP-only scripting
|
||||
|
||||
/usr/lib/alternc/update_domains.php
|
||||
|
||||
exit
|
||||
|
||||
# legacy code here
|
||||
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
for CONFIG_FILE in \
|
||||
|
|
Loading…
Reference in New Issue