#!/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 # counter hidden: counter # # 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); }