static char rcs_id[] = "$Id: http-analyze.c,v 2.0.1.15 1998/04/02 12:28:24 stefan Exp stefan $"; /* ************************************************************************* ** http-analyze 2.0 beta ** ** Analyzes web server logfiles and creates 2D and 3D summaries ** by year, month, and day. ** ** Copyright Š 1996-1998 by Stefan Stapelberg/RENT-A-GURUŽ, ** ** RENT-A-GURUŽ is a registered trademark of Martin Weitzel, Stefan ** Stapelberg, and Walter Mecky. NetstoreŽ and the Netstore logo is ** a registered trademark of Stefan Stapelberg. ** ************************************************************************* ** ** HTTP-ANALYZE NON-COMMERCIAL LICENSE ** ** Use of http-analyze indicates your acceptance of this license agreement. ** Please read the following paragraphs and use this software only if you ** agree to the terms contained herein. For commercial licensing please ** visit our web site or contact RENT-A-GURU by email. RENT-A-GURU has ** exclusive licensing rights for use of http-analyze for commercial ** purposes. ** ** This license applies to the computer program(s) known as http-analyze. ** The term "program" used in this license agreement refers to such ** program including all output files (HTML files, VRML files, image ** files) it generates, and the term "work based on the program" means ** either the program or any derivative work of the program including ** it's output files, such as a translation into another language, ** extending the program's functionality, use of parts of the program or ** it's output files or any modification of the program or it's output ** files. The program is a copyrigthed work whose copyright is held by ** Stefan Stapelberg, RENT-A-GURU (the "licensor"). ** ** 1. License terms ** ** Licensor hereby grants you the following rights, provided that you ** comply with all of the restrictions set forth in this license and ** provided further, that you distribute an unmodified copy of this ** license with the program: ** ** (a) You may copy and distribute the program's unaltered source code ** worldwide via any medium. Any distribution of the program or any ** programs containing source code of http-analyze as well as any output ** created with the program must contain an appropriate credit and all ** additional materials included with the original distribution, including ** the unaltered license agreement. ** ** (b) You may use the program for non-commercial purposes only, meaning ** that the program must not be sold commercially as a separate product, ** as part of another product (bundled software) or project, or used by ** service providers to provide statistics services to their webspace ** customers, or otherwise used for financial gain without a separate ** commercial license. Please see section 2, Restrictions, for details. ** ** (c) You may build derived versions of this software under the ** restrictions stated in section 2 (Restrictions) of this license. The ** derived versions must be clearly marked as such and must be called by ** a name other than http-analyze. ** ** All derived versions of the program must be made freely available under ** the terms of this license. RENT-A-GURU must be given the right to use ** the modified source code in their products without any compensation and ** without being required to separately name the parties whose modifications ** are being used. ** ** Credit for http-analyze must be given to RENT-A-GURUŽ in all derived ** works and in all HTML and/or VRML output files created by the derived ** work. This does not affect your ownership of the derived work or the ** statistics reports itself, and the intent is to assure proper credit ** for RENT-A-GURU, not to interfere with your use of this derived work. ** ** 2. Restrictions ** ** (a) Distribution of the program or any work based on the program by a ** commercial organization to any third party is prohibited if any payment ** is made in connection with such distribution, whether directly (as in ** payment for a copy of the program) or indirectly (as in payment for some ** service related to the program, or payment for some product or service ** that includes a copy of the program without charge, or payment for some ** product or service the delivery of which requires for the recipient to ** retrieve/download or otherwise obtain a copy of the program; these are ** only examples, and not an exhaustive enumeration of prohibited activities). ** ** As an exception to the above rule, putting this program on CD-ROMs ** containing other free software is explicitly permitted even when a ** modest distribution fee is charged for the CD, as long as this ** software is not a primary selling argument for the CD. Two CDs must ** be sent to RENT-A-GURU without having RENT-A-GURU requesting it. ** ** (b) You may not change the essential layout of the HTML and/or VRML ** output files the program generates, except for those parts which can ** be changed by using the appropriate settings in the configuration file ** or via command line options. The Copyright notice and the link to the ** homepage of http-analyze as produced by the program must remain intact ** in all HTML and/or VRML output files created by the program and must be ** included in all HTML and/or VRML output files created by work based on ** the program. ** ** (c) Activities other than copying, distribution and modification of ** the program are not subject to this license and they are outside it's ** scope. Functional use (running) of the program is not restricted. ** ** (d) You must meet all of the following conditions with respect to the ** distribution of any work based on the program: ** ** (i) All modified versions of the program, must carry prominent ** notice stating that the program has been modified. The notice ** must indicate who made the modifications and how the program's ** files were modified and the date of any change; ** ** (ii) You must cause any work that you distribute or publish, that ** in whole or in part contains or is derived from the program ** or any part thereof, to be licensed as a whole and at no charge ** to all third parties under the terms of this license; ** ** (iii) You must cause the program, at each time it commences operation, ** to print or display an announcement including an appropriate ** copyright notice and a notice that there is no warranty. The ** notice must also tell the user how to view the copy of the ** license included with the program, and state that users may ** redistribute the program only under the terms of this License; ** ** (iv) You must accompany any such work based on the program with the ** complete corresponding machine-readable source code, delivered ** on a medium customarily used for software interchange. The ** source code for a work means the preferred form of the work for ** making modifications to it; ** ** (v) If you distribute any written or printed material at all with ** the program or any work based on the program, such material must ** include either a written copy of this license, or a prominent ** written indication that the program or the work based on the ** program is covered by this license and written instructions for ** printing and/or displaying the copy of the License on the ** distribution medium; ** ** (vi) You may not change the terms in this License or impose any ** further restrictions on the recipient's exercise of the rights ** granted herein. ** ** 3. Reservation of Rights ** ** No rights are granted except as expressly set forth herein. You may not ** copy, modify, sublicense, or distribute the program or any parts of the ** HTML output files the program generates, except as expressly provided ** under this license. Any attempt otherwise to copy, modify, sublicense ** or distribute the program or the HTML output files as a whole or in parts ** is void, and will automatically terminate your rights under this license. ** However, parties who have received copies, or rights, from you under this ** license will not have their licenses terminated as long as such parties ** remain in full compliance with the license. ** ** 4. Limitations ** ** The program is provided ťas isŤ without warranty of any kind, either ** expressed or implied, including, but not limited to, the implied ** warranties of merchantability and fitness for a particular purpose. ** Use at your own risk. In no event unless required by applicable law or ** agreed to in writing will any copyright holder, or any other party who ** may use, modify and/or redistribute the program as permitted above, be ** liable to you for damages, including any general, special, incidental ** or consequential damages arising out of the use or inability to use ** the program (including but not limited to loss of data or data being ** rendered inaccurate or losses sustained by you or third parties or a ** failure of the program to operate with any other programs), even if such ** holder or other party has been advised of the possibility of such damages. ** Because under the non-commercial license the program is free of charge, ** RENT-A-GURU does not support it nor assist in it's usage. ** ** 5. General ** ** Some of the source code aggregated with this distribution is licensed ** by third parties under different terms, so the restrictions above may ** not apply to such components. As far as we know, all included source ** code is used in accordance with the relevant license agreements and ** can be used and distributed freely for any purpose; see below for ** details. ** ************************************************************************* ** ** The program uses the GD library for fast GIF creation by Thomas Boutell, ** which is not included in the distribution of http-analyze, but which is ** required to generate the program. The following credits apply: ** ** gd 1.2 is copyright 1994, 1995, Quest Protein Database Center, Cold ** Spring Harbor Labs. Permission granted to copy and distribute this ** work provided that this notice remains intact. Credit for the ** library must be given to the Quest Protein Database Center, Cold ** Spring Harbor Labs, in all derived works. ** ************************************************************************* ** ** Some functions in the file utils.c are owned by the Regents of the ** University of California, and can be freely used and distributed. ** License terms are included in the file utils.c. ** ************************************************************************* */ #include #include #include #include #include #include #include #include #include #include #include #include #if defined(unix) # include # define ISFULLPATH(cp) (*(cp) == '/') #else # if defined(WIN32) # include /* for the u_int, etc. types */ # include /* for other windows/watcom stuff */ # include /* for the F_OK, etc. symbolic constants */ # define ISFULLPATH(cp) (*(cp) == '/' || *(cp) == '\\' || *(cp+1) == ':') #elif defined(NETWARE) # include /* for other windows/watcom stuff */ # include /* for the F_OK, etc. symbolic constants */ # define ISFULLPATH(cp) (*(cp) == '\\' || strchr((cp), ':') != NULL) # endif #endif #if defined(TIME_STATS) # include /* for gettimeofday() */ # define TICKS_PMSEC 1000L #endif #if defined(USE_FAST_MALLOC) # include #endif #include "config.h" #include "defs.h" #define VERSION "2.01" /* fall-back version number */ /* global variables */ char *progname = NULL; /* name of program */ char *creator = NULL; /* creator of 3D model */ int verbose=0; /* 0=silent, 1=verbose, >1=debugging */ int nopageviews = 0; /* if set suppress pageviews and show 304's instead */ static char *version = NULL; /* program version */ static char *last_update = NULL; /* time of last update */ static char *srv_name = NULL; /* name of this server */ static char *srv_url = NULL; /* prefix to use for hot URLs */ static char *log_file = NULL; /* name of the logfile */ static char *log_format = NULL; /* logfile format */ static char *out_dir = NULL; /* directory for HTML output files */ static char *priv_dir = NULL; /* private HTML directory */ static char *tld_file = NULL; /* name of TLD file */ static char *time_win = NULL; /* time-window for session accounting */ static char *cur_tim = NULL; /* fake current time (for debugging) */ static char *ign_date = NULL; /* ignore date */ static char *end_date = NULL; /* end date */ static char *doc_title = NULL; /* document title */ static char *doc_root = NULL; /* document root (for virtual servers) */ static char *vrml_prlg = NULL; /* VRML 3D prolog file */ static char copyright[MAX_FNAMELEN]; /* copyright note */ static char *html_str[HTML_STRSIZE]; /* user-defined HTML strings */ static size_t ipnum = 0; /* total # of index filenames */ static size_t ptnum = 0; /* total # of pageview suffixes */ static size_t drlen = 0; /* length of document root */ static int maxbtn = 0; /* max # of buttons */ static int drneg = 0; /* set if doc root should be ignored */ static int regonly = 0; /* save registration ID */ static u_long lnum = 0; /* line number in logfile */ static PERIOD t; /* times: first/last entry, current time, time limit */ static LIC_INFO *lic = NULL; /* registration info */ static HSTRING indexpg[MAX_HPNAMES]; /* names of additional index files */ static HSTRING pvsuffix[MAX_PGTYPE]; /* pageview suffixes for page rating */ static enum { EXT_WIN, INT_WIN } vrml_win = EXT_WIN; /* VRML 3D window */ /* ** Counters */ static COUNTER total; /* hits/files/304s/sites total */ static COUNTER cksum; /* totals for history checksum */ static COUNTER monly[12]; /* hits per month */ static COUNTER weekly[7]; /* hits per weekday */ static COUNTER grmon[13]; /* hit cnt for img drawing functions */ COUNTER daily[31]; /* total hits per day */ COUNTER max_day, avg_day; /* max/average hits per day */ u_long wh_hits[7][24]; /* hits per weekday & hour */ u_long avg_wday[7]; /* avg hits per weekday */ u_long avg_whour[24]; /* avg hits per weekhour */ static u_long avg_hour[24]; /* average hits per hour */ static u_long max_hrhits = 0L; /* max hits per hour */ static u_long max_avhits = 0L; /* max average hits per hour */ u_long max_avdhits = 0L; /* max average hits per weekday */ u_long max_avhhits = 0L; /* max average hits per weekhour */ u_long max_whhits = 0L; /* max hits per weekday & hour */ size_t wdtab[31]; /* list of weekdays for this month */ static u_long corrupt = 0L; /* total # of invalid requests */ static u_long empty = 0L; /* total # of empty requests */ static u_long authenticated = 0L; /* total # of requests which required auth */ static u_long this_hits = 0L; /* total # of hits per run */ static u_long total_kbsaved = 0L; /* total kbytes saved by cache */ static u_long total_agents = 0L; /* total # of user agents */ static u_long total_refer = 0L; /* total # of referrer URLs */ static u_long uniq_urls = 0L; /* total # of unique URLs */ static u_long uniq_sites = 0L; /* total # of unique sites */ static u_long uniq_stime = 0L; /* duration before new session */ static size_t num_cntry = 0L; /* total # of countries */ static char *grlabel[13]; /* labels for img drawing functions */ static u_short wh_cnt[7]; /* # of days accounted for */ static int noiselevel = -1; /* skip entries with hits below this level */ /* ** Dynamic lists of hidden sites, items, agents and referrers. */ #if !defined(HASHSIZE) # define HASHSIZE 7001 #endif #if !defined(HIDELIST_SIZE) # define HIDELIST_SIZE 4001 #endif /* hidden/ignore tables */ static HIDE_TAB hidden[6] = { { NULL, "site", 0, 0, 0, 0 }, /* HIDDEN_SITES */ { NULL, "URL", 0, 0, 0, 0 }, /* HIDDEN_ITEMS */ { NULL, "referrer",0, 0, 0, 0 }, /* HIDDEN_REFERS */ { NULL, "agent", 0, 0, 0, 0 }, /* HIDDEN_AGENTS */ { NULL, "ignsite", 0, 0, 0, 0 }, /* IGNORED_SITES */ { NULL, "ignURL", 0, 0, 0, 0 } /* IGNORED_ITEMS */ }; /* hash tables for hidden and ignored items & sites */ static NLIST *hlist[4][HIDELIST_SIZE]; /* unknown agents/referrers */ #define SELF_REF IGNORED_ITEMS static NLIST unknown[6] = { { NULL, 0, 0L, 0L, 0L, 0.0 }, /* HIDDEN_ITEMS */ { NULL, 0, 0L, 0L, 0L, 0.0 }, /* HIDDEN_SITES */ { NULL, 0, 0L, 0L, 0L, 0.0 }, /* HIDDEN_REFERS */ { NULL, 0, 0L, 0L, 0L, 0.0 }, /* HIDDEN_AGENTS */ { NULL, 0, 0L, 0L, 0L, 0.0 }, /* SELF_REF */ { NULL, 0, 0L, 0L, 0L, 0.0 } /* not used */ }; static NLIST *sitetab[HASHSIZE]; /* table for sitenames */ static NLIST *urltab[HASHSIZE]; /* table for URLs */ static NLIST *uatab[HASHSIZE]; /* table for user agents */ static NLIST *reftab[HASHSIZE]; /* table for referrer URLs */ static NLIST *errtab[HASHSIZE]; /* table for Code 404 errors */ /* Dimensions of all top N lists. */ static int topn_urls = -1, lstn_urls = -1; static int topn_sites = -1, topn_agent = -1, topn_refer = -1; static int topn_day = -1, topn_hrs = -1, topn_min = -1, topn_sec = -1; /* The Top N sites/urls/agents/referrers lists. */ static NLIST **top_urls = NULL, **lst_urls = NULL; static NLIST **top_sites = NULL, **top_agent = NULL, **top_refer = NULL; /* The Top N secs/mins/hours/days */ static TOP_COUNTER *top_sec = NULL, *top_min = NULL; static TOP_COUNTER *top_hrs = NULL, *top_day = NULL; static TOP_COUNTER csec, cmin, chrs, cday; /* List of ignored sites and ignored items. */ static ITEM_LIST ignored_sites[200], ignored_items[200]; /* Self referrer */ static HSTRING *ref_urls = NULL; /* Common used strings. */ static char allimg[] = "All images"; static char enoprv[] = "Can't create private lists directory `%s'.\n" "The detailed lists will be omitted from the report.\n"; static char enolist[] = "will be omitted from the report."; static char enoent[] = "Can't open file `%s' (%s)\n"; static char ecompr[] = "Couldn't compress the model `%s' using gzip\n"; static char enomem[] = "Not enough memory -- need %u more bytes\n"; static char ha_home[] = "http://www.netstore.de/Supply/http-analyze/"; static char ha_reg[] = "http://www.netstore.de/Supply/http-analyze/register.html"; static char sitefmt[] = "%8lu %6.2f%% %7lu %6.2f%% %14.0f %6.2f%% | "; static char urlfmt[] = "%8lu %6.2f%% %7lu %6.2f%% %14.0f %6.2f%% %10lu | "; static char date_fmt[] = "(use [dd/]mm/yyyy)"; static char hrule1[] = "--------------------------------------------------------------------------------------------------"; static char hrule2[] = "=================================================================================================="; char *daynam[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; char *monnam[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; static u_short mdays[2][12] = { { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } }; /* Common variables */ static int logfmt = LOGF_UNKNOWN; /* defines the logfile format */ static int ign_auth = 0; /* if set ignore requests which required auth */ static int timestats = 0; /* if set print elapsed time */ static int vers_only = 0; /* print version only */ static int use_vrml = 0; /* create a VRML model */ static int use_frames = 0; /* if set create frames interface */ static int use_hist = 0; /* if set use values from history for expired period */ static int nohist = 0; /* if set ignore history file */ static int noload = 0; /* if set suppress average load and top hour/min/sec */ static int nohotlinks = 0; /* if set suppress hotlinks in URL list */ static int nointerpol = 0; /* if set suppress interpolation of graphs */ static int noimages = 0; /* if set suppress graphics in summary reports */ static int noitemlist = 0; /* if set suppress list of items/files */ static int nofilelist = 0; /* if set suppress list of filenames */ static int noerrlist = 0; /* if set suppress list of Code 404 NotFound responses */ static int nositelist = 0; /* if set suppress list of domains/hostnames */ static int nordomlist = 0; /* if set suppress list of reverse domains */ static int nohostlist = 0; /* if set suppress list of hostnames */ static int nocntrylist = 0; /* if set suppress list of countries */ static int noagentlist = 0; /* if set suppress detailed list of user agents */ static int noreferlist = 0; /* if set suppress detailed list of referrer URLs */ static int nodefhid = 0; /* if set don't collect image filenames */ static int font_size = -1; /* font size to use in detailed lists */ static int head_size = -1; /* header size to use in detailed lists */ static int nav_frame = -1; /* size of navigation frame in frames UI */ static int navwid = -1, navht = -1; /* size of navigation window */ static int w3wid = -1, w3ht = -1; /* size of 3D window */ static int monthly = -1; /* if set create monthly summary */ /* ** HTTP/1.1 (RFC2068) request methods and response codes. */ static RESPONSE ReqMethod[] = { { 0L, 0.0, "Unknown request method" }, { 0L, 0.0, "GET" }, { 0L, 0.0, "POST" }, { 0L, 0.0, "HEAD" }, { 0L, 0.0, "PUT" }, { 0L, 0.0, "OPTIONS" }, { 0L, 0.0, "BROWSE" }, { 0L, 0.0, "DELETE" }, { 0L, 0.0, "TRACE" } }; static RESPONSE RespCode[] = { { 0L, 0.0, "Unknown response" }, { 0L, 0.0, "Code 100 Continue" }, { 0L, 0.0, "Code 101 Switch Protocols" }, { 0L, 0.0, "Code 200 OK" }, { 0L, 0.0, "Code 201 Created" }, { 0L, 0.0, "Code 202 Accepted" }, { 0L, 0.0, "Code 203 Non-Authoritative Information" }, { 0L, 0.0, "Code 204 No Content" }, { 0L, 0.0, "Code 205 Reset Content" }, { 0L, 0.0, "Code 206 Partial Content" }, { 0L, 0.0, "Code 300 Multiple Choices" }, { 0L, 0.0, "Code 301 Moved Permanently" }, { 0L, 0.0, "Code 302 Moved Temporarily" }, { 0L, 0.0, "Code 303 See Other" }, { 0L, 0.0, "Code 304 Not Modified" }, { 0L, 0.0, "Code 305 Use Proxy" }, { 0L, 0.0, "Code 400 Bad Request" }, { 0L, 0.0, "Code 401 Unauthorized" }, { 0L, 0.0, "Code 402 Payment Required" }, { 0L, 0.0, "Code 403 Forbidden" }, { 0L, 0.0, "Code 404 Not Found" }, { 0L, 0.0, "Code 405 Method Not Allowed" }, { 0L, 0.0, "Code 406 Not Acceptable" }, { 0L, 0.0, "Code 407 Proxy Authentication Required" }, { 0L, 0.0, "Code 408 Request Timeout" }, { 0L, 0.0, "Code 409 Conflict" }, { 0L, 0.0, "Code 410 Gone" }, { 0L, 0.0, "Code 411 Length Required" }, { 0L, 0.0, "Code 412 Precondition Failed" }, { 0L, 0.0, "Code 413 Request Entity Too Large" }, { 0L, 0.0, "Code 414 Request-URI Too Long" }, { 0L, 0.0, "Code 415 Unsupported" }, { 0L, 0.0, "Code 500 Internal Server Error" }, { 0L, 0.0, "Code 501 Not Implemented" }, { 0L, 0.0, "Code 502 Bad Gateway" }, { 0L, 0.0, "Code 503 Service Unavailable" }, { 0L, 0.0, "Code 504 Gateway Timeout" }, { 0L, 0.0, "Code 505 HTTP Version Not Supported" } }; /* local functions */ static LOGENT *readLog(FILE * const, LOGTIME * const, LOGTIME * const); static NLIST *lookupItem(NLIST ** const, HSTRING * const, u_int const); static HSTRING *hashedString(u_int const, char *); static char *mkSubdir(char * const, u_short const); static char *readHTML(char * const, char * const); static void insertTop(TOP_COUNTER * const, TOP_COUNTER [], int const); static void saveUserAgent(HSTRING * const); static void saveReferHost(HSTRING * const); static void saveDomainName(HSTRING * const); static void addIgnoredItem(int const, char * const); static void addHiddenName(size_t const, char *, char * const, HSTRING * const); static void addSelfRefer(char *); static void initHiddenItems(int const); static void defHiddenImages(void), defHiddenRefers(void); static void prIndex(void), prMonStats(char * const); static void prMonIndex(char * const, int const, int const); static void prFrameHeader(char * const); static void prSiteStats(char * const, char * const); static void prURLStats(char * const, char * const); static void prAgentStats(char * const, char * const); static void prReferStats(char * const, char * const); static void prFileURL(FILE * const, NLIST * const); static void prRefURL(FILE * const, NLIST * const); static void prEscHTML(FILE * const, char const *); static void prDayStats(char * const); static void prIdxTab(FILE * const, FILE * const, COUNTER *, int const, u_long const, char * const); static void prHidden(FILE * const, NLIST ** const, size_t const, size_t const, COUNTER * const, int const); static void prItem(FILE * const, NLIST ** const, size_t const, size_t const, COUNTER * const); static void revDomain(char *, size_t const, char *, size_t); static void mkdtab(LOGTIME * const); static void hideArgs(int const, char *const, char *); static void clearItems(NLIST ** const, int const); static void clearCounter(int const); static void writeHistory(int const); static int mkPrivdir(char * const, char * const); static int sort_by_hits(const void *, const void *); static int sort_by_item(const void *, const void *); static int sort_by_agent(const void *, const void *); static int sort_by_refer(const void *, const void *); static int sort_by_cntry(const void *, const void *); static int sort_by_casestr(const void *, const void *); static int sort_by_string(const void *, const void *); static int isSelfRefer(char *, size_t); static int isHiddenItem(NLIST * const), isHiddenSite(NLIST * const); static int isHiddenAgent(NLIST * const), isHiddenRefer(NLIST * const); static int isIgnoredItem(int const, HSTRING * const); static int readHistory(int const, LOGTIME * const); static int readConfigFile(char * const); static int setLogFmt(char * const); static int parseDate(LOGTIME * const, u_short const, char * const); #if defined(VRML) static void prVRMLModel(char * const, char * const, char * const); extern int prVRMLStats(FILE * const, char * const, char * const, LOGTIME * const); extern int prVRMLYear(FILE * const, char * const, char * const, LOGTIME * const); #endif #if !defined(USE_FGETS) int readLine(char *, size_t, FILE * const); #endif /* ** Print program version. */ static void prVersion(SRVINFO *const sysinfo, char const *rcsinfo, LIC_INFO * const lic) { char lbuf[MEDIUMSIZE]; if (rcsinfo) { if (lic) (void) sprintf(lbuf, "Registered for %s (%s)\n", lic->company, lic->regID); else (void) sprintf(lbuf, "Evaluation copy - see %s\n", ha_reg); } prmsg(0, "%s %s (%s; %s %s), Copyright %hu RENT-A-GURU(TM)\n%s%s%s", progname, version, sysinfo->machine ? sysinfo->machine : "-", sysinfo->sysname ? sysinfo->sysname : "-", sysinfo->release ? sysinfo->release : "-", t.current.year, rcsinfo ? rcsinfo : "", rcsinfo ? "\n" : "", rcsinfo ? lbuf : ""); return; } /* ** Print an usage message. */ static char usage[] = "Usage:\n" " %s [-{hdmV}] [-3aefnvxy] [-c cfgfile] [-o outdir] [-p privdir]\n" "\t\t[-s opt,...] [-t num,...] [-u time] [-w hits] [-F logfmt]\n" "\t\t[-G suffix,...] [-H idxfile,...] [-I date] [-E date]\n" "\t\t[-O virtname,...] [-P prolog] [-R docroot] [-S srvname]\n" "\t\t[-T tldfile] [-U srvurl] [-W 3Dwin] [logfile ...]\n\n"; static void pusage(int const help, SRVINFO * const sinfo) { if (vers_only) prVersion(sinfo, NULL, NULL); (void) fprintf(stderr, usage, progname); if (!help) { (void) fprintf(stderr, "Use `%s -h' for a help list.\n\n", progname); return; } (void) fprintf(stderr, "\t-h\t\tprint this help list\n" "\t-d\t\tgenerate short statistics (\"daily\" mode)\n" "\t-m\t\tgenerate full statistics (\"monthly\" mode, default)\n" "\t-V\t\tprint program version and exit immediately\n" "\t-3\t\tcreate a VRML 2.0 compliant 3D model\n" "\t-a\t\tignore requests which required authentication\n" "\t-e\t\tuse history data even in full statistics mode\n" "\t-f\t\tcreate an additional frames-based interface\n"); (void) fprintf(stderr, "\t-n\t\tcompletely ignore the history data\n" "\t-v\t\tincrement the verbosity level\n" "\t-x\t\tdon't collect images under \"All images\"\n" "\t-y\t\tprint timing stats (if timing code is compiled in)\n" "\t-c cfgfile\tpathname of the configuration file\n" "\t-o outdir\tdirectory to use for the output files\n" "\t-p privdir\tsubdirectory (private area) for detailed lists\n"); if (help < 2) (void) fprintf(stderr, "\t-s opt,...\tsuppress a certain report (`-hh' for more help)\n" "\t-t num,...\tsize of top N lists ('-hh' for more help)\n"); (void) fprintf(stderr, "\t-u time\t\ttime-window for unique sessions (default: one day)\n" "\t-w hits\t\tset the noise-level (hits skipped in overview lists)\n" "\t-F logfmt\tlogfile format, may be one of auto, clf, dlf, elf\n" "\t-G suffix,...\tpageview suffixes (in addition to `.html')\n" "\t-H idxfile,...\tdirectory index filenames (in addition to `index.html')\n" "\t-I date\t\tskip entries until ignore date (DD/MM/YYYY or MM/YYYY)\n" "\t-E date\t\tskip entries after expire date (DD/MM/YYYY or MM/YYYY)\n"); (void) fprintf(stderr, "\t-O virtname,...\tadditional virtual names for this server\n" "\t-P prolog\tpathname of the prolog file for yearly 3D models\n" "\t-R docroot\tname of the document root to restrict analysis to\n" "\t-S srvname\tthe official server name: %s\n" "\t-T tldfile\tfile containing a list of all top-level domains\n" "\t-U srvurl\tthe server's URL: http://%s/\n" "\t-W 3Dwin\t3D window (extern/intern, default: extern)\n", sinfo->hostname, sinfo->hostname); if (help > 1) { (void) fprintf(stderr, "\n" "\t-s opt,...\tdefine `opt' to suppress ...\n" "\t AVLoad\tthe average load and top hour/min/sec\n" "\t URLs\t\tthe overview of URLs/items\n" "\t URLList\tthe list of all URLs grouped by item\n" "\t Code404\tthe list of Code 404 (NotFound) responses\n" "\t Sites\tthe overview of client domains\n" "\t RSites\tthe list of reverse client domains\n" "\t SiteList\tthe list of all hostnames\n" "\t Agents\tthe overview and list of browser types\n" "\t Referrer\tthe overview and list of referrers\n" "\t Country\tthe list of countries\n" "\t Pageviews\tpageview rating\n" "\t Graphics\tgraphics in the summary report\n" "\t Hotlinks\thotlinks in the list of all URLs\n" "\t Interpol\tinterpolation of values in graphs\n"); (void) fprintf(stderr, "\n" "\t-t num,...\tdefine size of Top N lists:\n" "\t #U\t\tnumber of entries in Top N URL list (30)\n" "\t #L\t\tnumber of entries in Least N URL list (10)\n" "\t #S\t\tnumber of entries in Top N domain list (30)\n" "\t #A\t\tnumber of entries in Top N browser list (30)\n" "\t #R\t\tnumber of entries in Top N referrer URL list (30)\n" "\t #d\t\tnumber of entries in Top N days list\n" "\t #h\t\tnumber of entries in Top N days list\n" "\t #m\t\tnumber of entries in Top N days list\n" "\t #s\t\tnumber of entries in Top N days list\n" "\t #F\t\tfont size for text in detailed lists (2)\n" "\t #H\t\theader font size in detailed lists (3)\n" "\t #N\t\tsize of navigation frame in pixels (120)\n\n"); } (void) fprintf(stderr, "\tlogfile ...\tname(s) of the logfile(s) or `-' for stdin\n\n"); return; } /* ** m a i n function */ int main(int const argc, char ** const argv) { extern char *optarg; /* option processing */ extern int optind; char *cp, *ep; /* temp */ char tbuf[MAX_FNAMELEN]; /* filename templates */ char *cfg_file = NULL; /* name of cfg file */ char *curdir = NULL; /* initial directory */ char *subdir = NULL; /* subdir (per year) for output files */ int cc, errflg = 0; /* set if invalid option */ int wday=0; /* current weekday */ int hflg=0; /* controls verbosity of usage message */ int cur_p = 0; /* set if period is current month */ int nlogf = 0; /* number of logfiles */ long lval; /* temp */ u_long cur_tick = 0LU; /* tick counter for days, hours, minutes, seconds */ u_long last_tick[4] = { 0LU, 0LU, 0LU, 0LU }; struct tm *tp; /* current (local) time */ LOGENT *entry; /* current logfile entry */ NLIST *np = NULL; /* namelist */ FILE *lfp = NULL; /* logfile */ SRVINFO *srvinfo; time_t now = time(NULL); /* get current time */ #if defined(TIME_STATS) struct timeval tvs, tve; long asec = 0L, rsec = 0L; (void) gettimeofday(&tvs, NULL); /* measure execution time */ #endif if ((progname = strrchr(argv[0], '/')) != NULL) progname++; /* save program name */ else progname = argv[0]; for (cp=rcs_id; *cp != '\0'; cp++) if (*cp == ' ' && is_digit(cp[1])) break; if (*cp == ' ') { for (ep=tbuf, cp++, cc=0; *cp && *cp != ' ' && ep < &tbuf[MAX_FNAMELEN-3]; cp++) { if (*cp == '.') { cc++; if (cc == 2) continue; if (cc == 3) { *ep++ = 'p'; *ep++ = 'l'; continue; } } *ep++ = *cp; } *ep = '\0'; if ((ep-tbuf) > 2) version = strsave(tbuf); } if (version == NULL) { version = VERSION; prmsg(1, "Couldn't determine version from RCS id, use `%s'.\n", version); } (void) sprintf(tbuf, "%s %s", progname, version); /* original creator */ if ((creator=strsave(tbuf)) == NULL) /* the "can't happen" case */ creator = "http-analyze"; /* define time-dependend strings */ tp = localtime(&now); (void) strftime(tbuf, sizeof tbuf, "%d/%b/%Y:%T", tp); if (!setDate(&t.current, tbuf)) { /* set the current date */ prmsg(2, "Can't parse current date: `%s'\n", tbuf); exit(1); } (void) sprintf(copyright, "Copyright © %hu by RENT-A-GURU®", t.current.year); (void) strftime(tbuf, sizeof tbuf, "%d/%b/%Y %H:%M", tp); last_update = strsave(tbuf); /* save string for display in HTML pages */ srvinfo = myHostName(); /* initialize server name */ indexpg[ipnum++].str = "/index.html"; /* name of default index file */ pvsuffix[ptnum++].str = ".html"; /* default suffix for text pages */ /* process options */ while ((cc = getopt(argc, argv, "hdm3aefnrvxyc:i:o:p:s:t:u:w:C:D:E:F:G:H:I:O:P:R:S:T:U:VW:")) != EOF) { switch (cc) { case 'h': hflg++; /*FALLTHROUGH*/ /* print help only */ default: errflg++; break; /* invalid option */ case 'd': monthly = 0; break; /* daily statistics */ case 'm': monthly = 1; break; /* monthly statistics */ case '3': use_vrml++; break; /* generate 3D model */ case 'a': ign_auth++; break; /* ignore auth req URLs */ case 'e': use_hist++; break; /* use values from history */ case 'f': use_frames++; break; /* generate also HTML frames */ case 'n': nohist++; break; /* don't use history */ case 'r': regonly++; break; /* save registration ID */ case 'v': verbose++; break; /* be verbose */ case 'x': nodefhid++; break; /* don't hide default items */ case 'y': timestats++; break; /* print elapsed time */ case 'c': cfg_file=optarg; break; /* configuration file */ case 'o': out_dir=optarg; break; /* directory for output files */ case 'p': priv_dir=optarg; break; /* private dir for site/URL lists */ case 'u': time_win=optarg; break; /* time-window for session */ case 'D': cur_tim=optarg; break; /* fake current time (for debugging only) */ case 'E': end_date=optarg; break; /* ignore entries from this date on */ case 'F': log_format=optarg;break;/* logfile format */ case 'I': ign_date=optarg; break; /* ignore entries until this date */ case 'P': vrml_prlg=optarg;break; /* VRML prolog file */ case 'R': doc_root=optarg; break; /* document root */ case 'S': srv_name=optarg; break; /* server name */ case 'T': tld_file=optarg; break; /* file containing the Top Level Domains */ case 'U': srv_url=optarg; break; /* prefix for hot links */ case 'V': vers_only++; break; /* print version only */ case 'W': if (*optarg == 'i' || *optarg == 'I') vrml_win = INT_WIN; /* VRML window type */ break; case 'G': cp = strtok(optarg, ", "); do { /* parse additional pageview suffixes */ if (ptnum < TABSIZE(pvsuffix)) pvsuffix[ptnum++].str = cp; else prmsg(1, "Too many pageview suffixes (max %u), ignore `%s'\n", ptnum, cp); } while ((cp=strtok(NULL, ", ")) != NULL); break; case 'H': cp = strtok(optarg, ", "); do { /* parse additional index filenames */ if (ipnum < TABSIZE(indexpg)) indexpg[ipnum++].str = cp; else prmsg(1, "Too many index filenames (max %u), ignore `%s'\n", ipnum, cp); } while ((cp=strtok(NULL, ", ")) != NULL); break; case 'O': cp = strtok(optarg, ", "); do { /* additional virtual server names */ addSelfRefer(cp); } while ((cp=strtok(NULL, ", ")) != NULL); break; case 'w': if ((lval = strtol(optarg, &ep, 10)) < 0 || ep == cp) { prmsg(1, "Illegal value for -w: %s\n", optarg); ++errflg; } else noiselevel = (int)lval; break; case 's': cp = strtok(optarg, ", "); do { if (streq(cp, "avload")) topn_day = topn_hrs = topn_min = topn_sec = 0; else if (streq(cp, "urls")) noitemlist++; else if (streq(cp, "hidden") || streq(cp, "urllist")) nofilelist++; else if (streq(cp, "code404")) noerrlist++; else if (streq(cp, "sites")) nositelist++; else if (streq(cp, "rsites")) nordomlist++; else if (streq(cp, "sitelist")) nohostlist++; else if (streq(cp, "agents")) noagentlist++; else if (streq(cp, "referrer")) noreferlist++; else if (streq(cp, "country")) nocntrylist++; else if (streq(cp, "interpol")) nointerpol++; else if (streq(cp, "hotlinks")) nohotlinks++; else if (streq(cp, "pageviews")) nopageviews++; else if (streq(cp, "graphics")) noimages++; else if (streq(cp, "timing")) { topn_day = topn_hrs = topn_min = topn_sec = 0; noimages = noitemlist = nofilelist = nositelist = 1; noagentlist = noreferlist = nocntrylist = nointerpol = 1; nohotlinks = timestats = 1; } else { prmsg(2, "Invalid value for -s: `%s'\n", cp); ++errflg; break; } } while ((cp=strtok(NULL, ", ")) != NULL); break; case 't': cp = strtok(optarg, ", "); do { if ((lval = strtol(cp, &ep, 10)) < 0) ep = cp; else if (ep > cp) { switch(is_upper(*ep) ? to_lower(*ep) : *ep) { case 'U': topn_urls = (int)lval; break; case 'L': lstn_urls = (int)lval; break; case 'S': topn_sites = (int)lval; break; case 'A': topn_agent = (int)lval; break; case 'R': topn_refer = (int)lval; break; case 'F': font_size = (int)lval; break; case 'H': head_size = (int)lval; break; case 'N': nav_frame = (int)lval; break; case 'd': topn_day = (int)lval; break; case 'h': topn_hrs = (int)lval; break; case 'm': topn_min = (int)lval; break; case 's': topn_sec = (int)lval; break; default: ep = cp; break; } } if (ep == cp) { prmsg(2, "Invalid value for -N: `%s'\n", cp); ++errflg; } } while ((cp=strtok(NULL, ", ")) != NULL); break; case 'i': prmsg(1, "The option -%c is obsolete now, use new syntax.\n", cc); errflg++; break; } } if (errflg) { /* print usage and exit */ pusage(hflg, srvinfo); exit(!hflg); } if (regonly) { /* save registration ID */ if (argc-optind != 2) { prmsg(0, "Use the command:\n" "\t%s -r 'company name' registration_ID\n" "to save the registration ID in file `%s'\n", progname, REGID_FILE); } else if (!saveRegID(REGID_FILE, argv[optind], argv[optind+1])) prmsg(0, "Couldn't write registration information " "into file `%s'\n", REGID_FILE); else prmsg(0, "Registration information saved " "in file `%s'\n", REGID_FILE); exit(0); } lic = getRegID(NULL, NULL); if (vers_only) { /* print version and exit */ prVersion(srvinfo, rcs_id, lic); exit(0); } if (verbose) prVersion(srvinfo, NULL, NULL); #if !defined(TIME_STATS) if (timestats) { prmsg(0, "Timing code not compiled in, -y ignored\n"); timestats = 0; } #endif #if !defined(VRML) if (use_vrml) { prmsg(0, "VRML code not compiled in, -3 ignored\n"); use_vrml = 0; vrml_prlg = NULL; } #endif if ((curdir=getcwd(NULL, 256)) == NULL) { prmsg(2, "Couldn't determine current directory (%s)?\n", strerror(errno)); exit(1); } if (cfg_file && !readConfigFile(cfg_file)) { /* read config file */ prmsg(2, enoent, cfg_file, strerror(errno)); exit(1); } if ((nlogf = argc-optind) > 0) { /* check filenames before reading data */ errflg = 0; for (cc=optind; cc < argc; cc++) { if (argv[cc][0] == '-' && argv[cc][1] == '\0') continue; cp = argv[cc]; if (!ISFULLPATH(cp)) { /* *cp != '/' */ (void) sprintf(tbuf, "%.255s/%.766s", curdir, argv[cc]); cp = tbuf; } errno = 0; if (access(cp, F_OK) < 0) { prmsg(2, "Can't access `%s' (%s)\n", argv[cc], strerror(errno)); errflg++; } } if (errflg) exit(1); log_file = (argv[optind][0] == '-' && !argv[optind][1]) ? NULL : argv[optind]; } else if ((cp = log_file) != NULL) { if (!ISFULLPATH(cp)) { /* *cp != '/' */ (void) sprintf(tbuf, "%.255s/%.766s", curdir, log_file); cp = tbuf; } errno = 0; if (access(cp, F_OK) < 0) { prmsg(2, "Can't access `%s' (%s)\n", log_file, strerror(errno)); exit(1); } } if (log_format && (logfmt=setLogFmt(log_format)) < 0) { prmsg(1, "Unknown logfile format `%s'\n", log_format); exit(1); } /* set defaults for some parameters not specified */ if (!srv_name || !*srv_name) /* set default server name */ srv_name = srvinfo->hostname; if (srv_url) { /* massage server URL if given */ size_t len = strlen(srv_url); while (len != 0 && srv_url[--len] == '/') srv_url[len] = '\0'; if (!strneq(srv_url, "http://", 7) && !strneq(srv_url, "https://", 8)) { while (*srv_url == '/') srv_url++; if (*srv_url) { (void) sprintf(tbuf, "http://%s", srv_url); srv_url = strsave(tbuf); } else srv_url = NULL; } } addSelfRefer(srv_url ? srv_url : srv_name); /* install default hidden items */ if (!nodefhid) defHiddenImages(); if (!noreferlist) defHiddenRefers(); /* save number of pre-defined hidden items */ hidden[HIDDEN_ITEMS].t_start = hidden[HIDDEN_ITEMS].t_count; hidden[HIDDEN_SITES].t_start = hidden[HIDDEN_SITES].t_count; hidden[HIDDEN_AGENTS].t_start = hidden[HIDDEN_AGENTS].t_count; hidden[HIDDEN_REFERS].t_start = hidden[HIDDEN_REFERS].t_count; if (!doc_title) doc_title = "WWW Access Statistics for"; if (monthly < 0) /* set default mode of operation */ monthly = 1; if (nohotlinks < 0) nohotlinks = 0; if (noiselevel < 0) noiselevel = 0; uniq_stime = TICKS_PHOUR(24); /* default: 24 hours */ if (time_win) { /* set time window for user sessions */ if ((lval = strtol(time_win, &ep, 10)) >= 0 && ep > time_win) { while (*ep == ' ') ep++; switch (*ep) { case '\0': /*FALLTHROUGH*/ case 't': /*FALLTHROUGH*/ case 'T': /*FALLTHROUGH*/ case 's': /*FALLTHROUGH*/ case 'S': uniq_stime = TICKS_PSEC(lval); break; case 'm': /*FALLTHROUGH*/ case 'M': uniq_stime = TICKS_PMIN(lval); break; case 'h': /*FALLTHROUGH*/ case 'H': uniq_stime = TICKS_PHOUR(lval); break; case 'd': /*FALLTHROUGH*/ case 'D': uniq_stime = TICKS_PDAY(lval); break; default: ep = time_win; break; } } if (ep == time_win || uniq_stime > TICKS_PMON(1)) { prmsg(1, "Invalid time-window for sessions: %s (using 24h)\n", time_win); uniq_stime = TICKS_PHOUR(24); } } if (font_size < 0) /* font size for lists */ font_size = 2; else if (!font_size || font_size > 5) { prmsg(1, "Invalid font size: %d ([1-5], using 2)\n ", font_size); font_size = 2; } if (head_size < 0) /* font size for headers in list */ head_size = 3; else if (!head_size || head_size > 5) { prmsg(1, "Invalid header level: %d ([1-5], using 3)\n ", head_size); head_size = 3; } if (nav_frame < 0) /* frame navigation column size in pixels */ nav_frame = 120; else if (nav_frame < 80 || nav_frame > 160) { prmsg(1, "Invalid navigation frame size: %d ([90-150], using 120)\n ", nav_frame); nav_frame = 120; } if (navwid < 0 && navht < 0) { /* size of navigation window */ navwid = 420; navht = 190; } else if (navwid < 100 || navwid > 1280) { prmsg(1, "Invalid navigation window size: %dx%d (using 420x190)\n", navwid, navht); navwid = 420; navht = 190; } else if (navht < 50 || navht > 1024) { prmsg(1, "Invalid navigation window size: %dx%d (using 420x190)\n", navwid, navht); navwid = 420; navht = 190; } if (w3wid < 0 && w3ht < 0) { /* size of 3D window */ w3wid = 520; w3ht = 420; } else if (w3wid < 420 || w3wid > 1280) { prmsg(1, "Invalid 3D window size: %dx%d (using 520x420)\n", w3wid, w3ht); w3wid = 520; w3ht = 420; } else if (w3ht < 220 || w3ht > 1024) { prmsg(1, "Invalid 3D window size: %dx%d (using 520x420)\n", w3wid, w3ht); w3wid = 520; w3ht = 420; } initTLD(tld_file); /* initialize list of top-level domains */ /* Add default dimensions for top lists, allocate space */ if (topn_sites < 0) topn_sites = 30; if (topn_urls < 0) topn_urls = 30; if (lstn_urls < 0) lstn_urls = 10; if (topn_agent < 0) topn_agent = 30; if (topn_refer < 0) topn_refer = 30; if (topn_day < 0) topn_day = 7; if (topn_hrs < 0) topn_hrs = 24; if (topn_min < 0) topn_min = 5; if (topn_sec < 0) topn_sec = 5; if (!topn_day && !topn_hrs && !topn_min && !topn_sec) noload++; if (topn_sites > 0) top_sites = (NLIST **)calloc((size_t)topn_sites, sizeof(NLIST *)); if (topn_urls > 0) top_urls = (NLIST **)calloc((size_t)topn_urls, sizeof(NLIST *)); if (lstn_urls > 0) lst_urls = (NLIST **)calloc((size_t)lstn_urls, sizeof(NLIST *)); if (topn_agent > 0) top_agent = (NLIST **)calloc((size_t)topn_agent, sizeof(NLIST *)); if (topn_refer > 0) top_refer = (NLIST **)calloc((size_t)topn_refer, sizeof(NLIST *)); if (topn_day > 0) top_day = (TOP_COUNTER *)calloc((size_t)topn_day, sizeof(TOP_COUNTER)); if (topn_hrs > 0) top_hrs = (TOP_COUNTER *)calloc((size_t)topn_hrs, sizeof(TOP_COUNTER)); if (topn_min > 0) top_min = (TOP_COUNTER *)calloc((size_t)topn_min, sizeof(TOP_COUNTER)); if (topn_sec > 0) top_sec = (TOP_COUNTER *)calloc((size_t)topn_sec, sizeof(TOP_COUNTER)); if ((topn_sites && !top_sites) || (topn_urls && !top_urls) || (topn_agent && !top_agent) || (topn_refer && !top_refer) || (lstn_urls && !lst_urls) || (!noload && (!top_day || !top_hrs || !top_min || !top_sec))) { prmsg(2, enomem, topn_sites+topn_urls+topn_agent+topn_refer+lstn_urls); exit(1); } for (cc=0; cc < (int)ipnum; cc++) { if (*indexpg[cc].str == '\0') { prmsg(2, "Invalid zero length name for index file.\n"); exit(1); } if (*indexpg[cc].str != '/') { /* add slash if filename fits into tbuf */ if (strlen(indexpg[cc].str) >= sizeof(tbuf)-1) { prmsg(2, "Additional index filenames must begin with a slash.\n"); exit(1); } (void) sprintf(tbuf, "/%s", indexpg[cc].str); indexpg[cc].str = strsave(tbuf); } indexpg[cc].len = indexpg[cc].str ? strlen(indexpg[cc].str) : 0; } if (nopageviews) ptnum = 0; else for (cc=0; cc < (int)ptnum; cc++) { if (*pvsuffix[cc].str == '\0') { prmsg(2, "Invalid zero length pageview suffix.\n"); exit(1); } if (*pvsuffix[cc].str != '.') { /* add dot if suffix fits into tbuf */ if (strlen(pvsuffix[cc].str) >= sizeof(tbuf)-1) { prmsg(2, "Pageview suffixes must begin with a dot.\n"); exit(1); } (void) sprintf(tbuf, ".%s", pvsuffix[cc].str); pvsuffix[cc].str = strsave(tbuf); } pvsuffix[cc].len = pvsuffix[cc].str ? strlen(pvsuffix[cc].str) : 0; } if (doc_root != NULL) { if (*doc_root == '\0') { prmsg(2, "Invalid zero length name for document root.\n"); exit(1); } if (*doc_root == '!') { drneg = 1; doc_root++; } if (*doc_root != '/') { if (strlen(doc_root) >= sizeof(tbuf)-1) { prmsg(2, "The name of the Document Root " "must start with a slash.\n"); exit(1); } (void) sprintf(tbuf, "/%s", doc_root); doc_root = strsave(tbuf); } drlen = doc_root ? strlen(doc_root) : 0; } clearCounter(1); /* clear all counters */ /* set the dates */ if (cur_tim && !parseDate(&t.current, t.current.year, cur_tim)) { prmsg(2, "Can't parse current date: `%s' %s\n", cur_tim, date_fmt); exit(1); } if (ign_date && !parseDate(&t.ignore, t.current.year, ign_date)) { prmsg(2, "Can't parse start date: `%s' %s\n", ign_date, date_fmt); exit(1); } if (end_date && !parseDate(&t.brk, t.current.year, end_date)) { prmsg(2, "Can't parse end date: `%s' %s\n", end_date, date_fmt); exit(1); } /* change into the HTML directory if given */ if (out_dir) { if (chdir(out_dir) != 0) { prmsg(2, "Can't change into directory `%s'\n", out_dir); exit(1); } else if (verbose) prmsg(0, "Generating statistics in directory `%s'\n", out_dir); } /* check for icons before analyzing data, make sure that icons dir exists */ errno = 0; if (mkdir("btn", 0777) < 0 && errno != EEXIST) { prmsg(2, "Couldn't create icon directory `btn' (%s)\n", strerror(errno)); exit(1); } checkForIcons(); maxbtn = checkForLogos(ha_home); if (maxbtn != BTN_CUSTOMW && maxbtn != BTN_CUSTOMB) lic = NULL; if (use_frames && access("btn/totals_on.gif", F_OK) < 0) { prmsg(1, "Frames creation disabled due to missing buttons\n"); use_frames = 0; } if (use_vrml && vrml_prlg) { errno = 0; if (access(vrml_prlg, R_OK) < 0) { if (verbose) prmsg(0, "Can't find prolog file `%s' (%s)\n", vrml_prlg, strerror(errno)); vrml_prlg = NULL; /* disable yearly model */ } } /* ** Now read the history to speed up processing. ** If doing a monthly summary, delay reading of ** the history file until we could determine the ** start time unless user requested to not do so. */ if (!monthly) { if (!t.ignore.mday) { t.ignore = t.current; t.ignore.mday = 1; /* tick back to first day of month */ } t.end = t.ignore; if (!nohist) /* adjust ignore time from history */ (void) readHistory(0, &t.end); if (!ign_date) t.ignore = t.end; } else if (use_hist) { t.end = t.current; t.end.mday = 1; /* tick back to first day of month */ (void) readHistory(2, &t.end); t.ignore = t.end; } /* ** Read the logfiles. Remember the last day done in t.end. ** For monthly stats, get summary period from first logfile ** entry read. Save start time of period in t.start. */ if (verbose) { if (t.ignore.mday) prmsg(0, "Skip all entries until " TIME_FMT "\n", t.ignore.mday, monnam[t.ignore.mon], t.ignore.year); if (t.brk.mday) prmsg(0, "Stop processing at " TIME_FMT "\n", t.brk.mday, monnam[t.brk.mon], t.brk.year); prmsg(0, "Reading data from `%s'\n", (log_file ? log_file : "stdin")); } errflg = 0; do { if (nlogf > 0) { --nlogf; lnum = 0L; /* reset line number */ log_file = argv[optind++]; if (*log_file == '-' && *(log_file+1) == '\0') log_file = NULL; } if (log_file) { if (!ISFULLPATH(log_file) && curdir) { (void) sprintf(tbuf, "%.255s/%.766s", curdir, log_file); errno = 0; lfp = fopen(tbuf, "r"); } else { errno = 0; lfp = fopen(log_file, "r"); } if (lfp == NULL) { prmsg(2, enoent, log_file, strerror(errno)); break; } } while ((entry=readLog(lfp ? lfp : stdin, &t.ignore, &t.brk)) != NULL) { if (t.ignore.mday) t.ignore.mday = 0; if (t.start.mday == 0) { /* we have no history so we use the */ t.start = t.end = entry->tm; /* first entry to determine the month */ if (verbose) prmsg(0, "Start new period at " TIME_FMT "\n", t.start.mday, monnam[t.start.mon], t.start.year); if (monthly) { (void) readHistory(1, &t.start); t.start.mday = 1; /* tick back to beginning of month */ } mkdtab(&t.start); } else if (entry->tm.year > t.start.year || entry->tm.mon > t.start.mon) { if (monthly) { /* flush top counter */ if (max_hrhits < chrs.count) max_hrhits = chrs.count; insertTop(&cday, top_day, topn_day); insertTop(&chrs, top_hrs, topn_hrs); insertTop(&cmin, top_min, topn_min); insertTop(&csec, top_sec, topn_sec); last_tick[0] = last_tick[1] = last_tick[2] = last_tick[3] = 0LU; if (verbose == 2) (void) fputc('\n', stderr); } #if defined(TIME_STATS) if (timestats) { (void) gettimeofday(&tve, NULL); rsec += (tve.tv_sec-tvs.tv_sec) * TICKS_PMSEC; rsec -= tvs.tv_usec/TICKS_PMSEC; rsec += tve.tv_usec/TICKS_PMSEC; } #endif subdir = mkSubdir("www", t.end.year); if (priv_dir && !mkPrivdir(subdir, priv_dir)) { nositelist = noitemlist = noagentlist = noreferlist = 1; prmsg(1, enoprv, priv_dir); priv_dir = NULL; /* suppress lists & msg */ } cur_p = (t.end.year == t.current.year && t.end.mon == t.current.mon); prMonStats(subdir); /* year or month wrap */ prMonIndex(subdir, cur_p, 0); if (!nohist) writeHistory(1); /* prepare for new period */ clearCounter(0); clearItems(sitetab, TABSIZE(sitetab)); clearItems(urltab, TABSIZE(urltab)); clearItems(uatab, TABSIZE(uatab)); clearItems(reftab, TABSIZE(reftab)); clearItems(errtab, TABSIZE(errtab)); initHiddenItems(HIDDEN_SITES); initHiddenItems(HIDDEN_ITEMS); initHiddenItems(HIDDEN_AGENTS); initHiddenItems(HIDDEN_REFERS); clearCountry(); t.start = entry->tm; t.start.mday = 1; /* tick back to beginning of month */ if (verbose) { prmsg(0, "Clear almost all counters at " TIME_FMT "\n" "Start new period at "TIME_FMT "\n", t.end.mday, monnam[t.end.mon], t.end.year, t.start.mday, monnam[t.start.mon], t.start.year); } if (errflg) { errflg = 0; if (verbose) prmsg(0, "Now reading data from `%s'\n", (log_file ? log_file : "stdin")); } t.end = entry->tm; mkdtab(&t.start); #if defined(TIME_STATS) if (timestats) { (void) gettimeofday(&tvs, NULL); asec += (tvs.tv_sec-tve.tv_sec) * TICKS_PMSEC; asec -= tve.tv_usec/TICKS_PMSEC; asec += tvs.tv_usec/TICKS_PMSEC; } #endif } else if (entry->tm.year < t.start.year || entry->tm.mon < t.end.mon) { if (monthly && verbose == 2) (void) fputc('\n', stderr); prmsg(1, "Skip logfile entries from " TIME_FMT " to " TIME_FMT " (%s)\n", entry->tm.mday, monnam[entry->tm.mon], entry->tm.year, t.end.mday, monnam[t.end.mon], t.end.year, log_file ? log_file : "stdin"); t.ignore = t.end; continue; } else { if (errflg) { errflg = 0; if (verbose) prmsg(0, "Now reading data from `%s'\n", (log_file ? log_file : "stdin")); } if (t.end.mday != entry->tm.mday) { /* remember last day done */ t.end.mday = entry->tm.mday; if (verbose == 2) (void) fputc('.', stderr); } } /* update the counters */ total.hits++; /* total hits */ total.bytes += (float)entry->reqsize; /* total bytes */ daily[entry->tm.mday-1].hits++; /* hits per day */ daily[entry->tm.mday-1].bytes += (float)entry->reqsize; /* bytes per day */ wday = wdtab[entry->tm.mday-1]; /* compute weekday */ weekly[wday].hits++; /* hits per weekday */ wh_hits[wday][entry->tm.hour]++; /* account for hour & weekday */ avg_hour[entry->tm.hour]++; if (!IS_METHOD(entry->ftype, METHOD_GET) && !IS_METHOD(entry->ftype, METHOD_POST)) { size_t idx = (size_t)(entry->ftype&METHOD_MASK); if (idx >= TABSIZE(ReqMethod)) idx = 0; ReqMethod[idx].count++; ReqMethod[idx].bytes += (float)entry->reqsize; } else if (entry->respidx != IDX_OK && entry->respidx != IDX_NOT_MODIFIED) { total.other++; daily[entry->tm.mday-1].other++; weekly[wday].other++; RespCode[entry->respidx].count++; RespCode[entry->respidx].bytes += (float)entry->reqsize; if (monthly && entry->respidx == IDX_NOT_FOUND) { np = lookupItem(errtab, &entry->request, 0); if (np != NULL) { /* update Code 404 counters */ np->count++; np->bytes += (float)entry->reqsize; } } } else { if (IS_TYPE(entry->ftype, TYPE_PGVIEW)) { total.views++; /* total pageviews */ daily[entry->tm.mday-1].views++;/* pageviews per day */ weekly[wday].views++; /* pageviews per weekday */ } if (entry->respidx == IDX_OK) { total.files++; daily[entry->tm.mday-1].files++; weekly[wday].files++; } else { /* IDX_NOT_MODIFIED */ total.nomod++; daily[entry->tm.mday-1].nomod++; weekly[wday].nomod++; } if (monthly) { /* check for known URL, save it */ np = lookupItem(urltab, &entry->request, 0); if (np == NULL || !np->count) /* no mem or brand new */ uniq_urls++; if (np != NULL) { /* update counters */ np->count++; np->bytes += (float)entry->reqsize; if (entry->respidx == IDX_NOT_MODIFIED) np->nomod++; else if (entry->reqsize > np->size) np->size = entry->reqsize; /* adjust doc size */ } } } /* ** Check for known user agent and referrer URL, save them. */ if (monthly) { if (logfmt == LOGF_ELF || logfmt == LOGF_NCSA) { if (!entry->uagent.str || *entry->uagent.str == '\0') { unknown[HIDDEN_AGENTS].count++; unknown[HIDDEN_AGENTS].bytes += (float)entry->reqsize; if (entry->respidx == IDX_NOT_MODIFIED) unknown[HIDDEN_AGENTS].nomod++; } else { np = lookupItem(uatab, &entry->uagent, entry->uatype.len); if (np != NULL) { if (!np->count) { total_agents++; /* brand new */ if (entry->uatype.len) /* skips also agents w/o version */ saveUserAgent(&entry->uatype); } np->count++; /* update counters */ np->ftype = entry->ftype; np->bytes += (float)entry->reqsize; if (entry->respidx == IDX_NOT_MODIFIED) np->nomod++; } } if (!entry->refer.str || *entry->refer.str == '\0') { unknown[HIDDEN_REFERS].count++; unknown[HIDDEN_REFERS].bytes += (float)entry->reqsize; if (entry->respidx == IDX_NOT_MODIFIED) unknown[HIDDEN_REFERS].nomod++; } else { np = lookupItem(reftab, &entry->refer, entry->refhost.len); if (np != NULL) { if (!np->count) { /* brand new */ total_refer++; if (entry->refhost.len) saveReferHost(&entry->refhost); } np->count++; /* update counters */ np->ftype = entry->ftype; np->bytes += (float)entry->reqsize; if (entry->respidx == IDX_NOT_MODIFIED) np->nomod++; } } } } /* ** Check for known sitename, save it */ np = lookupItem(sitetab, &entry->sitename, entry->sitename.len-entry->tldomain.len); if (np == NULL) { /* no more memory, count as new & unique */ total.sessions++; uniq_sites++; daily[entry->tm.mday-1].sessions++; } else { if (!np->count) { /* brand new */ uniq_sites++; /* sum of unique sites */ if (entry->tldomain.len) /* skips also TYPE_NODNS */ saveDomainName(&entry->tldomain); } if ((IS_METHOD(entry->ftype, METHOD_GET) || IS_METHOD(entry->ftype, METHOD_POST)) && np->ltick < entry->ltick-uniq_stime) { /* new session */ if (verbose > 3 && np->ltick) { int day, hour, min, sec; sec = entry->ltick - np->ltick; day = sec / (24LU * 60LU * 62LU); sec %= 24LU * 60LU * 62LU; hour = sec / (60LU * 62LU); sec %= 60LU * 62LU; min = sec / 62L; sec %= 62L; prmsg(0, "\tnew session after %d.%02d:%02d:%02d\n", day, hour, min, sec); } total.sessions++; /* sum of all sessions */ daily[entry->tm.mday-1].sessions++; np->ltick = entry->ltick; /* stamp it */ } np->count++; /* update counters */ np->ftype = entry->ftype; /* use size to store type */ np->bytes += (float)entry->reqsize; if (entry->respidx == IDX_NOT_MODIFIED) np->nomod++; } /* ** Update top counters. */ if (monthly) { /* compute "ticks" to count top seconds, minutes, and hours */ cur_tick = entry->ltick % TICKS_PMON(1); if (cur_tick-last_tick[0] >= TICKS_PDAY(1)) { /* day changed */ insertTop(&cday, top_day, topn_day); last_tick[0] = cur_tick - cur_tick % TICKS_PDAY(1); } if (cur_tick-last_tick[1] >= TICKS_PHOUR(1)) { /* hour changed */ if (max_hrhits < chrs.count) max_hrhits = chrs.count; insertTop(&chrs, top_hrs, topn_hrs); last_tick[1] = cur_tick - cur_tick % TICKS_PHOUR(1); } if (cur_tick-last_tick[2] >= TICKS_PMIN(1)) { /* minute changed */ insertTop(&cmin, top_min, topn_min); last_tick[2] = cur_tick - cur_tick % TICKS_PMIN(1); } if (cur_tick != last_tick[3]) { /* second has changed */ insertTop(&csec, top_sec, topn_sec); last_tick[3] = cur_tick; } if (entry->respidx == IDX_NOT_MODIFIED) { cday.nomod++; chrs.nomod++; cmin.nomod++; csec.nomod++; } cday.count++; chrs.count++; cmin.count++; csec.count++; cday.bytes += (float)entry->reqsize; chrs.bytes += (float)entry->reqsize; cmin.bytes += (float)entry->reqsize; csec.bytes += (float)entry->reqsize; cday.tm = chrs.tm = cmin.tm = csec.tm = entry->tm; } } if (lfp != NULL) { (void) fclose(lfp); lfp = NULL; } errflg = 1; if (monthly && verbose == 2) (void) fputc('\n', stderr); } while (nlogf > 0); if (monthly) { if (max_hrhits < chrs.count) max_hrhits = chrs.count; insertTop(&cday, top_day, topn_day); insertTop(&chrs, top_hrs, topn_hrs); insertTop(&cmin, top_min, topn_min); insertTop(&csec, top_sec, topn_sec); last_tick[0] = last_tick[1] = last_tick[2] = last_tick[3] = 0L; } if (this_hits == 0L && total.hits == 0L) { if (verbose) prmsg(1, "No hits at all?!?\n"); exit(0); } #if defined(TIME_STATS) if (timestats) { (void) gettimeofday(&tve, NULL); rsec += (tve.tv_sec-tvs.tv_sec) * TICKS_PMSEC; rsec -= tvs.tv_usec/TICKS_PMSEC; rsec += tve.tv_usec/TICKS_PMSEC; } #endif if (this_hits > 150 && total.hits <= 15) { if (verbose) prmsg(0, "Let the dust settle down: ignore %lu hits since " TIME_FMT "\n", total.hits, t.end.mday, monnam[t.end.mon], t.end.year); } else { subdir = mkSubdir("www", t.end.year); if (priv_dir && !mkPrivdir(subdir, priv_dir)) { nositelist = noitemlist = noagentlist = noreferlist = 1; prmsg(1, enoprv, priv_dir); priv_dir = NULL; /* suppress lists & msg */ } cur_p = (t.end.year == t.current.year && t.end.mon == t.current.mon); if (cur_p) { /* summary period is current month */ if (t.end.mday < t.current.mday || t.end.mday == t.ignore.mday) { if (verbose) prmsg(0, "No more hits since " TIME_FMT "\n", t.end.mday, monnam[t.end.mon], t.end.year); t.end.mday = t.current.mday; /* in case there were no hits since then */ } if (monthly && t.current.mday > 1) prMonStats(subdir); /* full summary */ prDayStats(subdir); /* short summary */ prMonIndex(subdir, cur_p, 0); /* monthly index */ if (!nohist) writeHistory(0); } else if (monthly) { /* previous periods */ int leap = t.end.year%4 == 0 && t.end.year%100 != 0 || t.end.year%400 == 0; if (t.end.mday != mdays[leap][t.end.mon]) { if (verbose) prmsg(0, "No more hits since " TIME_FMT "\n", t.end.mday, monnam[t.end.mon], t.end.year); t.end.mday = mdays[leap][t.end.mon]; } prMonStats(subdir); /* full summary */ prMonIndex(subdir, cur_p, 1); /* monthly index */ if (!nohist) writeHistory(1); } if (verbose && t.end.mday) prmsg(0, "Statistics complete until " TIME_FMT "\n", t.end.mday, monnam[t.end.mon], t.end.year); } prIndex(); /* update the (new) main index file */ #if defined(TIME_STATS) if (timestats) { (void) gettimeofday(&tvs, NULL); /* measure execution time */ asec += (tvs.tv_sec-tve.tv_sec) * TICKS_PMSEC; asec -= tve.tv_usec/TICKS_PMSEC; asec += tvs.tv_usec/TICKS_PMSEC; this_hits += total.hits; /* sum up this month' hits */ prmsg(0, "\nTime to process %lu entries: %ld.%03ld sec (%lu hits/sec)\n" "Time to create the summary: %ld.%03ld sec\n", this_hits, rsec/TICKS_PMSEC, rsec%TICKS_PMSEC, (u_long)((float)this_hits/(((float)rsec/(float)TICKS_PMSEC))), asec/TICKS_PMSEC, asec%TICKS_PMSEC); rsec += asec; prmsg(0, "Total time elapsed: %ld.%03ld sec (%lu hits/sec)\n\n", rsec/TICKS_PMSEC, rsec%TICKS_PMSEC, (u_long)((float)this_hits/(((float)rsec/(float)TICKS_PMSEC)))); } #endif /* ** Clean up. Although technically not necessary here, ** freeing allocated memory enables us to detect memory ** leaks elsewhere. */ clearItems(sitetab, TABSIZE(sitetab)); clearItems(urltab, TABSIZE(urltab)); clearItems(uatab, TABSIZE(uatab)); clearItems(reftab, TABSIZE(reftab)); clearItems(errtab, TABSIZE(errtab)); clearItems(hlist[HIDDEN_ITEMS], HIDELIST_SIZE); clearItems(hlist[HIDDEN_SITES], HIDELIST_SIZE); clearItems(hlist[HIDDEN_AGENTS], HIDELIST_SIZE); clearItems(hlist[HIDDEN_REFERS], HIDELIST_SIZE); if (top_sites != NULL) free(top_sites); if (top_refer != NULL) free(top_refer); if (top_agent != NULL) free(top_agent); if (top_urls != NULL) free(top_urls); if (lst_urls != NULL) free(lst_urls); if (last_update != NULL) free(last_update); return 0; } /* ** Find month by name. */ static u_short findMonth(char * const str) { size_t idx; for (idx=0; idx < 12; idx++) { if (!strncasecmp(monnam[idx], str, 3)) break; } return (u_short)(idx < 12 ? idx+1 : 0); } /* ** Parse the date. ** ** date-spec [- [date-spec]] range from date-spec1 to date-spec2 (default: today) ** -date-spec range from start of first logfile entry until date-spec ** ** date-spec: ** Full specification: 01/Oct/[19]97, 01/10/[19]97 ** Day defaults to 1st: Oct/97, 10/97, Oct, 10 ** Year defaults to tcur: 01/Oct, 01/10 */ static int parseDate(LOGTIME * const tp, u_short const curyear, char * const str) { u_short tmday=0, tmon=0, tyear=0; int leap, rc = 0; char smon[5]; tp->year = tp->mon = tp->mday = 0; if ((rc = sscanf(str, "%2hu/%2hu/%4hu", &tmday, &tmon, &tyear)) == 3) { tp->mday = tmday; tp->mon = tmon; tp->year = tyear; } else if ((rc = sscanf(str, "%2hu/%3s/%4hu", &tmday, smon, &tyear)) == 3) { tp->mon = findMonth(smon); tp->mday = tmday; tp->year = tyear; } else if ((rc = sscanf(str, "%2hu/%4hu", &tmon, &tyear)) == 2) { tp->mon = tmon; tp->year = tyear; } else if ((rc = sscanf(str, "%2hu/%3s", &tmday, smon)) == 2) { tp->mon = findMonth(smon); tp->mday = tmday; } else if ((rc = sscanf(str, "%3s/%4hu", smon, &tyear)) == 2) { tp->mon = findMonth(smon); tp->year = tyear; } else if ((rc = sscanf(str, "%2hu", &tmon)) == 1) tp->mon = tmon; else if ((rc = sscanf(str, "%3s", smon)) == 1) { tp->mon = findMonth(smon); } if (rc < 0 || !tp->mon || tp->mon > 12) return 0; tp->mon--; /* adjust month [0,11], set defaults */ if (!tp->mday) tp->mday = 1; if (!tp->year) tp->year = curyear; else if (tp->year < 100) tp->year += (tp->year < 70) ? 2000 : 1900; leap = tp->year%4 == 0 && tp->year%100 != 0 || tp->year%400 == 0; if (tp->mday > mdays[leap][tp->mon]) return 0; return 1; } /* ** Check for output subdirectory */ static char *mkSubdir(char * const templ, u_short const year) { static char subdir[MAX_FNAMELEN]; (void) sprintf(subdir, "%s%hu", templ, year); if (access(subdir, W_OK) != 0) { errno = 0; if (mkdir(subdir, 0777) < 0) { prmsg(2, "Couldn't create subdirectory `%s' (%s)\n", subdir, strerror(errno)); exit(1); } if (verbose) prmsg(0, "NOTE: output files will be " "created in subdirectory `%s'\n", subdir); } return (char *)subdir; } /* ** Check for private directory */ static int mkPrivdir(char * const subd, char * const privd) { char dname[MAX_FNAMELEN]; (void) sprintf(dname, "%s/%s", subd, privd); errno = 0; return mkdir(dname, 0777) == 0 || errno == EEXIST; } /* ** Insert item into top counter. */ static void insertTop(TOP_COUNTER * const cp, TOP_COUNTER tp[], int const max) { int idx, minidx = 0; u_long mincnt = ~0UL; float minbyt = 0.0; if (max <= 0 || tp == NULL) { cp->count = 0L; cp->nomod = 0L; cp->bytes = 0.0; return; } if (!cp->count && !cp->nomod && cp->bytes < 1.0) return; for (idx=0; idx < max; idx++) { /* find lowest value */ if (tp[idx].count < mincnt || tp[idx].count == mincnt && (minbyt < 1.0 || tp[idx].bytes < minbyt)) { minidx = idx; mincnt = tp[idx].count; minbyt = tp[idx].bytes; } } if (cp->count > tp[minidx].count || cp->count == tp[minidx].count && cp->bytes > tp[minidx].bytes) { tp[minidx].count = cp->count; tp[minidx].nomod = cp->nomod; tp[minidx].bytes = cp->bytes; tp[minidx].tm = cp->tm; } cp->count = 0L; cp->nomod = 0L; cp->bytes = 0.0; return; } /* ** Dual fprintf: writes into two files simultanously. */ /*PRINTFLIKE3*/ static void dfpr(FILE * const ofp, FILE * const ffp, char * const fmt, ...) { va_list ap; if (ofp != NULL) { va_start(ap, fmt); (void) vfprintf(ofp, fmt, ap); va_end(ap); } if (ffp != NULL) { va_start(ap, fmt); (void) vfprintf(ffp, fmt, ap); va_end(ap); } return; } /* ** Print a HTML header. */ /*PRINTFLIKE3*/ static void html_header(FILE * const ofp, char * const period, char * const jscript, ...) { va_list ap; (void) fprintf(ofp, "\n" "\n\n\n"); if (!period) (void) fprintf(ofp, "Access Statistics for %s\n", srv_name); else if (*period) (void) fprintf(ofp, "%s %s (%s)\n", doc_title, srv_name, period); if (jscript) { va_start(ap, jscript); (void) vfprintf(ofp, jscript, ap); va_end(ap); } if (period && !*period) { (void) fprintf(ofp, "\n\n"); return; } (void) fprintf(ofp, "\n\n"); if (html_str[HTML_HEADPFX]) (void) fprintf(ofp, "%s\n", html_str[HTML_HEADPFX]); else (void) fprintf(ofp, "\n"); if (html_str[HTML_HEADSFX]) (void) fprintf(ofp, "\n%s\n", html_str[HTML_HEADSFX]); return; } /* ** Print an HTML trailer. */ static void html_trailer(FILE * const ofp) { if (html_str[HTML_TRAILER]) (void) fprintf(ofp, "%s\n", html_str[HTML_TRAILER]); (void) fprintf(ofp, "

