2009-03-13 21:48:24 +01:00
< ? php
2022-06-28 14:46:11 +02:00
2018-01-26 15:45:57 +01:00
# $Id$
2009-03-13 21:48:24 +01:00
/**
* Simple class to represent a user .
*/
2021-03-22 10:28:28 +01:00
class MailboxHandler extends PFAHandler
{
2013-06-23 17:04:05 +02:00
protected $db_table = 'mailbox' ;
protected $id_field = 'username' ;
2012-04-09 17:02:16 +02:00
protected $domain_field = 'domain' ;
2015-04-05 22:23:38 +02:00
protected $searchfields = array ( 'username' );
2012-04-09 17:02:16 +02:00
# init $this->struct, $this->db_table and $this->id_field
2021-04-13 22:19:16 +02:00
protected function initStruct ()
{
2021-02-18 22:19:56 +01:00
$passwordReset = ( int ) ( Config :: bool ( 'forgotten_user_password_reset' ) && ! Config :: read ( 'mailbox_postpassword_script' ) );
2024-01-07 15:25:17 +01:00
$smtpActiveFlag = ( int ) ( Config :: bool ( 'smtp_active_flag' ) );
2018-04-27 23:23:57 +02:00
$reset_by_sms = 0 ;
2018-12-27 22:43:11 +01:00
if ( $passwordReset && Config :: read_string ( 'sms_send_function' )) {
2018-04-27 23:23:57 +02:00
$reset_by_sms = 1 ;
2018-04-22 22:52:13 +02:00
}
2021-01-11 20:25:30 +01:00
$editPw = 1 ;
if ( ! $this -> new && Config :: read ( 'mailbox_postpassword_script' )) {
$editPw = 0 ;
}
2018-04-22 22:52:13 +02:00
2019-08-13 09:53:43 +02:00
$this -> struct = array (
2020-09-12 22:04:18 +02:00
2012-04-09 17:02:16 +02:00
# field name allow display in... type $PALANG label $PALANG description default / options / ...
# editing? form list
2018-04-22 18:24:41 +02:00
'username' => pacol ( $this -> new , 1 , 1 , 'mail' , 'pEdit_mailbox_username' , '' , '' ),
2023-09-26 14:04:25 +02:00
'local_part' => pacol ( $this -> new , 0 , 0 , 'text' , 'pEdit_mailbox_username' , '' , '' ,
2023-09-26 15:27:24 +02:00
/*options*/ array ( 'legal_chars' => Config :: read ( 'username_legal_chars' ), 'legal_char_warning' => Config :: lang ( 'pLegal_char_warning' ))
2023-09-26 14:04:25 +02:00
),
2018-04-22 18:24:41 +02:00
'domain' => pacol ( $this -> new , 0 , 1 , 'enum' , '' , '' , '' ,
/*options*/ $this -> allowed_domains ),
2012-12-29 00:48:33 +01:00
# TODO: maildir: display in list is needed to include maildir in SQL result (for post_edit hook)
# TODO: (not a perfect solution, but works for now - maybe we need a separate "include in SELECT query" field?)
2018-04-22 18:24:41 +02:00
'maildir' => pacol ( $this -> new , 0 , 1 , 'text' , '' , '' , '' ),
2021-01-11 20:25:30 +01:00
'password' => pacol ( $editPw , $editPw , 0 , 'pass' , 'password' , 'pCreate_mailbox_password_text' , '' ),
'password2' => pacol ( $editPw , $editPw , 0 , 'pass' , 'password_again' , '' , '' ,
2018-12-27 22:43:11 +01:00
/*options*/ array (),
2012-04-09 17:02:16 +02:00
/*not_in_db*/ 0 ,
/*dont_write_to_db*/ 1 ,
/*select*/ 'password as password2'
),
2018-04-22 18:24:41 +02:00
'name' => pacol ( 1 , 1 , 1 , 'text' , 'name' , 'pCreate_mailbox_name_text' , '' ),
'quota' => pacol ( 1 , 1 , 1 , 'int' , 'pEdit_mailbox_quota' , 'pEdit_mailbox_quota_text' , '' ), # in MB
2012-12-29 00:48:33 +01:00
# read_from_db_postprocess() also sets 'quotabytes' for use in init()
2013-11-12 22:37:08 +01:00
# TODO: read used quota from quota/quota2 table
2018-04-22 18:24:41 +02:00
'active' => pacol ( 1 , 1 , 1 , 'bool' , 'active' , '' , 1 ),
2024-04-07 20:37:41 +02:00
'smtp_active' => pacol ( $smtpActiveFlag , $smtpActiveFlag , 0 , 'int' , 'smtp_active' , '' , 1 ),
2018-04-22 18:24:41 +02:00
'welcome_mail' => pacol ( $this -> new , $this -> new , 0 , 'bool' , 'pCreate_mailbox_mail' , '' , 1 ,
2018-12-27 22:43:11 +01:00
/*options*/ array (),
2018-04-22 18:24:41 +02:00
/*not_in_db*/ 1 ),
2018-04-22 22:52:13 +02:00
'phone' => pacol ( 1 , $reset_by_sms , 0 , 'text' , 'pCreate_mailbox_phone' , 'pCreate_mailbox_phone_desc' , '' ),
2018-04-22 18:24:41 +02:00
'email_other' => pacol ( 1 , $passwordReset , 0 , 'mail' , 'pCreate_mailbox_email' , 'pCreate_mailbox_email_desc' , '' ),
'token' => pacol ( 1 , 0 , 0 , 'text' , '' , '' ),
'token_validity' => pacol ( 1 , 0 , 0 , 'ts' , '' , '' , date ( " Y-m-d H:i:s " , time ())),
'created' => pacol ( 0 , 0 , 1 , 'ts' , 'created' , '' ),
'modified' => pacol ( 0 , 0 , 1 , 'ts' , 'last_modified' , '' ),
2018-08-28 22:19:56 +02:00
'password_expiry' => pacol ( 0 , 0 , 1 , 'ts' , 'password_expiration' , '' ),
2012-04-09 17:02:16 +02:00
# TODO: add virtual 'notified' column and allow to display who received a vacation response?
);
2013-02-10 00:52:03 +01:00
# update allowed quota
2018-01-26 15:45:57 +01:00
if ( count ( $this -> struct [ 'domain' ][ 'options' ]) > 0 ) {
$this -> prefill ( 'domain' , $this -> struct [ 'domain' ][ 'options' ][ 0 ]);
}
2012-04-09 17:02:16 +02:00
}
2022-06-28 14:46:11 +02:00
public function init ( string $id ) : bool
2021-04-13 22:19:16 +02:00
{
2013-02-10 00:52:03 +01:00
if ( ! parent :: init ( $id )) {
return false ;
}
2012-12-29 00:48:33 +01:00
if ( $this -> new ) {
2013-02-10 00:52:03 +01:00
$currentquota = 0 ;
2012-12-29 00:48:33 +01:00
} else {
2013-10-13 17:26:37 +02:00
$currentquota = $this -> result [ 'quotabytes' ]; # parent::init called ->view()
2012-12-29 00:48:33 +01:00
}
2014-09-12 11:22:09 +02:00
$this -> updateMaxquota ( $this -> domain , $currentquota );
2013-02-10 00:52:03 +01:00
return true ; # still here? good.
}
2021-04-13 22:19:16 +02:00
protected function domain_from_id ()
{
2018-01-26 15:45:57 +01:00
list ( /*NULL*/ , $domain ) = explode ( '@' , $this -> id );
2014-09-12 11:22:09 +02:00
return $domain ;
}
2013-02-10 00:52:03 +01:00
/**
* show max allowed quota in quota field description
* @ param string - domain
* @ param int - current quota
*/
2021-04-13 22:19:16 +02:00
protected function updateMaxquota ( $domain , $currentquota )
{
2018-01-26 15:45:57 +01:00
if ( $domain == '' ) {
return false ;
}
2013-02-10 00:52:03 +01:00
2013-08-07 22:05:18 +02:00
$maxquota = $this -> allowed_quota ( $domain , $currentquota );
2013-02-10 00:52:03 +01:00
if ( $maxquota == 0 ) {
2022-02-13 19:01:36 +01:00
$this -> struct [ 'quota' ][ 'desc' ] = Config :: lang ( 'mb_max_unlimited' );
} elseif ( $maxquota < 0 ) {
$this -> struct [ 'quota' ][ 'desc' ] = Config :: lang ( 'mb_max_disabled' );
2013-02-10 00:52:03 +01:00
} else {
2018-12-27 22:43:11 +01:00
$this -> struct [ 'quota' ][ 'desc' ] = Config :: lang_f ( 'mb_max' , " " . $maxquota );
2013-02-10 00:52:03 +01:00
}
2012-12-29 00:48:33 +01:00
}
2021-04-13 22:19:16 +02:00
protected function initMsg ()
{
2013-07-29 00:51:28 +02:00
$this -> msg [ 'error_already_exists' ] = 'email_address_already_exists' ;
2012-04-09 17:02:16 +02:00
$this -> msg [ 'error_does_not_exist' ] = 'pCreate_mailbox_username_text_error1' ;
2015-03-17 22:07:59 +01:00
$this -> msg [ 'confirm_delete' ] = 'confirm_delete_mailbox' ;
2012-04-09 17:02:16 +02:00
if ( $this -> new ) {
$this -> msg [ 'logname' ] = 'create_mailbox' ;
$this -> msg [ 'store_error' ] = 'pCreate_mailbox_result_error' ;
2013-02-16 21:42:17 +01:00
$this -> msg [ 'successmessage' ] = 'pCreate_mailbox_result_success' ;
2012-04-09 17:02:16 +02:00
} else {
$this -> msg [ 'logname' ] = 'edit_mailbox' ;
2013-11-16 20:49:40 +01:00
$this -> msg [ 'store_error' ] = 'mailbox_update_failed' ;
$this -> msg [ 'successmessage' ] = 'mailbox_updated' ;
2012-04-09 17:02:16 +02:00
}
}
2021-04-13 22:19:16 +02:00
public function webformConfig ()
{
2018-01-26 15:45:57 +01:00
if ( $this -> new ) { # the webform will display a local_part field + domain dropdown on $new
2012-04-10 00:43:30 +02:00
$this -> struct [ 'username' ][ 'display_in_form' ] = 0 ;
$this -> struct [ 'local_part' ][ 'display_in_form' ] = 1 ;
$this -> struct [ 'domain' ][ 'display_in_form' ] = 1 ;
}
2018-01-26 15:45:57 +01:00
return array (
2012-04-09 17:02:16 +02:00
# $PALANG labels
'formtitle_create' => 'pCreate_mailbox_welcome' ,
'formtitle_edit' => 'pEdit_mailbox_welcome' ,
2013-07-29 00:51:28 +02:00
'create_button' => 'add_mailbox' ,
2012-04-09 17:02:16 +02:00
# various settings
'required_role' => 'admin' ,
2013-01-27 20:52:19 +01:00
'listview' => 'list-virtual.php' ,
'early_init' => 0 ,
'prefill' => array ( 'domain' ),
2012-04-09 17:02:16 +02:00
);
}
2021-04-13 22:19:16 +02:00
protected function validate_new_id ()
{
2012-04-09 17:02:16 +02:00
if ( $this -> id == '' ) {
2013-10-13 20:11:18 +02:00
$this -> errormsg [ $this -> id_field ] = Config :: lang ( 'pCreate_mailbox_username_text_error1' );
2012-04-09 17:02:16 +02:00
return false ;
}
2013-04-01 23:22:30 +02:00
$email_check = check_email ( $this -> id );
2018-01-26 15:45:57 +01:00
if ( $email_check != '' ) {
2013-04-01 23:22:30 +02:00
$this -> errormsg [ $this -> id_field ] = $email_check ;
2013-02-05 00:18:01 +01:00
return false ;
}
2018-01-26 15:45:57 +01:00
list ( /*NULL*/ , $domain ) = explode ( '@' , $this -> id );
2012-04-09 17:02:16 +02:00
2018-01-26 15:45:57 +01:00
if ( ! $this -> create_allowed ( $domain )) {
2013-10-13 20:11:18 +02:00
$this -> errormsg [] = Config :: lang ( 'pCreate_mailbox_username_text_error3' );
2012-04-09 17:02:16 +02:00
return false ;
}
# check if an alias with this name already exists - if yes, don't allow to create the mailbox
$handler = new AliasHandler ( 1 );
2014-04-28 22:42:38 +02:00
$handler -> calledBy ( 'MailboxHandler' ); # make sure mailbox creation still works if the alias limit for the domain is hit
2012-04-09 17:02:16 +02:00
if ( ! $handler -> init ( $this -> id )) {
2014-04-28 22:42:38 +02:00
# TODO: keep original error message from AliasHandler
2013-10-13 20:11:18 +02:00
$this -> errormsg [] = Config :: lang ( 'email_address_already_exists' );
2012-04-09 17:02:16 +02:00
return false ;
}
2013-02-05 00:18:01 +01:00
return true ; # still here? good!
2012-04-09 17:02:16 +02:00
}
/**
* check number of existing mailboxes for this domain - is one more allowed ?
*/
2021-04-13 22:19:16 +02:00
private function create_allowed ( $domain )
{
2018-01-26 15:45:57 +01:00
$limit = get_domain_properties ( $domain );
2009-03-16 20:49:36 +01:00
2018-01-26 15:45:57 +01:00
if ( $limit [ 'mailboxes' ] == 0 ) {
return true ;
} # unlimited
if ( $limit [ 'mailboxes' ] < 0 ) {
return false ;
} # disabled
if ( $limit [ 'mailbox_count' ] >= $limit [ 'mailboxes' ]) {
return false ;
}
2012-04-09 17:02:16 +02:00
return true ;
}
2011-10-11 00:15:16 +02:00
2018-01-26 15:45:57 +01:00
/**
* merge local_part and domain to address
* called by edit . php ( if id_field is editable and hidden in editform ) _before_ -> init
*/
2021-04-13 22:19:16 +02:00
public function mergeId ( $values )
{
2012-04-10 00:43:30 +02:00
if ( $this -> struct [ 'local_part' ][ 'display_in_form' ] == 1 && $this -> struct [ 'domain' ][ 'display_in_form' ]) { # webform mode - combine to 'address' field
return $values [ 'local_part' ] . '@' . $values [ 'domain' ];
} else {
return $values [ $this -> id_field ];
}
}
2009-03-16 20:49:36 +01:00
2021-04-13 22:19:16 +02:00
protected function read_from_db_postprocess ( $db_result )
{
2012-12-29 00:48:33 +01:00
foreach ( $db_result as $key => $row ) {
2020-03-18 15:36:53 +01:00
if ( isset ( $row [ 'quota' ]) && is_numeric ( $row [ 'quota' ]) && $row [ 'quota' ] > - 1 ) { # quota could be disabled in $struct
2014-03-16 20:29:55 +01:00
$db_result [ $key ][ 'quotabytes' ] = $row [ 'quota' ];
2020-03-18 22:03:27 +01:00
$db_result [ $key ][ 'quota' ] = divide_quota ( ( int ) $row [ 'quota' ]); # convert quota to MB
2014-03-16 20:29:55 +01:00
} else {
$db_result [ $key ][ 'quotabytes' ] = - 1 ;
$db_result [ $key ][ 'quota' ] = - 1 ;
}
2012-12-29 00:48:33 +01:00
}
return $db_result ;
}
2022-06-28 14:46:11 +02:00
protected function preSave () : bool
2021-04-13 22:19:16 +02:00
{
2024-04-22 22:00:12 +02:00
if ( isset ( $this -> values [ 'quota' ]) && $this -> values [ 'quota' ] != - 1 && is_numeric ( $this -> values [ 'quota' ])) {
$multiplier = Config :: read_string ( 'quota_multiplier' );
if ( $multiplier == 0 || ! is_numeric ( $multiplier )) { // or empty string, or null, or false...
$multiplier = 1 ;
2019-02-27 14:39:20 +01:00
}
2024-04-22 22:00:12 +02:00
$this -> values [ 'quota' ] = $this -> values [ 'quota' ] * $multiplier ; # convert quota from MB to bytes
2020-05-15 21:30:31 +02:00
2024-04-22 22:00:12 +02:00
}
// Avoid trying to store '' in an integer field
if ( isset ( $this -> values [ 'quota' ]) && $this -> values [ 'quota' ] === '' ) {
$this -> values [ 'quota' ] = 0 ;
}
2013-04-01 21:57:25 +02:00
$ah = new AliasHandler ( $this -> new , $this -> admin_username );
2013-02-05 00:18:01 +01:00
2013-04-01 21:57:25 +02:00
$ah -> calledBy ( 'MailboxHandler' );
2013-02-05 00:18:01 +01:00
2018-01-26 15:45:57 +01:00
if ( ! $ah -> init ( $this -> id )) {
2013-05-01 21:05:32 +02:00
$arraykeys = array_keys ( $ah -> errormsg );
$this -> errormsg [] = $ah -> errormsg [ $arraykeys [ 0 ]]; # TODO: implement this as PFAHandler->firstErrormsg()
2013-04-01 21:57:25 +02:00
return false ;
}
2013-02-05 00:18:01 +01:00
2013-04-01 21:57:25 +02:00
$alias_data = array ();
2013-02-05 00:18:01 +01:00
2013-04-01 21:57:25 +02:00
if ( isset ( $this -> values [ 'active' ])) { # might not be set in edit mode
$alias_data [ 'active' ] = $this -> values [ 'active' ];
}
2013-02-05 00:18:01 +01:00
2013-04-01 21:57:25 +02:00
if ( $this -> new ) {
2013-06-11 00:18:53 +02:00
$alias_data [ 'goto' ] = array ( $this -> id ); # 'goto_mailbox' = 1; # would be technically correct, but setting 'goto' is easier
2013-04-01 21:57:25 +02:00
}
if ( ! $ah -> set ( $alias_data )) {
$this -> errormsg [] = $ah -> errormsg [ 0 ];
return false ;
}
2020-09-25 22:29:45 +02:00
if ( ! $ah -> save ()) {
2013-04-01 21:57:25 +02:00
$this -> errormsg [] = $ah -> errormsg [ 0 ];
return false ;
2013-02-05 00:18:01 +01:00
}
2012-12-29 00:48:33 +01:00
2019-06-11 22:05:53 +02:00
if ( ! empty ( $this -> values [ 'password' ])) {
// provide some default value to keep MySQL etc happy.
2019-06-08 21:34:07 +02:00
$this -> values [ 'password_expiry' ] = date ( 'Y-m-d H:i' , strtotime ( " +365 days " ));
if ( Config :: bool ( 'password_expiration' )) {
2019-02-27 15:36:45 +01:00
$domain_dirty = $this -> domain_from_id ();
$domain = trim ( $domain_dirty , " `' " ); // naive assumption it is ' escaping.
$password_expiration_value = ( int ) get_password_expiration_value ( $domain );
$this -> values [ 'password_expiry' ] = date ( 'Y-m-d H:i' , strtotime ( " + $password_expiration_value day " ));
}
}
2018-09-07 12:01:50 +02:00
return true ;
2013-02-05 00:18:01 +01:00
}
2018-04-22 18:24:41 +02:00
2021-04-13 22:19:16 +02:00
protected function setmore ( array $values )
{
2020-03-16 15:10:05 +01:00
if ( array_key_exists ( 'quota' , $this -> values )) {
2020-03-16 14:11:51 +01:00
$this -> values [ 'quota' ] = ( int ) $this -> values [ 'quota' ];
}
}
2019-06-08 21:34:07 +02:00
// Could perhaps also use _validate_local_part($new_value) { .... }
2021-04-13 22:19:16 +02:00
public function set ( array $values )
{
2019-06-08 21:05:33 +02:00
// See: https://github.com/postfixadmin/postfixadmin/issues/282 - ensure the 'local_part' does not contain an @ sign.
$ok = true ;
2019-06-08 21:38:54 +02:00
if ( isset ( $values [ 'local_part' ]) && strpos ( $values [ 'local_part' ], '@' )) {
2019-06-08 21:05:33 +02:00
$this -> errormsg [ 'local_part' ] = Config :: lang ( 'pCreate_mailbox_local_part_error' );
$ok = false ;
}
return $ok && parent :: set ( $values );
}
2022-06-28 14:46:11 +02:00
protected function postSave () : bool
2021-04-13 22:19:16 +02:00
{
2013-08-07 22:39:37 +02:00
if ( $this -> new ) {
2018-01-26 15:45:57 +01:00
if ( ! $this -> mailbox_post_script ()) {
2013-02-05 00:18:01 +01:00
# return false; # TODO: should this be fatal?
}
if ( $this -> values [ 'welcome_mail' ] == true ) {
2018-01-26 15:45:57 +01:00
if ( ! $this -> send_welcome_mail ()) {
2013-02-05 00:18:01 +01:00
# return false; # TODO: should this be fatal?
}
}
2018-01-26 15:45:57 +01:00
if ( ! $this -> create_mailbox_subfolders ()) {
2014-10-06 00:09:42 +02:00
$this -> infomsg [] = Config :: lang_f ( 'pCreate_mailbox_result_succes_nosubfolders' , $this -> id );
2018-01-26 15:45:57 +01:00
}
2013-02-05 00:18:01 +01:00
} else { # edit mode
2013-06-23 22:23:02 +02:00
# alias active status is updated in before_store()
2013-01-27 20:52:19 +01:00
2012-12-29 00:48:33 +01:00
# postedit hook
2013-02-05 00:18:01 +01:00
# TODO: implement a poststore() function? - would make handling of old and new values much easier...
2012-12-29 00:48:33 +01:00
$old_mh = new MailboxHandler ();
if ( ! $old_mh -> init ( $this -> id )) {
$this -> errormsg [] = $old_mh -> errormsg [ 0 ];
} elseif ( ! $old_mh -> view ()) {
$this -> errormsg [] = $old_mh -> errormsg [ 0 ];
} else {
$oldvalues = $old_mh -> result ();
2015-01-24 15:00:35 +01:00
$this -> values [ 'maildir' ] = $oldvalues [ 'maildir' ];
2012-12-29 00:48:33 +01:00
if ( isset ( $this -> values [ 'quota' ])) {
$quota = $this -> values [ 'quota' ];
} else {
$quota = $oldvalues [ 'quota' ];
}
2018-01-26 15:45:57 +01:00
if ( ! $this -> mailbox_post_script ()) {
2013-08-07 22:39:37 +02:00
# TODO: should this be fatal?
2012-12-29 00:48:33 +01:00
}
}
}
return true ; # even if a hook failed, mark the overall operation as OK
}
2021-04-13 22:19:16 +02:00
public function delete ()
{
2018-01-26 15:45:57 +01:00
if ( ! $this -> view ()) {
2013-11-10 20:38:43 +01:00
$this -> errormsg [] = Config :: Lang ( 'pFetchmail_invalid_mailbox' ); # TODO: can users hit this message at all? init() should already fail...
return false ;
}
# the correct way would be to delete the alias and fetchmail entries with *Handler before
# deleting the mailbox, but it's easier and a bit faster to do it on the database level.
# cleaning up all tables doesn't hurt, even if vacation or displaying the quota is disabled
2018-04-22 18:24:41 +02:00
db_delete ( 'fetchmail' , 'mailbox' , $this -> id );
db_delete ( 'vacation' , 'email' , $this -> id );
db_delete ( 'vacation_notification' , 'on_vacation' , $this -> id ); # should be caught by cascade, if PgSQL
db_delete ( 'quota' , 'username' , $this -> id );
db_delete ( 'quota2' , 'username' , $this -> id );
db_delete ( 'alias' , 'address' , $this -> id );
db_delete ( $this -> db_table , $this -> id_field , $this -> id ); # finally delete the mailbox
2013-11-10 20:38:43 +01:00
2018-01-26 15:45:57 +01:00
if ( ! $this -> mailbox_postdeletion ()) {
2018-12-27 22:43:11 +01:00
$this -> errormsg [] = Config :: Lang ( 'mailbox_postdel_failed' );
2013-11-10 20:38:43 +01:00
}
2018-01-26 15:45:57 +01:00
list ( /*NULL*/ , $domain ) = explode ( '@' , $this -> id );
db_log ( $domain , 'delete_mailbox' , $this -> id );
2013-11-10 20:38:43 +01:00
$this -> infomsg [] = Config :: Lang_f ( 'pDelete_delete_success' , $this -> id );
return true ;
2010-12-17 23:53:46 +01:00
}
2013-11-10 20:38:43 +01:00
2012-04-09 17:02:16 +02:00
2021-04-13 22:19:16 +02:00
protected function _prefill_domain ( $field , $val )
{
2013-02-10 00:52:03 +01:00
if ( in_array ( $val , $this -> struct [ $field ][ 'options' ])) {
$this -> struct [ $field ][ 'default' ] = $val ;
$this -> updateMaxquota ( $val , 0 );
}
}
2012-12-29 00:48:33 +01:00
/**
* check if quota is allowed
*/
2021-04-13 22:19:16 +02:00
protected function _validate_quota ( $field , $val )
{
2018-01-26 15:45:57 +01:00
if ( ! $this -> check_quota ( $val )) {
2013-10-13 20:11:18 +02:00
$this -> errormsg [ $field ] = Config :: lang ( 'pEdit_mailbox_quota_text_error' );
2012-12-29 00:48:33 +01:00
return false ;
}
2013-02-05 00:18:01 +01:00
return true ;
2012-12-29 00:48:33 +01:00
}
2013-02-19 23:48:02 +01:00
/**
* - compare password / password2 field ( error message will be displayed at password2 field )
* - autogenerate password if enabled in config and $new
* - display password on $new if enabled in config or autogenerated
*/
2021-04-13 22:19:16 +02:00
protected function _validate_password ( $field , $val )
{
2018-01-26 15:45:57 +01:00
if ( ! $this -> _validate_password2 ( $field , $val )) {
return false ;
}
2013-02-19 23:48:02 +01:00
if ( $this -> new && Config :: read ( 'generate_password' ) == 'YES' && $val == '' ) {
# auto-generate new password
2018-01-26 15:45:57 +01:00
unset ( $this -> errormsg [ $field ]); # remove "password too short" error message
2013-02-19 23:48:02 +01:00
$val = generate_password ();
$this -> values [ $field ] = $val ; # we are doing this "behind the back" of set()
2013-11-16 20:49:40 +01:00
$this -> infomsg [] = Config :: Lang ( 'password' ) . " : $val " ;
2013-02-19 23:48:02 +01:00
return false ; # to avoid that set() overwrites $this->values[$field]
} elseif ( $this -> new && Config :: read ( 'show_password' ) == 'YES' ) {
2013-11-16 20:49:40 +01:00
$this -> infomsg [] = Config :: Lang ( 'password' ) . " : $val " ;
2013-02-19 23:48:02 +01:00
}
2013-02-26 01:07:34 +01:00
return true ; # still here? good.
2013-02-19 23:48:02 +01:00
}
2012-04-10 00:43:30 +02:00
/**
* compare password / password2 field
* error message will be displayed at the password2 field
*/
2021-04-13 22:19:16 +02:00
protected function _validate_password2 ( $field , $val )
{
2012-04-10 00:43:30 +02:00
return $this -> compare_password_fields ( 'password' , 'password2' );
}
2018-01-26 15:45:57 +01:00
/**
* on $this -> new , set localpart based on address
*/
2021-04-13 22:19:16 +02:00
protected function _missing_local_part ( $field )
{
2018-01-26 15:45:57 +01:00
list ( $local_part , $domain ) = explode ( '@' , $this -> id );
$this -> RAWvalues [ 'local_part' ] = $local_part ;
}
2013-02-05 00:18:01 +01:00
2018-01-26 15:45:57 +01:00
/**
* on $this -> new , set domain based on address
*/
2021-04-13 22:19:16 +02:00
protected function _missing_domain ( $field )
{
2018-01-26 15:45:57 +01:00
list ( $local_part , $domain ) = explode ( '@' , $this -> id );
$this -> RAWvalues [ 'domain' ] = $domain ;
}
2013-02-05 00:18:01 +01:00
2013-11-12 22:37:08 +01:00
# TODO: read used quota from quota/quota2 table, then enable _formatted_quota()
# public function _formatted_quota ($item) { return $item['used_quota'] . ' / ' . $item['quota'] ; }
2013-02-05 00:18:01 +01:00
/**
* calculate maildir path for the mailbox
*/
2021-04-13 22:19:16 +02:00
protected function _missing_maildir ( $field )
{
2018-01-26 15:45:57 +01:00
list ( $local_part , $domain ) = explode ( '@' , $this -> id );
2013-02-05 00:18:01 +01:00
2020-09-28 21:33:54 +02:00
$maildir_name_hook = Config :: read ( 'maildir_name_hook' );
2013-10-13 22:07:34 +02:00
2018-12-27 22:43:11 +01:00
if ( is_string ( $maildir_name_hook ) && $maildir_name_hook != 'NO' && function_exists ( $maildir_name_hook )) {
2018-01-26 15:45:57 +01:00
$maildir = $maildir_name_hook ( $domain , $this -> id );
2013-10-13 22:07:34 +02:00
} elseif ( Config :: bool ( 'domain_path' )) {
if ( Config :: bool ( 'domain_in_mailbox' )) {
2013-02-05 00:18:01 +01:00
$maildir = $domain . " / " . $this -> id . " / " ;
} else {
$maildir = $domain . " / " . $local_part . " / " ;
}
} else {
# If $CONF['domain_path'] is set to NO, $CONF['domain_in_mailbox] is forced to YES.
# Otherwise user@example.com and user@foo.bar would be mixed up in the same maildir "user/".
$maildir = $this -> id . " / " ;
}
$this -> RAWvalues [ 'maildir' ] = $maildir ;
}
2021-04-13 22:19:16 +02:00
private function send_welcome_mail ()
{
2013-02-05 00:18:01 +01:00
$fTo = $this -> id ;
$fFrom = smtp_get_admin_email ();
2018-01-26 15:45:57 +01:00
if ( empty ( $fFrom ) || $fFrom == 'CLI' ) {
$fFrom = $this -> id ;
}
2013-10-13 20:11:18 +02:00
$fSubject = Config :: lang ( 'pSendmail_subject_text' );
2013-02-05 00:18:01 +01:00
$fBody = Config :: read ( 'welcome_text' );
2019-05-31 11:42:42 +02:00
if ( ! smtp_mail ( $fTo , $fFrom , $fSubject , smtp_get_admin_password (), $fBody )) {
2015-04-07 00:44:51 +02:00
$this -> errormsg [] = Config :: lang_f ( 'pSendmail_result_error' , $this -> id );
2013-02-05 00:18:01 +01:00
return false ;
}
return true ;
}
2012-04-09 17:02:16 +02:00
2013-08-07 22:05:18 +02:00
/**
* Check if the user is creating a mailbox within the quota limits of the domain
*
2018-06-18 22:34:10 +02:00
* @ param int $quota - quota wanted for the mailbox
* @ return boolean - true if requested quota is OK , otherwise false
2018-12-28 20:27:33 +01:00
* @ todo merge with allowed_quota ?
2013-08-07 22:05:18 +02:00
*/
2021-04-13 22:19:16 +02:00
protected function check_quota ( $quota )
{
2018-01-26 15:45:57 +01:00
if ( ! Config :: bool ( 'quota' )) {
2013-08-07 22:05:18 +02:00
return true ; # enforcing quotas is disabled - just allow it
}
2021-01-18 21:46:11 +01:00
$quota = ( int ) $quota ;
2018-01-26 15:45:57 +01:00
list ( /*NULL*/ , $domain ) = explode ( '@' , $this -> id );
$limit = get_domain_properties ( $domain );
2013-08-07 22:05:18 +02:00
if (( $limit [ 'maxquota' ] < 0 ) and ( $quota < 0 )) {
return true ; # maxquota and $quota are both disabled -> OK, no need for more checks
}
if (( $limit [ 'maxquota' ] > 0 ) and ( $quota == 0 )) {
return false ; # mailbox with unlimited quota on a domain with maxquota restriction -> not allowed, no more checks needed
}
if ( $limit [ 'maxquota' ] != 0 && $quota > $limit [ 'maxquota' ]) {
return false ; # mailbox bigger than maxquota restriction (and maxquota != unlimited) -> not allowed, no more checks needed
}
# TODO: detailed error message ("domain quota exceeded", "mailbox quota too big" etc.) via flash_error? Or "available quota: xxx MB"?
2018-01-26 15:45:57 +01:00
if ( ! Config :: bool ( 'domain_quota' )) {
2013-08-07 22:05:18 +02:00
return true ; # enforcing domain_quota is disabled - just allow it
} elseif ( $limit [ 'quota' ] <= 0 ) { # TODO: CHECK - 0 (unlimited) is fine, not sure about <= -1 (disabled)...
$rval = true ;
2015-01-12 18:12:07 +01:00
} elseif ( $quota == 0 ) { # trying to create an unlimited mailbox, but domain quota is set
return false ;
2013-08-07 22:05:18 +02:00
} else {
$table_mailbox = table_by_key ( 'mailbox' );
2019-01-06 21:25:24 +01:00
$query = " SELECT SUM(quota) as sum FROM $table_mailbox WHERE domain = ? AND username != ? " ;
2019-01-06 22:27:08 +01:00
$rows = db_query_all ( $query , array ( $domain , $this -> id ));
2019-01-06 21:25:24 +01:00
$cur_quota_total = divide_quota ( $rows [ 0 ][ 'sum' ]); # convert to MB
2018-01-26 15:45:57 +01:00
if (( $quota + $cur_quota_total ) > $limit [ 'quota' ]) {
2013-08-07 22:05:18 +02:00
$rval = false ;
} else {
$rval = true ;
}
}
return $rval ;
2013-08-07 22:58:02 +02:00
}
2013-08-07 22:05:18 +02:00
/**
* Get allowed maximum quota for a mailbox
*
2018-06-18 22:34:10 +02:00
* @ param string $domain
* @ param int $current_user_quota ( in bytes )
* @ return int allowed maximum quota ( in MB )
2013-08-07 22:05:18 +02:00
*/
2021-04-13 22:19:16 +02:00
protected function allowed_quota ( $domain , $current_user_quota )
{
2018-01-26 15:45:57 +01:00
if ( ! Config :: bool ( 'quota' )) {
return 0 ; # quota disabled means no limits - no need for more checks
}
2013-08-07 22:05:18 +02:00
2018-01-26 15:45:57 +01:00
$domain_properties = get_domain_properties ( $domain );
2013-08-07 22:05:18 +02:00
2018-01-26 15:45:57 +01:00
$tMaxquota = $domain_properties [ 'maxquota' ];
2013-08-07 22:05:18 +02:00
2018-01-26 15:45:57 +01:00
if ( Config :: bool ( 'domain_quota' ) && $domain_properties [ 'quota' ]) {
$dquota = $domain_properties [ 'quota' ] - $domain_properties [ 'total_quota' ] + divide_quota ( $current_user_quota );
if ( $dquota < $tMaxquota ) {
$tMaxquota = $dquota ;
}
2013-08-07 22:05:18 +02:00
2018-01-26 15:45:57 +01:00
if ( $tMaxquota == 0 ) {
$tMaxquota = $dquota ;
}
}
return $tMaxquota ;
2013-08-07 22:05:18 +02:00
}
/**
2013-08-07 22:39:37 +02:00
* Called after a mailbox has been created or edited in the DBMS .
2013-08-07 22:05:18 +02:00
*
2018-06-18 22:34:10 +02:00
* @ return boolean success / failure status
2013-08-07 22:05:18 +02:00
*/
2021-04-13 22:19:16 +02:00
protected function mailbox_post_script ()
{
2013-08-07 22:39:37 +02:00
if ( $this -> new ) {
2019-10-19 21:51:05 +02:00
$cmd = Config :: read_string ( 'mailbox_postcreation_script' );
2013-11-16 20:49:40 +01:00
$warnmsg = Config :: Lang ( 'mailbox_postcreate_failed' );
2013-08-07 22:39:37 +02:00
} else {
2019-10-19 21:51:05 +02:00
$cmd = Config :: read_string ( 'mailbox_postedit_script' );
2013-11-16 20:49:40 +01:00
$warnmsg = Config :: Lang ( 'mailbox_postedit_failed' );
2013-08-07 22:05:18 +02:00
}
2021-01-11 20:25:30 +01:00
if ( $this -> new ) {
$cmd_pw = Config :: read ( 'mailbox_postpassword_script' );
$warnmsg_pw = Config :: Lang ( 'mailbox_postpassword_failed' );
}
if ( empty ( $cmd ) && empty ( $cmd_pw )) {
2018-01-26 15:45:57 +01:00
return true ;
} # nothing to do
2013-08-07 22:05:18 +02:00
2018-01-26 15:45:57 +01:00
list ( /*NULL*/ , $domain ) = explode ( '@' , $this -> id );
2013-08-07 22:39:37 +02:00
$quota = $this -> values [ 'quota' ];
2018-01-26 15:45:57 +01:00
if ( empty ( $this -> id ) || empty ( $domain ) || empty ( $this -> values [ 'maildir' ])) {
trigger_error ( 'In ' . __FUNCTION__ . ': empty username, domain and/or maildir parameter' , E_USER_ERROR );
return false ;
2013-08-07 22:05:18 +02:00
}
2013-08-07 22:39:37 +02:00
$cmdarg1 = escapeshellarg ( $this -> id );
2013-08-07 22:05:18 +02:00
$cmdarg2 = escapeshellarg ( $domain );
2021-01-11 20:25:30 +01:00
$status = true ;
if ( ! empty ( $cmd )) {
$cmdarg3 = escapeshellarg ( $this -> values [ 'maildir' ]);
if ( $quota <= 0 ) {
$quota = 0 ;
} # TODO: check if this is correct behaviour
$cmdarg4 = escapeshellarg ( " " . $quota );
$command = " $cmd $cmdarg1 $cmdarg2 $cmdarg3 $cmdarg4 " ;
$retval = 0 ;
$output = array ();
$firstline = '' ;
$firstline = exec ( $command , $output , $retval );
if ( 0 != $retval ) {
error_log ( " Running $command yielded return value= $retval , first line of output= $firstline " );
$this -> errormsg [] .= $warnmsg ;
$status = false ;
}
2013-08-07 22:05:18 +02:00
}
2021-01-11 20:25:30 +01:00
if ( ! empty ( $cmd_pw )) {
2021-06-04 15:48:41 +02:00
// Use proc_open call to avoid safe_mode problems and to prevent showing plain password in process table
$spec = array (
0 => array ( " pipe " , " r " ), // stdin
1 => array ( " pipe " , " w " ), // stdout
);
$command = " $cmd_pw $cmdarg1 $cmdarg2 2>&1 " ;
$proc = proc_open ( $command , $spec , $pipes );
if ( ! $proc ) {
error_log ( " can't proc_open $cmd_pw " );
2021-01-11 20:25:30 +01:00
$this -> errormsg [] .= $warnmsg_pw ;
$status = false ;
2021-06-04 15:48:41 +02:00
} else {
// Write passwords through pipe to command stdin -- provide old password, then new password.
fwrite ( $pipes [ 0 ], " \0 " , 1 );
fwrite ( $pipes [ 0 ], $this -> values [ 'password' ] . " \0 " , 1 + strlen ( $this -> values [ 'password' ]));
$output = stream_get_contents ( $pipes [ 1 ]);
fclose ( $pipes [ 0 ]);
fclose ( $pipes [ 1 ]);
$retval = proc_close ( $proc );
if ( 0 != $retval ) {
error_log ( " Running $command yielded return value= $retval , output was: " . json_encode ( $output ));
$this -> errormsg [] .= $warnmsg_pw ;
$status = false ;
}
2021-01-11 20:25:30 +01:00
}
}
return $status ;
2013-08-07 22:05:18 +02:00
}
2013-11-14 23:10:05 +01:00
/**
* Called after a mailbox has been deleted
*
* @ return boolean true on success , false on failure
* also adds a detailed error message to $this -> errormsg []
*/
2021-04-13 22:19:16 +02:00
protected function mailbox_postdeletion ()
{
2019-10-19 21:51:05 +02:00
$cmd = Config :: read_string ( 'mailbox_postdeletion_script' );
2013-11-14 23:10:05 +01:00
2018-01-26 15:45:57 +01:00
if ( empty ( $cmd )) {
2013-11-14 23:10:05 +01:00
return true ;
}
2018-01-26 15:45:57 +01:00
list ( /*NULL*/ , $domain ) = explode ( '@' , $this -> id );
2013-11-14 23:10:05 +01:00
if ( empty ( $this -> id ) || empty ( $domain )) {
$this -> errormsg [] = 'Empty username and/or domain parameter in mailbox_postdeletion' ;
return false ;
}
$cmdarg1 = escapeshellarg ( $this -> id );
$cmdarg2 = escapeshellarg ( $domain );
$command = " $cmd $cmdarg1 $cmdarg2 " ;
$retval = 0 ;
$output = array ();
$firstline = '' ;
2018-01-26 15:45:57 +01:00
$firstline = exec ( $command , $output , $retval );
2013-11-14 23:10:05 +01:00
if ( 0 != $retval ) {
error_log ( " Running $command yielded return value= $retval , first line of output= $firstline " );
$this -> errormsg [] = 'Problems running mailbox postdeletion script!' ;
2018-01-26 15:45:57 +01:00
return false ;
2013-11-14 23:10:05 +01:00
}
2018-01-26 15:45:57 +01:00
return true ;
2013-11-14 23:10:05 +01:00
}
2013-08-07 22:05:18 +02:00
/**
2024-04-01 20:38:31 +02:00
* Effectively deprecated - see linked issues
* @ see https :// github . com / postfixadmin / postfixadmin / issues / 472
* @ see https :// github . com / postfixadmin / postfixadmin / issues / 812
2013-08-07 22:05:18 +02:00
*/
2021-04-13 22:19:16 +02:00
protected function create_mailbox_subfolders ()
{
2024-04-01 20:38:31 +02:00
// no longer implemented; code relied on deprecated PHP imap extension, output some sort of error message
// if it looks like the installation used to support it.
2024-04-01 20:32:27 +02:00
2013-08-07 22:05:18 +02:00
$create_mailbox_subdirs = Config :: read ( 'create_mailbox_subdirs' );
2018-01-26 15:45:57 +01:00
if ( empty ( $create_mailbox_subdirs )) {
return true ;
}
2019-09-05 19:10:07 +02:00
2024-04-01 20:38:31 +02:00
error_log ( __FILE__ . ' WARNING : PostfixAdmin no longer supports the imap folder population via config parameters, see https://github.com/postfixadmin/postfixadmin/issues/812' );
2018-01-26 15:45:57 +01:00
return true ;
2013-08-07 22:05:18 +02:00
}
2018-01-26 15:45:57 +01:00
#TODO: more self explaining language strings!
2009-03-13 21:48:24 +01:00
}
2010-12-17 23:53:46 +01:00
/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */