#!/usr/bin/perl
#####################################################################
# ipcounter						version 3.34
# Copyright 2000-2002			Marc Rasell
# Created February 2000			last modified 6 Mar 2003
# http://www.rasell.plus.com/ipcounter
#####################################################################
# COPYRIGHT NOTICE
# Copyright 2000-2002 Marc Rasell  All Rights Reserved.
#
# ipcounter may be used and modified free of charge by anyone so
# long as this copyright notice and the comments above remain
# intact.
#
# By using this code you agree to indemnify Marc Rasell
# from any liability that might arise from its use.
#
# Selling the code for this program without prior written consent is
# expressly forbidden.
#
# Obtain permission before redistributing this software over the
# Internet or in any other medium. In all cases copyright and header
# must remain intact.
#####################################################################
# Instructions
#
# counts unique IP per day
# the same script can be used for all pages
#
# counter link ->
#     (visible counting using SSI) <!--#include virtual="/cgi-bin/ipcounter.pl" -->
# OR  (invisible counting without SSI) <img src=/cgi-bin/ipcounter.pl width=1 height=1>
#
# login link ->
#     http://yourdomain.com/cgi-bin/ipcounter.pl?login
#
#####################################################################
# Define variables

# referers - set to your web site domain(s)
@referers = ("pinkemostar.com");

# administrators password
$password = "elliot13";

# log path - the path to the logs folder (create a sub directory in your cgi-bin and chmod 777)
$log_path = "/usr/pkg/libexec/cgi-bin/jess_ipcounter";

# log date determines how often new log files are created
# options = "month" or "year" or "once"
$log_date = "year";

# script name (no need to alter unless you change the name of the script)
$script_name = "ipcounter.pl";

# log name (no need to alter unless you want to change the name of the log files)
$log_name = "ipcounter";

# log host (option to log the remote host of visitors)
# options = "on" or "off"
$log_host = "on";

# There is no need to edit below this line
#####################################################################
# Other variables

# maximum amount of data that can be sent to the program
$max_data = 1024;

# ip and host of visitor
$ip = $ENV{'REMOTE_ADDR'};
$host = $ENV{'REMOTE_HOST'};

# use Fcntl symbolic names
use Fcntl qw(:DEFAULT :flock);

#
#####################################################################

# print http header
&print_header;

# get date
&get_date;

# create log files
&create_log_names;

# check referring url
&check_url;

# parse form
&parse_form;

# main subroutine
&main;

# exit program
exit;

#####################################################################

sub error
{
    local ($error);
    $error = $_[0];
    print <<"HTML";
<HTML>
<HEAD><TITLE>Error!</TITLE></HEAD>
<BODY>
<H1>Error!</H1>
An error has occured: $error
<br>
<p>
<hr>
<center>
<input type="button" value="Return to the previous page"
onClick="history.back();">
</center>
</body>
</html>
HTML
    exit;
}

sub parse_form
{
    local(@pairs,$buffer,$pair,$name,$value);

    # stop overload of data from form
    if ($ENV{'CONTENT_LENGTH'} > $max_data)
    {
        error_log('data overload', 'die');
    }

    if (uc($ENV{'REQUEST_METHOD'}) eq 'GET')
    {
        # Split the name-value pairs
        @pairs = split(/&/, $ENV{'QUERY_STRING'});
    }
    elsif (uc($ENV{'REQUEST_METHOD'}) eq 'POST')
    {
        # Get the input
        read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});

        # Split the name-value pairs
        @pairs = split(/&/, $buffer);
    }
    else
    {
        error_log('request_method', 'die');
    }

    foreach $pair (@pairs)
    {
        ($name, $value)=split(/=/, $pair);

	$name=~ tr/+/ /;
	$name=~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex ($1))/eg;

	$value=~ tr/+/ /;
	$value=~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex ($1))/eg;

	# remove metacharacters
	$name  = remove_meta($name);
	$value = remove_meta($value);

	$contents{$name}=$value;
    }
}