\n" "\n" "\n
\n" "\n", ha_home, creator); if (maxbtn != BTN_CUSTOMW) (void) fprintf(ofp, "\n", copyright); (void) fprintf(ofp, "\n
\n" "%s%s" "%s

\n
\n" "\n\n", last_update); return; } #define PERCENT(val, max) ((val&&max) ? ((float)(val)*100.0)/(float)(max) : 0.0) #define PR_TOP(ofp, ffp, label, c1, c2, c3, c4, c5) \ dfpr((ofp), (ffp), \ "%d\n" \ "%lu\n" \ "%4.2f%%\n" \ "%lu\n" \ "%4.2f%%\n" \ "%lu\n", \ (label), (c1), (c2), (c3), (c4), (c5)) #define FMT_HEAD(cspan, label) \ "" \ "" label "\n" #define FMT_SPACE(ht) "\n" #define KBYTES(val) ((u_long)(((val)/1024.0)+0.9)) #define FMT_SUM(label) \ "" \ "" label "\n" \ "" \ "%lu\n" /* File flags (indicate existance) */ #define FNAME_FILES 0 #define FNAME_LFILES 1 #define FNAME_RFILES 2 #define FNAME_SITES 3 #define FNAME_LSITES 4 #define FNAME_RSITES 5 #define FNAME_AGENTS 6 #define FNAME_LAGENTS 7 #define FNAME_REFERS 8 #define FNAME_LREFERS 9 #define FNAME_TOPFILES 10 #define FNAME_TOPLFILES 11 #define FNAME_TOPSITES 12 #define FNAME_TOPAGENTS 13 #define FNAME_TOPREFERS 14 #define FNAME_SIZE 15 static size_t lntab[FNAME_SIZE]; /* ** Print daily table entries. */ static void prIdxTab(FILE * const ofp, FILE * const ffp, COUNTER *base, int const stop, u_long const maxval, char * const label) { int idx; COUNTER *dp; char cell_C[] = "%d\n"; char cell_R[] = "%lu" "%4.2f%%\n"; dfpr(ofp, ffp, "

