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.
Completed
Push — stable-3.1 ( 714cfc...1fe966 )
by
unknown
07:39
created

m_mail::hook_variable_set()   C

Complexity

Conditions 7
Paths 10

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 24
rs 6.7272
cc 7
eloc 17
nc 10
nop 3
1
<?php
2
3
/*
4
  ----------------------------------------------------------------------
5
  AlternC - Web Hosting System
6
  Copyright (C) 2000-2012 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 Email accounts and aliases.
24
  ----------------------------------------------------------------------
25
 */
26
27
/**
28
 * This class handle emails (pop and/or aliases and even wrapper for internal
29
 * classes) of hosted users.
30
 *
31
 * @copyright    AlternC-Team 2012-09-01 http://alternc.com/
32
 * This class is directly using the following alternc MySQL tables:
33
 * address = any used email address will be defined here, mailbox = pop/imap mailboxes, recipient = redirection from an email to another
34
 * and indirectly the domain class, to know domain names from their id in the DB.
35
 * This class is also defining a few hooks, search ->invoke in the code.
36
 */
37
class m_mail {
38
    /* ----------------------------------------------------------------- */
39
40
    /** domain list for this account
41
     * @access private
42
     */
43
    var $domains;
44
45
    /* ----------------------------------------------------------------- */
46
47
    /** If an email has those chars, 'not nice in shell env' ;) 
48
     * we don't store the email in $mail/u/{user}_domain, but in $mail/_/{address_id}_domain
49
     * @access private
50
     */
51
    var $specialchars = array('"', "'", '\\', '/');
52
53
    /* ----------------------------------------------------------------- */
54
55
    /** If an email has those chars, we will ONLY allow RECIPIENTS, NOT POP/IMAP for DOVECOT !
56
     * Since Dovecot doesn't allow those characters
57
     * @access private
58
     */
59
    var $forbiddenchars = array('"', "'", '\\', '/', '?', '!', '*', '$', '|', '#', '+');
60
61
    /* ----------------------------------------------------------------- */
62
63
    /** Number of results for a pager display
64
     * @access public
65
     */
66
    var $total;
67
    // Human server name for help
68
    var $srv_submission;
69
    var $srv_smtp;
70
    var $srv_smtps;
71
    var $srv_imap;
72
    var $srv_imaps;
73
    var $srv_pop3;
74
    var $srv_pop3s;
75
    var $cache_domain_mail_size = array();
76
    var $enum_domains = array();
77
78
    /* ----------------------------------------------------------------- */
79
80
    /**
81
     * Constructeur
82
     */
83
    function m_mail() {
84
        global $L_FQDN;
85
        $this->srv_submission = variable_get('mail_human_submission', $L_FQDN, 'Human name for mail server (submission protocol), leave empty to disable help', array('desc' => 'Name', 'type' => 'string'));
86
        $this->srv_smtp = variable_get('mail_human_smtp', $L_FQDN, 'Human name for mail server (SMTP protocol), leave empty to disable help', array('desc' => 'Name', 'type' => 'string'));
87
        $this->srv_smtps = variable_get('mail_human_smtps', $L_FQDN, 'Human name for mail server (SMTPS protocol), leave empty to disable help', array('desc' => 'Name', 'type' => 'string'));
88
        $this->srv_imap = variable_get('mail_human_imap', $L_FQDN, 'Human name for IMAP mail server', array('desc' => 'Name', 'type' => 'string'));
89
        $this->srv_imaps = variable_get('mail_human_imaps', $L_FQDN, 'Human name for IMAPS mail server', array('desc' => 'Name', 'type' => 'string'));
90
        $this->srv_pop3 = variable_get('mail_human_pop3', $L_FQDN, 'Human name for POP3 mail server', array('desc' => 'Name', 'type' => 'string'));
91
        $this->srv_pop3s = variable_get('mail_human_pop3s', $L_FQDN, 'Human name for POP3s mail server', array('desc' => 'Name', 'type' => 'string'));
92
    }
93
94
    function hook_menu() {
95
        $obj = array(
96
            'title' => _("Email Addresses"),
97
            'ico' => 'images/mail.png',
98
            'link' => 'toggle',
99
            'pos' => 30,
100
            'links' => array(),
101
        );
102
103
        foreach ($this->enum_domains() as $d) {
104
            $obj['links'][] = array(
105
                'txt' => htmlentities($d["domaine"]) . '&nbsp;' . htmlentities("(" . $d["nb_mail"] . ")"),
106
                'url' => "mail_list.php?domain_id=" . urlencode($d['id']),
107
            );
108
        }
109
110
        return $obj;
111
    }
112
113
    function get_total_size_for_domain($domain) {
114
        global $db;
115
        if (empty($this->cache_domain_mail_size)) {
116
            $db->query("SELECT SUBSTRING_INDEX(user,'@', -1) as domain, SUM(quota_dovecot) AS sum FROM dovecot_view group by domain ;");
117
            while ($db->next_record()) {
118
                $dd = $db->f('domain');
119
                $this->cache_domain_mail_size[$dd] = $db->f('sum');
120
            }
121
        }
122
        if (isset($this->cache_domain_mail_size[$domain])) {
123
            return $this->cache_domain_mail_size[$domain];
124
        }
125
        return 0;
126
    }
127
128
    // FIXME documenter
129
130
    /**
131
     * @param string $domain_id
132
     */
133
    function catchall_getinfos($domain_id) {
134
        global $dom, $db;
135
        $rr = array(
136
            'mail_id' => '',
137
            'domain' => $dom->get_domain_byid($domain_id),
138
            'target' => '',
139
            'type' => '',
140
        );
141
142
        $db->query("select r.recipients as dst, a.id mail_id from address a, recipient r where a.domain_id = $domain_id and r.address_id = a.id and a.address='';");
143
        if ($db->next_record()) {
144
            $rr['target'] = $db->f('dst');
145
            $rr['mail_id'] = $db->f('mail_id');
146
        }
147
148
        // Does it redirect to a specific mail or to a domain
149
        if (empty($rr['target'])) {
150
            $rr['type'] = 'none';
151
        } elseif (substr($rr['target'], 0, 1) == '@') {
152
            $rr['type'] = 'domain';
153
        } else {
154
            $rr['type'] = 'mail';
155
        }
156
157
        return $rr;
158
    }
159
160
    /**
161
     * @param string $domain_id
162
     */
163
    function catchall_del($domain_id) {
164
        $catch = $this->catchall_getinfos($domain_id);
165
        if (empty($catch['mail_id'])) {
166
            return false;
167
        }
168
        return $this->delete($catch['mail_id']);
169
    }
170
171
    /**
172
     * @param string $domain_id
173
     * @param string $target
174
     */
175
    function catchall_set($domain_id, $target) {
176
        global $err;
177
        $target = rtrim($target);
178
        if (substr_count($target, '@') == 0) { // Pas de @
179
            $target = '@' . $target;
180
        }
181
182
        if (substr($target, 0, 1) == '@') { // le premier caractere est un @
183
            // FIXME validate domain
184
        } else { // ca doit être un mail
185
            if (!filter_var($target, FILTER_VALIDATE_EMAIL)) {
186
                $err->raise("mail", _("The email you entered is syntaxically incorrect"));
187
                return false;
188
            }
189
        }
190
        $this->catchall_del($domain_id);
191
        $err->error = "";
192
        return $this->create_alias($domain_id, '', $target, "catchall", true);
193
    }
194
195
    /* ----------------------------------------------------------------- */
196
197
    /** get_quota (hook for quota class), returns the number of used 
198
     * service for a quota-bound service
199
     * @param $name string the named quota we want
200
     * @return the number of used service for the specified quota, 
201
     * or false if I'm not the one for the named quota
202
     */
203
    function hook_quota_get() {
204
        global $db, $err, $cuid;
205
        $err->log("mail", "getquota");
206
        $q = Array("name" => "mail", "description" => _("Email addresses"), "used" => 0);
207
        $db->query("SELECT COUNT(*) AS cnt FROM address a, domaines d WHERE a.domain_id=d.id AND d.compte=$cuid AND a.type='';");
208
        if ($db->next_record()) {
209
            $q['used'] = $db->f("cnt");
210
        }
211
        return $q;
212
    }
213
214
    /* ----------------------------------------------------------------- */
215
216
    /** Password policy kind used in this class (hook for admin class)
217
     * @return array an array of policykey => "policy name (for humans)"
218
     */
219
    function alternc_password_policy() {
220
        return array("pop" => _("Email account password"));
221
    }
222
223
    /* ----------------------------------------------------------------- */
224
225
    /** Returns the list of mail-hosting domains for a user
226
     * @return array indexed array of hosted domains
227
     */
228
    function enum_domains($uid = -1) {
229
        global $db, $err, $cuid;
230
        $err->log("mail", "enum_domains");
231
        if ($uid == -1) {
232
            $uid = $cuid;
233
        }
234
        $db->query("
235
SELECT
236
  d.id,
237
  d.domaine,
238
  IFNULL( COUNT(a.id), 0) as nb_mail
239
FROM
240
  domaines d LEFT JOIN address a ON (d.id=a.domain_id AND a.type='')
241
WHERE
242
  d.compte = $uid
243
  and d.gesmx = 1
244
GROUP BY
245
  d.id
246
ORDER BY
247
  d.domaine
248
;
249
");
250
        $this->enum_domains = array();
251
        while ($db->next_record()) {
252
            $this->enum_domains[] = $db->Record;
253
        }
254
        return $this->enum_domains;
255
    }
256
257
    /* ----------------------------------------------------------------- */
258
259
    /** available: tells if an email address can be installed in the server
260
     * check the domain part (is it mine too), the syntax, and the availability.
261
     * @param $mail string email to check
262
     * @return boolean true if the email can be installed on the server 
263
     */
264
    function available($mail) {
265
        global $db, $err, $dom;
266
        $err->log("mail", "available");
267
        list($login, $domain) = explode("@", $mail, 2);
268
        // Validate the domain ownership & syntax
269
        if (!($dom_id = $dom->get_domain_byname($domain))) {
270
            return false;
271
        }
272
        // Validate the email syntax:
273
        if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) {
274
            $err->raise("mail", _("The email you entered is syntaxically incorrect"));
275
            return false;
276
        }
277
        // Check the availability
278
        $db->query("SELECT a.id FROM address a WHERE a.domain_id=" . $dom_id . " AND a.address='" . addslashes($login) . "';");
279
        if ($db->next_record()) {
280
            return false;
281
        } else {
282
            return true;
283
        }
284
    }
285
286
    /* ----------------------------------------------------------------- */
287
    /* function used to list every mail address hosted on a domain.
288
     * @param $dom_id integer the domain id.
289
     * @param $search string search that string in recipients or address.
290
     * @param $offset integer skip THAT much emails in the result.
291
     * @param $count integer return no more than THAT much emails. -1 for ALL. Offset is ignored then.
292
     * @result an array of each mail hosted under the domain.
293
     */
294
295
    function enum_domain_mails($dom_id = null, $search = "", $offset = 0, $count = 30, $show_systemmails = false) {
296
        global $db, $err, $hooks;
297
        $err->log("mail", "enum_domains_mail");
298
299
        $search = trim($search);
300
301
        $where = "a.domain_id=$dom_id";
302
        if ($search) {
303
            $where.=" AND (a.address LIKE '%" . addslashes($search) . "%' OR r.recipients LIKE '%" . addslashes($search) . "%')";
304
        }
305
        if (!$show_systemmails) {
306
            $where.=" AND type='' ";
307
        }
308
        $db->query("SELECT count(a.id) AS total FROM address a LEFT JOIN recipient r ON r.address_id=a.id WHERE $where;");
309
        $db->next_record();
310
        $this->total = $db->f("total");
311
        if ($count != -1) {
312
            $limit = "LIMIT $offset,$count";
313
        } else {
314
            $limit = "";
315
        }
316
        $db->query("SELECT a.id, a.address, a.password, a.`enabled`, a.mail_action, d.domaine AS domain, m.quota, m.quota*1024*1024 AS quotabytes, m.bytes AS used, NOT ISNULL(m.id) AS islocal, a.type, r.recipients, m.lastlogin, a.domain_id  
317
         FROM (address a LEFT JOIN mailbox m ON m.address_id=a.id) LEFT JOIN recipient r ON r.address_id=a.id, domaines d 
318
         WHERE $where AND d.id=a.domain_id $limit ;");
319
        if (!$db->next_record()) {
320
            $err->raise("mail", _("No email found for this query"));
321
            return array();
322
        }
323
        $res = array();
324
        do {
325
            $details = $db->Record;
326
            // if necessary, fill the typedata with data from hooks ...
327
            if ($details["type"]) {
328
                $result = $hooks->invoke("hook_mail_get_details", array($details)); // Will fill typedata if necessary
329
                $details["typedata"] = implode("<br />", $result);
330
            }
331
            $res[] = $details;
332
        } while ($db->next_record());
333
        return $res;
334
    }
335
336
    function hook_mail_get_details($detail) {
337
        if ($detail['type'] == 'catchall') {
338
            return _(sprintf("Special mail address for catch-all. <a href='mail_manage_catchall.php?domain_id=%s'>Click here to manage it.</a>", $detail['domain_id']));
339
        }
340
    }
341
342
    /* ----------------------------------------------------------------- */
343
344
    /** Function used to insert a new mail into the db
345
     * should be used by the web interface, not by third-party programs.
346
     *
347
     * This function calls the hook "hooks_mail_cancreate"
348
     * which must return FALSE if the user can't create this email, and raise and error accordingly
349
     * 
350
     * @param $dom_id integer A domain_id (owned by the user) 
351
     * (will be the part at the right of the @ in the email)
352
     * @param $mail string the left part of the email to create (something@dom_id)
353
     * @return an hashtable containing the database id of the newly created mail, 
354
     * or false if an error occured ($err is filled accordingly)
355
     */
356
    function create($dom_id, $mail, $type = "", $dontcheck = false) {
357
        global $err, $db, $quota, $dom, $hooks;
358
        $err->log("mail", "create", $mail);
359
360
        // Validate the domain id
361
        if (!($domain = $dom->get_domain_byid($dom_id))) {
362
            return false;
363
        }
364
365
        // Validate the email syntax:
366
        $m = $mail . "@" . $domain;
367
        if (!filter_var($m, FILTER_VALIDATE_EMAIL) && !$dontcheck) {
368
            $err->raise("mail", _("The email you entered is syntaxically incorrect"));
369
            return false;
370
        }
371
372
        // Call other classes to check we can create it:
373
        $cancreate = $hooks->invoke("hook_mail_cancreate", array($dom_id, $mail));
374
        if (in_array(false, $cancreate, true)) {
375
            return false;
376
        }
377
378
        // Check the quota:
379
        if (!$quota->cancreate("mail")) {
380
            $err->raise("mail", _("You cannot create email addresses: your quota is over"));
381
            return false;
382
        }
383
        // Already exists?
384
        $db->query("SELECT * FROM address WHERE domain_id=" . $dom_id . " AND address='" . addslashes($mail) . "';");
385
        if ($db->next_record()) {
386
            $err->raise("mail", _("This email address already exists"));
387
            return false;
388
        }
389
        // Create it now
390
        $db->query("INSERT INTO address (domain_id, address,type) VALUES ($dom_id, '" . addslashes($mail) . "','$type');");
391
        if (!($id = $db->lastid())) {
392
            $err->raise("mail", _("An unexpected error occured when creating the email"));
393
            return false;
394
        }
395
        return $id;
396
    }
397
398
    /* ----------------------------------------------------------------- */
399
400
    /** function used to get every information we can on a mail 
401
     * @param $mail_id integer
402
     * @return array a hashtable with all the informations for that email
403
     */
404
    function get_details($mail_id) {
405
        global $db, $err, $hooks;
406
        $err->log("mail", "get_details");
407
408
        $mail_id = intval($mail_id);
409
        // Validate that this email is owned by me...
410
        if (!($mail = $this->is_it_my_mail($mail_id))) {
411
            return false;
412
        }
413
414
        // We fetch all the informations for that email: these will fill the hastable : 
415
        $db->query("SELECT a.id, a.address, a.password, a.enabled, d.domaine AS domain, m.path, m.quota, m.quota*1024*1024 AS quotabytes, m.bytes AS used, NOT ISNULL(m.id) AS islocal, a.type, r.recipients, m.lastlogin, a.mail_action, m.mail_action AS mailbox_action FROM (address a LEFT JOIN mailbox m ON m.address_id=a.id) LEFT JOIN recipient r ON r.address_id=a.id, domaines d WHERE a.id=" . $mail_id . " AND d.id=a.domain_id;");
416
        if (!$db->next_record()) {
417
            return false;
418
        }
419
        $details = $db->Record;
420
        // if necessary, fill the typedata with data from hooks ...
421
        if ($details["type"]) {
422
            $result = $hooks->invoke("hook_mail_get_details", array($mail_id)); // Will fill typedata if necessary
423
            $details["typedata"] = implode("<br />", $result);
424
        }
425
        return $details;
426
    }
427
428
    private $isitmy_cache = array();
429
430
    /* ----------------------------------------------------------------- */
431
432
    /** Check if an email is mine ...
433
     *
434
     * @param $mail_id integer the number of the email to check
435
     * @return string the complete email address if that's mine, false if not
436
     * ($err is filled accordingly)
437
     */
438
    function is_it_my_mail($mail_id) {
439
        global $err, $db, $cuid;
440
        $mail_id = intval($mail_id);
441
        // cache it (may be called more than one time in the same page).
442
        if (isset($this->isitmy_cache[$mail_id])) {
443
            return $this->isitmy_cache[$mail_id];
444
        }
445
        $db->query("SELECT concat(a.address,'@',d.domaine) AS email FROM address a, domaines d WHERE d.id=a.domain_id AND a.id=$mail_id AND d.compte=$cuid;");
446
        if ($db->next_record()) {
447
            return $this->isitmy_cache[$mail_id] = $db->f("email");
448
        } else {
449
            $err->raise("mail", _("This email is not yours, you can't change anything on it"));
450
            return $this->isitmy_cache[$mail_id] = false;
451
        }
452
    }
453
454
    /* ----------------------------------------------------------------- */
455
456
    /** Hook called when the DOMAIN class will delete a domain.
457
     *
458
     * @param $dom integer the number of the email to delete
459
     * @return boolean if the email has been properly deleted 
460
     * or false if an error occured ($err is filled accordingly)
461
     */
462
    function hook_dom_del_mx_domain($dom_id) {
463
        global $db;
464
        $list = $this->enum_domain_mails($dom_id, "", 0, -1);
465
        if (is_array($list)) {
466
            foreach ($list as $one) {
467
                $this->delete($one["id"]);
468
            }
469
        }
470
        $db->query("SELECT domaine FROM domaines WHERE id=$domain_id;");
471
        if ($db->next_record()) {
472
            $db->query("UPDATE sub_domaines SET web_action='DELETE' WHERE domaine='" . addslashes($db->Record["domaine"]) . "' AND type='txt' AND (sub='' AND valeur LIKE 'v=spf1 %') OR (sub='_dmarc' AND valeur LIKE 'v=dmarc1;%');");
473
            $db->query("UPDATE domaines SET dns_action='UPDATE' WHERE id=$domain_id;");
474
        }
475
476
        return true;
477
    }
478
479
    // return the alternc account's ID of the mail_id
480
    function get_account_by_mail_id($mail_id) {
481
        global $db;
482
        $db->query("select compte as uid from domaines d, address a where a.domain_id = d.id and a.id = $mail_id");
483
        if (!$db->next_record()) {
484
            return false;
485
        }
486
        return $db->f('uid');
487
    }
488
489
    /* ----------------------------------------------------------------- */
490
491
    /** Function used to delete a mail from the db
492
     * should be used by the web interface, not by third-party programs.
493
     *
494
     * @param $mail_id integer the number of the email to delete
495
     * @return boolean if the email has been properly deleted 
496
     * or false if an error occured ($err is filled accordingly)
497
     */
498
    function delete($mail_id) {
499
        global $err, $db, $hooks;
500
        $err->log("mail", "delete");
501
502
        $mail_id = intval($mail_id);
503
504
        if (!$mail_id) {
505
            $err->raise("mail", _("The email you entered is syntaxically incorrect"));
506
            return false;
507
        }
508
        // Validate that this email is owned by me...
509
        if (!($mail = $this->is_it_my_mail($mail_id))) {
510
            return false;
511
        }
512
513
        $mailinfos = $this->get_details($mail_id);
514
        $hooks->invoke('hook_mail_delete', array($mail_id, $mailinfos['address'] . '@' . $mailinfos['domain']));
515
516
        // Search for that address:
517
        $db->query("SELECT a.id, a.type, a.mail_action, m.mail_action AS mailbox_action, NOT ISNULL(m.id) AS islocal FROM address a LEFT JOIN mailbox m ON m.address_id=a.id WHERE a.id='$mail_id';");
518
        if (!$db->next_record()) {
519
            $err->raise("mail", _("The email %s does not exist, it can't be deleted"), $mail);
520
            return false;
521
        }
522
        if ($db->f("mail_action") != "OK" || ($db->f("islocal") && $db->f("mailbox_action") != "OK")) { // will be deleted soon ...
523
            $err->raise("mail", _("The email %s is already marked for deletion, it can't be deleted"), $mail);
524
            return false;
525
        }
526
        $mail_id = $db->f("id");
527
528
        if ($db->f("islocal")) {
529
            // If it's a pop/imap mailbox, mark it for deletion
530
            $db->query("UPDATE address SET mail_action='DELETE', enabled=0 WHERE id='$mail_id';");
531
            $db->query("UPDATE mailbox SET mail_action='DELETE' WHERE address_id='$mail_id';");
532
            $err->raise("mail", _("The email %s has been marked for deletion"), $mail);
533
        } else {
534
            // If it's only aliases, delete it NOW.
535
            $db->query("DELETE FROM address WHERE id='$mail_id';");
536
            $db->query("DELETE FROM mailbox WHERE address_id='$mail_id';");
537
            $db->query("DELETE FROM recipient WHERE address_id='$mail_id';");
538
            $err->raise("mail", _("The email %s has been successfully deleted"), $mail);
539
        }
540
        return true;
541
    }
542
543
    /* ----------------------------------------------------------------- */
544
545
    /** Function used to undelete a pending deletion mail from the db
546
     * should be used by the web interface, not by third-party programs.
547
     *
548
     * @param $mail_id integer the email id
549
     * @return boolean if the email has been properly undeleted 
550
     * or false if an error occured ($err is filled accordingly)
551
     */
552
    function undelete($mail_id) {
553
        global $err, $db;
554
        $err->log("mail", "undelete");
555
556
        $mail_id = intval($mail_id);
557
558
        if (!$mail_id) {
559
            $err->raise("mail", _("The email you entered is syntaxically incorrect"));
560
            return false;
561
        }
562
        // Validate that this email is owned by me...
563
        if (!($mail = $this->is_it_my_mail($mail_id))) {
564
            return false;
565
        }
566
567
        // Search for that address:
568
        $db->query("SELECT a.id, a.type, a.mail_action, m.mail_action AS mailbox_action, NOT ISNULL(m.id) AS islocal FROM address a LEFT JOIN mailbox m ON m.address_id=a.id WHERE a.id='$mail_id';");
569
        if (!$db->next_record()) {
570
            $err->raise("mail", _("The email %s does not exist, it can't be undeleted"), $mail);
571
            return false;
572
        }
573
        if ($db->f("type") != "") { // Technically special : mailman, sympa ... 
574
            $err->raise("mail", _("The email %s is special, it can't be undeleted"), $mail);
575
            return false;
576
        }
577
        if ($db->f("mailbox_action") != "DELETE" || $db->f("mail_action") != "DELETE") { // will be deleted soon ...
578
            $err->raise("mail", _("Sorry, deletion of email %s is already in progress, or not marked for deletion, it can't be undeleted"), $mail);
579
            return false;
580
        }
581
        $mail_id = $db->f("id");
582
583
        if ($db->f("islocal")) {
584
            // If it's a pop/imap mailbox, mark it for deletion
585
            $db->query("UPDATE address SET mail_action='OK', `enabled`=1 WHERE id='$mail_id';");
586
            $db->query("UPDATE mailbox SET mail_action='OK' WHERE address_id='$mail_id';");
587
            $err->raise("mail", _("The email %s has been undeleted"), $mail);
588
            return true;
589
        } else {
590
            $err->raise("mail", _("-- Program Error -- The email %s can't be undeleted"), $mail);
591
            return false;
592
        }
593
    }
594
595
    /* ----------------------------------------------------------------- */
596
597
    /** set the password of an email address.
598
     * @param $mail_id integer email ID 
599
     * @param $pass string the new password.
600
     * @return boolean true if the password has been set, false else, raise an error.
601
     */
602
    function set_passwd($mail_id, $pass) {
603
        global $db, $err, $admin;
604
        $err->log("mail", "setpasswd");
605
606
        if (!($email = $this->is_it_my_mail($mail_id))) {
607
            return false;
608
        }
609
        if (!$admin->checkPolicy("pop", $email, $pass)) {
610
            return false;
611
        }
612
        if (!$db->query("UPDATE address SET password='" . _md5cr($pass) . "' where id=$mail_id;")) {
613
            return false;
614
        }
615
        return true;
616
    }
617
618
    /* ----------------------------------------------------------------- */
619
620
    /** Enables an email address.
621
     * @param $mail_id integer Email ID
622
     * @return boolean true if the email has been enabled.
623
     */
624
    function enable($mail_id) {
625
        global $db, $err;
626
        $err->log("mail", "enable");
627
        if (!($email = $this->is_it_my_mail($mail_id))) {
628
            return false;
629
        }
630
        if (!$db->query("UPDATE address SET `enabled`=1 where id=$mail_id;")) {
631
            return false;
632
        }
633
        return true;
634
    }
635
636
    /* ----------------------------------------------------------------- */
637
638
    /** Disables an email address.
639
     * @param $mail_id integer Email ID
640
     * @return boolean true if the email has been enabled.
641
     */
642
    function disable($mail_id) {
643
        global $db, $err;
644
        $err->log("mail", "disable");
645
        if (!($email = $this->is_it_my_mail($mail_id))) {
646
            return false;
647
        }
648
        if (!$db->query("UPDATE address SET `enabled`=0 where id=$mail_id;")) {
649
            return false;
650
        }
651
        return true;
652
    }
653
654
    /* ----------------------------------------------------------------- */
655
656
    /** Function used to update an email settings
657
     * should be used by the web interface, not by third-party programs.
658
     *
659
     * @param $mail_id integer the number of the email to delete
660
     * @param integer $islocal boolean is it a POP/IMAP mailbox ?
661
     * @param integer $quotamb integer if islocal=1, quota in MB
662
     * @param string $recipients string recipients, one mail per line.
663
     * @return boolean if the email has been properly edited
664
     * or false if an error occured ($err is filled accordingly)
665
     */
666
    function set_details($mail_id, $islocal, $quotamb, $recipients, $delivery = "dovecot", $dontcheck = false) {
667
        global $err, $db;
668
        $delivery = mysql_real_escape_string($delivery);
669
        $err->log("mail", "set_details");
670
        if (!($me = $this->get_details($mail_id))) {
671
            return false;
672
        }
673
        if ($me["islocal"] && !$islocal) {
674
            // delete pop
675
            $db->query("UPDATE mailbox SET mail_action='DELETE' WHERE address_id=" . $mail_id . ";");
676
        }
677
        if (!$me["islocal"] && $islocal) {
678
            // create pop
679
            $path = "";
680
            if ($delivery == "dovecot") {
681
                $path = ALTERNC_MAIL . "/" . substr($me["address"] . "_", 0, 1) . "/" . $me["address"] . "_" . $me["domain"];
682
            }
683
            foreach ($this->forbiddenchars as $str) {
684
                if (strpos($me["address"], $str) !== false) {
685
                    $err->raise("mail", _("There is forbidden characters in your email address. You can't make it a POP/IMAP account, you can only use it as redirection to other emails"));
686
                    return false;
687
                }
688
            }
689
            foreach ($this->specialchars as $str) {
690
                if (strpos($me["address"], $str) !== false) {
691
                    $path = ALTERNC_MAIL . "/_/" . $me["id"] . "_" . $me["domain"];
692
                    break;
693
                }
694
            }
695
            $db->query("INSERT INTO mailbox SET address_id=$mail_id, delivery='$delivery', path='" . addslashes($path) . "';");
696
        }
697
        if ($me["islocal"] && $islocal && $me["mailbox_action"] == "DELETE") {
698
            $db->query("UPDATE mailbox SET mail_action='OK' WHERE mail_action='DELETE' AND address_id=" . $mail_id . ";");
699
        }
700
701
        if ($islocal) {
702
            if ($quotamb != 0 && $quotamb < (intval($me["used"] / 1024 / 1024) + 1)) {
703
                $quotamb = intval($me["used"] / 1024 / 1024) + 1;
704
                $err->raise("mail", _("You set a quota smaller than the current mailbox size. Since it's not allowed, we set the quota to the current mailbox size"));
705
            }
706
            $db->query("UPDATE mailbox SET quota=" . intval($quotamb) . " WHERE address_id=" . $mail_id . ";");
707
        }
708
709
        $recipients = preg_replace('/[\r\t\s]/', "\n", $recipients); // Handle space AND new line
710
        $r = explode("\n", $recipients);
711
        $red = "";
712
        foreach ($r as $m) {
713
            $m = trim($m);
714
            if ($m && ( filter_var($m, FILTER_VALIDATE_EMAIL) || $dontcheck)  // Recipient Email is valid
715
                    && $m != ($me["address"] . "@" . $me["domain"])) {  // And not myself (no loop allowed easily ;) )
716
                $red.=$m . "\n";
717
            }
718
        }
719
        $db->query("DELETE FROM recipient WHERE address_id=" . $mail_id . ";");
720
        if (isset($red) && $red) {
721
            $db->query("INSERT INTO recipient SET address_id=" . $mail_id . ", recipients='" . addslashes($red) . "';");
722
        }
723
	if (!$islocal && !$red) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $red of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
724
	  $err->raise("mail", _("Warning: you created an email which is not an alias, and not a POP/IMAP mailbox. This is certainly NOT what you want to do. To fix this, edit the email address and check 'Yes' in POP/IMAP account, or set some recipients in the redirection field."));
725
	}
726
        return true;
727
    }
728
729
    /* ----------------------------------------------------------------- */
730
731
    /** A wrapper used by mailman class to create it's needed addresses 
732
     * @ param : $dom_id , the domain id associated to a given address
733
     * @ param : $m , the left part of the  mail address being created
734
     * @ param : $delivery , the delivery used to deliver the mail
735
     */
736
    function add_wrapper($dom_id, $m, $delivery) {
737
        global $err, $mail;
738
        $err->log("mail", "add_wrapper", "creating $delivery $m address");
739
740
        $mail_id = $mail->create($dom_id, $m, $delivery);
741
        $this->set_details($mail_id, 1, 0, '', $delivery);
742
        // FIXME return error code
743
    }
744
745
    /* ----------------------------------------------------------------- */
746
747
    /** A function used to create an alias for a specific address
748
     * @ param : $dom_id , the domain sql identifier
749
     * @ param : $m , the alias we want to create
750
     * @ param : $alias , the already existing aliased address
751
     * @ param : $type, the type of the alias created
752
     * @param string $m
753
     * @param string $alias
754
     * @param string $dom_id
755
     */
756
    function create_alias($dom_id, $m, $alias, $type = "", $dontcheck = false) {
757
        global $err, $mail;
758
        $err->log("mail", "create_alias", "creating $m alias for $alias type $type");
759
760
        $mail_id = $mail->create($dom_id, $m, $type, $dontcheck);
761
        if (!$mail_id) {
762
            return false;
763
        }
764
        $this->set_details($mail_id, 0, 0, $alias, "dovecot", $dontcheck);
765
        return true;
766
    }
767
768
    /* ----------------------------------------------------------------- */
769
770
    /** A wrapper used by mailman class to create it's needed addresses 
771
     * @ param : $mail_id , the mysql id of the mail address we want to delete
772
     * of the email for the current acccount.
773
     */
774
    function del_wrapper($mail_id) {
775
        global $err;
776
        $err->log("mail", "del_wrapper");
777
        $this->delete($mail_id);
778
    }
779
780
    /* ----------------------------------------------------------------- */
781
782
    /** Export the mail information of an account 
783
     * @return: str, string containing the complete configuration 
784
     * of the email for the current acccount.
785
     */
786
    function alternc_export_conf() {
787
        global $err;
788
        $err->log("mail", "export");
789
        $domain = $this->enum_domains();
790
        $str = "<mail>\n";
791
        foreach ($domain as $d) {
792
            $str.="  <domain>\n    <name>" . xml_entities($d["domain"]) . "</name>\n";
793
            $s = $this->enum_domain_mails($d["id"]);
794
            if (count($s)) {
795
                while (list($key, $val) = each($s)) {
796
                    $str.="    <address>\n";
797
                    $str.="      <name>" . xml_entities($val["address"]) . "</name>\n";
798
                    $str.="      <enabled>" . xml_entities($val["enabled"]) . "</enabled>\n";
799
                    if (is_array($val["islocal"])) {
800
                        $str.="      <islocal>1</islocal>\n";
801
                        $str.="      <quota>" . $val["quota"] . "</quota>\n";
802
                        $str.="      <path>" . $val["path"] . "</path>\n";
803
                    } else {
804
                        $str.="      <islocal>0</islocal>\n";
805
                    }
806
                    if (!empty($val["recipients"])) {
807
                        $r = explode("\n", $val["recipients"]);
808
                        foreach ($r as $recip) {
809
                            $str.="      <recipients>" . $recip . "<recipients>\n";
810
                        }
811
                    }
812
                    $str.="    </address>\n";
813
                }
814
            }
815
            $str.="  </domain>\n";
816
        }
817
        $str.="</mail>\n";
818
        return $str;
819
    }
820
821
    /* ----------------------------------------------------------------- */
822
823
    /**
824
     * Return the list of allowed slave accounts (secondary-mx)
825
     * @return array
826
     */
827
    function enum_slave_account() {
828
        global $db;
829
        $db->query("SELECT login,pass FROM mxaccount;");
830
        $res = array();
831
        while ($db->next_record()) {
832
            $res[] = $db->Record;
833
        }
834
        if (!count($res)) {
835
            return false;
836
        }
837
        return $res;
838
    }
839
840
    /* ----------------------------------------------------------------- */
841
842
    /**
843
     * Check for a slave account (secondary mx)
844
     * @param string $login the login to check
845
     * @param string $pass the password to check
846
     * @return boolean TRUE if the password is correct, or FALSE if an error occurred.
847
     */
848
    function check_slave_account($login, $pass) {
849
        global $db;
850
        $login = mysql_real_escape_string($login);
851
        $pass = mysql_real_escape_string($pass);
852
        $db->query("SELECT * FROM mxaccount WHERE login='$login' AND pass='$pass';");
853
        if ($db->next_record()) {
854
            return true;
855
        }
856
        return false;
857
    }
858
859
    /* ----------------------------------------------------------------- */
860
861
    /** Out (echo) the complete hosted domain list : 
862
     */
863
    function echo_domain_list($format = null) {
864
        global $db;
865
        $db->query("SELECT domaine FROM domaines WHERE gesmx=1 ORDER BY domaine");
866
        $lst = array();
867
        $tt = "";
868
        while ($db->next_record()) {
869
            $lst[] = $db->f("domaine");
870
            $tt.=$db->f("domaine");
871
        }
872
873
        # Generate an integrity check 
874
        $obj = array('integrity' => md5($tt), 'items' => $lst);
875
876
        switch ($format) {
877
            case "json":
878
                return json_encode($obj);
879
            default:
880
                foreach ($lst as $l) {
881
                    echo $l . "\n";
882
                }
883
                return true;
884
        } // switch
885
    }
886
887
    /* ----------------------------------------------------------------- */
888
889
    /**
890
     * Add a slave account that will be allowed to access the mxdomain list
891
     * @param string $login the login to add
892
     * @param string $pass the password to add
893
     * @return boolean TRUE if the account has been created, or FALSE if an error occurred.
894
     */
895
    function add_slave_account($login, $pass) {
896
        global $db, $err;
897
        $login = mysql_real_escape_string($login);
898
        $pass = mysql_real_escape_string($pass);
899
        $db->query("SELECT * FROM mxaccount WHERE login='$login'");
900
        if ($db->next_record()) {
901
            $err->raise("mail", _("The slave MX account was not found"));
902
            return false;
903
        }
904
        $db->query("INSERT INTO mxaccount (login,pass) VALUES ('$login','$pass')");
905
        return true;
906
    }
907
908
    /* ----------------------------------------------------------------- */
909
910
    /**
911
     * Remove a slave account
912
     * @param string $login the login to delete
913
     */
914
    function del_slave_account($login) {
915
        global $db;
916
        $login = mysql_real_escape_string($login);
917
        $db->query("DELETE FROM mxaccount WHERE login='$login'");
918
        return true;
919
    }
920
921
    /* ----------------------------------------------------------------- */
922
923
    /** hook function called by AlternC when a domain is created for
924
     * the current user account using the SLAVE DOMAIN feature
925
     * This function create a CATCHALL to the master domain
926
     * @param string $domain_id Domain that has just been created
927
     * @param string $target_domain Master domain 
928
     * @access private
929
     */
930
    function hook_dom_add_slave_domain($domain_id, $target_domain) {
931
        global $err;
932
        $err->log("mail", "hook_dom_add_slave_domain", $domain_id);
933
        $this->catchall_set($domain_id, '@' . $target_domain);
934
        return true;
935
    }
936
937
    /* ----------------------------------------------------------------- */
938
939
    /** hook function called by AlternC when a domain is created for
940
     * the current user account 
941
     * This function create a postmaster mail which is an alias to LOGIN @ FQDN
942
     * wich is a dynamic alias to the alternc's account mail
943
     * @param string $domain_id Domain that has just been created
944
     * @access private
945
     */
946
    function hook_dom_add_mx_domain($domain_id) {
947
        global $err, $mem, $db;
948
        $err->log("mail", "hook_dom_add_mx_domain", $domain_id);
949
950
        $db->query("SELECT value FROM variable where name='mailname_bounce';");
951
        if (!$db->next_record()) {
952
            $err->raise("mail", _("Problem: can't create default bounce mail"));
953
            return false;
954
        }
955
        $mailname = $db->f("value");
956
        // set spf & dmarc for this domain
957
        $db->query("SELECT domaine FROM domaines WHERE id=$domain_id;");
958
        if ($db->next_record()) {
959
            if ($spf = variable_get("default_spf_value")) {
960
                $this->set_dns_spf($db->Record["domaine"], $spf);
961
            }
962
            if ($dmarc = variable_get("default_dmarc_value")) {
963
                $this->set_dns_dmarc($db->Record["domaine"], $dmarc);
964
            }
965
        }
966
        return $this->create_alias($domain_id, 'postmaster', $mem->user['login'] . '@' . $mailname);
967
    }
968
969
    /* ----------------------------------------------------------------- */
970
971
    /** hook function called by variables when a variable is changed
972
     * @access private
973
     */
974
    function hook_variable_set($name, $old, $new) {
975
        global $err, $db;
976
        $err->log("mail", "hook_variable_set($name,$old,$new)");
977
978
        if ($name == "default_spf_value") {
979
            $new = trim($new);
980
            $old = trim($old);
981
            $db->query("SELECT domaine,login,compte FROM domaines, membres WHERE gesdns=1 AND gesmx=1 and membres.uid=domaines.compte;");
982
            while ($db->next_record()) $res[]=$db->Record;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$res was never initialized. Although not strictly required by PHP, it is generally a good practice to add $res = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
983
            foreach ($res as $record) {
0 ignored issues
show
Bug introduced by
The variable $res does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
984
                $this->set_dns_spf($record["domaine"], $new, $old, $record["compte"], $record["login"]);
985
            }
986
        }
987
988
        if ($name == "default_dmarc_value") {
989
            $new = trim($new);
990
            $old = trim($old);
991
            $db->query("SELECT domaine,login,compte FROM domaines, membres WHERE gesdns=1 AND gesmx=1 and membres.uid=domaines.compte;");
992
            while ($db->next_record()) $res[]=$db->Record;
993
            foreach ($res as $record) {
994
                $this->set_dns_dmarc($record["domaine"], $new, $old, $record["compte"], $record["login"]);
995
            }
996
        }
997
    }
998
999
    /* ----------------------------------------------------------------- */
1000
1001
    /** Set or UPDATE the DNS record for the domain $dom(str) to be $spf
1002
     * account's login is current and if not it's $login.
1003
     * don't change spf if current value is not $old
1004
     * @access private
1005
     */
1006
    function set_dns_spf($domain, $spf, $previous = -1, $uid = -1, $login = -1) {
1007
        global $db, $cuid, $mem;
1008
        // defaults
1009
        if ($uid === -1) {
1010
            $uid = intval($cuid);
1011
        } else {
1012
            $uid = intval($uid);
1013
        }
1014
        if ($login === -1) {
1015
            $login = $mem->user["login"];
1016
        }
1017
        // Search for the record in sub_domaines table
1018
        $db->query("SELECT * FROM sub_domaines WHERE compte=$uid AND domaine='" . addslashes($domain) . "' AND sub='' AND type='txt' AND valeur LIKE 'v=spf1 %' AND web_action!='DELETE';");
1019
        if ($db->next_record()) {
1020
            if ($previous !== -1 && $db->Record["valeur"] == "v=spf1 " . $spf) {
1021
                return; // skip, no change asked.
1022
            }
1023
            $db->query("UPDATE sub_domaines SET web_action='DELETE' WHERE id='" . $db->Record["id"] . "';");
1024
        }
1025
        $db->query("INSERT INTO sub_domaines SET compte=$uid, domaine='" . addslashes($domain) . "', sub='', type='txt', valeur='" . addslashes("v=spf1 " . $spf) . "', web_action='UPDATE';");
1026
        $db->query("UPDATE domaines SET dns_action='UPDATE' WHERE domaine='" . addslashes($domain) . "';");
1027
    }
1028
1029
    /* ----------------------------------------------------------------- */
1030
1031
    /** Set or UPDATE the DNS record for the domain $dom(str) to be $dmarc
1032
     * account's login is current and if not it's $login.
1033
     * don't change dmarc if current value is not $old
1034
     * @access private
1035
     */
1036
    function set_dns_dmarc($domain, $dmarc, $previous = -1, $uid = -1, $login = -1) {
1037
        global $db, $cuid, $mem, $L_FQDN;
1038
        // defaults
1039
        if ($uid === -1) {
1040
            $uid = intval($cuid);
1041
        } else {
1042
            $uid = intval($uid);
1043
        }
1044
        if ($login === -1) {
1045
            $login = $mem->user["login"];
1046
        }
1047
        $dmarc = str_replace("%%ADMINMAIL%%", "admin@" . $L_FQDN, $dmarc);
1048
        $dmarc = str_replace("%%USERMAIL%%", $login . "@" . $L_FQDN, $dmarc);
1049
1050
        // Search for the record in sub_domaines table
1051
        $db->query("SELECT * FROM sub_domaines WHERE compte=$uid AND domaine='" . addslashes($domain) . "' AND sub='_dmarc' AND type='txt' AND valeur LIKE 'v=dmarc1;%' AND web_action!='DELETE';");
1052
        if ($db->next_record()) {
1053
            if ($previous !== -1 && $db->Record["valeur"] == "v=dmarc1;" . $dmarc) {
1054
                return; // skip, no change asked.
1055
            }
1056
            $db->query("UPDATE sub_domaines SET web_action='DELETE' WHERE id='" . $db->Record["id"] . "';");
1057
        }
1058
        $db->query("INSERT INTO sub_domaines SET compte=$uid, domaine='" . addslashes($domain) . "', sub='_dmarc', type='txt', valeur='" . addslashes("v=dmarc1;" . $dmarc) . "', web_action='UPDATE';");
1059
        $db->query("UPDATE domaines SET dns_action='UPDATE' WHERE domaine='" . addslashes($domain) . "';");
1060
    }
1061
1062
    /* ----------------------------------------------------------------- */
1063
1064
    /** hook function called by AlternC-upnp to know which open 
1065
     * tcp or udp ports this class requires or suggests
1066
     * @return array a key => value list of port protocol name mandatory values
1067
     * @access private
1068
     */
1069
    function hook_upnp_list() {
1070
        return array(
1071
            "imap" => array("port" => 143, "protocol" => "tcp", "mandatory" => 1),
1072
            "imaps" => array("port" => 993, "protocol" => "tcp", "mandatory" => 1),
1073
            "pop" => array("port" => 110, "protocol" => "tcp", "mandatory" => 1),
1074
            "pops" => array("port" => 995, "protocol" => "tcp", "mandatory" => 1),
1075
            "smtp" => array("port" => 25, "protocol" => "tcp", "mandatory" => 1),
1076
            "sieve" => array("port" => 2000, "protocol" => "tcp", "mandatory" => 1),
1077
            "submission" => array("port" => 587, "protocol" => "tcp", "mandatory" => 0),
1078
        );
1079
    }
1080
1081
}
1082
1083
/* Class m_mail */
1084