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_mail::set_details()   F
last analyzed

Complexity

Conditions 26
Paths 1013

Size

Total Lines 61
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 26
eloc 41
nc 1013
nop 6
dl 0
loc 61
rs 3.1429
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 = ? and r.address_id = a.id and a.address='';", array($domain_id));
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= ? AND a.type='';", array($cuid));
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 = ? 
243
  and d.gesmx = 1
244
GROUP BY
245
  d.id
246
ORDER BY
247
  d.domaine
248
;
249
", array($uid));
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= ? AND a.address= ?;", array($dom_id, $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
        $query_args = array($dom_id);
300
        $search     = trim($search);
301
        $where      = " a.domain_id = ? ";
302
303
        if ($search) {
304
            $where .= " AND (a.address LIKE ? OR r.recipients LIKE ? )";
305
            array_push($query_args, "%" . $search . "%", "%" . $search . "%");
306
        }
307
        if (!$show_systemmails) {
308
            $where .= " AND type='' ";
309
        }
310
        $db->query("SELECT count(a.id) AS total FROM address a LEFT JOIN recipient r ON r.address_id=a.id WHERE " .  $where . ";", $query_args);
311
        $db->next_record();
312
        $this->total = $db->f("total");
313
        if ($count != -1) {
314
	  $offset = intval($offset);
315
	  $count = intval($count);
316
            $limit = " LIMIT $offset, $count "; 
317
        } else {
318
            $limit = "";
319
        }
320
        $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  
321
         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 
322
         WHERE " . $where . " AND d.id=a.domain_id " . $limit . " ;", $query_args);
323
        if (!$db->next_record()) {
324
            $err->raise("mail", _("No email found for this query"));
325
            return array();
326
        }
327
        $res = array();
328
        do {
329
            $details = $db->Record;
330
            // if necessary, fill the typedata with data from hooks ...
331
            if ($details["type"]) {
332
                $result = $hooks->invoke("hook_mail_get_details", array($details)); // Will fill typedata if necessary
333
                $details["typedata"] = implode("<br />", $result);
334
            }
335
            $res[] = $details;
336
        } while ($db->next_record());
337
        return $res;
338
    }
339
340
    function hook_mail_get_details($detail) {
341
        if ($detail['type'] == 'catchall') {
342
            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']));
343
        }
344
    }
345
346
    /* ----------------------------------------------------------------- */
347
348
    /** Function used to insert a new mail into the db
349
     * should be used by the web interface, not by third-party programs.
350
     *
351
     * This function calls the hook "hooks_mail_cancreate"
352
     * which must return FALSE if the user can't create this email, and raise and error accordingly
353
     * 
354
     * @param $dom_id integer A domain_id (owned by the user) 
355
     * (will be the part at the right of the @ in the email)
356
     * @param $mail string the left part of the email to create (something@dom_id)
357
     * @return an hashtable containing the database id of the newly created mail, 
358
     * or false if an error occured ($err is filled accordingly)
359
     */
360
    function create($dom_id, $mail, $type = "", $dontcheck = false) {
361
        global $err, $db, $quota, $dom, $hooks;
362
        $err->log("mail", "create", $mail);
363
364
        // Validate the domain id
365
        if (!($domain = $dom->get_domain_byid($dom_id))) {
366
            return false;
367
        }
368
369
        // Validate the email syntax:
370
        $m = $mail . "@" . $domain;
371
        if (!filter_var($m, FILTER_VALIDATE_EMAIL) && !$dontcheck) {
372
            $err->raise("mail", _("The email you entered is syntaxically incorrect"));
373
            return false;
374
        }
375
376
        // Call other classes to check we can create it:
377
        $cancreate = $hooks->invoke("hook_mail_cancreate", array($dom_id, $mail));
378
        if (in_array(false, $cancreate, true)) {
379
            return false;
380
        }
381
382
        // Check the quota:
383
        if (($type=="")&&!$quota->cancreate("mail")) {
384
            $err->raise("mail", _("You cannot create email addresses: your quota is over"));
385
            return false;
386
        }
387
        // Already exists?
388
        $db->query("SELECT * FROM address WHERE domain_id= ? AND address= ? ;", array($dom_id, $mail));
389
        if ($db->next_record()) {
390
            $err->raise("mail", _("This email address already exists"));
391
            return false;
392
        }
393
        // Create it now
394
        $db->query("INSERT INTO address (domain_id, address,type) VALUES (?, ?, ?);", array($dom_id, $mail, $type));
395
        if (!($id = $db->lastid())) {
396
            $err->raise("mail", _("An unexpected error occured when creating the email"));
397
            return false;
398
        }
399
        return $id;
400
    }
