Initial commit
[clearscm.git] / maps / bin / MAPS.pm
1 #!/usr/bin/perl
2 #################################################################################
3 #
4 # File:         $RCSfile: MAPS.pm,v $
5 # Revision:     $Revision: 1.1 $
6 # Description:  Main module for Mail Authentication and Permission System (MAPS)
7 # Author:       Andrew@DeFaria.com
8 # Created:      Fri Nov 29 14:17:21  2002
9 # Modified:     $Date: 2013/06/12 14:05:47 $
10 # Language:     perl
11 #
12 # (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved.
13 #
14 ################################################################################
15 package MAPS;
16
17 use strict;
18
19 use FindBin;
20
21 use MAPSDB;
22 use MAPSLog;
23 use MAPSFile;
24 use MAPSUtil;
25 use MIME::Entity;
26
27 use vars qw (@ISA @EXPORT);
28 use Exporter;
29
30 @ISA = qw (Exporter);
31
32 @EXPORT = qw (
33   Add2Blacklist
34   Add2Nulllist
35   Add2Whitelist
36   AddEmail
37   AddList
38   AddUser
39   AddUserOptions
40   Blacklist
41   CleanEmail
42   CleanLog
43   CleanList
44   CountMsg
45   Decrypt
46   DeleteEmail
47   DeleteList
48   DeleteLog
49   Encrypt
50   FindEmail
51   FindList
52   FindLog
53   FindUser
54   ForwardMsg
55   GetContext
56   GetEmail
57   GetList
58   GetLog
59   GetUser
60   GetUserOptions
61   ListLog
62   ListUsers
63   Login
64   Nulllist
65   OnBlacklist
66   OnNulllist
67   OnWhitelist
68   OptimizeDB
69   ReadMsg
70   ResequenceList
71   ReturnList
72   ReturnListEntry
73   ReturnMsg
74   ReturnMessages
75   ReturnSenders
76   SaveMsg
77   SearchEmails
78   SetContext
79   Space
80   UpdateList
81   UpdateUser
82   UpdateUserOptions
83   UserExists
84   Whitelist
85 );
86
87 my $mapsbase = "$FindBin::Bin/..";
88
89 # Forwards
90 sub Add2Blacklist;
91 sub Add2Nulllist;
92 sub Add2Whitelist;
93 sub AddEmail;
94 sub AddList;
95 sub AddUser;
96 sub AddUserOptions;
97 sub Blacklist;
98 sub CleanEmail;
99 sub CleanLog;
100 sub CountMsg;
101 sub Decrypt;
102 sub DeleteEmail;
103 sub DeleteList;
104 sub DeleteLog;
105 sub Encrypt;
106 sub FindEmail;
107 sub FindList;
108 sub FindLog;
109 sub FindUser;
110 sub ForwardMsg;
111 sub GetContext;
112 sub GetEmail;
113 sub GetList;
114 sub GetLog;
115 sub GetUser;
116 sub GetUserOptions;
117 sub Login;
118 sub Nulllist;
119 sub OnBlacklist;
120 sub OnNulllist;
121 sub OnWhitelist;
122 sub OptimizeDB;
123 sub ReadMsg;
124 sub ResequenceList;
125 sub ReturnList;
126 sub ReturnListEntry;
127 sub ReturnMsg;
128 sub ReturnMessages;
129 sub ReturnSenders;
130 sub SaveMsg;
131 sub SearchEmails;
132 sub SendMsg;
133 sub SetContext;
134 sub Space;
135 sub UpdateList;
136 sub UpdateUser;
137 sub UpdateUserOptions;
138 sub UserExists;
139 sub Whitelist;
140
141 BEGIN {
142   my $MAPS_username = "mapsadmin";
143   my $MAPS_password = "mapsadmin";
144
145   OpenDB $MAPS_username, $MAPS_password;
146 } # BEGIN
147
148 END {
149   CloseDB;
150 } # END
151
152 sub Add2Blacklist {
153   # Add2Blacklist will add an entry to the blacklist
154   my ($sender, $userid, $comment) = @_;
155
156   # First SetContext to the userid whose black list we are adding to
157   MAPSDB::SetContext $userid;
158
159   # Add to black list
160   AddList "black", $sender, 0, $comment;
161
162   # Log that we black listed the sender
163   Info "Added $sender to " . ucfirst $userid . "'s black list";
164
165   # Delete old emails
166   my $count = DeleteEmail $sender;
167
168   # Log out many emails we managed to remove
169   Info "Removed $count emails from $sender"
170 } # Add2Blacklist
171
172 sub Add2Nulllist ($$;$) {
173   # Add2Nulllist will add an entry to the nulllist
174   my ($sender, $userid, $comment) = @_;
175   
176   # First SetContext to the userid whose null list we are adding to
177   MAPSDB::SetContext $userid;
178
179   # Add to null list
180   AddList "null", $sender, 0, $comment;
181
182   # Log that we null listed the sender
183   Info "Added $sender to " . ucfirst $userid . "'s null list";
184
185   # Delete old emails
186   my $count = DeleteEmail $sender;
187
188   # Log out many emails we managed to remove
189   Info "Removed $count emails from $sender"
190 } # Add2Nulllist
191
192 sub Add2Whitelist ($$;$) {
193   # Add2Whitelist will add an entry to the whitelist
194   my ($sender, $userid, $comment) = @_;
195
196   # First SetContext to the userid whose white list we are adding to
197   MAPSDB::SetContext $userid;
198
199   # Add to white list
200   AddList 'white', $sender, 0, $comment;
201
202   # Log that we registered a user
203   Logmsg "registered", $sender, "Registered new sender";
204
205   # Check to see if there are any old messages to deliver
206   my $handle = FindEmail $sender;
207
208   my ($dbsender, $subject, $timestamp, $message);
209
210   # Deliver old emails
211   my $messages          = 0;
212   my $return_status     = 0;
213
214   while (($userid, $dbsender, $subject, $timestamp, $message) = GetEmail $handle) {
215     last 
216       unless $userid;
217
218     $return_status = Whitelist $sender, $message;
219
220     last
221       if $return_status;
222
223     $messages++;
224   } # while
225
226   # Done with $handle
227   $handle->finish;
228
229   # Return if we has a problem delivering email
230   return $return_status
231     if $return_status;
232
233   # Remove delivered messages.
234   DeleteEmail $sender;
235
236   return $messages;
237 } # Add2Whitelist
238
239 sub AddEmail ($$$) {
240   my ($sender, $subject, $data) = @_;
241
242   MAPSDB::AddEmail $sender, $subject, $data;
243 } # AddEmail
244
245 sub AddList ($$$;$) {
246   my ($listtype, $pattern, $sequence, $comment) = @_;
247
248   MAPSDB::AddList $listtype, $pattern, $sequence, $comment, CountMsg $pattern;
249 } # AddList
250
251 sub AddUser ($$$$) {
252   my ($userid, $realname, $email, $password) = @_;
253
254   return MAPSDB::AddUser $userid, $realname, $email, $password;
255 } # AddUser
256
257 sub AddUserOptions ($%) {
258   my ($userid, %options) = @_;
259
260   my $status;
261
262   foreach (keys (%options)) {
263     $status = MAPSDB::AddUserOption $userid, $_, $options{$_};
264     last if $status ne 0;
265   } # foreach
266
267   return $status;
268 } # AddUserOptions
269
270 sub Blacklist ($$$@) {
271   # Blacklist will send a message back to the $sender telling them that
272   # they've been blacklisted. Currently we save a copy of the message.
273   # In the future we should just disregard the message.
274   my ($sender, $sequence, $hit_count, @msg)  = @_;
275
276   # Check to see if this sender has already emailed us.
277   my $msg_count = CountMsg $sender;
278
279   if ($msg_count lt 5) {
280     # Bounce email
281     SendMsg ($sender, "Your email has been discarded by MAPS", "$mapsbase/blacklist.html", @msg);
282     Logmsg "blacklist", $sender, "Sent blacklist reply";
283   } else {
284     Logmsg "mailloop", $sender, "Mail loop encountered";
285   } # if
286
287   RecordHit "black", $sequence, ++$hit_count if $sequence;
288 } # Blacklist
289
290 sub CleanEmail ($) {
291   my ($timestamp) = @_;
292
293   MAPSDB::CleanEmail $timestamp;
294 } # CleanEmail
295
296 sub CleanLog ($) {
297   my ($timestamp) = @_;
298
299   MAPSDB::CleanLog $timestamp;
300 } # CleanLog
301
302 sub CleanList ($;$) {
303   my ($timestamp, $listtype) = @_;
304
305   MAPSDB::CleanList $timestamp, $listtype;
306 } # CleanList
307
308 sub CountMsg ($) {
309   my ($sender) = @_;
310
311   return MAPSDB::CountMsg $sender;
312 } # CountMsg
313
314 sub Decrypt ($$) {
315   my ($password, $userid) = @_;
316
317   return MAPSDB::Decrypt $password, shift;
318 } # Decrypt
319
320 sub DeleteEmail ($) {
321   my ($sender) = @_;
322
323   return MAPSDB::DeleteEmail $sender;
324 } # DeleteEmail
325
326 sub DeleteList ($$) {
327   my ($type, $sequence) = @_;
328
329   return MAPSDB::DeleteList $type, $sequence;
330 } # DeleteList
331
332 sub DeleteLog ($) {
333   my ($sender) = @_;
334
335   return MAPSDB::DeleteLog $sender;
336 } # DeleteLog
337
338 sub Encrypt ($$) {
339   my ($password, $userid) = @_;
340
341   return MAPSDB::Encrypt $password, $userid;
342 } # Encrypt
343
344 sub FindEmail (;$) {
345   my ($sender) = @_;
346
347   return MAPSDB::FindEmail $sender;
348 } # FindEmail
349
350 sub FindList ($;$) {
351   my ($type, $sender) = @_;
352
353   return MAPSDB::FindList $type, $sender;
354 } # FindList
355
356 sub FindLog ($) {
357   my ($how_many) = @_;
358
359   my $start_at = 0;
360   my $end_at   = MAPSDB::countlog ();
361
362   if ($how_many < 0) {
363     $start_at = $end_at - abs ($how_many);
364     $start_at = 0 if ($start_at < 0);
365   } # if
366
367   return MAPSDB::FindLog $start_at, $end_at;
368 } # FindLog
369
370 sub FindUser (;$) {
371   my ($userid) = @_;
372
373   return MAPSDB::FindUser $userid
374 } # FindUser
375
376 sub GetContext () {
377   return MAPSDB::GetContext ();
378 } # GetContext
379
380 sub GetEmail ($) {
381   my ($handle) = @_;
382
383   return MAPSDB::GetEmail $handle;
384 } # GetEmail
385
386 sub GetList ($) {
387   my ($handle) = @_;
388
389   return MAPSDB::GetList $handle;
390 } # GetList
391
392 sub GetLog ($) {
393   my ($handle) = @_;
394
395   return MAPSDB::GetLog $handle;
396 } # GetLog
397
398 sub GetUser ($) {
399   my ($handle) = @_;
400
401   return MAPSDB::GetUser $handle;
402 } # GetUser
403
404 sub GetUserOptions ($) {
405   my ($userid) = @_;
406
407   return MAPSDB::GetUserOptions $userid;
408 } # GetUserOptions
409
410 sub Login ($$) {
411   my ($userid, $password) = @_;
412
413   $password = Encrypt $password, $userid;
414
415   # Check if user exists
416   my $dbpassword = UserExists $userid;
417
418   # Return -1 if user doesn't exist
419   return -1 if !$dbpassword;
420
421   # Return -2 if password does not match
422   if ($password eq $dbpassword) {
423     MAPSDB::SetContext $userid;
424     return 0
425   } else {
426     return -2
427   } # if
428 } # Login
429
430 sub Nulllist ($;$$) {
431   # Nulllist will simply discard the message.
432   my ($sender, $sequence, $hit_count) = @_;
433
434   RecordHit "null", $sequence, ++$hit_count if $sequence;
435
436   # Discard Message
437   Logmsg "nulllist", $sender, "Discarded message";
438 } # Nulllist
439
440 sub OnBlacklist ($) {
441   my ($sender) = @_;
442
443   return CheckOnList "black", $sender;
444 } # CheckOnBlacklist
445
446 sub OnNulllist ($) {
447   my ($sender) = @_;
448
449   return CheckOnList "null", $sender;
450 } # CheckOnNulllist
451
452 sub OnWhitelist {
453   my ($sender, $userid) = @_;
454
455   if (defined $userid) {
456     MAPSDB::SetContext $userid;
457   } # if
458
459   return CheckOnList "white", $sender;
460 } # OnWhitelist
461
462 sub OptimizeDB () {
463   return MAPSDB::OptimizeDB ();
464 } # OptimizeDB
465
466 sub ReadMsg ($) {
467   # Reads an email message file from $input. Returns sender, subject,
468   # date and data, which is a copy of the entire message.
469   my ($input) = @_;
470
471   my $sender            = "";
472   my $sender_long       = "";
473   my $envelope_sender   = "";
474   my $reply_to          = "";
475   my $subject           = "";
476   my $data              = "";
477   my @data;
478
479   # Find first message's "From " line indicating start of message
480   while (<$input>) {
481     chomp;
482     last if /^From /;
483   } # while
484
485   # If we hit eof here then the message was garbled. Return indication of this
486   if (eof $input) {
487     $data = "Garbled message - unable to find From line";
488     return $sender, $sender_long, $reply_to, $subject, $data;
489   } # if
490
491   if (/From (\S*)/) {
492     $envelope_sender = $1;
493     $sender_long     = $envelope_sender;
494   } # if
495
496   push @data, $_ if /^From /;
497
498   while (<$input>) {
499     chomp;
500     push @data, $_;
501
502     # Blank line indicates start of message body
503     last if ($_ eq "" || $_ eq "\r");
504
505     # Extract sender's address
506     if (/^from: .*/i) {
507       $_ = substr ($_, 6);
508       $sender_long = $_;
509       if (/<(\S*)@(\S*)>/) {
510         $sender = lc ("$1\@$2");
511       } elsif (/(\S*)@(\S*)\ /) {
512         $sender = lc ("$1\@$2");
513       } elsif (/(\S*)@(\S*)/) {
514         $sender = lc ("$1\@$2");
515       } # if
516     } elsif (/^subject: .*/i) {
517       $subject = substr ($_, 9);
518     } elsif (/^reply-to: .*/i) {
519       $_ = substr ($_, 10);
520       if (/<(\S*)@(\S*)>/) {
521         $reply_to = lc ("$1\@$2");
522       } elsif (/(\S*)@(\S*)\ /) {
523         $reply_to = lc ("$1\@$2");
524       } elsif (/(\S*)@(\S*)/) {
525         $reply_to = lc ("$1\@$2");
526       } # if
527     } else {
528       next;
529     } # if
530   } # while
531
532   # Read message body
533   while (<$input>) {
534     chomp;
535
536     last if (/^From /);
537     push @data, $_;
538   } # while
539
540   # Set file pointer back by length of the line just read
541   seek ($input, -length () - 1, 1) if !eof $input;
542
543   # Sanitize email addresses
544   $envelope_sender      =~ s/\<//g;
545   $envelope_sender      =~ s/\>//g;
546   $envelope_sender      =~ s/\"//g;
547   $envelope_sender      =~ s/\'//g;
548   $sender               =~ s/\<//g;
549   $sender               =~ s/\>//g;
550   $sender               =~ s/\"//g;
551   $sender               =~ s/\'//g;
552   $reply_to             =~ s/\<//g;
553   $reply_to             =~ s/\>//g;
554   $reply_to             =~ s/\"//g;
555   $reply_to             =~ s/\'//g;
556
557   # Now let's pack the @data array to a scalar
558   foreach (@data) {
559     $data = $data . $_ . "\n";
560   } # foreach
561
562   # Determine best addresses
563   $sender       = $envelope_sender      if $sender eq "";
564   $reply_to     = $sender               if $reply_to eq "";
565
566   return $sender, $sender_long, $reply_to, $subject, $data;
567 } # ReadMsg
568
569 sub ResequenceList ($$) {
570   my ($userid, $type) = @_;
571
572   return MAPSDB::ResequenceList $userid, $type;
573 } # ResequenceList
574
575 sub ReturnMessages ($$) {
576   my ($userid, $sender) = @_;
577
578   return MAPSDB::ReturnMessages $userid, $sender;
579 } # ReturnMessages
580
581 sub ReturnSenders ($$$;$$) {
582   my ($userid, $type, $next, $lines, $date) = @_;
583
584   return MAPSDB::ReturnSenders $userid, $type, $next, $lines, $date;
585 } # ReturnSenders
586
587 sub ReturnList ($$$) {
588   my ($type, $start_at, $lines) = @_;
589
590   return MAPSDB::ReturnList $type, $start_at, $lines;
591 } # ReturnList
592
593 sub ReturnListEntry ($$) {
594   my ($type, $sequence) = @_;
595
596   return MAPSDB::ReturnListEntry $type, $sequence;
597 } # ReturnList
598
599 # Added reply_to. Previously we passed reply_to into here as sender. This
600 # caused a problem in that we were filtering as per sender but logging it
601 # as reply_to. We only need reply_to for SendMsg so as to honor reply_to
602 # so we now pass in both sender and reply_to
603 sub ReturnMsg ($$$$) {
604   # ReturnMsg will send back to the $sender the register message.
605   # Messages are saved to be delivered when the $sender registers.
606   my ($sender, $reply_to, $subject, $data) = @_;
607
608   # Check to see if this sender has already emailed us.
609   my $msg_count = CountMsg $sender;
610
611   if ($msg_count < 5) {
612     # Return register message
613     my @msg;
614     foreach (split /\n/,$data) {
615       push @msg, "$_\n";
616     } # foreach
617     SendMsg $reply_to,
618             "Your email has been returned by MAPS",
619             "$mapsbase/register.html",
620             GetContext,
621             @msg
622       if $msg_count eq 0;
623     Logmsg "returned", $sender, "Sent register reply";
624     # Save message
625     SaveMsg $sender, $subject, $data;
626   } else {
627     Add2Nulllist $sender, GetContext, "Auto Null List - Mail loop";
628     Logmsg "mailloop", $sender, "Mail loop encountered";
629   } # if
630 } # ReturnMsg
631
632 sub SaveMsg ($$$) {
633   my ($sender, $subject, $data) = @_;
634
635   AddEmail $sender, $subject, $data;
636 } # SaveMsg
637
638 sub SearchEmails ($$) {
639   my ($userid, $searchfield) = @_;
640
641   return MAPSDB::SearchEmails $userid, $searchfield;
642 } # SearchEmails
643
644 sub ForwardMsg ($$$) {
645   my ($sender, $subject, $data)  = @_;
646
647   my @lines = split /\n/, $data;
648
649   while ($_ = shift @lines) {
650     last if ($_ eq "" || $_ eq "\r");
651   } # while
652
653   my $to = "renn.leech\@compassbank.com";
654
655   my $msg = MIME::Entity->build (
656     From        => $sender,
657     To          => $to,
658     Subject     => $subject,
659     Type        => "text/html",
660     Data        => \@lines,
661   );
662
663   # Send it
664   open MAIL, "| /usr/lib/sendmail -t -oi -oem"
665     or die "ForwardMsg: Unable to open pipe to sendmail $!";
666   $msg->print(\*MAIL);
667   close MAIL;
668 } # ForwardMsg
669
670 sub SendMsg ($$$$@) {
671   # SendMsg will send the message contained in $msgfile.
672   my ($sender, $subject, $msgfile, $userid, @spammsg) = @_;
673
674   my @lines;
675
676   # Open return message template file
677   open RETURN_MSG_FILE, "$msgfile"
678     or die "Unable to open return msg file ($msgfile): $!\n";
679
680   # Read return message template file and print it to $msg_body
681   while (<RETURN_MSG_FILE>) {
682     if (/\$userid/) {
683       # Replace userid
684       s/\$userid/$userid/;
685     } # if
686     if (/\$sender/) {
687       # Replace sender
688       s/\$sender/$sender/;
689     } #if
690     push @lines, $_;
691   } # while
692
693   # Close RETURN_MSG_FILE
694   close RETURN_MSG_FILE;
695
696   # Create the message, and set up the mail headers:
697   my $msg = MIME::Entity->build (
698     From        => "MAPS\@DeFaria.com",
699     To          => $sender,
700     Subject     => $subject,
701     Type        => "text/html",
702     Data        => \@lines
703   );
704
705   # Need to obtain the spam message here...
706   $msg->attach (
707     Type        => "message",
708     Disposition => "attachment",
709     Data        => \@spammsg
710   );
711
712   # Send it
713   open MAIL, "| /usr/lib/sendmail -t -oi -oem"
714     or die "SendMsg: Unable to open pipe to sendmail $!";
715   $msg->print(\*MAIL);
716   close MAIL;
717 } # SendMsg
718
719 sub SetContext ($) {
720   my ($new_user) = @_;
721
722   return MAPSDB::SetContext $new_user;
723 } # SetContext
724
725 sub Space ($) {
726   my ($userid) = @_;
727
728   return MAPSDB::Space $userid;
729 } # Space
730
731 sub UpdateList ($$$$$$) {
732   my ($userid, $type, $pattern, $domain, $comment, $sequence) = @_;
733
734   return MAPSDB::UpdateList $userid, $type, $pattern, $domain, $comment, $sequence;
735 } # UpdateList
736
737 sub UpdateUser ($$$$) {
738   my ($userid, $fullname, $email, $password) = @_;
739
740   return MAPSDB::UpdateUser $userid, $fullname, $email, $password;
741 } # UpdateUser
742
743 sub UpdateUserOptions ($@) {
744   my ($userid, %options)        = @_;
745
746   my $status;
747
748   foreach (keys (%options)) {
749     $status = MAPSDB::UpdateUserOption $userid, $_, $options{$_};
750     last if $status ne 0;
751   }
752
753   return $status;
754 } # UpdateUserOptions
755
756 sub UserExists ($) {
757   my ($userid) = @_;
758
759   return MAPSDB::UserExists $userid
760 } # UserExists
761
762 sub Whitelist ($$;$$) {
763   # Whitelist will deliver the message.
764   my ($sender, $data, $sequence, $hit_count) = @_;
765
766   my $userid = GetContext;
767
768   # Dump message into a file
769   open MESSAGE, ">/tmp/MAPSMessage.$$"
770     or Error "Unable to open message file (/tmp/MAPSMessage.$$): $!\n", return -1;
771
772   print MESSAGE $data;
773
774   close MESSAGE;
775
776   # Now call MAPSDeliver
777   my $status = system "$FindBin::Bin/MAPSDeliver $userid /tmp/MAPSMessage.$$";
778
779   unlink "/tmp/MAPSMessage.$$";
780
781   if ($status eq 0) {
782     Logmsg "whitelist", $sender, "Delivered message";
783   } else { 
784     Error "Unable to deliver message - is MAPSDeliver setgid? - $!";
785   } # if
786
787   RecordHit "white", $sequence, ++$hit_count if $sequence;
788
789   return $status;
790 } # Whitelist
791
792 1;