Annotation of portolis/portolis.cgi, revision 1.1.1.1

1.1       snw         1: #!/usr/pkg/bin/perl
                      2: 
                      3: #
                      4: # Portolis Mail Management Portal
                      5: #  Copyright (C) 2025 Coherent Logic Development LLC
                      6: #
                      7: # $Id$
                      8: #  Author: Serena Willis <snw@coherent-logic.com>
                      9: #
                     10: # Licensed AGPL-3.0
                     11: #
                     12: # $Log$
                     13: #
                     14: 
                     15: use CGI qw(:standard);
                     16: use CGI::Session;
                     17: 
                     18: $portolis_version = '0.0.4';
                     19: $session = CGI::Session->new();
                     20: 
                     21: sub init
                     22: {
                     23:     $cookie = cookie(CGISESSID => $session->id );
                     24:     print header(-cookie=>$cookie);
                     25: }
                     26: 
                     27: sub find_alias {
                     28:     my ($target, $alias) = @_;
                     29: 
                     30:     open(FH, '<', '/etc/postfix/virtual_aliases');
                     31: 
                     32:     while(<FH>) {
                     33:        my @entry = split(' ', $_);
                     34:        my $falias = $entry[0];
                     35:        my $ftarget = $entry[1];
                     36: 
                     37:        if(($ftarget eq $target) && ($falias eq $alias)) {
                     38:            close FH;
                     39:            return 1;
                     40:        }
                     41:     }
                     42: 
                     43:     close FH;
                     44:     return 0;
                     45: }
                     46: 
                     47: sub add_alias {
                     48:     my ($target, $alias) = @_;
                     49: 
                     50: }
                     51: 
                     52: sub vmail_auth {
                     53:     my ($auser, $apass) = @_;
                     54: 
                     55:     my $line = "";
                     56:     my @pwent = ();
                     57:     
                     58:     open(FH, "<", "/usr/pkg/etc/dovecot/users");
                     59: 
                     60:     while(<FH>){       
                     61:        $line = $_;
                     62:        @pwent = split(':', $line);
                     63:        if($pwent[0] eq $auser) {
                     64: 
                     65:            my $result = `/usr/pkg/bin/doveadm pw -t \'$pwent[1]\' -p \'$apass\'`;
                     66:            my @res = split(' ', $result);
                     67:            
                     68:            if($res[1] eq "(verified)") {
                     69:                close FH;
                     70:                return 1;
                     71:            }
                     72:        }
                     73:     }
                     74: 
                     75:     close FH;
                     76:     return 0;        
                     77: }
                     78: 
                     79: sub render_header {
                     80:     my ($title) = @_;
                     81: 
                     82:     my $navbar = '';
                     83:     my $html = <<"END_HDR";
                     84: <HTML>
                     85: <HEAD>
                     86: <TITLE>$title</TITLE>
                     87: </HEAD>
                     88: <BODY BGCOLOR=PaleGoldenrod>
                     89: END_HDR
                     90: 
                     91:     print $html;
                     92:     
                     93:     if($session->param("~logged-in")) {
                     94:        my $email = $session->param("~email");
                     95:        $navbar = <<"END_NAVL";
                     96: <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=logout>Log out</A>
                     97: <HR>
                     98: END_NAVL
                     99:     }
                    100:     else {
                    101:        $navbar = <<"END_NAVO";
                    102: <A HREF=https://webmail.coherent-logic.com>WebMail</A> | <A HREF=/cgi-bin/portolis.cgi?exec=login>Log In</A>
                    103: <HR>
                    104: END_NAVO
                    105:     }
                    106: 
                    107:     print $navbar;
                    108:     
                    109: }
                    110: 
                    111: sub render_footer {
                    112: 
                    113:     my $html = <<'END_FTR';
                    114: <HR>
                    115: <EM>$Id$<BR>
                    116: Copyright &copy; 2025 Coherent Logic Development LLC</EM>
                    117: </BODY>
                    118: </HTML>
                    119: END_FTR
                    120: 
                    121:     print $html;
                    122: }
                    123: 
                    124: sub list_aliases {
                    125:     my $email = $session->param("~email");
                    126:     open(FH, '<', '/etc/postfix/virtual_aliases');
                    127: 
                    128:     print "<TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>";
                    129:     print "<TR><TH>Alias</TH><TH>Actions</TH></TR>";
                    130:     while(<FH>) {
                    131:        my $ln = $_;
                    132:        my @line = split(' ', $ln);
                    133:        
                    134:        if($line[1] eq $email) {
                    135:            print "<TR><TD>$line[0]</TD><TD><A HREF=/cgi-bin/portolis.cgi?exec=delalias&id=$line[0]>Delete</A></TD></TR>";
                    136:        }
                    137:     }
                    138:     print "</TABLE>";
                    139:     print "<A HREF=/cgi-bin/portolis.cgi?exec=newalias>New Alias</A>";
                    140:     close FH;
                    141: }
                    142: 
                    143: sub exec_home {
                    144:     render_header "Home";
                    145:     my $email = $session->param("~email");
                    146:     my $html = <<"END_HOME";
                    147:     <CENTER>
                    148:     <H1>Account Overview</H1>
                    149:     <P>Your e-mail address is <B>$email</B></P>
                    150: END_HOME
                    151: 
                    152:     print $html;
                    153: 
                    154:     list_aliases();
                    155: 
                    156:     print "</CENTER>";
                    157: }
                    158: 
                    159: sub exec_newalias {
                    160:     my $html = '';
                    161: 
                    162:     if($session->param("~logged-in") == 0) {
                    163:        render_header "Access Denied";
                    164:        $html = <<"END_BADNAF";
                    165:        <H1>Access Denied</H1>
                    166:        <P>You are not logged in.</P>
                    167: END_BADNAF
                    168: 
                    169:        print $html;
                    170:        return;
                    171:     }    
                    172: 
                    173:     my $email = $session->param("~email");
                    174:     render_header "New Alias";
                    175:     
                    176:     if(request_method() eq 'GET') {
                    177:        $html = <<"END_NAF";
                    178:        <CENTER>
                    179:        <H1>Create New Alias</H1>
                    180:        <FORM METHOD=POST ACTION=/cgi-bin/portolis.cgi?exec=newalias>
                    181:        <TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>
                    182:        <TR>
                    183:        <TD><B>Alias:</B></TD>
                    184:        <TD><INPUT TYPE=TEXT NAME=alias></TD>
                    185:        </TR>
                    186:        <TR>
                    187:        <TD><B>Target:</B></TD>
                    188:        <TD>$email</TD>
                    189:        </TR>
                    190:        <TR>
                    191:        <TD COLSPAN=2 ALIGN=RIGHT>
                    192:        <INPUT TYPE=SUBMIT NAME=SUBMIT VALUE=Submit>
                    193:        </TD>
                    194:        </TR>
                    195:        </TABLE>
                    196:        </FORM>
                    197:        </CENTER>
                    198: END_NAF
                    199:     }
                    200:     else {
                    201:        my $proposed = param("alias");
                    202:        my $result = find_alias($email, $proposed);
                    203: 
                    204:        if($result == 0) {
                    205:            open(FH, '>>', '/etc/postfix/virtual_aliases');
                    206:            flock(FH, 2);
                    207:            print FH "$proposed\t$email\n";
                    208:            flock(FH, 8);
                    209:            close(FH);
                    210:            chdir '/etc/postfix';
                    211:            system '/usr/sbin/postmap virtual_aliases';
                    212:            $html = <<"END_AAOK";
                    213:            <CENTER>
                    214:            <H1>Alias Added</H1>
                    215:            <A HREF=/cgi-bin/portolis.cgi?exec=home>Home</A>
                    216:            </CENTER>
                    217: END_AAOK
                    218:        }
                    219:        else {
                    220:            $html = <<"END_AADUP";
                    221:            <CENTER>
                    222:            <H1>Duplicate Alias</H1>
                    223:            <P>The alias <B>$proposed</B> already exists</P>
                    224:            <A HREF=/cgi-bin/portolis.cgi?exec=newalias>Try Again</a>
                    225:            </CENTER>
                    226: END_AADUP
                    227:        }
                    228:     }
                    229: 
                    230:     print $html;
                    231: 
                    232: }
                    233: 
                    234: sub exec_delalias {
                    235:     my $alias = url_param('id');
                    236:     my $html = '';
                    237:     
                    238:     if($session->param("~logged-in") == 0) {
                    239:        render_header "Access Denied";
                    240:        $html = <<"END_BADDAF";
                    241:        <H1>Access Denied</H1>
                    242:        <P>You are not logged in.</P>
                    243: END_BADDAF
                    244: 
                    245:        print $html;
                    246:        return;
                    247:     }
                    248:     
                    249:     my $email = $session->param("~email");
                    250:     render_header "Delete Alias";
                    251:     
                    252:     if(request_method() eq 'GET') {
                    253:        if(find_alias($email, $alias) == 1) {
                    254:            my @lines = ();
                    255: 
                    256:            open(FH, '<', '/etc/postfix/virtual_aliases');
                    257:            flock(FH, 2);
                    258:            while(<FH>) {
                    259:                my $line = $_;
                    260:                my @tmp = split(' ', $line);
                    261:                if($tmp[0] ne $alias) {
                    262:                    push @lines, $line;
                    263:                }
                    264:                if(($tmp[0] eq $alias) && ($tmp[1] ne $email)) {
                    265:                    push @lines, $line;
                    266:                }
                    267:            }
                    268:            flock(FH, 8);
                    269:            close(FH);
                    270:            open(FH, '>', '/etc/postfix/virtual_aliases');
                    271:            flock(FH, 2);
                    272:            seek(FH, 0, 0);
                    273:            truncate(FH, 0);
                    274:            print FH @lines;
                    275:            flock(FH, 8);
                    276:            close FH;
                    277:            chdir '/etc/postfix';
                    278:            system '/usr/sbin/postmap virtual_aliases';
                    279:            $html = <<"END_DAGOOD";
                    280:            <CENTER>
                    281:            <H1>Alias Deleted</H1>
                    282:            <P><A HREF=/cgi-bin/portolis.cgi?exec=home>Home</A>
                    283:            </CENTER>
                    284: END_DAGOOD
                    285:        }
                    286:        else {
                    287:            $html = <<"END_DANOF";
                    288:            <CENTER>
                    289:            <H1>Invalid Alias</H1>
                    290:            <P><A HREF=/cgi-bin/portolis.cgi?exec=home>Home</A>
                    291:            </CENTER>
                    292: END_DANOF
                    293:        }
                    294:     }
                    295:     
                    296:     print $html;
                    297:     
                    298: }
                    299: 
                    300: sub exec_login {
                    301: 
                    302:     render_header "Login";
                    303:     
                    304:     my $html = '';
                    305:     
                    306:     if(request_method() eq 'GET') {
                    307:        $html = <<"END_LOGIN";
                    308: <CENTER>
                    309: <H1>Log In</H1>
                    310: <FORM METHOD=POST ACTION=/cgi-bin/portolis.cgi?exec=login>
                    311: <TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>
                    312: <tr>
                    313:   <td><b>E-Mail Address:</b></td>
                    314:   <td><input type="text" name="email"></td>
                    315: </tr>
                    316: <tr>
                    317:   <td><b>Password:</b></td>
                    318:   <td><input type="password" name="password"></td>
                    319: </tr>
                    320: <tr>
                    321:   <td colspan=2 align=right><input type=submit name=submit value=Submit></td>
                    322: </tr>
                    323: </TABLE>
                    324: </FORM>
                    325: </CENTER>
                    326: END_LOGIN
                    327:     }
                    328:     elsif(request_method() eq 'POST') {
                    329:        my $email = param("email");
                    330:        my $password = param("password");
                    331: 
                    332:        if(vmail_auth($email, $password) == 1) {
                    333:            $session->param("~logged-in", 1);
                    334:            $session->param("~email", $email);
                    335:            $html = <<'END_LOGINDONE';
                    336:                <script>location.replace('/cgi-bin/portolis.cgi?exec=home');</script>
                    337: 
                    338: END_LOGINDONE
                    339:        }
                    340:        else {
                    341:            $html = <<'END_ACD';
                    342: <H1>Access Denied</H1>
                    343:            <P>Invalid e-mail address or password</P>
                    344: END_ACD
                    345:        }
                    346:     }
                    347: 
                    348:     print $html;
                    349:        
                    350:        
                    351: }
                    352: 
                    353: sub exec_logout {
                    354:     $session->clear(['~logged-in', 'email']);
                    355:     my $html = <<'END_LO';
                    356:     <script>location.replace('/cgi-bin/portolis.cgi?exec=login');</script>
                    357: END_LO
                    358:     print $html;
                    359: }
                    360: 
                    361: sub exec_unknown {
                    362: 
                    363:     render_header "Error";
                    364:     
                    365:     my $html = <<'END_UNK';
                    366: <CENTER>
                    367:    <H1>Invalid Request</H1>
                    368: <P>The action you're attempting is invalid or has not yet been implemented. Please try something else.</P>
                    369: </CENTER>
                    370: END_UNK
                    371: 
                    372:     print $html;
                    373:     
                    374: }
                    375: 
                    376: sub main {    
                    377:     init;
                    378: 
                    379:     my $exec = url_param('exec');
                    380:     
                    381:     if($exec ne '') {  
                    382:        if($exec eq 'login') {
                    383:            $funcref = \&exec_login;
                    384:        }
                    385:        elsif($exec eq 'logout') {
                    386:            $funcref = \&exec_logout;
                    387:        }
                    388:        elsif($exec eq 'home') {
                    389:            $funcref = \&exec_home;
                    390:        }
                    391:        elsif($exec eq 'newalias') {
                    392:            $funcref = \&exec_newalias;
                    393:        }
                    394:        elsif($exec eq 'delalias') {
                    395:            $funcref = \&exec_delalias;
                    396:        }
                    397:        else {
                    398:            $funcref = \&exec_unknown;
                    399:        }
                    400:     }
                    401:     else {
                    402:        $funcref = \&exec_login;
                    403:     }
                    404: 
                    405:     &$funcref;
                    406:     
                    407:     render_footer;
                    408: }
                    409: 
                    410: main();
                    411: 
                    412: 

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