Qmail/Vpopmail renaming a domain

qmailIn a Qmail/Vpopmail email server system, there is no direct way to rename a domain. Let’s say, you have a costumer that is using domain.net and then registers domain.com to replace domain.net. The obvious solution is to make domain.com an alias domain of domain.net, it’s just one simple command:

vaddaliasdomain domain.net domain.com

But if the old domain is to be dropped completely there is no logical reason to go for the domain alias route, except that is the quick and dirty solution. Also, it’s a bit stupid and confusing to be consuming all the email services (POP, IMAP, SMTP, Webmail, Control Panel) with credentials of the old domain when the domain in use is the new domain. All this hassle just because Vpopmail doesn’t provide a command do rename a domain.

Even with no direct command, it’s possible to rename a domain, keeping all the mailboxes (user@old-domain.com changes to user@new-domain.com), passwords, forwards, email alias, webmail preferences and contacts etc. It’s a bit painful, but doable:

  • move vpomail/domains/old_domain to vpomail/domains/new_domain
  • adjust if needed vpomail/domains/new_domain/.qmail-default
  • adjust if needed each mailbox .qmail file in vpomail/domains/new_domain/users(1,2,3)/.qmail
  • rename domain in rcpthosts or morercpthosts (if in morercpthosts run qmail-newmrh)
  • rename domain in qmail/users/assign and run qmail-newu
  • log in to mysql and in the vpopmail database rename the old domain table to new domain table (domain dots get converted to underscores)
  • in the new renamed table update the pw_dir field to match the new domain
  • update dir_control table to the new domain
  • update limits table
  • update valias (domain and valias_line field)
  • in the roundcube database update the users and identities tables to match the new domain in the respective old domain entries
  • restart qmail and test

If you have read all the way trough here, and are thinking in doing this procedure, WAIT i have a special treat, a script that takes care of all. It’s a PHP script, that should be executed in command line by root and accepts 2 arguments, the old domain and new domain. Just adjust the PHP path, the configurations (paths to stuff in the system), mark as executable and you are ready to go.

Beware, you will be running this as root, and i assure that it works fine on my server but i can’t assure that it won’t destroy yours. If you accept this terms please proceed and grab the script.

Continue reading “Qmail/Vpopmail renaming a domain”

.qmail delivery filtering by adress and subject

My goal was simple, to filter incoming emails by subject and to address (on a catch-all address…) in the server and move the matches to a specific folder instead of the normal delivery. I’m running a Qmail+Vpopmail system, and these directions should be valid for similar setups.

Probably i could do this with Procmail or Maildrop, but it seemed all so complicated to do just a simple one time task that i opted in for the fun route, doing it myself… for a recurrent email filtering task, multiple accounts, customization, any kind of heavy email filtering i strongly suggest to stop here and go read about Procmail or Maildrop.

Still here? Good. First, add a .qmail (see dot.qmail man) in the user Maildir folder that you want to setup a filter:

| /home/vpopmail/domains/domain.com/user/filter
/usr/home/vpopmail/domains/domain/user/Maildir/

Save it and take care with the permissions, vpopmail user should be able to read it. What we are doing here is really simple, in the first line we pipe incoming emails trough filter, and according to filter exit code qmail execute (or not) the second line and proceed with the normal delivery.

Now setup the filter itself, it’s written in dirty and messy PHP but it gets the job done. Also it depends on PEAR Mail_mimeDecode, so go ahed and install it:

pear install Mail_mimeDecode

Now the filter script itself, it must be customized to your needs:

#! /usr/local/bin/php
<?php

/*
 * QMAIL FILTER EMAILS
 *
 * invoked by .qmail files
 * | /path/to/this/script
 *
 * parses email to address and subject against target strings
 * if BOTH are matched email is saved in $save_matched_dir
 * and qmail is instructed to ignore further .qmail lines
 *
 * CONFIG:
 */
$max_bytes        = 262144;            // mail size > 256Kb is not scanned
$to_address       = 'to@domain.com';   // to address filter
$subjects         = array('subject 1',
                          'match this subject',
                          'other subject');
$save_matched_dir = '/home/vpopmail/domains/domain.com/save-matched-emails/';