\n" "\n"); dfpr(ofp, ffp, "\n" "\n" "\n", label); if (!nopageviews) dfpr(ofp, ffp, "\n"); else dfpr(ofp, ffp, "\n"); dfpr(ofp, ffp, "\n" "\n" FMT_SPACE("4")); for (idx=0; idx < stop; idx++) { dp = base + idx; dfpr(ofp, ffp, dp->hits == maxval ? "" : ""); dfpr(ofp, ffp, cell_C, idx+1); dfpr(ofp, ffp, cell_R, dp->hits, PERCENT(dp->hits, total.hits)); dfpr(ofp, ffp, cell_R, dp->files, PERCENT(dp->files, total.files)); if (!nopageviews) dfpr(ofp, ffp, cell_R, dp->views, PERCENT(dp->views, total.views)); else dfpr(ofp, ffp, cell_R, dp->nomod, PERCENT(dp->nomod, total.nomod)); dfpr(ofp, ffp, cell_R, dp->sessions, PERCENT(dp->sessions, total.sessions)); dfpr(ofp, ffp, cell_R, KBYTES(dp->bytes), PERCENT(dp->bytes, total.bytes)); dfpr(ofp, ffp, "\n"); } dfpr(ofp, ffp, FMT_SPACE("4") "" "\n"); dfpr(ofp, ffp, cell_R, total.hits, 100.0); dfpr(ofp, ffp, cell_R, total.files, 100.0); dfpr(ofp, ffp, cell_R, !nopageviews ? total.views : total.nomod, 100.0); dfpr(ofp, ffp, cell_R, total.sessions, 100.0); dfpr(ofp, ffp, cell_R, KBYTES(total.bytes), 100.0); dfpr(ofp, ffp, "\n" FMT_SPACE("4") "
" "%s" "Hits" "Files" "Pageviews" "304's" "Sessions" "KBytes sent" "
" "Total

\n
\n"); return; } /* ** Open a file for writing, exit on failure. */ /*PRINTFLIKE1*/ static FILE *efopen(char * const fmt, ...) { static char fname[MAX_FNAMELEN]; FILE *ofp; va_list ap; va_start(ap, fmt); (void) vsprintf(fname, fmt, ap); va_end(ap); errno = 0; if ((ofp=fopen(fname, "w")) == NULL) prmsg(2, enoent, fname, strerror(errno)); return ofp; } /* ** Print daily statistics. */ static void prDayStats(char * const stdir) { char fname[MAX_FNAMELEN], period[SMALLSIZE]; int curday; FILE *ofp; (void) sprintf(period, "%s %hu", monnam[t.end.mon], t.end.year); if (verbose) prmsg(0, "Creating short statistics for %s\n", period); for (curday=0; curday < (int)t.end.mday; curday++) { if (daily[curday].hits > max_day.hits) max_day.hits = daily[curday].hits; if (daily[curday].sessions > max_day.sessions) max_day.sessions = daily[curday].sessions; if (daily[curday].bytes > max_day.bytes) max_day.bytes = daily[curday].bytes; } if ((ofp=efopen("%s/stats.html", stdir)) == NULL) exit(1); html_header(ofp, period, NULL); dfpr(ofp, NULL, "

\n" "\n" "\n" "
Short statistics for %s

\n
\n", period); if (!noimages) { (void) fprintf(ofp, "

\n" "\"hits

\n
\n"); (void) sprintf(fname, "%s/stats.gif", stdir); (void) mn_bars(492, 317, 28, daily, t.end.mday, 31, fname, "by day"); } prIdxTab(ofp, NULL, daily, (int)t.current.mday, max_day.hits, "Day"); (void) fprintf(ofp, "

\n" "\n"); if (lic) { prEscHTML(ofp, lic->company); (void) fprintf(ofp, " · %s", lic->regID); } else (void) fprintf(ofp, "Evaluation version - " "please register your copy

\n
\n", ha_reg); html_trailer(ofp); (void) fclose(ofp); return; } /* ** Print items from a Top N list. */ static void prTopList(FILE * const ofp, TOP_COUNTER * const base, size_t const num, int which) { char *label1, *label2; int idx; switch (which) { default: assert(which != which); break; case 0: label1 = "days"; label2 = "Date"; break; case 1: label1 = "hours"; label2 = "Date/Time"; break; case 2: label1 = "minutes"; label2 = "Date/Time"; break; case 3: label1 = "seconds"; label2 = "Date/Time"; break; } dfpr(ofp, NULL, "

\n" "\n" FMT_SPACE("4") FMT_HEAD("7", "The Top %d %s of the period") FMT_SPACE("4"), num, label1); dfpr(ofp, NULL, FMT_SPACE("4") "\n" "\n" "\n" "\n" "\n" FMT_SPACE("4"), label2); for (idx=0; idx < num; idx++) if (base[idx].count > 0L) { PR_TOP(ofp, NULL, idx+1, base[idx].count, PERCENT(base[idx].count, total.hits), base[idx].nomod, PERCENT(base[idx].nomod, total.nomod), KBYTES(base[idx].bytes)); dfpr(ofp, NULL, "\n"); } dfpr(ofp, NULL, FMT_SPACE("4") "
No.Hits304'sKBytes sent%s
%02d/%3.3s/%d", base[idx].tm.mday, monnam[base[idx].tm.mon], base[idx].tm.year); if (which == 1) dfpr(ofp, NULL, ":%02d:XX:XX", base[idx].tm.hour); else if (which == 2) dfpr(ofp, NULL, ":%02d:%02d:XX", base[idx].tm.hour, base[idx].tm.min); else if (which == 3) dfpr(ofp, NULL, ":%02d:%02d:%02d", base[idx].tm.hour, base[idx].tm.min, base[idx].tm.sec); dfpr(ofp, NULL, "

\n
\n"); return; } /* ** Print navigation bar for month. */ static void prNavMon(FILE * const ofp, FILE * const efp) { dfpr(ofp, NULL, "

\n

\n

\n"); dfpr(NULL, efp, FMT_SPACE("4") "\n", t.start.mon+1, EPOCH(t.start.year), monnam[t.end.mon], t.start.year); dfpr(ofp, efp, FMT_SPACE("4") "\n" "\n"); if (lntab[FNAME_FILES] || lntab[FNAME_RFILES] || lntab[FNAME_LFILES] || lntab[FNAME_TOPFILES]) { dfpr(ofp, efp, "\n\n"); } if (lntab[FNAME_SITES] || lntab[FNAME_RSITES] || lntab[FNAME_LSITES] || lntab[FNAME_TOPSITES]) { dfpr(ofp, efp, "\n\n"); } if (lntab[FNAME_AGENTS] || lntab[FNAME_LAGENTS] || lntab[FNAME_TOPAGENTS]) { dfpr(ofp, efp, "\n\n"); } if (lntab[FNAME_REFERS] || lntab[FNAME_LREFERS] || lntab[FNAME_TOPREFERS]) { dfpr(ofp, efp, "\n\n"); } dfpr(ofp, efp, FMT_SPACE("4") "\n" FMT_SPACE("4") "
" "" "Full Statistics for " "%s %d
Hits:\n"); dfpr(NULL, efp, "", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "by Day /\n"); if (!noload) { dfpr(NULL, efp, "", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "by Weekday & Hour /\n"); } if (!nocntrylist) { dfpr(NULL, efp, "", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "by Country /\n"); } #if defined(VRML) if (use_vrml) { dfpr(ofp, efp, "3D model\n", t.start.mon+1, EPOCH(t.start.year), t.start.mon+1, EPOCH(t.start.year)); } #endif dfpr(ofp, efp, "
" "" "Items/URLs:" ""); if (lntab[FNAME_TOPFILES] || lntab[FNAME_TOPLFILES]) { dfpr(NULL, efp, "", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "Top Ten /\n"); } if (lntab[FNAME_FILES]) { dfpr(NULL, efp, "", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "Overview /\n"); } if (lntab[FNAME_RFILES]) { dfpr(NULL, efp, "", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "Not Found /\n"); } if (lntab[FNAME_LFILES]) dfpr(ofp, efp, "List\n", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "
" "" "Client Domain:" ""); if (lntab[FNAME_TOPSITES]) { dfpr(NULL, efp, "", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "Top Ten /\n"); } if (lntab[FNAME_SITES]) { dfpr(NULL, efp, "", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "Overview /\n"); } if (lntab[FNAME_RSITES]) { dfpr(NULL, efp, "", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "Reverse Domain /\n"); } if (lntab[FNAME_LSITES]) dfpr(ofp, efp, "List\n", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "
" "" "Browser Type:" ""); if (lntab[FNAME_TOPAGENTS]) { dfpr(NULL, efp, "", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "Top Ten /\n"); } if (lntab[FNAME_AGENTS]) { dfpr(NULL, efp, "", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "Overview /\n"); } if (lntab[FNAME_LAGENTS]) dfpr(ofp, efp, "List\n", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "
" "" "Referrer URL:" ""); if (lntab[FNAME_TOPREFERS]) { dfpr(NULL, efp, "", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "Top Ten /\n"); } if (lntab[FNAME_REFERS]) { dfpr(NULL, efp, "", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "Overview /\n"); } if (lntab[FNAME_LREFERS]) dfpr(ofp, efp, "List\n", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, efp, "
" ""); dfpr(ofp, NULL, "Summary for %d", t.start.year); dfpr(NULL, efp, "" "Summary for %d / Close navigation window", t.start.year); dfpr(ofp, efp, "

\n
\n"); return; } /* ** Print navigation bar for year. */ static void prNavYear(FILE * const ofp, char * const period, int const curlink) { int idx, ndx = 0; (void) fprintf(ofp, "

\n" "\n" FMT_SPACE("4") "\n", period); (void) fprintf(ofp, FMT_SPACE("4") "\n", t.end.mon+1, EPOCH(t.end.year), t.end.mon+1, EPOCH(t.end.year), monnam[t.end.mon], t.end.year); } else (void) fprintf(ofp, "%s %d\n", monnam[t.end.mon], t.end.year); for (idx=(int)t.end.mon; --idx >= 0; ) { (void) fprintf(ofp, "%s\n" : "\n"); if (monly[idx].hits) (void) fprintf(ofp, "%s %d\n", idx+1, EPOCH(t.end.year), idx+1, EPOCH(t.end.year), monnam[idx], t.end.year); else (void) fprintf(ofp, "%s %d\n", monnam[idx], t.end.year); } for (idx=11; idx > (int)t.end.mon; idx--) { (void) fprintf(ofp, "%s\n" : "\n"); if (monly[idx].hits) (void) fprintf(ofp, "%s %d\n", t.end.year-1U, idx+1, EPOCH(t.end.year-1U), t.end.year-1U, idx+1, EPOCH(t.end.year-1U), monnam[idx], t.end.year-1U); else (void) fprintf(ofp, "%s %d\n", monnam[idx], t.end.year-1U); } (void) fprintf(ofp, "\n" FMT_SPACE("4") "\n" FMT_SPACE("4") "
" "" "" "WWW Access Statistics for %s
\n" ""); if (curlink) { (void) fprintf(ofp, "%s %d\n" "", (++ndx%3) == 0 ? "
\n" "", (++ndx%3) == 0 ? "
" "" "Close navigation window

