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