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.

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

/*
 * Config
 */
$vpopmail_bin     = '/home/vpopmail/bin/';
$vpopmail_etc     = '/home/vpopmail/etc/';
$vpopmail_domains = '/home/vpopmail/domains/';
$qmail_bin        = '/var/qmail/bin/';
$qmail_control    = '/var/qmail/control/';
$qmail_users      = '/var/qmail/users/';
$qmailctl_bin     = '/usr/local/bin/qmailctl restart';       // path or false
$roundcube_config = '/home/roundcube/www/config/db.inc.php'; // path or false


/*
 * Functions
 */

function validateDomainSyntax($domain) {
	return preg_match('/^[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$/', $domain);
}

function runCommand($cmd) {
	try {
		system($cmd.' > /dev/null 2>&1', $return);
		if ($return > 0)
			die("Error in cmd: ".$cmd."\n");
	} catch (Exception $e) {
		die($e->getMessage()."\n");
	}
	
	return true;
}

function replaceInFile($file, $search, $replace) {
	if (! file_exists($file))
		return false; 
		
	$owner = fileowner($file);
	$group = filegroup($file);
	$perms = substr(sprintf('%o', fileperms($file)), -4);
	
	$read  = fopen($file, 'r');
	$write = fopen($file.'.tmp', 'w');

	$replaced = false;

	while (!feof($read)) {
		$line = fgets($read);

		$line = preg_replace('/'.preg_quote($search).'/', $replace, $line, -1, $count);
		if ($count)
			$replaced = true;

		fwrite($write, "$line");
	}
	
	fclose($read); 
	fclose($write);

	if ($replaced) {
		rename($file.'.tmp', $file);
		
		chown($file, $owner);
		chgrp($file, $group);
		chmod($file, $perms);
		return true;
	}
	
	unlink($file.'.tmp');
	return false;
}


/*
 * Script
 */
 
if (! isset($argv[2]))
	die("Usage ./rename_domain old_domain new_domain\n");

$old_domain = strtolower(trim($argv[1]));
$new_domain = strtolower(trim($argv[2]));

if ($old_domain == $new_domain)
	die("New_domain is equal to old_domain\n");

// validate old_domain
if (! validateDomainSyntax($old_domain))
	die("Invalid old_domain (check syntax)\n");

exec($vpopmail_bin.'vdominfo '.$old_domain, $output);

if (! isset($output[0]))
	die("Invalid old_domain\n");
	
if ($output[0] == 'Invalid domain name')
	die("Invalid old_domain\n");
	
foreach ($output as $line) {
	if ($line == 'alias: '.$old_domain) 
		die("Invalid old_domain (is an alias domain)\n");
}

if ($output[0] != 'domain: '.$old_domain) 
	die("Invalid old_domain\n");

// validate new_domain
if (! validateDomainSyntax($new_domain))
	die("Invalid new_domain (check syntax)\n");

$output = array();
exec($vpopmail_bin.'vdominfo '.$new_domain, $output);

if (! isset($output[0]))
	die("Invalid new_domain\n");
	
if ($output[0] != 'Invalid domain name') {
	if ($output[0] == 'domain: '.$new_domain) 
		die("Invalid new_domain (domain exists)\n");
		
	foreach ($output as $line) {
		if ($line == 'alias: '.$new_domain) 
			die("Invalid new_domain (is an alias domain)\n");
	}
	die("Invalid new_domain\n");
}


// filesystem changes
runCommand('/bin/mv '.$vpopmail_domains.$old_domain.' '.$vpopmail_domains.$new_domain);

if (! (replaceInFile($qmail_control.'rcpthosts', $old_domain, $new_domain) ||
      (replaceInFile($qmail_control.'morercpthosts', $old_domain, $new_domain) 
       && runCommand($qmail_bin.'qmail-newmrh')))) {
	die("Could not find domain in rcpthosts or morercpthosts\n");
}

if (! (replaceInFile($qmail_users.'assign', $old_domain, $new_domain) && 
       runCommand($qmail_bin.'qmail-newu')))
	die("Could not find domain in assign\n");

replaceInFile($vpopmail_domains.$new_domain.'/.qmail-default', $old_domain, $new_domain);

$d = dir($vpopmail_domains.$new_domain.'/');
while (false !== ($entry = $d->read())) {
	if ($entry != '..' && $entry != '.' && 
		is_dir($vpopmail_domains.$new_domain.'/'.$entry))
			replaceInFile($vpopmail_domains.$new_domain.'/'.$entry.'/.qmail', 
                    $old_domain, 
                    $new_domain);
}
$d->close();