401
402
    /* ----------------------------------------------------------------- */
403
404
    /** function used to get every information we can on a mail 
405
     * @param $mail_id integer
406
     * @return array a hashtable with all the informations for that email
407
     */
408
    function get_details($mail_id) {
409
        global $db, $err, $hooks;
410
        $err->log("mail", "get_details");
411
412
        $mail_id = intval($mail_id);
413
        // Validate that this email is owned by me...
414
        if (!($mail = $this->is_it_my_mail($mail_id))) {
415
            return false;
416
        }
417
418
        // We fetch all the informations for that email: these will fill the hastable : 
419
        $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= ? AND d.id=a.domain_id;", array($mail_id));
420
        if (!$db->next_record()) {
421
            return false;
422
        }
423
        $details = $db->Record;
424
        // if necessary, fill the typedata with data from hooks ...
425
        if ($details["type"]) {
426
            $result = $hooks->invoke("hook_mail_get_details", array($mail_id)); // Will fill typedata if necessary
427
            $details["typedata"] = implode("<br />", $result);
428
        }
429
        return $details;
430
    }
431
432
    private $isitmy_cache = array();
433
434
    /* ----------------------------------------------------------------- */
435
436
    /** Check if an email is mine ...
437
     *
438
     * @param $mail_id integer the number of the email to check
439
     * @return string the complete email address if that's mine, false if not
440
     * ($err is filled accordingly)
441
     */
442
    function is_it_my_mail($mail_id) {
443
        global $err, $db, $cuid;
444
        $mail_id = intval($mail_id);
445
        // cache it (may be called more than one time in the same page).
446
        if (isset($this->isitmy_cache[$mail_id])) {
447
            return $this->isitmy_cache[$mail_id];
448
        }
449
        $db->query("SELECT concat(a.address,'@',d.domaine) AS email FROM address a, domaines d WHERE d.id=a.domain_id AND a.id= ? AND d.compte= ?;", array($mail_id, $cuid));
450
        if ($db->next_record()) {
451
            return $this->isitmy_cache[$mail_id] = $db->f("email");
452
        } else {
453
            $err->raise("mail", _("This email is not yours, you can't change anything on it"));
454
            return $this->isitmy_cache[$mail_id] = false;
455
        }
456
    }
457
458
    /* ----------------------------------------------------------------- */
459
460
    /** Hook called when the DOMAIN class will delete a domain.
461
     * OR when the DOMAIN class tells us we don't host the emails of this domain anymore.
462
     * @param $dom the ID of the domain to delete
463
     * @return boolean if the email has been properly deleted 
464
     * or false if an error occured ($err is filled accordingly)
465
     */
466
    function hook_dom_del_mx_domain($dom_id) {
467
        global $db;
468
        $list = $this->enum_domain_mails($dom_id, "", 0, -1);
469
        if (is_array($list)) {
470
            foreach ($list as $one) {
471
                $this->delete($one["id"]);
472
            }
473
        }
474
        $db->query("SELECT domaine FROM domaines WHERE id= ? ;", array($dom_id));
475
        if ($db->next_record()) {
476
            $db->query("UPDATE sub_domaines SET web_action='DELETE' WHERE domaine= ? AND type='txt' AND (sub='' AND valeur LIKE 'v=spf1 %') OR (sub='_dmarc' AND valeur LIKE 'v=dmarc1;%');", array($db->Record["domaine"]));
477
            $db->query("UPDATE sub_domaines SET web_action='DELETE' WHERE domaine= ? AND (type='defmx' OR type='defmx2');", array($db->Record["domaine"]));
478
            $db->query("UPDATE domaines SET dns_action='UPDATE' WHERE id= ? ;", array($dom_id));
479
        }
480
481
        return true;
482
    }
