diff --git a/bureau/class/m_ssl.php b/bureau/class/m_ssl.php index dff259d5..7c3bb7c9 100644 --- a/bureau/class/m_ssl.php +++ b/bureau/class/m_ssl.php @@ -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 validendquery("SELECT id FROM certificates WHERE status=".self::STATUS_EXPIRED." AND validendnext_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 validendexpire_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 */