\n
\n"); return; } /* ** Print frameset and navigation subframe. */ static void prFrameHeader(char * const stdir) { char tbuf[MAX_FNAMELEN]; char *temp; FILE *ofp; int idx; if ((ofp=efopen("%s/frames.html", stdir)) == NULL) exit(1); (void) fprintf(ofp, "\n" "\n\n\n" "%s %s\n\n" "\n" " \n" " \n" "\n\n<BODY>\n<P>\nPlease use the <A" " HREF=\"index.html\">non-frame version</A> of the summary report.</P>\n" "</BODY>\n\n\n", doc_title, srv_name, nav_frame, t.start.year); (void) fclose(ofp); if ((ofp=efopen("%s/header.html", stdir)) == NULL) exit(1); (void) fprintf(ofp, "\n" "\n\n" "\n" "\n" "Table of Content\n\n" "\n\n" "\n\n" "\n" "
\n" "
\n

\n"); #define MAKE_BUTTON(number, which, cond) \ if (cond) \ (void) fprintf(ofp, \ "\n"); \ else (void) fprintf(ofp, \ "\n"); \ (void) fprintf(ofp, "\"%s\"
\n", \ buttons[(which)].name, buttons[(which)].text, \ buttons[(which)].wid, buttons[(which)].ht) /*CONSTCOND*/ MAKE_BUTTON(1, BTN_YEAR, 1); (void) fprintf(ofp, "" "
\n"); /*CONSTCOND*/ MAKE_BUTTON(2, BTN_TOTALS, 1); /*CONSTCOND*/ MAKE_BUTTON(3, BTN_DAYS, 1); MAKE_BUTTON(4, BTN_AVLOAD, !noload); MAKE_BUTTON(5, BTN_TOPURL, lntab[FNAME_TOPFILES] != 0); MAKE_BUTTON(6, BTN_TOPDOM, lntab[FNAME_TOPSITES] != 0); MAKE_BUTTON(7, BTN_TOPUAG, lntab[FNAME_TOPAGENTS] != 0); MAKE_BUTTON(8, BTN_TOPREF, lntab[FNAME_TOPREFERS] != 0); MAKE_BUTTON(9, BTN_COUNTRY,!nocntrylist); MAKE_BUTTON(10, BTN_FILES, lntab[FNAME_FILES] != 0); MAKE_BUTTON(11, BTN_RFILES, lntab[FNAME_RFILES] != 0); MAKE_BUTTON(12, BTN_SITES, lntab[FNAME_SITES] != 0); MAKE_BUTTON(13, BTN_RSITES, lntab[FNAME_RSITES] != 0); MAKE_BUTTON(14, BTN_AGENTS, lntab[FNAME_AGENTS] != 0); MAKE_BUTTON(15, BTN_REFER, lntab[FNAME_REFERS] != 0); #undef MAKE_BUTTON #if defined(VRML) if (use_vrml) { (void) fprintf(ofp, "" "" " VRML
\n"); if (vrml_prlg) (void) fprintf(ofp, "" " PC \n" " SGI
\n"); } #endif (void) fprintf(ofp, "\n" "


\n" "" "Main Page
\n"); if (lic && access("docs.html", F_OK) == 0) (void) fprintf(ofp, ""); else (void) fprintf(ofp, "", ha_home); (void) fprintf(ofp, "" "Online Documentation\n
\n"); if (buttons[BTN_CUSTOMB].name) (void) fprintf(ofp, "" "\"\"
\n
\n", buttons[BTN_CUSTOMB].text, buttons[BTN_CUSTOMB].name, buttons[BTN_CUSTOMB].wid, buttons[BTN_CUSTOMB].ht); (void) fprintf(ofp, "\"\""
\n" "" "http-analyze %s
\n%s

\n
\n" "
\n\n\n\n", !lic ? ha_reg : ha_home, buttons[BTN_NETSTORESB].name, buttons[BTN_NETSTORESB].wid, buttons[BTN_NETSTORESB].ht, version, copyright); (void) fclose(ofp); return; } #if defined(VRML) /* ** Create a VRML model */ static void prVRMLModel(char * const stdir, char * const srvname, char * const period) { char model[MEDIUMSIZE], templ[MEDIUMSIZE]; FILE *ofp; int rc; if (period) (void) sprintf(templ, "3Dstats%02d%02d", t.start.mon+1, EPOCH(t.start.year)); else (void) sprintf(templ, "3Dstats%04hu", t.start.year); (void) sprintf(model, "%s/%s.wrl", stdir ? stdir : ".", templ); if ((ofp=efopen(model)) == NULL) exit(1); if (period) { /* create VRML model */ if (verbose) prmsg(0, "Creating VRML model for %s\n", period); rc = prVRMLStats(ofp, srvname, period, &t.end); } else { if (verbose) prmsg(0, "Updating VRML model for %04d\n", t.end.year); rc = prVRMLYear(ofp, srvname, vrml_prlg, &t.end); } (void) fclose(ofp); if (!rc) /* something went wrong */ return; if (!compress(model)) { prmsg(1, ecompr, model); (void) sprintf(model, "%s.wrl", templ); } else (void) sprintf(model, "%s.wrl.gz", templ); /* create HTML file with VRML inline object */ if ((ofp=efopen("%s/%s.html", stdir ? stdir : ".", templ)) == NULL) exit(1); (void) fprintf(ofp, "\n" "\n\n\n" "3D Access statistics for %s", srvname); if (period) (void) fprintf(ofp, " (%s)", period); (void) fprintf(ofp, "\n\n" "\n" "

\n" "\n" "The VRML model requires a VRML 2.0 plug-in such\n" "as CosmoPlayer from SGI. If you have an external viewer\n" "which is fully VRML 2.0 compliant, <A HREF=\"%s\">download\n" "here the compressed model</A>.
\n" "" "Created by %s. %s

\n
\n" "\n\n", model, model, creator, copyright); (void) fclose(ofp); return; } #endif static void prLinks(FILE * const ofp, char * const title, char * const dpfx) { char fname[MAX_FNAMELEN]; char period[SMALLSIZE]; int idx = (int)t.current.year; int rc = 0; (void) fprintf(ofp, "

\n" "\n" "\n
\n" "" "%s Access Statistics
\n
\n" "\n", title); for (rc=0; idx > 1995; idx--) { /* create links for previous summary periods */ (void) sprintf(fname, "%s%04d", dpfx, idx); if (access(fname, F_OK) == 0) { rc++; (void) sprintf(period, (idx == (int)t.current.year) ? "the last 12 months" : "%04d", idx); (void) fprintf(ofp, "\n\n", fname); } } if (!rc && idx == 1995) (void) fprintf(ofp, "\n"); (void) fprintf(ofp, "
\"\""\n" "Statistics for %s", fname, fname, fname, fname, fname, period); (void) sprintf(fname, "%s%04d/frames.html", dpfx, idx); if (access(fname, F_OK) == 0) (void) fprintf(ofp, "
\nFrames version" "
Not available (yet)

\n
\n\n"); return; } /* ** JavaScript functions. */ static char *jscreate = "\n"; static char *jsloadpg = "Navigation Window\n" "\n"; /* ** Print an index file. */ static void prIndex(void) { FILE *ofp; errno = 0; if ((ofp=fopen("index.html", "w")) == NULL) { prmsg(2, "Couldn't create file `index.html' (%s)\n", strerror(errno)); return; } html_header(ofp, NULL, jscreate, navwid, navht, w3wid, w3ht); prLinks(ofp, "WWW", "www"); if (lic) { prLinks(ofp, "FTP", "ftp"); /*prLinks(ofp, "RealAudio", "ra");*/ } if (html_str[HTML_TRAILER]) (void) fprintf(ofp, "%s\n", html_str[HTML_TRAILER]); (void) fprintf(ofp, "

\n


\n" "\n" "\n" "\n" "\n
\n" "\"%s\""\n", !lic ? ha_reg : ha_home, buttons[BTN_NETSTORESW].name, buttons[BTN_NETSTORESW].text, buttons[BTN_NETSTORESW].wid, buttons[BTN_NETSTORESW].ht); if (lic && access("docs.html", F_OK) == 0) (void) fprintf(ofp, ""); else (void) fprintf(ofp, "", ha_home); (void) fprintf(ofp, "Online Documentation" "
\nStatistics by %s, %s
\n", ha_home, creator, copyright); if (lic) (void) fprintf(ofp, "%s · %s", lic->company, lic->regID); else (void) fprintf(ofp, "Evaluation version - " "please register your copy", ha_reg); (void) fprintf(ofp, "
\n"); if (buttons[BTN_CUSTOMW].name) (void) fprintf(ofp, "\"\""", buttons[BTN_CUSTOMW].text, buttons[BTN_CUSTOMW].name, buttons[BTN_CUSTOMW].wid, buttons[BTN_CUSTOMW].ht); else (void) fprintf(ofp, "\"\""", buttons[BTN_RAGSW].text, buttons[BTN_RAGSW].name, buttons[BTN_RAGSW].wid, buttons[BTN_RAGSW].ht); (void) fprintf(ofp, "

\n
\n" "\n\n"); (void) fclose(ofp); return; } /* ** Print a monthly index file. */ static void prMonIndex(char * const stdir, int const current, int const last) { char fname[MAX_FNAMELEN]; char period[SMALLSIZE]; int idx, dst = TABSIZE(grmon)-1; int curlink = 0; /* link to the full stats of currrent month */ COUNTER *mp; /* ptr to monthly COUNTER structure */ FILE *ofp, *ffp; /* ** Create an index file. */ (void) sprintf(period, current ? "the last 12 months" : "%hu", t.end.year); (void) sprintf(fname, "%s/index.html", stdir); if (access(fname, F_OK) == 0) { if (!monthly) /* suppress summary in short stats mode */ return; /* ... unless file does not exist */ if (verbose && !current && last) prmsg(0, "... updating `%s': last report is for %s %hu\n", fname, monnam[t.end.mon], t.end.year); } (void) sprintf(fname, "%s/stats%02d%02d.html", stdir, t.end.mon+1, EPOCH(t.end.year)); curlink = monthly ? (!current || t.end.mday > 1) : (access(fname, F_OK) == 0); if ((ofp=efopen("%s/index.html", stdir)) == NULL || (ffp=efopen("%s/jsnav.html", stdir)) == NULL) exit(1); html_header(ofp, period, jscreate, navwid, navht, w3wid, w3ht); html_header(ffp, "", jsloadpg, w3wid, w3ht); prNavYear(ffp, period, curlink); (void) fprintf(ffp, "\n\n"); (void) fclose(ffp); ffp = NULL; if (use_frames) { if ((ffp=efopen("%s/fstats%hu.html", stdir, t.end.year)) == NULL) exit(1); html_header(ffp, period, NULL); } dfpr(ofp, ffp, "

\n" "\n" "\n" "
%s %s

\n
\n", doc_title, period); if (!noimages) dfpr(ofp, ffp, "

\n" "\"hits" "

\n
\n", t.end.year); dfpr(ofp, ffp, "

\n" "\n"); if (current) { (void) sprintf(fname, "%s/stats.html", stdir); if (access(fname, F_OK) == 0) { dfpr(ofp, ffp, FMT_SPACE("4") "\n", monnam[t.end.mon], t.end.year); } } dfpr(ofp, ffp, FMT_SPACE("4") "\n" "\n" "\n"); if (!nopageviews) dfpr(ofp, ffp, "\n"); else dfpr(ofp, ffp, "\n"); dfpr(ofp, ffp, "\n" "\n" FMT_SPACE("4") "\n"); dfpr(ofp, ffp, " " "\n" "\n" "\n" "\n", total.hits, total.files, !nopageviews ? total.views : total.nomod, total.sessions, KBYTES(total.bytes)); cksum.hits = grmon[dst].hits = total.hits; cksum.files = grmon[dst].files = total.files; cksum.nomod = grmon[dst].nomod = total.nomod; cksum.views = grmon[dst].views = total.views; cksum.sessions = grmon[dst].sessions = total.sessions; cksum.bytes = grmon[dst].bytes = total.bytes; if (current && !nointerpol) { /* interpolate values based on average values */ int leap = t.end.year%4 == 0 && t.end.year%100 != 0 || t.end.year%400 == 0; int ddiff = (int)(mdays[leap][t.end.mon] - t.end.mday); if (ddiff > 0) { grmon[dst].hits += (grmon[dst].hits * (u_long)ddiff) / (u_long)t.current.mday; grmon[dst].files += (grmon[dst].files * (u_long)ddiff) / (u_long)t.current.mday; grmon[dst].nomod += (grmon[dst].nomod * (u_long)ddiff) / (u_long)t.current.mday; grmon[dst].views += (grmon[dst].views * (u_long)ddiff) / (u_long)t.current.mday; grmon[dst].sessions += (grmon[dst].sessions * (u_long)ddiff) / (u_long)t.current.mday; grmon[dst].bytes += (grmon[dst].bytes * (float)ddiff) / (float)t.current.mday; } } grlabel[dst--] = monnam[t.end.mon]; for (idx=(int)t.end.mon; --idx >= 0; ) { mp = &monly[idx]; dfpr(ofp, ffp, "\n" "\n" "\n" "\n" "\n" "\n", mp->hits, mp->files, !nopageviews ? mp->views : mp->nomod, mp->sessions, KBYTES(mp->bytes)); cksum.hits += (grmon[dst].hits = mp->hits); cksum.files += (grmon[dst].files = mp->files); cksum.nomod += (grmon[dst].nomod = mp->nomod); cksum.views += (grmon[dst].views = mp->views); cksum.sessions += (grmon[dst].sessions = mp->sessions); cksum.bytes += (grmon[dst].bytes = mp->bytes); grlabel[dst--] = monnam[idx]; } for (idx=11; idx > (int)t.end.mon; idx--) { mp = &monly[idx]; dfpr(ofp, ffp, "\n" "\n" "\n" "\n" "\n" "\n", mp->hits, mp->files, !nopageviews ? mp->views : mp->nomod, mp->sessions, KBYTES(mp->bytes)); cksum.hits += (grmon[dst].hits = mp->hits); cksum.files += (grmon[dst].files = mp->files); cksum.nomod += (grmon[dst].nomod = mp->nomod); cksum.views += (grmon[dst].views = mp->views); cksum.sessions += (grmon[dst].sessions = mp->sessions); cksum.bytes += (grmon[dst].bytes = mp->bytes); grlabel[dst--] = monnam[idx]; } grmon[dst].hits = monly[idx].hits; /* previous year */ grmon[dst].files = monly[idx].files; grmon[dst].nomod = monly[idx].nomod; grmon[dst].views = monly[idx].views; grmon[dst].sessions = monly[idx].sessions; grmon[dst].bytes = monly[idx].bytes; grlabel[dst] = monnam[idx]; dfpr(ofp, ffp, FMT_SPACE("4") "\n" "\n" "\n" "\n" "\n" "\n" "\n", cksum.hits, cksum.files, !nopageviews ? cksum.views : cksum.nomod, cksum.sessions, KBYTES(cksum.bytes)); dfpr(ofp, ffp, FMT_SPACE("4") "\n" "\n" "\n" "\n" "\n" "\n" "\n", cksum.hits/12L, cksum.files/12L, !nopageviews ? cksum.views/12L : cksum.nomod/12L, cksum.sessions/12L, KBYTES(cksum.bytes)/12L); dfpr(ofp, ffp, FMT_SPACE("4") "
" "" "Short statistics for %s %d (updated more frequently)" "
MonthHitsFilesPageviews304'sSessionsKBytes sent
"); if (curlink) { dfpr(ofp, NULL, "%s %d\n", t.end.mon+1, EPOCH(t.end.year), t.end.mon+1, EPOCH(t.end.year), t.end.mon+1, EPOCH(t.end.year), monnam[t.end.mon], t.end.year); } else dfpr(ofp, NULL, "%s %d", monnam[t.end.mon], t.end.year); dfpr(NULL, ffp, "%s %d", monnam[t.end.mon], t.end.year); dfpr(ofp, ffp, "%lu%lu%lu%lu%lu
"); if (mp->hits > 0L) dfpr(ofp, NULL, "%s %d\n", idx+1, EPOCH(t.end.year), idx+1, EPOCH(t.end.year), idx+1, EPOCH(t.end.year), monnam[idx], t.end.year); else dfpr(ofp, NULL, "%s %d", monnam[idx], t.end.year); dfpr(NULL, ffp, "%s %d", monnam[idx], t.end.year); dfpr(ofp, ffp, "%lu%lu%lu%lu%lu
"); if (mp->hits > 0L) dfpr(ofp, NULL, "%s %d\n", t.end.year-1U, idx+1, EPOCH(t.end.year-1U), t.end.year-1U, idx+1, EPOCH(t.end.year-1U), t.end.year-1U, idx+1, EPOCH(t.end.year-1U), monnam[idx], t.end.year-1U); else dfpr(ofp, NULL, "%s %d", monnam[idx], t.end.year-1U); dfpr(NULL, ffp, "%s %d", monnam[idx], t.end.year-1U); dfpr(ofp, ffp, "%lu%lu%lu%lu%lu
Total%lu%lu%lu%lu%lu
Average%lu%lu%lu%lu%lu

\n
\n

\n"); dfpr(ofp, NULL, "\n"); (void) sprintf(fname, "%s/frames.html", stdir); if (use_frames && access(fname, F_OK) == 0) { dfpr(ofp, NULL, "\n"); } else (void) fprintf(ofp, "\n"); dfpr(ofp, NULL, "\n"); if (use_vrml) { (void) fprintf(ofp, "\n"); } else (void) fprintf(ofp, ""); dfpr(ofp, NULL, "\n
\n" "" "Frames version
\n" "" "(requires JavaScript)
\n" "\nBack to the Main Page
\n"); if (lic) dfpr(ofp, ffp, "%s · %s", lic->company, lic->regID); else dfpr(ofp, ffp, "Evaluation version - " "please register your copy", ha_reg); dfpr(NULL, ffp, "

\n\n"); dfpr(ofp, NULL, "
\n" "" "" "3D model
\n" "" "(requires VRML)

\n
\n"); if (ffp) { html_trailer(ffp); (void) fclose(ffp); } html_trailer(ofp); (void) fclose(ofp); if (!noimages) { (void) sprintf(fname, "%s/graph%hu.gif", stdir, t.end.year); (void) graph(&grmon[0], grlabel, 490, 317, 28, t.end.year, fname); (void) sprintf(fname, "%s/gr-icon.gif", stdir); (void) graph(&grmon[0], NULL, 50, 32, 0, t.end.year, fname); } #if defined(VRML) if (use_vrml) { errno = 0; if ((ffp=fopen("3Dlogo.html", "w")) == NULL) { prmsg(2, enoent, "3Dlogo.html", strerror(errno)); exit(1); } (void) fprintf(ffp, /* create 3D logo file */ "\n" "\n\n\n" "3D Access statistics\n\n" "\n" "

\n" "\n" "The VRML model requires a VRML 2.0 plug-in such as\n" "CosmoPlayer from SGI. If you have an external viewer which is\n" "fully VRML 2.0 compliant, <A HREF=\"3Dlogo.wrl.gz\">download\n" "here the compressed model</A>.
\n" ""); for (idx=0; idx < 12; idx++) { (void) sprintf(fname, "%s/3Dstats%02d%02d.html", stdir, idx+1, EPOCH(t.end.year)); if (access(fname, F_OK) == 0) { (void) fprintf(ffp, "%.3s%s\n", fname, monnam[idx], idx < 11 ? " |" : ""); continue; } (void) fprintf(ffp, "%.3s%s\n", monnam[idx], idx < 11 ? " |" : ""); } (void) fprintf(ffp, "

\n
\n\n\n"); (void) fclose(ffp); } #endif return; } /* ** Sort list by hits, hidden item, country, agent, and lexicographically. */ static int sort_by_hits(const void *e1, const void *e2) { if ((*(NLIST **)e2)->count == (*(NLIST **)e1)->count) return 0; return ((*(NLIST **)e2)->count < (*(NLIST **)e1)->count) ? -1 : 1; } static int sort_by_item(const void *e1, const void *e2) { register size_t stamp1 = (size_t)(*(NLIST **)e1)->ishidden; /* already checked for -1 */ register size_t stamp2 = (size_t)(*(NLIST **)e2)->ishidden; register ITEM_LIST *ip = hidden[HIDDEN_ITEMS].tab; if (stamp1 == stamp2 || ip[stamp1].col == ip[stamp2].col) { if ((*(NLIST **)e2)->count == (*(NLIST **)e1)->count) return 0; return ((*(NLIST **)e2)->count < (*(NLIST **)e1)->count) ? -1 : 1; } if (ip[stamp1].col->count == ip[stamp2].col->count) return stamp2 - stamp1; return (ip[stamp2].col->count < ip[stamp1].col->count) ? -1 : 1; } static int sort_by_domain(const void *e1, const void *e2) { register size_t stamp1 = (size_t)(*(NLIST **)e1)->ishidden; register size_t stamp2 = (size_t)(*(NLIST **)e2)->ishidden; register ITEM_LIST *ip = hidden[HIDDEN_SITES].tab; if (stamp1 == stamp2 || ip[stamp2].col == ip[stamp1].col) { if ((*(NLIST **)e2)->count == (*(NLIST **)e1)->count) return 0; return ((*(NLIST **)e2)->count < (*(NLIST **)e1)->count) ? -1 : 1; } if (ip[stamp1].col->count == ip[stamp2].col->count) return stamp2 < stamp1 ? -1 : 1; return (ip[stamp2].col->count < ip[stamp1].col->count) ? -1 : 1; } static int sort_by_revdom(const void *e1, const void *e2) { static char nbuf1[MEDIUMSIZE], nbuf2[MEDIUMSIZE]; revDomain((*(NLIST **)e1)->str, (*(NLIST **)e1)->len, nbuf1, sizeof(nbuf1)); revDomain((*(NLIST **)e2)->str, (*(NLIST **)e2)->len, nbuf2, sizeof(nbuf2)); return strcasecmp(nbuf1, nbuf2); } static int sort_by_agent(const void *e1, const void *e2) { register size_t stamp1 = (size_t)(*(NLIST **)e1)->ishidden; register size_t stamp2 = (size_t)(*(NLIST **)e2)->ishidden; register ITEM_LIST *ip = hidden[HIDDEN_AGENTS].tab; if (stamp1 == stamp2 || ip[stamp2].col == ip[stamp1].col) { if ((*(NLIST **)e2)->count == (*(NLIST **)e1)->count) return 0; return ((*(NLIST **)e2)->count < (*(NLIST **)e1)->count) ? -1 : 1; } if (ip[stamp1].col->count == ip[stamp2].col->count) return stamp2 < stamp1 ? -1 : 1; return (ip[stamp2].col->count < ip[stamp1].col->count) ? -1 : 1; } static int sort_by_refer(const void *e1, const void *e2) { register size_t stamp1 = (size_t)(*(NLIST **)e1)->ishidden; register size_t stamp2 = (size_t)(*(NLIST **)e2)->ishidden; register ITEM_LIST *ip = hidden[HIDDEN_REFERS].tab; if (stamp1 == stamp2 || ip[stamp1].col == ip[stamp2].col) { if ((*(NLIST **)e2)->count == (*(NLIST **)e1)->count) return 0; return ((*(NLIST **)e2)->count < (*(NLIST **)e1)->count) ? -1 : 1; } if (ip[stamp1].col->count == ip[stamp2].col->count) return stamp2 < stamp1 ? -1 : 1; return (ip[stamp2].col->count < ip[stamp1].col->count) ? -1 : 1; } static int sort_by_cntry(const void *e1, const void *e2) { return ((*(COUNTRY **)e2)->count - (*(COUNTRY **)e1)->count); } static int sort_by_casestr(const void *e1, const void *e2) { return strcasecmp(((ITEM_LIST *)e1)->pfx, ((ITEM_LIST *)e2)->pfx); } static int sort_by_string(const void *e1, const void *e2) { return strcmp(((ITEM_LIST *)e1)->pfx, ((ITEM_LIST *)e2)->pfx); } /* ** Sort top list by hits and data sent. */ static int sort_top(const void *e1, const void *e2) { if (((TOP_COUNTER *)e2)->count == ((TOP_COUNTER *)e1)->count) { if (((TOP_COUNTER *)e2)->bytes > ((TOP_COUNTER *)e1)->bytes) return 1; else if (((TOP_COUNTER *)e2)->bytes > ((TOP_COUNTER *)e1)->bytes) return -1; else return 0; } return ((TOP_COUNTER *)e2)->count < ((TOP_COUNTER *)e1)->count ? -1 : 1; } /* ** Print monthly summary. */ #define SQ_WID(max, val) (!(val) || !(max) ? 1L : (u_long)(((val)*100.0)/(max))+1L) static void prMonStats(char * const stdir) { char tbuf[MAX_FNAMELEN]; /* name of output file */ char period[SMALLSIZE]; /* statistics period */ int idx, curday, prhead; /* temp, current day, flag */ u_long kbytes; /* total KB sent */ COUNTER hsum; COUNTRY *ccp, **clist = NULL; /* ptr into (sorted) country list */ FILE *ofp, *ffp; (void) sprintf(period, "%s %hu", monnam[t.end.mon], t.end.year); (void) memset((void *)lntab, 0, sizeof lntab); /* ** Compute average and max hits */ for (curday=0; curday < (int)t.end.mday; curday++) { int cc = wdtab[curday]; wh_cnt[cc]++; if (daily[curday].hits > max_day.hits) max_day.hits = daily[curday].hits; if (daily[curday].sessions > max_day.sessions) max_day.sessions = daily[curday].sessions; if (daily[curday].bytes > max_day.bytes) max_day.bytes = daily[curday].bytes; avg_day.hits += daily[curday].hits; avg_day.files += daily[curday].files; avg_day.nomod += daily[curday].nomod; avg_day.views += daily[curday].views; avg_day.other += daily[curday].other; } avg_day.hits /= (u_long)t.end.mday; avg_day.files /= (u_long)t.end.mday; avg_day.nomod /= (u_long)t.end.mday; avg_day.views /= (u_long)t.end.mday; avg_day.other /= (u_long)t.end.mday; if (max_day.bytes < 1024.0) max_day.bytes = 1024.0; #if defined(VRML) if (use_vrml) { for (curday=0; curday < 7; curday++) { for (idx=0; idx < 24; idx++) { if (wh_hits[curday][idx] && wh_cnt[curday]) wh_hits[curday][idx] /= (u_long)wh_cnt[curday]; if (max_whhits < wh_hits[curday][idx]) max_whhits = wh_hits[curday][idx]; avg_wday[curday] += wh_hits[curday][idx]; avg_whour[idx] += wh_hits[curday][idx]; } if (wh_cnt[curday]) { if (weekly[curday].hits) weekly[curday].hits /= (u_long)wh_cnt[curday]; if (weekly[curday].files) weekly[curday].files /= (u_long)wh_cnt[curday]; if (weekly[curday].nomod) weekly[curday].nomod /= (u_long)wh_cnt[curday]; if (weekly[curday].views) weekly[curday].views /= (u_long)wh_cnt[curday]; if (weekly[curday].other) weekly[curday].other /= (u_long)wh_cnt[curday]; } avg_wday[curday] /= 24L; if (max_avdhits < avg_wday[curday]) max_avdhits = avg_wday[curday]; } for (idx=0; idx < 24; idx++) { avg_hour[idx] /= (u_long)t.end.mday; if (max_avhits < avg_hour[idx]) max_avhits = avg_hour[idx]; avg_whour[idx] /= 7L; if (max_avhhits < avg_whour[idx]) max_avhhits = avg_whour[idx]; } if (max_avhits < 1L) max_avhits = 1L; if (max_avdhits < 1L) max_avdhits = 1L; if (max_avhhits < 1L) max_avhhits = 1L; } else #endif if (!noimages) { for (idx=0; idx < 24; idx++) { avg_hour[idx] /= (u_long)t.end.mday; if (max_avhits < avg_hour[idx]) max_avhits = avg_hour[idx]; } if (max_avhits < 1L) max_avhits = 1L; } #if defined(VRML) if (use_vrml) { prVRMLModel(stdir, srv_name, period); if (vrml_prlg) prVRMLModel(stdir, srv_name, NULL); } #endif if (verbose) prmsg(0, "Creating full statistics for %s\n", period); if (uniq_urls) prURLStats(stdir, period); else if (verbose) prmsg(0, "... no URLs found\n"); if (uniq_sites) prSiteStats(stdir, period); else if (verbose) prmsg(0, "... no hostnames found\n"); if (total_agents) prAgentStats(stdir, period); else if (verbose && logfmt != LOGF_CLF) prmsg(0, "... no user agents found\n"); if (total_refer) prReferStats(stdir, period); else if (verbose && logfmt != LOGF_CLF) prmsg(0, "... no referrer URLs found\n"); /* Now sort the top N lists by accesses */ if (top_day) { qsort((void *)top_day, topn_day, sizeof(TOP_COUNTER), sort_top); while (topn_day > 0 && !top_day[topn_day-1].count) topn_day--; } if (top_hrs) { qsort((void *)top_hrs, topn_hrs, sizeof(TOP_COUNTER), sort_top); while (topn_hrs > 0 && !top_hrs[topn_hrs-1].count) topn_hrs--; } if (top_min) { qsort((void *)top_min, topn_min, sizeof(TOP_COUNTER), sort_top); while (topn_min > 0 && !top_min[topn_min-1].count) topn_min--; } if (top_sec) { qsort((void *)top_sec, topn_sec, sizeof(TOP_COUNTER), sort_top); while (topn_sec > 0 && !top_min[topn_sec-1].count) topn_sec--; } if (use_frames) /* create a frames interface */ prFrameHeader(stdir); if ((ofp=efopen("%s/stats%02d%02d.html", stdir, t.start.mon+1, EPOCH(t.start.year))) == NULL) exit(1); html_header(ofp, period, NULL); if ((ffp=efopen("%s/totals%02d%02d.html", stdir, t.start.mon+1, EPOCH(t.start.year))) == NULL) exit(1); html_header(ffp, period, NULL); dfpr(ofp, ffp, "

\n" "\n" "\n" "
Full Statistics for %s

\n
\n", period); dfpr(ofp, ffp, "

\n" "\n" FMT_SPACE("4") "\n" FMT_SPACE("4")); dfpr(ofp, ffp, "\n", SQICON_GREEN, SQICON_BLUE, SQICON_YELLOW, SQICON_RED); dfpr(ofp, ffp, "\n", SQICON_GREEN, SQICON_BLUE, SQ_WID(total.hits, total.files), SQICON_YELLOW, SQ_WID(total.hits, total.nomod), SQICON_RED, SQ_WID(total.hits, total.hits-(total.files+total.nomod))); dfpr(ofp, ffp, "\n", total.hits, total.files, total.nomod, total.hits-(total.files+total.nomod)); if (!nopageviews) { dfpr(ofp, ffp, FMT_SPACE("4") "\n", SQICON_MAGENTA, SQICON_GREY); dfpr(ofp, ffp, "\n", SQICON_MAGENTA, SQ_WID(total.hits, total.views), SQICON_GREY, SQ_WID(total.hits, total.hits-total.views)+1); dfpr(ofp, ffp, "\n", total.views, total.hits-total.views); } dfpr(ofp, ffp, FMT_SPACE("4") "\n", SQICON_GREEN, SQICON_ORANGE, SQICON_YELLOW); kbytes = KBYTES(total.bytes); dfpr(ofp, ffp, "\n", SQICON_GREEN, 102, SQICON_ORANGE, SQ_WID(kbytes+total_kbsaved, kbytes), SQICON_YELLOW, SQ_WID(kbytes+total_kbsaved, total_kbsaved)); dfpr(ofp, ffp, "\n", kbytes+total_kbsaved, kbytes, total_kbsaved); dfpr(ofp, ffp, FMT_SPACE("4") FMT_SUM("Total unique URLs") FMT_SUM("Total unique sites") FMT_SUM("Total user sessions per %s"), uniq_urls, uniq_sites, time_win ? time_win : "24 hours", total.sessions); if (total_agents) dfpr(ofp, ffp, FMT_SUM("Total unique agents"), total_agents); if (total_refer) dfpr(ofp, ffp, FMT_SUM("Total unique referrers"), total_refer); if (authenticated) dfpr(ofp, ffp, FMT_SUM("Total authenticated requests"), authenticated); if (empty != 0 || corrupt != 0) { dfpr(ofp, ffp, FMT_SPACE("4") FMT_SUM("Total number of logfile entries"), total.hits+empty+corrupt); if (corrupt != 0) dfpr(ofp, ffp, FMT_SUM("Corrupted logfile entries"), corrupt); if (empty != 0) dfpr(ofp, ffp, FMT_SUM("Empty Requests"), empty); dfpr(ofp, ffp, FMT_SPACE("4")); } dfpr(ofp, ffp, FMT_SPACE("4") "\n" FMT_SPACE("4") FMT_SUM("Max hits per day") FMT_SUM("Average hits per day"), max_day.hits, avg_day.hits); dfpr(ofp, ffp, FMT_SUM("Max hits per hour"), max_hrhits); dfpr(ofp, ffp, FMT_SUM("Average hits per hour"), avg_day.hits/24L); prhead = 1; /* flag to print header */ for (idx=0; idx < (int)TABSIZE(RespCode); idx++) { if (idx == IDX_OK || idx == IDX_NOT_MODIFIED || !RespCode[idx].count) continue; if (prhead) { prhead = 0; dfpr(ofp, ffp, FMT_SPACE("4") "\n" FMT_SPACE("4")); } dfpr(ofp, ffp, FMT_SUM("%s"), RespCode[idx].msg, RespCode[idx].count); } prhead = 1; /* flag to print header */ for (idx=0; idx < (int)TABSIZE(ReqMethod); idx++) { if (!ReqMethod[idx].count) continue; if (prhead) { prhead = 0; dfpr(ofp, ffp, FMT_SPACE("4") "\n" FMT_SPACE("4")); } dfpr(ofp, ffp, FMT_SUM("%s"), ReqMethod[idx].msg, ReqMethod[idx].count); } dfpr(ofp, ffp, FMT_SPACE("4") "
" "Monthly Summary
\n" "Total hits " "\"\"
\n" "Total files sent " "\"\"
\n" "Total Code 304's (Not Modified) " "\"\"
\n" "Other responses (see below) " "\"\"
" "\"\"
\n" "\"\"" "\"\"" "\"\"
" "%lu
\n" "%lu
\n" "%lu
\n" "%lu
\n" "Total pageviews " "\"\"
\n" "Remaining responses " "\"\"
" "\"\"" "\"\"" "%lu
\n" "%lu
" "Total KB requested \n" "\"\"
\n" "Total KB transferred " "\"\"
\n" "Total KB saved by cache " "\"\"
\n" "\"\"
\n" "\"\"" "\"\"
" "%lu
\n" "%lu
\n" "%lu
" "Maximum/Average hits
" "Other Response Codes
" "Request Methods other than GET/POST

\n
\n"); html_trailer(ffp); (void) fclose(ffp); if ((ffp = efopen("%s/nav%02d%02d.html", stdir, t.start.mon+1, EPOCH(t.start.year))) == NULL) exit(1); html_header(ffp, "", jsloadpg, w3wid, w3ht); prNavMon(ofp, ffp); (void) fprintf(ffp, "\n\n"); (void) fclose(ffp); html_trailer(ofp); (void) fclose(ofp); /* hits by day */ if ((ofp=efopen("%s/days%02d%02d.html", stdir, t.start.mon+1, EPOCH(t.start.year))) == NULL) exit(1); html_header(ofp, period, NULL); dfpr(ofp, NULL, "

\n" "\n" "\n" "
Hits by day

\n
\n"); if (!noimages) { dfpr(ofp, NULL, "

" "\"hits" "

\n
\n", t.start.mon+1, EPOCH(t.start.year)); (void) sprintf(tbuf, "%s/stats%02d%02d.gif", stdir, t.start.mon+1, EPOCH(t.start.year)); (void) mn_bars(492, 317, 28, daily, t.end.mday, 31, tbuf, "by day"); } /* the daily values */ prIdxTab(ofp, NULL, daily, (int)t.end.mday, max_day.hits, "Day"); html_trailer(ofp); (void) fclose(ofp); if (!noload && topn_day > 0 || topn_hrs > 0 || topn_min > 0 || topn_sec > 0) { if ((ofp=efopen("%s/avload%02d%02d.html", stdir, t.start.mon+1, EPOCH(t.start.year))) == NULL) exit(1); html_header(ofp, period, NULL); dfpr(ofp, NULL, "

\n" "\n" "\n
Average load
" "

\n
\n"); if (!noimages) { dfpr(ofp, NULL, "

\n" "\"load

\n
\n", t.start.mon+1, EPOCH(t.start.year)); (void) sprintf(tbuf, "%s/avday%02d%02d.gif", stdir, t.start.mon+1, EPOCH(t.start.year)); (void) wd_bars(492, 160, 28, weekly, daily, t.end.mday, tbuf); } if (topn_day > 0) prTopList(ofp, top_day, topn_day, 0); if (!noimages) { dfpr(ofp, NULL, "

\n" "\"load

\n
\n", t.start.mon+1, EPOCH(t.start.year)); (void) sprintf(tbuf, "%s/avload%02d%02d.gif", stdir, t.start.mon+1, EPOCH(t.start.year)); (void) hr_bars(492, 160, 28, avg_hour, avg_day.hits, tbuf); } if (topn_hrs > 0) prTopList(ofp, top_hrs, topn_hrs, 1); if (topn_min > 0) prTopList(ofp, top_min, topn_min, 2); if (topn_sec > 0) prTopList(ofp, top_sec, topn_sec, 3); } html_trailer(ofp); (void) fclose(ofp); /* the top files/sites lists */ if (lntab[FNAME_TOPFILES] || lntab[FNAME_TOPLFILES]) { if ((ofp=efopen("%s/topurl%02d%02d.html", stdir, t.start.mon+1, EPOCH(t.start.year))) == NULL) exit(1); html_header(ofp, period, NULL); } if (lntab[FNAME_TOPFILES]) { /* the top N files */ dfpr(ofp, NULL, "

\n" "\n" "\n" "
The Top %u items/URLs

\n", lntab[FNAME_TOPFILES]); if (!noimages) dfpr(ofp, NULL, "

\n" "" "\"files

\n
\n", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year), t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "

\n" "\n"); dfpr(ofp, NULL, FMT_SPACE("4") "\n", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, FMT_SPACE("4") "\n" "\n" "\n" "\n" "\n" FMT_SPACE("4")); for (idx=0; idx < topn_urls; idx++) { if (top_urls[idx] == NULL) break; PR_TOP(ofp, NULL, idx+1, top_urls[idx]->count, PERCENT(top_urls[idx]->count, total.hits), top_urls[idx]->nomod, PERCENT(top_urls[idx]->nomod, total.nomod), KBYTES(top_urls[idx]->bytes)); prFileURL(ofp, top_urls[idx]); } dfpr(ofp, NULL, FMT_SPACE("4") "
More details
No.Hits304'sKBytes sentURL

\n
\n"); } if (lntab[FNAME_TOPLFILES]) { /* the least N files */ dfpr(ofp, NULL, "

\n" "\n" "\n" "
The %u least frequently accessed items/URLs

\n", lntab[FNAME_TOPLFILES]); dfpr(ofp, NULL, "\n"); dfpr(ofp, NULL, FMT_SPACE("4") "\n", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, FMT_SPACE("4") "\n" "\n" "\n" "\n" "\n" FMT_SPACE("4")); for (idx=lstn_urls-1; idx >= 0; idx--) { if (lst_urls[idx] == NULL) continue; PR_TOP(ofp, NULL, idx+1, lst_urls[idx]->count, PERCENT(lst_urls[idx]->count, total.hits), lst_urls[idx]->nomod, PERCENT(lst_urls[idx]->nomod, total.nomod), KBYTES(lst_urls[idx]->bytes)); prFileURL(ofp, lst_urls[idx]); } dfpr(ofp, NULL, FMT_SPACE("4") "
More details
No.Hits304'sKBytes sentURL
\n
\n"); } if (lntab[FNAME_TOPFILES] || lntab[FNAME_TOPLFILES]) { dfpr(ofp, NULL, "

\n
\n"); html_trailer(ofp); (void) fclose(ofp); } if (lntab[FNAME_TOPSITES]) { /* the top N domains */ if ((ofp=efopen("%s/topdom%02d%02d.html", stdir, t.start.mon+1, EPOCH(t.start.year))) == NULL) exit(1); html_header(ofp, period, NULL); dfpr(ofp, NULL, "

\n" "\n" "\n" "
The Top %u client domains

\n
\n", lntab[FNAME_TOPSITES]); if (!noimages) dfpr(ofp, NULL, "

\n" "" "\"domain

\n
\n", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year), t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "

\n" "\n"); dfpr(ofp, NULL, FMT_SPACE("4") "\n", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, FMT_SPACE("4") "\n" "\n" "\n" "\n" "\n"); hsum.hits = total.hits-unknown[HIDDEN_SITES].count; hsum.nomod = total.nomod-unknown[HIDDEN_SITES].nomod; hsum.bytes = total.bytes-unknown[HIDDEN_SITES].bytes; dfpr(ofp, NULL, FMT_SPACE("4")); for (idx=0; idx < topn_sites; idx++) { if (top_sites[idx] == NULL) break; PR_TOP(ofp, NULL, idx+1, top_sites[idx]->count, PERCENT(top_sites[idx]->count, hsum.hits), top_sites[idx]->nomod, PERCENT(top_sites[idx]->nomod, hsum.nomod), KBYTES(top_sites[idx]->bytes)); dfpr(ofp, NULL, "\n", *top_sites[idx]->str == '.' ? top_sites[idx]->str+1 : top_sites[idx]->str); } dfpr(ofp, NULL, FMT_SPACE("4") "
More details
No.Hits304'sKBytes sentDomain
%s

\n
\n"); html_trailer(ofp); (void) fclose(ofp); } if (lntab[FNAME_TOPAGENTS]) { /* the top N user agents */ if ((ofp=efopen("%s/topuag%02d%02d.html", stdir, t.start.mon+1, EPOCH(t.start.year))) == NULL) exit(1); html_header(ofp, period, NULL); dfpr(ofp, NULL, "

\n" "\n" "\n" "
The Top %u browser types

\n
\n", lntab[FNAME_TOPAGENTS]); if (!noimages) dfpr(ofp, NULL, "

\n" "" "\"browser

\n
\n", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year), t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "

\n" "\n"); dfpr(ofp, NULL, FMT_SPACE("4") "\n", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, FMT_SPACE("4") "\n" "\n" "\n" "\n" "\n"); hsum.hits = total.hits-unknown[HIDDEN_AGENTS].count; hsum.nomod = total.nomod-unknown[HIDDEN_AGENTS].nomod; hsum.bytes = total.bytes-unknown[HIDDEN_AGENTS].bytes; dfpr(ofp, NULL, FMT_SPACE("4")); for (idx=0; idx < topn_agent; idx++) { if (top_agent[idx] == NULL) break; PR_TOP(ofp, NULL, idx+1, top_agent[idx]->count, PERCENT(top_agent[idx]->count, hsum.hits), top_agent[idx]->nomod, PERCENT(top_agent[idx]->nomod, hsum.nomod), KBYTES(top_agent[idx]->bytes)); dfpr(ofp, NULL, "\n", top_agent[idx]->str, top_agent[idx]->str[top_agent[idx]->len-1] == '.' ? "*" : ""); } dfpr(ofp, NULL, FMT_SPACE("4") "
More details
No.Hits304'sKBytes sentBrowser
%s%s

\n
\n"); html_trailer(ofp); (void) fclose(ofp); } if (lntab[FNAME_TOPREFERS]) { /* the top N referrers */ if ((ofp=efopen("%s/topref%02d%02d.html", stdir, t.start.mon+1, EPOCH(t.start.year))) == NULL) exit(1); html_header(ofp, period, NULL); dfpr(ofp, NULL, "

\n" "\n" "\n" "
The Top %u referrer URLs

\n
\n", lntab[FNAME_TOPREFERS]); if (!noimages) dfpr(ofp, NULL, "

\n" "" "\"referrer

\n
\n", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year), t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "

\n" "\n" FMT_SPACE("4")); dfpr(ofp, NULL, FMT_SPACE("4") "\n", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, FMT_SPACE("4") "\n" "\n" "\n" "\n" "\n"); hsum.hits = total.hits-(unknown[HIDDEN_REFERS].count+unknown[SELF_REF].count); hsum.nomod = total.nomod-(unknown[HIDDEN_REFERS].nomod+unknown[SELF_REF].nomod); hsum.bytes = total.bytes-(unknown[HIDDEN_REFERS].bytes+unknown[SELF_REF].bytes); dfpr(ofp, NULL, FMT_SPACE("4")); for (idx=0; idx < topn_refer; idx++) { if (top_refer[idx] == NULL) break; PR_TOP(ofp, NULL, idx+1, top_refer[idx]->count, PERCENT(top_refer[idx]->count, hsum.hits), top_refer[idx]->nomod, PERCENT(top_refer[idx]->nomod, hsum.nomod), KBYTES(top_refer[idx]->bytes)); prRefURL(ofp, top_refer[idx]); } dfpr(ofp, NULL, FMT_SPACE("4") "
More details
No.Hits304'sKBytes sentReferrer Host

\n
\n"); html_trailer(ofp); (void) fclose(ofp); } if (!nocntrylist) { /* generate country list */ if ((clist = (COUNTRY **)calloc(num_cntry, sizeof(COUNTRY *))) == NULL) { prmsg(1, enomem, num_cntry*sizeof(NLIST *)); num_cntry = 0; } else { /* create a linear list */ char *label = "Hits by Country"; for (idx=0; ccp = nextCountry(0); idx++) clist[idx] = ccp; /* ** Save countries, sort the list by hits/cntry */ if ((num_cntry = (size_t)idx) != 0) { if (num_cntry > 1) qsort((void *)clist, num_cntry, sizeof(COUNTRY *), sort_by_cntry); if ((ofp=efopen("%s/country%02d%02d.html", stdir, t.start.mon+1, EPOCH(t.start.year))) == NULL) exit(1); html_header(ofp, period, NULL); dfpr(ofp, NULL, "

\n" "\n" "\n" "
%s

\n
\n", label); if (!noimages) dfpr(ofp, NULL, "

\n" "\"country

\n
\n", t.start.mon+1, EPOCH(t.start.year)); dfpr(ofp, NULL, "

\n" "\n"); dfpr(ofp, NULL, FMT_SPACE("4") "\n" "\n" "\n" "\n" "\n" FMT_SPACE("4")); for (idx=0; idx < (int)num_cntry; idx++) { PR_TOP(ofp, NULL, idx+1, clist[idx]->count, PERCENT(clist[idx]->count, total.hits), clist[idx]->nomod, PERCENT(clist[idx]->nomod, total.nomod), KBYTES(clist[idx]->bytes)); dfpr(ofp, NULL, "\n", clist[idx]->name); } dfpr(ofp, NULL, FMT_SPACE("4") "
No.Hits304'sKBytes sentCountry
" "%s

\n
\n"); html_trailer(ofp); (void) fclose(ofp); if (!noimages) { (void) sprintf(tbuf, "%s/country%02d%02d.gif", stdir, t.start.mon+1, EPOCH(t.start.year)); (void) c_chart(492, 320, 28, tbuf, clist, NULL, num_cntry, total.hits, label); } } free(clist); } } return; } /* ** Print an item form the top ten lists with it's URL if known. */ #define ISHIDDEN(listp, which) \ ((listp)->ishidden >= 0 && (listp)->ishidden < hidden[which].t_count) static void prFileURL(FILE * const ofp, NLIST * const np) { char *pfx, scratch[LBUFSIZE]; size_t len; (void) fprintf(ofp, ""); if (nohotlinks) (void) fprintf(ofp, "%s", np->str); else if (np->ishidden >= 0 && np->ishidden < (int)hidden[HIDDEN_ITEMS].t_count && (pfx = hidden[HIDDEN_ITEMS].tab[np->ishidden].pfx) != NULL) { if ((len = strlen(pfx)) >= sizeof(scratch)-1) len = sizeof(scratch)-1; (void) strncpy(scratch, pfx, len); scratch[len] = '\0'; /* terminate in case of overflow */ if (len > 2 && scratch[len-1] == '*' && scratch[len-2] == '/') scratch[--len] = '\0'; if (scratch[0] != '*' && scratch[len-1] != '*') (void) fprintf(ofp, "
%s", srv_url ? srv_url : "", scratch, np->str); else (void) fprintf(ofp, "%s", np->str); } else (void) fprintf(ofp, "%s", srv_url ? srv_url : "", np->str, np->str); (void) fprintf(ofp, "\n"); return; } /* ** Massage referrer URL, print it. */ static void prCGI(FILE * const ofp, int link, char *str, char *sref) { char *tm; u_char rc; int maxchr, maxcgi, incgi; if (link) { if (strneq(str, "http://", 7) || strneq(str, "https://", 8)) { (void) fprintf(ofp, "", ofp); tm = str+7; /* skip protocol and hostname */ if (*tm == '/') tm++; while (*tm && *tm != '/') tm++; if (*tm == '/') str = tm; } else link = 0; } for (maxchr=maxcgi=incgi=0; *str != '\0'; str++) { if (incgi) { /* special processing for CGI arguments */ if (*str == '&') { if (sref) break; if (maxchr > 120) { if (maxcgi < 34) (void) fputs("[...]", ofp); break; } (void) fputc(' ', ofp); maxcgi = 0; continue; } if (!sref && maxcgi > 32) { if (maxcgi == 33) { (void) fputs("[...]", ofp); maxcgi++; } continue; } maxcgi++; rc = (u_char)((*str == '+') ? ' ' : *str); } else if (maxchr > 120) { /* only if !incgi */ (void) fputs("[...]", ofp); break; } if (*str == '%' && isxdigit(str[1]) && isxdigit(str[2])) { str++; /* translate hex sequences */ if (is_upper(*str)) rc = ((*str-'A')+10)<<4; else if (is_lower(*str)) rc = ((*str-'a')+10)<<4; else rc = (*str-'0')<<4; str++; if (is_upper(*str)) rc += (*str-'A')+10; else if (is_lower(*str)) rc += (*str-'a')+10; else rc += *str-'0'; } if (!incgi) { if (*str == '?') { if (sref) { for (tm=str; (tm=strstr(tm, sref)) != NULL; tm += strlen(sref)) if (*(tm-1) == '&' || *(tm-1) == '?') break; if (tm) str = tm-1; else sref = tm; } (void) fputs(" ", ofp); incgi++; maxchr = 0; continue; } rc = *str; } if (rc == '<') /* escape special characters */ (void) fputs("<", ofp); else if (rc == '>') (void) fputs(">", ofp); else if (rc == '&') (void) fputs("&", ofp); else if (rc == '"') (void) fputs(""", ofp); else if (!isprint(rc)) continue; else /* show only printable characters */ (void) fputc(rc, ofp); maxchr++; } if (incgi || !link) (void) fputc('\n', ofp); else (void) fputs("\n", ofp); return; } /* ** Print a referrer with it's URL if known. */ static void prRefURL(FILE * const ofp, NLIST * const np) { char *pfx; int len; (void) fprintf(ofp, ""); if (!nohotlinks && ISHIDDEN(np, HIDDEN_REFERS) && (pfx = hidden[HIDDEN_REFERS].tab[np->ishidden].pfx)) { len = hidden[HIDDEN_REFERS].tab[np->ishidden].len; if (pfx == np->str) { (void) fprintf(ofp, "%s/", pfx, pfx); } else if (pfx[len-1] == '/') (void) fprintf(ofp, "%s", pfx, np->str); else (void) fprintf(ofp, "%s", priv_dir ? priv_dir : ".", t.start.mon+1, EPOCH(t.start.year), hidden[HIDDEN_REFERS].tab[np->ishidden].col->ishidden, np->str); } else (void) fprintf(ofp, "%s", np->str); (void) fprintf(ofp, "\n"); return; } /* ** Print a Code 404 (NotFound) request. May contain special ** characters which need to be encoded for HTML output. */ static void prEscHTML(FILE * const ofp, char const *str) { while (*str != '\0') { if (*str == '&') (void) fputs(""", ofp); else if (*str == '<') (void) fputs("<", ofp); else if (*str == '>') (void) fputs(">", ofp); else if (*str < 0x20) (void) fprintf(ofp, "^%c", (*str+0x40)); else (void) fputc(*str, ofp); str++; } return; } /* ** Print a domainname as reverse domain. */ static void revDomain(char *str, size_t const len, char *bp, size_t max) { register char *tm, *cp = str+len; if (*str == '*') str++; if (*str == '.') str++; while (cp > str && max > 2) { cp--; if (*cp != '.' && cp > str) continue; for (tm = (*cp == '.') ? cp+1 : cp; *tm && *tm !='.' && max > 2; tm++, max--) *bp++ = *tm; if (cp != str) { *bp++ = '.'; max--; } } *bp = '\0'; return; } /* ** Print an item from the table of all items. */ static void prItem(FILE * const ofp, NLIST ** const lp, size_t const cnt, size_t const which, COUNTER * const hsum) { char *fmt, *label; int num, idx, ndx = -1; int header = 1, isnoise = 0; NLIST noise = { NULL, 0, 0L, 0L, 0L, 0.0 }; ITEM_LIST *ip; switch (which) { default: assert(which != which); return; /*NOTREACHED*/ case HIDDEN_ITEMS: label = " Size | URL"; fmt = urlfmt; break; case HIDDEN_SITES: label = "| Hostname"; fmt = sitefmt; break; case HIDDEN_AGENTS: label = "| Browser"; fmt = sitefmt; break; case HIDDEN_REFERS: label = "| Referrer URL"; fmt = sitefmt; break; } ip = hidden[which].tab; for (idx=num=0; idx < (int)cnt; idx++) { if (ndx != (int)lp[idx]->ishidden) { if (ndx >= 0) { header = ip[lp[idx]->ishidden].col != ip[ndx].col; if (header && !isnoise) { (void) fprintf(ofp, "%s\n", hrule1); if (num > 1) { (void) fprintf(ofp, "%8lu %6.2f%% %7lu %6.2f%% %14.0f %6.2f%%", ip[ndx].col->count, PERCENT(ip[ndx].col->count, hsum->hits), ip[ndx].col->nomod, PERCENT(ip[ndx].col->nomod, hsum->nomod), ip[ndx].col->bytes, PERCENT(ip[ndx].col->bytes, hsum->bytes)); (void) fprintf(ofp, "\n%s\n", hrule1); } num = 0; } } ndx = (int)lp[idx]->ishidden; if (header && !isnoise) { if (ip[ndx].col->count < (u_long)noiselevel) { (void) fputs("\nNoise\n", ofp); isnoise++; } else if (which == HIDDEN_ITEMS) { (void) fprintf(ofp, "\n%s (%s)\n", ndx, ip[ndx].col->str, ip[ndx].pfx); } else if (which == HIDDEN_SITES) { (void) fprintf(ofp, "\n%s\n", ip[ndx].col->ishidden, *ip[ndx].col->str == '.' ? ip[ndx].col->str+1 : ip[ndx].col->str); } else if (which == HIDDEN_AGENTS) { (void) fprintf(ofp, "\n%s%s\n", ip[ndx].col->ishidden, ip[ndx].col->str, ip[ndx].pfx == ip[ndx].col->str ? "*" : ""); } else if (which == HIDDEN_REFERS) { if (ip[ndx].pfx == ip[ndx].col->str) (void) fprintf(ofp, "\n%s/\n", ip[ndx].col->ishidden, ip[ndx].pfx, ip[ndx].pfx); else if (ip[ndx].pfx[ip[ndx].len-1] == '/') (void) fprintf(ofp, "\n%s\n", ip[ndx].col->ishidden, ip[ndx].pfx, ip[ndx].col->str); else (void) fprintf(ofp, "\n%s\n", ip[ndx].col->ishidden, ip[ndx].col->str); } (void) fprintf(ofp, " Total Total 304's\n" " Requests (NoMod Req) Bytes sent %s\n%s\n", label, hrule1); } } num++; (void) fprintf(ofp, fmt, lp[idx]->count, PERCENT(lp[idx]->count, hsum->hits), lp[idx]->nomod, PERCENT(lp[idx]->nomod, hsum->nomod), lp[idx]->bytes, PERCENT(lp[idx]->bytes, hsum->bytes), lp[idx]->size); if (which == HIDDEN_ITEMS && !nohotlinks && *lp[idx]->str == '/') (void) fprintf(ofp, "%s\n", srv_url ? srv_url : "", lp[idx]->str, lp[idx]->str); else if (which == HIDDEN_REFERS) prCGI(ofp, !nohotlinks, lp[idx]->str, ip[ndx].sref); else (void) fprintf(ofp, "%s\n", lp[idx]->str); if (isnoise) { noise.count += lp[idx]->count; noise.nomod += lp[idx]->nomod; noise.bytes += lp[idx]->bytes; noise.size++; } } (void) fprintf(ofp, "%s", hrule1); if (num > 1) { if (isnoise) (void) fprintf(ofp, "\n%8lu 100.00%% %7lu 100.00%% %14.0f 100.00%%\n%s", noise.count, noise.nomod, noise.bytes, hrule2); else (void) fprintf(ofp, "\n%8lu %6.2f%% %7lu %6.2f%% %14.0f %6.2f%%\n%s", ip[ndx].col->count, PERCENT(ip[ndx].col->count, hsum->hits), ip[ndx].col->nomod, PERCENT(ip[ndx].col->nomod, hsum->nomod), ip[ndx].col->bytes, PERCENT(ip[ndx].col->bytes, hsum->bytes), hrule2); } (void) fprintf(ofp, "\n\n"); return; } /* ** Print an overview of all hidden items. */ static void prHidden(FILE * const ofp, NLIST ** const lp, size_t const cnt, size_t const which, COUNTER * const hsum, int const rev) { char dname[MEDIUMSIZE]; char *str, *fmt = sitefmt; char *label, *header; NLIST noise = { NULL, 0, 0L, 0L, 0L, 0.0 }; ITEM_LIST *ip; size_t idx; switch (which) { default: assert(which != which); return; /*NOTREACHED*/ case HIDDEN_ITEMS: label = " Size | URL"; header = "Items/URLs"; fmt = urlfmt; break; case HIDDEN_SITES: label = rev ? "| Reverse Domain" : "| Domain"; header = rev ? "Reverse Domain" : "Client Domain"; break; case HIDDEN_AGENTS: label = "| Browser Type"; header = "Browser Type"; break; case HIDDEN_REFERS: label = "| Referrer URL"; header = "Referrer URL"; break; } ip = hidden[which].tab; (void) fprintf(ofp, "" "Total Transfers by %s (Overview)\n" "
\n", head_size, header, font_size);

	(void) fprintf((ofp),
"        Total        Total 304's\n"
"        Requests     (NoMod Req)             Bytes sent  %s\n%s\n", label, hrule1);

	for (idx=0; idx < cnt; idx++) {
		NLIST *np = ISHIDDEN(lp[idx], which) ? ip[lp[idx]->ishidden].col : lp[idx];

		if (np->count < (u_long)noiselevel) {
			noise.count += np->count;
			noise.nomod += np->nomod;
			noise.bytes += np->bytes;
			noise.size++;
			continue;
		}
		(void) fprintf(ofp, fmt,
			lp[idx]->count, PERCENT(lp[idx]->count, hsum->hits),
			lp[idx]->nomod, PERCENT(lp[idx]->nomod, hsum->nomod),
			lp[idx]->bytes, PERCENT(lp[idx]->bytes, hsum->bytes), lp[idx]->size);

		if (which == HIDDEN_ITEMS) {
			if (!nofilelist && ISHIDDEN(lp[idx], which)) {
				(void) fprintf(ofp, "%s\n",
					t.start.mon+1, EPOCH(t.start.year),
					lp[idx]->ishidden, lp[idx]->str);
			} else if (!nohotlinks && *lp[idx]->str == '/')
				(void) fprintf(ofp, "%s\n",
					srv_url ? srv_url : "", lp[idx]->str, lp[idx]->str);
			else
				(void) fprintf(ofp, "%s\n", lp[idx]->str);
		} else if (which == HIDDEN_SITES) {
			if (rev) {
				revDomain(lp[idx]->str, lp[idx]->len, dname, sizeof dname);
				str = dname;
			} else
				str = lp[idx]->str;
			if (*str == '.')
				str++;
			if (!nohostlist && ISHIDDEN(lp[idx], which)) {
				(void) fprintf(ofp, "%s\n",
					t.start.mon+1, EPOCH(t.start.year),
					ip[lp[idx]->ishidden].col->ishidden, str);
			} else
				(void) fprintf(ofp, "%s\n", str);
		} else if (which == HIDDEN_AGENTS) {
			str = lp[idx]->str+lp[idx]->len;
			str = (*--str == '.' || *str == '-') ? "*" : "";
			if (!noagentlist && ISHIDDEN(lp[idx], which)) {
				(void) fprintf(ofp, "%s%s\n",
					t.start.mon+1, EPOCH(t.start.year),
					ip[lp[idx]->ishidden].col->ishidden, lp[idx]->str, str);
			} else
				(void) fprintf(ofp, "%s\n", lp[idx]->str);
		} else if (which == HIDDEN_REFERS) {
			if (!noreferlist && ISHIDDEN(lp[idx], which)) {
				(void) fprintf(ofp, "%s%s\n",
					t.start.mon+1, EPOCH(t.start.year),
					ip[lp[idx]->ishidden].col->ishidden, lp[idx]->str,
					ip[lp[idx]->ishidden].pfx == ip[lp[idx]->ishidden].col->str ? "/" : "");
			} else
				(void) fprintf(ofp, "%s\n", lp[idx]->str);
		} else
			assert(which != which);
	}
	if (noise.count != 0) {
		char *lfn;

		switch (which) {
		  case HIDDEN_ITEMS:	lfn = "lfiles";  break;
		  case HIDDEN_SITES:	lfn = "lsites";  break;
		  case HIDDEN_AGENTS:	lfn = "lagents"; break;
		  case HIDDEN_REFERS:	lfn = "lrefers"; break;
		}
		(void) fprintf(ofp, fmt,
			noise.count, PERCENT(noise.count, hsum->hits),
			noise.nomod, PERCENT(noise.nomod, hsum->nomod),
			noise.bytes, PERCENT(noise.bytes, hsum->bytes), 0L);
		(void) fprintf(ofp, ""
			"Noise (%lu %ss below %d hits)\n",
			lfn, t.start.mon+1, EPOCH(t.start.year),
			noise.size, hidden[which].what, noiselevel);
	}
	if (which == HIDDEN_ITEMS) {	/* print other codes */
		for (idx=0; idx < (int)TABSIZE(RespCode); idx++) {
			if (idx == IDX_OK || idx == IDX_NOT_MODIFIED || RespCode[idx].bytes == 0.0)
				continue;

			(void) fprintf(ofp, urlfmt,
				RespCode[idx].count, PERCENT(RespCode[idx].count, hsum->hits),
				0L, 0.0,
				RespCode[idx].bytes, PERCENT(RespCode[idx].bytes, hsum->bytes), 0L);

			if (!noerrlist && idx == IDX_NOT_FOUND)
				(void) fprintf(ofp, "%s\n",
					t.start.mon+1, EPOCH(t.start.year), RespCode[idx].msg);
			else
				(void) fprintf(ofp, "%s\n",
					RespCode[idx].msg);
		}
	}
	(void) fprintf(ofp,
		"%s\n%8lu 100.00%% %7lu 100.00%% %14.0f 100.00%%\n%s",
		hrule1, hsum->hits, hsum->nomod, hsum->bytes, hrule2);

	idx = 0;
	if (unknown[which].count || which == HIDDEN_REFERS && unknown[SELF_REF].count)
		(void) fprintf((ofp), "\n\n"
"        Total        Total 304's\n"
"        Requests     (NoMod Req)             Bytes sent  %s\n%s\n", label, hrule1);

	if (unknown[which].count) {
		(void) fprintf(ofp, sitefmt,
			unknown[which].count, PERCENT(unknown[which].count, total.hits),
			unknown[which].nomod, PERCENT(unknown[which].nomod, total.nomod),
			unknown[which].bytes, PERCENT(unknown[which].bytes, total.bytes));
		(void) fprintf(ofp, "%s (no %s info given)\n",
			unknown[which].str, hidden[which].what);
		idx++;
	}
	if (which == HIDDEN_REFERS && unknown[SELF_REF].count) {
		(void) fprintf(ofp, sitefmt,
			unknown[SELF_REF].count, PERCENT(unknown[SELF_REF].count, total.hits),
			unknown[SELF_REF].nomod, PERCENT(unknown[SELF_REF].nomod, total.nomod),
			unknown[SELF_REF].bytes, PERCENT(unknown[SELF_REF].bytes, total.bytes));
		(void) fprintf(ofp, "%s\n", unknown[SELF_REF].str);
		idx++;
	}
	if (idx) {
		(void) fprintf(ofp, sitefmt,
			hsum->hits, PERCENT(hsum->hits, total.hits),
			hsum->nomod, PERCENT(hsum->nomod, total.nomod),
			hsum->bytes, PERCENT(hsum->bytes, total.bytes));
		(void) fprintf(ofp, "Remaining entries shown above\n");
		(void) fprintf(ofp, "%s\n%8lu 100.00%% %7lu 100.00%% %14.0f 100.00%%\n%s",
				hrule1, total.hits, total.nomod, total.bytes, hrule2);
	}
	(void) fprintf(ofp, "\n
\n"); return; } /* ** Print detailed statistics about all unique sites. */ static void prSiteStats(char * const stdir, char * const period) { char tbuf[MAX_FNAMELEN]; /* filename templates */ char label[MEDIUMSIZE]; /* label in graphic */ size_t cnt = (size_t)uniq_sites;/* list size is total # of unique sites */ ITEM_LIST *ip = hidden[HIDDEN_SITES].tab; COUNTER hsum; /* correction for totals */ NLIST *np, **list; /* temp ptr */ int idx, ndx; /* indexes must be signed */ FILE *ofp; if (verbose) prmsg(0, "... processing hostnames\n"); if ((list = (NLIST **)calloc(cnt, sizeof(NLIST *))) == NULL) { prmsg(1, "Failed to allocate %u more bytes.\n" "The list of sites %s\n", cnt*sizeof(NLIST *), enolist); nositelist = 1; return; } /* sort the hidden domain table */ if ((hidden[HIDDEN_SITES].t_count-hidden[HIDDEN_SITES].t_start) > 1) qsort((void *)(ip+hidden[HIDDEN_SITES].t_start), hidden[HIDDEN_SITES].t_count-hidden[HIDDEN_SITES].t_start, sizeof(ITEM_LIST), sort_by_casestr); /* create a linear list of all sites */ for (idx=0, cnt=0; idx < HASHSIZE; idx++) { if ((np = sitetab[idx]) == NULL) continue; do { if (isHiddenSite(np)) continue; if (IS_TYPE(np->ftype, TYPE_NODNS)) { unknown[HIDDEN_SITES].count += np->count; unknown[HIDDEN_SITES].nomod += np->nomod; unknown[HIDDEN_SITES].bytes += np->bytes; } else list[cnt++] = np; } while ((np=np->next) != NULL && cnt < (size_t)uniq_sites); } /* add collected hidden sites to the list */ for (idx=0; idx < (int)TABSIZE(hlist[HIDDEN_SITES]); idx++) { if ((np=hlist[HIDDEN_SITES][idx]) == NULL) continue; do { if (np->count && cnt < (size_t)uniq_sites) list[cnt++] = np; } while ((np=np->next) != NULL); } assert(cnt <= (size_t)uniq_sites); if (cnt > 1) /* now sort the list by accesses */ qsort((void *)list, cnt, sizeof(NLIST *), sort_by_hits); hsum.hits = total.hits-unknown[HIDDEN_SITES].count; hsum.nomod = total.nomod-unknown[HIDDEN_SITES].nomod; hsum.bytes = total.bytes-unknown[HIDDEN_SITES].bytes; if (topn_sites) { /* create top site list, skip hidden sites */ for (idx=ndx=0; idx < topn_sites && ndx < (int)cnt; ndx++) { if (*list[ndx]->str == '[' || list[ndx]->count < (u_long)noiselevel) continue; top_sites[idx++] = list[ndx]; } lntab[FNAME_TOPSITES] = (idx > 1) ? idx : 0; if (!noimages && lntab[FNAME_TOPSITES]) { /* create pie chart image */ (void) sprintf(label, "The Top %u Client Domains", lntab[FNAME_TOPSITES]); (void) sprintf(tbuf, "%s/domains%02d%02d.gif", stdir, t.start.mon+1, EPOCH(t.start.year)); (void) c_chart(492, 320, 28, tbuf, NULL, list, cnt, hsum.hits, label); } } if (!nocntrylist) { /* generate country list */ num_cntry = (size_t)addCountry(&hidden[HIDDEN_SITES], &unknown[HIDDEN_SITES], list, cnt); assert(num_cntry != 0); } if (nositelist) { /* done already */ free(list); return; } /* total transfers by client domain */ if ((ofp=efopen("%s/%s/sites%02d%02d.html", stdir, !priv_dir ? "." : priv_dir, t.start.mon+1, EPOCH(t.start.year))) == NULL) { prmsg(1, "The list of domains %s\n", enolist); nositelist = 1; free(list); return; } lntab[FNAME_SITES] = cnt; html_header(ofp, period, NULL); prHidden(ofp, list, cnt, HIDDEN_SITES, &hsum, 0); html_trailer(ofp); (void) fclose(ofp); /* total transfers by reverse domain */ if (!nordomlist && (ofp=efopen("%s/%s/rsites%02d%02d.html", stdir, !priv_dir ? "." : priv_dir, t.start.mon+1, EPOCH(t.start.year))) == NULL) { prmsg(1, "The list of reverse domains %s\n", enolist); nordomlist = 1; } if (!nordomlist) { if ((lntab[FNAME_RSITES] = cnt) > 1) qsort((void *)list, cnt, sizeof(NLIST *), sort_by_revdom); html_header(ofp, period, NULL); prHidden(ofp, list, cnt, HIDDEN_SITES, &hsum, 1); html_trailer(ofp); (void) fclose(ofp); } /* total transfers by hostname */ if (!nohostlist) { /* create a linear list of all hidden items */ for (idx=0, cnt=0; idx < HASHSIZE && cnt < (size_t)uniq_sites; idx++) { if ((np = sitetab[idx]) == NULL) continue; do { if (ISHIDDEN(np, HIDDEN_SITES) && ip[np->ishidden].pfx != NULL && ip[np->ishidden].col->count) list[cnt++] = np; } while ((np=np->next) != NULL && cnt < (size_t)uniq_sites); } if (cnt != 0) { if ((ofp=efopen("%s/%s/lsites%02d%02d.html", stdir, !priv_dir ? "." : priv_dir, t.start.mon+1, EPOCH(t.start.year))) == NULL) { prmsg(1, "The detailed list of hostnames %s\n", enolist); nohostlist = 1; free(list); return; } lntab[FNAME_LSITES] = cnt; if (cnt > 1) qsort((void *)list, cnt, sizeof(NLIST *), sort_by_domain); html_header(ofp, period, NULL); (void) fprintf(ofp, "" "Total Transfers by Client Domain\n" "
\n", head_size, font_size);
			prItem(ofp, list, cnt, HIDDEN_SITES, &hsum);
			html_trailer(ofp);
			(void) fclose(ofp);	/* cleanup */
		}
	}
	free(list);
	return;
}

/*
** Print detailed statistics for all requested URLs.
*/
static void prURLStats(char * const stdir, char * const period) {
	char tbuf[MAX_FNAMELEN];	/* filename templates */
	char label[MEDIUMSIZE];		/* label in graphic */
	size_t cnt = (size_t)uniq_urls;	/* list size is total # of unique URLs */
	ITEM_LIST *ip = hidden[HIDDEN_ITEMS].tab;
	NLIST *np, **list;		/* temp ptr */
	int idx, ndx;			/* indexes must be signed */
	FILE *ofp;

	if (verbose)
		prmsg(0, "... processing URLs\n");

	if ((list = (NLIST **)calloc(cnt, sizeof(NLIST *))) == NULL) {
		prmsg(1, "Failed to allocate %d more bytes.\n"
			 "The list of files %s\n", cnt*sizeof(NLIST *), enolist);
		noitemlist = 1;
		return;
	}

	/* create a linear list of hidden items */
	for (idx=0, cnt=0; idx < HASHSIZE && cnt < (size_t)uniq_urls; idx++) {
		if ((np = urltab[idx]) == NULL)
			continue;

		do {	/* compute total bytes saved, check for hidden item */
			total_kbsaved += ((np->nomod*np->size)+1023L)/1024L;
			if (!isHiddenItem(np))
				list[cnt++] = np;
		} while ((np=np->next) != NULL && cnt < (size_t)uniq_urls);
	}

	/* add collected hidden items to the list */
	for (idx=0; idx < (int)TABSIZE(hlist[HIDDEN_ITEMS]); idx++) {
		if ((np=hlist[HIDDEN_ITEMS][idx]) == NULL)
			continue;
		do {
			if (np->count && cnt < (size_t)uniq_urls)
				list[cnt++] = np;
		} while ((np=np->next) != NULL);
	}
	assert(cnt <= (size_t)uniq_urls);

	if (cnt > 1)			/* now sort the list by accesses */
		qsort((void *)list, cnt, sizeof(NLIST *), sort_by_hits);

	if (topn_urls) {	/* create top URL list, skip hidden items */
		for (idx=ndx=0; idx < topn_urls && ndx < (int)cnt; ndx++) {
			if (*list[ndx]->str == '[' || (list[ndx]->count < (u_long)noiselevel))
				continue;
			top_urls[idx++] = list[ndx];
		}
		lntab[FNAME_TOPFILES] = (idx > 1) ? idx : 0;
	}

	if (lstn_urls &&	/* conditionally create list of most boring URLs */
	    (lstn_urls+topn_urls < (int)cnt)) {
		for (idx=0, ndx=(int)cnt-1; idx < lstn_urls && ndx >= 0; ndx--) {
			if (*list[ndx]->str == '[')
				continue;
			lst_urls[idx++] = list[ndx];
		}
		lntab[FNAME_TOPLFILES] = (idx > 1) ? idx : 0;
	}

	if (!noimages && topn_urls && lntab[FNAME_TOPFILES]) {	/* create pie chart image */
		(void) sprintf(label, "The Top %u Items/URLs", lntab[FNAME_TOPFILES]);
		(void) sprintf(tbuf, "%s/files%02d%02d.gif",
				stdir, t.start.mon+1, EPOCH(t.start.year));
		(void) c_chart(492, 320, 28, tbuf, NULL, list, cnt, total.hits, label);
	}

	if (noitemlist) {	/* we already have the list of the top URLs */
		free(list);
		return;
	}

	/* print the list of items/files */
	if ((ofp=efopen("%s/%s/files%02d%02d.html",
		stdir, !priv_dir ? "." : priv_dir, t.start.mon+1, EPOCH(t.start.year))) == NULL) {
		prmsg(1, "The overview of filenames (URLs) %s\n", enolist);
		noitemlist = 1;
		free(list);
		return;
	}
	lntab[FNAME_FILES] = cnt;

	html_header(ofp, period, NULL);
	prHidden(ofp, list, cnt, HIDDEN_ITEMS, &total, 0);
	html_trailer(ofp);
	(void) fclose(ofp);

	if (!nofilelist) {
		/* create a linear list of all hidden items */
		for (idx=0, cnt=0; idx < HASHSIZE && cnt < (size_t)uniq_urls; idx++) {
			if ((np = urltab[idx]) == NULL)
				continue;

			do {
				if (ISHIDDEN(np, HIDDEN_ITEMS) && ip[np->ishidden].pfx != NULL)
					list[cnt++] = np;
			} while ((np=np->next) != NULL && cnt < (size_t)uniq_urls);
		}
		if (cnt != 0) {
			if ((ofp=efopen("%s/%s/lfiles%02d%02d.html",
					stdir, !priv_dir ? "." : priv_dir,
					t.start.mon+1, EPOCH(t.start.year))) == NULL) {
				prmsg(1, "The list of filenames (URLs) %s\n", enolist);
				nofilelist = 1;
			} else {
				lntab[FNAME_LFILES] = cnt;

				if (cnt > 1)
					qsort((void *)list, cnt, sizeof(NLIST *), sort_by_item);

				html_header(ofp, period, NULL);
				(void) fprintf(ofp, ""
					"Total Transfers by Items/URLs\n"
					"
\n", head_size, font_size);
				prItem(ofp, list, cnt, HIDDEN_ITEMS, &total);
				html_trailer(ofp);
				(void) fclose(ofp);
			}
		}
	}
	if (!noerrlist && RespCode[IDX_NOT_FOUND].count) {
		if ((ofp=efopen("%s/%s/rfiles%02d%02d.html",
			stdir, !priv_dir ? "." : priv_dir,
			t.start.mon+1, EPOCH(t.start.year))) == NULL) {
			prmsg(1, "The list of Code 404 NotFound responses %s\n", enolist);
			noerrlist = 1;
			free(list);
			return;
		}
		/* create a list of all Code 404 requests if it fits into the memory */
		for (idx=0, cnt=0; idx < HASHSIZE && cnt < (size_t)uniq_urls; idx++) {
			if ((np = errtab[idx]) == NULL)
				continue;

			do {
				list[cnt++] = np;
			} while ((np=np->next) != NULL && cnt < (size_t)uniq_urls);
		}
		if ((lntab[FNAME_RFILES] = cnt) != 0) {
			if (cnt > 1)
				qsort((void *)list, cnt, sizeof(NLIST *), sort_by_hits);

			html_header(ofp, period, NULL);
			(void) fprintf(ofp, "Code 404 Not Found Requests\n"
				"
\n", head_size, font_size);
			(void) fprintf(ofp,
"        Total\n"
"        Requests             Bytes sent  | URL\n%s\n", hrule1);
			for (idx=0; idx < (int)cnt; idx++) {
				(void) fprintf(ofp, "%8lu %6.2f%% %14.0f %6.2f%%  | ",
					list[idx]->count, PERCENT(list[idx]->count, total.hits),
					list[idx]->bytes, PERCENT(list[idx]->bytes, total.bytes));
				prEscHTML(ofp, list[idx]->str);
				(void) fputc('\n', ofp);
			}
			(void) fprintf(ofp, "%s\n%8lu %6.2f%% %14.0f %6.2f%%\n%s",
hrule1, RespCode[IDX_NOT_FOUND].count, PERCENT(RespCode[IDX_NOT_FOUND].count, total.hits),
RespCode[IDX_NOT_FOUND].bytes, PERCENT(RespCode[IDX_NOT_FOUND].bytes, total.bytes), hrule2);
			(void) fprintf(ofp, "\n
\n"); html_trailer(ofp); } (void) fclose(ofp); } free(list); return; } /* ** Print detailed statistics about all unique browsers. */ static void prAgentStats(char * const stdir, char * const period) { char tbuf[MAX_FNAMELEN]; /* filename templates */ char label[MEDIUMSIZE]; /* label in graphic */ size_t cnt = (size_t)total_agents; /* list size is total # of unique agents */ ITEM_LIST *ip = hidden[HIDDEN_AGENTS].tab; COUNTER hsum; /* correction for total hits */ NLIST *np, **list; /* temp ptr */ int idx, ndx; /* indexes must be signed */ FILE *ofp; if (verbose) prmsg(0, "... processing user agents\n"); if ((list = (NLIST **)calloc(cnt, sizeof(NLIST *))) == NULL) { prmsg(1, "Failed to allocate %u more bytes.\n" "The list of user agents %s\n", cnt*sizeof(NLIST *), enolist); noagentlist = 1; return; } /* sort the hidden agent table */ if ((hidden[HIDDEN_AGENTS].t_count-hidden[HIDDEN_AGENTS].t_start) > 1) qsort((void *)(ip+hidden[HIDDEN_AGENTS].t_start), hidden[HIDDEN_AGENTS].t_count-hidden[HIDDEN_AGENTS].t_start, sizeof(ITEM_LIST), sort_by_string); /* create a linear list of hidden agents */ for (idx=0, cnt=0; idx < HASHSIZE && cnt < (size_t)total_agents; idx++) { if ((np = uatab[idx]) == NULL) continue; do { /* check for hidden agent, collect values */ if (!isHiddenAgent(np)) list[cnt++] = np; } while ((np=np->next) != NULL && cnt < (size_t)total_agents); } /* add collected user agents to the list */ for (idx=0; idx < TABSIZE(hlist[HIDDEN_AGENTS]); idx++) { if ((np=hlist[HIDDEN_AGENTS][idx]) == NULL) continue; do { if (np->count && cnt < (size_t)total_agents) list[cnt++] = np; } while ((np=np->next) != NULL); } assert(cnt <= (size_t)total_agents); if (cnt > 1) /* now sort the list by accesses */ qsort((void *)list, cnt, sizeof(NLIST *), sort_by_hits); hsum.hits = total.hits-unknown[HIDDEN_AGENTS].count; hsum.nomod = total.nomod-unknown[HIDDEN_AGENTS].nomod; hsum.bytes = total.bytes-unknown[HIDDEN_AGENTS].bytes; if (topn_agent) { /* create top user agent list */ for (idx=ndx=0; idx < topn_agent && ndx < (int)cnt; ndx++) { if (list[ndx]->count < noiselevel) continue; top_agent[idx++] = list[ndx]; } lntab[FNAME_TOPAGENTS] = (idx > 1) ? idx : 0; if (!noimages && lntab[FNAME_TOPAGENTS]) { /* create pie chart image */ (void) sprintf(label, "The Top %u Browser Types", lntab[FNAME_TOPAGENTS]); (void) sprintf(tbuf, "%s/agents%02d%02d.gif", stdir, t.start.mon+1, EPOCH(t.start.year)); (void) c_chart(492, 320, 28, tbuf, NULL, list, cnt, hsum.hits, label); } } if (noagentlist) { /* done already */ free(list); return; } if ((ofp=efopen("%s/%s/agents%02d%02d.html", stdir, !priv_dir ? "." : priv_dir, t.start.mon+1, EPOCH(t.start.year))) == NULL) { prmsg(1, "The overview of browser types %s\n", enolist); noagentlist = 1; free(list); return; } lntab[FNAME_AGENTS] = cnt; html_header(ofp, period, NULL); prHidden(ofp, list, cnt, HIDDEN_AGENTS, &hsum, 0); html_trailer(ofp); (void) fclose(ofp); /* create a linear list of all agents */ for (idx=0, cnt=0; idx < HASHSIZE && cnt < (size_t)total_agents; idx++) { if ((np = uatab[idx]) == NULL) continue; do { if (ISHIDDEN(np, HIDDEN_AGENTS) && ip[np->ishidden].pfx != NULL) list[cnt++] = np; } while ((np=np->next) != NULL && cnt < (size_t)total_agents); } if (cnt != 0) { if ((ofp=efopen("%s/%s/lagents%02d%02d.html", stdir, !priv_dir ? "." : priv_dir, t.start.mon+1, EPOCH(t.start.year))) == NULL) { prmsg(1, "The list of user agents %s\n", enolist); free(list); return; } if ((lntab[FNAME_LAGENTS] = cnt) > 1) qsort((void *)list, cnt, sizeof(NLIST *), sort_by_agent); html_header(ofp, period, NULL); (void) fprintf(ofp, "" "Total Transfers by User Agent (Browser)\n" "
\n", head_size, font_size);
		prItem(ofp, list, cnt, HIDDEN_AGENTS, &hsum);
		html_trailer(ofp);
		(void) fclose(ofp);	/* cleanup */
	}
	free(list);
	return;
}

/*
** Print statistics about all referrer URLs.
*/
static void prReferStats(char * const stdir, char * const period) {
	char tbuf[MAX_FNAMELEN];	/* filename templates */
	char label[MEDIUMSIZE];		/* label in graphic */
	size_t cnt = (size_t)total_refer;/* list size is total # of unique referrer URLs */
	ITEM_LIST *ip = hidden[HIDDEN_REFERS].tab;
	COUNTER hsum;			/* correction for total hits */
	NLIST *np, **list;		/* temp ptr */
	int idx, ndx;
	FILE *ofp;

	if (verbose)
		prmsg(0, "... processing referrer URLs\n");

	if ((list = (NLIST **)calloc(cnt, sizeof(NLIST *))) == NULL) {
		prmsg(1, "Failed to allocate %u more bytes.\n"
			 "The list of referrer URLs %s\n", cnt*sizeof(NLIST *), enolist);
		noreferlist = 1;
		return;
	}

	/* sort the hidden referrer table */
	if ((hidden[HIDDEN_REFERS].t_count-hidden[HIDDEN_REFERS].t_start) > 1)
		qsort((void *)(ip+hidden[HIDDEN_REFERS].t_start),
			hidden[HIDDEN_REFERS].t_count-hidden[HIDDEN_REFERS].t_start,
			sizeof(ITEM_LIST), sort_by_casestr);

	/* create a linear list of all referrers */
	for (idx=0, cnt=0; idx < HASHSIZE && cnt < (size_t)total_refer; idx++) {
		if ((np = reftab[idx]) == NULL)
			continue;

		do {	/* check for hidden referrer, collect values */
			if (!isHiddenRefer(np))
				list[cnt++] = np;
		} while ((np=np->next) != NULL && cnt < (size_t)total_refer);
	}

	/* add collected referrers to the list */
	for (idx=0; idx < (int)TABSIZE(hlist[HIDDEN_REFERS]); idx++) {
		if ((np=hlist[HIDDEN_REFERS][idx]) == NULL)
			continue;
		do {
			if (isSelfRefer(np->str, np->len)) {
				unknown[SELF_REF].count += np->count;
				unknown[SELF_REF].nomod += np->nomod;
				unknown[SELF_REF].bytes += np->bytes;
			} else if (np->count && cnt < (size_t)total_refer)
				list[cnt++] = np;
		} while ((np=np->next) != NULL);
	}
	assert(cnt <= (size_t)total_refer);

	if (cnt > 1)		/* now sort the list by accesses */
		qsort((void *)list, cnt, sizeof(NLIST *), sort_by_hits);

	hsum.hits = total.hits-(unknown[HIDDEN_REFERS].count+unknown[SELF_REF].count);
	hsum.nomod = total.nomod-(unknown[HIDDEN_REFERS].nomod+unknown[SELF_REF].nomod);
	hsum.bytes = total.bytes-(unknown[HIDDEN_REFERS].bytes+unknown[SELF_REF].bytes);

	if (topn_refer) {	/* create top referrer list */
		for (idx=ndx=0; idx < topn_refer && ndx < (int)cnt; ndx++) {
			if (list[ndx]->count < noiselevel)
				continue;
			top_refer[idx++] = list[ndx];
		}
		lntab[FNAME_TOPREFERS] = (idx > 1) ? idx : 0;
		if (!noimages && lntab[FNAME_TOPREFERS]) {
			(void) sprintf(label, "The Top %u Referrer URLs", lntab[FNAME_TOPREFERS]);
			(void) sprintf(tbuf, "%s/refers%02d%02d.gif",
					stdir, t.start.mon+1, EPOCH(t.start.year));
			(void) c_chart(492, 320, 28, tbuf, NULL, list, cnt, hsum.hits, label);
		}
	}

	if (noreferlist) {		/* done already */
		free(list);
		return;
	}

	if ((ofp=efopen("%s/%s/refers%02d%02d.html",
		stdir, !priv_dir ? "." : priv_dir, t.start.mon+1, EPOCH(t.start.year))) == NULL) {
		prmsg(1, "The overview of referrer URLs %s\n", enolist);
		noreferlist = 1;
		free(list);
		return;
	}
	lntab[FNAME_REFERS] = cnt;

	html_header(ofp, period, NULL);
	prHidden(ofp, list, cnt, HIDDEN_REFERS, &hsum, 0);
	html_trailer(ofp);
	(void) fclose(ofp);

	/* create a linear list of all referrers */
	for (idx=0, cnt=0; idx < HASHSIZE && cnt < (size_t)total_refer; idx++) {
		if ((np = reftab[idx]) == NULL)
			continue;

		do {
			if (ISHIDDEN(np, HIDDEN_REFERS) && ip[np->ishidden].pfx != NULL)
				list[cnt++] = np;
		} while ((np=np->next) != NULL && cnt < (size_t)total_refer);
	}
	if (cnt != 0) {
		if ((ofp=efopen("%s/%s/lrefers%02d%02d.html",
			stdir, !priv_dir ? "." : priv_dir, t.start.mon+1, EPOCH(t.start.year))) == NULL) {
			prmsg(1, "The list of referrer URLs %s\n", enolist);
			free(list);
			return;
		}
		lntab[FNAME_LREFERS] = cnt;

		if (cnt > 1)
			qsort((void *)list, cnt, sizeof(NLIST *), sort_by_refer);

		html_header(ofp, period, NULL);
		(void) fprintf(ofp, ""
			"Total Transfers by Referrer URL\n"
			"
\n", head_size, font_size);
		prItem(ofp, list, cnt, HIDDEN_REFERS, &hsum);
		html_trailer(ofp);
		(void) fclose(ofp);
	}
	free(list);
	return;
}

/*
** Set the logfile format.
*/
static int setLogFmt(char * const rq) {
	int fmt;

	if (streq(rq, "default") || strneq(rq, "auto", 4))
		fmt = LOGF_UNKNOWN;	/* automatic recognition */
	else if (streq(rq, "clf"))	/* Common Logfile Format (CLF) */
		fmt = LOGF_CLF;
	else if (streq(rq, "dlf"))	/* Combined Format: CLF "referrer" "uagent" */
		fmt = LOGF_NCSA;
	else if (streq(rq, "elf"))	/* Extended Format: CLF uagent referrer */
		fmt = LOGF_ELF;
	else
		fmt = -1;		/* unknown */
	return fmt;
}

/*
** Read the logfile, scan the entries, fill in the
** elements of a LOGENT structure. Returns a ptr
** to the logfile entry, NULL on error or EOF.
** Highly optimized version.
*/
#define SKIPMSG(msg,ln,lb) if (verbose) prmsg(1, (msg), (ln), (lb))

static LOGENT *readLog(FILE * const lfp, LOGTIME * const stp, LOGTIME * const etp) {
	static char lbuf[BIGSIZE];	/* line buffer */
	static u_long dtstart = 0L;	/* dayticks start time */
	static u_long dtend = 0L;	/* dayticks end time */
	static LOGENT np;		/* buffer for logfile entry */
	register int len;		/* string length */
	register char *cp, *tm, rc;	/* temp */
	HSTRING sitename, request;	/* DNS sitename, URL request */
	HSTRING uagent, refer;		/* browser type, referrer URL */
	HSTRING authuser, uatype;	/* auth username, uagent type */
	HSTRING tldomain, refhost;	/* 2nd level domain, referrer host */
	HSTRING reqbase;		/* last component of request */
	int strip, nogbg = 0;		/* strip part of string, errmsg flag */
	size_t idx, reqmethod;		/* temp storage */
	u_int resp, ftype;		/* response code, file type */
	u_long reqsize;			/* size of request */
	u_long ltick;			/* last tick */
	LOGTIME *tp;			/* temp */

	if (lfp == NULL)
		return NULL;

	if (!dtstart && stp->mday)	/* set time window, assume 00:00:00 */
		dtstart = TICKS_PDAY(stp->mday-1)+TICKS_PMON(stp->mon)+TICKS_PYEAR(stp->year);
	if (!dtend && etp->mday)
		dtend = TICKS_PDAY(etp->mday-1)+TICKS_PMON(etp->mon)+TICKS_PYEAR(etp->year);

#if defined(USE_FGETS)
# define NEXT_LINE	((cp=fgets(lbuf, sizeof lbuf, lfp)) != NULL)
#else
# define NEXT_LINE	((len=readLine(lbuf, sizeof lbuf, lfp)) >= 0)
#endif
	while (NEXT_LINE) {
#if defined(USE_FGETS)
		for (len=0, tm=cp; *tm != '\0' && *tm != '\n' && *tm != '\r'; tm++)
			len++;			/* determine string length */
		if (*tm == '\n' || *tm == '\r')	/* strip trailing newline */
			*tm = '\0';
#else
		cp = lbuf;			/* the optimized version did this already */
#endif
		if (!len || *cp == '#')		/* skip empty and comment lines */
			continue;

		/*
		** Try to detect binary garbagge. Skip first format lines for
		** Netscape Enterprise and Fasttrack servers.
		*/
		if (!is_print(*cp)) {
			if (verbose && !nogbg) {
				prmsg(1, "Garbagge detected, skip binary data until next valid line ...\n");
				nogbg = 1;
			}
			continue;
		}
		if (!lnum++ && !strncmp(lbuf, "format", 6)) {
			if (verbose)
				prmsg(1, "Skip Netscape log format definition: `%.23s ...'\n", lbuf);
			continue;
		}

		/*
		** Save ptr to end of line, search for the sitename
		** and compute the hash value.
		*/
		request.str = refer.str = cp+len;
		tldomain.str = NULL;
		tldomain.len = 0;
		tldomain.hval = 0;
		nogbg = 0;
		ftype = 0;

		tm = lbuf;
		for (sitename.hval=0; *cp && *cp != ' '; cp++) {
			if (*cp == '.') {
				tldomain.str = tm;
				tm = cp;
			}
			sitename.hval = (sitename.hval<<1)+(u_char)*cp;
		}
		if (*cp == '\0') {
			SKIPMSG("Couldn't find the sitename, skip line %lu: %s\n", lnum, lbuf);
			corrupt++;
			continue;
		}
		if (tldomain.str) {
			if (is_digit(*(cp-1)) && is_digit(*lbuf)) {
				ftype |= TYPE_NODNS;
				tldomain.str = NULL;
				tldomain.len = 0;
			} else
				tldomain.len = (size_t)(cp-tldomain.str);
		} else {
			tldomain.str = lbuf;
			tldomain.len = (size_t)(cp-lbuf);
		}
		sitename.str = lbuf;
		sitename.len = (size_t)(cp-lbuf);
		sitename.hval %= TABSIZE(sitetab);
		*cp++ = '\0';

		/* Ignore certain sites if requested */
		if (hidden[IGNORED_SITES].t_count && isIgnoredItem(IGNORED_SITES, &sitename))
			continue;

		/* Find the authentication field */
		for (tm=cp; *cp != '\0' && *cp != ' '; cp++)
			/* no-op */ ;

		if (*cp == '\0') {
			SKIPMSG("Couldn't find the authentication field, skip line %lu: %s\n", lnum, tm);
			corrupt++;
			continue;
		}
		tm = ++cp;

		/*
		** Count all requests which required authentication
		** and then ignore them if desired.
		*/
		authuser.str = NULL;
		authuser.len = 0;
		authuser.hval = 0;

		if (*cp == '-')
			cp++;
		else {
			authenticated++;
			if (ign_auth) {
				SKIPMSG("Skip authenticated request in line %lu: %s\n", lnum, tm);
				continue;
			}

			while (*cp && *cp != ' ')
				authuser.hval = (authuser.hval<<1)+(u_char)*cp++;

			if (*cp == ' ') {
				authuser.str = tm;
				authuser.len = (size_t)(cp-tm);
				*cp++ = '\0';
			}
		}
			
		while (*cp && *cp != '[')		/* find the date */
			cp++;

		if (*cp++ != '[' || refer.str-cp < 27 || cp[26] != ']' ||
		    cp[2] != '/' || cp[6] != '/' || cp[11] != ':' ||
		    cp[14] != ':' || cp[17] != ':') {
			SKIPMSG("Couldn't find the date, skip line %lu: %s\n", lnum, tm);
			corrupt++;
			continue;
		}
		tm = cp;
		cp += 26;
		*cp++ = '\0';

		/*
		** Parse a date string in format DD/MMM/YYYY:HH:MM:SS
		** and fill in the elements of the LOGTIME structure.
		*/
		tp = &np.tm;
		tp->hour = (u_short)((tm[12]-'0') * 10  + (tm[13]-'0'));
		tp->min  = (u_short)((tm[15]-'0') * 10  + (tm[16]-'0'));
		tp->sec  = (u_short)((tm[18]-'0') * 10  + (tm[19]-'0'));
		tp->mday = (u_short)((tm[0]-'0')  * 10   + (tm[1]-'0'));
		tp->year = (u_short)((tm[7]-'0')  * 1000 + (tm[8]-'0')*100 +
				     (tm[9]-'0')  * 10   + (tm[10]-'0'));

		switch (tm[4]) {
		  case 'a':		/* jan, mar, may */
			switch (tm[5]) {
			  case 'n':	tp->mon = 0;	break;
			  case 'r':	tp->mon = 2;	break;
			  default:	tp->mon = 4;	break;
			}
			break;

		  case 'u':		/* jun, jul, aug */
			switch (tm[5]) {
			  case 'n':	tp->mon = 5;	break;
			  case 'l':	tp->mon = 6;	break;
			  default:	tp->mon = 7;	break;
			}
			break;

		  case 'e':		/* feb, sec, dec */
			switch (tm[3]) {
			  case 'F':	tp->mon = 1;	break;
			  case 'S':	tp->mon = 8;	break;
			  default:	tp->mon = 11;	break;
			}
			break;

		  default:		/* apr, oct, nov */
			switch (tm[3]) {
			  case 'A':	tp->mon = 3;	break;
			  case 'O':	tp->mon = 9;	break;
			  default:	tp->mon = 10;	break;
			}
			break;
		}
		/* Compute ticks, ignore all entries of a certain period if specified. */
		ltick = TICKS_PSEC(np.tm.sec)+TICKS_PMIN(np.tm.min)+TICKS_PHOUR(np.tm.hour)+
			TICKS_PDAY(np.tm.mday-1)+TICKS_PMON(np.tm.mon)+TICKS_PYEAR(np.tm.year);

		if (dtstart || dtend) {
			if (dtstart && ltick < dtstart)
				continue;
			if (dtend && ltick >= dtend)
				break;

			dtstart = 0L;
		}
		while (*cp != '\0' && *cp != '"')	/* check request */
			cp++;

		if (*cp != '"' || *++cp == '\0') {
			SKIPMSG("Couldn't find start of request, skip line %lu: %s\n",
				lnum, *cp ? cp : "[empty]");
			corrupt++;
			continue;
		}
		/*
		** Determine the request method and isolate the URL.
		** Take care for embedded `"' characters.
		*/
		tm = cp;
		switch (*cp) {
		  default:	reqmethod = METHOD_UNKNOWN;
				break;

		  case 'G':	cp++;
				if (*cp++ == 'E' && *cp++ == 'T' && *cp++ == ' ')
					reqmethod = METHOD_GET;
				break;
		  case 'H':	cp++;
				if (*cp++ == 'E' && *cp++ == 'A' && *cp++ == 'D' && *cp++ == ' ')
					reqmethod = METHOD_HEAD;
				break;
		  case 'P':	cp++;
				if (*cp == 'O') {
					if (*++cp == 'S' && *++cp == 'T' && *++cp == ' ') {
						reqmethod = METHOD_POST;
						cp++;
					}
				} else if (*cp++ == 'U' && *cp++ == 'T' && *cp++ == ' ')
					reqmethod = METHOD_PUT;
				break;
		  case 'B':	cp++;
				if (*cp++ == 'R' && *cp++ == 'O' && *cp++ == 'W' &&
				    *cp++ == 'S' && *cp++ == 'E' && *cp++ == ' ')
					reqmethod = METHOD_BROWSE;
				break;
		  case 'O':	cp++;
				if (*cp++ == 'P' && *cp++ == 'T' && *cp++ == 'I' &&
				    *cp++ == 'O' && *cp++ == 'N' && *cp++ == 'S' && *cp++ == ' ')
					reqmethod = METHOD_OPTIONS;
				break;
		  case 'D':	cp++;
				if (*cp++ == 'E' && *cp++ == 'L' && *cp++ == 'E' &&
				    *cp++ == 'T' && *cp++ == 'E' && *cp++ == ' ')
					reqmethod = METHOD_DELETE;
				break;
		  case 'T':	cp++;
				if (*cp++ == 'R' && *cp++ == 'A' && *cp++ == 'C' &&
				    *cp++ == 'E' && *cp++ == ' ')
					reqmethod = METHOD_TRACE;
				break;
		  case '-':	cp++;	/* the Apache way to log timeouts :-( */
				if (*cp++ == '"' && *cp++ == ' ' && *cp++ == '4' &&
				    *cp++ == '0' && *cp++ == '8') {
					empty++;	/* account for as empty request */
					continue;
				}
				break;
		}
		if (reqmethod == METHOD_UNKNOWN) {
			SKIPMSG("Unknown request method, skip line %lu: %s\n", lnum, tm);
			corrupt++;
			continue;
		}

		/*
		** Save request method, then search for end of request.
		*/
		ftype |= (reqmethod & METHOD_MASK);	/* save request method */
		for (request.str = cp; *cp != '\0'; cp++) {
			if (*cp == ' ')			/* end of URI part */
				*cp = '\0';
			else if (*cp == '"' && cp[1] == ' ' && (is_digit(cp[2]) || cp[2] == '-'))
				break;			/* end of req-url */
		}
		if (*cp != '"') {
			SKIPMSG("Couldn't find end of request, skip line %lu: %s\n",
				lnum, *request.str ? request.str : "[empty]");
			corrupt++;
			continue;
		}
		*cp++ = '\0';

		/* obtain response code */
		if (*cp != ' ' || *++cp == '\0') {
			SKIPMSG("Couldn't find the repsonse code, skip line %lu: %s\n",
				lnum, *cp ? cp : "[empty]");
			corrupt++;
			continue;
		}
		resp = 0;
		if (*cp != '-') {
			while (is_digit(*cp))
				resp = 10*resp + (u_int)(*cp++ - '0');
		} else
			cp++;

		/* obtain request size */
		if (*cp != ' ' || *++cp == '\0') {
			SKIPMSG("Couldn't find the request size, skip line %lu: %s\n",
				lnum, *cp ? cp : "[empty]");
			corrupt++;
			continue;
		}
		reqsize = 0L;
		if (*cp != '-') {
			while (is_digit(*cp))
				reqsize = 10L*reqsize + (u_long)(*cp++ - '0');
		} else
			cp++;

		if (*cp != '\0' && *cp != ' ') {
			SKIPMSG("Invalid request size, skip line %lu: %s\n",
				lnum, *cp ? cp : "[empty]");
			corrupt++;
			continue;
		}

		/* don't account for document size if request method was HEAD */
		if (reqmethod == METHOD_HEAD)
			reqsize = 0;

		/*
		** Parse the user agent and referrer in Extended Logfile Format.
		** Switch to appropriate type of logile format if still unknown.
		**
		** Correct ELF format is:
		**	UserAgent[ (WinSys; OS; CPU)] Referrer
		** For example:
		**	Mozilla/1.0S (X11; IRIX 5.3; IP22) http://foo/bar.html
		**
		** A rather strange, although common used format is:
		**	"referrer" "uagent"
		** For example:
		**	"http://foo/bar.html" "Mozilla/1.0S (X11; IRIX 5.3; IP22)"
		** This format is not supported yet.
		*/

		while (*cp == ' ' || *cp == '\t')	/* skip leading spaces */
			cp++;

		if (logfmt == LOGF_UNKNOWN) {
			if (*cp == '\0') {		/* fall back to CLF */
				if (total.hits > 50L) {
					logfmt = LOGF_CLF;
					if (verbose)
						prmsg(0, "Common Logfile Format detected\n");
				}
			} else if (*cp == '"' && *(refer.str-1) == '"') {
				logfmt = LOGF_NCSA;	/* NCSA combined format */
				if (verbose)
					prmsg(0, "Hmm, looks like Combined Logfile Format (DLF)\n");
			} else {
				logfmt = LOGF_ELF;	/* unambiguous extended format */
				if (verbose)
					prmsg(0, "Hmm, looks like Extended Logfile Format (ELF)\n");
			}
		}

		uatype.str = refhost.str = NULL;
		uatype.len = refhost.len = 0;
		uatype.hval = refhost.hval = 0;

		/*
		** Handle ambiguous NCSA format.
		*/
		if (*cp == '\0' || logfmt == LOGF_CLF) {
			uagent.len = refer.len = 0;
			uagent.str = refer.str = NULL;
		} else if (logfmt == LOGF_NCSA) {
			tm = refer.str;			/* save ptr to end of line */
			uagent.len = refer.len = 0;
			uagent.str = refer.str = NULL;

			if (*cp == '"')
				cp++;

			for (refer.str=cp; *cp != '\0'; cp++)	/* find end of referrer field */
				if (*cp == '"' && (cp[1] == ' ' || cp[1] == '\0'))
					break;

			if ((refer.len = (size_t)(cp-refer.str)) == 0)
				refer.str = NULL;
			else if (refer.len == 1 && *refer.str == '-') {
				*refer.str = '\0'; /* no referrer URL given */
				refer.len = 0;
			}
			if (*cp == '"') *cp++ = '\0';
			if (*cp == ' ') *cp++ = '\0';

			if (*cp == '"') {
				cp++;
				if (*(tm-1) == '"')
					*--tm = '\0';
			}
			if (cp < tm) {
				uagent.len = (size_t)(tm-cp);
				uagent.str = cp;
				if (uagent.len == 1 && *uagent.str == '-') {
					*uagent.str = '\0';	/* no user agent given */
					uagent.len = 0;
				} else {		/* compute hash value */
					strip = 0;
					uagent.hval = 0;
					for (cp=uagent.str; *cp != '\0'; cp++) {
						if (strip > 0 && *cp == ' ') {
							uatype.str = uagent.str;
							uatype.len = (size_t)(cp-uagent.str);
							uatype.hval = uagent.hval%HIDELIST_SIZE;
							strip = -1;
						}
						uagent.hval = (uagent.hval<<1) + (u_char)*cp;
						if (*cp == ')') {
							*++cp = '\0';
							uagent.len = (size_t)(cp-uagent.str);
							break;
						}
						if (strip < 0)
							continue;

						if (!strip) {
							if (is_digit(*cp))
								strip++;
							else if (*cp == '(')
								strip = -1;
						} else if (*cp == '.' || *cp == '-') {
							uatype.str = uagent.str;
							uatype.len = (size_t)(cp-uagent.str)+1;
							uatype.hval = uagent.hval%HIDELIST_SIZE;
							strip = -1;
						}
					}
					if (!uatype.len) {
						uatype.str = uagent.str;
						uatype.len = uagent.len;
						uatype.hval = uagent.hval%HIDELIST_SIZE;
					}
					uagent.hval %= TABSIZE(uatab);
				}
			}
		} else {			/* handle unambiguous extended format */
			uagent.str = cp;
			cp = refer.str-1;

			if (*cp == '-' && cp-1 > uagent.str && *(cp-1) == ' ') {
				refer.str = cp;	/* no referrer URL given */
				refer.len = 0;
				*cp-- = '\0';	/* separate user agent from referrer */
				*cp = '\0';
				uagent.len = (size_t)(cp-uagent.str);
			} else {		/* try to find the :// part */
				while (cp > uagent.str && *cp != ':')
					cp--;
				while (cp > uagent.str && *cp != ' ')
					cp--;
				if (cp == uagent.str) {	/* no referrer found */
					while (refer.str-1 > uagent.str && *(refer.str-1) == ' ')
						*--refer.str = '\0';	/* delete trailing blanks */
					uagent.len = (size_t)(refer.str-uagent.str);
					refer.str = NULL;
					refer.len = 0;
				} else {	/* compute length of referrer URL */
					uagent.len = (size_t)(cp-uagent.str);
					*cp++ = '\0';
					refer.len = (size_t)(refer.str-cp);
					refer.str = cp;
				}
			}
			if (uagent.len == 1 && *uagent.str == '-') {
				*uagent.str = '\0';	/* has user agent field, but none given */
				uagent.len = 0;
			} else {			/* find type, compute hash value */
				strip = 0;
				uagent.hval = 0;
				for (cp=uagent.str; *cp != '\0'; cp++) {
					if (strip > 0 && *cp == ' ') {
						uatype.str = uagent.str;
						uatype.len = (size_t)(cp-uagent.str);
						uatype.hval = uagent.hval%HIDELIST_SIZE;
						strip = -1;
					}
					uagent.hval = (uagent.hval<<1) + (u_char)*cp;
					if (*cp == ')') {
						*++cp = '\0';
						uagent.len = (size_t)(cp-uagent.str);
						break;
					}
					if (strip < 0)
						continue;

					if (!strip) {
						if (is_digit(*cp))
							strip++;
						else if (*cp == '(')
							strip = -1;
					} else if (*cp == '.' || *cp == '-') {
						uatype.str = uagent.str;
						uatype.len = (size_t)(cp-uagent.str)+1;
						uatype.hval = uagent.hval%HIDELIST_SIZE;
						strip = -1;
					}
				}
				if (!uatype.len) {
					uatype.str = uagent.str;
					uatype.len = uagent.len;
					uatype.hval = uagent.hval%HIDELIST_SIZE;
				}
				uagent.hval %= TABSIZE(uatab);
			}
		}

		/*
		** Strip cgi-bin arguments, target names, and the HTTP
		** protocol. Decode hex sequences into character codes.
		** Since the decoded string must be always shorter than the
		** encoded version, we can safely use the same array for the
		** resulting string. Precompute hash value. Remember last
		** component of URL.
		*/
		request.hval = 0;
		tm = reqbase.str = request.str;
		for (cp=request.str; *cp && *cp != '?' && *cp != '#'; cp++) {
			if (*cp == '%' && isxdigit(cp[1]) && isxdigit(cp[2])) {
				cp++;
				if (is_upper(*cp))
					rc = ((*cp-'A')+10)<<4;
				else if (is_lower(*cp))
					rc = ((*cp-'a')+10)<<4;
				else 	rc = (*cp-'0')<<4;

				cp++;
				if (is_upper(*cp))
					rc += (*cp-'A')+10;
				else if (is_lower(*cp))
					rc += (*cp-'a')+10;
				else 	rc += (*cp-'0');

				*tm++ = rc;
				request.hval = (request.hval<<1)+(u_char)rc;
				continue;
			}
			if (*cp == '/')
				reqbase.str = tm;		/* remember last component */
			*tm++ = *cp;
			request.hval = (request.hval<<1)+(u_char)*cp;
		}

		/*
		** Check for empty requests.
		*/
		if ((request.len = (size_t)(tm-request.str)) == 0) {
			SKIPMSG("Empty request?!? %s line %lu\n", "Skip", lnum);
			empty++;
			continue;
		}
		reqbase.len = (size_t)(tm-reqbase.str);
		request.hval %= TABSIZE(urltab);
		*tm = '\0';			/* terminate string */

		/*
		** Check for document root if given.
		*/
		if (drlen > 1) {
			if (request.len >= drlen) {
				tm = doc_root;
				cp = request.str;
				while (*tm && *tm == *cp)
					tm++, cp++;
				if (drneg ? *tm == '\0' : *tm != '\0')
					continue;
			} else if (!drneg)
				continue;
		}

		/*
		** Ignore certain URLs if requested
		*/
		if (hidden[IGNORED_ITEMS].t_count && isIgnoredItem(IGNORED_ITEMS, &request))
			continue;

		/*
		** Truncate the name of index files and it's variations so they merge with `/'.
		** Check whether we rate this file as a pageview.
		*/
		if (request.str[request.len-1] == '/') {
			ftype |= TYPE_PGVIEW;
		} else for (idx=0; idx < MAX(ipnum, ptnum); idx++) {
			if (idx < ipnum && reqbase.len == indexpg[idx].len &&
			    reqbase.str[1] == indexpg[idx].str[1]) {
				len = indexpg[idx].len;
				tm = indexpg[idx].str;
				cp = reqbase.str;
				while (*tm && *tm++ == *cp++)
					len--;
				if (len == 0) {		/* must re-compute hash value */
					request.len -= (size_t)(indexpg[idx].len-1);
					*++reqbase.str = '\0';
					request.hval = 0;
					cp = request.str;
					while (*cp)
						request.hval = (request.hval<<1) + (u_char)*cp++;
					request.hval %= TABSIZE(urltab);
					ftype |= TYPE_PGVIEW;
					break;
				}
			}
			if (idx < ptnum && reqbase.len > pvsuffix[idx].len) {
				len = pvsuffix[idx].len;
				tm = pvsuffix[idx].str;
				cp = request.str + (request.len-pvsuffix[idx].len);
				while (*tm && *tm++ == *cp++)
					len--;
				if (len == 0)
					ftype |= TYPE_PGVIEW;
			}
		}

		/*
		** Massage the referrer URL, delete usernames and passwords,
		** find referrer host, compute hash value.
		*/
		if (refer.len) {	/* massage the referrer URL, compute hash value */
			refer.hval = 0;
			strip = 0;

			for (cp=refer.str; !strip && *cp != '\0' && *cp != '/'; cp++) {
				refer.hval = (refer.hval<<1) + (u_char)*cp;
				if (*cp == ':' && cp[1] == '/' && cp[2] == '/') {
					refer.hval = (refer.hval<<1) + (u_char)*++cp;
					refer.hval = (refer.hval<<1) + (u_char)*++cp;
					strip++;
				}
			}
			if (strip) {			/* found hostname part */
				for (tm = cp; *tm != '\0'; tm++)
					if (*tm == '@' || *tm == '/')
						break;

				if (*tm == '@') {	/* delete username and password */
					char *dp = cp;
					while ((*dp++ = *++tm) != '\0')
						/* no-op */ ;
				}
				while (*cp != '\0') {	/* compute rest of hash value */
					if (*cp == '/' || *cp == ':' || (*cp == '.' && cp[1] == '/'))
						if (++strip == 2) {
							refhost.str = refer.str;
							refhost.len = (size_t)(cp-refer.str);
							refhost.hval = refer.hval%HIDELIST_SIZE;
						}
					refer.hval = (refer.hval<<1) + (u_char)*cp++;
				}
				refer.len = (size_t)(cp-refer.str);
				if (strip == 1) {
					refhost.str = refer.str;
					refhost.len = refer.len;
					refhost.hval = refer.hval%HIDELIST_SIZE;
				}
			} else while (*cp != '\0')
				refer.hval = (refer.hval<<1) + (u_char)*cp++;


			refer.hval %= TABSIZE(reftab);
		}

		/* valid entry, copy data */
		np.sitename = sitename;
		np.authuser = authuser;
		np.request  = request;
		np.uagent   = uagent;
		np.refer    = refer;
		np.tldomain = tldomain;
		np.uatype   = uatype;
		np.refhost  = refhost;
		np.reqsize  = reqsize;
		np.ltick    = ltick;
		np.ftype    = ftype;

		switch (resp) {
		  default:			np.respidx = IDX_UNKNOWN;	break;
		  case RES_CONTINUE:		np.respidx = IDX_CONTINUE;	break;
		  case RES_SWITCH_PROTO:	np.respidx = IDX_SWITCH_PROTO;	break;
		  case RES_OK:			np.respidx = IDX_OK;		break;
		  case RES_CREATED:		np.respidx = IDX_CREATED;	break;
		  case RES_ACCEPTED:		np.respidx = IDX_ACCEPTED;	break;
		  case RES_NON_AUTH:		np.respidx = IDX_NON_AUTH;	break;
		  case RES_NO_CONTENT:		np.respidx = IDX_NO_CONTENT;	break;
		  case RES_RSET_CONTENT:	np.respidx = IDX_RSET_CONTENT;	break;
		  case RES_PART_CONTENT:	np.respidx = IDX_PART_CONTENT;	break;
		  case RES_MULT_CHOICES:	np.respidx = IDX_MULT_CHOICES;	break;
		  case RES_MOVED_PERM:		np.respidx = IDX_MOVED_PERM;	break;
		  case RES_MOVED_TEMP:		np.respidx = IDX_MOVED_TEMP;	break;
		  case RES_SEE_OTHER:		np.respidx = IDX_SEE_OTHER;	break;
		  case RES_NOT_MODIFIED:	np.respidx = IDX_NOT_MODIFIED;	break;
		  case RES_USE_PROXY:		np.respidx = IDX_USE_PROXY;	break;
		  case RES_BAD_REQUEST:		np.respidx = IDX_BAD_REQUEST;	break;
		  case RES_UNAUTHORIZED:	np.respidx = IDX_UNAUTHORIZED;	break;
		  case RES_PAYMENT_REQ:		np.respidx = IDX_PAYMENT_REQ;	break;
		  case RES_FORBIDDEN:		np.respidx = IDX_FORBIDDEN;	break;
		  case RES_NOT_FOUND:		np.respidx = IDX_NOT_FOUND;	break;
		  case RES_METHOD_NALLOWED:	np.respidx = IDX_METHOD_NALLOWED;break;
		  case RES_NOT_ACCEPTABLE:	np.respidx = IDX_NOT_ACCEPTABLE;break;
		  case RES_PROXY_AUTHREQ:	np.respidx = IDX_PROXY_AUTHREQ;	break;
		  case RES_REQ_TIMEOUT:		np.respidx = IDX_REQ_TIMEOUT;	break;
		  case RES_CONFLICT:		np.respidx = IDX_CONFLICT;	break;
		  case RES_GONE:		np.respidx = IDX_GONE;		break;
		  case RES_LENGTH_REQ:		np.respidx = IDX_LENGTH_REQ;	break;
		  case RES_PRECOND_FAILED:	np.respidx = IDX_PRECOND_FAILED;break;
		  case RES_REQ_TOOLARGE:	np.respidx = IDX_REQ_TOOLARGE;	break;
		  case RES_REQ_TOOLONG:		np.respidx = IDX_REQ_TOOLONG;	break;
		  case RES_UNSUPPORTED:		np.respidx = IDX_UNSUPPORTED;	break;
		  case RES_SERVER_ERROR:	np.respidx = IDX_SERVER_ERROR;	break;
		  case RES_NOT_IMPLEMENTED:	np.respidx = IDX_NOT_IMPLEMENTED;break;
		  case RES_BAD_GATEWAY:		np.respidx = IDX_BAD_GATEWAY;	break;
		  case RES_SERVICE_UNAVAIL:	np.respidx = IDX_SERVICE_UNAVAIL;break;
		  case RES_GATEWAY_TIMEOUT:	np.respidx = IDX_GATEWAY_TIMEOUT;break;
		  case RES_VERS_NSUPPORTED:	np.respidx = IDX_VERS_NSUPPORTED;break;
		}