483
484
    // return the alternc account's ID of the mail_id
485
    function get_account_by_mail_id($mail_id) {
486
        global $db;
487
        $db->query("select compte as uid from domaines d, address a where a.domain_id = d.id and a.id = ? ;", array($mail_id));
488
        if (!$db->next_record()) {
489
            return false;
490
        }
491
        return $db->f('uid');
492
    }
493
494
    /* ----------------------------------------------------------------- */
495
496
    /** Function used to delete a mail from the db
497
     * should be used by the web interface, not by third-party programs.
498
     *
499
     * @param $mail_id integer the number of the email to delete
500
     * @return boolean if the email has been properly deleted 
501
     * or false if an error occured ($err is filled accordingly)
502
     */
503
    function delete($mail_id) {
504
        global $err, $db, $hooks;
505
        $err->log("mail", "delete");
506
507
        $mail_id = intval($mail_id);
508
509
        if (!$mail_id) {
510
            $err->raise("mail", _("The email you entered is syntaxically incorrect"));
511
            return false;
512
        }
513
        // Validate that this email is owned by me...
514
        if (!($mail = $this->is_it_my_mail($mail_id))) {
515
            return false;
516
        }
517
518
        $mailinfos = $this->get_details($mail_id);
519
        $hooks->invoke('hook_mail_delete', array($mail_id, $mailinfos['address'] . '@' . $mailinfos['domain']));
520
521
        // Search for that address:
522
        $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= ? ;", array($mail_id));
523
        if (!$db->next_record()) {
524
            $err->raise("mail", _("The email %s does not exist, it can't be deleted"), $mail);
525
            return false;
526
        }
527
        if ($db->f("mail_action") != "OK" || ($db->f("islocal") && $db->f("mailbox_action") != "OK")) { // will be deleted soon ...
528
            $err->raise("mail", _("The email %s is already marked for deletion, it can't be deleted"), $mail);
529
            return false;
530
        }
531
        $mail_id = $db->f("id");
532
533
        if ($db->f("islocal")) {
534
            // If it's a pop/imap mailbox, mark it for deletion
535
            $db->query("UPDATE address SET mail_action='DELETE', enabled=0 WHERE id= ?;", array($mail_id));
536
            $db->query("UPDATE mailbox SET mail_action='DELETE' WHERE address_id= ?;", array($mail_id));
537
            $err->raise("mail", _("The email %s has been marked for deletion"), $mail);
538
        } else {
539
            // If it's only aliases, delete it NOW.
540
            $db->query("DELETE FROM address WHERE id= ? ;", array($mail_id));
541
            $db->query("DELETE FROM mailbox WHERE address_id= ? ;", array($mail_id));
542
            $db->query("DELETE FROM recipient WHERE address_id= ? ;", array($mail_id));
543
            $err->raise("mail", _("The email %s has been successfully deleted"), $mail);
544
        }
545
        return true;
546
    }
547
548
    /* ----------------------------------------------------------------- */
549
550
    /** Function used to undelete a pending deletion mail from the db
551
     * should be used by the web interface, not by third-party programs.
552
     *
553
     * @param $mail_id integer the email id
554
     * @return boolean if the email has been properly undeleted 
555
     * or false if an error occured ($err is filled accordingly)
556
     */