/*
 * QMAIL EXIT CODES
 *
 * 0 - Success (go to next .qmail line)
 * 99 - Success and abort (do not execute next lines)
 * 100 - permanent error (bounce)
 * 111 - soft error (retry later)
 */
try {
  function decodeHeader($input) {
    // Remove white space between encoded-words
    $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?',
                         $input);

    // For each encoded-word...
    while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {

      $encoded  = $matches[1];
      $charset  = $matches[2];
      $encoding = $matches[3];
      $text     = $matches[4];
      
      switch (strtolower($encoding)) {
        case 'b':
          $text = base64_decode($text);
          break;

        case 'q':
          $text = str_replace('_', ' ', $text);
          preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
          foreach($matches[1] as $value)
            $text = str_replace('='.$value, chr(hexdec($value)), $text);
          break;
      }

      $input = str_replace($encoded, $text, $input);
    }

    if (! isset($charset))
      $charset = 'ASCII';

    $input = strtolower(
               preg_replace('/[^a-z ]/i', 
                            '', 
                 iconv($charset, 
                       'ASCII//TRANSLIT//IGNORE', 
                       $input)
               )
             );
    return $input;
  }

  $mail  = '';
  $bytes = 0;

  $fr = fopen("php://stdin", "r");
  while (!feof($fr)) {
    $mail .= fread($fr, 1024);
    $bytes += 1024;
    if ($bytes > $max_bytes) {
      fclose($fr);
      exit(0);
    }
  }
  fclose($fr);

  require_once 'Mail/mimeDecode.php';
  $decoder   = new Mail_mimeDecode($mail);
  $structure = $decoder->decode(array('decode_headers' => true));

  // check from address
  $patt = '/[a-z0-9]+([_\\.-][a-z0-9]+)*@([a-z0-9]+([\.-][a-z0-9]+)*)+\\.[a-z]{2,}/i';
  preg_match($patt, 
             $structure->headers['to'], $matches);
  if (isset($matches[0]) && $matches[0] == $to_address) {
    // check subject
    $structure->headers['subject'] = decodeHeader($structure->headers['subject']);

    foreach ($subjects as $subject) {
       if (strpos($structure->headers['subject'], $subject) !== false) {
         $fw = fopen($save_matched_dir.
                     time().
                     '.'.
                     rand(1000, 99999).
                     '.'.
                     gethostname().
                     'S='.
                     strlen($mail).
                     ':2', 'w');
         fwrite($fw, $mail);
         fclose($fw);
         exit(99);
         // exit(0);
       }
    }
  }
} catch (Exception $e) {
}


// default, continue normal processing
exit(0);

?>

save, add the php hash bang, mark it executable and vpopmail owned. Also, the $save_matched_dir is not created and should already be present in your system.

Thats it. After this setup you should start seeing a steady flow of matched emails being saved in the matched directory and not delivered in your Inbox. As usually this works like a charm to me but can work incredible bad for you, so use at your own peril.

Qmail + Vpopmail – Rebuild maildirsize file

Let’s say some program or script that doesn’t support maildirquota was poking around with a user maildir and you need to recalculate the user maildirsize file (recalculate the quota), you just need to:

cd /home/vpopmail/domains/domain.com/user/Maildir
rm maildirsize
vuserinfo -Q user@domain.com

and, voilá a new and accurate maildirsize file!

EDIT

I did a very simple script that rebuilds the quotas system wide (yes.. it’s PHP, not in real man C or some other fashion language, but it works for me)

$vpopmail_bin = '/home/vpopmail/bin/';

$domains = dir('/home/vpopmail/domains');
while (false !== ($domain = $domains->read())) {
  if ($domain != '.' && $domain != '..' && is_dir($domains->path.'/'.$domain)) {
    $users = dir($domains->path.'/'.$domain);
    while (false !== ($user = $users->read())) {
      if ($user != '.' && $user != '..' && is_dir($users->path.'/'.$user)) {
        if (file_exists($users->path.'/'.$user.'/Maildir/maildirsize')) {
          unlink($users->path.'/'.$user.'/Maildir/maildirsize');
          exec($vpopmail_bin.'vuserinfo -Q '.$user.'@'.$domain);
        }
      }
    }
  }
}