[enh] new generation of the m_ssl class: less code, no shared or ssl_action, simpler searchBestCert(), no duplicates in /var/lib/alternc/ssl/private/ etc. [NEED TESTS]
This commit is contained in:
parent
8dc5c20fa5
commit
9587ff1bfc
|
@ -41,13 +41,10 @@ class m_ssl {
|
|||
const FILTER_PENDING = 1;
|
||||
const FILTER_OK = 2;
|
||||
const FILTER_EXPIRED = 4;
|
||||
const FILTER_SHARED = 8;
|
||||
const SSL_INCRON_FILE = "/var/run/alternc-ssl/generate_certif_alias";
|
||||
|
||||
var $myDomainesTypes = array("vhost-ssl", "vhost-mixssl", "panel-ssl", "roundcube-ssl", "squirrelmail-ssl", "php52-ssl", "php52-mixssl", "url-ssl");
|
||||
|
||||
const KEY_REPOSITORY = "/var/lib/alternc/ssl/private";
|
||||
|
||||
const SPECIAL_CERTIFICATE_ID_PATH = "/var/lib/alternc/ssl/special_id.json";
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -58,48 +55,64 @@ class m_ssl {
|
|||
$this->default_certificate_fqdn=variable_get('default_certificate_fqdn',$L_FQDN,'FQDN of the certificate we will use as a default one before getting a proper one through any provider. If unsure, keep the default');
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/**
|
||||
* Hook to add the "ssl certificate" menu in the Panel
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/**
|
||||
* Return the list of special FQDN for which we'd like to obtain a certificate too.
|
||||
* (apart from sub+domaine from sub_domaines table)
|
||||
* used by providers to get the certs they should generate
|
||||
* also used by update_domaines to choose which cert to use for a specific fqdn
|
||||
*/
|
||||
function hook_menu() {
|
||||
global $quota, $db, $cuid;
|
||||
$q = $quota->getquota("ssl");
|
||||
$obj = null;
|
||||
if ($q['t'] > 0) {
|
||||
$obj = array(
|
||||
'title' => _("SSL Certificates"),
|
||||
'ico' => 'images/ssl.png',
|
||||
'link' => 'toggle',
|
||||
'pos' => 130,
|
||||
'links' => array(),
|
||||
);
|
||||
|
||||
if ($quota->cancreate("ssl")) {
|
||||
$obj['links'][] = array(
|
||||
'ico' => 'images/new.png',
|
||||
'txt' => _("New SSL certificate"),
|
||||
'url' => "ssl_new.php",
|
||||
'class' => '',
|
||||
);
|
||||
}
|
||||
|
||||
// or admin shared >0 !
|
||||
$db->query("SELECT COUNT(*) AS cnt FROM certificates WHERE uid='$cuid' OR shared=1");
|
||||
$used = $q['u'];
|
||||
if ($db->next_record()) {
|
||||
$used = $db->f("cnt");
|
||||
}
|
||||
if ($used > 0) { // if there are some SSL certificates
|
||||
$obj['links'][] = array(
|
||||
'txt' => _("List SSL Certificates"),
|
||||
'url' => "ssl_list.php"
|
||||
);
|
||||
function get_fqdn_specials() {
|
||||
global $L_FQDN;
|
||||
$specials=array($L_FQDN);
|
||||
$variables=array("fqdn_dovecot","fqdn_postfix","fqdn_proftpd","fqdn_mailman");
|
||||
foreach($variables as $var) {
|
||||
$value = variable_get($var,null);
|
||||
if ($value && !in_array($value,$specials)) {
|
||||
$specials[]=$value;
|
||||
}
|
||||
}
|
||||
return $obj;
|
||||
return $specials;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/**
|
||||
* set expired certificates as such :
|
||||
*/
|
||||
function expire_certificates() {
|
||||
$db->query("UPDATE certificates SET status=".self::STATUS_EXPIRED." WHERE status=".self::STATUS_OK." AND validend<NOW();");
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/**
|
||||
* delete old certificates (expired for more than a year)
|
||||
*/
|
||||
function delete_old_certificates() {
|
||||
$db->query("SELECT id FROM certificates WHERE status=".self::STATUS_EXPIRED." AND validend<DATE_SUB(NOW(), INTERVAL 12 MONTH) AND validend!='0000-00-00 00:00:00';");
|
||||
while ($db->next_record()) {
|
||||
$CRTDIR = self::KEY_REPOSITORY . "/" . floor($db->Record["id"]/1000);
|
||||
@unlink($CRTDIR."/".$db->Record["id"].".crt");
|
||||
@unlink($CRTDIR."/".$db->Record["id"].".key");
|
||||
@unlink($CRTDIR."/".$db->Record["id"].".chain");
|
||||
$d=opendir($CRTDIR);
|
||||
$empty=true;
|
||||
while (($c=readdir($d))!==false) {
|
||||
if (is_file($CRTDIR."/".$c)) {
|
||||
$empty=false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
closedir($d);
|
||||
if ($empty) {
|
||||
rmdir($CRTDIR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Return all the SSL certificates for an account (or the searched one)
|
||||
* @param $filter an integer telling which certificate we want to see (see FILTER_* constants above)
|
||||
|
@ -111,28 +124,17 @@ class m_ssl {
|
|||
function get_list(&$filter = null) {
|
||||
global $db, $msg, $cuid;
|
||||
$msg->log("ssl", "get_list");
|
||||
// Expire expired certificates:
|
||||
$db->query("UPDATE certificates SET status=".self::STATUS_EXPIRED." WHERE status=".self::STATUS_OK." AND validend<NOW();");
|
||||
$this->expire_certificates();
|
||||
$r = array();
|
||||
// If we have no filter, we filter by default on pending and ok certificates if there is more than 10 of them for the same user.
|
||||
if (is_null($filter)) {
|
||||
$db->query("SELECT count(*) AS cnt FROM certificates WHERE uid='$cuid' OR shared=1;");
|
||||
$db->next_record();
|
||||
if ($db->f("cnt") > 10) {
|
||||
$filter = (self::FILTER_PENDING | self::FILTER_OK);
|
||||
} else {
|
||||
$filter = (self::FILTER_PENDING | self::FILTER_OK | self::FILTER_EXPIRED | self::FILTER_SHARED);
|
||||
}
|
||||
$filter = (self::FILTER_PENDING | self::FILTER_OK);
|
||||
}
|
||||
// filter the filter values :)
|
||||
$filter = ($filter & (self::FILTER_PENDING | self::FILTER_OK | self::FILTER_EXPIRED | self::FILTER_SHARED));
|
||||
$filter = ($filter & (self::FILTER_PENDING | self::FILTER_OK | self::FILTER_EXPIRED));
|
||||
// Here filter can't be null (and will be returned to the caller !)
|
||||
$sql = "";
|
||||
if ($filter & self::FILTER_SHARED) {
|
||||
$sql = " (uid='$cuid' OR shared=1) ";
|
||||
} else {
|
||||
$sql = " uid='$cuid' ";
|
||||
}
|
||||
$sql = " uid='$cuid' ";
|
||||
$sql.=" AND status IN (-1";
|
||||
if ($filter & self::FILTER_PENDING) {
|
||||
$sql.="," . self::STATUS_PENDING;
|
||||
|
@ -144,7 +146,7 @@ class m_ssl {
|
|||
$sql.="," . self::STATUS_EXPIRED;
|
||||
}
|
||||
$sql.=") ";
|
||||
$db->query("SELECT *, UNIX_TIMESTAMP(validstart) AS validstartts, UNIX_TIMESTAMP(validend) AS validendts FROM certificates WHERE $sql ORDER BY shared, fqdn;");
|
||||
$db->query("SELECT *, UNIX_TIMESTAMP(validstart) AS validstartts, UNIX_TIMESTAMP(validend) AS validendts FROM certificates WHERE $sql ORDER BY validstart DESC;");
|
||||
if ($db->num_rows()) {
|
||||
while ($db->next_record()) {
|
||||
$r[] = $db->Record;
|
||||
|
@ -156,37 +158,17 @@ class m_ssl {
|
|||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Return all the Vhosts of this user using SSL certificates
|
||||
* @return array all the ssl certificate and hosts of this user
|
||||
*/
|
||||
function get_vhosts() {
|
||||
global $db, $msg, $cuid;
|
||||
$msg->log("ssl", "get_vhosts");
|
||||
$r=array();
|
||||
$db->query("SELECT ch.*, UNIX_TIMESTAMP(c.validstart) AS validstartts, UNIX_TIMESTAMP(c.validend) AS validendts, sd.domaine, sd.sub "
|
||||
. "FROM certif_hosts ch LEFT JOIN certificates c ON ch.certif=c.id "
|
||||
. ", sub_domaines sd WHERE sd.id=ch.sub AND ch.uid=$cuid "
|
||||
. "ORDER BY sd.domaine, sd.sub;");
|
||||
if ($db->num_rows()) {
|
||||
while ($db->next_record()) {
|
||||
$r[] = $db->Record;
|
||||
}
|
||||
return $r;
|
||||
} else {
|
||||
$msg->raise("INFO","ssl", _("You currently have no hosting using SSL certificate"));
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Generate a new CSR, a new Private RSA Key, for FQDN.
|
||||
* @param $fqdn string the FQDN of the domain name for which we want a CSR.
|
||||
* a wildcard certificate must start by *.
|
||||
* @param $provider string a provider if necessary
|
||||
* @return integer the Certificate ID created in the MySQL database
|
||||
* or false if an error occurred
|
||||
*/
|
||||
function new_csr($fqdn) {
|
||||
function new_csr($fqdn, $provider="manual") {
|
||||
global $db, $msg, $cuid;
|
||||
$msg->log("ssl", "new_csr");
|
||||
if (substr($fqdn, 0, 2) == "*.") {
|
||||
|
@ -215,7 +197,7 @@ class m_ssl {
|
|||
$csr = openssl_csr_new($dn, $pkey, $config);
|
||||
$csrout = "";
|
||||
openssl_csr_export($csr, $csrout);
|
||||
$db->query("INSERT INTO certificates SET uid='$cuid', status=" . self::STATUS_PENDING . ", shared=0, fqdn='" . addslashes($fqdn) . "', altnames='', validstart=NOW(), sslcsr='" . addslashes($csrout) . "', sslkey='" . addslashes($privKey) . "';");
|
||||
$db->query("INSERT INTO certificates SET uid=?, status=?, fqdn=?, altnames='', validstart=NOW(), sslcsr=?, sslkey=?, provider=?;",array($cuid, self::STATUS_PENDING, $fqdn, $csrout, $privKey, $provider));
|
||||
if (!($id = $db->lastid())) {
|
||||
$msg->raise("ERROR","ssl", _("Can't generate a CSR"));
|
||||
return false;
|
||||
|
@ -223,15 +205,22 @@ class m_ssl {
|
|||
return $id;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Return all informations of a given certificate for the current user.
|
||||
* @param $id integer the certificate by id
|
||||
* @param $anyuser integer if you want to search cert for any user, set this to true
|
||||
* @return array all the informations of the current certificate as a hash.
|
||||
*/
|
||||
function get_certificate($id) {
|
||||
function get_certificate($id, $anyuser=false) {
|
||||
global $db, $msg, $cuid;
|
||||
$msg->log("ssl", "get_certificate");
|
||||
$id = intval($id);
|
||||
$db->query("SELECT *, UNIX_TIMESTAMP(validstart) AS validstartts, UNIX_TIMESTAMP(validend) AS validendts FROM certificates WHERE (uid='$cuid' OR (shared=1 AND status=" . self::STATUS_OK . ") ) AND id='$id';");
|
||||
$sql="";
|
||||
if (!$anyuser) {
|
||||
$sql=" AND uid='".intval($cuid)."' ";
|
||||
}
|
||||
$db->query("SELECT *, UNIX_TIMESTAMP(validstart) AS validstartts, UNIX_TIMESTAMP(validend) AS validendts FROM certificates WHERE id=? $sql;",array($id));
|
||||
if (!$db->next_record()) {
|
||||
$msg->raise("ERROR","ssl", _("Can't find this Certificate"));
|
||||
return false;
|
||||
|
@ -239,63 +228,23 @@ class m_ssl {
|
|||
return $db->Record;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Delete a Certificate for the current user.
|
||||
* @return boolean TRUE if the certificate has been deleted successfully.
|
||||
*/
|
||||
function del_certificate($id) {
|
||||
global $db, $msg, $cuid;
|
||||
$msg->log("ssl", "del_certificate");
|
||||
$id = intval($id);
|
||||
$db->query("SELECT * FROM certificates WHERE uid='$cuid' AND id='$id';");
|
||||
if (!$db->next_record()) {
|
||||
$msg->raise("ERROR","ssl", _("Can't find this Certificate"));
|
||||
return false;
|
||||
}
|
||||
$fqdn = $db->Record["fqdn"];
|
||||
$altnames = $db->Record["altnames"];
|
||||
$db->query("DELETE FROM certificates WHERE uid='$cuid' AND id='$id';");
|
||||
// Update any existing VHOST using this cert/key
|
||||
$this->updateTrigger($fqdn, $altnames);
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Share (or unshare) an ssl certificate
|
||||
* @param $id integer the id of the certificate in the table.
|
||||
* @param $action integer share (1) or unshare (0) this certificate
|
||||
* @return boolean
|
||||
*/
|
||||
function share($id, $action = 1) {
|
||||
global $db, $msg, $cuid;
|
||||
$msg->log("ssl", "share");
|
||||
$id = intval($id);
|
||||
$db->query("SELECT * FROM certificates WHERE uid='$cuid' AND status=" . self::STATUS_OK . " AND id='$id';");
|
||||
if (!$db->next_record()) {
|
||||
$msg->raise("ERROR","ssl", _("Can't find this Certificate"));
|
||||
return false;
|
||||
}
|
||||
if ($action) {
|
||||
$action = 1;
|
||||
$this->updateTrigger($db->Record["fqdn"], $db->Record["altnames"]);
|
||||
} else {
|
||||
$action = 0;
|
||||
}
|
||||
$db->query("UPDATE certificates SET shared=$action WHERE id='$id';");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Return all the valid certificates that can be used for a specific FQDN
|
||||
* return the list of certificates by order of preference (2 lasts bein the default FQDN and the snakeoil if necessary)
|
||||
* return the list of certificates by order of preference
|
||||
* (the 2 last will be the default FQDN and the snakeoil if necessary)
|
||||
* keys: id, provider, crt, chain, key, validstart, validend
|
||||
*/
|
||||
function get_valid_certs($fqdn, $provider="") {
|
||||
global $db, $msg, $cuid;
|
||||
$db->query("SELECT * FROM certificates WHERE status=".self::STATUS_OK." ORDER BY validstart DESC;");
|
||||
$this->expire_certificates();
|
||||
|
||||
$db->query("SELECT *, UNIX_TIMESTAMP(validstart) AS validstartts, UNIX_TIMESTAMP(validend) AS validendts FROM certificates WHERE status=".self::STATUS_OK." ORDER BY validstart DESC;");
|
||||
|
||||
$good=array(); // list of good certificates
|
||||
$bof=array(); // good but not with the right provider
|
||||
$bad=array();
|
||||
$ugly=array(); // good but not with the right provider
|
||||
$bad=array(); // our snakeoil
|
||||
|
||||
$wildcard="*".substr($fqdn,strpos($fqdn,"."));
|
||||
$defaultwild="*".substr($this->default_certificate_fqdn,strpos($this->default_certificate_fqdn,"."));
|
||||
|
||||
|
@ -317,7 +266,7 @@ class m_ssl {
|
|||
if ($provider=="" || $provider==$db->Record["provider"]) {
|
||||
$good[]=$db->Record;
|
||||
} else {
|
||||
$bof[]=$db->Record;
|
||||
$ugly[]=$db->Record;
|
||||
}
|
||||
}
|
||||
// search for the default one, the one used by the panel
|
||||
|
@ -338,16 +287,16 @@ class m_ssl {
|
|||
$bad=$db->Record;
|
||||
}
|
||||
}
|
||||
// TODO : manages BAD (default) and UGLY (snakeoil)
|
||||
}
|
||||
// add the one with the bad provider
|
||||
if (count($bof)) {
|
||||
$good=array_merge($good,$bof);
|
||||
if (count($ugly)) {
|
||||
$good=array_merge($good,$ugly);
|
||||
}
|
||||
// add the panel/default one
|
||||
if (count($bad)) {
|
||||
$good[]=$bad;
|
||||
}
|
||||
// $ugly Add the Snakeoil : #0
|
||||
// Add the Snakeoil : #0
|
||||
$db->query("SELECT * FROM certificates WHERE id=0;");
|
||||
if ($db->next_record()) {
|
||||
$good[]=$db->Record;
|
||||
|
@ -356,52 +305,15 @@ class m_ssl {
|
|||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Return all the subdomains that can be ssl-enabled for the current account.
|
||||
* @return array of strings : all the subdomains.
|
||||
* Excludes the one for which a cert is already available
|
||||
*/
|
||||
function get_new_advice() {
|
||||
global $db, $msg, $cuid;
|
||||
$msg->log("ssl", "get_new_advice");
|
||||
$r = array();
|
||||
// my certificates, either OK or PENDING (not expired) or the SHARED one (only OK then)
|
||||
$db->query("SELECT fqdn FROM certificates WHERE
|
||||
(uid='$cuid' AND status IN (" . self::STATUS_PENDING . "," . self::STATUS_OK . ") )
|
||||
OR (shared=1 AND status=" . self::STATUS_OK . ")
|
||||
ORDER BY shared, fqdn;");
|
||||
$r = array();
|
||||
while ($db->next_record()) {
|
||||
$r[] = $db->f("fqdn");
|
||||
}
|
||||
// Now we get all our subdomains for certain domaines_types
|
||||
$db->query("SELECT sub,domaine FROM sub_domaines WHERE compte='$cuid' AND type IN ('vhost', 'url', 'roundcube', 'squirrelmail', 'panel', 'php52');");
|
||||
$advice = array();
|
||||
while ($db->next_record()) {
|
||||
$me = $db->f("sub");
|
||||
if ($me) {
|
||||
$me.=".";
|
||||
}
|
||||
$me.=$db->f("domaine");
|
||||
if (!in_array($me, $r) && !in_array($me, $advice)) {
|
||||
$advice[] = $me;
|
||||
}
|
||||
if (!in_array("*." . $db->f("domaine"), $r) && !in_array("*." . $db->f("domaine"), $advice)) {
|
||||
$advice[] = "*." . $db->f("domaine");
|
||||
}
|
||||
}
|
||||
sort($advice);
|
||||
return($advice);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Import an existing ssl Key, Certificate and (maybe) a Chained Cert
|
||||
* @param $key string the X.509 PEM-encoded RSA key
|
||||
* @param $crt string the X.509 PEM-encoded certificate, which *must*
|
||||
* be the one signinf the private RSA key in $key
|
||||
* @param $crt string the X.509 PEM-encoded certificate, which *must*
|
||||
* be the one signing the private RSA key in $key (we will check that anyway...)
|
||||
* @param $chain string the X.509 PEM-encoded list of SSL Certificate chain if intermediate authorities
|
||||
* @return integer the ID of the newly created certificate in the table
|
||||
* @return string the ssl cert provider
|
||||
* TODO: check that the chain is effectively a chain to the CRT ...
|
||||
* @param $provider string the ssl cert provider
|
||||
* @return integer the ID of the newly created certificate in the table
|
||||
* or false if an error occurred
|
||||
*/
|
||||
function import_cert($key, $crt, $chain = "", $provider = "") {
|
||||
|
@ -426,17 +338,20 @@ class m_ssl {
|
|||
$msg->raise("ERROR","ssl", _("Certificate already exists in database"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Everything is PERFECT and has been thoroughly checked, let's insert those in the DB !
|
||||
$sql = "INSERT INTO certificates SET uid='?', status=?, shared=0, fqdn=?, altnames=?, validstart=FROM_UNIXTIME(?), validend=FROM_UNIXTIME(?), sslkey=?, sslcrt=?, sslchain=?, provider=?;";
|
||||
$db->query($sql,array($cuid,self::STATUS_OK,$fqdn,$altnames,intval($validstart),intval($validend),$key,$crt,$chain,$provider));
|
||||
$db->query(
|
||||
"INSERT INTO certificates SET uid='?', status=?, shared=0, fqdn=?, altnames=?, validstart=FROM_UNIXTIME(?), validend=FROM_UNIXTIME(?), sslkey=?, sslcrt=?, sslchain=?, provider=?;",
|
||||
array($cuid,self::STATUS_OK,$fqdn,$altnames,intval($validstart),intval($validend),$key,$crt,$chain,$provider)
|
||||
);
|
||||
if (!($id = $db->lastid())) {
|
||||
$msg->raise("ERROR","ssl", _("Can't save the Key/Crt/Chain now. Please try later."));
|
||||
return false;
|
||||
}
|
||||
$this->updateTrigger($fqdn, $altnames);
|
||||
return $id;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Import an ssl certificate into an existing certificate entry in the DB.
|
||||
* (finalize an enrollment process)
|
||||
|
@ -465,15 +380,17 @@ class m_ssl {
|
|||
$altnames = $this->parseAltNames($crtdata["extensions"]["subjectAltName"]);
|
||||
|
||||
// Everything is PERFECT and has been thoroughly checked, let's insert those in the DB !
|
||||
$sql = "UPDATE certificates SET status=" . self::STATUS_OK . ", shared=0, fqdn='" . addslashes($fqdn) . "', altnames='" . addslashes($altnames) . "', validstart=FROM_UNIXTIME(" . intval($validstart) . "), validend=FROM_UNIXTIME(" . intval($validend) . "), sslcrt='" . addslashes($crt) . "', sslchain='" . addslashes($chain) . "' WHERE id='$certid' ;";
|
||||
if (!$db->query($sql)) {
|
||||
if (!$db->query(
|
||||
"UPDATE certificates SET status=?, fqdn=?, altnames=?, validstart=FROM_UNIXTIME(?), validend=FROM_UNIXTIME(?), sslcrt=?, sslchain=? WHERE id=?;",
|
||||
array(self::STATUS_OK, $fqdn, $altnames, $validstart, $validend, $crt, $chain, $certid)
|
||||
)) {
|
||||
$msg->raise("ERROR","ssl", _("Can't save the Crt/Chain now. Please try later."));
|
||||
return false;
|
||||
}
|
||||
$this->updateTrigger($fqdn, $altnames);
|
||||
return $certid;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Function called by a hook when an AlternC member is deleted.
|
||||
* @access private
|
||||
|
@ -482,47 +399,11 @@ class m_ssl {
|
|||
function alternc_del_member() {
|
||||
global $db, $msg, $cuid;
|
||||
$msg->log("ssl", "alternc_del_member");
|
||||
$db->query("UPDATE certificates SET ssl_action='DELETE' WHERE uid='$cuid'");
|
||||
$db->query("UPDATE certificates SET uid=2000 WHERE uid=?;",array($cuid));
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Hook which returns the used quota for the $name service for the current user.
|
||||
* @param $name string name of the quota
|
||||
* @return integer the number of service used or false if an error occured
|
||||
* @access private
|
||||
*/
|
||||
function hook_quota_get() {
|
||||
global $db, $msg, $cuid;
|
||||
$msg->log("ssl", "getquota");
|
||||
$q = Array("name" => "ssl", "description" => _("SSL Certificates"), "used" => 0);
|
||||
$db->query("SELECT COUNT(*) AS cnt FROM certificates WHERE uid='$cuid' AND status!=" . self::STATUS_EXPIRED);
|
||||
if ($db->next_record()) {
|
||||
$q['used'] = $db->f("cnt");
|
||||
}
|
||||
return $q;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Launched by functions in this class
|
||||
* when a certificate is validated, expired or shared.
|
||||
* so that existing vhost using expired or self-signed certificates
|
||||
* may have the chance to use a proper one automagically
|
||||
* @param string $fqdn the FQDN of the certificate
|
||||
* @param string $altnames any alternative names this certificate may have.
|
||||
*/
|
||||
public function updateTrigger($fqdn, $altnames = "") {
|
||||
global $db;
|
||||
$fqdns = array($fqdn);
|
||||
$an = explode("\n", $altnames);
|
||||
foreach ($an as $a)
|
||||
if (trim($a))
|
||||
$fqdns[] = trim($a);
|
||||
$db->query("UPDATE sub_domaines SET web_action='UPDATE' WHERE "
|
||||
. "if(LENGTH(sub)>0,CONCAT(sub,'.',domaine),domaine) IN ('" . implode("','", $fqdns) . "') "
|
||||
. "AND type LIKE '%ssl';");
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Launched by hosting_functions.sh launched by update_domaines.sh
|
||||
* Action may be create/postinst/delete/enable/disable
|
||||
|
@ -533,17 +414,21 @@ class m_ssl {
|
|||
public function updateDomain($action, $type, $fqdn, $mail = 0, $value = "") {
|
||||
global $db, $msg;
|
||||
$msg->log("ssl", "update_domain($action,$type,$fqdn)");
|
||||
if (!in_array($type, $this->myDomainesTypes)) {
|
||||
return; // nothing to do : the type is not our to start with ;)
|
||||
|
||||
// the domain type must be a "dns_only=false" one:
|
||||
if (!($domtype=$dom->domains_type_get($type)) || $domtype["dns_only"]==true) {
|
||||
return; // nothing to do : this domain type does not involve Vhosts
|
||||
}
|
||||
|
||||
if ($action == "postinst") {
|
||||
$msg->log("ssl", "update_domain:CREATE($action,$type,$fqdn)");
|
||||
$offset = 0;
|
||||
$found = false;
|
||||
do { // try each subdomain (strtok-style) and search them in sub_domaines table:
|
||||
$db->query("SELECT * FROM sub_domaines WHERE "
|
||||
. "sub='" . substr($fqdn, 0, $offset) . "' AND domaine='" . substr($fqdn, $offset + ($offset != 0)) . "' "
|
||||
. "AND web_action NOT IN ('','OK') AND type='" . $type . "';");
|
||||
$db->query(
|
||||
"SELECT * FROM sub_domaines WHERE sub=? AND domaine=? AND web_action NOT IN ('','OK') AND type=?",
|
||||
array(substr($fqdn, 0, $offset), substr($fqdn, $offset + ($offset != 0)), $type)
|
||||
);
|
||||
if ($db->next_record()) {
|
||||
$found = true;
|
||||
break;
|
||||
|
@ -561,106 +446,66 @@ class m_ssl {
|
|||
// found and $db point to it:
|
||||
$subdom = $db->Record;
|
||||
$TARGET_FILE = "/var/lib/alternc/apache-vhost/" . substr($subdom["compte"], -1) . "/" . $subdom["compte"] . "/" . $fqdn . ".conf";
|
||||
$cert = $this->searchBestCert($subdom["compte"], $fqdn);
|
||||
// DEBUG echo "Return from searchBestCert(" . $subdom["compte"] . "," . $fqdn . ") is "; print_r($cert);
|
||||
// Save crt/key/chain into KEY_REPOSITORY
|
||||
$CRTDIR = self::KEY_REPOSITORY . "/" . $subdom["compte"];
|
||||
@mkdir($CRTDIR);
|
||||
// Don't *overwrite* existing self-signed certificates in KEY_REPOSITORY
|
||||
if (isset($cert["selfsigned"]) &&
|
||||
file_exists($CRTDIR . "/" . $fqdn . ".crt") &&
|
||||
file_exists($CRTDIR . "/" . $fqdn . ".key")) {
|
||||
echo "Self-Signed certificate reused...\n";
|
||||
} else {
|
||||
file_put_contents($CRTDIR . "/" . $fqdn . ".crt", $cert["sslcrt"]);
|
||||
file_put_contents($CRTDIR . "/" . $fqdn . ".key", $cert["sslkey"]);
|
||||
if (isset($cert["sslchain"]) && $cert["sslchain"]) {
|
||||
file_put_contents($CRTDIR . "/" . $fqdn . ".chain", $cert["sslchain"]);
|
||||
}
|
||||
}
|
||||
$cert = $this->searchBestCert($subdom,$fqdn);
|
||||
// $cert[crt/key/chain] are path to the proper files
|
||||
|
||||
// edit apache conf file to set the certificate:
|
||||
$s = file_get_contents($TARGET_FILE);
|
||||
$s = str_replace("%%CRT%%", $CRTDIR . "/" . $fqdn . ".crt", $s);
|
||||
$s = str_replace("%%KEY%%", $CRTDIR . "/" . $fqdn . ".key", $s);
|
||||
$s = str_replace("%%CRT%%", $cert["crt"], $s);
|
||||
$s = str_replace("%%KEY%%", $cert["key"], $s);
|
||||
if (isset($cert["sslchain"]) && $cert["sslchain"]) {
|
||||
$s = str_replace("%%CHAINLINE%%", "SSLCertificateChainFile " . $CRTDIR . "/" . $fqdn . ".chain", $s);
|
||||
$s = str_replace("%%CHAINLINE%%", "SSLCertificateChainFile " . $cert["chain"], $s);
|
||||
} else {
|
||||
$s = str_replace("%%CHAINLINE%%", "", $s);
|
||||
}
|
||||
file_put_contents($TARGET_FILE, $s);
|
||||
// Edit certif_hosts:
|
||||
$db->query("DELETE FROM certif_hosts WHERE sub=" . $subdom["id"] . ";");
|
||||
$db->query("INSERT INTO certif_hosts SET "
|
||||
. "sub=" . intval($subdom["id"]) . ", "
|
||||
. "certif=" . intval($cert["id"]) . ", "
|
||||
. "uid=" . intval($subdom["compte"]) . ";");
|
||||
$db->query("UPDATE sub_domaines SET certificate_id=? WHERE id=?;",array($cert["id"], $subdom["id"]));
|
||||
} // action==create
|
||||
if ($action == "delete") {
|
||||
$msg->log("ssl", "update_domain:DELETE($action,$type,$fqdn)");
|
||||
$offset = 0;
|
||||
$found = false;
|
||||
do { // try each subdomain (strtok-style) and search them in sub_domaines table:
|
||||
$db->query("SELECT * FROM sub_domaines WHERE "
|
||||
. "sub='" . substr($fqdn, 0, $offset) . "' AND domaine='" . substr($fqdn, $offset + ($offset != 0)) . "' "
|
||||
. "AND web_action NOT IN ('','OK') AND type='" . $type . "';");
|
||||
if ($db->next_record()) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
$offset = strpos($fqdn, ".", $offset+1);
|
||||
//No more dot, we prevent an infinite loop
|
||||
if (!$offset) {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
if (!$found) {
|
||||
echo "FATAL: didn't found fqdn $fqdn in sub_domaines table !\n";
|
||||
return;
|
||||
}
|
||||
// found and $db point to it:
|
||||
$subdom = $db->Record;
|
||||
$db->query("DELETE FROM certif_hosts WHERE sub=" . $subdom["id"] . ";");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
/** Search for the best certificate for a user and a fqdn
|
||||
* Return a hash with sslcrt, sslkey and maybe sslchain.
|
||||
* return ANYWAY : if necessary, return a newly created (and stored in KEY_REPOSITORY localhost self-signed certificate...
|
||||
* Return a hash with crt, key and maybe chain.
|
||||
* they are the full path to the best certificate for this FQDN.
|
||||
* if necessary, use "default_certificate_fqdn" or a "snakeoil"
|
||||
* @param $subdom array the subdomain entry from sub_domaines table
|
||||
* @param $fqdn string the fully qualified domain name to search for
|
||||
* @return array an has with crt key chain
|
||||
*/
|
||||
public function searchBestCert($uid, $fqdn) {
|
||||
public function searchBestCert($subdom,$fqdn) {
|
||||
global $db;
|
||||
$uid = intval($uid);
|
||||
// 1st search for a valid certificate in my account or shared by the admin:
|
||||
// the ORDER BY make it so that we try VALID then EXPIRED one (sad)
|
||||
$wildcard = "*." . substr($fqdn, strpos($fqdn, ".") + 1);
|
||||
$db->query("SELECT * FROM certificates WHERE (status=".self::STATUS_OK." OR status=".self::STATUS_EXPIRED.") "
|
||||
. "AND (uid=" . $uid . " OR shared=1) "
|
||||
. "AND (fqdn='" . $fqdn . "' OR fqdn='" . $wildcard . "' OR altnames LIKE '%" . $fqdn . "%') "
|
||||
. "ORDER BY (validstart<=NOW() AND validend>=NOW()) DESC, validstart DESC ");
|
||||
while ($db->next_record()) {
|
||||
// name
|
||||
if ($db->Record["fqdn"] == $fqdn) {
|
||||
return $db->Record;
|
||||
|
||||
// get the first good certificate:
|
||||
list($cert) = $this->get_valid_certs($fqdn, $subdom["provider"]);
|
||||
|
||||
// we split the certificates by 1000
|
||||
$CRTDIR = self::KEY_REPOSITORY . "/" . floor($cert["id"]/1000);
|
||||
@mkdir($CRTDIR);
|
||||
if (
|
||||
!file_exists($CRTDIR . "/" . $cert["id"].".crt") ||
|
||||
!file_exists($CRTDIR . "/" . $cert["id"].".key")) {
|
||||
// write the files (first time we use a certificate)
|
||||
file_put_contents($CRTDIR . "/" . $cert["id"].".crt", $cert["sslcrt"]);
|
||||
file_put_contents($CRTDIR . "/" . $cert["id"].".key", $cert["sslkey"]);
|
||||
if (isset($cert["sslchain"]) && $cert["sslchain"]) {
|
||||
file_put_contents($CRTDIR . "/" . $cert["id"] . ".chain", $cert["sslchain"]);
|
||||
}
|
||||
// or alternative names
|
||||
$altnames = explode("\n", $db->Record["altnames"]);
|
||||
foreach ($altnames as $altname) {
|
||||
if (trim($altname) == $fqdn) {
|
||||
return $db->Record;
|
||||
}
|
||||
}
|
||||
// or wildcard
|
||||
if ($db->Record["fqdn"] == $wildcard) {
|
||||
return $db->Record;
|
||||
}
|
||||
}
|
||||
// not found, we generate a one-time self-signed certificate for this host.
|
||||
$crt = $this->selfSigned($fqdn);
|
||||
$crt["uid"] = $uid;
|
||||
return $crt;
|
||||
// we have the files, let's fill the output array :
|
||||
$output=array(
|
||||
"crt" => $CRTDIR . "/" . $cert["id"].".crt",
|
||||
"key" => $CRTDIR . "/" . $cert["id"].".key",
|
||||
);
|
||||
if (file_exists($CRTDIR . "/" . $cert["id"].".chain")) {
|
||||
$output["chain"] = $CRTDIR . "/" . $cert["id"].".chain";
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Export every information for an AlternC's account
|
||||
* @access private
|
||||
|
@ -682,6 +527,7 @@ class m_ssl {
|
|||
return $str;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Returns the list of alternate names of an X.509 SSL Certificate
|
||||
* from the attribute list.
|
||||
|
@ -697,47 +543,6 @@ class m_ssl {
|
|||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Add (immediately) a global alias to the HTTP
|
||||
* certif_alias table and add it to apache configuration
|
||||
* by launching a incron action.
|
||||
* name is the name of the alias, starting by /
|
||||
* content is the content of the filename stored at this location
|
||||
* If an alias with the same name already exists, return false.
|
||||
* if the alias has been properly defined, return true.
|
||||
* @return boolean
|
||||
*/
|
||||
function alias_add($name, $content) {
|
||||
global $msg, $cuid, $db;
|
||||
$db->query("SELECT name FROM certif_alias WHERE name='" . addslashes($name) . "';");
|
||||
if ($db->next_record()) {
|
||||
$msg->raise("ERROR","ssl", _("Alias already exists"));
|
||||
return false;
|
||||
}
|
||||
$db->query("INSERT INTO certif_alias SET name='" . addslashes($name) . "', content='" . addslashes($content) . "', uid=" . intval($cuid) . ";");
|
||||
touch(self::SSL_INCRON_FILE);
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Removes (immediately) a global alias to the HTTP
|
||||
* certif_alias table and add it to apache configuration
|
||||
* by launching a incron action.
|
||||
* name is the name of the alias, starting by /
|
||||
* @return boolean
|
||||
*/
|
||||
function alias_del($name) {
|
||||
global $msg, $cuid, $db;
|
||||
$db->query("SELECT name FROM certif_alias WHERE name='" . addslashes($name) . "' AND uid=" . intval($cuid) . ";");
|
||||
if (!$db->next_record()) {
|
||||
$msg->raise("ERROR","ssl", _("Alias not found"));
|
||||
return false;
|
||||
}
|
||||
$db->query("DELETE FROM certif_alias WHERE name='" . addslashes($name) . "' AND uid=" . intval($cuid) . ";");
|
||||
touch(self::SSL_INCRON_FILE);
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Check that a crt is a proper certificate
|
||||
* @param $crt string an SSL Certificate
|
||||
|
@ -757,7 +562,7 @@ class m_ssl {
|
|||
$this->error = "";
|
||||
if (trim($key) == "" && !is_null($certid)) {
|
||||
// find it in the DB :
|
||||
$db->query("SELECT sslkey FROM certificates WHERE id=" . intval($certid) . ";");
|
||||
$db->query("SELECT sslkey FROM certificates WHERE id=?;",array(intval($certid)));
|
||||
if (!$db->next_record()) {
|
||||
$this->error.=_("Can't find the private key in the certificate table, please check your form.");
|
||||
return false;
|
||||
|
@ -856,52 +661,6 @@ class m_ssl {
|
|||
return array($crt, $chain, $key, $crtdata);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
/** Generate a self-signed certificate
|
||||
*
|
||||
* @param string $fqdn the fully qualified domain name to set as commonName for the certificate
|
||||
* @return hash an array similar to a certificate DB row containing everything (sslcrt, sslcsr, sslkey, sslchain)
|
||||
*/
|
||||
private function selfSigned($fqdn) {
|
||||
global $msg;
|
||||
putenv("OPENSSL_CONF=/etc/alternc/openssl.cnf");
|
||||
$pkey = openssl_pkey_new();
|
||||
if (!$pkey) {
|
||||
$msg->raise("ERROR","ssl", _("Can't generate a private key (1)"));
|
||||
return false;
|
||||
}
|
||||
$privKey = "";
|
||||
if (!openssl_pkey_export($pkey, $privKey)) {
|
||||
$msg->raise("ERROR","ssl", _("Can't generate a private key (2)"));
|
||||
return false;
|
||||
}
|
||||
$dn = array("commonName" => $fqdn);
|
||||
// override the (not taken from openssl.cnf) digest to use SHA-2 / SHA256 and not SHA-1 or MD5 :
|
||||
$config = array("digest_alg" => "sha256");
|
||||
$csr = openssl_csr_new($dn, $pkey, $config);
|
||||
$csrout = "";
|
||||
openssl_csr_export($csr, $csrout);
|
||||
$crt = openssl_csr_sign($csr, null, $pkey, 3650, $config);
|
||||
$crtout = "";
|
||||
openssl_x509_export($crt, $crtout);
|
||||
return array("id" => 0, "status" => 1, "shared" => 0, "fqdn" => $fqdn, "altnames" => "",
|
||||
"validstart" => date("Y-m-d H:i:s"), "validend" => date("Y-m-d H:i:s", time() + 86400 * 10 * 365.249),
|
||||
"sslcsr" => $csrout, "sslcrt" => $crtout, "sslkey" => $privKey, "sslchain" => "",
|
||||
"selfsigned" => true,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function dummy() {
|
||||
_("Locally hosted forcing HTTPS");
|
||||
_("Locally hosted HTTP and HTTPS");
|
||||
_("HTTPS AlternC panel access");
|
||||
_("HTTPS Roundcube Webmail");
|
||||
_("HTTPS Squirrelmail Webmail");
|
||||
_("php52 forcing HTTPS");
|
||||
_("php52 HTTP and HTTPS");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Class m_ssl */
|
||||
|
|
Loading…
Reference in New Issue