#!/usr/local/bin/perl
# 4.3.1997-2002, 2003-02-19 J.E. Klasek jk AT auto tuwien ac at
# V 2.4
#
# Generic counter script for a Server Side Include or image reference usage.
#
# based on ``CGI Programming in the WWW, Shishir Gundavaram, O'Reilly Assoc., Inc.,
# pp. 94
# inline xbm image technique from "Yet Another Access Counter" by Omar Syed
#
# Usage:
# Rename or link counter to one of following names:
# Textcounter:
# counter.cgi textual count value (usage in SSI environments only)
# counter.quiet.cgi hidden count up (no output, in SSI env. only)
#
# Graphical counters:
# counter.gif.cgi graphic count value (image/gif), for reference with
# tag.
# Need external text to gif converter tool, see
# configuration section below!
#
# counter.img.cgi graphic count value (image/xbm)
# counter.img0.cgi ... with leading zeros, default length
# counter.img06.cgi ... with leading zeros, length 6
# counter.img-.cgi ... inverse (white on black)
# counter.img-0.cgi ... inverse with leading zeros, default length
# counter.img-06.cgi ... inverse with leading zeros, length 6
# counter.img.quiet.cgi ... hidden counter (1 transparent Point)
#
# Initialize the counter by calling the script interactively as follows:
# counter.cgi init
# Note: this is only allowed to the owner of link or real file of counter!
# At the first time this creates the file counter.cnt containing the count
# value in ASCII. Be sure that the HTTP server's CGI-call permit an update
# of the .cnt file content!
# Another file, counter.ref will be created which acts as time referencefile for the
# starting point of counting.
#
# Each further call (e.g. from cron) with the "init" parameter resets the
# counter to zero and creates a automatically numbered history
# (counter.cnts/counter.cnt.NUMBER) file with following content:
#
# 1891
# 1999274:00:05:00:10/01/1999
# 1999305:00:05:00:11/01/1999
#
# The first line represents the ASCII value of the last counter value, incremented
# so far during the intervall represented by the timestamps in the second
# and third line. The format of the timestamp (with certain redundancy for ease
# of extraction):
#
# 1999305:00:05:00:11/01/1999
# YYYYTTT hh mm ss MM DD YYYY
#
# YYYY the year, always 4 digits
# TTT day of year, alway 3 digits
# hh, mm, ss time with hours, minutes and seconds
# MM DD YYYY date with month, day of month and year (4 digit)
#
# HTML-Embedding:
# Serverside Includes (SSI) for .shtml files or with XBitHack (Apache: files with
# execute permissions):
#
# text counter (SSI): ,
# Reference:
#
# Image Counter (with value image or hidden):
#
# counter 7-digit:
# counter hidden:
#
# Logging:
# If file COUNTERNAME.log exist for each countercall a
# line with the access timestamp and the remote host information
# (host IP and name - if the latter exists).
# Mind the permission of the file, it must be writeable in the CGI-context
# (for the httpd-User or the user in a suexec oder cgiwrap environment)!
# Example:
# 2003051:10:43:52:02/20/2003:128.131.34.67:jk.kom.tuwien.ac.at
# ^ IP-Address ^ Hostname (if resolvable)
# See timestamp format above for a description.
#
#
# CGI-Errors:
# There is only one error condition reported:
#
# text mode (only in non-quiet operation) image mode diagnostic
# ------------------------------------------------------------------------------
# Unable to modify the counter! 99999991 The counter file has not the
# proper permissions (from the
# web servers perspective).
#
# CGI-Parameters:
# CGI parameters are ignored (...?xx+xxx+xxx or .../xxx/xxx/xxx). It is therefore
# not possible to initialize the counter from a website.
#
# Modified by J.E. Klasek, 2/1997
# Added management option for initializing the counter file and
# the counter date reference file.
#
# Revisions:
# V 2.0 18.6.1999 jk
# Using counter file locking for mutual exclusion for
# counter update.
# Counter archiving (init) in seperate directory COUNTERNAME.cnts.
#
# V 2.0a 23.8.1999 jk
# Counterfile with permission 644 instead of 777 for use with cgiwrap, where
# cgi scripts runs with user permission.
# V 2.1 26.8.1999 jk
# Add counter.img.cgi tag, in which case a GIF image is produced.
# Fixed error processing in quiet-mode.
# V 2.1.1 17.2.2000 jk
# Fixed: cdate: year correction obsolete, text2gif variable usage
# V 2.2 22.2.2000 jk
# Added: inline xbm image generation with empty digits;
# CGI error handling.
# Fixed: .ref file generation
# V 2.2.1 15.3.2001 jk
# Fixed: quiet operation produces empty content (but with content-type!)
# to prevent Apache error message "Premature end of script headers".
# V 2.3 15.9.2001 jk
# New: Log source address/hostname if logfile exists.
# V 2.4 19.2.2003 jk
# Fixed: missing non-leading "0" in counter graphic on some occasions.
# New: img.quiet modifier, produces a hidden 1x1 point transparent
# image.
#
################ CONFIGURATION #############################################
#
# text to GIF converter tool, last parameter with the "text" is appended.
# called via system() (/bin/sh).
# ONLY used with tag "gif", e.g. the script is named "counter.gif.cgi"!
$text2gif = "/usr/bin/X11/text2gif -t ";
#
#
#############################################################################
### initialising
@pattern = (0x00,0x00,0x00,0x3c,0x66,0x66,0x66,0x66,
0x66,0x66,0x66,0x66,0x3c,0x00,0x00,0x00,
0x00,0x00,0x00,0x30,0x38,0x30,0x30,0x30,
0x30,0x30,0x30,0x30,0x30,0x00,0x00,0x00,
0x00,0x00,0x00,0x3c,0x66,0x60,0x60,0x30,
0x18,0x0c,0x06,0x06,0x7e,0x00,0x00,0x00,
0x00,0x00,0x00,0x3c,0x66,0x60,0x60,0x38,
0x60,0x60,0x60,0x66,0x3c,0x00,0x00,0x00,
0x00,0x00,0x00,0x30,0x30,0x38,0x38,0x34,
0x34,0x32,0x7e,0x30,0x78,0x00,0x00,0x00,
0x00,0x00,0x00,0x7e,0x06,0x06,0x06,0x3e,
0x60,0x60,0x60,0x66,0x3c,0x00,0x00,0x00,
0x00,0x00,0x00,0x38,0x0c,0x06,0x06,0x3e,
0x66,0x66,0x66,0x66,0x3c,0x00,0x00,0x00,
0x00,0x00,0x00,0x7e,0x66,0x60,0x60,0x30,
0x30,0x18,0x18,0x0c,0x0c,0x00,0x00,0x00,
0x00,0x00,0x00,0x3c,0x66,0x66,0x66,0x3c,
0x66,0x66,0x66,0x66,0x3c,0x00,0x00,0x00,
0x00,0x00,0x00,0x3c,0x66,0x66,0x66,0x66,
0x7c,0x60,0x60,0x30,0x1c,0x00,0x00,0x00
);
### setup
($dev,$ino,$mode,$nlink,$uid) = lstat($0);
# check against effective uid
if ($#ARGV >= 0 && $> != $uid && $> != 0 && ! defined($ENV{"GATEWAY_INTERFACE"}) ) {
# only linkowner are allowed to do special
warn "Illegal operation: permission denied (not owner of link or script).\n"; # processing
exit(1);
}
### path & name analysation
$base = $0;
$base =~ s|[.][^./]*$||; # path without extension
$fquiet = 0;
if ($base =~ m|^(.*)\.([^./]+)$|) {
$base = $1; # remove tag, like ``.quiet''
$tag = $2;
if ($tag eq "quiet") {
$fquiet = 1;
$base = $1;
$base =~ m|^(.*)\.([^./]+)$|;
$base = $1;
$tag = $2;
}
if ($tag =~ /^(img|gif)(-)?(\d)?(\d)?/) {
$fimg = 1;
if ($1 eq "img") { $fimg++; } # internal image
if (defined($2)) { $finv = 1; }
$digits = 5;
if ($3 eq "0") { $fzero = 1; $digits = $4 if defined($4); }
elsif (defined($3)) { $digits = $3; }
$digits = $digits > 7 ? 7 : ($digits < 3 ? 3 : $digits);
}
}
($count_name ) = $base =~ m|([^/]*)$|;
$count_dir = "$base" . ".cnts";
$count_file = $base . ".cnt";
$count_dfile = "$base.cnts/$count_name" . ".cnt";
$count_log = $base . ".log";
### interactive init
# check against effective uid
if ($#ARGV == 0 && ($> == $uid || $> == 0) && !defined($ENV{"GATEWAY_INTERFACE"}) ) {
if ($ARGV[0] eq "init") {
$count_ref = $base . ".ref";
if (open(REF,$count_ref)) {
if (! -d $count_dir) {
mkdir($count_dir,0750); # asure some kind of privacy
}
$count_old = $count_file;
if (-d $count_dir) { $count_old = $count_dfile; }
$bucnt = 1;
while(-e "${count_old}.$bucnt") { $bucnt++; }
$count_save = $count_old . "." . $bucnt;
(-e $count_file) || die "Counter file missing ($count_file)\n";
rename($count_file,$count_save);
chmod( 0640, $count_save); # turn back permissions
print "Created counter result file $count_save.\n";
# create new counter file immediate after removing
open(FILE,">".$count_file);
if (defined(FILE)) {
print FILE "0"; close(FILE);
print "Initialize counter to 0 in file $count_file.\n";
}
# assemble a result file with counter value, begin date and end date
open(SAVE,">>".$count_save) || die "Can't append to $count_save\n";
print SAVE "\n";
print SAVE [;
print SAVE cdate()."\n";
close(SAVE);
close(REF);
}
else {
warn("Date reference file does not exist. Creating a new one.\n");
open(FILE,">".$count_file);
if (defined(FILE)) {
print FILE "0"; close(FILE);
print "Initialize counter to 0 in file $count_file.\n";
}
else { die "Can't create new counter file ($count_file)\n"; }
}
# file must be writeable for the http daemon
chmod( 0644, $count_file) || (unlink($count_ref),die("Can't create counter file ($count_file).\n"));
open(REF,">" . $count_ref) || die("Can't create date reference file ($count_ref).\n");
print REF cdate()."\n";
close(REF);
print "Created date reference file $count_ref.\n";
exit(0);
}
elsif ($ARGV[0] ne "help") {
warn "Illegal parameter(s).\n";
}
warn "usage: $0 init|help\n";
warn "\tinit\tinitialize counter to 0 and touch the date reference.\n";
warn "\thelp\tprints this text.\n";
exit(1);
}
### count action
if (-w $count_log) {
open(LOG, ">>$count_log");
print LOG cdate().":".$ENV{"REMOTE_ADDR"}.":".$ENV{"REMOTE_HOST"}."\n";
close(LOG);
}
if (open(FILE, "+<$count_file")) { # open existing file, DO NOT TRUNCATE!
# NOTE: other modes like e.g. ">" do always truncate!
flock(FILE,2); # lock for atomic read/modify/write action
$access_count = ; # read current value
seek(FILE,0,0); # back to begin (overwrite old value)
$access_count++; # count up
print FILE "$access_count"; # write back
flock(FILE,8); # unlock
close(FILE);
}
else {
# count action failed
if ($fimg) {
$access_count = -1; # can't access counter file
}
else {
$access_count = "[ Unable to modify the counter! ]\n";
}
}
# be quiet if called with parameter ``quiet''
if (! $fquiet && !$fimg ) { print "Content-type: text/plain\n\n$access_count"; }
elsif ($fquiet && !$fimg) {
# empty content, but send at least the content type to prevent
# an error log entry from Apache.
print "Content-type: text/plain\n\n";
}
else {
# we have to produce an image ...
# prepare counter value as string
if ($access_count < 0) {
$counttext = "9999999".(-$access_count); # error image
$digits = 8;
}
else {
$counttext = sprintf("%".($fzero?"0":"")."${digits}u",$access_count);
}
if ($fimg == 1) {
$| = 1;
print "Content-type: image/gif\nPragma: no-cache\nExpire: now\n\n";
system("$text2gif \"".$counttext."\"");
}
else {
# internal image generation
print "Content-type: image/xbm\nPragma: no-cache\nExpire: now\n\n";
if ($fquiet) {
# 1 Punkt Image
# Generate an X11 bitmap
print "#define count_width 1\n#define count_height 1\n";
print "static char count_bits[] = { };\n";
}
else {
# Generate an X11 bitmap
printf "#define count_width %d\n#define count_height 16\n", $digits*8;
printf "static char count_bits[] = {\n";
for ($y=0; $y < 16; $y++) {
for ($x=0; $x < $digits; $x++) {
$d = substr($counttext,$x,1) - '0';
if ($d != 0) { $fzero = 1; }
if (!$fzero && $d == 0 && $leading) {
printf $finv?"0xff":"0x00";
}
elsif ($finv) {
printf "0x%02x",$pattern[($d * 16) + $y] ^ 0xff ;
} else {
printf "0x%02x",$pattern[($d * 16) + $y] ;
}
if ($d != 0) { $leading = 1; }
if ($x < $digits-1) {
print ',';
}
}
if ($y==15) {
print '};';
} else {
print ',';
}
print "\n";
} # for
} # !$fquiet
} # ! $fimg == 1
}
exit(0);
### get date/time information in special format
sub cdate {
my($sec,$min,$hour,$day,$mon,$year,$wday,$yday,$dst) = localtime(time);
$mon++; # month starts from 0 for january
$yday++; # starts from 0
$year += 1900; # offset from year 1900
# easy to sort date, ":" delemiter for post processing with
# full date for human readablity
# format: YYYYDDD:hh:mm:ss:MM/DD/YYYY
return sprintf("%04d%03d:%02d:%02d:%02d:%02d/%02d/%04d",$year,$yday,$hour,$min,$sec,$mon,$day,$year);
}
]