sub lockfile
{
	my $count = 0;
	my $handle = shift;

	until (flock($handle, LOCK_NB | LOCK_EX))
	{
		select (undef, undef, undef, 0.1);
		if (++$count>50)
		{
			error_log('server busy, try again later', 'die');
		}
	}
}

sub error_log
{
    open (LOG, ">>$error_log") or
    error "can't open $error_log: $!<br>\n$_[0]";
    lockfile(LOG);
    seek LOG, 0, 2 or die "can't seek to end of $error_log: $!";
    print LOG $_[0] . "|DATE:$date|TIME:$time|IP:$ip|HOST:$host\n" or die "can't write to $error_log: $!";
    close(LOG) or die "can't close $error_log: $!";
    if ($_[1] ne 'warn')
    {
        if ($_[1] eq 'hide') {&error('an internal error has occured');}
        else {&error($_[0]);}
    }
}

sub get_date
{
    local(@days,@months,$sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst,$month);

    @days   = ('Sunday','Monday','Tuesday','Wednesday',
               'Thursday','Friday','Saturday');
    @months = ('January','February','March','April','May','June','July',
	         'August','September','October','November','December');

    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

    $year += 1900;
    if ($mday < 10) {$mday = "0$mday";}
    if ($mon < 10) {$mon = "0$mon";}
    $month = ++$mon;
    $date = "$mday/$month/$year";
    $current_month = "$year/$month";

	$current_log_month = "$year$month";
	$current_log_year = $year;

    if ($hour < 10) {$hour = "0$hour";}
    if ($min < 10) {$min = "0$min";}
    if ($sec < 10) {$sec = "0$sec";}
    $time = "$hour\:$min\:$sec";
}