557
    function undelete($mail_id) {
558
        global $err, $db;
559
        $err->log("mail", "undelete");
560
561
        $mail_id = intval($mail_id);
562
563
        if (!$mail_id) {
564
            $err->raise("mail", _("The email you entered does not exist"));
565
            return false;
566
        }
567
        // Validate that this email is owned by me...
568
        if (!($mail = $this->is_it_my_mail($mail_id))) {
569
            return false;
570
        }
571
572
        // Search for that address:
573
        $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= ? ;", array($mail_id));
574
        if (!$db->next_record()) {
575
            $err->raise("mail", _("The email %s does not exist, it can't be undeleted"), $mail);
576
            return false;
577
        }
578
        if ($db->f("type") != "") { // Technically special : mailman, sympa ... 
579
            $err->raise("mail", _("The email %s is special, it can't be undeleted"), $mail);
580
            return false;
581
        }
582
        if ($db->f("mailbox_action") != "DELETE" || $db->f("mail_action") != "DELETE") { // will be deleted soon ...
583
            $err->raise("mail", _("Sorry, deletion of email %s is already in progress, or not marked for deletion, it can't be undeleted"), $mail);
584
            return false;
585
        }
586
        $mail_id = $db->f("id");
587
588
        if ($db->f("islocal")) {
589
            // If it's a pop/imap mailbox, mark it for deletion
590
            $db->query("UPDATE address SET mail_action='OK', `enabled`=1 WHERE id= ?;", array($mail_id));
591
            $db->query("UPDATE mailbox SET mail_action='OK' WHERE address_id= ? ;", array($mail_id));
592
            $err->raise("mail", _("The email %s has been undeleted"), $mail);
593
            return true;
594
        } else {
595
            $err->raise("mail", _("-- Program Error -- The email %s can't be undeleted"), $mail);
596
            return false;
597
        }
598
    }
599
600
    /* ----------------------------------------------------------------- */
601
602
    /** set the password of an email address.
603
     * @param $mail_id integer email ID 
604
     * @param $pass string the new password.
605
     * @return boolean true if the password has been set, false else, raise an error.
606
     */
607
    function set_passwd($mail_id, $pass) {
608
        global $db, $err, $admin;
609
        $err->log("mail", "setpasswd");
610
611
        if (!($email = $this->is_it_my_mail($mail_id))) {
612
            return false;
613
        }
614
        if (!$admin->checkPolicy("pop", $email, $pass)) {
615
            return false;
616
        }
617
        if (!$db->query("UPDATE address SET password= ? where id = ? ;", array(_md5cr($pass), $mail_id ))) {
618
            return false;
619
        }
620
        return true;
621
    }
622
623
    /* ----------------------------------------------------------------- */
624
625
    /** Enables an email address.
626
     * @param $mail_id integer Email ID
627
     * @return boolean true if the email has been enabled.
628
     */
629
    function enable($mail_id) {
630
        global $db, $err;
631
        $err->log("mail", "enable");
632
        if (!($email = $this->is_it_my_mail($mail_id))) {
633
            return false;
634
        }
635
        if (!$db->query("UPDATE address SET `enabled`=1 where id= ? ;", array($mail_id))) {
636
            return false;
637
        }
638
        return true;
639
    }
640
641
    /* ----------------------------------------------------------------- */
642
643
    /** Disables an email address.
644
     * @param $mail_id integer Email ID
645
     * @return boolean true if the email has been enabled.
646
     */
647
    function disable($mail_id) {
648
        global $db, $err;
649
        $err->log("mail", "disable");
650
        if (!($email = $this->is_it_my_mail($mail_id))) {
651
            return false;
652
        }
653
        if (!$db->query("UPDATE address SET `enabled`=0 where id= ? ;", array($mail_id))) {
654
            return false;
655
        }
656
        return true;
657
    }
658
659
    /* ----------------------------------------------------------------- */
660
661
    /** Function used to update an email settings
662
     * should be used by the web interface, not by third-party programs.
663
     *
664
     * @param $mail_id integer the number of the email to delete
665
     * @param integer $islocal boolean is it a POP/IMAP mailbox ?
666
     * @param integer $quotamb integer if islocal=1, quota in MB
667
     * @param string $recipients string recipients, one mail per line.
668
     * @return boolean if the email has been properly edited
669
     * or false if an error occured ($err is filled accordingly)
670
     */
