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 (5 months, 2 weeks ago) by snw
Branches: MAIN
CVS tags: HEAD
Make password changes apply to the actual dovecot users file, though hardcoded to NetBSD paths

    1: #!/usr/pkg/bin/perl
    2: 
    3: #
    4: # Portolis Mail Management Portal
    5: #  Copyright (C) 2025 Coherent Logic Development LLC
    6: #
    7: # $Id: portolis.cgi,v 1.5 2025/02/17 17:16:26 snw Exp $
    8: #  Author: Serena Willis <snw@coherent-logic.com>
    9: #
   10: # Licensed AGPL-3.0
   11: #
   12: # $Log: portolis.cgi,v $
   13: # Revision 1.5  2025/02/17 17:16:26  snw
   14: # Make password changes apply to the actual dovecot users file, though hardcoded to NetBSD paths
   15: #
   16: # Revision 1.4  2025/02/17 15:55:49  snw
   17: # Fix password change form action URL
   18: #
   19: # Revision 1.3  2025/02/17 15:54:19  snw
   20: # Add password change capability
   21: #
   22: # Revision 1.2  2025/02/16 03:14:50  snw
   23: # Add background image
   24: #
   25: # Revision 1.1.1.1  2025/02/15 23:09:22  snw
   26: # Initial commit
   27: #
   28: #
   29: 
   30: use CGI qw(:standard);
   31: use CGI::Session;
   32: 
   33: $session = CGI::Session->new();
   34: 
   35: sub init
   36: {
   37:     $cookie = cookie(CGISESSID => $session->id );
   38:     print header(-cookie=>$cookie);
   39: }
   40: 
   41: sub find_alias {
   42:     my ($target, $alias) = @_;
   43: 
   44:     open(FH, '<', '/etc/postfix/virtual_aliases');
   45: 
   46:     while(<FH>) {
   47: 	my @entry = split(' ', $_);
   48: 	my $falias = $entry[0];
   49: 	my $ftarget = $entry[1];
   50: 
   51: 	if(($ftarget eq $target) && ($falias eq $alias)) {
   52: 	    close FH;
   53: 	    return 1;
   54: 	}
   55:     }
   56: 
   57:     close FH;
   58:     return 0;
   59: }
   60: 
   61: sub add_alias {
   62:     my ($target, $alias) = @_;
   63: 
   64: }
   65: 
   66: sub vmail_auth {
   67:     my ($auser, $apass) = @_;
   68: 
   69:     my $line = "";
   70:     my @pwent = ();
   71:     
   72:     open(FH, "<", "/usr/pkg/etc/dovecot/users");
   73: 
   74:     while(<FH>){	
   75: 	$line = $_;
   76: 	@pwent = split(':', $line);
   77: 	if($pwent[0] eq $auser) {
   78: 
   79: 	    my $result = `/usr/pkg/bin/doveadm pw -t \'$pwent[1]\' -p \'$apass\'`;
   80: 	    my @res = split(' ', $result);
   81: 	    
   82: 	    if($res[1] eq "(verified)") {
   83: 		close FH;
   84: 		return 1;
   85: 	    }
   86: 	}
   87:     }
   88: 
   89:     close FH;
   90:     return 0;        
   91: }
   92: 
   93: sub render_header {
   94:     my ($title) = @_;
   95: 
   96:     my $navbar = '';
   97:     my $html = <<"END_HDR";
   98: <HTML>
   99: <HEAD>
  100: <TITLE>$title</TITLE>
  101: </HEAD>
  102: <BODY BGCOLOR=PaleGoldenrod BACKGROUND=/images/linen2d.jpg>
  103: END_HDR
  104: 
  105:     print $html;
  106:     
  107:     if($session->param("~logged-in")) {
  108: 	my $email = $session->param("~email");
  109: 	$navbar = <<"END_NAVL";
  110: <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>
  111: <HR>
  112: END_NAVL
  113:     }
  114:     else {
  115: 	$navbar = <<"END_NAVO";
  116: <A HREF=https://webmail.coherent-logic.com>WebMail</A> | <A HREF=/cgi-bin/portolis.cgi?exec=login>Log In</A>
  117: <HR>
  118: END_NAVO
  119:     }
  120: 
  121:     print $navbar;
  122:     
  123: }
  124: 
  125: sub render_footer {
  126: 
  127:     my $html = <<'END_FTR';
  128: <HR>
  129: <EM>$Id: portolis.cgi,v 1.5 2025/02/17 17:16:26 snw Exp $<BR>
  130: Copyright &copy; 2025 Coherent Logic Development LLC</EM>
  131: </BODY>
  132: </HTML>
  133: END_FTR
  134: 
  135:     print $html;
  136: }
  137: 
  138: sub list_aliases {
  139:     my $email = $session->param("~email");
  140:     open(FH, '<', '/etc/postfix/virtual_aliases');
  141: 
  142:     print "<TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>";
  143:     print "<TR><TH>Alias</TH><TH>Actions</TH></TR>";
  144:     while(<FH>) {
  145: 	my $ln = $_;
  146: 	my @line = split(' ', $ln);
  147: 	
  148: 	if($line[1] eq $email) {
  149: 	    print "<TR><TD>$line[0]</TD><TD><A HREF=/cgi-bin/portolis.cgi?exec=delalias&id=$line[0]>Delete</A></TD></TR>";
  150: 	}
  151:     }
  152:     print "</TABLE>";
  153:     print "<A HREF=/cgi-bin/portolis.cgi?exec=newalias>New Alias</A>";
  154:     close FH;
  155: }
  156: 
  157: sub exec_home {
  158:     render_header "Home";
  159:     my $email = $session->param("~email");
  160:     my $html = <<"END_HOME";
  161:     <CENTER>
  162:     <H1>Account Overview</H1>
  163:     <P>Your e-mail address is <B>$email</B></P>
  164: END_HOME
  165: 
  166:     print $html;
  167: 
  168:     list_aliases();
  169: 
  170:     print "</CENTER>";
  171: }
  172: 
  173: sub exec_newalias {
  174:     my $html = '';
  175: 
  176:     if($session->param("~logged-in") == 0) {
  177: 	render_header "Access Denied";
  178: 	$html = <<"END_BADNAF";
  179: 	<H1>Access Denied</H1>
  180: 	<P>You are not logged in.</P>
  181: END_BADNAF
  182: 
  183: 	print $html;
  184: 	return;
  185:     }    
  186: 
  187:     my $email = $session->param("~email");
  188:     render_header "New Alias";
  189:     
  190:     if(request_method() eq 'GET') {
  191: 	$html = <<"END_NAF";
  192: 	<CENTER>
  193: 	<H1>Create New Alias</H1>
  194: 	<FORM METHOD=POST ACTION=/cgi-bin/portolis.cgi?exec=newalias>
  195: 	<TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>
  196: 	<TR>
  197: 	<TD><B>Alias:</B></TD>
  198: 	<TD><INPUT TYPE=TEXT NAME=alias></TD>
  199: 	</TR>
  200: 	<TR>
  201: 	<TD><B>Target:</B></TD>
  202: 	<TD>$email</TD>
  203: 	</TR>
  204: 	<TR>
  205: 	<TD COLSPAN=2 ALIGN=RIGHT>
  206: 	<INPUT TYPE=SUBMIT NAME=SUBMIT VALUE=Submit>
  207: 	</TD>
  208: 	</TR>
  209: 	</TABLE>
  210: 	</FORM>
  211: 	</CENTER>
  212: END_NAF
  213:     }
  214:     else {
  215: 	my $proposed = param("alias");
  216: 	my $result = find_alias($email, $proposed);
  217: 
  218: 	if($result == 0) {
  219: 	    open(FH, '>>', '/etc/postfix/virtual_aliases');
  220: 	    flock(FH, 2);
  221: 	    print FH "$proposed\t$email\n";
  222: 	    flock(FH, 8);
  223: 	    close(FH);
  224: 	    chdir '/etc/postfix';
  225: 	    system '/usr/sbin/postmap virtual_aliases';
  226: 	    $html = <<"END_AAOK";
  227: 	    <CENTER>
  228: 	    <H1>Alias Added</H1>
  229: 	    <A HREF=/cgi-bin/portolis.cgi?exec=home>Home</A>
  230: 	    </CENTER>
  231: END_AAOK
  232: 	}
  233: 	else {
  234: 	    $html = <<"END_AADUP";
  235: 	    <CENTER>
  236: 	    <H1>Duplicate Alias</H1>
  237: 	    <P>The alias <B>$proposed</B> already exists</P>
  238: 	    <A HREF=/cgi-bin/portolis.cgi?exec=newalias>Try Again</a>
  239: 	    </CENTER>
  240: END_AADUP
  241: 	}
  242:     }
  243: 
  244:     print $html;
  245: 
  246: }
  247: 
  248: sub exec_delalias {
  249:     my $alias = url_param('id');
  250:     my $html = '';
  251:     
  252:     if($session->param("~logged-in") == 0) {
  253: 	render_header "Access Denied";
  254: 	$html = <<"END_BADDAF";
  255: 	<H1>Access Denied</H1>
  256: 	<P>You are not logged in.</P>
  257: END_BADDAF
  258: 
  259: 	print $html;
  260: 	return;
  261:     }
  262:     
  263:     my $email = $session->param("~email");
  264:     render_header "Delete Alias";
  265:     
  266:     if(request_method() eq 'GET') {
  267: 	if(find_alias($email, $alias) == 1) {
  268: 	    my @lines = ();
  269: 
  270: 	    open(FH, '<', '/etc/postfix/virtual_aliases');
  271: 	    flock(FH, 2);
  272: 	    while(<FH>) {
  273: 		my $line = $_;
  274: 		my @tmp = split(' ', $line);
  275: 		if($tmp[0] ne $alias) {
  276: 		    push @lines, $line;
  277: 		}
  278: 		if(($tmp[0] eq $alias) && ($tmp[1] ne $email)) {
  279: 		    push @lines, $line;
  280: 		}
  281: 	    }
  282: 	    flock(FH, 8);
  283: 	    close(FH);
  284: 	    open(FH, '>', '/etc/postfix/virtual_aliases');
  285: 	    flock(FH, 2);
  286: 	    seek(FH, 0, 0);
  287: 	    truncate(FH, 0);
  288: 	    print FH @lines;
  289: 	    flock(FH, 8);
  290: 	    close FH;
  291: 	    chdir '/etc/postfix';
  292: 	    system '/usr/sbin/postmap virtual_aliases';
  293: 	    $html = <<"END_DAGOOD";
  294: 	    <CENTER>
  295: 	    <H1>Alias Deleted</H1>
  296: 	    <P><A HREF=/cgi-bin/portolis.cgi?exec=home>Home</A>
  297: 	    </CENTER>
  298: END_DAGOOD
  299: 	}
  300: 	else {
  301: 	    $html = <<"END_DANOF";
  302: 	    <CENTER>
  303: 	    <H1>Invalid Alias</H1>
  304: 	    <P><A HREF=/cgi-bin/portolis.cgi?exec=home>Home</A>
  305: 	    </CENTER>
  306: END_DANOF
  307: 	}
  308:     }
  309:     
  310:     print $html;
  311:     
  312: }
  313: 
  314: sub exec_pw {
  315: 
  316:     if($session->param("~logged-in") == 0) {
  317: 	render_header "Access Denied";
  318: 	$html = <<"END_BADDAF";
  319: 	<H1>Access Denied</H1>
  320: 	<P>You are not logged in.</P>
  321: END_BADDAF
  322: 
  323: 	print $html;
  324: 	return;
  325:     } 
  326:     
  327:     my $html = '';
  328:     render_header "Change Password";    
  329:     
  330:     if(request_method() eq 'GET') {
  331: 	$html = <<"END_EPW";
  332: 	<CENTER>
  333: 	<H1>Change Password</H1>
  334: 	<FORM METHOD=POST ACTION=/cgi-bin/portolis.cgi?exec=pw>
  335:         <TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>
  336: 	<TR>
  337: 	<TD><B>Password:</B></TD>
  338: 	<TD><INPUT TYPE=PASSWORD NAME=password></TD>
  339: 	</TR>
  340: 	<TR>
  341: 	<TD><B>Enter again to confirm:</B></TD>
  342: 	<TD><INPUT TYPE=PASSWORD NAME=password_confirm></TD>
  343: 	</TR>
  344: 	<TR>
  345: 	<TD COLSPAN=2 ALIGN=RIGHT>
  346: 	<INPUT TYPE=SUBMIT NAME=submit VALUE=Submit>
  347: 	</TD>
  348: 	</TR>
  349:         </TABLE>
  350:         </FORM>
  351: END_EPW
  352:     }
  353:     elsif(request_method() eq 'POST') {
  354: 	my $password = param('password');
  355: 	my $password_confirm = param('password_confirm');
  356: 	my $email = $session->param("~email");
  357: 	my @parts = split('@', $email);
  358: 	my $localpart = $parts[0];
  359: 	my $domainpart = $parts[1];
  360: 	if($password eq $password_confirm) {
  361: 	    my $hash = `/usr/pkg/bin/doveadm pw -p \"$password\"`;
  362: 	    $hash =~s/\R//g;
  363: 	    my $str = "$email:$hash:1007:1007:/home/maildeliverer/$domainpart/$localpart\n";
  364: 
  365: 	    open(FH, '<', '/usr/pkg/etc/dovecot/users');	    
  366: 	    flock FH, 2;
  367: 
  368: 	    my @lines = ();
  369: 	    
  370: 	    while(<FH>) {
  371: 		my $line = $_;
  372: 		my @pwent = split(':', $line);
  373: 		if($pwent[0] ne $email) {
  374: 		    push @lines, $line;
  375: 		}
  376: 	    }
  377: 	    push @lines, $str;
  378: 
  379: 	    flock FH, 8;
  380: 	    close FH;
  381: 
  382: 	    open(FH, '>', '/usr/pkg/etc/dovecot/users');
  383: 	    flock(FH, 2);
  384: 	    seek(FH, 0, 0);
  385: 	    truncate(FH, 0);
  386: 	    print FH @lines;
  387: 	    flock FH, 8;
  388: 	    close(FH);	    
  389: 	    $html = <<"END_PWCPOST";
  390: 	    <CENTER>
  391: 	    <H1>Password Changed</H1>
  392: 	    <A HREF=/cgi-bin/portolis.cgi?exec=home>Home</A>
  393: 	    </CENTER>
  394: END_PWCPOST
  395: 	    
  396: 	}
  397: 	else {
  398: 	    $html = <<"END_PWCNM";
  399: 	    <CENTER>
  400: 	    <H1>Submission Error</H1>
  401: 	    <P>Passwords did not match. <A HREF=/cgi-bin/portolis.cgi?exec=pw>Try again</A>.</P>
  402: 	    </CENTER>
  403: END_PWCNM
  404: 	}
  405:     }
  406: 
  407:     print $html;
  408:     
  409: }
  410: 
  411: sub exec_login {
  412: 
  413:     render_header "Login";
  414:     
  415:     my $html = '';
  416:     
  417:     if(request_method() eq 'GET') {
  418: 	$html = <<"END_LOGIN";
  419: <CENTER>
  420: <H1>Log In</H1>
  421: <FORM METHOD=POST ACTION=/cgi-bin/portolis.cgi?exec=login>
  422: <TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>
  423: <tr>
  424:   <td><b>E-Mail Address:</b></td>
  425:   <td><input type="text" name="email"></td>
  426: </tr>
  427: <tr>
  428:   <td><b>Password:</b></td>
  429:   <td><input type="password" name="password"></td>
  430: </tr>
  431: <tr>
  432:   <td colspan=2 align=right><input type=submit name=submit value=Submit></td>
  433: </tr>
  434: </TABLE>
  435: </FORM>
  436: </CENTER>
  437: END_LOGIN
  438:     }
  439:     elsif(request_method() eq 'POST') {
  440: 	my $email = param("email");
  441: 	my $password = param("password");
  442: 
  443: 	if(vmail_auth($email, $password) == 1) {
  444: 	    $session->param("~logged-in", 1);
  445: 	    $session->param("~email", $email);
  446: 	    $html = <<'END_LOGINDONE';
  447: 	        <script>location.replace('/cgi-bin/portolis.cgi?exec=home');</script>
  448: 
  449: END_LOGINDONE
  450: 	}
  451: 	else {
  452: 	    $html = <<'END_ACD';
  453: <H1>Access Denied</H1>
  454: 	    <P>Invalid e-mail address or password</P>
  455: END_ACD
  456: 	}
  457:     }
  458: 
  459:     print $html;
  460: 	
  461: 	
  462: }
  463: 
  464: sub exec_logout {
  465:     $session->clear(['~logged-in', 'email']);
  466:     my $html = <<'END_LO';
  467:     <script>location.replace('/cgi-bin/portolis.cgi?exec=login');</script>
  468: END_LO
  469:     print $html;
  470: }
  471: 
  472: sub exec_unknown {
  473: 
  474:     render_header "Error";
  475:     
  476:     my $html = <<'END_UNK';
  477: <CENTER>
  478:    <H1>Invalid Request</H1>
  479: <P>The action you're attempting is invalid or has not yet been implemented. Please try something else.</P>
  480: </CENTER>
  481: END_UNK
  482: 
  483:     print $html;
  484:     
  485: }
  486: 
  487: sub main {    
  488:     init;
  489: 
  490:     my $exec = url_param('exec');
  491:     
  492:     if($exec ne '') {	
  493: 	if($exec eq 'login') {
  494: 	    $funcref = \&exec_login;
  495: 	}
  496: 	elsif($exec eq 'logout') {
  497: 	    $funcref = \&exec_logout;
  498: 	}
  499: 	elsif($exec eq 'home') {
  500: 	    $funcref = \&exec_home;
  501: 	}
  502: 	elsif($exec eq 'newalias') {
  503: 	    $funcref = \&exec_newalias;
  504: 	}
  505: 	elsif($exec eq 'delalias') {
  506: 	    $funcref = \&exec_delalias;
  507: 	}
  508: 	elsif($exec eq 'pw') {
  509: 	    $funcref = \&exec_pw;
  510: 	}
  511: 	else {
  512: 	    $funcref = \&exec_unknown;
  513: 	}
  514:     }
  515:     else {
  516: 	$funcref = \&exec_login;
  517:     }
  518: 
  519:     &$funcref;
  520:     
  521:     render_footer;
  522: }
  523: 
  524: main();
  525: 
  526: 

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