sub remove_meta
{
    $_[0] =~ tr/&;`'\"|*?~<>^()[]{}$\n\r/_/;
    return $_[0];
}

sub login
{
    print <<"HTML";
<html>
<body>
<center>
<h2>IP Counter</h2>
<form action=$script_name method=post>Enter admin password below to login<br>
<input type=password name=password><br>
<input type=submit name=login value=Login>
<input type=hidden name=login>
<input type=reset name=clear_box value=\"Clear Box\">
</form>
</body>
</html>
HTML
}

sub count
{
    local($unique_visitor,$line,@history,$count);
    sysopen (HISTORY, "$log_file", O_CREAT | O_RDWR) or error_log("can't open $log_file: $!", "hide");
    lockfile(HISTORY);
    seek HISTORY, 0, 0 or die "can't seek to start of $log_file: $!";
    $unique_visitor = 1;
    $count = 0;
    while (<HISTORY>)
    {
        $count++;
        $line = $_;
        chomp $line;
        @history = split (/\|/, $line);
        if ($history[0] eq $date && $history[1] eq $ip)
        {
            $unique_visitor = 0;
        }
    }
    if ($unique_visitor)
    {
        $count++;
        if ($log_host eq "on")
        {
            print HISTORY "$date|$ip|$host\n" or error_log("can't write to $log_file: $!", "hide");
        }
        elsif ($log_host eq "off")
        {
            print HISTORY "$date|$ip\n" or error_log("can't write to $log_file: $!", "hide");
        }
        else
        {
            error_log('invalid log host option', 'die');
        }
    }
    close (HISTORY) or error_log("can't close $log_file: $!", "hide");
    print "$count";
}

sub admin
{
    local($count,$line,@history,%dates,@split_date,$month,%months,$year,%years,$key);
    &check_password;
    sysopen (HISTORY, "$log_file", O_CREAT | O_RDONLY) or error_log("can't open $log_file: $!", "hide");
    lockfile(HISTORY);
    seek HISTORY, 0, 0 or die "can't seek to start of $log_file: $!";
    $count = 0;
    while (<HISTORY>)
    {
        $line = $_;
        chomp $line;
        @history = split (/\|/, $line);
        $history_date = $history[0];
        @split_date = split (/\//, $history_date);
        $month = $split_date[2] . "/" . $split_date[1];
        $year = $split_date[2];
        $count++;
        if ($month eq $current_month) {$dates{$history_date}++;}
        $months{$month}++;
        $years{$year}++;
    }
    close (HISTORY) or error_log("can't close $log_file: $!", "hide");
    print <<"HTML1";
<html>
<h2><center>IP Counter</center></h2>
<center><form action=$script_name method=post>Enter admin password below<br>
<input type=password name=password><br>
Enter an alternate month below or leave blank to view the current month<br>
<input type=text name=other_month><br>
<input type=submit name=refresh value=\"Refresh Page\">
<input type=hidden name=refresh>
</form></center>
<h3><strong>Unique Visitors</strong></h3>
<ul>
    <li><strong>Visitors This Month: $current_month</strong>
        <ul>
HTML1
    foreach $key (reverse sort keys %dates)
    {
        print "<li>$key -> $dates{$key}\n";
    }
    print <<"HTML2";
        </ul>
    <li><strong>Visitors Per Month</strong>
        <ul>
HTML2
    foreach $key (reverse sort keys %months)
    {
        print "<li>$key -> $months{$key}\n";
    }
    print <<"HTML3";
        </ul>
    <li><strong>Visitors Per Year</strong>
        <ul>
HTML3
    foreach $key (reverse sort keys %years)
    {
        print "<li>$key -> $years{$key}\n";
    }
    print <<"HTML4";
        </ul>
    <li><strong>Total -> $count</strong>
</ul>
</html>
HTML4
}

sub check_url
{
    local($check_referer) = 0;

    if ($ENV{'HTTP_REFERER'})
    {
        foreach $referer (@referers)
        {
            if ($ENV{'HTTP_REFERER'} =~ m|https?://([^/]*)$referer|i)
            {
                $check_referer = 1;
                last;
            }
        }
    }
    else
    {
	# allow the script to run even if there is no referer
        $check_referer = 1;
    }
    if ($check_referer != 1) {error_log('bad_referer', 'die');}
}

sub main
{
    if ($ENV{'QUERY_STRING'} eq "login") {&login;}
    elsif (exists($contents{'login'})) {&admin;}
    elsif (exists($contents{'refresh'})) {&refresh_page;}
    else {&count;}
}

sub print_header
{
    print "Content-type: text/html\n\n";
}

sub refresh_page
{
    local ($other_month,@split_other_month,$old_error_log);
    &check_password;
	$other_month = $contents{'other_month'};
	if ($other_month)
	{
		if ($other_month =~ m|\d{4,4}/\d{2,2}|)
		{
			$current_month = $other_month;
            if ($log_date ne 'once')
            {
                @split_other_month = split (/\//, $other_month);
                $current_log_year = $split_other_month[0];
                $current_log_month = "$split_other_month[0]$split_other_month[1]";
				$old_error_log = $error_log;
                &create_log_names;
				$error_log = $old_error_log;
                if (!-e "$log_file")
                {
                    error_log('no log file exists for that month', 'die');
                }
            }
        }
        else
        {
            error_log('invalid month format, please use NNNN/NN e.g. 2001/01', 'die');
        }
    }
    &admin;
}

sub check_password
{
    if ($password ne $contents{'password'})
    {
        error_log('invalid password', 'die');
    }
}

sub create_log_names
{
    local ($log_date_format);
    if (!$log_path)
    {
        error_log('no log path', 'die');        
    }
    if ($log_date ne 'once')
    {
        if ($log_date eq 'month')
        {
            $log_date_format = ".$current_log_month";
        }
        elsif ($log_date eq 'year')
        {
            $log_date_format = ".$current_log_year";
        }
		else
		{
            error_log('invalid log date option', 'die');
		}
    }
    else
    {
        $log_date_format = "";
    }
    # path to the student number and password list
    $log_file = "$log_path/$log_name$log_date_format.log";

    # error log
    $error_log = "$log_path/$log_name$log_date_format.err";
}

