#!/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 = ); 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"); }