Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:
I'm creating a site where users vote for their favourite music CD or
something like that. Users do not register or anything like that to use the site, so
I'd like to know of a quick and easy way to prevent the user from voting more than once (within a given time period).
Of course, I know that nothing can ever be 100% accurate -- but I don't want someone just to
be able to reload the page 300 times to drastically skew the vote.
My idea was to use some kind of flat file where all the IPs and USER_AGENTs are stored (user agents used to narrow it down a bit among ISPs who proxy all requests), but I'm not sure exactly what the best way is. Perhaps cookies? Any help (and especially code!) would be appreciated.
Re: Quick and easy way to prevent multiple votes?
by ZZamboni (Curate) on Jun 08, 2000 at 06:21 UTC
|
If you are willing to make the users work a little more, you could
use a scheme in which a random key is given to the user before
he/she can vote, and has to be entered for the vote to be registered.
Then you store the random key for a certain period of time, during
which any votes that come with the same key are rejected.
The way I have seen this done (here)
is that you have to type in your email address, the key is
emailed to you, and then you enter it when you vote. This is
a form of registration, but it's not permanent and only an
email address is needed.
--ZZamboni
| [reply] |
Re: Quick and easy way to prevent multiple votes?
by plaid (Chaplain) on Jun 08, 2000 at 03:09 UTC
|
Cookies wouldn't work well, as they could be deleted by a
knowledgable user, who could then gain infinite votes. The
way that many sites do it (perlmonks and slashdot, for
instance), is by tracking IP. This has some margin of
error because of things like proxies, and people voting
from more than one IP, but is basically the best all-around
way to do it. | [reply] |
|
I've done both cookies and IPs in the past. Users coming in from dial-ups can always reconnect, delete the cookies and vote again, but that is such a pain in the 455 that I can't see someone doing it over and over and over. Even permanent IP users could spoof the address and do the same process, but that's even a worse case of nothing-better-to-do-with-ones-life.
#!/home/bbq/bin/perl
# Trust no1!
| [reply] |
Re: Quick and easy way to prevent multiple votes?
by PipTigger (Hermit) on Jun 08, 2000 at 03:58 UTC
|
I think it would be valuable to write all IPs to a file. Have a space separating the IP from the USER_AGENT on each line. Then suck each line into a regex like /(\S+)\s(.*)/ so that $1 has the IP and $2 is the rest of the info you'd want to store about each session. Make a hash $visitlog{$1}=$2 for each line. When you get a new submission, check if (defined $visitlog{$newipadd}) {
# maybe check $USER_AGENT && $othrsave stuff to examine
# if anything further about unique proxy users can be determined
#failure code && error mesg
} else {
# maybe check $USER_AGENT && $othrsave stuff to further
# determine if the user is attempting to revote.
#process vote && success mesg
$visitlog{$newipadd} = "$USER_AGENT" . $othrsave;
}
Then before you end the script, overwrite your logfile with the current visitlog hash with each line containing "$key $value". This is how I'd probably do it. Hope it's helpful. I'd be glad to try to help more if you'd like any. TTFN & Shalom.
-PipTigger | [reply] [d/l] |
|
> Then suck each line into a regex like /(\S+)\s(.*)/ so that $1 has the
> IP and $2 is the rest of the info you'd want to store...
Quick note: For a large list like the one potentially generated
by such a script, a regex will not be as efficient as
a split.
| [reply] |
|
I was under the impression that split employs regexes to do it's bidding and if no particular one is specified, it defaults to /\s+/. Maybe it also runs a =~ s/^\s+//; before matching the split field. I'm not sure about the underlying implementation but I thought one of my O'Reilly boox described split like this. Is that not the case? TTFN & Shalom.
-PipTigger
| [reply] |
Re: Quick and easy way to prevent multiple votes?
by Apterigo (Scribe) on Jun 08, 2000 at 03:46 UTC
|
Why not just use the CGI Enviornment Variable REMOTE_ADDR
to ensure that they this IP has not voted previously. IPs
that vote could be stored in a file which could later be
parsed to ensure that that IP has not voted. If they have
not voted, it would allow them to, and if they have, they
would receive a message such as "You have already voted
from this IP."
Apterigo | [reply] |
|
The main problem with this, and others below that only
use the IP address, is that proxies mess everything up.
Glancing through my access_log, I seem to have an awful lot
of people from cache-rc09.proxy.aol.com and similar hosts.
You need to either specify a timeout, use cookies, or
use the HTTP_USER_AGENT value. Better yet, use all three.
Start by checking for a cookie. If it is found, stop (don't
allow the vote). If not, check the IP. If it has not been
seen before, go (allow the vote, save the IP). Otherwise, check the
user agent. If it's new, go. If not, check the timeout.
If it's over a certain time (say, 2 days) you might allow
it anyway. Some pseudo-code:
$ip=$ENV{'REMOTE_HOST'};
$br=$ENV{'HTTP_USER_AGENT'};
$timeout = 60*60*24*2; ## sec x min x hours x 2 days = seconds in 2 da
+ys
$cookie_found and &NoVote; ## NoVote exits
## Load data file, check for a match
open (IP, "< $ipfile") or &SeriousError;
$found=0;
while(<IP>) {
m/^$ip/ or next;
$found=1;
## IP matches - does the browser?
(undef,$brow, $time) = split(/##/,$_);
if ($br eq $brow) {
## Browser matches too - allow a timeout?
$^T-$time>$timeout and &Vote; ## exits
}
}
&Vote if !$found; ## This is a new IP
&NoVote;
sub Vote {
## Voting code here
## Set a cookie
print "Set-cookie: etc...";
##..and in case that doesn't work or they delete it:
if (open(IP, ">>$ipfile")) {
print "$ip##$br##$^T\n";
close(IP);
}
exit;
}
A final trick to slow down ballot-stuffing (someone
*could* write a perl script that changes the user agent
every time, in theory) is to limit the rate of voting by
sticking a sleep(15) in there, or by allowing the same
IP but different user agents to vote only after a timeout
of 30 seconds.
| [reply] [d/l] |
|
| [reply] |
|
Please be nice. Many people have never had to consider this problem before and therefore would have no way of knowing these things. Maybe it's YetAnotherTime for you but that's what cooperative forums for a community of any sort have to deal in 99% of the time. That's what FAQ's are all about and why it's great to reference them for answers. Thanks very much. TTFN & Shalom.
-PipTigger
| [reply] |
|
|
|
RE: Quick and easy way to prevent multiple votes?
by Anonymous Monk on Jun 08, 2000 at 19:09 UTC
|
Thank you for all your interesting ideas and suggestions. The code I have copy with now is
sub already_voted {
my ($ip_address, $user_agent) = @_;
my (@lines, $line, $file_ip, $file_ua, %voters, $voted);
# Open our list of IP address and user agents.
# This is in the format:
#
# IP_address:::UserAgent
#
open (IPADDRESS, "<ips.txt");
@lines = <IPADDRESS>;
close IPADDRESS;
# Parse the array and store IP and User Agent in a hash.
foreach $line (@lines) {
chomp $line; # Remove \n
($file_ip, $file_ua) = split(/:::/, $line);
$voters{$file_ip} = $file_ua;
}
# Check to see if the IP of the person currently voting
# is in the hash.
if (exists($voters{$ip_address})) {
# It looks like the same person is voting again. Stop the
# evil person. But wait, let's check the user agents too.
if ($voters{$ip_address} eq $user_agent) {
# Same IP, same user-agent. Looks like a duplicate vote.
$voted = 1;
sharedcode::logfile("Bad: dup vote $ip_address and $user_agent."
+);.
}
}
# Let's put *this* vote into the file:
open (IPFILE, ">>ips.txt");
print IPFILE "$ip_address".':::'."$user_agent\n";
close IPFILE;
if ((-M "ips.txt") >= 7) {
# File is getting old, let's clear the file.
open IPFILE, ">ips.txt";
print IPFILE " ";
close IPFILE;
}
return $voted;
}
Obviously, this code isn't going to trap 100% of the cheaters, but that's fine by me.
Thanks again for all your suggestions.
| [reply] [d/l] |
(jcwren) Re: Quick and easy way to prevent multiple votes?
by jcwren (Prior) on Jun 08, 2000 at 17:45 UTC
|
Heh. This about the time everyone starts thinking that maybe Intel didn't have such a bad idea with the processor serial numbers...
The problem with requiring an email address to vote may make it more awkward for internet cafe and handheld devices users.
No scheme that doesn't require a user to register is going to be full-proof. One of the determinations you have to make is how hard is someone going to try before it's not worth thier effort. As an example, the security on my system at work is tight, but only in the context that our users are naive. If someone fires up a packet sniffer, then my system can be compromised.
You also have to evaluate why someone would want to vote 300 times. Is there a cash reward at stake? Prestige? If they vote for Britney Spears 300 times, will her "music" get played that much more?
It can be hard to be objective about your site, because it's got your hard work in it. The site I manage at work they swore was going to need a T1 feedg, redundant servers, RAID, yada yada yada. The reality is if we get 100 hits a day, we'll be doing good. Now, it's targetted at an extremely specific market, so our information is important, but we're not running a site that's gonna get traffic like CNN or SlashDot.
If you can provide a simple method of preventing multiple votes, that may be all you need. Using the random number scheme mentioned above has some definite merit. Requiring accountability, however, is probably the best way. Just keep in mind who may be using your site, how often, and most importantly, WHY.
--Chris | [reply] |
RE: Quick and easy way to prevent multiple votes?
by BigJoe (Curate) on Jun 08, 2000 at 04:04 UTC
|
open(INFILE, "< listofips.txt");
$filesize= -s INFILE;
read(INFILE, $wholepage, $filesize);
close(INFILE);
if( !($wholepage =~ m/$ENV{'REMOTE_ADDR'}/) )
{
open(OUTFILE, ">listofips.txt");
print OUTFILE "$wholepage\n";
print OUTFILE $ENV{'REMOTE_ADDR'};
close(OUTFILE);
# add vote stuff here
}
This should be kinda what you are looking for. I think there is a simpler way. I just don't know it yet. | [reply] [d/l] |
|
Yeah, there are simpler ways to keep a list of IPs. As the list grows, time to check them increases tremendously.
If you really must do it this way, use a database. Even something simple like DBD::CSV would help immensely.
Specific things in your code that I'd do differently are:
- Error checking (always check the return value of open calls!)
- Open the IP list for appending, at least. ">> listofips.txt". That way, you don't have to write the whole thing at once.
- Setting $/ to undef and slurping the whole file into a scalar instaed of using read.
But tracking IP addresses really isn't the way to go here, as merlyn and others have pointed out.
| [reply] |
|
|