#if !defined(NDEBUG)
		if (verbose > 2) {
			if (verbose > 3)
				(void) fputc('\n', stderr);
			(void) fprintf(stderr,
				"%7lu %02hu/%.3s/%hu:%02hu:%02hu:%02hu [%lu], "
				"req=\"%s %s\", sz=%lu <- %s%s%s\n",
				lnum, np.tm.mday, monnam[np.tm.mon], np.tm.year,
				np.tm.hour, np.tm.min, np.tm.sec, np.ltick,
				ReqMethod[(np.ftype&METHOD_MASK)].msg,
				np.request.str, np.reqsize, RespCode[np.respidx].msg,
				IS_TYPE(ftype, TYPE_PGVIEW) ? ", PAGEVIEW" : "",
				np.authuser.str ? ", AUTHREQ" : "");

			if (verbose > 3) {
				if (!np.tldomain.len)
					(void) fprintf(stderr, "\thost=%s, dom=%s\n", np.sitename.str,
						IS_TYPE(np.ftype, TYPE_NODNS) ? "[IP]" : "[none]");
				else	(void) fprintf(stderr, "\thost=%s, dom=%s\n",
						np.sitename.str, np.tldomain.str);
				if (np.uagent.len) {
					if (np.uatype.len)
						(void) fprintf(stderr, "\tuag=%s, uatype=%.*s\n",
							np.uagent.str, np.uatype.len, np.uatype.str);
					else	(void) fprintf(stderr, "\tuag:%s\n", np.uagent.str);
				}
				if (np.refer.len) {
					if (np.refhost.len)
						(void) fprintf(stderr, "\tref=%s, refhost=%.*s\n",
							np.refer.str, np.refhost.len, np.refhost.str);
					else	(void) fprintf(stderr, "\tref:%s\n", np.refer.str);
				}
			}
		}
