GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

m_ssl   D
last analyzed

Complexity

Total Complexity 118

Size/Duplication

Total Lines 787
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 0

Importance

Changes 0
Metric Value
dl 0
loc 787
rs 4.4444
c 0
b 0
f 0
wmc 118
lcom 2
cbo 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A m_ssl() 0 3 1
B hook_menu() 0 37 5
C get_list() 0 47 9
A get_vhosts() 0 18 3
B new_csr() 0 36 6
A get_certificate() 0 11 2
A del_certificate() 0 16 2
A share() 0 18 3
C get_new_advice() 0 32 8
B import_cert() 0 26 3
B finalize() 0 26 3
A alternc_del_member() 0 6 1
A hook_quota_get() 0 10 2
A updateTrigger() 0 11 3
D updateDomain() 0 84 17
B searchBestCert() 0 32 6
A alternc_export_conf() 0 15 2
A parseAltNames() 0 8 2
A alias_add() 0 11 2
A alias_del() 0 11 2
F check_cert() 0 108 32
B selfSigned() 0 28 3
A dummy() 0 9 1

How to fix   Complexity   

Complex Class

Complex classes like m_ssl often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use m_ssl, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
  ----------------------------------------------------------------------
5
  AlternC - Web Hosting System
6
  Copyright (C) 2000-2014 by the AlternC Development Team.
7
  https://alternc.org/
8
  ----------------------------------------------------------------------
9
  LICENSE
10
11
  This program is free software; you can redistribute it and/or
12
  modify it under the terms of the GNU General Public License (GPL)
13
  as published by the Free Software Foundation; either version 2
14
  of the License, or (at your option) any later version.
15
16
  This program is distributed in the hope that it will be useful,
17
  but WITHOUT ANY WARRANTY; without even the implied warranty of
18
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
  GNU General Public License for more details.
20
21
  To read the license please visit http://www.gnu.org/copyleft/gpl.html
22
  ----------------------------------------------------------------------
23
  Purpose of file: Manage SSL Certificates and HTTPS Hosting
24
  ----------------------------------------------------------------------
25
 */
26
27
// ----------------------------------------------------------------- 
28
/**
29
 * SSL Certificates management class
30
 */
