That means that two elements are related to each
other in such a way that, no matter which element you originate from, you always
have a unique relation to the other one (bijectivity).
This term originates from mathematics and means that a function not only has
unambiguous results (which, by definition, is a requirement), but the inverse
function does so, too.
Example: f(x) = x³ is biunique, g(x) = x² in contrast is not. This is bacause
of the values of the function appearing exactly once for each locus for the
first function whereas for the second function its values appear twice, with
the exception of its peak. f⁻¹(x) = ³√x is thus as unambiguous as f(x), because
it is the exact inverse calculation (f⁻¹(f(x)) = x), but this is not true for
g⁻¹(x) = √x, because each locus would have two values and therefore would have
to adequately restrict the domain for g(x) and the range of values for g⁻¹(x)
so that a unique inverse can be established.
Fighting spam: Spam-free guest book
Anonymity on the 'Net
Whenever anyone accesses a services on the Internet, his identity normally is
not known. After all, many of these services can be accessed without having to
disclose one's identity, and any potential address details are normally not
verified. It's this anonymity that spammers and cybercriminals take advantage
of for unloading their digital junk incognito or pursuing their dubious
machinations.
Unfortunately normal Internet users have no way of telling from the IP address
who is behind all that. To do so it would be necessary to query the connection
details for the address in question, but that's just not possible, because it's
only investigative or security authorities that have the means of querying these
data in the course of investigations. Providers won't issue these data to other
persons, because they won't possibly be able to claim a legitimate interest.
to the top
The author's identity
If you want to accept an entry, you want of course to make sure that that entity
is a real person and not a bot. This, however, requires that the user identifies
himself somehow.
However, one should avoid to take a sledgehammer to crack a nut and query the
complete personal data of the author, because in some countries that may entail
legal problems: According to their legislation you are required to collect only
the data that is absolutely necessary for the task at hand (e. g. it is the
§ 3a BDSG
in Germany that enforces this rule). If you don't want to send the author a
paper that is to prove his identity or require an identity check (the person in
question would surely thank you for this annoyance) there is no reason for
collecting any address data. So something else is necessary that can serve as
proof of identity.
This is where the e-mail address comes into play. When you make providing a mail
address mandatory for submitting a message, you have a nifty tool at hand for
checking whether or not you are dealing with a real person. All you have to do
is send an e-mail to the specified address and request that the recipient
confirm the address. This ensures that the originator is a real person.
to the top
This is how the confirmation mail screens out nonsense
The effect is based on the fact that bots enter a (oftentimes imaginary) mail
address to take this obstacle, which you effectively can use against them:
Simply send a mail to the stated address. If the mail bounces, you know that
the address is invalid and you can immediately discard the message. And even if
the address turns out to be valid, the recipient could wonder why he has
received the mail in the first place. It is therefore advisable to provide an
explanation why the message has been sent in the first place and state the
options available to the recipient.
It is of utmost importance that one of the options for the recipient is to do
nothing at all. In this event the accepted message should be silently discarded
after a grace period, e. g. one week. It would be inappropriate for the
recipient to take any action in a matter that he has no business in. This would
only be necessary if, and only if, he himself has submitted the entry. In this
case you should provide a link so he can prove the legitimacy of the entry and
so unlock it.
You can also provide a link as an additional option so that the recipient of
the confirmation mail can explicitly block his mail address against further use.
In this event you need to record the mail address as being blocked and can
immediately reject any subsequent attempts to use it for publishing any entries
without having to send any additional mails.
And if a mail address is confirmed as legitimate, it would be annoying as well
if the person in question would have to confirm his mail address over and over
again for every new entry. You therefore can record the fact that the mail
address has been confirmed as well and so make a reconfirmation unnecessary for
some time. This avoids once again that your users feel molested.
And should you discover that existing mail addresses are used for distributing
spam, you have the option to block them manually so that this security measure
cannot be bypassed any more with the addresses in question. Combined with
moderating the guest book you are guaranteed to
get rid of any sort of spam before it shows up in your guest book.
The other point is the fact that a spammer would unlikely provide his own mail
address, because due to the amount of spam being usually disseminated, he would
get flooded with confirmation mails. If he had to manually confirm every entry,
he wouldn't be able to do anything else. You also can block any mail address
that you suspect to belong to a spammer manually so that this bypass quickly
becomes impracticable.
to the top
Automatized mail transmission
You should have a mail server set up that can be used to tansmit the
confirmation mails. You could alternatively have this done via a mail address
created at a mail provider specifically for this purpose, but that's suboptimal
at best. With an own mail server you can ensure that the confirmation mails can
be unambiguously associated with you.
This requires that you are able to set up a mail server in a way that it cannot
be abused as an open relay for spam and you are able to add the sender address
required for sending the confirmation mails.
Releasing guest book entries by confirmation mail is based on two components:
The confirmation mail sent by the control script of your guest book and a page
invoked solely for the purpose of verification.
To begin with, it is necessary to modify the control script for the guest book
in a way that it automatically sends a confirmation mail to the mail address
provided by the author. Within this mail a link is preferably contained that
can be clicked on by the recipient of the mail (providing he exists) to complete
the action. Internally an entry is added to your database that contains a mark
(e. g. a multi-digit hex code that can only be guessed with considerable effort)
identifying the entry in question
biuniquely.
This link containing this element as well points to a page that reads the hex
code from the link and compares it to the entries in the database. In case of a
hit the guest book entry to be released – which in turn is also confirmed – can
be determined. Upon a miss the action can be rejected with an error. However, if
no code is passed when invoking the page, you could either display an error
again, which can be implemented rather easily, or you offer a form instead into
which the confirmation code can be pasted. If something wrong or nothing at all
is entered, the process should best be aborted with an error.
In order to do so you need to take some preparations. For all subsequent works
please log in as root first, because you need the
associated privileges. After that you generate a routine that checks mail
addresses for plausibiltiy and sends e-mails via your mail server:
package sendmail;
require Exporter;
use strict;
use IO::Socket::SSL;
use Net::SMTP;
use checktld;
use Net::DNS::Resolver;
$Net::SSLeay::version = 12;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(send_mail);
my $fingerprint = '<Please insert fingerprint of your SMTP certificate here>';
sub validate_mailaddr
{
my $p_email_addr = $_[0];
my $l_valid_format;
my $l_dns_error;
die "validate_mailaddr: Illegal argument count" if(@_ != 1);
my $l_dns = Net::DNS::Resolver->new( nameservers => [ qw(127.0.0.1) ] );
$p_email_addr =~ /^([^\s@,:"<>]+)@([^\s@,:"<>]+)\.([^\s@,:"<>.\d]{2,})$/;
return undef if(!valid_tld($3));
$l_dns->query($2.'.'.$3);
$l_dns_error = $l_dns->errorstring;
return undef if($l_dns_error eq 'NXDOMAIN');
return undef if($l_dns_error ne 'NOERROR');
return "$1\@$2.$3";
};
sub send_mail
{
my $p_sender;
my $p_recipient;
my $p_response_path;
my $p_cc;
my $p_subject;
my $p_message;
my $l_result = 1;
my $l_mailer;
my $l_status;
my $l_eval_err;
die "send_mail: Illegal argument count" if(@_ != 6);
($p_sender,$p_recipient,$p_response_path,$p_cc,$p_subject,$p_message) = @_;
return 0 if(!defined ($p_recipient = validate_mailaddr($p_recipient)));
if($p_response_path ne '')
{
return 0 if(!defined ($p_response_path = validate_mailaddr($p_response_path)));
}
$l_mailer = Net::SMTP->new( 'localhost',
Hello => '<Please set your server name here>',
Timeout => 60 );
return undef if(!defined $l_mailer);
$l_status = eval
{
$l_mailer->starttls( SSL_fingerprint => "sha256\$$fingerprint" );
$l_mailer->mail($p_sender);
if($l_mailer->to($p_recipient))
{
$l_mailer->cc($p_response_path) if($p_cc && $p_response_path ne '' && $p_cc);
$l_mailer->data();
$l_mailer->datasend("Content-Type: text/plain; charset=utf-8\n");
$l_mailer->datasend("From: <$p_sender>\n");
$l_mailer->datasend("To: <$p_recipient>\n");
$l_mailer->datasend("Reply-To: <$p_response_path>\n") if($p_response_path ne '');
$l_mailer->datasend("Cc: <$p_response_path>\n") if($p_response_path ne '' && $p_cc);
$l_mailer->datasend("Subject: $p_subject\n\n");
$l_mailer->datasend("$p_message"); $l_mailer->dataend();
return "OK";
}
else
{
return "Connection refused";
}
};
$l_eval_err = $@;
$l_mailer->quit;
if($l_eval_err)
{
$l_result = 0;
print STDERR "The transaction failed: $l_eval_err\n";
$l_result = -1 if($l_eval_err =~ /Greylisted/);
}
elsif(defined $l_status && $l_status ne 'OK')
{
print STDERR "The transaction failed: $l_status\n";
return 0;
}
elsif(!defined $l_status)
{
print STDERR "The transaction failed: Internal error!\n";
return undef;
}
return $l_result;
};
1;
__END__
However, the plausibility check also verifies that the top-level domain (the
rightmost part in the domain name) is defined in the first place. Since this
list may change over time, it is necessary to regularly update it. You need to
create a background process for this task that is invoked periodically:
#!/usr/bin/perl -w -I /srv/www-ssl/cgi-lib
use strict;
use LWP::UserAgent;
my $current_time = time();
my $timestr = gmtime($current_time);
my $tld_doc = 'https://data.iana.org/TLD/tlds-alpha-by-domain.txt';
my @filelines;
my $has_prev = undef;
my $ua = LWP::UserAgent->new( agent => '<Den Updater bitte benennen...>',
from =>'<Bitte eine Kontaktadresse hinterlegen!>' );
my $response = $ua->get($tld_doc);
if(defined $response)
{
if($response->is_success)
{
@filelines = split(/\n/, $response->decoded_content);
# Erzeuge ein Perl-Modul aus den erhaltenen Daten!
if(defined open(TLDLIST, ">/srv/www1-ssl/cgi-lib/checktld.pm"))
{
print TLDLIST "# CAUTION: This is an auto-generated file!\n";
print TLDLIST "# Any changes to this file are eventually going to be lost!\n";
print TLDLIST "# Created at $timestr UTC\n\n";
print TLDLIST "package checktld;\n\n";
print TLDLIST "require Exporter;\nuse strict;\n\n";
print TLDLIST "our \@ISA = qw(Exporter);\n";
print TLDLIST "our \@EXPORT = qw(valid_tld);\n\n";
print TLDLIST "my \@tldlist = ( ";
foreach (@filelines)
{
next if($_ =~ /^#/);
print TLDLIST ', ' if($has_prev);
print TLDLIST "'".lc($_)."'";
$has_prev = 1;
}
print TLDLIST " );\n\n";
print TLDLIST <<'EOT';
sub valid_tld
{
my $p_this = $_[0];
return undef if(@_ != 1);
foreach (@tldlist)
{
return 1 if($p_this eq $_);
}
return 0;
};
EOT
print TLDLIST "\n1;\n\n__END__\n";
close(TLDLIST);
}
}
}
1;
__END__
Copy this script to a suitable directory, e. g.
/usr/local/bin, or create one for any potential
background processes, for example as a subdirectory of
/opt (e. g. /opt/background).
After this just add an entry to the crontab to have this script executed
periodically. Invoke crontab -e to edit the crontab of
root. If none is present, a new one is generated.
Then enter a line similarly constructed as this one:
5 2 * * 0 /opt/background/do-updates.pl
Save and quit crontab. If you entered this correctly,
the new line is added. Otherwise it complains and you are requested to correct
the error.
In this example the update script, placed in
/opt/background, is invoked each Sunday at five past
two (in respect to the local or specified timezone) and recreates the module for
checking the TLD. However, it is necessary that you do this update manually
once, because mail transmissions won't be possible until the first update.
After you are done with these preparations you need to create a sender address
for the confirmation mails. This is done with the file
/etc/aliases.
noreply: /dev/null
This accomplishes two things: At first it makes the sender
noreply known, and secondly any mail arriving at this
“mailbox” vanishes in the digital void. This sender is meant to transmit mail
after all – it is not supposed to receive any.
After adding this line you need to invoke the command
newaliases to recreate the alias database and restart
the mail server. Now you are all set for sending confirmation mails.
Now it's getting to work incorporating the control logic into the control script
of the guest book. It must ascertain certain things, especially that no new
messages can be submitted pending a confirmation, and you also have the option
to relax some of the checks in case of already confirmed mail addresses.
At irst you need to incorporate the following subroutine into the control
script:
my %verify_mail = ( 'de' => { 'emailcheck' => 'Bestätigung Ihrer E-Mail-Adresse',
'part1' => "Guten Tag!\n\nSie haben diese Mail erhalten, weil auf <servername.tld> unter".
" Angabe Ihrer Mailadresse ein Eintrag ins Gästebuch vorgenmmen werden soll.\n".
'Daten zu der fraglichen Eintragung',
'time_recv' => 'Erstellungsdatum',
'name_recv' => 'Angegebener Name',
'ip_recv' => 'IP-Adresse',
'subj_recv' => 'Betreff der Nachricht',
'hp_recv' => 'Homepage',
'part2' => 'Sie haben die Möglichkeit anzuzeigen, ob diese Eintragung tatsächlich'.
' von Ihnen stammt oder nicht. Wenn ja, wird die Eintragung freigeschaltet und'.
' Ihre E-Mail-Adresse für die nächsten 30 Tage seit der letzten Eintragung'.
' freigegeben. Sie können in dieser Zeit ohne Nachfrage weitere Eintragungen'.
" vornehmen.\n".
'Für den Fall, daß diese Eintragung nicht von Ihnen stammt, können Sie Ihre'.
' E-Mail-Adresse für 30 Tage seit dem letzten Versuch sperren lassen. Die fragliche'.
' Eintragung wird dann verworfen, und während dieser Zeit ist es nicht möglich,'.
" Eintragungen mit Ihrer Mailadresse zu tätigen.\n".
'Wenn Sie nichts unternehmen, wird die Nachricht nach 7 Tagen verworfen.',
'do_verify' => 'Die Eintragung stammt von mir',
'do_blacklist' => 'Das war nicht ich',
'part3' => 'Diese Nachricht wurde automatisch erzeugt. Eine Antwort darauf ist nicht möglich.' },
'en' => { 'emailcheck' => 'Confirmation of your e-mail address',
'part1' => "Good day!\n\nYou have received this e-mail, because an entry is to be made".
" in the guest book on <servername.tld> with your e-mail address.\n".
'Information on the entry in question',
'time_recv' => 'Submission date',
'name_recv' => 'Name specified',
'ip_recv' => 'IP address',
'subj_recv' => 'Subject of the message',
'hp_recv' => 'Home page',
'part2' => 'You have the option of indicating whether or not the mail has been sent by'.
' you. If yes, your entry will be released and your e-mail address is'.
' whitelisted for the next 30 days beginning with your last entry. You may'.
" post freely within this period without having to confirm again.\n".
'In case you haven\'t posted the entry you may have your e-mail address blocked'.
' for the next 30 days beginning with the most recent attempt. The entry in'.
' question is discarded, and during this time it won\'t be possible to post'.
" with your e-mail address.\n".
'If you don\'t do anything, the message is dropped after 7 days.',
'do_verify' => 'That\'s my post',
'do_blacklist' => 'That hasn\'t been me',
'part3' => 'This message has been generated automatically. It\'s not possible to respond to it.' },
'fr' => { 'emailcheck' => 'Confirmation de votre adresse courrier électronique',
'part1' => "Bonjour!\n\nVous avez reçu ce message, parce qu\'il y a une entrée dans le".
' livre d\'or sur <nom-serveur.tld> avec votre adresse courrier électronique à'.
" publier.\n". 'Des données pour l\'entrée en question',
'time_recv' => 'Date d\'inscription',
'name_recv' => 'Nom donné',
'ip_recv' => 'adresse IP',
'subj_recv' => 'Sujet du message',
'hp_recv' => 'Page d\'accueil',
'part2' => 'Vous avez l\'option de signaler lorsque vous avez réellement inscrit cette entrée'.
' ou non. Si oui, l\'entrée sera débloquée et votre adresse courriel sera'.
' approuvée pour les suivants 30 jours en commençant de la dernière demande.'.
' Vous pouvez faire des additionelles entrées pendant cette période sans être'.
" demandés encore une fois.\n".
'Sinon, vous pouvez bloquer votre adresse courrier électronique pour les'.
' suivants 30 jours. L\'entrée en question sera rejetée, et il ne sera plus'.
' possible de faire des autres entrées avec votre adresse courriel pendant'.
" cette période.\n".
'Lorsque vous ne passez pas à l\'action, le message sera récusé après 7 jours.',
'do_verify' => 'C\'est mon entrée',
'do_blacklist' => 'Je ne l\'envoyai pas',
'part3' => 'Ce message était généré automatiquement. Il n\'est pas possible à y répondre.' } );
sub verify_email
{
my $p_date_sent;
my $p_recipient;
my $p_name;
my $p_subject;
my $p_homepage;
my $p_remote_host;
my $p_verify_key;
my $l_result = 1;
my $l_this_msg;
if(@_ != 7)
{
return undef;
}
($p_date_sent,$p_recipient,$p_name,$p_subject,$p_homepage,$p_remote_host,$p_verify_key) = @_;
$l_this_msg = $verify_mail{$p_lang}{'part1'}.":\n".
$verify_mail{$p_lang}{'time_recv'}.': '.localtime($p_date_sent)."\n".
$verify_mail{$p_lang}{'ip_recv'}.': '.$p_remote_host."\n".
$verify_mail{$p_lang}{'name_recv'}.': '.$p_name."\n".
$verify_mail{$p_lang}{'subj_recv'}.': '.$p_subject."\n".
$verify_mail{$p_lang}{'hp_recv'}.": ".$p_homepage."\n\n".
$verify_mail{$p_lang}{'part2'}."\n\n".
$verify_mail{$p_lang}{'do_verify'}.":\n".
"https://<servername.tld>/cgi-bin/misc/verify-me.pl?lang=$p_lang;action=confirm;id=$p_verify_key\n\n".
$verify_mail{$p_lang}{'do_blacklist'}.":\n".
"https://<servername.tld>/cgi-bin/misc/verify-me.pl?lang=$p_lang;action=reject;id=$p_verify_key\n\n".
$verify_mail{$p_lang}{'part3'};
$l_result = send_mail('noreply@<domain.tld>',$p_recipient,'',0,$verify_mail{$p_lang}{'emailcheck'},$l_this_msg);
if($l_result == -1)
{ $sth = $dbh->prepare(qq{insert into messages values (DEFAULT, ?, ?, ?, ?, ?, from_unixtime(?))});
$sth->execute($p_remote_host, $p_subject, $p_recipient, 8, $l_this_msg, $current_time + $globals[globm_grey_defer] * 60);
$sth->finish();
}
return $l_result;
};
Then you need to incorporate the following block, behind all the other checks
and before the entry is posted, that is:
if($do_checks && $globals[globm_operflags] =~ /force_verify/)
{
if(!@emaillist || $emaillist[ec_addrstate] eq "unknown")
{
my $l_verify = sha256_hex(rand(1024).$p_remote_host.$current_time);
$l_msgflags = 1;
$l_msgflags |= 4 if($globals[globm_operflags] =~ /gb_moderate/);
$do_checks = 0;
$has_confirm = 1;
$l_result = verify_email($current_time, $p_email2, $p_name, $p_subject, $p_hp2, $p_remote_host, $l_verify);
if(!defined $l_result)
{
output_error($outtext{$p_lang}{'err_internal'});
$dbh->do(qq{rollback});
}
elsif($l_result)
{
print $cgi->p({ -class => 'confirm' },$outtext{$p_lang}{'need-verify'});
$sth = $dbh->prepare(qq{insert into guestbook values ( DEFAULT, ?, ?, ?, ?, ?, ?, from_unixtime(?), ? )});
$sth->execute($p_name, $p_remote_host, $p_email2, $p_hp2, $p_subject, $p_message, $current_time, $l_msgflags);
$sth->finish();
$sth = $dbh->prepare(qq{select msgid from guestbook where email_addr = ? and flags & 1 = 1});
$sth->execute($p_email2);
$l_msgid = $sth->fetchrow_array;
$sth->finish();
$sth = $dbh->prepare(qq{insert into email_verify values ( ?, ?, ?, ?, from_unixtime(?), 2 )});
$sth->execute($p_email2, $p_remote_host, $l_verify, $l_msgid, $current_time + 86400 * $globals[globm_verify_tmout]);
$sth->finish();
$sth = $dbh->prepare(qq{update emailcheck set addrstate = 2 where email_addr = ?});
$sth->execute($p_email2);
$sth->finish();
$dbh->do(qq{commit});
}
else
{
print $cgi->p({ -class => 'negative' }, $outtext{$p_lang}{'no_smtp'});
$dbh->do(qq{rollback});
}
}
}
As you can see a means of disabling the confirmation mails is provided. This is
there in case that too many people feel molested about this, but it should be in
everybody's interest to block any nonsense from the 'Net whenever possible,
therefore this should only be disabled if absolutely necessary, e. g. in case of
legal action.
It is important that the mail doesn't contain anything that could be perceived
as an ad (see a
decision
of the Germen Federal court from December 15, 2015, file number VI ZR 134/15)
so that you don't risk a warning. Therefore keep the message neutral.
Once you have integrated this into your script, any entries with a nonsensical
e-mail address won't come through any more, because the confirmation mail is
undeliverable and the entries won't get confirmed. Even if someone manages to
bypass your spam traps and filters, it is here that he is stopped at the latest
– and even if a spammer enters his junk manally or has set up an automatic
confirmation of any confirmation mails, both moderating the guest book and
permanently banning the mail addresses in question helps stop this nonsense in
the future.
to the top
How guest book moderation helps