671
    function set_details($mail_id, $islocal, $quotamb, $recipients, $delivery = "dovecot", $dontcheck = false) {
672
        global $err, $db;
673
        $err->log("mail", "set_details");
674
        if (!($me = $this->get_details($mail_id))) {
675
            return false;
676
        }
677
        if ($me["islocal"] && !$islocal) {
678
            // delete pop
679
            $db->query("UPDATE mailbox SET mail_action='DELETE' WHERE address_id= ? ;", array($mail_id));
680
        }
681
        if (!$me["islocal"] && $islocal) {
682
            // create pop
683
            $path = "";
684
            if ($delivery == "dovecot") {
685
                $path = ALTERNC_MAIL . "/" . substr($me["address"] . "_", 0, 1) . "/" . $me["address"] . "_" . $me["domain"];
686
            }
687
            foreach ($this->forbiddenchars as $str) {
688
                if (strpos($me["address"], $str) !== false) {
689
                    $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"));
690
                    return false;
691
                }
692
            }
693
            foreach ($this->specialchars as $str) {
694
                if (strpos($me["address"], $str) !== false) {
695
                    $path = ALTERNC_MAIL . "/_/" . $me["id"] . "_" . $me["domain"];
696
                    break;
697
                }
698
            }
699
            $db->query("INSERT INTO mailbox SET address_id= ? , delivery= ?, path= ? ;", array($mail_id, $delivery, $path));
700
        }
701
        if ($me["islocal"] && $islocal && $me["mailbox_action"] == "DELETE") {
702
            $db->query("UPDATE mailbox SET mail_action='OK' WHERE mail_action='DELETE' AND address_id= ? ;", array($mail_id));
703
        }
704
705
        if ($islocal) {
706
            if ($quotamb != 0 && $quotamb < (intval($me["used"] / 1024 / 1024) + 1)) {
707
                $quotamb = intval($me["used"] / 1024 / 1024) + 1;
708
                $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"));
709
            }
710
            $db->query("UPDATE mailbox SET quota= ? WHERE address_id= ? ;", array($quotamb, $mail_id));
711
        }
712
713
        $recipients = preg_replace('/[\r\t\s]/', "\n", $recipients); // Handle space AND new line
714
        $r = explode("\n", $recipients);
715
        $red = "";
716
        foreach ($r as $m) {
717
            $m = trim($m);
718
            if ($m && ( filter_var($m, FILTER_VALIDATE_EMAIL) || $dontcheck)  // Recipient Email is valid
719
                    && $m != ($me["address"] . "@" . $me["domain"])) {  // And not myself (no loop allowed easily ;) )
720
                $red.=$m . "\n";
721
            }
722
        }
723
        $db->query("DELETE FROM recipient WHERE address_id= ? ;", array($mail_id));
724
        if (isset($red) && $red) {
725
            $db->query("INSERT INTO recipient SET address_id= ?, recipients= ? ;", array($mail_id, $red));
726
        }
727
	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...
728
	  $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."));
729
	}
730
        return true;
731
    }
732
733
    /* ----------------------------------------------------------------- */
734
735
    /** A wrapper used by mailman class to create it's needed addresses 
736
     * @ param : $dom_id , the domain id associated to a given address
737
     * @ param : $m , the left part of the  mail address being created
738
     * @ param : $delivery , the delivery used to deliver the mail
739
     */
740
    function add_wrapper($dom_id, $m, $delivery) {
741
        global $err, $mail;
742
        $err->log("mail", "add_wrapper", "creating $delivery $m address");
743
744
        $mail_id = $mail->create($dom_id, $m, $delivery);
745
        $this->set_details($mail_id, 1, 0, '', $delivery);
746
        // FIXME return error code
747
    }
748
749
    /* ----------------------------------------------------------------- */
750
751
    /** A function used to create an alias for a specific address
752
     * @ param : $dom_id , the domain sql identifier
753
     * @ param : $m , the alias we want to create
754
     * @ param : $alias , the already existing aliased address
755
     * @ param : $type, the type of the alias created
756
     * @param string $m
757
     * @param string $alias
758
     * @param string $dom_id
759
     */
760
    function create_alias($dom_id, $m, $alias, $type = "", $dontcheck = false) {
761
        global $err, $mail;
762
        $err->log("mail", "create_alias", "creating $m alias for $alias type $type");
763
764
        $mail_id = $mail->create($dom_id, $m, $type, $dontcheck);
765
        if (!$mail_id) {
766
            return false;
767
        }
768
        $this->set_details($mail_id, 0, 0, $alias, "dovecot", $dontcheck);
769
        return true;
770
    }
771
772
    /* ----------------------------------------------------------------- */