31
class m_ssl {
32
33
    const STATUS_PENDING = 0; // we have a key / csr, but no CRT 
34
    const STATUS_OK = 1; // we have the key, csr, crt, chain
35
    const STATUS_EXPIRED = 99; // The certificate is now expired.
36
37
    public $error = "";
38
39
    // Includes one or more of those flags to see only those certificates 
40
    // when listing them: 
41
    const FILTER_PENDING = 1;
42
    const FILTER_OK = 2;
43
    const FILTER_EXPIRED = 4;
44
    const FILTER_SHARED = 8;
45
    const SSL_INCRON_FILE = "/var/run/alternc-ssl/generate_certif_alias";
46
47
    var $myDomainesTypes = array("vhost-ssl", "vhost-mixssl", "panel-ssl", "roundcube-ssl", "squirrelmail-ssl", "php52-ssl", "php52-mixssl", "url-ssl");
48
49
    const KEY_REPOSITORY = "/var/lib/alternc/ssl/private";
50
51
    // ----------------------------------------------------------------- 
52
    /**
53
     * Constructor
54
     */
55
    function m_ssl() {
56
        
57
    }
58
59
    // ----------------------------------------------------------------- 
60
    /**
61
     * Hook to add the "ssl certificate" menu in the Panel
62
     */
63
    function hook_menu() {
64
        global $quota, $db, $cuid;
65
        $q = $quota->getquota("ssl");
66
        $obj = null;
67
        if ($q['t'] > 0) {
68
            $obj = array(
69
                'title' => _("SSL Certificates"),
70
                'ico' => 'images/ssl.png',
71
                'link' => 'toggle',
72
                'pos' => 130,
73
                'links' => array(),
74
            );
75
76
            if ($quota->cancreate("ssl")) {
77
                $obj['links'][] = array(
78
                    'ico' => 'images/new.png',
79
                    'txt' => _("New SSL certificate"),
80
                    'url' => "ssl_new.php",
81
                    'class' => '',
82
                );
83
            }
84
85
            // or admin shared >0 !
86
            $db->query("SELECT COUNT(*) AS cnt FROM certificates WHERE uid='$cuid' OR shared=1");
87
            $used = $q['u'];
88
            if ($db->next_record()) {
89
                $used = $db->f("cnt");
90
            }
91
            if ($used > 0) { // if there are some SSL certificates
92
                $obj['links'][] = array(
93
                    'txt' => _("List SSL Certificates"),
94
                    'url' => "ssl_list.php"
95
                );
96
            }
97
        }
98
        return $obj;
99
    }
100
101
    // ----------------------------------------------------------------- 
102
    /** Return all the SSL certificates for an account (or the searched one)
103
     * @param $filter an integer telling which certificate we want to see (see FILTER_* constants above)
104
     * the default is showing all certificate, but only Pending and OK certificates, not expired or shared one 
105
     * when there is more than 10.
106
     * @return array all the ssl certificate this user can use 
107
     * (each array is the content of the certificates table)
108
     */
109
    function get_list(&$filter = null) {
110
        global $db, $err, $cuid;
111
        $err->log("ssl", "get_list");
112
        // Expire expired certificates:
113
        $db->query("UPDATE certificates SET status=".self::STATUS_EXPIRED." WHERE status=".self::STATUS_OK." AND validend<NOW();");
114
        $r = array();
115
        // 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.
116
        if (is_null($filter)) {
117
            $db->query("SELECT count(*) AS cnt FROM certificates WHERE uid='$cuid' OR shared=1;");
118
            $db->next_record();
119
            if ($db->f("cnt") > 10) {
120
                $filter = (self::FILTER_PENDING | self::FILTER_OK);
121
            } else {
122
                $filter = (self::FILTER_PENDING | self::FILTER_OK | self::FILTER_EXPIRED | self::FILTER_SHARED);
123
            }
124
        }
125
        // filter the filter values :) 
126
        $filter = ($filter & (self::FILTER_PENDING | self::FILTER_OK | self::FILTER_EXPIRED | self::FILTER_SHARED));
127
        // Here filter can't be null (and will be returned to the caller !)
128
        $sql = "";
129
        if ($filter & self::FILTER_SHARED) {
130
            $sql = " (uid='$cuid' OR shared=1) ";
131
        } else {
132
            $sql = " uid='$cuid' ";
133
        }
134
        $sql.=" AND status IN (-1";
135
        if ($filter & self::FILTER_PENDING) {
136
            $sql.="," . self::STATUS_PENDING;
137
        }
138
        if ($filter & self::FILTER_OK) {
139
            $sql.="," . self::STATUS_OK;
140
        }
141
        if ($filter & self::FILTER_EXPIRED) {
142
            $sql.="," . self::STATUS_EXPIRED;
143
        }
144
        $sql.=") ";
145
        $db->query("SELECT *, UNIX_TIMESTAMP(validstart) AS validstartts, UNIX_TIMESTAMP(validend) AS validendts FROM certificates WHERE $sql ORDER BY shared, fqdn;");
146
        if ($db->num_rows()) {
147
            while ($db->next_record()) {
148
                $r[] = $db->Record;
149
            }
150
            return $r;
151
        } else {
152
            $err->raise("ssl", _("No SSL certificates available"));
153
            return array();
154
        }
155
    }
156
157
    // ----------------------------------------------------------------- 
158
    /** Return all the Vhosts of this user using SSL certificates 
159
     * @return array all the ssl certificate  and hosts of this user 
160
     */
161
    function get_vhosts() {
162
        global $db, $err, $cuid;
163
        $err->log("ssl", "get_vhosts");
164
        $r=array();
165
        $db->query("SELECT ch.*, UNIX_TIMESTAMP(c.validstart) AS validstartts, UNIX_TIMESTAMP(c.validend) AS validendts, sd.domaine, sd.sub "
166
                . "FROM certif_hosts ch LEFT JOIN certificates c ON ch.certif=c.id "
167
                . ", sub_domaines sd WHERE sd.id=ch.sub AND ch.uid=$cuid "
168
                . "ORDER BY sd.domaine, sd.sub;");
169
        if ($db->num_rows()) {
170
            while ($db->next_record()) {
171
                $r[] = $db->Record;
172
            }
173
            return $r;
174
        } else {
175
            $err->raise("ssl", _("You currently have no hosting using SSL certificate"));
176
            return array();
177
        }
178
    }
179
180
    // ----------------------------------------------------------------- 
181
    /** Generate a new CSR, a new Private RSA Key, for FQDN.
182
     * @param $fqdn string the FQDN of the domain name for which we want a CSR.
183
     * a wildcard certificate must start by *.
184
     * @return integer the Certificate ID created in the MySQL database
185
     * or false if an error occurred
186
     */
187
    function new_csr($fqdn) {
188
        global $db, $err, $cuid;
189
        $err->log("ssl", "new_csr");
190
        if (substr($fqdn, 0, 2) == "*.") {
191
            $f = substr($fqdn, 2);
192
        } else {
193
            $f = $fqdn;
194
        }
195
        if (checkfqdn($f)) {
196
            $err->raise("ssl", _("Bad FQDN domain name"));
197
            return false;
198
        }
199
        putenv("OPENSSL_CONF=/etc/alternc/openssl.cnf");
200
        $pkey = openssl_pkey_new();
201
        if (!$pkey) {
202
            $err->raise("ssl", _("Can't generate a private key (1)"));
203
            return false;
204
        }
205
        $privKey = "";
206
        if (!openssl_pkey_export($pkey, $privKey)) {
207
            $err->raise("ssl", _("Can't generate a private key (2)"));
208
            return false;
209
        }
210
        $dn = array("commonName" => $fqdn);
211
        // override the (not taken from openssl.cnf) digest to use SHA-2 / SHA256 and not SHA-1 or MD5 :
212
        $config = array("digest_alg" => "sha256");
213
        $csr = openssl_csr_new($dn, $pkey, $config);
214
        $csrout = "";
215
        openssl_csr_export($csr, $csrout);
216
        $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) . "';");