#endif
		return &np;		/* found an entry */
	}
	return NULL;
}

/*
** Return a hashed string.
** Needed only for few constant strings, since
** values are computed "on the fly" elsewhere.
*/
static HSTRING *hashedString(u_int const hsize, char *str) {
	static HSTRING hstr;

	hstr.hval = 0;
	hstr.len = 0;
	for (hstr.str=str; *str != '\0'; str++)
		hstr.hval = (hstr.hval<<1) + (u_char)*str;

	hstr.hval %= hsize;
	hstr.len = (size_t)(str-hstr.str);
	return &hstr;
}

/*
** Lookup item in hash list.
*/
static int nomem = 0;

#if defined(MEMCMP_FASTER_STRCMP)
# define IS_EQUAL(s1, s2, len)	(!strcmp((s1), (s2)))
#else
# define IS_EQUAL(s1, s2, len)	(!memcmp((void *)(s1), (void *)(s2), (len)+1))
#endif

static NLIST *lookupItem(NLIST ** const htab, HSTRING * const item, u_int const sslen) {
	register char *s1, *s2;
	register NLIST *np;

	/* lookup the item in the given list */
	for (np=htab[item->hval]; np != NULL; np = np->next)
		if (item->len == np->len && IS_EQUAL(np->str, item->str, item->len))
			break;

	if (np != NULL)		/* known already */
		return np;

	if (nomem)		/* new entry, but no more memory available */
		return NULL;	/* we can't do anything useful */

	/* create new entry */
	if (!(np = (NLIST *)malloc(sizeof(NLIST))) || !(np->str = malloc(item->len+1))) {
		prmsg(1, enomem, sizeof(NLIST));
		nomem = 1;
		return NULL;
	}
	for (s1=np->str, s2=item->str; (*s1 = *s2) != '\0'; s1++, s2++)
		/* copy string */ ;
	
	np->len = item->len;		/* initialize NLIST values */
        np->ishidden = -1;
        np->sslen = sslen;
	np->count = np->nomod = np->size = np->ltick = 0L;
        np->bytes = 0.0;
	np->next = htab[item->hval];	/* insert element into hash list */
	htab[item->hval] = np;
	return np;
}
#undef IS_EQUAL