773
774
    /** A wrapper used by mailman class to create it's needed addresses 
775
     * @ param : $mail_id , the mysql id of the mail address we want to delete
776
     * of the email for the current acccount.
777
     */
778
    function del_wrapper($mail_id) {
779
        global $err;
780
        $err->log("mail", "del_wrapper");
781
        $this->delete($mail_id);
782
    }
783
784
    /* ----------------------------------------------------------------- */
785
786
    /** Export the mail information of an account 
787
     * @return: str, string containing the complete configuration 
788
     * of the email for the current acccount.
789
     */
790
    function alternc_export_conf() {
791
        global $err;
792
        $err->log("mail", "export");
793
        $domain = $this->enum_domains();
794
        $str = "<mail>\n";
795
        foreach ($domain as $d) {
796
            $str.="  <domain>\n    <name>" . xml_entities($d["domain"]) . "</name>\n";
797
            $s = $this->enum_domain_mails($d["id"]);
798
            if (count($s)) {
799
                while (list($key, $val) = each($s)) {
800
                    $str.="    <address>\n";
801
                    $str.="      <name>" . xml_entities($val["address"]) . "</name>\n";
802
                    $str.="      <enabled>" . xml_entities($val["enabled"]) . "</enabled>\n";
803
                    if (is_array($val["islocal"])) {
804
                        $str.="      <islocal>1</islocal>\n";
805
                        $str.="      <quota>" . $val["quota"] . "</quota>\n";
806
                        $str.="      <path>" . $val["path"] . "</path>\n";
807
                    } else {
808
                        $str.="      <islocal>0</islocal>\n";
809
                    }
810
                    if (!empty($val["recipients"])) {
811
                        $r = explode("\n", $val["recipients"]);
812
                        foreach ($r as $recip) {
813
                            $str.="      <recipients>" . $recip . "<recipients>\n";
814
                        }
815
                    }
816
                    $str.="    </address>\n";
817
                }
818
            }
819
            $str.="  </domain>\n";
820
        }
821
        $str.="</mail>\n";
822
        return $str;
823
    }
824
825
    /* ----------------------------------------------------------------- */
826
827
    /**
828
     * Return the list of allowed slave accounts (secondary-mx)
829
     * @return array
830
     */
831
    function enum_slave_account() {
832
        global $db;
833
        $db->query("SELECT login,pass FROM mxaccount;");
834
        $res = array();
835
        while ($db->next_record()) {
836
            $res[] = $db->Record;
837
        }
838
        if (!count($res)) {
839
            return false;
840
        }
841
        return $res;
842
    }
843
844
    /* ----------------------------------------------------------------- */
845
846
    /**
847
     * Check for a slave account (secondary mx)
848
     * @param string $login the login to check
849
     * @param string $pass the password to check
850
     * @return boolean TRUE if the password is correct, or FALSE if an error occurred.
851
     */
852
    function check_slave_account($login, $pass) {
853
        global $db;
854
        $db->query("SELECT * FROM mxaccount WHERE login= ? AND pass= ?;", array($login, $pass));
855
        if ($db->next_record()) {
856
            return true;
857
        }
858
        return false;
859
    }
860
861
    /* ----------------------------------------------------------------- */
862
863
    /** Out (echo) the complete hosted domain list : 
864
     */
865
    function echo_domain_list($format = null) {
866
        global $db;
867
        $db->query("SELECT domaine FROM domaines WHERE gesmx=1 ORDER BY domaine");
868
        $lst = array();
869
        $tt = "";
870
        while ($db->next_record()) {
871
            $lst[] = $db->f("domaine");
872
            $tt.=$db->f("domaine");
873
        }
874
875
        # Generate an integrity check 
876
        $obj = array('integrity' => md5($tt), 'items' => $lst);
877
878
        switch ($format) {
879
            case "json":
880
                return json_encode($obj);
881
            default:
882
                foreach ($lst as $l) {
883
                    echo $l . "\n";
884
                }
885
                return true;
886
        } // switch
887
    }
888
889
    /* ----------------------------------------------------------------- */
890
891
    /**
892
     * Add a slave account that will be allowed to access the mxdomain list
893
     * @param string $login the login to add
894
     * @param string $pass the password to add
895
     * @return boolean TRUE if the account has been created, or FALSE if an error occurred.
896
     */
