1
0
Fork 0
smtp_auth_sync/smtp_auth_sync.pl
2020-01-19 10:22:31 +01:00

124 lines
3.2 KiB
Perl
Executable file

#!/usr/bin/perl
# Copyright (C) 2020 Marius Schellenberger
use strict;
use warnings;
use Config;
use IO::Socket;
use IO::Select;
use Sys::Syslog qw(:standard :macros);
use IPC::Run qw(start finish);
# Configs
# File to store the user:password hash mappings
my $authfile = "/etc/mail/secrets";
# UNIX Domain Socket inside the web chroot
my $sfile = "/var/www/run/smtp_auth_sync.sock";
# Absolute path of smtpctl
my $smtpctl = "/usr/sbin/smtpctl";
# Group OpenSMTPD runs with
my $smtp_group = "_smtpd";
# Group the webserver runs with
my $www_group = "www";
sub read_timeout {
my ($s, $conn) = @_;
$s->can_read(2) or return 0;
return readline($conn);
}
my ($r_uid,$r_gid) = (getpwnam("root"))[2,3];
my $s_gid = (getpwnam($smtp_group))[3] or die "group '${smtp_group}' not found: $!";
my $w_gid = (getpwnam($www_group))[3] or die "group '${www_group}' not found: $!";
my $authfiletmp = "${authfile}.tmp";
if ($Config{osname} eq "openbsd") {
eval "use OpenBSD::Pledge";
if ($Config{osvers} >= "6.6") {
eval "use OpenBSD::Unveil";
unveil($authfile, "rwc") or die "Unable to unveil: $!";
unveil($authfiletmp, "rwc") or die "Unable to unveil: $!";
unveil($sfile, "rwc") or die "Unable to unveil: $!";
unveil($smtpctl, "rx") or die "Unable to unveil: $!";
}
pledge("rpath wpath cpath fattr chown unix proc exec prot_exec") or die "Unable to pledge: $!";
}
openlog("smtp_auth_sync", "ndelay,pid", LOG_AUDIT) or die "Could not open syslog: $!";
if (-e $sfile) {
unlink $sfile or die "Could not delete socket file '${sfile}': $!";
}
my $sock = IO::Socket::UNIX->new(
Type => SOCK_STREAM(),
Local => $sfile,
Listen => 1,
) or die "Could not create socket: '$!'";
chown($r_uid, $w_gid, $sfile);
chmod(0620, $sfile);
sub cleanup {
$sock->close();
unlink $sfile;
exit 0;
}
$SIG{INT} = \&cleanup;
$SIG{TERM} = \&cleanup;
my @enc = (${smtpctl}, "encrypt");
my @update = (${smtpctl}, "update", "table", "secrets");
while (my $conn = $sock->accept()) {
my $user;
my $pass;
my $s = IO::Select->new($conn);
$user = read_timeout($s, $conn) or do { $conn->close(); next; };
chomp($user);
$pass = read_timeout($s, $conn) or do { $conn->close(); next; };
chomp($pass);
$conn->close();
if ($user eq "" or $pass eq "") {
next;
}
my $hash;
my $h = start(\@enc, \<<IN, \$hash);
$pass
IN
finish $h or next;
chomp($hash);
if ($hash eq "") {
next;
}
if (! -f $authfile) {
open(my $auth, ">", $authfile) or next;
chown($r_uid, $s_gid, $authfile);
chmod(0640, $authfile);
print $auth "${user} ${hash}\n";
close($auth);
next;
}
my $found = 0;
open(my $auth, "<", $authfile) or do { print "Could not open file '${authfile}': $!"; next; };
chown($r_uid, $s_gid, $authfile);
chmod(0640, $authfile);
open(my $authtmp, ">", $authfiletmp) or do { print "Could not open file '${authfiletmp}': $!"; next; };
chown($r_uid, $s_gid, $authfiletmp);
chmod(0640, $authfiletmp);
while (<$auth>) {
my $line = $_;
if ($line =~ /$user/) {
print $authtmp "${user} ${hash}\n";
$found = 1;
} else {
print $authtmp "${line}";
}
}
if ($found == 0) {
print $authtmp "${user} ${hash}\n";
}
close($auth);
close($authtmp);
rename($authfiletmp, $authfile);
my $out;
my $u = start(\@update, \undef, \$out);
finish $u or next;
syslog(LOG_INFO, "updated password of user ${user}");
}