"Fossies" - the Fresh Open Source Software Archive

Member "openmailadmin-1.0.1/inc/lib/openmailadmin.php" (20 May 2008, 48017 Bytes) of package /linux/privat/old/openmailadmin-1.0.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "openmailadmin.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 /**
    3  * This class collects all methods of Openmailadmin, except for the view
    4  * and data storage.
    5  */
    6 class openmailadmin
    7 {
    8     public  $current_user;      // What user do we edit/display currently?
    9     public  $authenticated_user;    // What user did log in?
   10 
   11     private $db;
   12     private $validator;
   13     protected   $ErrorHandler;
   14 
   15     private $tablenames;
   16     private $cfg;
   17     public  $imap;
   18 
   19     // "alias" == "local part"
   20     const   regex_valid_alias   = '(?=^.{1,64}$)[a-z0-9]+(?:(?<![!$+\-_.])[!$+\-_.][a-z0-9]+)*';
   21     const   regex_valid_email   = '[a-z0-9]+(?:(?<![!$+\-_.])[!$+\-_.][a-z0-9]+)*@(?:(?:(?![.-])[a-z0-9\-]{1,63}(?<!-)\.?)+(?:(?<!\.)\.[a-z]{2,}))';
   22     const   regex_valid_domain  = '(?=^.{1,254}$)(?:^localhost$)|(?:^(?:(?![.-])[a-z0-9\-]{1,63}(?<!-)\.?)+(?:(?<!\.)\.[a-z]{2,})$)';
   23 
   24     function __construct(ADOConnection $adodb_handler, array $tablenames, array $cfg, IMAP_Administrator $imap) {
   25         $this->db       = $adodb_handler;
   26         $this->tablenames   = $tablenames;
   27         $this->cfg      = $cfg;
   28         $this->imap     = $imap;
   29         $this->validator    = new InputValidatorSuite($this, $cfg);
   30         $this->ErrorHandler = ErrorHandler::getInstance();
   31     }
   32 
   33     /*
   34      * This procedure simply executes every command stored in the array.
   35      */
   36     private function rollback($what) {
   37         if(is_array($what)) {
   38             foreach($what as $cmd) {
   39                 eval($cmd.';');
   40             }
   41         } else {
   42             eval($what.';');
   43         }
   44     }
   45 
   46     /*
   47      * Returns a long list with every active mailbox.
   48      */
   49     private function get_mailbox_names() {
   50         $tmp    = array();
   51 
   52         $result = $this->db->Execute('SELECT mbox FROM '.$this->tablenames['user'].' WHERE active = 1');
   53         while(!$result->EOF) {
   54             if($result->fields['mbox'] != '')
   55                 $tmp[] = $result->fields['mbox'];
   56             $result->MoveNext();
   57         }
   58         return $tmp;
   59     }
   60 
   61     /*
   62      * As the name says, returns an array containing the entire row
   63      * of the "user" table belonging to that mailbox.
   64      */
   65     public function get_user_row($mailbox) {
   66         return $this->db->GetRow('SELECT * FROM '.$this->tablenames['user'].' WHERE mbox='.$this->db->qstr($mailbox));
   67     }
   68 
   69     /*
   70      * Accepts a string containing possible destination for an email-address,
   71      * selects valid destinations and returns them.
   72      */
   73     public function get_valid_destinations($possible) {
   74         // Define what addresses we will accept.
   75         $pattern  = openmailadmin::regex_valid_email;
   76         $pattern .= '|'.$this->current_user->mbox.'|'.txt('5').'|'.strtolower(txt('5'));
   77         if($this->cfg['allow_mbox_as_target']) {
   78             $mailboxes = &$this->get_mailbox_names();
   79             if(count($mailboxes) > 0) {
   80                 $mailboxes = array_map('preg_quote', $mailboxes);
   81                 $pattern .= '|'.implode('|', $mailboxes);
   82             }
   83         } else if($this->cfg['allow_wcyr_as_target']) {
   84             $pattern .= '|[a-z]{2,}[0-9]{4}';
   85         }
   86 
   87         // Get valid destinations.
   88         if(preg_match_all('/'.$pattern.'/iu', $possible, $matched)) {
   89             if(is_array($matched[0])) {
   90                 // Replace every occurence of 'mailbox' with the correct name.
   91                 array_walk($matched[0],
   92                     create_function('&$item,$index',
   93                             'if(strtolower($item) == \''.strtolower(txt('5')).'\') $item = \''.$this->current_user->mbox.'\';'
   94                             ));
   95                 return $matched[0];
   96             }
   97         }
   98         return array();
   99     }
  100 
  101     /*
  102      * Returns an array containing all domains the user may choose from.
  103      */
  104     public function get_domain_set($user, $categories, $cache = true) {
  105         $cat = '';
  106         $poss_dom = array();
  107 
  108         if($cache && isset($_SESSION['cache']['getDomainSet'][$user][$categories])) {
  109             return $_SESSION['cache']['getDomainSet'][$user][$categories];
  110         } else {
  111             foreach(explode(',', $categories) as $value) {
  112                 $poss_dom[] = trim($value);
  113                 $cat .= ' OR categories LIKE '.$this->db->qstr('%'.trim($value).'%');
  114             }
  115             $dom = array();
  116             $result = $this->db->Execute('SELECT domain FROM '.$this->tablenames['domains']
  117                 .' WHERE owner='.$this->db->qstr($user).' OR a_admin LIKE '.$this->db->qstr('%'.$user.'%').' OR '.db_find_in_set($this->db, 'domain', $poss_dom).$cat);
  118             if(!$result === false) {
  119                 while(!$result->EOF) {
  120                     $dom[] = idn_to_utf8($result->fields['domain']);
  121                     $result->MoveNext();
  122                 }
  123             }
  124 
  125             $_SESSION['cache']['getDomainSet'][$user][$categories] = $dom;
  126             return $_SESSION['cache']['getDomainSet'][$user][$categories];
  127         }
  128     }
  129 
  130     /*
  131      * Checks whether a user is a descendant of another user.
  132      * (Unfortunately, PHP does not support inline functions.)
  133      */
  134     public function user_is_descendant($child, $parent, $levels = 7, $cache = array()) {
  135         // initialize cache
  136         if(!isset($_SESSION['cache']['IsDescendant'])) {
  137             $_SESSION['cache']['IsDescendant'] = array();
  138         }
  139 
  140         if(trim($child) == '' || trim($parent) == '')
  141             return false;
  142         if(isset($_SESSION['cache']['IsDescendant'][$parent][$child]))
  143             return $_SESSION['cache']['IsDescendant'][$parent][$child];
  144 
  145         if($child == $parent) {
  146             $rec = true;
  147         } else if($levels <= 0 ) {
  148             $rec = false;
  149         } else {
  150             $inter = $this->db->GetOne('SELECT pate FROM '.$this->tablenames['user'].' WHERE mbox='.$this->db->qstr($child));
  151             if($inter === false) {
  152                 $rec = false;
  153             } else {
  154                 if($inter == $parent) {
  155                     $rec = true;
  156                 } else if(in_array($inter, $cache)) {   // avoids loops
  157                     $rec = false;
  158                 } else {
  159                     $rec = $this->user_is_descendant($inter, $parent, $levels--, array_merge($cache, array($inter)));
  160                 }
  161             }
  162         }
  163         $_SESSION['cache']['IsDescendant'][$parent][$child] = $rec;
  164         return $rec;
  165     }
  166 
  167     /*
  168      * How many aliases the user has already in use?
  169      * Does cache, but not session-wide.
  170      */
  171     public function user_get_used_alias($username) {
  172         static $used = array();
  173         if(!isset($used[$username])) {
  174             $used[$username] = $this->db->GetOne('SELECT COUNT(*) FROM '.$this->tablenames['virtual'].' WHERE owner='.$this->db->qstr($username));
  175         }
  176         return $used[$username];
  177     }
  178     /*
  179      * How many regexp-addresses the user has already in use?
  180      * Does cache, but not session-wide.
  181      */
  182     public function user_get_used_regexp($username) {
  183         static $used = array();
  184         if(!isset($used[$username])) {
  185             $used[$username] = $this->db->GetOne('SELECT COUNT(*) FROM '.$this->tablenames['virtual_regexp'].' WHERE owner='.$this->db->qstr($username));
  186         }
  187         return $used[$username];
  188     }
  189 
  190     /*
  191      * These just count how many elements have been assigned to that given user.
  192      */
  193     public function user_get_number_mailboxes($username) {
  194         if(!isset($_SESSION['cache']['n_Mailboxes'][$username]['mailboxes'])) {
  195             $tmp = $this->db->GetOne('SELECT COUNT(*) FROM '.$this->tablenames['user'].' WHERE pate='.$this->db->qstr($username));
  196             $_SESSION['cache']['n_Mailboxes'][$username]['mailboxes'] = $tmp;
  197         }
  198         return $_SESSION['cache']['n_Mailboxes'][$username]['mailboxes'];
  199     }
  200     /*
  201      * These just count how many elements have been assigned to that given user.
  202      */
  203     public function user_get_number_domains($username) {
  204         if(!isset($_SESSION['cache']['n_Domains'][$username]['domains'])) {
  205             $tmp = $this->db->GetOne('SELECT COUNT(*) FROM '.$this->tablenames['domains'].' WHERE owner='.$this->db->qstr($username));
  206             $_SESSION['cache']['n_Domains'][$username]['domains'] = $tmp;
  207         }
  208         return $_SESSION['cache']['n_Domains'][$username]['domains'];
  209     }
  210     /*
  211      * In case you have changed something about domains...
  212      */
  213     private function user_invalidate_domain_sets() {
  214         if(isset($_SESSION['cache']['getDomainSet'])) {
  215             unset($_SESSION['cache']['getDomainSet']);
  216         }
  217     }
  218 
  219 /* ******************************* addresses ******************************** */
  220     /*
  221      * Returns a long list with all addresses (the virtuals' table).
  222      */
  223     public function get_addresses() {
  224         $alias = array();
  225 
  226         $result = $this->db->SelectLimit('SELECT address, dest, active'
  227                     .' FROM '.$this->tablenames['virtual']
  228                     .' WHERE owner='.$this->db->qstr($this->current_user->mbox).$_SESSION['filter']['str']['address']
  229                     .' ORDER BY address, dest',
  230                     $_SESSION['limit'], $_SESSION['offset']['address']);
  231         if(!$result === false) {
  232             while(!$result->EOF) {
  233                 $row    = $result->fields;
  234                 // explode all destinations (as there may be many)
  235                 $dest = array();
  236                 foreach(explode(',', $row['dest']) as $value) {
  237                     $value = trim($value);
  238                     // replace the current user's name with "mailbox"
  239                     if($value == $this->current_user->mbox)
  240                         $dest[] = txt('5');
  241                     else
  242                         $dest[] = $value;
  243                 }
  244                 sort($dest);
  245                 $row['dest'] = $dest;
  246                 // detect where the "@" is
  247                 $at = strpos($row['address'], '@');
  248                 //turn the alias of catchalls to a star
  249                 if($at == 0)
  250                     $row['alias'] = '*';
  251                 else
  252                     $row['alias'] = substr($row['address'], 0, $at);
  253                 $row['domain'] = idn_to_utf8(substr($row['address'], $at+1));
  254                 // add the current entry to our list of aliases
  255                 $alias[] = $row;
  256                 $result->MoveNext();
  257             }
  258             usort($alias, create_function('$a, $b', 'return ($a["domain"] == $b["domain"] ? strcmp($a["alias"], $b["alias"]) : strcmp($a["domain"], $b["domain"]));'));
  259         }
  260         return $alias;
  261     }
  262 
  263     /*
  264      * Creates a new email-address.
  265      */
  266     public function address_create($alias, $domain, $arr_destinations) {
  267         $domain = idn_to_ascii($domain);
  268         // May the user create another address?
  269         if($this->current_user->used_alias < $this->current_user->max_alias
  270            || $this->authenticated_user->a_super >= 1) {
  271             // If he did choose a catchall, may he create such an address?
  272             if($alias == '*' && $this->cfg['address']['allow_catchall']) {
  273                 if($this->cfg['address']['restrict_catchall']) {
  274                     // If either the current or the authenticated user is
  275                     // owner of that given domain, we can permit creation of that catchall.
  276                     $result = $this->db->GetOne('SELECT domain FROM '.$this->tablenames['domains']
  277                                 .' WHERE domain='.$this->db->qstr($domain)
  278                                 .' AND (owner='.$this->db->qstr($this->current_user->mbox).' OR owner='.$this->db->qstr($this->authenticated_user->mbox).')');
  279                     if($result === false) {         // negative check!
  280                         $this->ErrorHandler->add_error(txt('16'));
  281                         return false;
  282                     }
  283                     // There shall be no local part in the address. That is characteristic for catchalls.
  284                     $alias = '';
  285                 }
  286             }
  287             // Will his new address be a valid one?
  288             else if(! preg_match('/^'.openmailadmin::regex_valid_alias.'$/i', $alias)) {
  289                 $this->ErrorHandler->add_error(txt('13'));
  290                 return false;
  291             }
  292             // Restrict amount of possible destinations.
  293             $max = $alias == '' ? $this->cfg['address']['max_dest_p_catchall'] : $this->cfg['address']['max_dest_p_address'];
  294             if(count($arr_destinations) > $max) {
  295                 $this->ErrorHandler->add_error(sprintf(txt('136'), $max));
  296                 return false;
  297             }
  298             // Finally, create that address.
  299             $this->db->Execute('INSERT INTO '.$this->tablenames['virtual'].' (address, dest, owner) VALUES (?, ?, ?)',
  300                         array(strtolower($alias.'@'.$domain), implode(',', $arr_destinations), $this->current_user->mbox));
  301             if($this->db->Affected_Rows() < 1) {
  302                 $this->ErrorHandler->add_error(txt('133'));
  303             } else {
  304                 $this->current_user->used_alias++;
  305                 return true;
  306             }
  307         } else {
  308             $this->ErrorHandler->add_error(txt('14'));
  309         }
  310         return false;
  311     }
  312     /*
  313      * Deletes the given addresses if they belong to the current user.
  314      */
  315     public function address_delete($arr_addresses) {
  316         $this->db->Execute('DELETE FROM '.$this->tablenames['virtual']
  317                 .' WHERE owner='.$this->db->qstr($this->current_user->mbox)
  318                 .' AND '.db_find_in_set($this->db, 'address', $arr_addresses));
  319         if($this->db->Affected_Rows() < 1) {
  320             if($this->db->ErrorNo() != 0) {
  321                 $this->ErrorHandler->add_error($this->db->ErrorMsg());
  322             }
  323         } else {
  324             array_walk($arr_addresses,
  325                     create_function('&$item,$index',
  326                             '$parts = explode(\'@\', $item); $item = implode(\'@\', array($parts[0], idn_to_utf8($parts[1])));'
  327                             ));
  328             $this->ErrorHandler->add_info(sprintf(txt('15'), implode(',', $arr_addresses)));
  329             $this->current_user->used_alias -= $this->db->Affected_Rows();
  330             return true;
  331         }
  332 
  333         return false;
  334     }
  335     /*
  336      * Changes the destination of the given addresses if they belong to the current user.
  337      */
  338     public function address_change_destination($arr_addresses, $arr_destinations) {
  339         $max = $this->cfg['address']['max_dest_p_address'];
  340         foreach($arr_addresses as $addr) {
  341             if($addr{0} == '@') { // catchall!
  342                 $max = $this->cfg['address']['max_dest_p_catchall'];
  343                 break;
  344             }
  345         }
  346         if(count($arr_destinations) > $max) {
  347             $this->ErrorHandler->add_error(sprintf(txt('136'), $max));
  348             return false;
  349         }
  350         $this->db->Execute('UPDATE '.$this->tablenames['virtual'].' SET dest='.$this->db->qstr(implode(',', $arr_destinations)).', neu=1'
  351                 .' WHERE owner='.$this->db->qstr($this->current_user->mbox)
  352                 .' AND '.db_find_in_set($this->db, 'address', $arr_addresses));
  353         if($this->db->Affected_Rows() < 1) {
  354             if($this->db->ErrorNo() != 0) {
  355                 $this->ErrorHandler->add_error($this->db->ErrorMsg());
  356             }
  357         } else {
  358             return true;
  359         }
  360         return false;
  361     }
  362     /*
  363      * Toggles the 'active'-flag of a set of addresses  of the current user
  364      * and thus sets inactive ones to active ones and vice versa.
  365      */
  366     public function address_toggle_active($arr_addresses) {
  367         $this->db->Execute('UPDATE '.$this->tablenames['virtual'].' SET active=NOT active, neu=1'
  368                 .' WHERE owner='.$this->db->qstr($this->current_user->mbox)
  369                 .' AND '.db_find_in_set($this->db, 'address', $arr_addresses));
  370         if($this->db->Affected_Rows() < 1) {
  371             if($this->db->ErrorNo() != 0) {
  372                 $this->ErrorHandler->add_error($this->db->ErrorMsg());
  373             }
  374         } else {
  375             return true;
  376         }
  377 
  378         return false;
  379     }
  380 
  381 /* ******************************* domains ********************************** */
  382     public $editable_domains;   // How many domains can the current user change?
  383     /*
  384      * Returns a long list with all domains (from table 'domains').
  385      */
  386     public function get_domains() {
  387         $this->editable_domains = 0;
  388         $domains = array();
  389 
  390         $query  = 'SELECT * FROM '.$this->tablenames['domains'];
  391         if($this->authenticated_user->a_super > 0) {
  392             $query .= ' WHERE 1=1 '.$_SESSION['filter']['str']['domain'];
  393         } else {
  394             $query .= ' WHERE (owner='.$this->db->qstr($this->current_user->mbox).' or a_admin LIKE '.$this->db->qstr('%'.$this->current_user->mbox.'%').')'
  395                  .$_SESSION['filter']['str']['domain'];
  396         }
  397         $query .= ' ORDER BY owner, length(a_admin), domain';
  398 
  399         $result = $this->db->SelectLimit($query, $_SESSION['limit'], $_SESSION['offset']['mbox']);
  400         if(!$result === false) {
  401             while(!$result->EOF) {
  402                 $row    = $result->fields;
  403                 if($row['owner'] == $this->authenticated_user->mbox
  404                    || find_in_set($this->authenticated_user->mbox, $row['a_admin'])) {
  405                     $row['selectable']  = true;
  406                     ++$this->editable_domains;
  407                 } else {
  408                     $row['selectable']  = false;
  409                 }
  410                 $row['domain'] = idn_to_utf8($row['domain']);
  411                 $domains[] = $row;
  412                 $result->MoveNext();
  413             }
  414         }
  415 
  416         $this->current_user->n_domains = $this->user_get_number_domains($this->current_user->mbox);
  417 
  418         return $domains;
  419     }
  420     /**
  421      * May the new user only select from domains which have been assigned to
  422      * the reference user? If so, return true.
  423      *
  424      * @param   reference   Instance of User
  425      * @param   tobechecked Mailbox-name.
  426      */
  427     public function domain_check(User $reference, $tobechecked, $domain_key) {
  428         if(!isset($reference->domain_set)) {
  429             $reference->domain_set = $this->get_domain_set($reference->mbox, $reference->domains);
  430         }
  431         // new domain-key must not lead to more domains than the user already has to choose from
  432         // A = Domains the new user will be able to choose from.
  433         $dom_a = $this->get_domain_set($tobechecked, $domain_key, false);
  434         // B = Domains the creator may choose from (that is $reference['domain_set'])?
  435         // Okay, if A is part of B. (Thus, no additional domains are added for user "A".)
  436         // Indication: A <= B
  437         if(count($dom_a) == 0) {
  438             // This will be only a warning.
  439             $this->ErrorHandler->add_error(txt('80'));
  440         } else if(count($dom_a) > count($reference->domain_set)
  441                && count(array_diff($dom_a, $reference->domain_set)) > 0) {
  442             // A could have domains which the reference cannot access.
  443             return false;
  444         }
  445 
  446         return true;
  447     }
  448     /*
  449      * Adds a new domain into the corresponding table.
  450      * Categories are for grouping domains.
  451      */
  452     public function domain_add($domain, $props) {
  453         $domain = idn_to_ascii($domain);
  454         $props['domain'] = $domain;
  455         if(!$this->validator->validate($props, array('domain', 'categories', 'owner', 'a_admin'))) {
  456             return false;
  457         }
  458 
  459         if(!stristr($props['categories'], 'all'))
  460             $props['categories'] = 'all,'.$props['categories'];
  461         if(!stristr($props['a_admin'], $this->current_user->mbox))
  462             $props['a_admin'] .= ','.$this->current_user->mbox;
  463 
  464         $this->db->Execute('INSERT INTO '.$this->tablenames['domains'].' (domain, categories, owner, a_admin) VALUES (?, ?, ?, ?)',
  465                 array($domain, $props['categories'], $props['owner'], $props['a_admin']));
  466         if($this->db->Affected_Rows() < 1) {
  467             $this->ErrorHandler->add_error(txt('134'));
  468         } else {
  469             $this->user_invalidate_domain_sets();
  470             return true;
  471         }
  472 
  473         return false;
  474     }
  475     /*
  476      * Not only removes the given domains by their ids,
  477      * it deactivates every address which ends in that domain.
  478      */
  479     public function domain_remove($domains) {
  480         // We need the old domain name later...
  481         if(is_array($domains) && count($domains) > 0) {
  482             if($this->cfg['admins_delete_domains']) {
  483                 $result = $this->db->SelectLimit('SELECT ID, domain'
  484                     .' FROM '.$this->tablenames['domains']
  485                     .' WHERE (owner='.$this->db->qstr($this->authenticated_user->mbox).' OR a_admin LIKE '.$this->db->qstr('%'.$this->authenticated_user->mbox.'%').') AND '.db_find_in_set($this->db, 'ID', $domains),
  486                     count($domains));
  487             } else {
  488                 $result = $this->db->SelectLimit('SELECT ID, domain'
  489                     .' FROM '.$this->tablenames['domains']
  490                     .' WHERE owner='.$this->db->qstr($this->authenticated_user->mbox).' AND '.db_find_in_set($this->db, 'ID', $domains),
  491                     count($domains));
  492             }
  493             if(!$result === false) {
  494                 while(!$result->EOF) {
  495                     $del_ID[] = $result->fields['ID'];
  496                     $del_nm[] = idn_to_utf8($result->fields['domain']);
  497                     $result->MoveNext();
  498                 }
  499                 if(isset($del_ID)) {
  500                     $this->db->Execute('DELETE FROM '.$this->tablenames['domains'].' WHERE '.db_find_in_set($this->db, 'ID', $del_ID));
  501                     if($this->db->Affected_Rows() < 1) {
  502                         if($this->db->ErrorNo() != 0) {
  503                             $this->ErrorHandler->add_error($this->db->ErrorMsg());
  504                         }
  505                     } else {
  506                         $this->ErrorHandler->add_info(txt('52').'<br />'.implode(', ', $del_nm));
  507                         // We better deactivate all aliases containing that domain, so users can see the domain was deleted.
  508                         foreach($del_nm as $domainname) {
  509                             $this->db->Execute('UPDATE '.$this->tablenames['virtual'].' SET active = 0, neu = 1 WHERE address LIKE '.$this->db->qstr('%'.idn_to_ascii($domainname)));
  510                         }
  511                         // We can't do such on REGEXP addresses: They may catch more than the given domains.
  512                         $this->user_invalidate_domain_sets();
  513                         return true;
  514                     }
  515                 } else {
  516                     $this->ErrorHandler->add_error(txt('16'));
  517                 }
  518             } else {
  519                 $this->ErrorHandler->add_error(txt('16'));
  520             }
  521         } else {
  522             $this->ErrorHandler->add_error(txt('11'));
  523         }
  524 
  525         return false;
  526     }
  527     /*
  528      * Every parameter is an array. $domains contains IDs.
  529      */
  530     public function domain_change($domains, $change, $data) {
  531         $toc = array();     // to be changed
  532 
  533         if(!$this->validator->validate($data, $change)) {
  534             return false;
  535         }
  536 
  537         if(!is_array($change)) {
  538             $this->ErrorHandler->add_error(txt('53'));
  539             return false;
  540         }
  541         if($this->cfg['admins_delete_domains'] && in_array('owner', $change))
  542             $toc[] = 'owner='.$this->db->qstr($data['owner']);
  543         if(in_array('a_admin', $change))
  544             $toc[] = 'a_admin='.$this->db->qstr($data['a_admin']);
  545         if(in_array('categories', $change))
  546             $toc[] = 'categories='.$this->db->qstr($data['categories']);
  547         if(count($toc) > 0) {
  548             $this->db->Execute('UPDATE '.$this->tablenames['domains']
  549                 .' SET '.implode(',', $toc)
  550                 .' WHERE (owner='.$this->db->qstr($this->authenticated_user->mbox).' or a_admin LIKE '.$this->db->qstr('%'.$this->authenticated_user->mbox.'%').') AND '.db_find_in_set($this->db, 'ID', $domains));
  551             if($this->db->Affected_Rows() < 1) {
  552                 if($this->db->ErrorNo() != 0) {
  553                     $this->ErrorHandler->add_error($this->db->ErrorMsg());
  554                 } else {
  555                     $this->ErrorHandler->add_error(txt('16'));
  556                 }
  557             }
  558         }
  559         // changing ownership if $this->cfg['admins_delete_domains'] == false
  560         if(!$this->cfg['admins_delete_domains'] && in_array('owner', $change)) {
  561             $this->db->Execute('UPDATE '.$this->tablenames['domains']
  562                 .' SET owner='.$this->db->qstr($data['owner'])
  563                 .' WHERE owner='.$this->db->qstr($this->authenticated_user->mbox).' AND '.db_find_in_set($this->db, 'ID', $domains));
  564         }
  565         $this->user_invalidate_domain_sets();
  566         // No domain be renamed?
  567         if(! in_array('domain', $change)) {
  568             return true;
  569         }
  570         // Otherwise (and if only one) try adapting older addresses.
  571         if(count($domains) == 1) {
  572             // Grep the old name, we will need it later for replacement.
  573             $domain = $this->db->GetRow('SELECT ID, domain AS name FROM '.$this->tablenames['domains'].' WHERE ID = '.$this->db->qstr($domains[0]).' AND (owner='.$this->db->qstr($this->authenticated_user->mbox).' or a_admin LIKE '.$this->db->qstr('%'.$this->authenticated_user->mbox.'%').')');
  574             if(!$domain === false) {
  575                 // First, update the name. (Corresponding field is marked as unique, therefore we will not receive doublettes.)...
  576                 $this->db->Execute('UPDATE '.$this->tablenames['domains'].' SET domain = '.$this->db->qstr($data['domain']).' WHERE ID = '.$domain['ID']);
  577                 // ... and then, change every old address.
  578                 if($this->db->Affected_Rows() == 1) {
  579                     // address
  580                     $this->db->Execute('UPDATE '.$this->tablenames['virtual'].' SET neu = 1, address = REPLACE(address, '.$this->db->qstr('@'.$domain['name']).', '.$this->db->qstr('@'.$data['domain']).') WHERE address LIKE '.$this->db->qstr('%@'.$domain['name']));
  581                     // dest
  582                     $this->db->Execute('UPDATE '.$this->tablenames['virtual'].' SET neu = 1, dest = REPLACE(dest, '.$this->db->qstr('@'.$domain['name']).', '.$this->db->qstr('@'.$data['domain']).') WHERE dest LIKE '.$this->db->qstr('%@'.$domain['name'].'%'));
  583                     $this->db->Execute('UPDATE '.$this->tablenames['virtual_regexp'].' SET neu = 1, dest = REPLACE(dest, '.$this->db->qstr('@'.$domain['name']).', '.$this->db->qstr('@'.$data['domain']).') WHERE dest LIKE '.$this->db->qstr('%@'.$domain['name'].'%'));
  584                     // canonical
  585                     $this->db->Execute('UPDATE '.$this->tablenames['user'].' SET canonical = REPLACE(canonical, '.$this->db->qstr('@'.$domain['name']).', '.$this->db->qstr('@'.$data['domain']).') WHERE canonical LIKE '.$this->db->qstr('%@'.$domain['name']));
  586                 } else {
  587                     $this->ErrorHandler->add_error($this->db->ErrorMsg());
  588                 }
  589                 return true;
  590             } else {
  591                 $this->ErrorHandler->add_error(txt('91'));
  592             }
  593         } else {
  594             $this->ErrorHandler->add_error(txt('53'));
  595         }
  596 
  597         return false;
  598     }
  599 
  600 /* ******************************* passwords ******************************** */
  601     /**
  602      * Changes the current user's password.
  603      * This requires the former password for authentication if current user and
  604      * authenticated user are the same.
  605      */
  606     public function user_change_password($new, $new_repeat, $old_passwd = null) {
  607         if($this->current_user->mbox == $this->authenticated_user->mbox
  608            && !is_null($old_passwd)
  609            && !$this->current_user->password->equals($old_passwd)) {
  610             $this->ErrorHandler->add_error(txt('45'));
  611         } else if($new != $new_repeat) {
  612             $this->ErrorHandler->add_error(txt('44'));
  613         } else if(strlen($new) < $this->cfg['passwd']['min_length']
  614             || strlen($new) > $this->cfg['passwd']['max_length']) {
  615             $this->ErrorHandler->add_error(sprintf(txt('46'), $this->cfg['passwd']['min_length'], $this->cfg['passwd']['max_length']));
  616         } else {
  617             // Warn about insecure passwords, but let them pass.
  618             if(!Password::is_secure($new)) {
  619                 $this->ErrorHandler->add_error(txt('47'));
  620             }
  621             if($this->current_user->password->set($new)) {
  622                 $this->ErrorHandler->add_info(txt('48'));
  623                 return true;
  624             }
  625         }
  626         return false;
  627     }
  628 
  629 /* ******************************* regexp *********************************** */
  630     /*
  631      * Returns a long list with all regular expressions (the virtual_regexp table).
  632      * If $match_against is given, the flag "matching" will be set on matches.
  633      */
  634     public function get_regexp($match_against = null) {
  635         $regexp = array();
  636 
  637         $result = $this->db->SelectLimit('SELECT * FROM '.$this->tablenames['virtual_regexp']
  638                 .' WHERE owner='.$this->db->qstr($this->current_user->mbox).$_SESSION['filter']['str']['regexp']
  639                 .' ORDER BY dest',
  640                 $_SESSION['limit'], $_SESSION['offset']['regexp']);
  641         if(!$result === false) {
  642             while(!$result->EOF) {
  643                 $row    = $result->fields;
  644                 // if ordered, check whether expression matches probe address
  645                 if(!is_null($match_against)
  646                    && @preg_match($row['reg_exp'], $match_against)) {
  647                     $row['matching']    = true;
  648                 } else {
  649                     $row['matching']    = false;
  650                 }
  651                 // explode all destinations (as there may be many)
  652                 $dest = array();
  653                 foreach(explode(',', $row['dest']) as $value) {
  654                     $value = trim($value);
  655                     // replace the current user's name with "mailbox"
  656                     if($value == $this->current_user->mbox)
  657                     $dest[] = txt('5');
  658                     else
  659                     $dest[] = $value;
  660                 }
  661                 sort($dest);
  662                 $row['dest'] = $dest;
  663                 // add the current entry to our list of aliases
  664                 $regexp[] = $row;
  665                 $result->MoveNext();
  666             }
  667         }
  668         return $regexp;
  669     }
  670     /*
  671      * Creates a new regexp-address.
  672      */
  673     public function regexp_create($regexp, $arr_destinations) {
  674         // some dull checks;
  675         // if someone knows how to find out whether an string is a valid regexp -> write me please
  676         if($regexp == '' || $regexp{0} != '/') {
  677             $this->ErrorHandler->add_error(txt('127'));
  678             return false;
  679         }
  680 
  681         if($this->current_user->used_regexp < $this->current_user->max_regexp
  682            || $this->authenticated_user->a_super > 0) {
  683             $this->db->Execute('INSERT INTO '.$this->tablenames['virtual_regexp'].' (reg_exp, dest, owner) VALUES (?, ?, ?)',
  684                 array($regexp, implode(',', $arr_destinations), $this->current_user->mbox));
  685             if($this->db->Affected_Rows() < 1) {
  686                 if($this->db->ErrorNo() != 0) {
  687                     $this->ErrorHandler->add_error(txt('133'));
  688                 }
  689             } else {
  690                 $this->current_user->used_regexp++;
  691                 return true;
  692             }
  693         } else {
  694             $this->ErrorHandler->add_error(txt('31'));
  695         }
  696 
  697         return false;
  698     }
  699     /*
  700      * Deletes the given regular expressions if they belong to the current user.
  701      */
  702     public function regexp_delete($arr_regexp_ids) {
  703         $this->db->Execute('DELETE FROM '.$this->tablenames['virtual_regexp']
  704                 .' WHERE owner='.$this->db->qstr($this->current_user->mbox)
  705                 .' AND '.db_find_in_set($this->db, 'ID', $arr_regexp_ids));
  706         if($this->db->Affected_Rows() < 1) {
  707             if($this->db->ErrorNo() != 0) {
  708                 $this->ErrorHandler->add_error($this->db->ErrorMsg());
  709             }
  710         } else {
  711             $this->ErrorHandler->add_info(txt('32'));
  712             $this->current_user->used_regexp -= $this->db->Affected_Rows();
  713             return true;
  714         }
  715 
  716         return false;
  717     }
  718     /*
  719      * See "address_change_destination".
  720      */
  721     public function regexp_change_destination($arr_regexp_ids, $arr_destinations) {
  722         $this->db->Execute('UPDATE '.$this->tablenames['virtual_regexp'].' SET dest='.$this->db->qstr(implode(',', $arr_destinations)).', neu = 1'
  723                 .' WHERE owner='.$this->db->qstr($this->current_user->mbox)
  724                 .' AND '.db_find_in_set($this->db, 'ID', $arr_regexp_ids));
  725         if($this->db->Affected_Rows() < 1) {
  726             if($this->db->ErrorNo() != 0) {
  727                 $this->ErrorHandler->add_error($this->db->ErrorMsg());
  728             }
  729         } else {
  730             return true;
  731         }
  732 
  733         return false;
  734     }
  735     /*
  736      * See "address_toggle_active".
  737      */
  738     public function regexp_toggle_active($arr_regexp_ids) {
  739         $this->db->Execute('UPDATE '.$this->tablenames['virtual_regexp'].' SET active = NOT active, neu = 1'
  740                 .' WHERE owner='.$this->db->qstr($this->current_user->mbox)
  741                 .' AND '.db_find_in_set($this->db, 'ID', $arr_regexp_ids));
  742         if($this->db->Affected_Rows() < 1) {
  743             if($this->db->ErrorNo() != 0) {
  744                 $this->ErrorHandler->add_error($this->db->ErrorMsg());
  745             }
  746         } else {
  747             return true;
  748         }
  749 
  750         return false;
  751     }
  752 
  753 /* ******************************* mailboxes ******************************** */
  754     /*
  755      * Returns list with mailboxes the current user can see.
  756      * Typically all his patenkinder will show up.
  757      * If the current user is at his pages and is superuser, he will see all mailboxes.
  758      */
  759     public function get_mailboxes() {
  760         $mailboxes = array();
  761 
  762         if($this->current_user->mbox == $this->authenticated_user->mbox
  763            && $this->authenticated_user->a_super >= 1) {
  764             $where_clause = ' WHERE TRUE';
  765         } else {
  766             $where_clause = ' WHERE pate='.$this->db->qstr($this->current_user->mbox);
  767         }
  768 
  769         $result = $this->db->SelectLimit('SELECT mbox, person, canonical, pate, max_alias, max_regexp, usr.active, last_login AS lastlogin, a_super, a_admin_domains, a_admin_user, '
  770                         .'COUNT(DISTINCT virt.address) AS num_alias, '
  771                         .'COUNT(DISTINCT rexp.ID) AS num_regexp '
  772                     .'FROM '.$this->tablenames['user'].' usr '
  773                         .'LEFT OUTER JOIN '.$this->tablenames['virtual'].' virt ON (mbox=virt.owner) '
  774                         .'LEFT OUTER JOIN '.$this->tablenames['virtual_regexp'].' rexp ON (mbox=rexp.owner) '
  775                     .$where_clause.$_SESSION['filter']['str']['mbox']
  776                     .' GROUP BY mbox, person, canonical, pate,  max_alias, max_regexp, usr.active, last_login, a_super, a_admin_domains, a_admin_user '
  777                     .'ORDER BY pate, mbox',
  778                     $_SESSION['limit'], $_SESSION['offset']['mbox']);
  779 
  780         if(!$result === false) {
  781             while(!$result->EOF) {
  782                 if(!in_array($result->fields['mbox'], $this->cfg['user_ignore']))
  783                     $mailboxes[] = $result->fields;
  784                 $result->MoveNext();
  785             }
  786         }
  787         $this->current_user->n_mbox = $this->user_get_number_mailboxes($this->current_user->mbox);
  788 
  789         return $mailboxes;
  790     }
  791 
  792     /*
  793      * This will return a list with $whose's patenkinder for further use in selections.
  794      */
  795     public function get_selectable_paten($whose) {
  796         if(!isset($_SESSION['paten'][$whose])) {
  797             $selectable_paten = array();
  798             if($this->authenticated_user->a_super >= 1) {
  799                 $result = $this->db->Execute('SELECT mbox FROM '.$this->tablenames['user']);
  800             } else {
  801                 $result = $this->db->Execute('SELECT mbox FROM '.$this->tablenames['user'].' WHERE pate='.$this->db->qstr($whose));
  802             }
  803             while(!$result->EOF) {
  804                 if(!in_array($result->fields['mbox'], $this->cfg['user_ignore']))
  805                     $selectable_paten[] = $result->fields['mbox'];
  806                 $result->MoveNext();
  807             }
  808             $selectable_paten[] = $whose;
  809             $selectable_paten[] = $this->authenticated_user->mbox;
  810 
  811             // Array_unique() will do alphabetical sorting.
  812             $_SESSION['paten'][$whose] = array_unique($selectable_paten);
  813         }
  814 
  815         return $_SESSION['paten'][$whose];
  816     }
  817 
  818     /*
  819      * Eliminates every mailbox name from $desired_mboxes which is no descendant
  820      * of $who. If the authenticated user is superuser, no filtering is done
  821      * except elimination imposed by $this->cfg['user_ignore'].
  822      */
  823     private function mailbox_filter_manipulable($who, $desired_mboxes) {
  824         $allowed = array();
  825 
  826         // Does the authenticated user have the right to do that?
  827         if($this->authenticated_user->a_super >= 1) {
  828             $allowed = array_diff($desired_mboxes, $this->cfg['user_ignore']);
  829         } else {
  830             foreach($desired_mboxes as $mbox) {
  831                 if(!in_array($mbox, $this->cfg['user_ignore']) && $this->user_is_descendant($mbox, $who)) {
  832                     $allowed[] = $mbox;
  833                 }
  834             }
  835         }
  836 
  837         return $allowed;
  838     }
  839 
  840     /*
  841      * $props is typically $_POST, as this function selects all the necessary fields
  842      * itself.
  843      */
  844     public function mailbox_create($mboxname, $props) {
  845         $rollback   = array();
  846 
  847         // Check inputs for sanity and consistency.
  848         if(!$this->authenticated_user->a_admin_user > 0) {
  849             $this->ErrorHandler->add_error(txt('16'));
  850             return false;
  851         }
  852         if(in_array($mboxname, $this->cfg['user_ignore'])) {
  853             $this->ErrorHandler->add_error(sprintf(txt('130'), txt('83')));
  854             return false;
  855         }
  856         if(!$this->validator->validate($props, array('mbox','person','pate','canonical','domains','max_alias','max_regexp','a_admin_domains','a_admin_user','a_super','quota'))) {
  857             return false;
  858         }
  859 
  860         // check contingents (only if non-superuser)
  861         if($this->authenticated_user->a_super == 0) {
  862             // As the current user's contingents will be decreased we have to use his values.
  863             if($props['max_alias'] > ($this->current_user->max_alias - $this->user_get_used_alias($this->current_user->mbox))
  864                || $props['max_regexp'] > ($this->current_user->max_regexp - $this->user_get_used_regexp($this->current_user->mbox))) {
  865                 $this->ErrorHandler->add_error(txt('66'));
  866                 return false;
  867             }
  868             $quota  = $this->imap->get_users_quota($this->current_user->mbox);
  869             if($quota->is_set && $props['quota']*1024 > $quota->free) {
  870                 $this->ErrorHandler->add_error(txt('65'));
  871                 return false;
  872             }
  873         }
  874 
  875         // first create the default-from (canonical) (must not already exist!)
  876         if($this->cfg['create_canonical']) {
  877             $this->db->Execute('INSERT INTO '.$this->tablenames['virtual'].' (address, dest, owner) VALUES (?, ?, ?)',
  878                     array($props['canonical'], $mboxname, $mboxname));
  879             if($this->db->Affected_Rows() < 1) {
  880                 $this->ErrorHandler->add_error(txt('133'));
  881                 return false;
  882             }
  883             $rollback[] = '$this->db->Execute(\'DELETE FROM '.$this->tablenames['virtual'].' WHERE address='.addslashes($this->db->qstr($props['canonical'])).' AND owner='.addslashes($this->db->qstr($mboxname)).'\')';
  884         }
  885 
  886         // on success write the new user to database
  887         $this->db->Execute('INSERT INTO '.$this->tablenames['user'].' (mbox, person, pate, canonical, domains, max_alias, max_regexp, created, a_admin_domains, a_admin_user, a_super)'
  888                 .' VALUES (?,?,?,?,?,?,?,?,?,?,?)',
  889                 array($props['mbox'], $props['person'], $props['pate'], $props['canonical'], $props['domains'], $props['max_alias'], $props['max_regexp'], time(), $props['a_admin_domains'], $props['a_admin_user'], $props['a_super'])
  890                 );
  891         if($this->db->Affected_Rows() < 1) {
  892             $this->ErrorHandler->add_error(txt('92'));
  893             // Rollback
  894             $this->rollback($rollback);
  895             return false;
  896         }
  897         $rollback[] = '$this->db->Execute(\'DELETE FROM '.$this->tablenames['user'].' WHERE mbox='.addslashes($this->db->qstr($mboxname)).'\')';
  898 
  899         $tmpu = new User($props['mbox']);
  900         $pw = $tmpu->password->set_random($this->cfg['passwd']['min_length'], $this->cfg['passwd']['max_length']);
  901 
  902         // Decrease current users's contingents...
  903         if($this->authenticated_user->a_super == 0) {
  904             $rollback[] = '$this->db->Execute(\'UPDATE '.$this->tablenames['user'].' SET max_alias='.$this->current_user->max_alias.', max_regexp='.$this->current_user->max_regexp.' WHERE mbox='.addslashes($this->db->qstr($this->current_user->mbox)).'\')';
  905             $this->db->Execute('UPDATE '.$this->tablenames['user']
  906                 .' SET max_alias='.($this->current_user->max_alias-intval($props['max_alias'])).', max_regexp='.($this->current_user->max_regexp-intval($props['max_regexp']))
  907                 .' WHERE mbox='.$this->db->qstr($this->current_user->mbox));
  908         }
  909         // ... and then create the user on the server.
  910         $result = $this->imap->createmb($this->imap->format_user($mboxname));
  911         if(!$result) {
  912             $this->ErrorHandler->add_error($this->imap->error_msg);
  913             // Rollback
  914             $this->rollback($rollback);
  915             return false;
  916         } else {
  917             if(isset($this->cfg['folders']['create_default']) && is_array($this->cfg['folders']['create_default'])) {
  918                 foreach($this->cfg['folders']['create_default'] as $new_folder) {
  919                     $this->imap->createmb($this->imap->format_user($mboxname, $new_folder));
  920                 }
  921             }
  922         }
  923         $rollback[] = '$this->imap->deletemb($this->imap->format_user(\''.$mboxname.'\'))';
  924 
  925         // Decrease the creator's quota...
  926         $cur_usr_quota  = $this->imap->getquota($this->imap->format_user($this->current_user->mbox));
  927         if($this->authenticated_user->a_super == 0 && $cur_usr_quota->is_set) {
  928             $result = $this->imap->setquota($this->imap->format_user($this->current_user->mbox), $cur_usr_quota->max - $props['quota']*1024);
  929             if(!$result) {
  930                 $this->ErrorHandler->add_error($this->imap->error_msg);
  931                 // Rollback
  932                 $this->rollback($rollback);
  933                 return false;
  934             }
  935             $rollback[] = '$this->imap->setquota($this->imap->format_user($this->current_user->mbox), '.$cur_usr_quota->max .'))';
  936             $this->ErrorHandler->add_info(sprintf(txt('69'), floor(($cur_usr_quota->max - $props['quota']*1024)/1024) ));
  937         } else {
  938             $this->ErrorHandler->add_info(txt('71'));
  939         }
  940 
  941         // ... and set the new user's quota.
  942         if(is_numeric($props['quota'])) {
  943             $result = $this->imap->setquota($this->imap->format_user($mboxname), $props['quota']*1024);
  944             if(!$result) {
  945                 $this->ErrorHandler->add_error($this->imap->error_msg);
  946                 // Rollback
  947                 $this->rollback($rollback);
  948                 return false;
  949             }
  950         }
  951         $this->ErrorHandler->add_info(sprintf(txt('72'), $mboxname, $props['person'], $pw));
  952         if(isset($_SESSION['paten'][$props['pate']])) {
  953             $_SESSION['paten'][$props['pate']][] = $mboxname;
  954         }
  955 
  956         return true;
  957     }
  958 
  959     /*
  960      * $props can be $_POST, as every sutable field from $change is used.
  961      */
  962     public function mailbox_change($mboxnames, $change, $props) {
  963         // Ensure sanity of inputs and check requirements.
  964         if(!$this->authenticated_user->a_admin_user > 0) {
  965             $this->ErrorHandler->add_error(txt('16'));
  966             return false;
  967         }
  968         if(!$this->validator->validate($props, $change)) {
  969             return false;
  970         }
  971         $mboxnames = $this->mailbox_filter_manipulable($this->authenticated_user->mbox, $mboxnames);
  972         if(!count($mboxnames) > 0) {
  973             return false;
  974         }
  975 
  976         // Create an array holding every property we have to change.
  977         $to_change  = array();
  978         foreach(array('person', 'canonical', 'pate', 'domains', 'a_admin_domains', 'a_admin_user', 'a_super')
  979             as $property) {
  980             if(in_array($property, $change)) {
  981                 if(is_numeric($props[$property])) {
  982                     $to_change[]    = $property.' = '.$props[$property];
  983                 } else {
  984                     $to_change[]    = $property.'='.$this->db->qstr($props[$property]);
  985                 }
  986             }
  987         }
  988 
  989         // Execute the change operation regarding properties in DB.
  990         if(count($to_change) > 0) {
  991             $this->db->Execute('UPDATE '.$this->tablenames['user']
  992                 .' SET '.implode(',', $to_change)
  993                 .' WHERE '.db_find_in_set($this->db, 'mbox', $mboxnames));
  994         }
  995 
  996         // Manipulate contingents (except quota).
  997         foreach(array('max_alias', 'max_regexp') as $what) {
  998             if(in_array($what, $change)) {
  999                 $seek_table = $what == 'max_alias'
 1000                         ? $this->tablenames['virtual']
 1001                         : $this->tablenames['virtual_regexp'];
 1002                 $to_be_processed = $mboxnames;
 1003                 // Select users which use more aliases than allowed in future.
 1004                 $result = $this->db->Execute('SELECT COUNT(*) AS consum, owner, person'
 1005                         .' FROM '.$seek_table.','.$this->tablenames['user']
 1006                         .' WHERE '.db_find_in_set($this->db, 'owner', $mboxnames).' AND owner=mbox'
 1007                         .' GROUP BY owner'
 1008                         .' HAVING consum > '.$props[$what]);
 1009                 if(!$result === false) {
 1010                     // We have to skip them.
 1011                     $have_skipped = array();
 1012                     while(!$result->EOF) {
 1013                         $row    = $result->fields;
 1014                         $have_skipped[] = $row['owner'];
 1015                         if($this->cfg['mboxview_pers']) {
 1016                             $tmp[] = '<a href="'.mkSelfRef(array('cuser' => $row['owner'])).'" title="'.$row['owner'].'">'.$row['person'].' ('.$row['consum'].')</a>';
 1017                         } else {
 1018                             $tmp[] = '<a href="'.mkSelfRef(array('cuser' => $row['owner'])).'" title="'.$row['person'].'">'.$row['owner'].' ('.$row['consum'].')</a>';
 1019                         }
 1020                         $result->MoveNext();
 1021                     }
 1022                     if(count($have_skipped) > 0) {
 1023                         $this->ErrorHandler->add_error(sprintf(txt('131'),
 1024                                     $props[$what], $what == 'max_alias' ? txt('88') : txt('89'),
 1025                                     implode(', ', $tmp)));
 1026                         $to_be_processed = array_diff($to_be_processed, $have_skipped);
 1027                     }
 1028                 }
 1029                 if(count($to_be_processed) > 0) {
 1030                     // We don't need further checks if a superuser is logged in.
 1031                     if($this->authenticated_user->a_super > 0) {
 1032                     $this->db->Execute('UPDATE '.$this->tablenames['user']
 1033                         .' SET '.$what.'='.$props[$what]
 1034                         .' WHERE '.db_find_in_set($this->db, 'mbox', $to_be_processed));
 1035                     } else {
 1036                         // Now, calculate whether the current user has enough free contingents.
 1037                         $has_to_be_free = $this->db->GetOne('SELECT SUM('.$props[$what].'-'.$what.')'
 1038                                 .' FROM '.$this->tablenames['user']
 1039                                 .' WHERE '.db_find_in_set($this->db, 'mbox', $to_be_processed));
 1040                         if($has_to_be_free <= $this->user_get_used_alias($this->current_user->mbox)) {
 1041                             // If so, set new contingents on the users...
 1042                             $this->db->Execute('UPDATE '.$this->tablenames['user']
 1043                             .' SET '.$what.'='.$props[$what]
 1044                             .' WHERE '.db_find_in_set($this->db, 'mbox', $to_be_processed));
 1045                             // ... and add/remove the difference from the current user.
 1046                             $this->db->Execute('UPDATE '.$this->tablenames['user']
 1047                             .' SET '.$what.'='.$what.'-'.$has_to_be_free
 1048                             .' WHERE mbox='.$this->db->qstr($this->current_user->mbox));
 1049                         } else {
 1050                             // Else, we have to show an error message.
 1051                             $this->ErrorHandler->add_error(txt('66'));
 1052                         }
 1053                     }
 1054                 }
 1055             }
 1056         }
 1057 
 1058         // Change Quota.
 1059         if(in_array('quota', $change)) {
 1060             $add_quota = 0;
 1061             if($this->authenticated_user->a_super == 0) {
 1062                 foreach($mboxnames as $user) {
 1063                     if($user != '') {
 1064                         $quota  = $this->imap->get_users_quota($user);
 1065                         if($quota->is_set)
 1066                             $add_quota += intval($props['quota'])*1024 - $quota->max;
 1067                     }
 1068                 }
 1069                 $quota  = $this->imap->get_users_quota($this->current_user->mbox);
 1070                 if($add_quota != 0 && $quota->is_set) {
 1071                     $this->imap->setquota($this->imap->format_user($this->current_user->mbox), $quota->max - $add_quota);
 1072                     $this->ErrorHandler->add_info(sprintf(txt('78'), floor(($quota->max - $add_quota)/1024) ));
 1073                 }
 1074             }
 1075             reset($mboxnames);
 1076             foreach($mboxnames as $user) {
 1077                 if($user != '') {
 1078                     $result = $this->imap->setquota($this->imap->format_user($user), intval($props['quota'])*1024);
 1079                     if(!$result) {
 1080                         $this->ErrorHandler->add_error($this->imap->error_msg);
 1081                     }
 1082                 }
 1083             }
 1084         }
 1085 
 1086         // Renaming of (single) user.
 1087         if(in_array('mbox', $change)) {
 1088             if($this->imap->renamemb($this->imap->format_user($mboxnames['0']), $this->imap->format_user($props['mbox']))) {
 1089                 $this->db->Execute('UPDATE '.$this->tablenames['user'].' SET mbox='.$this->db->qstr($props['mbox']).' WHERE mbox='.$this->db->qstr($mboxnames['0']));
 1090                 $this->db->Execute('UPDATE '.$this->tablenames['domains'].' SET owner='.$this->db->qstr($props['mbox']).' WHERE owner='.$this->db->qstr($mboxnames['0']));
 1091                 $this->db->Execute('UPDATE '.$this->tablenames['domains'].' SET a_admin = REPLACE(a_admin, '.$this->db->qstr($mboxnames['0']).', '.$this->db->qstr($props['mbox']).') WHERE a_admin LIKE '.$this->db->qstr('%'.$mboxnames['0'].'%'));
 1092                 $this->db->Execute('UPDATE '.$this->tablenames['virtual'].' SET dest=REPLACE(dest, '.$this->db->qstr($mboxnames['0']).', '.$this->db->qstr($props['mbox']).'), neu = 1 WHERE dest REGEXP '.$this->db->qstr($mboxnames['0'].'[^@]{1,}').' OR dest LIKE '.$this->db->qstr('%'.$mboxnames['0']));
 1093                 $this->db->Execute('UPDATE '.$this->tablenames['virtual'].' SET owner='.$this->db->qstr($props['mbox']).' WHERE owner='.$this->db->qstr($mboxnames['0']));
 1094                 $this->db->Execute('UPDATE '.$this->tablenames['virtual_regexp'].' SET dest=REPLACE(dest, '.$this->db->qstr($mboxnames['0']).', '.$this->db->qstr($props['mbox']).'), neu = 1 WHERE dest REGEXP '.$this->db->qstr($mboxnames['0'].'[^@]{1,}').' OR dest LIKE '.$this->db->qstr('%'.$mboxnames['0']));
 1095                 $this->db->Execute('UPDATE '.$this->tablenames['virtual_regexp'].' SET owner='.$this->db->qstr($props['mbox']).' WHERE owner='.$this->db->qstr($mboxnames['0']));
 1096             } else {
 1097                 $this->ErrorHandler->add_error($this->imap->error_msg.'<br />'.txt('94'));
 1098             }
 1099         }
 1100 
 1101         if(isset($_SESSION['paten']) && in_array(array('mbox', 'pate'), $change)) {
 1102             unset($_SESSION['paten']);  // again: inefficient, but maybe we come up with something more elegant
 1103         }
 1104 
 1105         return true;
 1106     }
 1107 
 1108     /*
 1109      * If ressources are freed, the current user will get them.
 1110      */
 1111     public function mailbox_delete($mboxnames) {
 1112         $mboxnames = $this->mailbox_filter_manipulable($this->authenticated_user->mbox, $mboxnames);
 1113         if(count($mboxnames) == 0) {
 1114             return false;
 1115         }
 1116 
 1117         // Delete the given mailboxes from server.
 1118         $add_quota = 0;         // how many space was actually freed?
 1119         $toadd = 0;
 1120         $processed = array();       // which users did we delete successfully?
 1121         foreach($mboxnames as $user) {
 1122             if($user != '') {
 1123                 // We have to sum up all the space which gets freed in case we later want increase the deleter's quota.
 1124                 $quota  = $this->imap->get_users_quota($user);
 1125                 if($this->authenticated_user->a_super == 0
 1126                    && $quota->is_set) {
 1127                     $toadd = $quota->max;
 1128                 }
 1129                 $result = $this->imap->deletemb($this->imap->format_user($user));
 1130                 if(!$result) {      // failure
 1131                     $this->ErrorHandler->add_error($this->imap->error_msg);
 1132                 } else {        // success
 1133                     $add_quota += $toadd;
 1134                     $processed[] = $user;
 1135                 }
 1136             }
 1137         }
 1138 
 1139         // We need not proceed in case no users were deleted.
 1140         if(count($processed) == 0) {
 1141             return false;
 1142         }
 1143 
 1144         // Now we have to increase the current user's quota.
 1145         $quota  = $this->imap->get_users_quota($this->current_user->mbox);
 1146         if($this->authenticated_user->a_super == 0
 1147            && $add_quota > 0
 1148            && $quota->is_set) {
 1149             $this->imap->setquota($this->imap->format_user($this->current_user->mbox), $quota->max + $add_quota);
 1150             $this->ErrorHandler->add_info(sprintf(txt('76'), floor(($quota->max + $add_quota)/1024) ));
 1151         }
 1152 
 1153         // Calculate how many contingents get freed if we delete the users.
 1154         if($this->authenticated_user->a_super == 0) {
 1155             $result = $this->db->GetRow('SELECT SUM(max_alias) AS nr_alias, SUM(max_regexp) AS nr_regexp'
 1156                     .' FROM '.$this->tablenames['user']
 1157                     .' WHERE '.db_find_in_set($this->db, 'mbox', $processed));
 1158             if(!$result === false) {
 1159                 $will_be_free = $result;
 1160             }
 1161         }
 1162 
 1163         // virtual
 1164         $this->db->Execute('DELETE FROM '.$this->tablenames['virtual'].' WHERE '.db_find_in_set($this->db, 'owner', $processed));
 1165         $this->db->Execute('UPDATE '.$this->tablenames['virtual'].' SET active=0, neu=1 WHERE '.db_find_in_set($this->db, 'dest', $processed));
 1166         // virtual.regexp
 1167         $this->db->Execute('DELETE FROM '.$this->tablenames['virtual_regexp'].' WHERE '.db_find_in_set($this->db, 'owner', $processed));
 1168         $this->db->Execute('UPDATE '.$this->tablenames['virtual_regexp'].' SET active=0, neu=1 WHERE '.db_find_in_set($this->db, 'dest', $processed));
 1169         // domain (if the one to be deleted owns domains, the deletor will inherit them)
 1170         $this->db->Execute('UPDATE '.$this->tablenames['domains'].' SET owner='.$this->db->qstr($this->current_user->mbox).' WHERE '.db_find_in_set($this->db, 'owner', $processed));
 1171         // user
 1172         $this->db->Execute('DELETE FROM '.$this->tablenames['user'].' WHERE '.db_find_in_set($this->db, 'mbox', $processed));
 1173         if($this->authenticated_user->a_super == 0 && isset($will_be_free['nr_alias'])) {
 1174             $this->db->Execute('UPDATE '.$this->tablenames['user']
 1175             .' SET max_alias='.($this->current_user->max_alias+$will_be_free['nr_alias']).', max_regexp='.($this->current_user->max_regexp+$will_be_free['nr_regexp'])
 1176             .' WHERE mbox='.$this->db->qstr($this->current_user->mbox));
 1177         }
 1178         // patenkinder (will be inherited by the one deleting)
 1179         $this->db->Execute('UPDATE '.$this->tablenames['user']
 1180             .' SET pate='.$this->db->qstr($this->current_user->mbox)
 1181             .' WHERE '.db_find_in_set($this->db, 'pate', $processed));
 1182 
 1183         $this->ErrorHandler->add_info(sprintf(txt('75'), implode(',', $processed)));
 1184         if(isset($_SESSION['paten'])) unset($_SESSION['paten']); // inefficient, but maybe we come up with something more elegant
 1185 
 1186         return true;
 1187     }
 1188 
 1189     /*
 1190      * active <-> inactive
 1191      */
 1192     public function mailbox_toggle_active($mboxnames) {
 1193         $tobechanged = $this->mailbox_filter_manipulable($this->current_user->mbox, $mboxnames);
 1194         if(count($tobechanged) > 0) {
 1195             $this->db->Execute('UPDATE '.$this->tablenames['user']
 1196                     .' SET active = NOT active'
 1197                     .' WHERE '.db_find_in_set($this->db, 'mbox', $tobechanged));
 1198             if($this->db->Affected_Rows() < 1) {
 1199                 if($this->db->ErrorNo() != 0) {
 1200                     $this->ErrorHandler->add_error($this->db->ErrorMsg());
 1201                 }
 1202             } else {
 1203                 return true;
 1204             }
 1205         }
 1206         return false;
 1207     }
 1208 
 1209 }
 1210 ?>