// database changes
$mysql_configuration = file($vpopmail_etc.'vpopmail.mysql', FILE_SKIP_EMPTY_LINES);
foreach ($mysql_configuration as $line) {
	$line = trim($line);
	if ($line[0] != '#') {
		$parts = explode('|', $line);
		if (count($parts) == 5) {
			$mysql_host = $parts[0]; 
			$mysql_port = $parts[1];
			$mysql_user = $parts[2];
			$mysql_pass = $parts[3];
			$mysql_db   = $parts[4];
		}
	}
}

if (! isset($mysql_db)) 
	die("Can't get mysql configuration values in ".$vpopmail_etc."vpopmail.mysql\n");
	
$link = mysqli_connect($mysql_host, 
                       $mysql_user, 
                       $mysql_pass, 
                       $mysql_db, 
                       ($mysql_port ? $mysql_port : null));
if (!$link)
	die("Connect Error (".mysqli_connect_errno().") ".mysqli_connect_error()."\n");
	
$old_domain_table = preg_replace('/[^a-zA-Z\d\s:]/', '_', $old_domain);
$new_domain_table = preg_replace('/[^a-zA-Z\d\s:]/', '_', $new_domain);

if (mysqli_query($link, "RENAME TABLE $old_domain_table TO $new_domain_table") !== true) 
	die("Error (".mysqli_error($link)."\n");

if (mysqli_query($link, "UPDATE ".$new_domain_table." ".
                        "SET pw_dir = REPLACE(pw_dir, ".
                                              "'".$old_domain."', ".
                                              "'".$new_domain."')") !== true) 
	die("Error (".mysqli_error($link)."\n");

if (mysqli_query($link, "UPDATE dir_control ".
                        "SET domain = '".$new_domain."' ".
                        "WHERE domain = '".$old_domain."'") !== true) 
	die("Error (".mysqli_error($link)."\n");
	
if (mysqli_query($link, "UPDATE limits ".
                        "SET domain = '".$new_domain."' ".
                        "WHERE domain = '".$old_domain."'") !== true) 
	die("Error (".mysqli_error($link)."\n");

if (mysqli_query($link, "UPDATE valias ".
                        "SET domain = '".$new_domain."' ".
                        "WHERE domain = '".$old_domain."'") !== true) 
	die("Error (".mysqli_error($link)."\n");

if (mysqli_query($link, "UPDATE valias ".
                        "SET valias_line = REPLACE(valias_line, ".
                                                   "'".$old_domain."', ".
                                                   "'".$new_domain."')") !== true) 
	die("Error (".mysqli_error($link)."\n");

mysqli_close($link);

	
// roundcube (optional)
if ($roundcube_config) {
	include($roundcube_config);
	
	if (! isset($rcmail_config['db_dsnw']))
		 die("Unable to load Roundcube configuration\n");

	$tmp = explode('://', $rcmail_config['db_dsnw']);

	if (! (isset($tmp[0]) && strtolower($tmp[0]) == 'mysql'))
		die("Roundcube database is not mysql");

	$tmp = preg_replace('/[^a-zA-Z0-9]/', ' ', $tmp[1]);
	$rc_config = explode(' ', $tmp);
        $rc_config = array_filter($rc_config);

	$link = mysqli_connect($rc_config[2], $rc_config[0], $rc_config[1], $rc_config[3]);
	if (!$link)
        	die("Connect Error (".mysqli_connect_errno().") ".mysqli_connect_error()."\n");

	if (mysqli_query($link, "UPDATE ".$rcmail_config['db_table_users']." ".
                                "SET username = REPLACE(username, ".
                                                        "'".$old_domain."', ".
                                                        "'".$new_domain."')") !== true)
		die("Error (".mysqli_error($link)."\n");

	if (mysqli_query($link, "UPDATE ".$rcmail_config['db_table_identities']." ".
                                "SET email = REPLACE(email, ".
                                                     "'".$old_domain."', ".
                                                     "'".$new_domain."')") !== true)
		die("Error (".mysqli_error($link)."\n");

	if (mysqli_query($link, "UPDATE ".$rcmail_config['db_table_identities']." ".
                                "SET `reply-to` = REPLACE(`reply-to`, ".
                                                          "'".$old_domain."', ".
                                                          "'".$new_domain."')") !== true)
		die("Error (".mysqli_error($link)."\n");
}

if ($qmailctl_bin)
	exec($qmailctl_bin.' restart');

?>

2 thoughts on “Qmail/Vpopmail renaming a domain”

  1. Thanks for this great script! I slightly modified it here (https://notes.sagredo.eu/files/qmail/rename_domain.txt, please let me know if I can share there a copy) just to allow the possibility of compilation of vpopmail –enable-many-domains –enable-mysql-limts –enable-valias. In addotion the RC config variable is now $config and I had a break due to permission issues in the replaceInFile() function, which I had to write from scratch.

Leave a Reply