/*
** Clear all items from the given list.
*/
static void clearItems(NLIST ** const htab, int const max) {
	NLIST *node, *np;
	int idx;

	for (idx=0; idx < max; idx++) {
		if ((node = htab[idx]) == NULL)
			continue;
			
		do {	np = node->next;
			free((void *)node->str);
			free((void *)node);
		} while ((node = np) != NULL);
		htab[idx] = NULL;
	}
	nomem = 0;	/* try again */
	return;
}

/*
** Clear all or some counters.
*/
static void clearCounter(int const all) {
	size_t idx;

	this_hits += total.hits;	/* add total hits to hits per session */

	corrupt = empty = authenticated = 0L;
	total_agents = total_refer = total_kbsaved = uniq_urls = uniq_sites = 0L;
	max_avhits = max_avdhits = max_avhhits = max_hrhits = max_whhits = 0L;

	if (all) {
		(void) memset((void *)monly, 0, sizeof monly);
		for (idx=0; idx < TABSIZE(monly); idx++)
			monly[idx].bytes = 0.0;

		(void) memset((void *)&cksum, 0, sizeof cksum);
		cksum.bytes = 0.0;
	}

	(void) memset((void *)&total, 0, sizeof total);
	(void) memset((void *)&avg_day, 0, sizeof avg_day);
	(void) memset((void *)&max_day, 0, sizeof max_day);
	total.bytes = avg_day.bytes = max_day.bytes = 0.0;

	(void) memset((void *)avg_hour, 0, sizeof avg_hour);
	(void) memset((void *)avg_wday, 0, sizeof avg_wday);
	(void) memset((void *)avg_whour, 0, sizeof avg_whour);
	(void) memset((void *)wh_hits, 0, sizeof wh_hits);
	(void) memset((void *)wh_cnt, 0, sizeof wh_cnt);
	(void) memset((void *)daily, 0, sizeof daily);
	(void) memset((void *)weekly, 0, sizeof weekly);

	for (idx=0; idx < TABSIZE(daily); idx++)
		daily[idx].bytes = 0.0;

	for (idx=0; idx < TABSIZE(weekly); idx++)
		weekly[idx].bytes = 0.0;

	(void) memset((void *)unknown, 0, sizeof unknown);
	unknown[HIDDEN_ITEMS].str = NULL;
	unknown[HIDDEN_SITES].str = "Unresolved";
	unknown[HIDDEN_AGENTS].str = unknown[HIDDEN_REFERS].str = "Unknown";
	unknown[SELF_REF].str = "Self Referrer";
	unknown[HIDDEN_ITEMS].bytes = unknown[HIDDEN_SITES].bytes = 0.0;
	unknown[HIDDEN_AGENTS].bytes = unknown[HIDDEN_REFERS].bytes = 0.0;
	unknown[SELF_REF].bytes = 0.0;

	if (top_sites != NULL)
		(void) memset((void *)top_sites, 0, (size_t)topn_sites*sizeof(NLIST *));
	if (top_agent != NULL)
		(void) memset((void *)top_agent, 0, (size_t)topn_agent*sizeof(NLIST *));
	if (top_refer != NULL)
		(void) memset((void *)top_refer, 0, (size_t)topn_refer*sizeof(NLIST *));
	if (top_urls != NULL)
		(void) memset((void *)top_urls, 0, (size_t)topn_urls*sizeof(NLIST *));
	if (lst_urls != NULL)
		(void) memset((void *)lst_urls, 0, (size_t)lstn_urls*sizeof(NLIST *));

	if (top_day) {
		(void) memset((void *)top_day, 0, (size_t)topn_sec*sizeof(TOP_COUNTER));
		for (idx=0; idx < topn_day; idx++)
			top_day[idx].bytes = 0.0;
	}
	if (top_hrs) {
		(void) memset((void *)top_hrs, 0, (size_t)topn_hrs*sizeof(TOP_COUNTER));
		for (idx=0; idx < topn_hrs; idx++)
			top_hrs[idx].bytes = 0.0;
	}
	if (top_min) {
		(void) memset((void *)top_min, 0, (size_t)topn_min*sizeof(TOP_COUNTER));
		for (idx=0; idx < topn_min; idx++)
			top_min[idx].bytes = 0.0;
	}
	if (top_sec) {
		(void) memset((void *)top_sec, 0, (size_t)topn_sec*sizeof(TOP_COUNTER));
		for (idx=0; idx < topn_sec; idx++)
			top_sec[idx].bytes = 0.0;
	}

	for (idx=0; idx < TABSIZE(RespCode); idx++) {
		RespCode[idx].count = 0L;
		RespCode[idx].bytes = 0.0;
	}
	for (idx=0; idx < TABSIZE(ReqMethod); idx++) {
		ReqMethod[idx].count = 0L;
		ReqMethod[idx].bytes = 0.0;
	}
	return;
}

/*
** Create a table with the weekdays for this month.
** Note that in Germany a week starts at Monday, so
** we have to adjust the table entries for the values
** used by Unix time functions.
*/
static void mkdtab(LOGTIME * const lt) {
	size_t wday;
	size_t idx;
	time_t now;
	struct tm *tp;

	tp = localtime(&now);		/* get current time to compensate for timezone */
	tp->tm_mday = 1;		/* tick back to first day of month, 00:00:00 */
	tp->tm_mon = (int)lt->mon;
	tp->tm_year = (int)lt->year-1900;
	tp->tm_hour = tp->tm_min = tp->tm_sec = 0;

	(void) mktime(tp);		/* adjust tm_wday */

	if ((wday = (size_t)tp->tm_wday) == 0)	/* get first day of week, create table */
		wday = 6;
	else	wday--;			/* localization */

	for (idx=0; idx < TABSIZE(wdtab); idx++, wday++)
		wdtab[idx] = wday % 7;
	return;
}

/*
** Add an URL or a hostname to the list of ignored items.
*/
static void addIgnoredItem(int const which, char * const pfx) {
	size_t max, plen = 0;
	ITEM_LIST *ip;

	switch (which) {
	  default:		assert(which != which); return;	/*NOTREACHED*/
	  case IGNORED_SITES:	ip = ignored_sites; max = TABSIZE(ignored_sites); break;
	  case IGNORED_ITEMS:	ip = ignored_items; max = TABSIZE(ignored_items); break;
	}
	if (hidden[which].t_count == max) {
		if (!hidden[which].t_errmsg) {
			hidden[which].t_errmsg++;
			prmsg(1, "Table overflow in ignored list, some %s are ignored.\n",
				which == IGNORED_SITES ? "hosts" : "URLs");
		}
		return;
	}
	plen = strlen(pfx);
	if (!plen) {
		prmsg(1, "Can't add zero length URL/hostname to the list of ignored items?!?\n");
		return;
	}
	if (*pfx == '*' || *(pfx+plen-1) == '*')
		plen--;

	ip += hidden[which].t_count;
	ip->col = NULL;
	ip->len = plen;			/* save prefix */
	ip->pfx = pfx;
	hidden[which].t_count++;	/* remember no of items */
	return;
}

/*
** Extract domain name part to create hidden domains.
*/
static void saveDomainName(HSTRING * const host) {
	register char *tm;
	HSTRING nhost;

	nhost.len = host->len;
	nhost.str = tm = host->str;
	for (nhost.hval=0; *tm != '\0'; tm++)
		nhost.hval = (nhost.hval<<1) + (u_char)*tm;

	nhost.hval %= HIDELIST_SIZE;
	addHiddenName(HIDDEN_SITES, NULL, NULL, &nhost);
	return;
}

/*
** Extract user agent to create hidden agents.
*/
static void saveUserAgent(HSTRING * const uatype) {
	register size_t len = 0;
	char nbuf[MEDIUMSIZE];
	HSTRING nagent;
	
	for (len=0; len < sizeof(nbuf)-1 && len < uatype->len; len++)
		if ((nbuf[len] = uatype->str[len]) == '\0')
			break;

	nbuf[len] = '\0';
	nagent.len = len;
	nagent.str = nbuf;
	nagent.hval = uatype->hval;
	addHiddenName(HIDDEN_AGENTS, NULL, NULL, &nagent);
	return;
}

/*
** Extract hostname to create hidden referrer.
*/
static void saveReferHost(HSTRING * const rhost) {
	register size_t len = 0;
	char nbuf[MEDIUMSIZE];
	HSTRING nrefer;

	for (len=0; len < sizeof(nbuf)-1 && len < rhost->len; len++)
		if ((nbuf[len] = rhost->str[len]) == '\0')
			break;
	nbuf[len] = '\0';
	nrefer.len = len;
	nrefer.str = nbuf;
	nrefer.hval = rhost->hval;
	addHiddenName(HIDDEN_REFERS, NULL, NULL, &nrefer);
	return;
}

/*
** Add an item to a list of hidden items.
*/
#define MEM_CHUNK	1000

static void addHiddenName(size_t const which, char *pfx, char * const sref, HSTRING * const dsc) {
	size_t idx, plen=0;
	NLIST *np;

	if (pfx != NULL)
		plen = strlen(pfx);

	switch (which) {
	  default:		assert(which != which); return; /*NOTREACHED*/

	  case HIDDEN_SITES:	/*FALLTHROUGH*/
	  case HIDDEN_ITEMS:	if (plen && (*pfx == '*' || *(pfx+plen-1) == '*'))
					plen--;
				break;

	  case HIDDEN_AGENTS:	/*FALLTHROUGH*/
	  case HIDDEN_REFERS:	if (plen) {
					if (*pfx == '*') {
						pfx++;
						plen--;
					} else if (*(pfx+plen-1) == '*')
						pfx[--plen] = '\0';
				}
				break;
	}

	if (hidden[which].t_errmsg)	/* table overflow */
		return;

	if ((np = lookupItem(hlist[which], dsc, 0)) == NULL) {
		hidden[which].t_errmsg++;
		prmsg(2, "Not enough memory to add %s `%s'?!?\n",
			hidden[which].what, dsc->str);
		return;
	}
	if (!pfx && !np->ishidden)
		return;					/* known already */

	np->ishidden = 0;				/* make it known */
	if ((idx = hidden[which].t_count) == hidden[which].t_avail) {
		ITEM_LIST *newcore = !idx
				   ? (ITEM_LIST *)calloc(MEM_CHUNK, sizeof(ITEM_LIST))
				   : (ITEM_LIST *)realloc((void *)hidden[which].tab,
					(hidden[which].t_avail+MEM_CHUNK)*sizeof(ITEM_LIST));
		if (newcore != NULL) {
			hidden[which].t_avail += MEM_CHUNK;
			hidden[which].tab = newcore;
		} else {
			hidden[which].t_errmsg++;
			prmsg(1, "Not enough memory for %s list, some %ss are ignored.\n",
				hidden[which].what, hidden[which].what);
			return;
		}
	}

	hidden[which].tab[idx].col = np;			/* description */
	hidden[which].tab[idx].len = pfx ? plen : np->len;	/* length of prefix */
	hidden[which].tab[idx].pfx = pfx ? pfx  : np->str;	/* prefix (referrer host) */
	hidden[which].tab[idx].sref = sref ? strsave(sref) : sref; /* query string/self referal */
	hidden[which].t_count++;
	return;
}

/*
** Append image suffixes as defaults to the list of hidden items.
*/
static char *imglist[] = {
	"*.gif", "*.ief", "*.jpg", "*.jpeg", "*.pcd",
	"*.png", "*.rgb", "*.xbm", "*.xpm", "*.xwd",
	"*.tiff", "*.tif", NULL };

static void defHiddenImages(void) {
	HSTRING *hsp;
	char **img;

	hsp = hashedString(HIDELIST_SIZE, allimg);
	for (img=imglist; *img != NULL; img++)
		addHiddenName(HIDDEN_ITEMS, *img, NULL, hsp);
	return;
}

/*
** Set the default hidden referrer URLs.
*/
static void defHiddenRefers(void) {
	HSTRING *hsp;

	hsp = hashedString(HIDELIST_SIZE, "FILE REFERRER");
	addHiddenName(HIDDEN_REFERS, "about:", NULL, hsp);
	addHiddenName(HIDDEN_REFERS, "file:", NULL, hsp);
	hsp = hashedString(HIDELIST_SIZE, "USENET REFERRER");
	addHiddenName(HIDDEN_REFERS, "news:", NULL, hsp);
	hsp = hashedString(HIDELIST_SIZE, "FTP REFERRER");
	addHiddenName(HIDDEN_REFERS, "ftp:", NULL, hsp);
	hsp = hashedString(HIDELIST_SIZE, "MAILTO REFERRER");
	addHiddenName(HIDDEN_REFERS, "mailto:", NULL, hsp);
	return;
}
static size_t refn_urls = 0;
static size_t refn_max = 0;

/*
** Add self referrer names.
*/
static void addSelfRefer(char *str) {
	char tbuf[MEDIUMSIZE];
	HSTRING *morecore = NULL;
	size_t len = strlen(str);

	while (len != 0 && str[--len] == '/')
		str[len] = '\0';

	if (!strneq(str, "http://", 7) && !strneq(str, "https://", 8)) {
		while (*str == '/')
			str++;
		if (*str)
			(void) sprintf(tbuf, "http://%s", str);
	} else {
		tbuf[0] = '\0';
		(void) strncat(tbuf, str, sizeof(tbuf));
	}
	if ((str = strsave(tbuf)) == NULL) {
		prmsg(0, enomem, len);
		return;
	}
	if (!refn_max || refn_max == refn_urls) {
		if (!refn_max)
			morecore = (HSTRING *)malloc(10*sizeof(HSTRING));
		else	morecore = (HSTRING *)realloc((void *)ref_urls, (refn_max+10)*sizeof(HSTRING));

		if (morecore == NULL) {
			prmsg(0, enomem, (refn_max+10)*sizeof(HSTRING));
			return;
		}
		refn_max += 10;
		ref_urls = morecore;
	}
	ref_urls[refn_urls].str = str;
	ref_urls[refn_urls++].len = strlen(str);	/* may have changed meanwhile */
	refn_max += 10;
	return;
}

