2 #################################################################################
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 $
12 # (c) Copyright 2000-2018, Andrew@DeFaria.com, all rights reserved.
14 ################################################################################
33 use base qw(Exporter);
40 my $userid = $ENV{MAPS_USERNAME} ? $ENV{MAPS_USERNAME} : $ENV{USER};
103 my $mapsbase = "$FindBin::Bin/..";
106 sub _cleanTables($$;$) {
107 my ($table, $timestamp, $dryrun) = @_;
109 my $condition = "userid = '$userid' and timestamp < '$timestamp'";
112 return $db->count($table, $condition);
114 my ($count, $msg) = $db->delete($table, $condition);
120 sub _retention2Days($) {
121 my ($retention) = @_;
123 # Of the retnetion periods I'm thinking of where they are <n> and then
124 # something like (days|weeks|months|years) none are tricky except for months
125 # because months, unlike (days|weeks|years) are ill-defined. Are there 28, 29
126 # 30 or 31 days in a month? Days are simple <n> days. Weeks are simple <n> * 7
127 # days. Years are simple - just change the year (a little oddity of 365 or
128 # 366) days this year? To keep things simple, we will ignore the oddities of
129 # leap years and just use 30 for number of days in month. We really don't need
130 # to be that accurate here...
132 # BTW we aren't checking for odd things like 34320 weeks or 5000 years...
133 if ($retention =~ /(\d+)\s+(day|days)/) {
135 } elsif ($retention =~ /(\d+)\s+(week|weeks)/){
137 } elsif ($retention =~ /(\d+)\s+(month|months)/) {
139 } elsif ($retention =~ /(\d+)\s+(year|years)/) {
149 my ($username, $password) = @_;
152 my $dbserver = $ENV{MAPS_SERVER} || 'localhost';
154 $db = MyDB->new($username, $password, $dbname, $dbserver);
156 croak "Unable to instantiate MyDB ($username\@$dbserver:$dbname)" unless $db;
160 my $MAPS_username = "maps";
161 my $MAPS_password = "spam";
163 OpenDB($MAPS_username, $MAPS_password);
166 sub Add2Blacklist(%) {
169 # Add2Blacklist will add an entry to the blacklist
170 # First SetContext to the userid whose black list we are adding to
171 SetContext($params{userid});
174 $params{sequence} = 0;
175 my ($err, $msg) = AddList(%params);
177 # Log that we black listed the sender
178 Info("Added $params{sender} to " . ucfirst $params{userid} . "'s black list");
181 my $count = DeleteEmail(
182 userid => $params{userid},
183 sender => $params{sender},
186 # Log out many emails we managed to remove
187 Info("Removed $count emails from $params{sender}");
192 sub Add2Nulllist(%) {
195 # First SetContext to the userid whose null list we are adding to
196 SetContext($params{userid});
199 $params{sequence} = 0;
200 my ($err, $msg) = AddList(%params);
202 # Log that we null listed the sender
203 Info("Added $params{sender} to " . ucfirst $params{userid }. "'s null list");
206 my $count = DeleteEmail(
207 userid => $params{userid},
208 sender => $params{sender},
211 # Log out many emails we managed to remove
212 Info("Removed $count emails from $params{sender}");
217 sub Add2Whitelist(%) {
220 # Add2Whitelist will add an entry to the whitelist
221 # First SetContext to the userid whose white list we are adding to
222 SetContext($params{userid});
225 $params{sequence} = 0;
226 my ($err, $msg) = AddList(%params);
228 return -$err, $msg if $err;
230 # Log that we registered a user
232 userid => $params{userid},
233 type => 'registered',
234 sender => $params{sender},
235 message => 'Registered new sender',
238 # Check to see if there are any old messages to deliver
239 ($err, $msg) = $db->find('email', "sender = '$params{sender}'", ['userid', 'sender', 'data']);
241 return ($err, $msg) if $err;
247 while (my $rec = $db->getnext) {
248 last unless $rec->{userid};
250 $status = Whitelist($rec->{sender}, $rec->data);
257 # Return if we has a problem delivering email
258 return -1, 'Problem delivering some email' if $status;
260 # Remove delivered messages
262 userid => $params{userid},
263 sender => $params{sender},
266 return $messages, 'Messages delivered';
272 CheckParms(['userid', 'sender', 'subject', 'data'], \%rec);
274 $rec{timestamp} = UnixDatetime2SQLDatetime(scalar(localtime));
276 return $db->add('email', %rec);
282 CheckParms(['userid', 'type', 'sender', 'sequence'], \%rec);
284 croak "Type $rec{type} not valid. Must be one of white, black or null"
285 unless $rec{type} =~ /(white|black|null)/;
287 croak "Sender must contain \@" unless $rec{sender} =~ /\@/;
289 $rec{retention} //= '';
290 $rec{retention} = lc $rec{retention};
292 $rec{hit_count} //= $db->count(
294 "userid = '$rec{userid}' and sender like '%$rec{sender}%'"
297 ($rec{pattern}, $rec{domain}) = split /\@/, delete $rec{sender};
299 $rec{sequence} = GetNextSequenceNo(%rec);
301 $rec{last_hit} //= UnixDatetime2SQLDatetime(scalar (localtime));
303 return $db->add('list', %rec);
309 $params{timestamp} = UnixDatetime2SQLDatetime(scalar(localtime));
311 return $db->add('log', %params);
317 CheckParms(['userid', 'name', 'email', 'password'], \%rec);
319 return 1 if UserExists($rec{userid});
321 return $db->add('user', %rec);
324 sub AddUserOptions(%) {
327 croak('Userid is required') unless $rec{userid};
328 croak('No options to add') unless $rec{options};
330 return (1, "User doesn't exists") unless UserExist($rec{userid});
332 my %useropts = delete $rec{userid};
333 my %opts = delete $rec{options};
337 for my $key (%opts) {
338 $useropts{name} = $_;
339 $useropts{value} = $opts{$_};
341 ($err, $msg) = $db->add('useropts', %useropts);
346 return ($err, $msg) if $err;
350 # Blacklist will send a message back to the $sender telling them that
351 # they've been blacklisted. Currently we save a copy of the message.
352 # In the future we should just disregard the message.
355 # Check to see if this sender has already emailed us.
356 my $msg_count = $db->count('email', "userid='$rec{userid}' and sender like '%$rec{sender}%'");
358 if ($msg_count < 5) {
360 my @spammsg = split "\n", $rec{data};
363 userid => $rec{userid},
364 sender => $rec{sender},
365 subject => 'Your email has been discarded by MAPS',
366 msgfile => "$mapsbase/blacklist.html",
373 sender => $rec{sender},
374 message => 'Sent blacklist reply',
380 sender => $rec{sender},
381 message => 'Mail loop encountered',
385 $rec{hit_count}++ if $rec{sequence};
390 sequence => $rec{sequence},
391 hit_count => $rec{hit_count},
397 sub CheckEmail(;$$) {
398 my ($username, $domain) = @_;
400 return lc "$username\@$domain" if $username and $domain;
402 # Check to see if a full email address in either $username or $domain
404 if ($username =~ /(.*)\@(.*)/) {
407 return lc "$username\@";
410 if ($domain =~ /(.*)\@(.*)/) {
418 sub CheckOnList ($$;$) {
419 # CheckOnList will check to see if the $sender is on the list. Return 1 if
421 my ($listtype, $sender, $update) = @_;
426 my ($rule, $sequence);
429 my $condition = "userid='$userid' and type='$listtype'";
431 my ($err, $errmsg) = $db->find($table, $condition, '*', 'order by sequence');
433 my ($email_on_file, $rec);
435 while ($rec = $db->getnext) {
436 unless ($rec->{domain}) {
437 $email_on_file = $rec->{pattern};
439 unless ($rec->{pattern}) {
440 $email_on_file = '@' . $rec->{domain};
442 $email_on_file = $rec->{pattern} . '@' . $rec->{domain};
446 # Escape some special characters
447 $email_on_file =~ s/\@/\\@/;
448 $email_on_file =~ s/^\*/.\*/;
450 # We want to terminate the search string with a "$" iff there's an
451 # "@" in there. This is because some "email_on_file" may have no
452 # domain (e.g. "mailer-daemon" with no domain). In that case we
453 # don't want to terminate the search string with a "$" rather we
454 # wish to terminate it with an "@". But in the case of say
455 # "@ti.com" if we don't terminate the search string with "$" then
456 # "@ti.com" would also match "@tixcom.com"!
457 my $search_for = $email_on_file =~ /\@/
459 : !defined $rec->{domain}
462 if ($sender and $sender =~ /$search_for/i) {
463 my $comment = $rec->{comment} ? " - $rec->{comment}" : '';
465 $rule = "Matching rule: ($listtype:$rec->{sequence}) \"$email_on_file$comment\"";
466 $rule .= " - $rec->{comment}" if $rec->{comment};
469 $rec->{hit_count} //= 0;
474 sequence => $rec->{sequence},
475 hit_count => $rec->{hit_count} + 1,
482 return ($status, $rule, $rec->{sequence}, $rec->{hit_count});
485 sub CleanEmail($;$) {
486 my ($timestamp, $dryrun) = @_;
488 return _cleanTables 'email', $timestamp, $dryrun;
492 my ($timestamp, $dryrun) = @_;
494 return _cleanTables('log', $timestamp, $dryrun);
500 CheckParms(['userid', 'type'], \%params);
502 my $dryrunstr = $params{dryrun} ? '(dryrun)' : '';
505 my $condition = "userid='$params{userid}' and type='$params{type}'";
509 # First let's go through the list to see if we have an domain level entry
510 # (e.g. @spammer.com) and also individual entries (baddude@spammer.com) then
511 # we don't really need any of the individual entries since the domain block
513 $db->find($table, $condition, ['domain'], ' and pattern is null');
515 while (my $domains = $db->getnext) {
516 for my $recs ($db->get($table, $condition, ['sequence', 'pattern', 'domain'],
517 " and domain='$domains->{domain}' and pattern is not null")) {
518 if (@$recs and not $params{dryrun}) {
519 for my $rec (@$recs) {
521 userid => $params{userid},
522 type => $params{type},
523 sequence => $rec->{sequence},
526 $params{log}->msg("Deleted $params{userid}:$params{type}:$rec->{sequence} "
527 . "$rec->{pattern}\@$rec->{domain} $dryrunstr")
534 $params{log}->msg("The domain $domains->{domain} has the following subrecords");
536 for my $rec (@$recs) {
537 $params{log}->msg("$rec->{pattern}\@$rec->{domain}");
544 $condition = "userid='$params{userid}' and type='$params{type}' and retention is not null";
546 # First see if anything needs to be deleted
547 ($count, $msg) = $db->count($table, $condition);
549 return 0 unless $count;
553 my ($err, $errmsg) = $db->find($table, $condition);
555 croak "Unable to find $params{type} entries for $condition - $errmsg" if $err;
557 my $todaysDate = Today2SQLDatetime;
559 while (my $rec = $db->getnext) {
560 my $days = _retention2Days($rec->{retention});
562 my $agedDate = SubtractDays($todaysDate, $days);
564 # If last_hit < retentiondays then delete
565 if (Compare($rec->{last_hit}, $agedDate) == -1) {
566 unless ($params{dryrun}) {
568 userid => $params{userid},
569 type => $params{type},
570 sequence => $rec->{sequence},
574 $rec->{pattern} //= '';
575 $rec->{domain} //= '';
577 $params{log}->msg("Deleted $rec->{userid}:$params{type}:$rec->{sequence} "
578 . "$rec->{pattern}\@$rec->{domain} $dryrunstr");
579 $params{log}->dbug("last hit = $rec->{last_hit} < agedDate = $agedDate");
585 $params{log}->dbug("$rec->{userid}:$params{type}:$rec->{sequence}: nodelete $dryrunstr "
586 . "last hit = $rec->{last_hit} >= agedDate = $agedDate")
592 userid => $params{userid},
593 type => $params{type},
594 ) if $count && !$params{dryrun};
602 CheckParms(['userid'], \%params);
605 my $condition = "userid='$params{userid}'";
606 $condition .= " and $params{additional}" if $params{additional};
608 return $db->count($table, $condition);
614 CheckParms(['userid', 'type'], \%params);
617 my $condition = "userid='$params{userid}' and type='$params{type}'";
619 return $db->count($table, $condition);
625 CheckParms(['userid'], \%params);
627 my ($additional_condition) = delete $params{additional} || '';
629 my $condition = "userid='$userid'";
630 $condition .= " and $additional_condition" if $additional_condition;
632 return $db->count_distinct('log', 'sender', $condition);
636 my ($password, $userid) = @_;
638 return $db->decode($password, $userid);
646 CheckParms(['userid', 'sender'], \%rec);
648 my ($username, $domain) = split /@/, $rec{sender};
652 $condition = "userid = '$rec{userid}' and sender = '$rec{sender}'";
654 $condition = "userid = '$rec{userid}' and sender like '%\@$domain'";
657 return $db->delete($table, $condition);
663 CheckParms(['userid', 'type', 'sequence'], \%rec);
665 my $condition = "userid = '$rec{userid}' and "
666 . "type = '$rec{type}' and "
667 . "sequence = $rec{sequence}";
669 return $db->delete('list', $condition);
673 my ($password, $userid) = @_;
675 return $db->encode($password, $userid);
681 CheckParms(['userid'], \%params);
684 my $condition = "userid='$params{userid}'";
685 $condition .= " and sender='$params{sender}'" if $params{sender};
686 $condition .= " and timestamp='$params{timestamp}'" if $params{timestamp};
688 return $db->find($table, $condition);
694 my ($type, $sender) = @_;
696 CheckParms(['userid', 'type'], \%params);
699 my $condition = "userid='$params{userid}' and type='$params{type}'";
701 if ($params{sender}) {
702 my ($username, $domain) = split /\@/, $params{sender};
704 # Split will return '' if either username or domain is missing. This messes
705 # up SQL's find as '' ~= NULL. Therefore we only specify username or domain
707 $condition .= " and pattern='$username'" if $username;
708 $condition .= " and domain='$domain'" if $domain;
711 return $db->find($table, $condition);
718 my $end_at = CountLog(
723 $start_at = $end_at - abs ($how_many);
724 $start_at = 0 if ($start_at < 0);
728 my $condition = "userid='$userid'";
729 my $additional = "order by timestamp limit $start_at, $end_at";
731 return $db->find($table, $condition, '*', $additional);
740 $condition = "userid='$userid'" if $params{userid};
742 return $db->find($table, $condition, $params{fields});
746 return $db->find('user', '', ['userid']);
765 sub GetNextSequenceNo(%) {
768 CheckParms(['userid', 'type'], \%rec);
771 my $condition = "userid='$rec{userid}' and type='$rec{type}'";
773 my $count = $db->count('list', $condition);
776 } # GetNextSequenceNo
785 my $userinfo = $db->getone('user', "userid='$userid'", ['name', 'email']);
787 return %{$db->getone('user', "userid='$userid'", ['name', 'email'])};
792 sub GetUserOptions($) {
795 my $table = 'useropts';
796 my $condition = "userid='$userid'";
798 $db->find($table, $condition);
802 while (my $rec = $db->getnext) {
803 $useropts{$rec->{name}} = $rec->{value};
810 my ($userid, $password) = @_;
812 $password = Encrypt($password, $userid);
814 # Check if user exists
815 my $dbpassword = UserExists($userid);
817 # Return -1 if user doesn't exist
818 return -1 unless $dbpassword;
820 # Return -2 if password does not match
821 if ($password eq $dbpassword) {
830 # Nulllist will simply discard the message.
831 my ($sender, $sequence, $hit_count) = @_;
836 sequence => $sequence,
837 hit_count => ++$hit_count,
845 message => 'Discarded message'
851 sub OnBlacklist($;$) {
852 my ($sender, $update) = @_;
854 return CheckOnList('black', $sender, $update);
857 sub OnNulllist($;$) {
858 my ($sender, $update) = @_;
860 return CheckOnList('null', $sender, $update);
863 sub OnWhitelist($;$$) {
864 my ($sender, $userid, $update) = @_;
866 SetContext($userid) if $userid;
868 return CheckOnList('white', $sender, $update);
872 my @tables = qw(email list log user useropts);
874 my ($err, $msg) = $db->lock('read', \@tables);
876 croak "Unable to lock table - $msg" if $err;
878 ($err, $msg) = $db->check(\@tables);
880 croak 'Unable to check tables ' . $msg if $err;
882 ($err, $msg) = $db->optimize(\@tables);
884 croak 'Unable to optimize tables ' . $msg if $err;
886 return $db->unlock();
890 # Reads an email message file from $input. Returns sender, subject,
891 # date and data, which is a copy of the entire message.
895 my $sender_long = '';
896 my $envelope_sender = '';
902 # Find first message's "From " line indicating start of message
908 # If we hit eof here then the message was garbled. Return indication of this
910 $data = "Garbled message - unable to find From line";
911 return $sender, $sender_long, $reply_to, $subject, $data;
915 $envelope_sender = $1;
916 $sender_long = $envelope_sender;
919 push @data, $_ if /^From /;
925 # Blank line indicates start of message body
926 last if ($_ eq "" || $_ eq "\r");
928 # Extract sender's address
934 if (/<(\S*)@(\S*)>/) {
935 $sender = lc ("$1\@$2");
936 } elsif (/(\S*)@(\S*)\ /) {
937 $sender = lc ("$1\@$2");
938 } elsif (/(\S*)@(\S*)/) {
939 $sender = lc ("$1\@$2");
941 } elsif (/^subject: .*/i) {
942 $subject = substr ($_, 9);
943 } elsif (/^reply-to: .*/i) {
944 $_ = substr ($_, 10);
945 if (/<(\S*)@(\S*)>/) {
946 $reply_to = lc ("$1\@$2");
947 } elsif (/(\S*)@(\S*)\ /) {
948 $reply_to = lc ("$1\@$2");
949 } elsif (/(\S*)@(\S*)/) {
950 $reply_to = lc ("$1\@$2");
963 # Set file pointer back by length of the line just read
964 seek ($input, -length () - 1, 1) if !eof $input;
966 # Sanitize email addresses
967 $envelope_sender =~ s/\<//g;
968 $envelope_sender =~ s/\>//g;
969 $envelope_sender =~ s/\"//g;
970 $envelope_sender =~ s/\'//g;
975 $reply_to =~ s/\<//g;
976 $reply_to =~ s/\>//g;
977 $reply_to =~ s/\"//g;
978 $reply_to =~ s/\'//g;
980 # Determine best addresses
981 $sender = $envelope_sender if $sender eq "";
982 $reply_to = $sender if $reply_to eq "";
984 return $sender, $sender_long, $reply_to, $subject, join "\n", @data;
990 CheckParms(['userid', 'type', 'sequence', ], \%rec);
992 my $current_date = UnixDatetime2SQLDatetime(scalar(localtime));
995 my $condition = "userid='rec{userid} and type=$rec{type} and sequence='$rec{sequence}";
999 condition => $condition,
1004 sub ResequenceList(%) {
1007 CheckParms(['userid', 'type'], \%params);
1010 return 1 unless $params{type} =~ /(white|black|null)/;
1011 return 2 unless UserExists($params{userid});
1014 my $condition = "userid='$params{userid}' and type ='$params{type}'";
1017 $db->lock('write', $table);
1019 # Get all records for $userid and $type
1020 my $listrecs = $db->get($table, $condition,'*', 'order by hit_count desc');
1022 # Delete all of the list entries for this $userid and $type
1023 my ($count, $msg) = $db->delete($table, $condition);
1025 # Now re-add list entries renumbering them
1029 $_->{sequence} = $sequence++;
1031 my ($err, $msg) = $db->add($table, %$_);
1044 CheckParms(['userid', 'type'], \%params);
1046 my $start_at = delete $params{start_at} || 0;
1047 my $lines = delete $params{lines} || 10;
1050 my $condition = "userid='$params{userid}' and type='$params{type}'";
1051 my $additional = "order by sequence limit $start_at, $lines";
1053 return $db->get($table, $condition, '*', $additional);
1059 # ReturnMsg will send back to the $sender the register message.
1060 # Messages are saved to be delivered when the $sender registers.
1062 # Added reply_to. Previously we passed reply_to into here as sender. This
1063 # caused a problem in that we were filtering as per sender but logging it
1064 # as reply_to. We only need reply_to for SendMsg so as to honor reply_to
1065 # so we now pass in both sender and reply_to
1067 CheckParms(['userid', 'sender', 'reply_to', 'subject', 'data'], \%params);
1069 #my ($sender, $reply_to, $subject, $data) = @_;
1071 # Check to see if this sender has already emailed us.
1072 my $msg_count = $db->count('email', "userid='$userid' and sender like '%$params{sender}%'");
1074 if ($msg_count < 5) {
1075 # Return register message
1077 userid => $params{userid},
1078 sender => $params{reply_to},
1079 subject => 'Your email has been returned by MAPS',
1080 msgfile => "$mapsbase/register.html",
1081 data => $params{data},
1082 ) if $msg_count == 0;
1085 userid => $params{userid},
1087 sender => $params{sender},
1088 message => 'Sent register reply',
1092 SaveMsg($params{sender}, $params{subject}, $params{data});
1094 Add2Nulllist($params{sender}, GetContext, "Auto Null List - Mail loop");
1097 userid => $params{userid},
1099 sender => $params{sender},
1100 message => 'Mail loop encountered',
1107 sub ReturnMessages(%) {
1110 CheckParms(['userid', 'sender'], \%params);
1112 my $table = 'email';
1113 my $condition = "userid='$params{userid}' and sender='$params{sender}'";
1114 my $fields = ['subject', 'timestamp'];
1115 my $additional = 'group by timestamp desc';
1117 return $db->get($table, $condition, $fields, $additional);
1120 sub ReturnSenders(%) {
1122 # This subroutine returns an array of senders in reverse chronological
1123 # order based on time timestamp from the log table of when we returned
1124 # their message. The complication here is that a single sender may
1125 # send multiple times in a single day. So if spammer@foo.com sends
1126 # spam @ 1 second after midnight and then again at 2 Pm there will be
1127 # at least two records in the log table saying that we returned his
1128 # email. Getting records sorted by timestamp desc will have
1129 # spammer@foo.com listed twice. But we want him listed only once, as
1130 # the first entry in the returned array. Plus we may be called
1131 # repeatedly with different $start_at's. Therefore we need to process
1132 # the whole list of returns for today, eliminate duplicate entries for
1133 # a single sender then slice the resulting array.
1134 CheckParms(['userid', 'type', 'lines'], \%params);
1137 my $condition = "userid='$params{userid}' and type='$params{type}'";
1138 my $additional = 'order by timestamp desc';
1140 $params{start_at} ||= 0;
1142 if ($params{date}) {
1143 $condition .= "and timestamp > '$params{date} 00:00:00' and "
1144 . "timestamp < '$params{date} 23:59:59'";
1147 $db->find($table, $condition, '*', $additional);
1149 # Watch the distinction between senders (plural) and sender (singular)
1152 # Run through the results and add to %senders by sender key. This
1153 # results in a hash that has the sender in it and the first
1154 # timestamp value. Since we already sorted timestamp desc by the
1155 # above select statement, and we've narrowed it down to only log
1156 # message that occurred for the given $date, we will have a hash
1157 # containing 1 sender and the latest timestamp for the day.
1158 while (my $rec = $db->getnext) {
1159 $senders{$rec->{sender}} = $rec->{timestamp}
1160 unless $senders{$rec->{sender}};
1163 # Make a hash whose keys are the timestamp (so we can later sort on
1165 my %sendersByTimestamp = reverse %senders;
1169 # Sort by timestamp desc and push on to the @senders array
1170 push @senders, $sendersByTimestamp{$_}
1171 for (sort { $b cmp $a } keys %sendersByTimestamp);
1173 # Finally slice for the given range
1174 my $end_at = $params{start_at} + $params{lines} - 1;
1176 $end_at = (@senders - 1)
1177 if $end_at > @senders;
1179 return (@senders) [$params{start_at} .. $end_at];
1183 my ($sender, $subject, $data) = @_;
1188 subject => $subject,
1195 sub SearchEmails(%) {
1198 CheckParms(['userid', 'search'], \%params);
1200 my $table = 'email';
1201 my $fields = ['sender', 'subject', 'timestamp'];
1202 my $condition = "userid='$params{userid}' and (sender like '\%$params{search}\%' "
1203 . "or subject like '\%$params{search}\%')";
1204 my $additional = 'order by timestamp desc';
1206 my ($err, $msg) = $db->find($table, $condition, $fields, $additional);
1210 while (my $rec = $db->getnext) {
1218 # SendMsg will send the message contained in $msgfile.
1221 #my ($sender, $subject, $msgfile, $userid, @spammsg) = @_;
1225 # Open return message template file
1226 open my $return_msg_file, '<', $params{msgfile}
1227 or die "Unable to open return msg file ($params{msgfile}): $!\n";
1229 # Read return message template file and print it to $msg_body
1230 while (<$return_msg_file>) {
1233 s/\$userid/$userid/;
1237 s/\$sender/$params{sender}/;
1243 close $return_msg_file;
1245 # Create the message, and set up the mail headers:
1246 my $msg = MIME::Entity->build(
1247 From => "MAPS\@DeFaria.com",
1248 To => $params{sender},
1249 Subject => $params{subject},
1250 Type => "text/html",
1254 # Need to obtain the spam message here...
1255 my @spammsg = split "\n", $params{data};
1259 Disposition => "attachment",
1264 open my $mail, '|-', '/usr/lib/sendmail -t -oi -oem'
1265 or croak "SendMsg: Unable to open pipe to sendmail $!";
1267 $msg->print(\*$mail);
1277 if (UserExists($to_user)) {
1280 return GetUserInfo $userid;
1289 my $total_space = 0;
1290 my $table = 'email';
1291 my $condition = "userid='$userid'";
1293 $db->find($table, $condition);
1295 while (my $rec = $db->getnext) {
1297 length($rec->{userid}) +
1298 length($rec->{sender}) +
1299 length($rec->{subject}) +
1300 length($rec->{timestamp}) +
1301 length($rec->{data});
1304 return $total_space;
1310 CheckParms(['userid', 'type', 'sequence'], \%rec);
1313 my $condition = "userid = '$rec{userid}' and type = '$rec{type}' and sequence = $rec{sequence}";
1315 if ($rec{pattern} =~ /\@/ and !$rec{domain}) {
1316 ($rec{pattern}, $rec{domain}) = split /\@/, $rec{pattern};
1317 } elsif (!$rec{pattern} and $rec{domain} =~ /\@/) {
1318 ($rec{pattern}, $rec{domain}) = split /\@/, $rec{domain};
1319 } elsif (!$rec{pattern} and !$rec{domain}) {
1320 return "Must specify either Username or Domain";
1323 $rec{pattern} //= 'null';
1324 $rec{domain} //= 'null';
1325 $rec{comment} //= 'null';
1327 if ($rec{retention}) {
1328 $rec{retention} = lc $rec{retention};
1331 return $db->update($table, $condition, %rec);
1337 CheckParms(['userid', 'name', 'email'], \%rec);
1339 return 1 unless UserExists($rec{userid});
1342 my $condition = "userid='$rec{userid}'";
1344 return $db->update($table, $condition, %rec);
1347 sub UpdateUserOptions ($@) {
1348 my ($userid, %options) = @_;
1350 return unless UserExists($userid);
1352 my $table = 'useropts';
1353 my $condition = "userid='$userid' and name=";
1355 $db->update($table, "$condition'$_'", (name=>$_, value=>$options{$_})) for (keys %options);
1358 } # UpdateUserOptions
1363 return 0 unless $userid;
1366 my $condition = "userid='$userid'";
1368 my $rec = $db->get($table, $condition);
1370 return 0 if scalar(@$rec) == 0;
1372 return $rec->[0]{password};
1375 sub Whitelist ($$;$$) {
1376 # Whitelist will deliver the message.
1377 my ($sender, $data, $sequence, $hit_count) = @_;
1379 my $userid = GetContext;
1381 # Dump message into a file
1382 open my $message, '>', "/tmp/MAPSMessage.$$"
1383 or Error("Unable to open message file (/tmp/MAPSMessage.$$): $!\n"), return -1;
1385 print $message $data;
1389 # Now call MAPSDeliver
1390 my $status = system "$FindBin::Bin/MAPSDeliver $userid /tmp/MAPSMessage.$$";
1392 unlink "/tmp/MAPSMessage.$$";
1397 type => 'whitelist',
1399 message => 'Delivered message',
1402 Error("Unable to deliver message - is MAPSDeliver setgid? - $!");
1405 $hit_count++ if $sequence;
1410 sequence => $sequence,
1411 hit_count => $hit_count,