897
    function add_slave_account($login, $pass) {
898
        global $db, $err;
899
        $db->query("SELECT * FROM mxaccount WHERE login= ? ;", array($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 (?, ?);", array($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
        $db->query("DELETE FROM mxaccount WHERE login= ? ;", array($login));
917
        return true;
918
    }
919
920
    /* ----------------------------------------------------------------- */
921
922
    /** hook function called by AlternC when a domain is created for
923
     * the current user account using the SLAVE DOMAIN feature
924
     * This function create a CATCHALL to the master domain
925
     * @param string $domain_id Domain that has just been created
926
     * @param string $target_domain Master domain 
927
     * @access private
928
     */
929
    function hook_dom_add_slave_domain($domain_id, $target_domain) {
930
        global $err;
931
        $err->log("mail", "hook_dom_add_slave_domain", $domain_id);
932
        $this->catchall_set($domain_id, '@' . $target_domain);
933
        return true;
934
    }
935
936
    /* ----------------------------------------------------------------- */
937
938
    /** hook function called by AlternC when a domain is created for
939
     * the current user account 
940
     * This function create a postmaster mail which is an alias to LOGIN @ FQDN
941
     * wich is a dynamic alias to the alternc's account mail
942
     * @param string $domain_id Domain that has just been created
943
     * @access private
944
     */
945
    function hook_dom_add_mx_domain($domain_id) {
946
        global $err, $mem, $db;
947
        $err->log("mail", "hook_dom_add_mx_domain", $domain_id);
948
949
        $db->query("SELECT value FROM variable where name='mailname_bounce';");
950
        if (!$db->next_record()) {
951
            $err->raise("mail", _("Problem: can't create default bounce mail"));
952
            return false;
953
        }
954
        $mailname = $db->f("value");
955
        // set spf & dmarc for this domain
956
        $db->query("SELECT domaine FROM domaines WHERE id= ?;", array($domain_id));
957
        if ($db->next_record()) {
958
            if ($spf = variable_get("default_spf_value")) {
959
                $this->set_dns_spf($db->Record["domaine"], $spf);
960
            }
961
            if ($dmarc = variable_get("default_dmarc_value")) {
962
                $this->set_dns_dmarc($db->Record["domaine"], $dmarc);
963
            }
964
        }
965
        return $this->create_alias($domain_id, 'postmaster', $mem->user['login'] . '@' . $mailname);
966
    }
967
968
    /* ----------------------------------------------------------------- */
969
970
    /** hook function called by variables when a variable is changed
971
     * @access private
972
     */
973
    function hook_variable_set($name, $old, $new) {
974
        global $err, $db;
975
        $err->log("mail", "hook_variable_set($name,$old,$new)");
976
977
        if ($name == "default_spf_value") {
978
            $new = trim($new);
979
            $old = trim($old);
980
            $db->query("SELECT domaine,login,compte FROM domaines, membres WHERE gesdns=1 AND gesmx=1 and membres.uid=domaines.compte;");
981
            $res=array();
982
            while ($db->next_record()) $res[]=$db->Record;
983
            foreach ($res as $record) {
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
            $res=array();
993
            while ($db->next_record()) $res[]=$db->Record;
994
            foreach ($res as $record) {
995
                $this->set_dns_dmarc($record["domaine"], $new, $old, $record["compte"], $record["login"]);
996
            }
997
        }
998
    }
999
1000
    /* ----------------------------------------------------------------- */
1001
1002
    /** Set or UPDATE the DNS record for the domain $dom(str) to be $spf
1003
     * account's login is current and if not it's $login.
1004
     * don't change spf if current value is not $old
1005
     * @access private
1006
     */
1007
    function set_dns_spf($domain, $spf, $previous = -1, $uid = -1, $login = -1) {
1008
        global $db, $cuid, $mem;
1009
        // defaults
1010
        if ($uid === -1) {
1011
            $uid = intval($cuid);
1012
        } else {
1013
            $uid = intval($uid);
1014
        }
1015
        if ($login === -1) {
1016
            $login = $mem->user["login"];
1017
        }
1018
        // Search for the record in sub_domaines table
1019
        $db->query("SELECT * FROM sub_domaines WHERE compte= ? AND domaine= ? AND sub='' AND type='txt' AND valeur LIKE 'v=spf1 %' AND web_action!='DELETE';", array($uid, $domain));
1020
        if ($db->next_record()) {
1021
            if ($previous !== -1 && $db->Record["valeur"] == "v=spf1 " . $spf) {
1022
                return; // skip, no change asked.
1023
            }
1024
            $db->query("UPDATE sub_domaines SET web_action='DELETE' WHERE id= ? ;",array($db->Record["id"]));
1025
        }
1026
        $db->query("INSERT INTO sub_domaines SET compte= ?, domaine= ?, sub='', type='txt', valeur= ? , web_action='UPDATE';", array($uid, $domain, "v=spf1 " . $spf));
1027
        $db->query("UPDATE domaines SET dns_action='UPDATE' WHERE domaine= ?;", array($domain));
1028
    }
1029
1030
    /* ----------------------------------------------------------------- */
1031
1032
    /** Set or UPDATE the DNS record for the domain $dom(str) to be $dmarc
1033
     * account's login is current and if not it's $login.
1034
     * don't change dmarc if current value is not $old
1035
     * @access private
1036
     */
1037
    function set_dns_dmarc($domain, $dmarc, $previous = -1, $uid = -1, $login = -1) {
1038
        global $db, $cuid, $mem, $L_FQDN;
1039
        // defaults
1040
        if ($uid === -1) {
1041
            $uid = intval($cuid);
1042
        } else {
1043
            $uid = intval($uid);
1044
        }
1045
        if ($login === -1) {
1046
            $login = $mem->user["login"];
1047
        }
1048
        $dmarc = str_replace("%%ADMINMAIL%%", "admin@" . $L_FQDN, $dmarc);
1049
        $dmarc = str_replace("%%USERMAIL%%", $login . "@" . $L_FQDN, $dmarc);
1050
1051
        // Search for the record in sub_domaines table
1052
        $db->query("SELECT * FROM sub_domaines WHERE compte= ? AND domaine= ? AND sub='_dmarc' AND type='txt' AND valeur LIKE 'v=dmarc1;%' AND web_action!='DELETE';", array($uid, $domain));
1053
        if ($db->next_record()) {
1054
            if ($previous !== -1 && $db->Record["valeur"] == "v=dmarc1;" . $dmarc) {
1055
                return; // skip, no change asked.
1056
            }
1057
            $db->query("UPDATE sub_domaines SET web_action='DELETE' WHERE id= ?;", array($db->Record["id"]));
1058
        }
1059
        $db->query("INSERT INTO sub_domaines SET compte= ?, domaine= ?, sub='_dmarc', type='txt', valeur= ?, web_action='UPDATE';", array($uid, $domain, "v=dmarc1;" . $dmarc));
1060
        $db->query("UPDATE domaines SET dns_action='UPDATE' WHERE domaine= ?;", array($domain));
1061
    }
1062
1063
    /* ----------------------------------------------------------------- */
1064
1065
    /** hook function called by AlternC-upnp to know which open 
1066
     * tcp or udp ports this class requires or suggests
1067
     * @return array a key => value list of port protocol name mandatory values
1068
     * @access private
1069
     */
1070
    function hook_upnp_list() {
1071
        return array(
1072
            "imap" => array("port" => 143, "protocol" => "tcp", "mandatory" => 1),
1073
            "imaps" => array("port" => 993, "protocol" => "tcp", "mandatory" => 1),
1074
            "pop" => array("port" => 110, "protocol" => "tcp", "mandatory" => 1),
1075
            "pops" => array("port" => 995, "protocol" => "tcp", "mandatory" => 1),
1076
            "smtp" => array("port" => 25, "protocol" => "tcp", "mandatory" => 1),
1077
            "sieve" => array("port" => 2000, "protocol" => "tcp", "mandatory" => 1),
1078
            "submission" => array("port" => 587, "protocol" => "tcp", "mandatory" => 0),
1079
        );
1080
    }
1081
1082
}
1083
1084
/* Class m_mail */
1085