File:  [Coherent Logic Development] / portolis / portolis.cgi
Revision 1.5: download - view: text, annotated - select for diffs
Mon Feb 17 17:16:26 2025 UTC (6 weeks, 2 days ago) by snw
Branches: MAIN
CVS tags: HEAD
Make password changes apply to the actual dovecot users file, though hardcoded to NetBSD paths

#!/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 <snw@coherent-logic.com>
#
# 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(<FH>) {
	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(<FH>){	
	$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";
<HTML>
<HEAD>
<TITLE>$title</TITLE>
</HEAD>
<BODY BGCOLOR=PaleGoldenrod BACKGROUND=/images/linen2d.jpg>
END_HDR

    print $html;
    
    if($session->param("~logged-in")) {
	my $email = $session->param("~email");
	$navbar = <<"END_NAVL";
<A HREF=/cgi-bin/portolis.cgi?exec=home>$email</A> | <A HREF=https://webmail.coherent-logic.com>WebMail</A> | <A HREF=/cgi-bin/portolis.cgi?exec=pw>Change Password</A> | <A HREF=/cgi-bin/portolis.cgi?exec=logout>Log out</A>
<HR>
END_NAVL
    }
    else {
	$navbar = <<"END_NAVO";
<A HREF=https://webmail.coherent-logic.com>WebMail</A> | <A HREF=/cgi-bin/portolis.cgi?exec=login>Log In</A>
<HR>
END_NAVO
    }

    print $navbar;
    
}

sub render_footer {

    my $html = <<'END_FTR';
<HR>
<EM>$Id: portolis.cgi,v 1.5 2025/02/17 17:16:26 snw Exp $<BR>
Copyright &copy; 2025 Coherent Logic Development LLC</EM>
</BODY>
</HTML>
END_FTR

    print $html;
}

sub list_aliases {
    my $email = $session->param("~email");
    open(FH, '<', '/etc/postfix/virtual_aliases');

    print "<TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>";
    print "<TR><TH>Alias</TH><TH>Actions</TH></TR>";
    while(<FH>) {
	my $ln = $_;
	my @line = split(' ', $ln);
	
	if($line[1] eq $email) {
	    print "<TR><TD>$line[0]</TD><TD><A HREF=/cgi-bin/portolis.cgi?exec=delalias&id=$line[0]>Delete</A></TD></TR>";
	}
    }
    print "</TABLE>";
    print "<A HREF=/cgi-bin/portolis.cgi?exec=newalias>New Alias</A>";
    close FH;
}

sub exec_home {
    render_header "Home";
    my $email = $session->param("~email");
    my $html = <<"END_HOME";
    <CENTER>
    <H1>Account Overview</H1>
    <P>Your e-mail address is <B>$email</B></P>
END_HOME

    print $html;

    list_aliases();

    print "</CENTER>";
}

sub exec_newalias {
    my $html = '';

    if($session->param("~logged-in") == 0) {
	render_header "Access Denied";
	$html = <<"END_BADNAF";
	<H1>Access Denied</H1>
	<P>You are not logged in.</P>
END_BADNAF

	print $html;
	return;
    }    

    my $email = $session->param("~email");
    render_header "New Alias";
    
    if(request_method() eq 'GET') {
	$html = <<"END_NAF";
	<CENTER>
	<H1>Create New Alias</H1>
	<FORM METHOD=POST ACTION=/cgi-bin/portolis.cgi?exec=newalias>
	<TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>
	<TR>
	<TD><B>Alias:</B></TD>
	<TD><INPUT TYPE=TEXT NAME=alias></TD>
	</TR>
	<TR>
	<TD><B>Target:</B></TD>
	<TD>$email</TD>
	</TR>
	<TR>
	<TD COLSPAN=2 ALIGN=RIGHT>
	<INPUT TYPE=SUBMIT NAME=SUBMIT VALUE=Submit>
	</TD>
	</TR>
	</TABLE>
	</FORM>
	</CENTER>
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";
	    <CENTER>
	    <H1>Alias Added</H1>
	    <A HREF=/cgi-bin/portolis.cgi?exec=home>Home</A>
	    </CENTER>
END_AAOK
	}
	else {
	    $html = <<"END_AADUP";
	    <CENTER>
	    <H1>Duplicate Alias</H1>
	    <P>The alias <B>$proposed</B> already exists</P>
	    <A HREF=/cgi-bin/portolis.cgi?exec=newalias>Try Again</a>
	    </CENTER>
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";
	<H1>Access Denied</H1>
	<P>You are not logged in.</P>
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(<FH>) {
		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";
	    <CENTER>
	    <H1>Alias Deleted</H1>
	    <P><A HREF=/cgi-bin/portolis.cgi?exec=home>Home</A>
	    </CENTER>
END_DAGOOD
	}
	else {
	    $html = <<"END_DANOF";
	    <CENTER>
	    <H1>Invalid Alias</H1>
	    <P><A HREF=/cgi-bin/portolis.cgi?exec=home>Home</A>
	    </CENTER>
END_DANOF
	}
    }
    
    print $html;
    
}

