#!/usr/bin/env perl

## v0.17

use strict;
use DBI;

my($debug) = 1;
my($debug) = 0;

my($default_realm) = "domain.tld";

my($password_blowfish_cost) = '5'; # 4-31

my($doveadm_path) = "/usr/local/bin/doveadm";

my($db_host) = '127.0.0.1';
my($db_name) = 'postfixadmin';
my($db_user) = 'postfixadmin';
my($db_pass) = '';
my($password_scheme) = '';

my($postfixadmin_local_config_filename) = "/usr/local/www/postfixadmin/config.local.php";


die("File $postfixadmin_local_config_filename not found\n") if ( ! -f $postfixadmin_local_config_filename );
if ((!$db_pass) and ( -f $postfixadmin_local_config_filename )) {
	unless (open(CONFIG, $postfixadmin_local_config_filename)) {
		print(STDERR "Couldn't open file $postfixadmin_local_config_filename: $!\n");
	} else {
		while (<CONFIG>) {
			my($option, $value) = (/^\$CONF\[['"](\S+)["']\]\s*=\s*['"](.*)["']/);
			$value =~ s/"(\S+)"/$1/;
			$value =~ s/'(\S+)'/$1/;
			if ($option eq "database_host") {
				$db_host = $value;
			} elsif ($option eq "database_user") {
				$db_user = $value;
			} elsif ($option eq "database_password") {
				$db_pass = $value;
			} elsif ($option eq "database_name") {
				$db_name = $value;
			} elsif ($option eq "encrypt") {
				$password_scheme = $value;
				$password_scheme =~ s/^dovecot://;
			}
		}
		close(CONFIG) or print(STDERR "Couldn't close file $postfixadmin_local_config_filename: $!\n");
	}
}
die("Failed to get database access settings\n") if (!$db_user or !$db_pass or !$db_name);
$db_host = '127.0.0.1' unless ($db_host);


my($user);
print('user@domain: ');
if ($ARGV[0]) {
	$user = $ARGV[0];
} else {
	chomp($user = <STDIN>);
}
$user = lc($user);
$user .= "\@$default_realm" unless ($user =~ /\@/);
print("$user\n") if ($ARGV[0]);

my($word1, $word2);
if ($ARGV[1]) {
	$word1 = $ARGV[1];
} else {
	while (! $word1) {
		print "Password: ";
		system "stty -echo";
		chomp($word1 = <STDIN>);
		system "stty echo";
		print("\n");

		print "Retype password: ";
		system "stty -echo";
		chomp($word2 = <STDIN>);
		system "stty echo";
		print("\n");

		if ($word1 ne $word2) {
			print("Sorry, passwords do not match\n");
			$word1 = '';
		}
	}
}
print("got password: $word1\n") if ($debug);


sub crypt_blowfish {
	my($pass) = shift;
	if ( -x $doveadm_path ) {
		my($encrypted_pass) = qx($doveadm_path pw -s BLF-CRYPT -r $password_blowfish_cost -p $pass);
		my($rc) = $?;
		$rc = $rc >> 8 unless ($rc == -1);
print("got doveadm output: $encrypted_pass") if ($debug);

		die("Error executing $doveadm_path: $!\n") if ($rc != 0);
		$encrypted_pass =~ s/^\{BLF-CRYPT\}//;
		chomp($encrypted_pass);
		return($encrypted_pass);
	} else {
		die("ERROR   File $doveadm_path not found\n");
	}
}


my($new_password);
if ($word1 =~ /^(\{[A-Z\d]+(-CRYPT)?\})?\$(1|2|2a|2b|2x|2y|5|6)\$.+\$/) {
	$new_password = $word1;
} else {
	my($salt) = '';
	$salt .= ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64] while (length($salt) < 8 + rand(9));

	if ($password_scheme eq 'MD5-CRYPT') {
		$new_password = crypt($word1, "\$1\$${salt}\$");
	} elsif ($password_scheme eq 'SHA256-CRYPT') {
		$new_password = crypt($word1, "\$5\$${salt}\$");
	} elsif ($password_scheme eq 'SHA512-CRYPT') {
		$new_password = crypt($word1, "\$6\$${salt}\$");
	} elsif ($password_scheme eq 'BLF-CRYPT') {
		my($salt) = '';
		$salt .= ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64] while (length($salt) < 22);
		unless ($new_password = crypt($word1, sprintf('$2y$%02d$%s', $password_blowfish_cost, $salt ))) {
			$new_password = crypt_blowfish($word1);
		}
	}
}
print("encrypted password: $new_password\n") if ($debug);


