Eduardo M KALINOWSKI wrote:
I'm using in my server dovecot and Horde to provide IMAP and Webmail for virtual users. Their password are stored in a passwd file, since the setup is quite simple and small. I'd like to use the Horde module sork-passwd to allow changing of the passwords. Apparently, this can be done for the case of passwd file by using an expect script, or simply calling a program that receives the data.
It should not be hard to write a Perl (or Python, or whatever) script that does that. I'm just asking if someone has already done that and is willing to share the solution. OK, since apparently nobody needed that before, here's a quick and dirty solution. There is lots of room for improvement, but it Works For Me (and My Setup). First, this goes in the passwd's backends.php:
$backends['dovecotpasswdfile'] = array( 'name' => 'localhost', 'password policy' => array(), 'driver' => 'procopen', 'params' => array( 'program' => '/usr/local/bin/chdovecotpw' ) );
The script is attached. A big problem is that I had to pass the password in the command-line, because dovecotpw seems to play tricks in order to read the input, simply writing the password twice to its STDIN does not seem to work.
-- Não deixe a escola atrapalhar seus estudos.
Eduardo M KALINOWSKI eduardo@kalinowski.com.br http://move.to/hpkb
#!/usr/bin/perl -w use strict; use warnings;
use Time::HiRes qw(usleep);
### Settings # Only %u, %n & %d are supported in PASSWORD_FILE and USER_FORMAT! # Location of the password file my $PASSWORD_FILE = '/home/vmail/%d/db/passwd'; # Leave undefined if users are listed in the passwd file in the same # way they are received; or specify the format they appear in the passwd # file. #my $USER_FORMAT = undef; my $USER_FORMAT = '%n'; # Location of dovecotpw binary my $DOVECOTPW = '/usr/sbin/dovecotpw'; # Hash method to use # Methods which require the username are not supported my $METHOD = 'CRAM-MD5'; # Place to store logs my $LOGFILE = '/var/log/horde/passwd.log';
### Exit codes: # 1: Not all parameters were specified # 2: Could not open pipe to dovecotpw # 3: Error reading hash from dovecotpw pipe # 4: Could not open passwd file # 5: User not found # 6: Could not open temporary passwd file for writing # 7: Error in final rename # 100: Wrong password informed
sub execute { my ($inputuser, $oldpassword, $newpassword) = read_input();
my ($user, $passwdlocation) = substitute_variables($inputuser);
check_password($user, $oldpassword, $passwdlocation);
write_new_password($user, $newpassword, $passwdlocation);
}
sub read_input { my $username = <STDIN>; my $oldpassword = <STDIN>; my $newpassword = <STDIN>; if (!$username || !$oldpassword || !$newpassword) { print "Internal error\n"; print $::fLog "ERROR: Did not receive all parameters\n"; print $::fLog "--- Ended in error! ---\n"; close($::fLog); exit 1; }
chomp($username);
chomp($oldpassword);
chomp($newpassword);
return ($username, $oldpassword, $newpassword);
}
sub generate_hash { my ($password, $method) = @_;
my $pwpipe;
my $result = open($pwpipe, "-|");
if (!defined($result)) {
print "Internal error\n";
print $::fLog "ERROR: Could not open pipe to dovecotpw\n";
print $::fLog "--- Ended in error! ---\n";
close($::fLog);
exit 2;
}
if ($result == 0) {
if (!exec($DOVECOTPW, "-s", $method, "-p", $password)) {
print "Internal error\n";
print $::fLog "ERROR: Could not open pipe to dovecotpw\n";
print $::fLog "--- Ended in error! ---\n";
close($::fLog);
exit 2;
}
}
my $hash = <$pwpipe>;
if (!$hash) {
print "Internal error\n";
print $::fLog "ERROR: Error reading hash from dovecotpw pipe\n";
print $::fLog "--- Ended in error! ---\n";
close($::fLog);
exit 3;
}
chomp($hash);
return $hash;
}
sub get_hash { my ($user, $passwdlocation) = @_;
my $pwfile;
if (!open($pwfile, '<', $passwdlocation)) {
print "Internal error\n";
print $::fLog "ERROR: Could not open passwd file\n";
print $::fLog "--- Ended in error! ---\n";
close($::fLog);
exit 4;
}
my ($hash, $method);
while (<$pwfile>) {
chomp;
my ($thisuser, $thishash) = split(/:/);
if ($thisuser eq $user) {
$hash = $thishash;
last;
}
}
close($pwfile);
if (!$hash) {
print "Internal error\n";
print $::fLog "ERROR: User not found\n";
print $::fLog "--- Ended in error! ---\n";
close($::fLog);
exit 5;
}
if ($hash =~ /^\{([-a-zA-Z0-9]+)\}/) {
$method = $1;
} else {
$method = 'PLAIN';
}
return ($hash, $method);
}
sub substitute_variables { my ($inputuser) = @_;
my ($localpart, $domain);
if ($inputuser =~ /^(.*)@(.*)$/) {
$localpart = $1;
$domain = $2;
} else {
$localpart = $inputuser;
$domain = '';
}
my $user;
if (defined $USER_FORMAT) {
$user = $USER_FORMAT;
$user =~ s/%u/$inputuser/;
$user =~ s/%n/$localpart/;
$user =~ s/%d/$domain/;
} else {
$user = $inputuser;
}
my $passwdfile = $PASSWORD_FILE;
$passwdfile =~ s/%u/$inputuser/;
$passwdfile =~ s/%n/$localpart/;
$passwdfile =~ s/%d/$domain/;
return ($user, $passwdfile);
}
sub check_password { my ($user, $oldpassword, $passwdlocation) = @_;
my ($oldhash, $oldmethod) = get_hash($user, $passwdlocation);
if ($oldhash ne generate_hash($oldpassword, $oldmethod)) {
print "Wrong password\n";
print $::fLog "ERROR: User provided wrong password\n";
print $::fLog "--- End successfully, no change ---\n";
close($::fLog);
exit 100;
}
}
sub write_new_password { my ($user, $newpassword, $passwdlocation) = @_;
my $tempfile = $passwdlocation . ".new";
# Dirty locking mechanism. Will wait forever if some process
# leaves the .new file hanging around.
while (1) {
last if (! -f $tempfile);
usleep(int(rand(2000)));
}
my $pwfile_old;
if (!open($pwfile_old, '<', $passwdlocation)) {
print "Internal error\n";
print $::fLog "ERROR: Could not open passwd file\n";
print $::fLog "--- Ended in error! ---\n";
close($::fLog);
exit 4;
}
my $pwfile_new;
if (!open($pwfile_new, '>', $tempfile)) {
print "Internal error\n";
print $::fLog "ERROR: Could not open temporary passwd file for writing\n";
print $::fLog "--- Ended in error! ---\n";
close($::fLog);
exit 6;
}
while (<$pwfile_old>) {
chomp;
if (/^$user:/) {
print $pwfile_new "$user:" . generate_hash($newpassword, $METHOD) . "\n";
} else {
print $pwfile_new "$_\n";
}
}
close($pwfile_new);
close($pwfile_old);
if (!rename($tempfile, $passwdlocation)) {
unlink($tempfile);
print "Internal error\n";
print $::fLog "ERROR: Error in final rename\n";
print $::fLog "--- Ended in error! ---\n";
close($::fLog);
exit 7;
}
print $::fLog "Changed password for $user in $passwdlocation\n";
}
### Real work stars here our($fLog); open($fLog, '>>', $LOGFILE); print $fLog "--- Started at " . localtime() . " ---\n";
execute();
print $fLog "--- Ended successfully, changes were made ---\n"; close($fLog);
exit 0;