m_mail.php 33.9 KB
Newer Older
1
2
<?php
/*
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  ----------------------------------------------------------------------
  AlternC - Web Hosting System
  Copyright (C) 2000-2012 by the AlternC Development Team.
  https://alternc.org/
  ----------------------------------------------------------------------
  LICENSE
  
  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License (GPL)
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  To read the license please visit http://www.gnu.org/copyleft/gpl.html
  ----------------------------------------------------------------------
  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
* classes) of hosted users.
29
*
30
31
32
33
34
* @copyright    AlternC-Team 2012-09-01 http://alternc.com/
* This class is directly using the following alternc MySQL tables:
* address = any used email address will be defined here, mailbox = pop/imap mailboxes, recipient = redirection from an email to another
* and indirectly the domain class, to know domain names from their id in the DB.
* This class is also defining a few hooks, search ->invoke in the code.
35
36
37
*/
class m_mail {

38
39
40

  /* ----------------------------------------------------------------- */
  /** domain list for this account
41
42
43
44
45
   * @access private
   */
  var $domains;


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
  /* ----------------------------------------------------------------- */
  /** If an email has those chars, 'not nice in shell env' ;) 
   * we don't store the email in $mail/u/{user}_domain, but in $mail/_/{address_id}_domain
   * @access private
   */
  var $specialchars=array('"',"'",'\\','/');


  /* ----------------------------------------------------------------- */
  /** If an email has those chars, we will ONLY allow RECIPIENTS, NOT POP/IMAP for DOVECOT !
   * Since Dovecot doesn't allow those characters
   * @access private
   */
  var $forbiddenchars=array('"',"'",'\\','/','?','!','*','$','|','#','+');


62
  /* ----------------------------------------------------------------- */
63
64
  /** Number of results for a pager display
   * @access public
65
   */
66
  var $total;
67

68
69
70
71
72
73
74
75
76
77
  
  // Human server name for help
  var $srv_submission;
  var $srv_smtp;
  var $srv_smtps;
  var $srv_imap;
  var $srv_imaps;
  var $srv_pop3;
  var $srv_pop3s;

78
  var $cache_domain_mail_size = array();
79
80
81
82
83
  /* ----------------------------------------------------------------- */
  /** 
   * Constructeur
   */
  function m_mail() {
Alan Garcia's avatar
Alan Garcia committed
84
85
86
87
88
89
90
    $this->srv_submission = variable_get('mail_human_submission', '%%FQDN%%','Human name for mail server (submission protocol)', array(array('desc'=>'Name','type'=>'string')));
    $this->srv_smtp       = variable_get('mail_human_smtp',       '%%FQDN%%','Human name for mail server (SMTP protocol)', array(array('desc'=>'Name','type'=>'string')));
    $this->srv_smtps      = variable_get('mail_human_smtps',      '%%FQDN%%','Human name for mail server (SMTPS protocol)', array(array('desc'=>'Name','type'=>'string')));
    $this->srv_imap       = variable_get('mail_human_imap',       '%%FQDN%%','Human name for IMAP mail server', array(array('desc'=>'Name','type'=>'string')));
    $this->srv_imaps      = variable_get('mail_human_imaps',      '%%FQDN%%','Human name for IMAPS mail server', array(array('desc'=>'Name','type'=>'string')));
    $this->srv_pop3       = variable_get('mail_human_pop3',       '%%FQDN%%','Human name for POP3 mail server', array(array('desc'=>'Name','type'=>'string')));
    $this->srv_pop3s      = variable_get('mail_human_pop3s',      '%%FQDN%%','Human name for POP3s mail server', array(array('desc'=>'Name','type'=>'string')));
91
92
  }

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
  function hook_menu() {
    $obj = array(
      'title'       => _("Email Addresses"),
      'ico'         => 'images/mail.png',
      'link'        => 'toggle',
      'pos'         => 30,
      'links'       => array(),
     ) ;

     foreach ($this->enum_domains() as $d) {
       $obj['links'][] =
         array (
           'txt' => htmlentities($d["domaine"]).'&nbsp;'.htmlentities("(".$d["nb_mail"].")"),
           'url' => "mail_list.php?domain_id=".urlencode($d['id']),
         );
     }

     return $obj;
  }

113
114
115
116
117
118
119
120
121
122
123
124
125
  function get_total_size_for_domain($domain) {
    global $db;
    if (empty($this->cache_domain_mail_size)) {
      $db->query("SELECT SUBSTRING_INDEX(user,'@', -1) as domain, SUM(quota_dovecot) AS sum FROM dovecot_view group by domain ;");
      while ($db->next_record() ) {
        $dd = $db->f('domain');
        $this->cache_domain_mail_size[ $dd ] = $db->f('sum');
      }
    }
    if ( isset( $this->cache_domain_mail_size[$domain]) ) return $this->cache_domain_mail_size[$domain];
    return 0;
  }

126
127
128
129
  // FIXME documenter
  function catchall_getinfos($domain_id) {
    global $dom, $db;
    $rr=array(
130
      'mail_id'=>'',
131
132
133
134
135
      'domain' =>$dom->get_domain_byid($domain_id),
      'target' => '',
      'type'   => '',
      );
    
136
    $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='';");
137
138
    if ($db->next_record()) {
      $rr['target'] = $db->f('dst');
139
      $rr['mail_id'] = $db->f('mail_id');
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
    }

    // Does it redirect to a specific mail or to a domain
    if (empty($rr['target'])) {
      $rr['type']='none';
    } elseif (substr($rr['target'],0,1)=='@') {
      $rr['type']='domain';
    } else {
      $rr['type']='mail';
    }
    
    return $rr;
  }