/*
** Check for self referrer.
*/
static int isSelfRefer(char *host, size_t hlen) {
	register char *cp, *tm;
	register size_t idx;

	if (!ref_urls || !refn_urls)
		return 0;

	for (idx=0; idx < refn_urls; idx++) {
		if (hlen == ref_urls[idx].len) {
			tm = ref_urls[idx].str+ref_urls[idx].len;
			cp = host+ref_urls[idx].len;
			do {
				--cp;
				if (*--tm != (is_upper(*cp) ? to_lower(*cp) : *cp))
					break;
			} while (tm > ref_urls[idx].str && cp > host);
			if (tm == ref_urls[idx].str && cp == host)
				return 1;
		}
	}
	return 0;
}

/*
** Check for ignored item. If prefix begins with `*', check only for
** a match of the suffix. If prefix ends with `*', check only for a
** match of the leading part, otherwise check for an exact match.
** We use case-independant comparison for URLs and sitenames.
*/
static int isIgnoredItem(int const which, HSTRING * const hsp) {
	register char *pfx;
	register size_t idx, plen;
	ITEM_LIST *tab;

	switch (which) {
	  default:		assert(which != which); return 0; /*NOTREACHED*/
	  case IGNORED_ITEMS:	tab = ignored_items;	break;
	  case IGNORED_SITES:	tab = ignored_sites;	break;
	}
	for (idx=0; idx < hidden[which].t_count; idx++) {
		pfx = tab[idx].pfx;
		plen = tab[idx].len;
		if (*pfx == '*') {			/* handle `*' prefix */
			if (hsp->len >= plen) {
				plen = hsp->len - plen;
				if (hsp->str[plen] == *++pfx && streq(hsp->str+plen, pfx))
					break;
			}
		} else if (*(pfx+plen) == '*') {	/* handle `*' suffix */
			if (hsp->len >= plen &&
			   *hsp->str == *pfx && strneq(hsp->str, pfx, plen))
				break;
		} else if (hsp->len == plen &&		/* exact match */
			  *hsp->str == *pfx && streq(hsp->str, pfx))
			break;
	}
	return idx != hidden[which].t_count;
}

/*
** Check for hidden item, collect data. If prefix begins with `*',
** check only for a match of the suffix. If prefix ends with `*',
** check only for a match of the leading part, otherwise check
** for an exact match.
*/
static int isHiddenItem(NLIST * const np) {
	register char *pfx;
	register size_t idx, plen;
	register ITEM_LIST *ip;

	ip = hidden[HIDDEN_ITEMS].tab;
	for (idx=0; idx < hidden[HIDDEN_ITEMS].t_count; idx++) {
		pfx = ip[idx].pfx;
		plen = ip[idx].len;
		if (*pfx == '*') {			/* handle `*' prefix */
			if (np->len >= plen) {
				plen = np->len - plen;
				if (*++pfx == np->str[plen] && !strcmp(pfx, np->str+plen))
				break;
			}
		} else if (*(pfx+plen) == '*') {	/* handle `*' suffix */
			if (np->len >= plen &&
			    *pfx == *np->str && !strncmp(pfx, np->str, plen))
				break;
		} else if (np->len == plen &&		/* exact match */
			   *pfx == *np->str && !strcmp(pfx, np->str))
			break;
	}
	if (idx == hidden[HIDDEN_ITEMS].t_count || ip[idx].col->str == NULL) {
		np->ishidden = -1;
		return 0;
	}
	ip[idx].col->count += np->count;
	ip[idx].col->nomod += np->nomod;
	ip[idx].col->bytes += np->bytes;
	ip[idx].col->ishidden = np->ishidden = (short)idx;	/* stamp it */
	return 1;
}
/*
** Check for hidden site.
*/
static int isHiddenSite(NLIST * const np) {
	register char *tm;
	register size_t dlen, plen;
	register int idx, rc, low, high;
	register ITEM_LIST *ip;

	idx = 0;
	tm = np->str;
	dlen = np->len;
	ip = hidden[HIDDEN_SITES].tab;
	if (hidden[HIDDEN_SITES].t_start) {		/* linear search for pre-defined domains */
		for ( ; idx < hidden[HIDDEN_SITES].t_start; idx++) {
			plen = ip[idx].len;
			if (*ip[idx].pfx == '*') {			/* handle `*' prefix */
				if (dlen >= plen && streq(ip[idx].pfx+1, tm+(dlen-plen)))
					break;
			} else if (*(ip[idx].pfx+plen) == '*') {	/* handle `*' suffix */
				if (dlen >= plen && strneq(ip[idx].pfx, tm, plen))
					break;
			} else if (dlen == plen && streq(ip[idx].pfx, tm))
				break;					/* exact match */
		}
	}
	if (idx == hidden[HIDDEN_SITES].t_start) {	/* binary search for rest of the list */
		if (np->sslen < dlen) {			/* sanity check */
			tm += np->sslen;
			dlen -= np->sslen;
		}
		low = hidden[HIDDEN_SITES].t_start;
		high = hidden[HIDDEN_SITES].t_count-1;

		while (low <= high) {
			idx = (low+high) / 2;
			if ((rc = strcasecmp(tm, ip[idx].pfx)) < 0)
				high = idx-1;
			else if (rc > 0)
				low = idx+1;
			else	break;
		}
		if (high < low) {
			np->ishidden = -1;
			return 0;
		}
	}
	if (ip[idx].col->str == NULL) {
		np->ishidden = -1;
		return 0;
	}
	ip[idx].col->count += np->count;
	ip[idx].col->nomod += np->nomod;
	ip[idx].col->bytes += np->bytes;
	ip[idx].col->ishidden = np->ishidden = (short)idx;	/* stamp it */
	return 1;
}

/*
** Check for hidden agent.
*/
static int isHiddenAgent(NLIST * const np) {
	register int idx, rc, low, high;
	register ITEM_LIST *ip;

	idx = 0;
	ip = hidden[HIDDEN_AGENTS].tab;
	if (hidden[HIDDEN_AGENTS].t_start) {		/* linear search for pre-defined agents */
		for ( ; idx < hidden[HIDDEN_AGENTS].t_start; idx++)
			if (np->len >= ip[idx].len && strneq(np->str, ip[idx].pfx, ip[idx].len))
				break;
	}
	if (idx == hidden[HIDDEN_AGENTS].t_start) {	/* binary search for rest of the list */
		if (!np->sslen) {			/* done already */
			np->ishidden = -1;
			return 0;
		}
		low = hidden[HIDDEN_AGENTS].t_start;
		high = hidden[HIDDEN_AGENTS].t_count-1;

		while (low <= high) {
			idx = (low+high) / 2;
			rc = strncmp(np->str, ip[idx].pfx, np->sslen);
			if (!rc && np->sslen < ip[idx].len)
				rc = -ip[idx].pfx[np->sslen];
			if (rc < 0)
				high = idx-1;
			else if (rc > 0)
				low = idx+1;
			else	break;
		}
		if (high < low) {
			np->ishidden = -1;
			return 0;
		}
	}
	if (ip[idx].col->str == NULL) {
		np->ishidden = -1;
		return 0;
	}
	ip[idx].col->count += np->count;
	ip[idx].col->nomod += np->nomod;
	ip[idx].col->bytes += np->bytes;
	ip[idx].col->ishidden = np->ishidden = (short)idx;	/* stamp it */
	return 1;
}

/*
** Check for hidden referrer.
*/
static int isHiddenRefer(NLIST * const np) {
	register int rc, idx, low, high;
	register ITEM_LIST *ip;

	idx = 0;
	ip = hidden[HIDDEN_REFERS].tab;
	if (hidden[HIDDEN_REFERS].t_start) {		/* linear search for pre-defined referrer URLs */
		for ( ; idx < hidden[HIDDEN_REFERS].t_start; idx++)
			if (np->len >= ip[idx].len && strneq(np->str, ip[idx].pfx, ip[idx].len))
				break;
	}
	if (idx == hidden[HIDDEN_REFERS].t_start) {
		if (!np->sslen) {			/* done already */
			np->ishidden = -1;
			return 0;
		}
		low = hidden[HIDDEN_REFERS].t_start;
		high = hidden[HIDDEN_REFERS].t_count-1;

		while (low <= high) {			/* binary search for rest of the list */
			idx = (low+high) / 2;
			rc = strncasecmp(np->str, ip[idx].pfx, np->sslen);
			if (!rc && np->sslen < ip[idx].len)
				rc = -ip[idx].pfx[np->sslen];
			if (rc < 0)
				high = idx-1;
			else if (rc > 0)
				low = idx+1;
			else	break;
		}
		if (high < low) {
			np->ishidden = -1;
			return 0;
		}
	}
	if (ip[idx].col->str == NULL) {			/* sanity check */
		np->ishidden = -1;
		return 0;
	}
	ip[idx].col->count += np->count;
	ip[idx].col->nomod += np->nomod;
	ip[idx].col->bytes += np->bytes;
	ip[idx].col->ishidden = np->ishidden = (short)idx;	/* stamp it */
	return 1;
}

/*
** Initialize list of hidden items.
*/
static void initHiddenItems(int const which) {
	int initval;
	size_t idx;
	ITEM_LIST *ip;

	switch (which) {
	  default:		assert(which != which); return; /*NOTREACHED*/
	  case HIDDEN_ITEMS:	initval = -1;
				break;

	  case HIDDEN_SITES:	/*FALLTHROUGH*/
	  case HIDDEN_AGENTS:	/*FALLTHROUGH*/
	  case HIDDEN_REFERS:	initval = 0;
				break;
	}

	if ((ip = hidden[which].tab) != NULL) {
		for (idx=0; idx < hidden[which].t_count; idx++) {
			ip[idx].col->ishidden = initval;
			ip[idx].col->nomod = 0L;
			ip[idx].col->count = 0L;
			ip[idx].col->ltick = 0L;
			ip[idx].col->bytes = 0.0;
		}
	}
	return;
}

/*
** Process arguments for hidden items, add them to the list.
*/
static void hideArgs(int const which, char *const s1, char *s2) {
	char *tm, nbuf[MEDIUMSIZE];
	size_t len;
	HSTRING *hsp;

	for (len=0; len < sizeof(nbuf)-1 && *s2 != '\0'; len++) {
		nbuf[len] = *s2++;
		if (nbuf[len] == ' ' && *s2 == '[')
			break;
	}
	nbuf[len] = '\0';
	if (*s2 == '[') {
		s2++;
		for (tm=s2; *tm && *tm != ']'; tm++)
			/* no-op */ ;
		if (*tm == ']')
			*tm = '\0';
	} else
		s2 = NULL;

	hsp = hashedString(HIDELIST_SIZE, nbuf);
	addHiddenName(which, s1, s2, hsp);
	return;
}


/*
** Read configuration file, set global variables.
*/
/* WARNING: the following macro evaluates it's argument more than once! */
#define SKIPWS(ptr)	while (*(ptr) == '\t' || *(ptr) == ' ') (ptr)++

static int readConfigFile(char * const filename) {
	char *missing = "Missing third value field in `%s' entry for `%s'.\n";
	char *skipmsg = "Skip invalid entry in file `%s', line %d: %s\n";
	char lbuf[LBUFSIZE]; 		/* line buffer */
	char *cp, *args[5];		/* entry from configuration file */
	size_t len, cnt;		/* line counter */
	FILE *cfp = fopen(filename, "r");

	if (cfp == NULL)
		return 0;

	for (cnt=1; fgets(lbuf, sizeof lbuf, cfp) != NULL; cnt++) {
		len = strlen(lbuf)-1;
		if (lbuf[len] == '\n')
			lbuf[len] = '\0'; 	/* delete trailing newline */

		cp = lbuf;
		SKIPWS(cp);
		if (*cp == '#' || *cp == '\0')
			continue;		/* ignore empty and comment lines */

		/* split the line into arguments */
		if ((len = (size_t)getargs(lbuf, args, TABSIZE(args))) < 2 || len > 3) {
			prmsg(1, skipmsg, filename, cnt, lbuf);
			continue;		/* skip invalid lines */
		}
		/* save value */
		if ((cp=strsave(args[1])) == NULL) {
			prmsg(2, "Too few memory for definitions from file `%s', line %d?!?\n",
				filename, cnt);
			exit(1);
		}
		if (streq(args[0], "ServerName")) {
			if (!srv_name)
				srv_name = cp;
			else	free(cp);
		} else if (streq(args[0], "ServerURL")) {
			if (!srv_url)
				srv_url = cp;
			else	free(cp);
		} else if (streq(args[0], "DocRoot")) {
			if (!doc_root)
				doc_root = cp;
			else	free(cp);
		} else if (streq(args[0], "DefaultMode")) {
			if (monthly == -1)
				monthly = (*cp == 'd' || *cp == 'D') ? 0 : 1;
			free(cp);
		} else if (streq(args[0], "LogFile") || streq(args[0], "HTTPLogFile")) {
			if (!log_file)
				log_file = cp;
			else	free(cp);
		} else if (streq(args[0], "LogFormat") || streq(args[0], "HTTPLogFormat")) {
			if (!log_format)
				log_format = cp;
			else	free(cp);
		} else if (streq(args[0], "Session")) {
			if (!time_win)
				time_win = cp;
			else	free(cp);
		} else if (streq(args[0], "OutputDir") || streq(args[0], "HTMLDir")) {
			if (!out_dir)
				out_dir = cp;
			else	free(cp);
		} else if (streq(args[0], "PrivateDir")) {
			if (!priv_dir)
				priv_dir = cp;
			else	free(cp);
		} else if (streq(args[0], "TLDFile")) {
			if (!tld_file)
				tld_file = cp;
			else	free(cp);
		} else if (streq(args[0], "VRMLProlog")) {
			if (!vrml_prlg)
				vrml_prlg = cp;
			else	free(cp);
		} else if (streq(args[0], "3DWindow") || streq(args[0], "VRMLWindow")) {
			vrml_win = (*cp == 'i' || *cp == 'I') ? INT_WIN : EXT_WIN;
			free(cp);
		} else if (streq(args[0], "NavWinSize") || streq(args[0], "NavWindow")) {
			if (sscanf(cp, "%d x %d", &navwid, &navht) != 2)
				navwid = navht = 0;	/* invalid */
			free(cp);
		} else if (streq(args[0], "3DWinSize") || streq(args[0], "VRMLWinSize")) {
			if (sscanf(cp, "%d x %d", &w3wid, &w3ht) != 2)
				w3wid = w3ht = 0;	/* invalid */
			free(cp);
		} else if (streq(args[0], "ReportTitle") || streq(args[0], "DocTitle")) {
			if (!doc_title)
				doc_title = cp;
			else	free(cp);
		} else if (streq(args[0], "HTMLPrefix") || streq(args[0], "HeadPrefix")) {
			html_str[HTML_HEADPFX] = cp;
			if (ISFULLPATH(cp)) { /* *cp == '/' */
				html_str[HTML_HEADPFX] = readHTML(args[0], cp);
				free(cp);
			}
		} else if (streq(args[0], "HTMLSuffix") || streq(args[0], "HeadSuffix")) {
			html_str[HTML_HEADSFX] = cp;
			if (ISFULLPATH(cp)) { /* *cp == '/' */
				html_str[HTML_HEADSFX] = readHTML(args[0], cp);
				free(cp);
			}
		} else if (streq(args[0], "HTMLTrailer") || streq(args[0], "DocTrailer")) {
			html_str[HTML_TRAILER] = cp;
			if (ISFULLPATH(cp)) { /* *cp == '/' */
				html_str[HTML_TRAILER] = readHTML(args[0], cp);
				free(cp);
			}
		} else if (streq(args[0], "HeadSize")) {
			if (head_size == -1)
				head_size = (int)strtol(cp, NULL, 10);
			free(cp);
		} else if (streq(args[0], "FontSize")) {
			if (font_size == -1)
				font_size = (int)strtol(cp, NULL, 10);
			free(cp);
		} else if (streq(args[0], "NavigFrame") || streq(args[0], "FrameSize")) {
			if (nav_frame == -1)
				nav_frame = (int)strtol(cp, NULL, 10);
			free(cp);
		} else if (streq(args[0], "IndexFiles") || streq(args[0], "HomePage")) {
			char *tm = strtok(cp, ", ");
			do {	/* name of additional index files */
				if (ipnum < TABSIZE(indexpg))
					indexpg[ipnum++].str = tm;
				else	prmsg(1, "Too many index filenames (max %u), ignore `%s'\n",
						ipnum, tm);
			} while ((tm=strtok(NULL, ", ")) != NULL);
		} else if (streq(args[0], "PageView")) {
			char *tm = strtok(cp, ", ");
			do {	/* name of additional pageview suffixes */
				if (ptnum < TABSIZE(pvsuffix))
					pvsuffix[ptnum++].str = tm;
				else	prmsg(1, "Too many pageview suffixes (max %u), ignore `%s'\n",
						ptnum, tm);
			} while ((tm=strtok(NULL, ", ")) != NULL);
		} else if (streq(args[0], "VirtualNames")) {
			char *tm = strtok(cp, ", ");
			do {
				addSelfRefer(tm);	/* additional hostnames for self referrer */
			} while ((tm=strtok(NULL, ", ")) != NULL);
		} else if (streq(args[0], "Suppress")) {
			char *tp = strtok(cp, ", ");
			do {
				if (streq(cp, "avload"))
					topn_day = topn_hrs = topn_min = topn_sec = 0;
				else if (streq(cp, "urls"))
					noitemlist++;
				else if (streq(cp, "hidden") || streq(cp, "urllist"))
					nofilelist++;
				else if (streq(cp, "code404"))
					noerrlist++;
				else if (streq(cp, "sites"))
					nositelist++;
				else if (streq(cp, "rsites"))
					nordomlist++;
				else if (streq(cp, "sitelist"))
					nohostlist++;
				else if (streq(cp, "agents"))
					noagentlist++;
				else if (streq(cp, "referrer"))
					noreferlist++;
				else if (streq(cp, "country"))
					nocntrylist++;
				else if (streq(cp, "interpol"))
					nointerpol++;
				else if (streq(cp, "hotlinks"))
					nohotlinks++;
				else if (streq(cp, "pageviews"))
					nopageviews++;
				else if (streq(cp, "graphics"))
					noimages++;
				else if (streq(cp, "timing")) {
					topn_day = topn_hrs = topn_min = topn_sec = 0;
					noimages = noitemlist = nofilelist = nositelist = 1;
					noagentlist = noreferlist = nocntrylist = nointerpol = 1;
					nohotlinks = timestats = 1;
				} else {
					prmsg(1, "Invalid value (%s) for `%s' directive in line %d\n",
						tp, args[0], cnt);
				}
			} while ((tp=strtok(NULL, ", ")) != NULL);
			free(cp);
		} else if (streq(args[0], "TopURLs")) {
			if (topn_urls == -1)
				topn_urls = (int)strtol(cp, NULL, 10);
			free(cp);
		} else if (streq(args[0], "LeastURLs") || streq(args[0], "LastURLs")) {
			if (lstn_urls == -1)
				lstn_urls = (int)strtol(cp, NULL, 10);
			free(cp);
		} else if (streq(args[0], "TopSites")) {
			if (topn_sites == -1)
				topn_sites = (int)strtol(cp, NULL, 10);
			free(cp);
		} else if (streq(args[0], "TopAgents")) {
			if (topn_agent == -1)
				topn_agent = (int)strtol(cp, NULL, 10);
			free(cp);
		} else if (streq(args[0], "NoiseLevel")) {
			if (noiselevel == -1)
				noiselevel = (int)strtol(cp, NULL, 10);
			free(cp);
		} else if (streq(args[0], "TopRefers")) {
			if (topn_refer == -1)
				topn_refer = (int)strtol(cp, NULL, 10);
			free(cp);
		} else if (streq(args[0], "TopDays")) {
			if (topn_day == -1)
				topn_day = (int)strtol(cp, NULL, 10);
			free(cp);
		} else if (streq(args[0], "TopHours")) {
			if (topn_hrs == -1)
				topn_hrs = (int)strtol(cp, NULL, 10);
			free(cp);
		} else if (streq(args[0], "TopMinutes")) {
			if (topn_min == -1)
				topn_min = (int)strtol(cp, NULL, 10);
			free(cp);
		} else if (streq(args[0], "TopSeconds")) {
			if (topn_sec == -1)
				topn_sec = (int)strtol(cp, NULL, 10);
			free(cp);
		} else if (streq(args[0], "RegInfo")) {
			if (args[2] != NULL)
				lic = getRegID(cp, args[2]);
			free(cp);
		} else if (strneq(args[0], "CustLogo", 8)) {
			if (args[2] == NULL) {
				prmsg(1, missing, args[0], args[1]);
				free(cp);
			} else if (args[0][8] == 'W') {	/* add customer's logos */
				buttons[BTN_CUSTOMW].name = cp;
				buttons[BTN_CUSTOMW].text = strsave(args[2]);
			} else {
				buttons[BTN_CUSTOMB].name = cp;
				buttons[BTN_CUSTOMB].text = strsave(args[2]);
			}
		} else if (streq(args[0], "HideURL")) {
			if (args[2] == NULL) {
				prmsg(1, missing, args[0], args[1]);
				free(cp);
			} else if (*cp != '*' && *cp != '/') { /* wrong syntax, skip */
				prmsg(1, "%s value in line %d (%s) must start with"
					 " either a '/' or '*'.\n", args[0], cnt, cp);
				free(cp);
			} else	 	/* append to list of hidden URLs */
				hideArgs(HIDDEN_ITEMS, cp, args[2]);
		} else if (streq(args[0], "HideSys")) {
			if (args[2] == NULL) {
				prmsg(1, missing, args[0], args[1]);
				free(cp);
			} else		/* append to list of hidden sites */
				hideArgs(HIDDEN_SITES, cp, args[2]);
		} else if (streq(args[0], "HideAgent") || streq(args[0], "AddAgent")) {
			if (args[2] == NULL) {
				prmsg(1, missing, args[0], args[1]);
				free(cp);
			} else		/* append to list of user agents */
				hideArgs(HIDDEN_AGENTS, cp, args[2]);
		} else if (streq(args[0], "HideRefer") || streq(args[0], "AddRefer")) {
			if (args[2] == NULL) {
				prmsg(1, missing, args[0], args[1]);
				free(cp);
			} else		/* append to list of referrers */
				hideArgs(HIDDEN_REFERS, cp, args[2]);
		} else if (strneq(args[0], "AddDom", 6)) {
			if (args[2] == NULL) {
				prmsg(1, missing, args[0], args[1]);
				free(cp);
			} else		/* append to list of countries */
				addTLD(cp, args[2]);
		} else if (streq(args[0], "IgnURL")) {	/* append to list of ignored items */
			char *tm = strtok(cp, ", ");
			do {
				addIgnoredItem(IGNORED_ITEMS, tm);
			} while ((tm=strtok(NULL, ", ")) != NULL);
		} else if (streq(args[0], "IgnSys")) {	/* append to list of ignored sites */
			char *tm = strtok(cp, ", ");
			do {
				addIgnoredItem(IGNORED_SITES, tm);
			} while ((tm=strtok(NULL, ", ")) != NULL);
		} else {			/* skip invalid lines */
			prmsg(1, skipmsg, filename, cnt, lbuf);
			free(cp);
		}
	}
	(void) fclose(cfp);
	return 1;
}
#undef SKIPWS

/*
** Read values from history file, initialize counters.
*/
static int readHistory(int const mflag, LOGTIME *const tp) {
	char *hname = "www-stats.hist";	/* name of history file */
	char lbuf[LBUFSIZE]; 		/* line buffer */
	COUNTER *mp, *dp;		/* ptr to monthly & daily COUNTER structure */
	int rc, newfmt = -1;
	u_short md, mn, yr, lmn = 0, lyr = 0;
	u_long hits, files, nomod, views, sessions, kbytes;
	float bytes;
	FILE *hfp;

	if ((hfp = fopen(hname, "r")) == NULL)
		return 0;

	while (fgets(lbuf, sizeof lbuf, hfp) != NULL) {
		if (newfmt < 0) {
			if (!strncmp(lbuf, "# WWW History 2.01", 18))
				newfmt = 2;	/* 2.0 including page views */
			else if (!strncmp(lbuf, "# WWW History 2.", 16))
				newfmt = 1;	/* 2.0 beta version */
			else	newfmt = 0;	/* 1.9e version */
			if (newfmt)
				continue;
		}
		if (lbuf[0] == '\n' || lbuf[0] == '#')
			continue;	/* skip empty and comment lines */

		if (newfmt) {
			if (2 == sscanf(lbuf, "LASTMON: %2hu/%4hu", &lmn, &lyr)) {
				if (--lmn > 11)
					lmn = 0;
				continue;
			}
			if (newfmt > 1)
				rc = (8 == sscanf(lbuf, "MON %hu/%hu: %lu %lu %lu %lu %lu %f",
					&mn, &yr, &hits, &files, &nomod, &views, &sessions, &bytes));
			else {
				rc = (7 == sscanf(lbuf, "MON %hu/%hu: %lu %lu %lu %lu %f",
					&mn, &yr, &hits, &files, &nomod, &sessions, &bytes));
				views = 0L;
			}
		} else {
			rc = (7 == sscanf(lbuf, "MON %hu/%hu: %lu %lu %lu %lu %lu",
				&mn, &yr, &hits, &files, &nomod, &sessions, &kbytes));
			bytes = 1024.0 * (float)kbytes;
		}
		if (rc) {
			mn--;
			assert(mn < (u_short)TABSIZE(monly));

			if ((yr == tp->year && mn == tp->mon) ||	/* skip current month */
			    (mn < tp->mon && yr != tp->year) ||		/* skip older/newer periods */
			    (mn > tp->mon && yr != tp->year-1))
				continue;

			mp = &monly[mn];
			mp->hits = hits;
			mp->files = files;
			mp->nomod = nomod;
			mp->views = views;
			mp->sessions = sessions;
			mp->bytes = bytes;
			continue;
		}
		if (mflag)			/* skip daily stats for monthly summary */
			continue;

		if (newfmt > 1)
			rc = (9 == sscanf(lbuf, "DAY %hu/%hu/%hu: %lu %lu %lu %lu %lu %f",
				&md, &mn, &yr, &hits, &files, &nomod, &views, &sessions, &bytes));
		else if (newfmt) {
			rc = (8 == sscanf(lbuf, "DAY %hu/%hu/%hu: %lu %lu %lu %lu %f",
				&md, &mn, &yr, &hits, &files, &nomod, &sessions, &bytes));
			views = 0L;
		} else {
			rc = (8 == sscanf(lbuf, "DAY %hu/%hu/%hu: %lu %lu %lu %lu %lu",
				&md, &mn, &yr, &hits, &files, &nomod, &sessions, &kbytes));
			bytes = 1024.0 * (float)kbytes;
		}
		if (rc) {
			md--;
			assert(md < (u_short)TABSIZE(daily));

			mn--;
			assert(mn < (u_short)TABSIZE(monly));

			if (mn != t.current.mon || yr != t.current.year)	/* skip expired data */
				continue;

			dp = &daily[md];
			total.hits  += (dp->hits = hits);
			total.files += (dp->files = files);
			total.nomod += (dp->nomod = nomod);
			total.views += (dp->views = views);
			total.sessions += (dp->sessions = sessions);
			total.bytes += (dp->bytes = bytes);

			tp->mday = md+2;
			tp->mon  = mn;
			tp->year = yr;
		}
	}
	(void) fclose(hfp);
	if (mflag == 2) {			/* save month/year of last run */
		tp->mon  = lmn;
		tp->year = lyr;
	}
	return 1;
}

/*
** Create or update the history file.
*/

static void writeHistory(int const mflag) {
	char *hname = "www-stats.hist";	/* name of history file */
	COUNTER *mp;			/* ptr to COUNTER structure */
	u_short sti;
	size_t idx;
	FILE *hfp;

	errno = 0;
	if ((hfp = fopen(hname, "w")) == NULL) {
		prmsg(1, enoent, hname, strerror(errno));
		return;
	}
	(void) fprintf(hfp, "# WWW History %s\n", version);

	if (!mflag)
		sti = t.end.mon;
	else {
		sti = t.end.mon+1;	/* update counter */
		mp = &monly[t.start.mon];
		mp->hits = total.hits;
		mp->files = total.files;
		mp->nomod = total.nomod;
		mp->views = total.views;
		mp->sessions = total.sessions;
		mp->bytes = total.bytes;
	}
	if (!mflag && t.end.mday > 1) {	/* save daily counters until last day done */
		(void) fprintf(hfp, "# DAILY COUNTERS for summary period 1-%d %3.3s %d\n",
			t.end.mday, monnam[t.end.mon], t.end.year);
		for (idx=0; idx < (u_int)t.end.mday-1; idx++) {
			mp = &daily[idx];
			(void) fprintf(hfp, "DAY %02d/%02d/%04d:\t%7lu %7lu %7lu %7lu %7lu %1.0f\n",
				idx+1, t.end.mon+1, t.end.year,
				mp->hits, mp->files, mp->nomod, mp->views, mp->sessions, mp->bytes);
		}
		mp = &daily[idx];
		(void) fprintf(hfp, "CUR %02d/%02d/%04d:\t%7lu %7lu %7lu %7lu %7lu %1.0f\n",
				idx+1, t.end.mon+1, t.end.year,
				mp->hits, mp->files, mp->nomod, mp->views, mp->sessions, mp->bytes);
		(void) fprintf(hfp, "checksum\t%7lu %7lu %7lu %7lu %7lu %1.0f\n\n",
			total.hits, total.files, total.nomod, total.views, total.sessions, total.bytes);
	}

	(void) memset((void *)&cksum, 0, sizeof cksum);
	cksum.bytes = 0.0;

	(void) fprintf(hfp, "# MONTHLY COUNTERS for the last 12 months\n");
	(void) fprintf(hfp, "LASTMON: %02hu/%04hu\n", t.end.mon+1, t.end.year);
	for (idx=0; idx < 12; idx++) {
		mp = &monly[idx];
		(void) fprintf(hfp, "MON %02d/%04d:\t%7lu %7lu %7lu %7lu %7lu %1.0f\n",
			idx+1, idx >= (size_t)sti ? (int)t.end.year-1 : (int)t.end.year,
			mp->hits, mp->files, mp->nomod, mp->views, mp->sessions, mp->bytes);
		cksum.hits += mp->hits;
		cksum.files += mp->files;
		cksum.nomod += mp->nomod;
		cksum.views += mp->views;
		cksum.sessions += mp->sessions;
		cksum.bytes += mp->bytes;
	}
	(void) fprintf(hfp, "checksum\t%7lu %7lu %7lu %7lu %7lu %1.0f\n\n",
		cksum.hits, cksum.files, cksum.nomod, cksum.views, cksum.sessions, cksum.bytes);

	(void) fclose(hfp);
	return;
}

/*
** Read in HTML file if name is given.
*/
static char *readHTML(char * const key, char * const filename) {
	char lbuf[LBUFSIZE]; 		/* line buffer */
	size_t nbytes;
	FILE *hfp;

	if ((hfp=fopen(filename, "r")) == NULL) {
		prmsg(1, "Can't open HTML %s file `%s'\n", key, filename);
		return NULL;
	}
	if ((nbytes=fread(lbuf, 1, sizeof(lbuf)-1, hfp)) <= 0) {
		prmsg(1, "HTML %s file `%s' unreadable or empty\n", key, filename);
		(void) fclose(hfp);
		return NULL;
	}
	lbuf[nbytes] = '\0';
	(void) fclose(hfp);
	return strsave(lbuf);		/* error condition checked elsewhere */
}