Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling

Quick and easy way to prevent multiple votes?

by Anonymous Monk
on Jun 08, 2000 at 02:58 UTC ( #16981=perlquestion: print w/replies, xml ) Need Help??

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.

  • Comment on Quick and easy way to prevent multiple votes?

Replies are listed 'Best First'.
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.


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.


      > 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.

        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.

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.
      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.

      # Trust no1!
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."

      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 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.

      Argh! Not the "IP for unique votes" cargo-cult junk again! How many times do we need to go through this?

      Repeat after me:

      • An IP address is not a user
      • An IP address is not a user
      • An IP address is not a user
      Trivial counter-examples:
      1.'s proxy, where every hit comes from a different address, even within the same session
      2. Dialup sessions (including most cable modems) where every session is a different IP
      3. Proxy servers for nearly all of the corporate access
      Please don't make me have to repeat this stuff Yet Another Time.

      -- Randal L. Schwartz, Perl hacker

        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.

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.
(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.

RE: Quick and easy way to prevent multiple votes?
by BigJoe (Curate) on Jun 08, 2000 at 04:04 UTC
    try something like
    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.
      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.

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://16981]
Approved by root
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others meditating upon the Monastery: (7)
As of 2022-05-19 11:34 GMT
Find Nodes?
    Voting Booth?
    Do you prefer to work remotely?

    Results (71 votes). Check out past polls.