sub exec_pw {

    if($session->param("~logged-in") == 0) {
	render_header "Access Denied";
	$html = <<"END_BADDAF";
	<H1>Access Denied</H1>
	<P>You are not logged in.</P>
END_BADDAF

	print $html;
	return;
    } 
    
    my $html = '';
    render_header "Change Password";    
    
    if(request_method() eq 'GET') {
	$html = <<"END_EPW";
	<CENTER>
	<H1>Change Password</H1>
	<FORM METHOD=POST ACTION=/cgi-bin/portolis.cgi?exec=pw>
        <TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>
	<TR>
	<TD><B>Password:</B></TD>
	<TD><INPUT TYPE=PASSWORD NAME=password></TD>
	</TR>
	<TR>
	<TD><B>Enter again to confirm:</B></TD>
	<TD><INPUT TYPE=PASSWORD NAME=password_confirm></TD>
	</TR>
	<TR>
	<TD COLSPAN=2 ALIGN=RIGHT>
	<INPUT TYPE=SUBMIT NAME=submit VALUE=Submit>
	</TD>
	</TR>
        </TABLE>
        </FORM>
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(<FH>) {
		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";
	    <CENTER>
	    <H1>Password Changed</H1>
	    <A HREF=/cgi-bin/portolis.cgi?exec=home>Home</A>
	    </CENTER>
END_PWCPOST
	    
	}
	else {
	    $html = <<"END_PWCNM";
	    <CENTER>
	    <H1>Submission Error</H1>
	    <P>Passwords did not match. <A HREF=/cgi-bin/portolis.cgi?exec=pw>Try again</A>.</P>
	    </CENTER>
END_PWCNM
	}
    }

    print $html;
    
}

sub exec_login {

    render_header "Login";
    
    my $html = '';
    
    if(request_method() eq 'GET') {
	$html = <<"END_LOGIN";
<CENTER>
<H1>Log In</H1>
<FORM METHOD=POST ACTION=/cgi-bin/portolis.cgi?exec=login>
<TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>
<tr>
  <td><b>E-Mail Address:</b></td>
  <td><input type="text" name="email"></td>
</tr>
<tr>
  <td><b>Password:</b></td>
  <td><input type="password" name="password"></td>
</tr>
<tr>
  <td colspan=2 align=right><input type=submit name=submit value=Submit></td>
</tr>
</TABLE>
</FORM>
</CENTER>
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';
	        <script>location.replace('/cgi-bin/portolis.cgi?exec=home');</script>

END_LOGINDONE
	}
	else {
	    $html = <<'END_ACD';
<H1>Access Denied</H1>
	    <P>Invalid e-mail address or password</P>
END_ACD
	}
    }

    print $html;
	
	
}

sub exec_logout {
    $session->clear(['~logged-in', 'email']);
    my $html = <<'END_LO';
    <script>location.replace('/cgi-bin/portolis.cgi?exec=login');</script>
END_LO
    print $html;
}

sub exec_unknown {

    render_header "Error";
    
    my $html = <<'END_UNK';
<CENTER>
   <H1>Invalid Request</H1>
<P>The action you're attempting is invalid or has not yet been implemented. Please try something else.</P>
</CENTER>
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();



FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>