my($dsn) = "DBI:mysql:database=$db_name;$db_host;mysql_ssl=1;mysql_get_server_pubkey=1";

die("Couldn't connect to db '$db_name' on host '$db_host' as user '$db_user': ".$DBI::errstr."\n") unless (my($dbh) = DBI->connect($dsn, $db_user, $db_pass, { RaiseError => 0 }));


my($exit_code) = 0;
my($sth_get_user) = $dbh->prepare(qq{SELECT `password` FROM `mailbox` WHERE `username`=?});
unless ($sth_get_user) {
	print(STDERR "Couldn't prepare query: ".$dbh->errstr()."\n");
	$exit_code = 1;
} else {
	unless ($sth_get_user->execute($user)) {
		print(STDERR "Couldn't execute query: ".$sth_get_user->errstr()."\n");
		$exit_code = 1;
	} else {
		if (my @data = $sth_get_user->fetchrow_array()) {
			my($sth_change_passwd) = $dbh->prepare(qq{UPDATE `mailbox` SET `password`=?, `modified`=NOW() WHERE `username`=?});
			if (!$sth_change_passwd) {
				print(STDERR "Couldn't prepare query: ".$dbh->errstr()."\n");
				$exit_code = 1;
			} elsif (!$sth_change_passwd->execute($new_password, $user)) {
				print(STDERR "Couldn't execute query: ".$sth_change_passwd->errstr()."\n");
				$exit_code = 1;
			}
		} else {
			my($fullname);
			if ($ARGV[2]) {
				$fullname = $ARGV[2];
			} else {
				print("Full name: ");
				chomp($fullname = <STDIN>);
			}
			$fullname = $user unless ($fullname);

			my($sth_add_user) = $dbh->prepare(qq{INSERT `mailbox` (`username`, `password`, `name`, `maildir`, `local_part`, `domain`, `created`, `modified`) VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())});
			my($mailbox, $domain) = ($user =~ /^(.+)\@(.+)$/);
			if (!$sth_add_user) {
				print(STDERR "Couldn't prepare query: ".$dbh->errstr()."\n");
				$exit_code = 1;
			} elsif (!$sth_add_user->execute($user, $new_password, $fullname, "$domain/$mailbox/", $mailbox, $domain)) {
				print(STDERR "Couldn't execute query: ".$sth_add_user->errstr()."\n");
				$exit_code = 1;
			} else {
				my($sth_get_alias) = $dbh->prepare(qq{SELECT `goto` FROM `alias` WHERE `address`=?});
				unless ($sth_get_alias) {
					print(STDERR "Couldn't prepare query: ".$dbh->errstr()."\n");
					$exit_code = 1;
				} else {
					unless ($sth_get_alias->execute($user)) {
						print(STDERR "Couldn't execute query: ".$sth_get_alias->errstr()."\n");
						$exit_code = 1;
					} else {
						if (my @data = $sth_get_alias->fetchrow_array()) {
							print(STDERR "WARNING: alias for user ".$user." already exists and point to ".$data[0]."\n");
						} else {
							my($sth_add_alias) = $dbh->prepare(qq{INSERT `alias` (`address`, `goto`, `domain`, `created`, `modified`) VALUES (?, ?, ?, NOW(), NOW())});
							if (!$sth_add_alias) {
								print(STDERR "Couldn't prepare query: ".$dbh->errstr()."\n");
								$exit_code = 1;
							} elsif (!$sth_add_alias->execute($user, $user, $domain)) {
								print(STDERR "Couldn't execute query: ".$sth_add_alias->errstr()."\n");
								$exit_code = 1;
							}
						}
						$sth_get_alias->finish();
					}
				}
			}
		}
		$sth_get_user->finish();
	}
}
$dbh->disconnect() or die("Couldn't disconnect from db '$db_name' on host '$db_host': ".$DBI::errstr."\n");
exit($exit_code);
