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