217
        if (!($id = $db->lastid())) {
218
            $err->raise("ssl", _("Can't generate a CSR"));
219
            return false;
220
        }
221
        return $id;
222
    }
223
224
    // ----------------------------------------------------------------- 
225
    /** Return all informations of a given certificate for the current user.
226
     * @return array all the informations of the current certificate as a hash.
227
     */
228
    function get_certificate($id) {
229
        global $db, $err, $cuid;
230
        $err->log("ssl", "get_certificate");
231
        $id = intval($id);
232
        $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';");
233
        if (!$db->next_record()) {
234
            $err->raise("ssl", _("Can't find this Certificate"));
235
            return false;
236
        }
237
        return $db->Record;
238
    }
239
240
    // ----------------------------------------------------------------- 
241
    /** Delete a Certificate for the current user.
242
     * @return boolean TRUE if the certificate has been deleted successfully.
243
     */
244
    function del_certificate($id) {
245
        global $db, $err, $cuid;
246
        $err->log("ssl", "del_certificate");
247
        $id = intval($id);
248
        $db->query("SELECT * FROM certificates WHERE uid='$cuid' AND id='$id';");
249
        if (!$db->next_record()) {
250
            $err->raise("ssl", _("Can't find this Certificate"));
251
            return false;
252
        }
253
        $fqdn = $db->Record["fqdn"];
254
        $altnames = $db->Record["altnames"];
255
        $db->query("DELETE FROM certificates  WHERE uid='$cuid' AND id='$id';");
256
        // Update any existing VHOST using this cert/key
257
        $this->updateTrigger($fqdn, $altnames);
258
        return true;
259
    }
260
261
    // ----------------------------------------------------------------- 
262
    /** Share (or unshare) an ssl certificate
263
     * @param $id integer the id of the certificate in the table.
264
     * @param $action integer share (1) or unshare (0) this certificate
265
     * @return boolean
266
     */
267
    function share($id, $action = 1) {
268
        global $db, $err, $cuid;
269
        $err->log("ssl", "share");
270
        $id = intval($id);
271
        $db->query("SELECT * FROM certificates WHERE uid='$cuid' AND status=" . self::STATUS_OK . " AND id='$id';");
272
        if (!$db->next_record()) {
273
            $err->raise("ssl", _("Can't find this Certificate"));
274
            return false;
275
        }
276
        if ($action) {
277
            $action = 1;
278
            $this->updateTrigger($db->Record["fqdn"], $db->Record["altnames"]);
279
        } else {
280
            $action = 0;
281
        }
282
        $db->query("UPDATE certificates SET shared=$action WHERE id='$id';");
283
        return true;
284
    }
285
286
    // ----------------------------------------------------------------- 
287
    /** Return all the subdomains that can be ssl-enabled for the current account.
288
     * @return array of strings : all the subdomains. 
289
     * Excludes the one for which a cert is already available
290
     */
