2006-04-26 12:28:53 +00:00
< ? php
2015-09-25 15:42:00 +00:00
2006-04-26 12:28:53 +00:00
/*
2012-08-28 08:59:05 +00:00
----------------------------------------------------------------------
AlternC - Web Hosting System
Copyright ( C ) 2000 - 2012 by the AlternC Development Team .
https :// alternc . org /
----------------------------------------------------------------------
LICENSE
2015-09-25 15:42:00 +00:00
2012-08-28 08:59:05 +00:00
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 .
2015-09-25 15:42:00 +00:00
2012-08-28 08:59:05 +00:00
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 .
2015-09-25 15:42:00 +00:00
2012-08-28 08:59:05 +00:00
To read the license please visit http :// www . gnu . org / copyleft / gpl . html
----------------------------------------------------------------------
Purpose of file : Manage Email accounts and aliases .
----------------------------------------------------------------------
2015-09-25 15:42:00 +00:00
*/
2009-09-08 05:29:38 +00:00
2006-04-26 12:28:53 +00:00
/**
2015-09-25 15:42:00 +00:00
* This class handle emails ( pop and / or aliases and even wrapper for internal
* classes ) of hosted users .
*
* @ 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 .
*/
2006-04-26 12:28:53 +00:00
class m_mail {
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** domain list for this account
* @ access private
*/
var $domains ;
/* ----------------------------------------------------------------- */
/** 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 ( '"' , " ' " , '\\' , '/' , '?' , '!' , '*' , '$' , '|' , '#' , '+' );
/* ----------------------------------------------------------------- */
/** Number of results for a pager display
* @ access public
*/
var $total ;
// 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 ;
var $cache_domain_mail_size = array ();
var $enum_domains = array ();
/* ----------------------------------------------------------------- */
/**
* Constructeur
*/
function m_mail () {
global $L_FQDN ;
$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' ));
$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' ));
$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' ));
$this -> srv_imap = variable_get ( 'mail_human_imap' , $L_FQDN , 'Human name for IMAP mail server' , array ( 'desc' => 'Name' , 'type' => 'string' ));
$this -> srv_imaps = variable_get ( 'mail_human_imaps' , $L_FQDN , 'Human name for IMAPS mail server' , array ( 'desc' => 'Name' , 'type' => 'string' ));
$this -> srv_pop3 = variable_get ( 'mail_human_pop3' , $L_FQDN , 'Human name for POP3 mail server' , array ( 'desc' => 'Name' , 'type' => 'string' ));
$this -> srv_pop3s = variable_get ( 'mail_human_pop3s' , $L_FQDN , 'Human name for POP3s mail server' , array ( 'desc' => 'Name' , 'type' => 'string' ));
}
2006-04-26 12:28:53 +00:00
2015-09-25 15:42:00 +00:00
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 " ]) . ' ' . htmlentities ( " ( " . $d [ " nb_mail " ] . " ) " ),
'url' => " mail_list.php?domain_id= " . urlencode ( $d [ 'id' ]),
);
}
2012-08-23 18:46:47 +00:00
2015-09-25 15:42:00 +00:00
return $obj ;
2014-03-07 08:36:28 +00:00
}
2015-09-25 15:42:00 +00:00
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 ;
2012-10-20 13:16:14 +00:00
}
2015-09-25 15:42:00 +00:00
// FIXME documenter
/**
* @ param string $domain_id
*/
function catchall_getinfos ( $domain_id ) {
global $dom , $db ;
$rr = array (
'mail_id' => '' ,
'domain' => $dom -> get_domain_byid ( $domain_id ),
'target' => '' ,
'type' => '' ,
);
$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=''; " );
if ( $db -> next_record ()) {
$rr [ 'target' ] = $db -> f ( 'dst' );
$rr [ 'mail_id' ] = $db -> f ( 'mail_id' );
}
// 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 ;
2012-10-20 13:16:14 +00:00
}
2015-09-25 15:42:00 +00:00
/**
* @ param string $domain_id
*/
function catchall_del ( $domain_id ) {
$catch = $this -> catchall_getinfos ( $domain_id );
if ( empty ( $catch [ 'mail_id' ])) {
return false ;
}
return $this -> delete ( $catch [ 'mail_id' ]);
2012-10-20 14:41:45 +00:00
}
2015-09-25 15:42:00 +00:00
/**
* @ param string $domain_id
* @ param string $target
*/
function catchall_set ( $domain_id , $target ) {
global $err ;
$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 );
$err -> error = " " ;
return $this -> create_alias ( $domain_id , '' , $target , " catchall " , true );
2012-04-06 10:10:36 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** 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
*/
function hook_quota_get () {
global $db , $err , $cuid ;
$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 " );
}
return $q ;
}
/* ----------------------------------------------------------------- */
/** Password policy kind used in this class ( hook for admin class )
* @ return array an array of policykey => " policy name (for humans) "
*/
function alternc_password_policy () {
return array ( " pop " => _ ( " Email account password " ));
}
/* ----------------------------------------------------------------- */
/** Returns the list of mail - hosting domains for a user
* @ return array indexed array of hosted domains
*/
function enum_domains ( $uid = - 1 ) {
global $db , $err , $cuid ;
$err -> log ( " mail " , " enum_domains " );
if ( $uid == - 1 ) {
$uid = $cuid ;
}
$db -> query ( "
2012-10-22 08:58:53 +00:00
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
2013-03-01 16:12:43 +00:00
and d . gesmx = 1
2012-10-22 08:58:53 +00:00
GROUP BY
d . id
ORDER BY
d . domaine
;
" );
2015-09-25 15:42:00 +00:00
$this -> enum_domains = array ();
while ( $db -> next_record ()) {
$this -> enum_domains [] = $db -> Record ;
}
return $this -> enum_domains ;
2012-08-25 15:54:28 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** 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 ;
}
2012-08-25 15:54:28 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/* function used to list every mail address hosted on a domain .
* @ 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 .
* @ param $count integer return no more than THAT much emails . - 1 for ALL . Offset is ignored then .
* @ result an array of each mail hosted under the domain .
*/
function enum_domain_mails ( $dom_id = null , $search = " " , $offset = 0 , $count = 30 , $show_systemmails = false ) {
global $db , $err , $hooks ;
$err -> log ( " mail " , " enum_domains_mail " );
$search = trim ( $search );
$where = " a.domain_id= $dom_id " ;
if ( $search ) {
$where .= " AND (a.address LIKE '% " . addslashes ( $search ) . " %' OR r.recipients LIKE '% " . addslashes ( $search ) . " %') " ;
}
if ( ! $show_systemmails ) {
$where .= " AND type='' " ;
}
$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 " );
if ( $count != - 1 ) {
$limit = " LIMIT $offset , $count " ;
} else {
$limit = " " ;
}
$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
2012-08-23 18:46:47 +00:00
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
2012-10-18 13:21:42 +00:00
WHERE $where AND d . id = a . domain_id $limit ; " );
2015-09-25 15:42:00 +00:00
if ( ! $db -> next_record ()) {
$err -> raise ( " mail " , _ ( " No email found for this query " ));
return array ();
}
$res = array ();
do {
$details = $db -> Record ;
// if necessary, fill the typedata with data from hooks ...
if ( $details [ " type " ]) {
$result = $hooks -> invoke ( " hook_mail_get_details " , array ( $details )); // Will fill typedata if necessary
$details [ " typedata " ] = implode ( " <br /> " , $result );
}
$res [] = $details ;
} while ( $db -> next_record ());
return $res ;
2012-08-19 20:50:47 +00:00
}
2012-08-23 18:46:47 +00:00
2015-09-25 15:42:00 +00:00
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' ]));
}
2012-04-06 10:10:36 +00:00
}
2012-08-23 18:46:47 +00:00
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** Function used to insert a new mail into the db
* 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 )
*/
function create ( $dom_id , $mail , $type = " " , $dontcheck = false ) {
global $err , $db , $quota , $dom , $hooks ;
$err -> log ( " mail " , " create " , $mail );
// Validate the domain id
if ( ! ( $domain = $dom -> get_domain_byid ( $dom_id ))) {
return false ;
}
2012-08-19 20:50:47 +00:00
2015-09-25 15:42:00 +00:00
// Validate the email syntax:
$m = $mail . " @ " . $domain ;
if ( ! filter_var ( $m , FILTER_VALIDATE_EMAIL ) && ! $dontcheck ) {
$err -> raise ( " mail " , _ ( " The email you entered is syntaxically incorrect " ));
return false ;
}
2012-08-23 18:46:47 +00:00
2015-09-25 15:42:00 +00:00
// Call other classes to check we can create it:
$cancreate = $hooks -> invoke ( " hook_mail_cancreate " , array ( $dom_id , $mail ));
if ( in_array ( false , $cancreate , true )) {
return false ;
}
// Check the quota:
if ( ! $quota -> cancreate ( " mail " )) {
$err -> raise ( " mail " , _ ( " You cannot create email addresses: your quota is over " ));
return false ;
}
// 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
$db -> query ( " INSERT INTO address (domain_id, address,type) VALUES ( $dom_id , ' " . addslashes ( $mail ) . " ',' $type '); " );
if ( ! ( $id = $db -> lastid ())) {
$err -> raise ( " mail " , _ ( " An unexpected error occured when creating the email " ));
return false ;
}
return $id ;
2012-08-24 13:37:56 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** 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 ) {
global $db , $err , $hooks ;
$err -> log ( " mail " , " get_details " );
$mail_id = intval ( $mail_id );
// Validate that this email is owned by me...
if ( ! ( $mail = $this -> is_it_my_mail ( $mail_id ))) {
return false ;
}
// We fetch all the informations for that email: these will fill the hastable :
$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; " );
if ( ! $db -> next_record ()) {
return false ;
}
$details = $db -> Record ;
// if necessary, fill the typedata with data from hooks ...
if ( $details [ " type " ]) {
$result = $hooks -> invoke ( " hook_mail_get_details " , array ( $mail_id )); // Will fill typedata if necessary
$details [ " typedata " ] = implode ( " <br /> " , $result );
}
return $details ;
2012-08-25 14:04:38 +00:00
}
2015-09-25 15:42:00 +00:00
private $isitmy_cache = array ();
/* ----------------------------------------------------------------- */
/** 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 );
// 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 ];
}
$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 ()) {
return $this -> isitmy_cache [ $mail_id ] = $db -> f ( " email " );
} else {
$err -> raise ( " mail " , _ ( " This email is not yours, you can't change anything on it " ));
return $this -> isitmy_cache [ $mail_id ] = false ;
}
2015-06-17 17:56:51 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** Hook called when the DOMAIN class will delete a domain .
*
* @ param $dom integer the number of the email to delete
* @ return boolean 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 ) {
global $db ;
$list = $this -> enum_domain_mails ( $dom_id , " " , 0 , - 1 );
if ( is_array ( $list )) {
foreach ( $list as $one ) {
$this -> delete ( $one [ " id " ]);
}
}
$db -> query ( " SELECT domaine FROM domaines WHERE id= $domain_id ; " );
if ( $db -> next_record ()) {
$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;%'); " );
$db -> query ( " UPDATE domaines SET dns_action='UPDATE' WHERE id= $domain_id ; " );
}
return true ;
2012-08-24 13:37:56 +00:00
}
2015-09-25 15:42:00 +00:00
// return the alternc account's ID of the mail_id
function get_account_by_mail_id ( $mail_id ) {
global $db ;
$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' );
2012-08-24 13:37:56 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
2013-02-07 15:48:28 +00:00
2015-09-25 15:42:00 +00:00
/** 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 boolean if the email has been properly deleted
* or false if an error occured ( $err is filled accordingly )
*/
function delete ( $mail_id ) {
global $err , $db , $hooks ;
$err -> log ( " mail " , " delete " );
$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 ;
}
$mailinfos = $this -> get_details ( $mail_id );
$hooks -> invoke ( 'hook_mail_delete' , array ( $mail_id , $mailinfos [ 'address' ] . '@' . $mailinfos [ 'domain' ]));
// 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 ;
}
if ( $db -> f ( " mail_action " ) != " OK " || ( $db -> f ( " islocal " ) && $db -> f ( " mailbox_action " ) != " OK " )) { // will be deleted soon ...
$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 ;
2012-08-24 13:37:56 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** 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 boolean if the email has been properly undeleted
* or false if an error occured ( $err is filled accordingly )
*/
function undelete ( $mail_id ) {
global $err , $db ;
$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
$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 '; " );
$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 ;
}
2012-08-24 13:37:56 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** 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 ;
2012-08-24 13:37:56 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** 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 ;
2012-08-24 13:37:56 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** 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 ;
2012-08-24 13:37:56 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** 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 integer $islocal boolean is it a POP / IMAP mailbox ?
* @ param integer $quotamb integer if islocal = 1 , quota in MB
* @ param string $recipients string recipients , one mail per line .
* @ return boolean if the email has been properly edited
* or false if an error occured ( $err is filled accordingly )
*/
function set_details ( $mail_id , $islocal , $quotamb , $recipients , $delivery = " dovecot " , $dontcheck = false ) {
global $err , $db ;
$delivery = mysql_real_escape_string ( $delivery );
$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
$path = " " ;
if ( $delivery == " dovecot " ) {
$path = ALTERNC_MAIL . " / " . substr ( $me [ " address " ] . " _ " , 0 , 1 ) . " / " . $me [ " address " ] . " _ " . $me [ " domain " ];
}
foreach ( $this -> forbiddenchars as $str ) {
if ( strpos ( $me [ " address " ], $str ) !== false ) {
$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 " ));
return false ;
}
}
foreach ( $this -> specialchars as $str ) {
if ( strpos ( $me [ " address " ], $str ) !== false ) {
$path = ALTERNC_MAIL . " /_/ " . $me [ " id " ] . " _ " . $me [ " domain " ];
break ;
}
}
$db -> query ( " INSERT INTO mailbox SET address_id= $mail_id , delivery=' $delivery ', path=' " . addslashes ( $path ) . " '; " );
}
if ( $me [ " islocal " ] && $islocal && $me [ " mailbox_action " ] == " DELETE " ) {
$db -> query ( " UPDATE mailbox SET mail_action='OK' WHERE mail_action='DELETE' AND address_id= " . $mail_id . " ; " );
}
if ( $islocal ) {
if ( $quotamb != 0 && $quotamb < ( intval ( $me [ " used " ] / 1024 / 1024 ) + 1 )) {
$quotamb = intval ( $me [ " used " ] / 1024 / 1024 ) + 1 ;
$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 " ));
}
$db -> query ( " UPDATE mailbox SET quota= " . intval ( $quotamb ) . " WHERE address_id= " . $mail_id . " ; " );
}
$recipients = preg_replace ( '/[\r\t\s]/' , " \n " , $recipients ); // Handle space AND new line
$r = explode ( " \n " , $recipients );
$red = " " ;
foreach ( $r as $m ) {
$m = trim ( $m );
if ( $m && ( filter_var ( $m , FILTER_VALIDATE_EMAIL ) || $dontcheck ) // Recipient Email is valid
&& $m != ( $me [ " address " ] . " @ " . $me [ " domain " ])) { // And not myself (no loop allowed easily ;) )
$red .= $m . " \n " ;
}
}
$db -> query ( " DELETE FROM recipient WHERE address_id= " . $mail_id . " ; " );
if ( isset ( $red ) && $red ) {
$db -> query ( " INSERT INTO recipient SET address_id= " . $mail_id . " , recipients=' " . addslashes ( $red ) . " '; " );
}
return true ;
2012-08-24 13:37:56 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** 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
*/
function add_wrapper ( $dom_id , $m , $delivery ) {
global $err , $mail ;
$err -> log ( " mail " , " add_wrapper " , " creating $delivery $m address " );
$mail_id = $mail -> create ( $dom_id , $m , $delivery );
$this -> set_details ( $mail_id , 1 , 0 , '' , $delivery );
// FIXME return error code
2012-08-24 13:37:56 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** 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
* @ param : $type , the type of the alias created
* @ param string $m
* @ param string $alias
* @ param string $dom_id
*/
function create_alias ( $dom_id , $m , $alias , $type = " " , $dontcheck = false ) {
global $err , $mail ;
$err -> log ( " mail " , " create_alias " , " creating $m alias for $alias type $type " );
$mail_id = $mail -> create ( $dom_id , $m , $type , $dontcheck );
if ( ! $mail_id ) {
return false ;
}
$this -> set_details ( $mail_id , 0 , 0 , $alias , " dovecot " , $dontcheck );
return true ;
2012-08-24 13:37:56 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** 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 ;
$err -> log ( " mail " , " del_wrapper " );
$this -> delete ( $mail_id );
2012-08-24 18:21:00 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** Export the mail information of an account
* @ return : str , string containing the complete configuration
* of the email for the current acccount .
*/
function alternc_export_conf () {
global $err ;
$err -> log ( " mail " , " export " );
$domain = $this -> enum_domains ();
$str = " <mail> \n " ;
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 )) {
$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 " ;
} else {
$str .= " <islocal>0</islocal> \n " ;
}
if ( ! empty ( $val [ " recipients " ])) {
$r = explode ( " \n " , $val [ " recipients " ]);
foreach ( $r as $recip ) {
$str .= " <recipients> " . $recip . " <recipients> \n " ;
}
}
$str .= " </address> \n " ;
}
}
$str .= " </domain> \n " ;
}
$str .= " </mail> \n " ;
return $str ;
2012-08-24 18:21:00 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/**
* Return the list of allowed slave accounts ( secondary - mx )
* @ return array
*/
function enum_slave_account () {
global $db ;
$db -> query ( " SELECT login,pass FROM mxaccount; " );
$res = array ();
while ( $db -> next_record ()) {
$res [] = $db -> Record ;
}
if ( ! count ( $res )) {
return false ;
}
return $res ;
2012-08-24 18:21:00 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/**
* 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 ;
$login = mysql_real_escape_string ( $login );
$pass = mysql_real_escape_string ( $pass );
$db -> query ( " SELECT * FROM mxaccount WHERE login=' $login ' AND pass=' $pass '; " );
if ( $db -> next_record ()) {
return true ;
}
return false ;
2012-08-24 18:21:00 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** Out ( echo ) the complete hosted domain list :
*/
function echo_domain_list ( $format = null ) {
global $db ;
$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 );
default :
foreach ( $lst as $l ) {
echo $l . " \n " ;
}
return true ;
} // switch
2012-08-24 18:21:00 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/**
* 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 ;
$login = mysql_real_escape_string ( $login );
$pass = mysql_real_escape_string ( $pass );
$db -> query ( " SELECT * FROM mxaccount WHERE login=' $login ' " );
if ( $db -> next_record ()) {
$err -> raise ( " mail " , _ ( " The slave MX account was not found " ));
return false ;
}
$db -> query ( " INSERT INTO mxaccount (login,pass) VALUES (' $login ',' $pass ') " );
return true ;
2012-08-24 18:21:00 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/**
* Remove a slave account
* @ param string $login the login to delete
*/
function del_slave_account ( $login ) {
global $db ;
$login = mysql_real_escape_string ( $login );
$db -> query ( " DELETE FROM mxaccount WHERE login=' $login ' " );
return true ;
2012-08-31 16:00:47 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** 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
* @ param string $domain_id Domain that has just been created
* @ param string $target_domain Master domain
* @ access private
*/
function hook_dom_add_slave_domain ( $domain_id , $target_domain ) {
global $err ;
$err -> log ( " mail " , " hook_dom_add_slave_domain " , $domain_id );
$this -> catchall_set ( $domain_id , '@' . $target_domain );
2012-08-31 16:00:47 +00:00
return true ;
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** hook function called by AlternC when a domain is created for
* the current user account
* This function create a postmaster mail which is an alias to LOGIN @ FQDN
* 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 ) {
global $err , $mem , $db ;
$err -> log ( " mail " , " hook_dom_add_mx_domain " , $domain_id );
$db -> query ( " SELECT value FROM variable where name='mailname_bounce'; " );
if ( ! $db -> next_record ()) {
$err -> raise ( " mail " , _ ( " Problem: can't create default bounce mail " ));
return false ;
}
$mailname = $db -> f ( " value " );
// set spf & dmarc for this domain
$db -> query ( " SELECT domaine FROM domaines WHERE id= $domain_id ; " );
if ( $db -> next_record ()) {
if ( $spf = variable_get ( " default_spf_value " )) {
$this -> set_dns_spf ( $db -> Record [ " domaine " ], $spf );
}
if ( $dmarc = variable_get ( " default_dmarc_value " )) {
$this -> set_dns_dmarc ( $db -> Record [ " domaine " ], $dmarc );
}
}
return $this -> create_alias ( $domain_id , 'postmaster' , $mem -> user [ 'login' ] . '@' . $mailname );
2013-04-23 12:58:59 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** hook function called by variables when a variable is changed
* @ access private
*/
function hook_variable_set ( $name , $old , $new ) {
global $err , $db ;
$err -> log ( " mail " , " hook_variable_set( $name , $old , $new ) " );
if ( $name == " default_spf_value " ) {
$new = trim ( $new );
$old = trim ( $old );
$db -> query ( " SELECT domaine,login,compte FROM domaines, membres WHERE gesdns=1 AND gesmx=1 and membres.uid=domaines.compte; " );
while ( $db -> next_record ()) {
$this -> set_dns_spf ( $db -> Record [ " domaine " ], $new , $old , $db -> Record [ " compte " ], $db -> Record [ " login " ]);
}
}
2013-04-23 12:58:59 +00:00
2015-09-25 15:42:00 +00:00
if ( $name == " default_dmarc_value " ) {
$new = trim ( $new );
$old = trim ( $old );
$db -> query ( " SELECT domaine,login,compte FROM domaines, membres WHERE gesdns=1 AND gesmx=1 and membres.uid=domaines.compte; " );
while ( $db -> next_record ()) {
$this -> set_dns_dmarc ( $db -> Record [ " domaine " ], $new , $old , $db -> Record [ " compte " ], $db -> Record [ " login " ]);
}
}
2012-08-31 16:00:47 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** Set or UPDATE the DNS record for the domain $dom ( str ) to be $spf
* account 's login is current and if not it' s $login .
* don ' t change spf if current value is not $old
* @ access private
*/
function set_dns_spf ( $domain , $spf , $previous = - 1 , $uid = - 1 , $login = - 1 ) {
global $db , $cuid , $mem ;
// defaults
if ( $uid === - 1 ) {
$uid = intval ( $cuid );
} else {
$uid = intval ( $uid );
}
if ( $login === - 1 ) {
$login = $mem -> user [ " login " ];
}
// Search for the record in sub_domaines table
$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'; " );
if ( $db -> next_record ()) {
if ( $previous !== - 1 && $db -> Record [ " valeur " ] == " v=spf1 " . $spf ) {
return ; // skip, no change asked.
}
$db -> query ( " UPDATE sub_domaines SET web_action='DELETE' WHERE id=' " . $db -> Record [ " id " ] . " '; " );
}
$db -> query ( " INSERT INTO sub_domaines SET compte= $uid , domaine=' " . addslashes ( $domain ) . " ', sub='', type='txt', valeur=' " . addslashes ( " v=spf1 " . $spf ) . " ', web_action='UPDATE'; " );
$db -> query ( " UPDATE domaines SET dns_action='UPDATE' WHERE domaine=' " . addslashes ( $domain ) . " '; " );
}
/* ----------------------------------------------------------------- */
/** Set or UPDATE the DNS record for the domain $dom ( str ) to be $dmarc
* account 's login is current and if not it' s $login .
* don ' t change dmarc if current value is not $old
* @ access private
*/
function set_dns_dmarc ( $domain , $dmarc , $previous = - 1 , $uid = - 1 , $login = - 1 ) {
global $db , $cuid , $mem , $L_FQDN ;
// defaults
if ( $uid === - 1 ) {
$uid = intval ( $cuid );
} else {
$uid = intval ( $uid );
}
if ( $login === - 1 ) {
$login = $mem -> user [ " login " ];
}
$dmarc = str_replace ( " %%ADMINMAIL%% " , " admin@ " . $L_FQDN , $dmarc );
$dmarc = str_replace ( " %%USERMAIL%% " , $login . " @ " . $L_FQDN , $dmarc );
// Search for the record in sub_domaines table
$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'; " );
if ( $db -> next_record ()) {
if ( $previous !== - 1 && $db -> Record [ " valeur " ] == " v=dmarc1; " . $dmarc ) {
return ; // skip, no change asked.
}
$db -> query ( " UPDATE sub_domaines SET web_action='DELETE' WHERE id=' " . $db -> Record [ " id " ] . " '; " );
}
$db -> query ( " INSERT INTO sub_domaines SET compte= $uid , domaine=' " . addslashes ( $domain ) . " ', sub='_dmarc', type='txt', valeur=' " . addslashes ( " v=dmarc1; " . $dmarc ) . " ', web_action='UPDATE'; " );
$db -> query ( " UPDATE domaines SET dns_action='UPDATE' WHERE domaine=' " . addslashes ( $domain ) . " '; " );
2013-03-14 08:41:43 +00:00
}
2015-09-25 15:42:00 +00:00
/* ----------------------------------------------------------------- */
/** 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 ),
);
2015-06-17 17:56:51 +00:00
}
2006-04-26 12:28:53 +00:00
2015-09-25 15:42:00 +00:00
}
2012-04-06 10:10:36 +00:00
2015-09-25 15:42:00 +00:00
/* Class m_mail */