#!/usr/pkg/bin/perl # # Portolis Mail Management Portal # Copyright (C) 2025 Coherent Logic Development LLC # # $Id: portolis.cgi,v 1.5 2025/02/17 17:16:26 snw Exp $ # Author: Serena Willis # # Licensed AGPL-3.0 # # $Log: portolis.cgi,v $ # Revision 1.5 2025/02/17 17:16:26 snw # Make password changes apply to the actual dovecot users file, though hardcoded to NetBSD paths # # Revision 1.4 2025/02/17 15:55:49 snw # Fix password change form action URL # # Revision 1.3 2025/02/17 15:54:19 snw # Add password change capability # # Revision 1.2 2025/02/16 03:14:50 snw # Add background image # # Revision 1.1.1.1 2025/02/15 23:09:22 snw # Initial commit # # use CGI qw(:standard); use CGI::Session; $session = CGI::Session->new(); sub init { $cookie = cookie(CGISESSID => $session->id ); print header(-cookie=>$cookie); } sub find_alias { my ($target, $alias) = @_; open(FH, '<', '/etc/postfix/virtual_aliases'); while() { my @entry = split(' ', $_); my $falias = $entry[0]; my $ftarget = $entry[1]; if(($ftarget eq $target) && ($falias eq $alias)) { close FH; return 1; } } close FH; return 0; } sub add_alias { my ($target, $alias) = @_; } sub vmail_auth { my ($auser, $apass) = @_; my $line = ""; my @pwent = (); open(FH, "<", "/usr/pkg/etc/dovecot/users"); while(){ $line = $_; @pwent = split(':', $line); if($pwent[0] eq $auser) { my $result = `/usr/pkg/bin/doveadm pw -t \'$pwent[1]\' -p \'$apass\'`; my @res = split(' ', $result); if($res[1] eq "(verified)") { close FH; return 1; } } } close FH; return 0; } sub render_header { my ($title) = @_; my $navbar = ''; my $html = <<"END_HDR"; $title END_HDR print $html; if($session->param("~logged-in")) { my $email = $session->param("~email"); $navbar = <<"END_NAVL"; $email | WebMail | Change Password | Log out
END_NAVL } else { $navbar = <<"END_NAVO"; WebMail | Log In
END_NAVO } print $navbar; } sub render_footer { my $html = <<'END_FTR';
$Id: portolis.cgi,v 1.5 2025/02/17 17:16:26 snw Exp $
Copyright © 2025 Coherent Logic Development LLC
END_FTR print $html; } sub list_aliases { my $email = $session->param("~email"); open(FH, '<', '/etc/postfix/virtual_aliases'); print ""; print ""; while() { my $ln = $_; my @line = split(' ', $ln); if($line[1] eq $email) { print ""; } } print "
AliasActions
$line[0]Delete
"; print "New Alias"; close FH; } sub exec_home { render_header "Home"; my $email = $session->param("~email"); my $html = <<"END_HOME";

Account Overview

Your e-mail address is $email

END_HOME print $html; list_aliases(); print "
"; } sub exec_newalias { my $html = ''; if($session->param("~logged-in") == 0) { render_header "Access Denied"; $html = <<"END_BADNAF";

Access Denied

You are not logged in.

END_BADNAF print $html; return; } my $email = $session->param("~email"); render_header "New Alias"; if(request_method() eq 'GET') { $html = <<"END_NAF";

Create New Alias

Alias:
Target: $email
END_NAF } else { my $proposed = param("alias"); my $result = find_alias($email, $proposed); if($result == 0) { open(FH, '>>', '/etc/postfix/virtual_aliases'); flock(FH, 2); print FH "$proposed\t$email\n"; flock(FH, 8); close(FH); chdir '/etc/postfix'; system '/usr/sbin/postmap virtual_aliases'; $html = <<"END_AAOK";

Alias Added

Home
END_AAOK } else { $html = <<"END_AADUP";

Duplicate Alias

The alias $proposed already exists

Try Again
END_AADUP } } print $html; } sub exec_delalias { my $alias = url_param('id'); my $html = ''; if($session->param("~logged-in") == 0) { render_header "Access Denied"; $html = <<"END_BADDAF";

Access Denied

You are not logged in.

END_BADDAF print $html; return; } my $email = $session->param("~email"); render_header "Delete Alias"; if(request_method() eq 'GET') { if(find_alias($email, $alias) == 1) { my @lines = (); open(FH, '<', '/etc/postfix/virtual_aliases'); flock(FH, 2); while() { my $line = $_; my @tmp = split(' ', $line); if($tmp[0] ne $alias) { push @lines, $line; } if(($tmp[0] eq $alias) && ($tmp[1] ne $email)) { push @lines, $line; } } flock(FH, 8); close(FH); open(FH, '>', '/etc/postfix/virtual_aliases'); flock(FH, 2); seek(FH, 0, 0); truncate(FH, 0); print FH @lines; flock(FH, 8); close FH; chdir '/etc/postfix'; system '/usr/sbin/postmap virtual_aliases'; $html = <<"END_DAGOOD";

Alias Deleted

Home

END_DAGOOD } else { $html = <<"END_DANOF";

Invalid Alias

Home

END_DANOF } } print $html; } sub exec_pw { if($session->param("~logged-in") == 0) { render_header "Access Denied"; $html = <<"END_BADDAF";

Access Denied

You are not logged in.

END_BADDAF print $html; return; } my $html = ''; render_header "Change Password"; if(request_method() eq 'GET') { $html = <<"END_EPW";

Change Password

Password:
Enter again to confirm:
END_EPW } elsif(request_method() eq 'POST') { my $password = param('password'); my $password_confirm = param('password_confirm'); my $email = $session->param("~email"); my @parts = split('@', $email); my $localpart = $parts[0]; my $domainpart = $parts[1]; if($password eq $password_confirm) { my $hash = `/usr/pkg/bin/doveadm pw -p \"$password\"`; $hash =~s/\R//g; my $str = "$email:$hash:1007:1007:/home/maildeliverer/$domainpart/$localpart\n"; open(FH, '<', '/usr/pkg/etc/dovecot/users'); flock FH, 2; my @lines = (); while() { my $line = $_; my @pwent = split(':', $line); if($pwent[0] ne $email) { push @lines, $line; } } push @lines, $str; flock FH, 8; close FH; open(FH, '>', '/usr/pkg/etc/dovecot/users'); flock(FH, 2); seek(FH, 0, 0); truncate(FH, 0); print FH @lines; flock FH, 8; close(FH); $html = <<"END_PWCPOST";

Password Changed

Home
END_PWCPOST } else { $html = <<"END_PWCNM";

Submission Error

Passwords did not match. Try again.

END_PWCNM } } print $html; } sub exec_login { render_header "Login"; my $html = ''; if(request_method() eq 'GET') { $html = <<"END_LOGIN";

Log In

E-Mail Address:
Password:
END_LOGIN } elsif(request_method() eq 'POST') { my $email = param("email"); my $password = param("password"); if(vmail_auth($email, $password) == 1) { $session->param("~logged-in", 1); $session->param("~email", $email); $html = <<'END_LOGINDONE'; END_LOGINDONE } else { $html = <<'END_ACD';

Access Denied

Invalid e-mail address or password

END_ACD } } print $html; } sub exec_logout { $session->clear(['~logged-in', 'email']); my $html = <<'END_LO'; END_LO print $html; } sub exec_unknown { render_header "Error"; my $html = <<'END_UNK';

Invalid Request

The action you're attempting is invalid or has not yet been implemented. Please try something else.

END_UNK print $html; } sub main { init; my $exec = url_param('exec'); if($exec ne '') { if($exec eq 'login') { $funcref = \&exec_login; } elsif($exec eq 'logout') { $funcref = \&exec_logout; } elsif($exec eq 'home') { $funcref = \&exec_home; } elsif($exec eq 'newalias') { $funcref = \&exec_newalias; } elsif($exec eq 'delalias') { $funcref = \&exec_delalias; } elsif($exec eq 'pw') { $funcref = \&exec_pw; } else { $funcref = \&exec_unknown; } } else { $funcref = \&exec_login; } &$funcref; render_footer; } main();