291
    function get_new_advice() {
292
        global $db, $err, $cuid;
293
        $err->log("ssl", "get_new_advice");
294
        $r = array();
295
        // my certificates, either OK or PENDING (not expired) or the SHARED one (only OK then)
296
        $db->query("SELECT fqdn FROM certificates WHERE
297
      (uid='$cuid' AND status IN (" . self::STATUS_PENDING . "," . self::STATUS_OK . ") ) 
298
   OR (shared=1 AND status=" . self::STATUS_OK . ") 
299
   ORDER BY shared, fqdn;");
300
        $r = array();
301
        while ($db->next_record()) {
302
            $r[] = $db->f("fqdn");
303
        }
304
        // Now we get all our subdomains for certain domaines_types
305
        $db->query("SELECT sub,domaine FROM sub_domaines WHERE compte='$cuid' AND type IN ('vhost', 'url', 'roundcube', 'squirrelmail', 'panel', 'php52');");
306
        $advice = array();
307
        while ($db->next_record()) {
308
            $me = $db->f("sub");
309
            if ($me) {
310
                $me.=".";
311
            }
312
            $me.=$db->f("domaine");
313
            if (!in_array($me, $r) && !in_array($me, $advice)) {
314
                $advice[] = $me;
315
            }
316
            if (!in_array("*." . $db->f("domaine"), $r) && !in_array("*." . $db->f("domaine"), $advice)) {
317
                $advice[] = "*." . $db->f("domaine");
318
            }
319
        }
320
        sort($advice);
321
        return($advice);
322
    }
323
324
    // ----------------------------------------------------------------- 
325
    /** Import an existing ssl Key, Certificate and (maybe) a Chained Cert
326
     * @param $key string the X.509 PEM-encoded RSA key
327
     * @param $crt string the X.509  PEM-encoded certificate, which *must* 
328
     * be the one signinf the private RSA key in $key
329
     * @param $chain string the X.509 PEM-encoded list of SSL Certificate chain if intermediate authorities
330
     * @return integer the ID of the newly created certificate in the table
331
     * or false if an error occurred
332
     */
333
    function import_cert($key, $crt, $chain = "") {
334
        global $cuid, $err, $db;
335
        $err->log("ssl", "import_cert");
336
337
        $result = $this->check_cert($crt, $chain, $key);
338
        if ($result === false) {
339
            $err->raise("ssl", $this->error);
340
            return false;
341
        }
342
        list($crt, $chain, $key, $crtdata) = $result;
343
344
        $validstart = $crtdata['validFrom_time_t'];
345
        $validend = $crtdata['validTo_time_t'];
346
        $fqdn = $crtdata["subject"]["CN"];
347
        $altnames = $this->parseAltNames($crtdata["extensions"]["subjectAltName"]);
348
349
        // Everything is PERFECT and has been thoroughly checked, let's insert those in the DB !
350
        $sql = "INSERT INTO certificates SET uid='$cuid', status=" . self::STATUS_OK . ", shared=0, fqdn='" . addslashes($fqdn) . "', altnames='" . addslashes($altnames) . "', validstart=FROM_UNIXTIME(" . intval($validstart) . "), validend=FROM_UNIXTIME(" . intval($validend) . "), sslkey='" . addslashes($key) . "', sslcrt='" . addslashes($crt) . "', sslchain='" . addslashes($chain) . "';";
351
        $db->query($sql);
352
        if (!($id = $db->lastid())) {
353
            $err->raise("ssl", _("Can't save the Key/Crt/Chain now. Please try later."));
354
            return false;
355
        }
356
        $this->updateTrigger($fqdn, $altnames);
357
        return $id;
358
    }
359
360
    // ----------------------------------------------------------------- 
361
    /** Import an ssl certificate into an existing certificate entry in the DB.
362
     * (finalize an enrollment process)
363
     * @param $certid integer the ID in the database of the SSL Certificate
364
     * @param $crt string the X.509  PEM-encoded certificate, which *must* 
365
     * be the one signing the private RSA key in certificate $certid
366
     * @param $chain string the X.509 PEM-encoded list of SSL Certificate chain if intermediate authorities
367
     * @return integer the ID of the updated certificate in the table
368
     * or false if an error occurred
369
     */
370
    function finalize($certid, $crt, $chain) {
371
        global $cuid, $err, $db;
372
        $err->log("ssl", "finalize");
373
374
        $certid = intval($certid);
375
        $result = $this->check_cert($crt, $chain, "", $certid);
376
        if ($result === false) {
377
            $err->raise("ssl", $this->error);
378
            return false;
379
        }
380
        list($crt, $chain, $key, $crtdata) = $result;
381
382
        $validstart = $crtdata['validFrom_time_t'];
383
        $validend = $crtdata['validTo_time_t'];
384
        $fqdn = $crtdata["subject"]["CN"];
385
        $altnames = $this->parseAltNames($crtdata["extensions"]["subjectAltName"]);
386
387
        // Everything is PERFECT and has been thoroughly checked, let's insert those in the DB !
388
        $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' ;";
389
        if (!$db->query($sql)) {
390
            $err->raise("ssl", _("Can't save the Crt/Chain now. Please try later."));
391
            return false;
392
        }
393
        $this->updateTrigger($fqdn, $altnames);
394
        return $certid;
395
    }
396
397
    // ----------------------------------------------------------------- 
398
    /** Function called by a hook when an AlternC member is deleted.
399
     * @access private
400
     * TODO: delete unused ssl certificates ?? > do this in the crontab.
401
     */
402
    function alternc_del_member() {
403
        global $db, $err, $cuid;
404
        $err->log("ssl", "alternc_del_member");
405
        $db->query("UPDATE certificates SET ssl_action='DELETE' WHERE uid='$cuid'");
406
        return true;
407
    }
408
409
    // ----------------------------------------------------------------- 
410
    /** Hook which returns the used quota for the $name service for the current user.
411
     * @param $name string name of the quota 
412
     * @return integer the number of service used or false if an error occured
413
     * @access private
414
     */
415
    function hook_quota_get() {
416
        global $db, $err, $cuid;
417
        $err->log("ssl", "getquota");
418
        $q = Array("name" => "ssl", "description" => _("SSL Certificates"), "used" => 0);
419
        $db->query("SELECT COUNT(*) AS cnt FROM certificates WHERE uid='$cuid' AND status!=" . self::STATUS_EXPIRED);
420
        if ($db->next_record()) {
421
            $q['used'] = $db->f("cnt");
422
        }
423
        return $q;
424
    }
425
426
    //  ----------------------------------------------------------------- 
427
    /** Launched by functions in this class
428
     * when a certificate is validated, expired or shared.
429
     * so that existing vhost using expired or self-signed certificates
430
     * may have the chance to use a proper one automagically
431
     * @param string $fqdn the FQDN of the certificate 
432
     * @param string $altnames any alternative names this certificate may have.
433
     */
434
    public function updateTrigger($fqdn, $altnames = "") {
435
        global $db;
436
        $fqdns = array($fqdn);
437
        $an = explode("\n", $altnames);
438
        foreach ($an as $a)
439
            if (trim($a))
440
                $fqdns[] = trim($a);
441
        $db->query("UPDATE sub_domaines SET web_action='UPDATE' WHERE "
442
                . "if(LENGTH(sub)>0,CONCAT(sub,'.',domaine),domaine) IN ('" . implode("','", $fqdns) . "') "
443
                . "AND type LIKE '%ssl';");
444
    }
445
446
    //  ----------------------------------------------------------------- 
447
    /** Launched by hosting_functions.sh launched by update_domaines.sh
448
     * Action may be create/postinst/delete/enable/disable
449
     * Change the template for this domain name to have the proper CERTIFICATE
450
     * An algorithm determine the best possible certificate, which may be a BAD one 
451
     * (like a generic admin-shared or self-signed for localhost as a last chance)
452
     */
453
    public function updateDomain($action, $type, $fqdn, $mail = 0, $value = "") {
0 ignored issues
show
Unused Code introduced by
The parameter $mail is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $value is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
454
        global $db, $err;
455
        $err->log("ssl", "update_domain($action,$type,$fqdn)");
456
        if (!in_array($type, $this->myDomainesTypes)) {
457
            return; // nothing to do : the type is not our to start with ;) 
458
        }
459
        if ($action == "postinst") {
460
            $err->log("ssl", "update_domain:CREATE($action,$type,$fqdn)");
461
            $offset = 0;
462
            $found = false;
463
            do { // try each subdomain (strtok-style) and search them in sub_domaines table:
464
                $db->query("SELECT * FROM sub_domaines WHERE "
465
                        . "sub='" . substr($fqdn, 0, $offset) . "' AND domaine='" . substr($fqdn, $offset + ($offset != 0)) . "' "
466
                        . "AND web_action NOT IN ('','OK') AND type='" . $type . "';");
467
                if ($db->next_record()) {
468
                    $found = true;
469
                    break;
470
                }
471
                $offset = strpos($fqdn, ".", $offset);
472
            } while (true);
473
            if (!$found) {
474
                echo "FATAL: didn't found fqdn $fqdn in sub_domaines table !\n";
475
                return;
476
            }
477
            // found and $db point to it:
478
            $subdom = $db->Record;
479
            $TARGET_FILE = "/var/lib/alternc/apache-vhost/" . substr($subdom["compte"], -1) . "/" . $subdom["compte"] . "/" . $fqdn . ".conf";
480
            $cert = $this->searchBestCert($subdom["compte"], $fqdn);
481
            // DEBUG             echo "Return from searchBestCert(" . $subdom["compte"] . "," . $fqdn . ") is ";            print_r($cert);
482
            // Save crt/key/chain into KEY_REPOSITORY
483
            $CRTDIR = self::KEY_REPOSITORY . "/" . $subdom["compte"];
484
            @mkdir($CRTDIR);
485
            // Don't *overwrite* existing self-signed certificates in KEY_REPOSITORY
486
            if (isset($cert["selfsigned"]) &&
487
                    file_exists($CRTDIR . "/" . $fqdn . ".crt") &&
488
                    file_exists($CRTDIR . "/" . $fqdn . ".key")) {
489
                echo "Self-Signed certificate reused...\n";
490
            } else {
491
                file_put_contents($CRTDIR . "/" . $fqdn . ".crt", $cert["sslcrt"]);
492
                file_put_contents($CRTDIR . "/" . $fqdn . ".key", $cert["sslkey"]);
493
                if (isset($cert["sslchain"]) && $cert["sslchain"]) {
494
                    file_put_contents($CRTDIR . "/" . $fqdn . ".chain", $cert["sslchain"]);
495
                }
496
            }
497
            // edit apache conf file to set the certificate:
498
            $s = file_get_contents($TARGET_FILE);
499
            $s = str_replace("%%CRT%%", $CRTDIR . "/" . $fqdn . ".crt", $s);
500
            $s = str_replace("%%KEY%%", $CRTDIR . "/" . $fqdn . ".key", $s);
501
            if (isset($cert["sslchain"]) && $cert["sslchain"]) {
502
                $s = str_replace("%%CHAINLINE%%", "SSLCertificateChainFile " . $CRTDIR . "/" . $fqdn . ".chain", $s);
503
            } else {
504
                $s = str_replace("%%CHAINLINE%%", "", $s);
505
            }
506
            file_put_contents($TARGET_FILE, $s);
507
            // Edit certif_hosts:
508
            $db->query("DELETE FROM certif_hosts WHERE sub=" . $subdom["id"] . ";");
509
            $db->query("INSERT INTO certif_hosts SET "
510
                    . "sub=" . intval($subdom["id"]) . ", "
511
                    . "certif=" . intval($cert["id"]) . ", "
512
                    . "uid=" . intval($subdom["compte"]) . ";");
513
        } // action==create
514
        if ($action == "delete") {
515
            $err->log("ssl", "update_domain:DELETE($action,$type,$fqdn)");
516
            $offset = 0;
517
            $found = false;
518
            do { // try each subdomain (strtok-style) and search them in sub_domaines table:
519
                $db->query("SELECT * FROM sub_domaines WHERE "
520
                        . "sub='" . substr($fqdn, 0, $offset) . "' AND domaine='" . substr($fqdn, $offset + ($offset != 0)) . "' "
521
                        . "AND web_action NOT IN ('','OK') AND type='" . $type . "';");
522
                if ($db->next_record()) {
523
                    $found = true;
524
                    break;
525
                }
526
                $offset = strpos($fqdn, ".", $offset);
527
            } while (true);
528
            if (!$found) {
529
                echo "FATAL: didn't found fqdn $fqdn in sub_domaines table !\n";
530
                return;
531
            }
532
            // found and $db point to it:
533
            $subdom = $db->Record;
534
            $db->query("DELETE FROM certif_hosts WHERE sub=" . $subdom["id"] . ";");
535
        }
536
    }
537
538
    //  ---------------------------------------------------------------- 
539
    /** Search for the best certificate for a user and a fqdn 
540
     * Return a hash with sslcrt, sslkey and maybe sslchain.
541
     * return ANYWAY : if necessary, return a newly created (and stored in KEY_REPOSITORY localhost self-signed certificate...
542
     */
543
    public function searchBestCert($uid, $fqdn) {
544
        global $db;
545
        $uid = intval($uid);
546
        // 1st search for a valid certificate in my account or shared by the admin:
547
        // the ORDER BY make it so that we try VALID then EXPIRED one (sad)
548
        $wildcard = "*." . substr($fqdn, strpos($fqdn, ".") + 1);
549
        $db->query("SELECT * FROM certificates WHERE (status=".self::STATUS_OK." OR status=".self::STATUS_EXPIRED.") "
550
                . "AND (uid=" . $uid . " OR shared=1) "
551
                . "AND (fqdn='" . $fqdn . "' OR fqdn='" . $wildcard . "' OR altnames LIKE '%" . $fqdn . "%') "
552
                . "ORDER BY (validstart<=NOW() AND validend>=NOW()) DESC, validstart DESC ");
553
        while ($db->next_record()) {
554
	  // name
555
            if ($db->Record["fqdn"] == $fqdn) {
556
                return $db->Record;
557
            }
558
	    // or alternative names 
559
            $altnames = explode("\n", $db->Record["altnames"]);
560
            foreach ($altnames as $altname) {
561
                if (trim($altname) == $fqdn) {
562
                    return $db->Record;
563
                }
564
            }
565
	    // or wildcard
566
	    if ($db->Record["fqdn"] == $wildcard) {
567
	      return $db->Record;
568
	    }
569
        }
570
        // not found, we generate a one-time self-signed certificate for this host.
571
        $crt = $this->selfSigned($fqdn);
572
        $crt["uid"] = $uid;
573
        return $crt;
574
    }
575
576
    // ----------------------------------------------------------------- 
577
    /** Export every information for an AlternC's account
578
     * @access private
579
     * EXPERIMENTAL 'sid' function ;) 
580
     */
581
    function alternc_export_conf() {
582
        global $db, $err, $cuid;
583
        $err->log("ssl", "export");
584
        $str = "  <ssl>";
585
        $db->query("SELECT COUNT(*) AS cnt FROM certificates WHERE uid='$cuid' AND status!=" . self::STATUS_EXPIRED);
586
        while ($db->next_record()) {
587
            $str.="   <id>" . ($db->Record["id"]) . "</id>\n";
588
            $str.="   <csr>" . ($db->Record["sslcsr"]) . "</key>\n";
589
            $str.="   <key>" . ($db->Record["sslkey"]) . "<key>\n";
590
            $str.="   <crt>" . ($db->Record["sslcrt"]) . "</crt>\n";
591
            $str.="   <chain>" . ($db->Record["sslchain"]) . "<chain>\n";
592
        }
593
        $str.=" </ssl>\n";
594
        return $str;
595
    }
596
597
    // ----------------------------------------------------------------- 
598
    /** Returns the list of alternate names of an X.509 SSL Certificate 
599
     * from the attribute list.
600
     * @param $str string the $crtdata["extensions"]["subjectAltName"] from openssl
601
     * @return array an array of FQDNs
602
     */
603
    function parseAltNames($str) {
604
        $mat = array();
605
        if (preg_match_all("#DNS:([^,]*)#", $str, $mat, PREG_PATTERN_ORDER)) {
606
            return implode("\n", $mat[1]);
607
        } else {
608
            return "";
609
        }
610
    }
611
612
    // ----------------------------------------------------------------- 
613
    /** Add (immediately) a global alias to the HTTP 
614
     * certif_alias table and add it to apache configuration
615
     * by launching a incron action. 
616
     * name is the name of the alias, starting by /
617
     * content is the content of the filename stored at this location
618
     * If an alias with the same name already exists, return false.
619
     * if the alias has been properly defined, return true.
620
     * @return boolean
621
     */
622
    function alias_add($name, $content) {
623
        global $err, $cuid, $db;
624
        $db->query("SELECT name FROM certif_alias WHERE name='" . addslashes($name) . "';");
625
        if ($db->next_record()) {
626
            $err->raise("ssl", _("Alias already exists"));
627
            return false;
628
        }
629
        $db->query("INSERT INTO certif_alias SET name='" . addslashes($name) . "', content='" . addslashes($content) . "', uid=" . intval($cuid) . ";");
630
        touch(self::SSL_INCRON_FILE);
631
        return true;
632
    }
633
634
    // ----------------------------------------------------------------- 
635
    /** Removes (immediately) a global alias to the HTTP 
636
     * certif_alias table and add it to apache configuration
637
     * by launching a incron action. 
638
     * name is the name of the alias, starting by /
639
     * @return boolean
640
     */
641
    function alias_del($name) {
642
        global $err, $cuid, $db;
643
        $db->query("SELECT name FROM certif_alias WHERE name='" . addslashes($name) . "' AND uid=" . intval($cuid) . ";");
644
        if (!$db->next_record()) {
645
            $err->raise("ssl", _("Alias not found"));
646
            return false;
647
        }
648
        $db->query("DELETE FROM certif_alias WHERE name='" . addslashes($name) . "' AND uid=" . intval($cuid) . ";");
649
        touch(self::SSL_INCRON_FILE);
650
        return true;
651
    }
652
653
    // ----------------------------------------------------------------- 
654
    /** Check that a crt is a proper certificate
655
     * @param $crt string an SSL Certificate
656
     * @param $chain string is a list of certificates
657
     * @param $key string  is a rsa key associated with certificate 
658
     * @param $certid if no key is specified, use it from this certificate ID in the table
659
     * @return array the crt, chain, key, crtdata(array) after a proper reformatting  
660
     * or false if an error occurred (in that case $this->error is filled)
661
     */
662
    function check_cert($crt, $chain, $key = "", $certid = null) {
663
        global $db;
664
        // Check that the key crt and chain are really SSL certificates and keys
665
        $crt = trim(str_replace("\r\n", "\n", $crt)) . "\n";
666
        $key = trim(str_replace("\r\n", "\n", $key)) . "\n";
667
        $chain = trim(str_replace("\r\n", "\n", $chain)) . "\n";
668
669
        $this->error = "";
670
        if (trim($key) == "" && !is_null($certid)) {
671
            // find it in the DB : 
672
            $db->query("SELECT sslkey FROM certificates WHERE id=" . intval($certid) . ";");
673
            if (!$db->next_record()) {
674
                $this->error.=_("Can't find the private key in the certificate table, please check your form.");
675
                return false;
676
            }
677
            $key = $db->f("sslkey");
678
            $key = trim(str_replace("\r\n", "\n", $key)) . "\n";
679
        }
680
681
        if (substr($crt, 0, 28) != "-----BEGIN CERTIFICATE-----\n" ||
682
                substr($crt, -26, 26) != "-----END CERTIFICATE-----\n") {
683
            $this->error.=_("The certificate must begin by BEGIN CERTIFICATE and end by END CERTIFICATE lines. Please check you pasted it in PEM form.") . "<br>\n";
684
        }
685
        if (trim($chain) &&
686
                (substr($chain, 0, 28) != "-----BEGIN CERTIFICATE-----\n" ||
687
                substr($chain, -26, 26) != "-----END CERTIFICATE-----\n")) {
688
            $this->error.=_("The chained certificate must begin by BEGIN CERTIFICATE and end by END CERTIFICATE lines. Please check you pasted it in PEM form.") . "<br>\n";
689
        }
690
        if ((substr($key, 0, 32) != "-----BEGIN RSA PRIVATE KEY-----\n" ||
691
                substr($key, -30, 30) != "-----END RSA PRIVATE KEY-----\n") &&
692
                (substr($key, 0, 28) != "-----BEGIN PRIVATE KEY-----\n" ||
693
                substr($key, -26, 26) != "-----END PRIVATE KEY-----\n")) {
694
            $this->error.=_("The private key must begin by BEGIN (RSA )PRIVATE KEY and end by END (RSA )PRIVATE KEY lines. Please check you pasted it in PEM form.") . "<br>\n";
695
        }
696
        if ($this->error) {
697
            return false;
698
        }
699
700
        // We split the chained certificates in individuals certificates : 
701
        $chains = array();
702
        $status = 0;
703
        $new = "";
704
        $lines = explode("\n", $chain);
705
        foreach ($lines as $line) {
706
            if ($line == "-----BEGIN CERTIFICATE-----" && $status == 0) {
707
                $status = 1;
708
                $new = $line . "\n";
709
                continue;
710
            }
711
            if ($line == "-----END CERTIFICATE-----" && $status == 1) {
712
                $status = 0;
713
                $new.=$line . "\n";
714
                $chains[] = $new;
715
                $new = "";
716
                continue;
717
            }
718
            if ($status == 1) {
719
                $new.=$line . "\n";
720
            }
721
        }
722
        // here chains contains all the ssl certificates in the chained certs.
723
        // Now we check those using Openssl functions (real check :) ) 
724
        $rchains = array();
725
        $i = 0;
726
        foreach ($chains as $tmpcert) {
727
            $i++;
728
            $tmpr = openssl_x509_read($tmpcert);
729
            if ($tmpr === false) {
730
                $this->error.=sprintf(_("The %d-th certificate in the chain is invalid"), $i) . "<br>\n";
731
            } else {
732
                $rchains[] = $tmpr;
733
            }
734
        }
735
        $rcrt = openssl_x509_read($crt);
736
        $crtdata = openssl_x509_parse($crt);
737
        if ($rcrt === false || $crtdata === false) {
738
            $this->error.=_("The certificate is invalid.") . "<br>\n";
739
        }
740
741
        $rkey = openssl_pkey_get_private($key);
742
        if ($rkey === false) {
743
            $this->error.=_("The private key is invalid.") . "<br>\n";
744
        }
745
        if (!$this->error) {
746
            // check that the private key and the certificates are matching :
747
            if (!openssl_x509_check_private_key($rcrt, $rkey)) {
748
                $this->error.=_("The private key is not the one signed inside the certificate.") . "<br>\n";
749
            }
750
        }
751
        if (!$this->error) {
752
            // Everything is fine, let's recreate crt, chain, key from our internal OpenSSL structures:
753
            if (!openssl_x509_export($rcrt, $crt)) {
754
                $this->error.=_("Can't export your certificate as a string, please check its syntax.") . "<br>\n";
755
            }
756
            $chain = "";
757
            foreach ($rchains as $r) {
758
                if (!openssl_x509_export($r, $tmp)) {
759
                    $this->error.=_("Can't export one of your chained certificates as a string, please check its syntax.") . "<br>\n";
760
                } else {
761
                    $chain.=$tmp;
762
                }
763
            }
764
            if (!openssl_pkey_export($rkey, $key)) {
765
                $this->error.=_("Can't export your private key as a string, please check its syntax.") . "<br>\n";
766
            }
767
        }
768
        return array($crt, $chain, $key, $crtdata);
769
    }
770
771
    // -----------------------------------------------------------------
772
    /** Generate a self-signed certificate
773
     * 
774
     * @param string $fqdn the fully qualified domain name to set as commonName for the certificate
775
     * @return hash an array similar to a certificate DB row containing everything (sslcrt, sslcsr, sslkey, sslchain)
776
     */
777
    private function selfSigned($fqdn) {
778
        global $err;
779
        putenv("OPENSSL_CONF=/etc/alternc/openssl.cnf");
780
        $pkey = openssl_pkey_new();
781
        if (!$pkey) {
782
            $err->raise("ssl", _("Can't generate a private key (1)"));
783
            return false;
784
        }
785
        $privKey = "";
786
        if (!openssl_pkey_export($pkey, $privKey)) {
787
            $err->raise("ssl", _("Can't generate a private key (2)"));
788
            return false;
789
        }
790
        $dn = array("commonName" => $fqdn);
791
        // override the (not taken from openssl.cnf) digest to use SHA-2 / SHA256 and not SHA-1 or MD5 :
792
        $config = array("digest_alg" => "sha256");
793
        $csr = openssl_csr_new($dn, $pkey, $config);
794
        $csrout = "";
795
        openssl_csr_export($csr, $csrout);
796
        $crt = openssl_csr_sign($csr, null, $pkey, 3650, $config);
797
        $crtout = "";
798
        openssl_x509_export($crt, $crtout);
799
        return array("id" => 0, "status" => 1, "shared" => 0, "fqdn" => $fqdn, "altnames" => "",
800
            "validstart" => date("Y-m-d H:i:s"), "validend" => date("Y-m-d H:i:s", time() + 86400 * 10 * 365.249),
801
            "sslcsr" => $csrout, "sslcrt" => $crtout, "sslkey" => $privKey, "sslchain" => "",
802
            "selfsigned" => true,
803
        );
804
    }
805
806
807
    function dummy() {
808
      _("Locally hosted forcing HTTPS");
809
      _("Locally hosted HTTP and HTTPS");
810
      _("HTTPS AlternC panel access");
811
      _("HTTPS Roundcube Webmail");
812
      _("HTTPS Squirrelmail Webmail");
813
      _("php52 forcing HTTPS");
814
      _("php52 HTTP and HTTPS");
815
    }
816
817
}
818
819
/* Class m_ssl */
820