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