117 lines
3 KiB
Perl
Executable file
117 lines
3 KiB
Perl
Executable file
#!/usr/bin/perl
|
|
use strict;
|
|
use warnings;
|
|
use Config;
|
|
use IO::Socket;
|
|
use IO::Select;
|
|
use IPC::Open3;
|
|
|
|
# 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: $!";
|
|
}
|
|
|
|
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;
|
|
}
|
|
open3(\*WRITE, \*READ, 0, $enc);
|
|
print WRITE "$pass";
|
|
close(WRITE);
|
|
chomp(my $hash = <READ>);
|
|
close(READ);
|
|
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);
|
|
system("$update >/dev/null");
|
|
}
|