  function catchall_del($domain_id) {
155
156
157
    $catch = $this->catchall_getinfos($domain_id);
    if (empty($catch['mail_id'])) return false;
    return $this->delete($catch['mail_id']);
158
159
160
  }

  function catchall_set($domain_id, $target) {
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
    // target :
    $target=rtrim($target);
    if ( substr_count($target,'@') == 0 ) { // Pas de @
      $target = '@'.$target;
    } 

    if ( substr($target,0,1) == '@' ) { // le premier caractere est un @
      // FIXME validate domain
    } else { // ca doit être un mail
      if (!filter_var($target,FILTER_VALIDATE_EMAIL)) {
        $err->raise("mail",_("The email you entered is syntaxically incorrect"));
        return false;
      }
    }
    $this->catchall_del($domain_id);
    return $this->create_alias($domain_id, '', $target, "catchall", true);
177
178
  }

179

180
181
182
183
184
185
186
  /* ----------------------------------------------------------------- */
  /** get_quota (hook for quota class), returns the number of used 
   * service for a quota-bound service
   * @param $name string the named quota we want
   * @return the number of used service for the specified quota, 
   * or false if I'm not the one for the named quota
   */
187
  function hook_quota_get() {
188
    global $db,$err,$cuid;
189
190
191
192
193
    $err->log("mail","getquota");
    $q=Array("name"=>"mail", "description"=>_("Email addresses"), "used"=>0);
    $db->query("SELECT COUNT(*) AS cnt FROM address a, domaines d WHERE a.domain_id=d.id AND d.compte=$cuid AND a.type='';");
    if ($db->next_record()) {
      $q['used']=$db->f("cnt");
194
    }
195
    return $q;
196
197
  }

198

199
200
201
  /* ----------------------------------------------------------------- */
  /** Password policy kind used in this class (hook for admin class)
   * @return array an array of policykey => "policy name (for humans)"
202
203
   */
  function alternc_password_policy() {
204
    return array("pop"=>_("Email account password"));
205
206
  }

207

208
  /* ----------------------------------------------------------------- */
209
  /** Returns the list of mail-hosting domains for a user
210
   * @return array indexed array of hosted domains
211
   */
212
  function enum_domains($uid=-1) {
213
214
      global $db,$err,$cuid;
      $err->log("mail","enum_domains");
215
      if ($uid == -1) { $uid = $cuid; }
Alan Garcia's avatar
Alan Garcia committed
216
217
218
219
220
221
222
223
224
      $db->query("
SELECT
  d.id,
  d.domaine,
  IFNULL( COUNT(a.id), 0) as nb_mail
FROM
  domaines d LEFT JOIN address a ON (d.id=a.domain_id AND a.type='')
WHERE
  d.compte = $uid
225
  and d.gesmx = 1
Alan Garcia's avatar
Alan Garcia committed
226
227
228
229
230
231
GROUP BY
  d.id
ORDER BY
  d.domaine
;
");
232
      $this->enum_domains=array();
233
234
      while($db->next_record()){
          $this->enum_domains[]=$db->Record;
235
      }
236
237
      return $this->enum_domains;
  }
238

239

240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
  /* ----------------------------------------------------------------- */
  /** available: tells if an email address can be installed in the server
   * check the domain part (is it mine too), the syntax, and the availability.
   * @param $mail string email to check
   * @return boolean true if the email can be installed on the server 
   */  
  function available($mail){
    global $db,$err,$dom;
    $err->log("mail","available");    
    list($login,$domain)=explode("@",$mail,2);
    // Validate the domain ownership & syntax
    if (!($dom_id=$dom->get_domain_byname($domain))) {
      return false;
    }
    // Validate the email syntax:
    if (!filter_var($mail,FILTER_VALIDATE_EMAIL)) {
      $err->raise("mail",_("The email you entered is syntaxically incorrect"));
      return false;
    }
    // Check the availability
    $db->query("SELECT a.id FROM address a WHERE a.domain_id=".$dom_id." AND a.address='".addslashes($login)."';");
    if ($db->next_record()) {
      return false;
    } else {
      return true;
    }
  }


269
  /* ----------------------------------------------------------------- */
270
  /* function used to list every mail address hosted on a domain.
271
272
273
   * @param $dom_id integer the domain id.
   * @param $search string search that string in recipients or address.
   * @param $offset integer skip THAT much emails in the result.
274
   * @param $count integer return no more than THAT much emails. -1 for ALL. Offset is ignored then.
275
   * @result an array of each mail hosted under the domain.
276
   */
277
  function enum_domain_mails($dom_id = null, $search="", $offset=0, $count=30, $show_systemmails=false){
278
    global $db,$err,$cuid,$hooks;
279
    $err->log("mail","enum_domains_mail");
280
281
282
283
284

    $search=trim($search);

    $where="a.domain_id=$dom_id";
    if ($search) $where.=" AND (a.address LIKE '%".addslashes($search)."%' OR r.recipients LIKE '%".addslashes($search)."%')";
285
    if (!$show_systemmails) $where.=" AND type='' ";
286
287
288
    $db->query("SELECT count(a.id) AS total FROM address a LEFT JOIN recipient r ON r.address_id=a.id WHERE $where;");
    $db->next_record();
    $this->total=$db->f("total");
289
    if ($count!=-1) $limit="LIMIT $offset,$count"; else $limit="";
290
    $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  
291
         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 
292
         WHERE $where AND d.id=a.domain_id $limit ;");
293
    if (! $db->next_record()) {
294
      $err->raise("mail",_("No email found for this query"));
295
296
      return false;
    }
297
    $res=array();
298
299
300
301
    do {
      $details=$db->Record;
      // if necessary, fill the typedata with data from hooks ...
      if ($details["type"]) {
302
	      $result=$hooks->invoke("hook_mail_get_details",array($details)); // Will fill typedata if necessary
303
	      $details["typedata"]=implode("<br />",$result);
304
305
306
      }
      $res[]=$details;
    } while ($db->next_record());
307
308
309
    return $res;
  }

310

311
312
313
314
  function hook_mail_get_details($detail) {
    if ($detail['type']=='catchall') 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']));
  }

315
316

  /* ----------------------------------------------------------------- */
317
  /** Function used to insert a new mail into the db
318
319
320
321
322
323
324
325
326
327
   * should be used by the web interface, not by third-party programs.
   *
   * This function calls the hook "hooks_mail_cancreate"
   * which must return FALSE if the user can't create this email, and raise and error accordingly
   * 
   * @param $dom_id integer A domain_id (owned by the user) 
   * (will be the part at the right of the @ in the email)
   * @param $mail string the left part of the email to create (something@dom_id)
   * @return an hashtable containing the database id of the newly created mail, 
   * or false if an error occured ($err is filled accordingly)
328
   */ 
329
  function create($dom_id, $mail,$type="",$dontcheck=false){
330
    global $err,$db,$cuid,$quota,$dom,$hooks;
331
    $err->log("mail","create",$mail);
332
333
334
335

    // Validate the domain id
    if (!($domain=$dom->get_domain_byid($dom_id))) {
      return false;
Steven Mondji-Lerider's avatar
Steven Mondji-Lerider committed
336
    }
337
338
339

    // Validate the email syntax:
    $m=$mail."@".$domain;
340
    if (!filter_var($m,FILTER_VALIDATE_EMAIL) && !$dontcheck) {
341
342
      $err->raise("mail",_("The email you entered is syntaxically incorrect"));
      return false;
343
    }
344
345

    // Call other classes to check we can create it:
346
    $cancreate=$hooks->invoke("hook_mail_cancreate",array($dom_id,$mail));
347
348
    if (in_array(false,$cancreate,true)) {
      return false;
349
    }
Steven Mondji-Lerider's avatar
Steven Mondji-Lerider committed
350

351
    // Check the quota:
Steven Mondji-Lerider's avatar
Steven Mondji-Lerider committed
352
    if (!$quota->cancreate("mail")) {
353
      $err->raise("mail",_("You cannot create email addresses: your quota is over"));
Steven Mondji-Lerider's avatar
Steven Mondji-Lerider committed
354
355
      return false;
    }
356
357
358
359
360
361
362
    // Already exists?
    $db->query("SELECT * FROM address WHERE domain_id=".$dom_id." AND address='".addslashes($mail)."';");
    if ($db->next_record()) {
      $err->raise("mail",_("This email address already exists"));
      return false;
    }
    // Create it now
363
    $db->query("INSERT INTO address (domain_id, address,type) VALUES ($dom_id, '".addslashes($mail)."','$type');");
364
365
366
367
368
369
    if (!($id=$db->lastid())) {
      $err->raise("mail",_("An unexpected error occured when creating the email"));
      return false;
    }
    return $id;
  }
Steven Mondji-Lerider's avatar
Steven Mondji-Lerider committed
370

371
372
373
374
375
376
377

  /* ----------------------------------------------------------------- */
  /** function used to get every information we can on a mail 
  * @param $mail_id integer
  * @return array a hashtable with all the informations for that email
  */
  function get_details($mail_id) {
378
    global $db, $err, $cuid, $hooks;
379
380
381
    $err->log("mail","get_details");

    $mail_id=intval($mail_id);
382
383
384
385
    // Validate that this email is owned by me...
    if (!($mail=$this->is_it_my_mail($mail_id))) {
      return false;
    }
386
387

    // We fetch all the informations for that email: these will fill the hastable : 
388
    $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;");
389
390
391
392
    if (! $db->next_record()) return false;
    $details=$db->Record;
    // if necessary, fill the typedata with data from hooks ...
    if ($details["type"]) {
393
      $result=$hooks->invoke("hook_mail_get_details",array($mail_id)); // Will fill typedata if necessary
394
395
396
      $details["typedata"]=implode("<br />",$result);
    }
    return $details;
397
398
  }

399

400
401
  private $isitmy_cache=array(); 

402
403
404
405
406
407
408
409
410
411
  /* ----------------------------------------------------------------- */
  /** Check if an email is mine ...
   *
   * @param $mail_id integer the number of the email to check
   * @return string the complete email address if that's mine, false if not
   * ($err is filled accordingly)
   */ 
  function is_it_my_mail($mail_id){
    global $err,$db,$cuid;
    $mail_id=intval($mail_id);
412
413
414
    // cache it (may be called more than one time in the same page).
    if (isset($this->isitmy_cache[$mail_id])) return $this->isitmy_cache[$mail_id];

415
416
    $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;");
    if ($db->next_record()) {
417
      return $this->isitmy_cache[$mail_id]=$db->f("email");
418
419
    } else {
      $err->raise("mail",_("This email is not yours, you can't change anything on it"));
420
      return $this->isitmy_cache[$mail_id]=false;
421
422
423
424
    }
  }


425
426
427
428
429
430
431
432
433
  /* ----------------------------------------------------------------- */
  /** Hook called when the DOMAIN class will delete a domain.
   *
   * @param $dom integer the number of the email to delete
   * @return true if the email has been properly deleted 
   * or false if an error occured ($err is filled accordingly)
   */ 
  function hook_dom_del_mx_domain($dom_id) {
    $list=$this->enum_domain_mails($dom_id,"",0,-1);
434
435
436
437
    if (is_array($list)) {
      foreach($list as $one) {
	$this->delete($one["id"]);
      }
438
439
440
441
    }
    return true;
  }

Alan Garcia's avatar
Alan Garcia committed
442
443
444
445
446
447
448
449
450
451
  // return the alternc account's ID of the mail_id
  function get_account_by_mail_id($mail_id) {
    global $db,$err;
    $db->query("select compte as uid from domaines d, address a where a.domain_id = d.id and a.id = $mail_id");
    if ( !$db->next_record()) {
      return false;
    }  
    return $db->f('uid');
  }

452

453
454
455
456
457
458
459
460
461
462
463
  /* ----------------------------------------------------------------- */
  /** Function used to delete a mail from the db
   * should be used by the web interface, not by third-party programs.
   *
   * @param $mail_id integer the number of the email to delete
   * @return true if the email has been properly deleted 
   * or false if an error occured ($err is filled accordingly)
   */ 
  function delete($mail_id){
    global $err,$db,$cuid,$quota,$dom,$hooks;
    $err->log("mail","delete");
464

465
466
467
468
469
470
471
472
473
474
475
    $mail_id=intval($mail_id);

    if (!$mail_id)  {
      $err->raise("mail",_("The email you entered is syntaxically incorrect"));
      return false;
    }
    // Validate that this email is owned by me...
    if (!($mail=$this->is_it_my_mail($mail_id))) {
      return false;
    }

Alan Garcia's avatar
Alan Garcia committed
476
477
478
    $mailinfos=$this->get_details($mail_id);
    $hooks->invoke('hook_mail_delete', array($mail_id, $mailinfos['address'].'@'.$mailinfos['domain'] ));

479
480
481
482
483
484
    // Search for that address:
    $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';");
    if (!$db->next_record()) {
      $err->raise("mail",_("The email %s does not exist, it can't be deleted"),$mail);
      return false;
    }
485
    if ($db->f("mail_action")!="OK" || ($db->f("islocal") && $db->f("mailbox_action")!="OK")) { // will be deleted soon ...
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
      $err->raise("mail",_("The email %s is already marked for deletion, it can't be deleted"),$mail);
      return false;
    }
    $mail_id=$db->f("id");

    if ($db->f("islocal")) {
      // If it's a pop/imap mailbox, mark it for deletion
      $db->query("UPDATE address SET mail_action='DELETE', enabled=0 WHERE id='$mail_id';");
      $db->query("UPDATE mailbox SET mail_action='DELETE' WHERE address_id='$mail_id';");
      $err->raise("mail",_("The email %s has been marked for deletion"),$mail);
    } else {
      // If it's only aliases, delete it NOW.
      $db->query("DELETE FROM address WHERE id='$mail_id';");
      $db->query("DELETE FROM mailbox WHERE address_id='$mail_id';");
      $db->query("DELETE FROM recipient WHERE address_id='$mail_id';");
      $err->raise("mail",_("The email %s has been successfully deleted"),$mail);
    }
    return true;
  }


  /* ----------------------------------------------------------------- */
  /** Function used to undelete a pending deletion mail from the db
   * should be used by the web interface, not by third-party programs.
   *
   * @param $mail_id integer the email id
   * @return true if the email has been properly undeleted 
   * or false if an error occured ($err is filled accordingly)
   */ 
  function undelete($mail_id){
    global $err,$db,$cuid,$quota,$dom,$hooks;
    $err->log("mail","undelete");

    $mail_id=intval($mail_id);

    if (!$mail_id)  {
      $err->raise("mail",_("The email you entered is syntaxically incorrect"));
      return false;
    }
    // Validate that this email is owned by me...
    if (!($mail=$this->is_it_my_mail($mail_id))) {
      return false;
    }

    // Search for that address:
    $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';");
    if (!$db->next_record()) {
      $err->raise("mail",_("The email %s does not exist, it can't be undeleted"),$mail);
      return false;
    }
    if ($db->f("type")!="") { // Technically special : mailman, sympa ... 
      $err->raise("mail",_("The email %s is special, it can't be undeleted"),$mail);
      return false;
    }
    if ($db->f("mailbox_action")!="DELETE" || $db->f("mail_action")!="DELETE") { // will be deleted soon ...
      $err->raise("mail",_("Sorry, deletion of email %s is already in progress, or not marked for deletion, it can't be undeleted"),$mail);
      return false;
    }
    $mail_id=$db->f("id");

    if ($db->f("islocal")) {
      // If it's a pop/imap mailbox, mark it for deletion
Alan Garcia's avatar
Alan Garcia committed
548
549
      $db->query("UPDATE address SET mail_action='OK', `enabled`=1 WHERE id='$mail_id';");
      $db->query("UPDATE mailbox SET mail_action='OK' WHERE address_id='$mail_id';");
550
551
552
553
554
555
556
      $err->raise("mail",_("The email %s has been undeleted"),$mail);
      return true;
    } else {
      $err->raise("mail",_("-- Program Error -- The email %s can't be undeleted"),$mail);
      return false;
    }
  }
557
558


559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
  /* ----------------------------------------------------------------- */
  /** set the password of an email address.
   * @param $mail_id integer email ID 
   * @param $pass string the new password.
   * @return boolean true if the password has been set, false else, raise an error.
   */  
  function set_passwd($mail_id,$pass){
    global $db,$err,$admin;
    $err->log("mail","setpasswd");

    if (!($email=$this->is_it_my_mail($mail_id))) return false;
    if (!$admin->checkPolicy("pop",$email,$pass)) return false;
    if (!$db->query("UPDATE address SET password='"._md5cr($pass)."' where id=$mail_id;")) return false;
    return true;
  }


  /* ----------------------------------------------------------------- */
  /** Enables an email address.
   * @param $mail_id integer Email ID
   * @return boolean true if the email has been enabled.
   */  
  function enable($mail_id){
    global $db,$err;
    $err->log("mail","enable");
    if (!($email=$this->is_it_my_mail($mail_id))) return false;
    if (!$db->query("UPDATE address SET `enabled`=1 where id=$mail_id;")) return false;
    return true;
  }


  /* ----------------------------------------------------------------- */
  /** Disables an email address.
   * @param $mail_id integer Email ID
   * @return boolean true if the email has been enabled.
   */ 
  function disable($mail_id){
    global $db,$err;
    $err->log("mail","disable");
    if (!($email=$this->is_it_my_mail($mail_id))) return false;
    if (!$db->query("UPDATE address SET `enabled`=0 where id=$mail_id;")) return false;
    return true;
  }


  /* ----------------------------------------------------------------- */
  /** Function used to update an email settings
   * should be used by the web interface, not by third-party programs.
   *
   * @param $mail_id integer the number of the email to delete
   * @param $islocal boolean is it a POP/IMAP mailbox ?
   * @param $quotamb integer if islocal=1, quota in MB
611
   * @param $recipients string recipients, one mail per line.
612
613
614
   * @return true if the email has been properly edited
   * or false if an error occured ($err is filled accordingly)
   */ 
615
  function set_details($mail_id, $islocal, $quotamb, $recipients,$delivery="dovecot",$dontcheck=false) {
616
    global $err,$db,$cuid,$quota,$dom,$hooks;
617
    $delivery=mysql_real_escape_string($delivery);
618
619
620
621
622
623
624
625
626
627
    $err->log("mail","set_details");
    if (!($me=$this->get_details($mail_id))) {
      return false;
    }
    if ($me["islocal"] && !$islocal) {
      // delete pop
      $db->query("UPDATE mailbox SET mail_action='DELETE' WHERE address_id=".$mail_id.";");
    } 
    if (!$me["islocal"] && $islocal) {
      // create pop
628
629
630
631
      $path="";
      if($delivery=="dovecot"){
        $path=ALTERNC_MAIL."/".substr($me["address"]."_",0,1)."/".$me["address"]."_".$me["domain"];
      }
632
      foreach($this->forbiddenchars as $str) {
633
	    if (strpos($me["address"],$str)!==false) {
634
	      $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"));
635
636
637
          return false;
          break;
        }
638
639
      }
      foreach($this->specialchars as $str) {
640
641
642
643
	    if (strpos($me["address"],$str)!==false) {
	      $path=ALTERNC_MAIL."/_/".$me["id"]."_".$me["domain"];
	      break;
  	    } 
644
      }
645
      $db->query("INSERT INTO mailbox SET address_id=$mail_id, delivery='$delivery', path='".addslashes($path)."';");
646
647
    }
    if ($me["islocal"] && $islocal && $me["mailbox_action"]=="DELETE") {
648
      $db->query("UPDATE mailbox SET mail_action='OK' WHERE mail_action='DELETE' AND address_id=".$mail_id.";");
649
650
651
    }

    if ($islocal) {
Benjamin Sonntag's avatar
Benjamin Sonntag committed
652
      if ($quotamb!=0 && $quotamb<(intval($me["used"]/1024/1024)+1)) {
653
	$quotamb=intval($me["used"]/1024/1024)+1;
654
	$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"));
655
      }
656
657
658
659
660
661
662
      $db->query("UPDATE mailbox SET quota=".intval($quotamb)." WHERE address_id=".$mail_id.";");
    }

    $r=explode("\n",$recipients);
    $red="";
    foreach($r as $m) {
      $m=trim($m);
663
      if ($m && ( filter_var($m,FILTER_VALIDATE_EMAIL) || $dontcheck)  // Recipient Email is valid
664
	  && $m!=($me["address"]."@".$me["domain"])) {  // And not myself (no loop allowed easily ;) )
665
666
667
668
669
670
671
672
673
674
	$red.=$m."\n";
      }
    }
    $db->query("DELETE FROM recipient WHERE address_id=".$mail_id.";");
    if ($m) {
      $db->query("INSERT INTO recipient SET address_id=".$mail_id.", recipients='".addslashes($red)."';");
    }
    return true;
  }

675
676
677
678
679
680
  /* ----------------------------------------------------------------- */
  /** A wrapper used by mailman class to create it's needed addresses 
   * @ param : $dom_id , the domain id associated to a given address
   * @ param : $m , the left part of the  mail address being created
   * @ param : $delivery , the delivery used to deliver the mail
   */
681

682
  function add_wrapper($dom_id,$m,$delivery){
683
684
    global $err,$db,$mail;
    $err->log("mail","add_wrapper","creating $delivery $m address");
685

686
687
688
    $mail_id=$mail->create($dom_id,$m,$delivery);
    $this->set_details($mail_id,1,0,'',$delivery);
    // FIXME return error code
689
  }
690
691
692
693
694
695

  /* ----------------------------------------------------------------- */
  /** A function used to create an alias for a specific address
   * @ param : $dom_id , the domain sql identifier
   * @ param : $m , the alias we want to create
   * @ param : $alias , the already existing aliased address
696
   * @ param : $type, the type of the alias created
697
   */
698
699
700
  function create_alias($dom_id,$m,$alias,$type="",$dontcheck=false) {
    global $err,$db,$mail;
    $err->log("mail","create_alias","creating $m alias for $alias type $type");
701

702
703
704
    $mail_id=$mail->create($dom_id,$m,$type,$dontcheck);
    $this->set_details($mail_id,0,0,$alias,"dovecot",$dontcheck);
    // FIXME return error code
705
706
707
708
  }



709
710
711
712
713
714
715
716
717
718
  /* ----------------------------------------------------------------- */
  /** A wrapper used by mailman class to create it's needed addresses 
   * @ param : $mail_id , the mysql id of the mail address we want to delete
   * of the email for the current acccount.
   */
  function del_wrapper($mail_id){
    global $err,$db;
    $err->log("mail","del_wrapper");
    $this->delete($mail_id);
  }
719

720
  /* ----------------------------------------------------------------- */
721
722
723
  /** Export the mail information of an account 
   * @return: str, string containing the complete configuration 
   * of the email for the current acccount.
724
   */
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
  function alternc_export_conf() {
    global $db,$err,$mail_localbox;
    $err->log("mail","export");
    $domain=$this->enum_domains();
    $str="<mail>\n";
    $onepop=false;
    foreach ($domain as $d) {
      $str.="  <domain>\n    <name>".xml_entities($d["domain"])."</name>\n";
      $s=$this->enum_domain_mails($d["id"]);
      if (count($s)) {
	while (list($key,$val)=each($s)){
	  $test=$this->get_details($val['id']);
	  $str.="    <address>\n";
	  $str.="      <name>".xml_entities($val["address"])."</name>\n";
	  $str.="      <enabled>".xml_entities($val["enabled"])."</enabled>\n";
	  if(is_array($val["islocal"])){
	    $str.="      <islocal>1</islocal>\n";
             $str.="      <quota>".$val["quota"]."</quota>\n";
             $str.="      <path>".$val["path"]."</path>\n";
744
           }else{
745
             $str.="      <islocal>0</islocal>\n";
746
          }
747
748
749
          if(!empty($val["recipients"])){
	    $r=explode("\n",$val["recipients"]);
              foreach($r as $recip){
750
751
752
753
754
755
756
757
758
759
760
761
                $str.="      <recipients>".$recip."<recipients>\n";
              }
          }
       $str.="    </address>\n";
     }
       }     
       $str.="  </domain>\n";
     }
     $str.="</mail>\n";
     return $str;
   }
 
762

Alan Garcia's avatar
Alan Garcia committed
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
  /* ----------------------------------------------------------------- */
  /**
   * Return the list of allowed slave accounts (secondary-mx)
   * @return array
   */
  function enum_slave_account() {
    global $db,$err;
    $db->query("SELECT login,pass FROM mxaccount;");
    $res=array();
    while ($db->next_record()) {
        $res[]=$db->Record;
    }
    if (!count($res)) return false;
    return $res;
  }

  /* ----------------------------------------------------------------- */
  /**
   * Check for a slave account (secondary mx)
   * @param string $login the login to check
   * @param string $pass the password to check
   * @return boolean TRUE if the password is correct, or FALSE if an error occurred.
   */
  function check_slave_account($login,$pass) {
    global $db,$err;
788
789
    $login=mysql_real_escape_string($login);
    $pass=mysql_real_escape_string($pass);
Alan Garcia's avatar
Alan Garcia committed
790
791
792
793
794
795
    $db->query("SELECT * FROM mxaccount WHERE login='$login' AND pass='$pass';");
    if ($db->next_record()) {
        return true;
    }
    return false;
  }
796
797
798
799
800


  /* ----------------------------------------------------------------- */
  /** Out (echo) the complete hosted domain list : 
   */
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
  function echo_domain_list($format=null) {
    global $db,$err;
    $db->query("SELECT domaine FROM domaines WHERE gesmx=1 ORDER BY domaine");
    $lst=array();
    $tt="";
    while ($db->next_record()) {
      $lst[]=$db->f("domaine");
      $tt.=$db->f("domaine");
    }

    # Generate an integrity check 
    $obj=array('integrity'=>md5($tt),'items'=>$lst);

    switch($format) {
      case "json":
        return json_encode($obj);
        break;
      default:
        foreach ($lst as $l) { echo $l."\n"; }
        return true;
        break;
    } // switch
823
824
825
  }


Alan Garcia's avatar
Alan Garcia committed
826
827
828
829
830
831
832
833
834
  /* ----------------------------------------------------------------- */
  /**
   * Add a slave account that will be allowed to access the mxdomain list
   * @param string $login the login to add
   * @param string $pass the password to add
   * @return boolean TRUE if the account has been created, or FALSE if an error occurred.
   */
  function add_slave_account($login,$pass) {
    global $db,$err;
835
836
    $login=mysql_real_escape_string($login);
    $pass=mysql_real_escape_string($pass);
Alan Garcia's avatar
Alan Garcia committed
837
838
    $db->query("SELECT * FROM mxaccount WHERE login='$login'");
    if ($db->next_record()) {
839
      $err->raise("mail",_("The slave MX account was not found"));
Alan Garcia's avatar
Alan Garcia committed
840
841
842
843
844
845
846
847
848
849
850
851
852
853
      return false;
    }
    $db->query("INSERT INTO mxaccount (login,pass) VALUES ('$login','$pass')");
    return true;
  }


  /* ----------------------------------------------------------------- */
  /**
   * Remove a slave account
   * @param string $login the login to delete
   */
  function del_slave_account($login) {
    global $db,$err;
854
    $login=mysql_real_escape_string($login);
Alan Garcia's avatar
Alan Garcia committed
855
856
857
858
859
860
861
862
    $db->query("DELETE FROM mxaccount WHERE login='$login'");
    return true;
  }

  /* ----------------------------------------------------------------- */
  /** hook function called by AlternC when a domain is created for
   * the current user account using the SLAVE DOMAIN feature
   * This function create a CATCHALL to the master domain
863
864
   * @param string $domain_id Domain that has just been created
   * @param string $target_domain Master domain 
Alan Garcia's avatar
Alan Garcia committed
865
866
   * @access private
   */
867
  function hook_dom_add_slave_domain($domain_id,$target_domain) { 
Alan Garcia's avatar
Alan Garcia committed
868
    global $err;
869
870
    $err->log("mail","hook_dom_add_slave_domain",$domain_id);
    $this->catchall_set($domain_id,'@'.$target_domain); 
Alan Garcia's avatar
Alan Garcia committed
871
872
873
    return true;
  }

874
875
876
  /* ----------------------------------------------------------------- */
  /** hook function called by AlternC when a domain is created for
   * the current user account 
877
   * This function create a postmaster mail which is an alias to LOGIN @ FQDN
878
879
880
881
882
   * wich is a dynamic alias to the alternc's account mail
   * @param string $domain_id Domain that has just been created
   * @access private
   */
   function hook_dom_add_mx_domain($domain_id) {
883
    global $err, $mem, $L_FQDN,$db;
884
    $err->log("mail","hook_dom_add_mx_domain",$domain_id);
885
886
887
888
889
890
891
892
893

    $db->query("SELECT value FROM variable where name='mailname_bounce';");
    if (!$db->next_record()) {
      $err->raise("mail",_("The email %s does not exist, it can't be deleted"),$mail);
      return false;
    }
    $mailname=$db->f("value");

    return $this->create_alias($domain_id, 'postmaster', $mem->user['login'].'@'.$mailname );
894
895
  }

Alan Garcia's avatar
Alan Garcia committed
896

897

898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
  /* ----------------------------------------------------------------- */
  /** hook function called by AlternC-upnp to know which open 
   * tcp or udp ports this class requires or suggests
   * @return array a key => value list of port protocol name mandatory values
   * @access private
   */
  function hook_upnp_list() {
    return array(
		 "imap" => array("port" => 143, "protocol" => "tcp", "mandatory" => 1),
		 "imaps" => array("port" => 993, "protocol" => "tcp", "mandatory" => 1),
		 "pop" => array("port" => 110, "protocol" => "tcp", "mandatory" => 1),
		 "pops" => array("port" => 995, "protocol" => "tcp", "mandatory" => 1),
		 "smtp" => array("port" => 25, "protocol" => "tcp", "mandatory" => 1),
		 "sieve" => array("port" => 2000, "protocol" => "tcp", "mandatory" => 1),
		 "submission" => array("port" => 587, "protocol" => "tcp", "mandatory" => 0),
		 );
  }

 

918
919
} /* Class m_mail */

920