/* ==================================================================== * * Copyright (c) 2000-2004 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ==================================================================== * * This software was contributed by Covalent Technologies Inc, * http://www.covalent.net around April 20002. * * mod_specweb99.c -- Apache specweb99 module * sctemme July 2001 * * {sctemme | dirkx }@{ apache.org | covalent.net } */ #include "httpd.h" #include "http_core.h" #include "http_config.h" #include "http_log.h" #include "http_protocol.h" #include "http_main.h" #include "ap_config.h" #include "apr.h" #define APR_WANT_STRFUNC #include "apr_strings.h" #include "apr_lib.h" #include "apr_file_info.h" #include "apr_time.h" #include "apr_tables.h" #include "apr_buckets.h" #include "apr_uri.h" #include "mod_core.h" #if APR_HAVE_SYS_TYPES_H #include #endif #if APR_HAVE_UNISTD_H #include #endif #if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE) #include "unixd.h" #define MOD_SPECWEB_SET_MUTEX_PERMS /* XXX Apache should define something */ #endif /* Hardcoded lock file path for the Post log file mutex. Whether this file * will actually be created depends on the default global mutex implementation * that APR was compiled with. * * Global mutex structure pointer for Post log mutex. */ #define LOCKFILENAME "/tmp/specweb99_lockfile" apr_global_mutex_t *log_mutex; /* Note: version must be of the x.yy type - as it is * send over the http protocol wire; where x and y * are single 0-9 ascii digits :-). The name should * be an A-Za-z0-9 string. Which does not start with * a number :-) You want to avoid _ but '-' is not too * bad usually.. */ #define NAME "Specweb" #define VERSION "2.01" #include "mod_specweb99.h" module AP_MODULE_DECLARE_DATA specweb99_module; typedef struct specweb99_module_data specweb99_module_data; /* Module private data space - where we keep some per * (virtual?) server precalculated data */ struct specweb99_module_data { char *log_path; char *cadgen99; char *upfgen99; apr_time_t up_lastmod; apr_time_t check; char *up_path; apr_pool_t *up_pool; apr_int32_t *up; apr_uint16_t up_count; char *cad_path; apr_pool_t *cad_pool; struct cadrec *cad; apr_uint16_t cad_count; }; static const char boilerplate_start[] = BOILERPLATE_START; static const int boilerplate_start_len = sizeof(boilerplate_start) - 1; static const char boilerplate_end[] = BOILERPLATE_END; static const int boilerplate_end_len = sizeof(boilerplate_end) - 1; static apr_int16_t getCADFile(struct server_rec *sv, struct request_rec *r, struct specweb99_module_data * _my); /* Use interal locking - the main reason for doing * so is error trapping and being able to warn/info * when we spend a lot of time in camping on a lock. */ #define _rlock(s,r,fg,file) (_dolock(s,r,fg,APR_FLOCK_SHARED,file)) #define _wlock(s,r,fg,file) (_dolock(s,r,fg,APR_FLOCK_EXCLUSIVE,file)) /* generic locking * - when 'r' is passed - will do a timeout. * - when 's' is passed - do appropriate logging. * - when 'file' is passed - logging will be more meaningfull */ static int _dolock(struct server_rec *s, struct request_rec *r, apr_file_t * f, int type, char *file) { int e; /* * Rather than simply try-lock-and-wait - we first check * if a lock would block - and then set a timeout before * camping out on the lock. * */ if ((e = apr_file_lock(f, type | APR_FLOCK_NONBLOCK)) != APR_SUCCESS) { /* * XXX timeouts removed ! not sure how to do that in 2.0 */ if (s) { ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, e, s, "Camping out %s%s for a %s lock", file ? "on " : "", file ? file : "", ((type == APR_FLOCK_SHARED) ? "read" : "write")); } e = apr_file_lock(f, type); /* * XXXX clear timeout removed not sure how to do that in 2.0 */ } /* Trap both first/second flock() error. */ if (e) { if (s) { ap_log_error(APLOG_MARK, APLOG_ERR, e, s, "Failed to %s" "lock%s%s", ((type == APR_FLOCK_SHARED) ? "read" : "write"), ((file) ? ": " : ""), ((file) ? file : "")); } return -1; } return 0; } static char *returnHTMLPageHead(request_rec *r) { char *bp_head; /* Fill up the boilerplate with info */ bp_head = apr_psprintf(r->pool, boilerplate_start, ap_get_server_version(), ap_get_remote_host(r->connection, NULL, REMOTE_NOLOOKUP, NULL), r->uri, r->args ? r->args : ""); r->content_type = "text/html"; return bp_head; } static void returnHTMLPageWithBuffer(request_rec *r, char *buf, apr_size_t buflen) { char *bp_head; conn_rec *c = r->connection; apr_bucket_brigade *bb; apr_bucket *b1, *b2, *b3; apr_status_t rv; bp_head = returnHTMLPageHead(r); bb = apr_brigade_create(r->pool, c->bucket_alloc); b1 = apr_bucket_transient_create(bp_head, strlen(bp_head), c->bucket_alloc); b2 = apr_bucket_transient_create(buf, buflen, c->bucket_alloc); b3 = apr_bucket_immortal_create(boilerplate_end, boilerplate_end_len, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b1); APR_BRIGADE_INSERT_TAIL(bb, b2); APR_BRIGADE_INSERT_TAIL(bb, b3); APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(c->bucket_alloc)); rv = ap_pass_brigade(r->output_filters, bb); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "mod_specweb: ap_pass_brigade failed for buffer '%s'", buf); } } /* returnHTMLPageWithBuffer */ static void returnHTMLPageWithMessage(request_rec *r, char *fmt,...) { va_list args; char *m; va_start(args, fmt); m = apr_pvsprintf(r->pool, fmt, args); va_end(args); returnHTMLPageWithBuffer(r, m, strlen(m)); } static void returnHTMLPageWithFile(request_rec *r, char *fname) { char *bp_head; struct apr_finfo_t s; apr_file_t *f; apr_status_t rv; apr_bucket_brigade *bb; apr_bucket *b1, *b2, *b3; apr_off_t zero = 0; conn_rec *c = r->connection; bp_head = returnHTMLPageHead(r); b1 = apr_bucket_transient_create(bp_head, strlen(bp_head), c->bucket_alloc); rv = apr_file_open(&f, fname, APR_READ #ifdef APR_SENDFILE_ENABLED | APR_SENDFILE_ENABLED #endif , APR_OS_DEFAULT, r->pool); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, "Could not open file '%s' for reading", fname); returnHTMLPageWithMessage(r, "Error: could not open file for reading."); return; } if ((rv = apr_file_info_get(&s, APR_FINFO_SIZE, f)) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, "Could not stat file '%s' for reading", fname); returnHTMLPageWithMessage(r, "Error: Failed to stat the file"); return; } bb = apr_brigade_create(r->pool, c->bucket_alloc); b2 = apr_bucket_file_create(f, zero, (apr_size_t) (s.size), r->pool, c->bucket_alloc); b3 = apr_bucket_immortal_create(boilerplate_end, boilerplate_end_len, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b1); APR_BRIGADE_INSERT_TAIL(bb, b2); APR_BRIGADE_INSERT_TAIL(bb, b3); APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(c->bucket_alloc)); rv = ap_pass_brigade(r->output_filters, bb); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "mod_specweb: ap_pass_brigade failed for %s", fname); } } /* returnHTMLPageWithFile */ /*********************************************************************** * checkUPFile * ***********************************************************************/ static apr_int16_t checkUPFile(struct server_rec *sv, struct request_rec *r, struct specweb99_module_data * _my, time_t now) { apr_finfo_t s; apr_status_t rv; apr_file_t *f; apr_int16_t numrecords, up_uid; int e = 0; char up_record[UPRLENGTH + 1]; if (_my->check == now) { return 0; } _my->check = now; /* stat it, compare to stored stat */ rv = apr_stat(&s, _my->up_path, APR_FINFO_SIZE | APR_FINFO_MTIME, r->pool); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, sv, "Could not stat User.Personality file '%s'", _my->up_path); return 1; }; if (s.mtime == _my->up_lastmod) { return 0; } numrecords = s.size / UPRLENGTH; /* * Check buffer array for nullness and bigness, make if necessary. */ if ((_my->up == NULL) || (numrecords > _my->up_count)) { /* User personalities are only 32 bits (sad, really) */ apr_pool_clear(_my->up_pool); _my->up = apr_palloc(_my->up_pool, numrecords * sizeof(apr_uint32_t)); _my->up_count = numrecords; } /* * open the file, with memory from the request pool because we will * not need it very long. */ rv = apr_file_open(&f, _my->up_path, APR_READ, APR_OS_DEFAULT, r->pool); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, sv, "Could not open User.Personality file '%s'", _my->up_path); return 1; } if (_rlock(sv, r, f, _my->up_path)) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, sv, "Failed to lock User.Personality file '%s'", _my->up_path); apr_file_close(f); return 1; } /* Read every record, parse, put user demographics in array */ up_uid = 0; while (1) { int id, dem; apr_size_t l; rv = apr_file_read_full(f, up_record, UPRLENGTH, &l); if (rv != APR_SUCCESS) break; up_record[UPRLENGTH] = '\0'; if (sscanf(up_record, "%d %x", &id, &dem) != 2) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, sv, "corrupted entry in UP file"); e = 1; } if (up_uid != id) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, sv, "user id out of sync in UP file"); e = 1; } _my->up[up_uid] = dem; up_uid++; } if ((up_uid != numrecords) && (rv != APR_SUCCESS)) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, sv, "Failed read from User.Personality file '%s'", _my->up_path); e++; } if (apr_file_unlock(f) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, sv, "Failed to unlock User.Personality file '%s'", _my->up_path); e = 1; }; /* Close file */ apr_file_close(f); /* Store last modified date assuming no errors. */ if (e) { _my->up_lastmod = 0; return e; } _my->up_lastmod = s.mtime; /* * Since User.Profile changed, we need to get a new copy of * Custom.Ads as well */ return getCADFile(sv, r, _my); } /*********************************************************************** * getCADFile * ***********************************************************************/ static apr_int16_t getCADFile(struct server_rec *sv, struct request_rec *r, struct specweb99_module_data * _my) { apr_finfo_t s; apr_status_t rv; size_t numrecords; apr_file_t *f; char cadline[CADRLENGTH]; apr_uint16_t cad_uid; int e = 0; rv = apr_stat(&s, _my->cad_path, APR_FINFO_SIZE | APR_FINFO_MTIME, r->pool); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, sv, "Failed to stat CAD file '%s'", _my->cad_path); return 1; }; /* * Need to read file into memory - and re-allocate the array if the * size has changed. */ numrecords = s.size / CADRLENGTH; if (numrecords > _my->cad_count) { apr_pool_clear(_my->cad_pool); _my->cad = apr_palloc(_my->cad_pool, numrecords * sizeof(struct cadrec)); _my->cad_count = numrecords; } rv = apr_file_open(&f, _my->cad_path, APR_READ, APR_OS_DEFAULT, r->pool); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, sv, "Failed to open CAD file '%s'", _my->cad_path); return 1; }; if (_rlock(sv, r, f, _my->cad_path)) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, sv, "Failed to lock CAD file '%s'", _my->cad_path); return 1; } cad_uid = 0; while (cad_uid < numrecords) { int id, dem, adw, adm, exp; apr_size_t l; rv = apr_file_read_full(f, cadline, CADRLENGTH, &l); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, sv, "Failed to read from CAD file '%s'", _my->cad_path); e = 1; break; }; /* * Decode AD file (see specweb page ..) * * 0123456789.123456789.123456789.12345678 01234 01234567 01234567 * 012 0123456789n "%5d %8X %8X %3d %10d\n", Ad_id, * AdDemographics, Weightings, Minimum_Match_Value, * Expiration_Time * */ if (sscanf(cadline, "%d %x %x %d %d", &id, &dem, &adw, &adm, &exp) != 5) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, sv, "Entry CAD file corrupted"); continue; } if (cad_uid != id) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, sv, "Entry CAD file Id# out of sync"); continue; } _my->cad[cad_uid].addemographics = dem; _my->cad[cad_uid].gen_weightings = (adw & 0x00f0000) >> 16; _my->cad[cad_uid].age_weightings = (adw & 0x000f000) >> 12; _my->cad[cad_uid].reg_weightings = (adw & 0x0000f00) >> 8; _my->cad[cad_uid].int1_weightings = (adw & 0x00000f0) >> 4; _my->cad[cad_uid].int2_weightings = (adw & 0x000000f); _my->cad[cad_uid].minimum_match_value = adm; _my->cad[cad_uid].expiration_time = exp; cad_uid++; } if (apr_file_unlock(f) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, sv, "Failed to unlock the CAD file '%s'", _my->cad_path); e = 1; }; apr_file_close(f); return e; } static void *specweb99_server_create(apr_pool_t * p, server_rec *s) { struct specweb99_module_data *_my; _my = (struct specweb99_module_data *) apr_pcalloc(p, sizeof(struct specweb99_module_data)); _my->up_lastmod = (apr_time_t) 0L; _my->up = NULL; _my->cad = NULL; _my->up_count = 0; _my->cad_count = 0; return (void *) _my; } /* Get rid of the Post log mutex. This function is registered as * cleanup for the pool that we are passed when the parent is * initialized. */ static apr_status_t log_mutex_remove(void *data) { apr_global_mutex_destroy(log_mutex); log_mutex = NULL; return(0); } static int specweb99_module_init(apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp, server_rec *s) { apr_status_t rv; ap_add_version_component(p, NAME "/" VERSION); ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, 0, s, NAME "/" VERSION " module: Compiled on %s at %s", __DATE__, __TIME__); /* Create the Post log mutex, using the default global locking method that * APR was compiled with. */ rv = apr_global_mutex_create(&log_mutex, LOCKFILENAME, APR_LOCK_DEFAULT, p); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, "mod_specweb99: Parent could not create Post log mutex " "with file %s", LOCKFILENAME); return HTTP_INTERNAL_SERVER_ERROR; } /* This is defined at the top of this file and causes the permissions * function to not get called on platforms that don't require it. */ #ifdef MOD_SPECWEB_SET_MUTEX_PERMS rv = unixd_set_global_mutex_perms(log_mutex); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, "mod_specweb99: Parent could not set permissions " "on Post log mutex; check User and Group directives"); return HTTP_INTERNAL_SERVER_ERROR; } #endif /* MOD_SPECWEB_SET_MUTEX_PERMS */ apr_pool_cleanup_register(p, (void *)s, log_mutex_remove, apr_pool_cleanup_null); return OK; } static void specweb99_child_init(apr_pool_t * p, server_rec *s) { struct specweb99_module_data *_my = ap_get_module_config(s->module_config, &specweb99_module); struct request_rec r; const char *docroot; r.server = s; docroot = ap_document_root(&r); _my->check = 0; _my->up_path = ap_make_full_path(p, docroot, "User.Personality"); _my->cad_path = ap_make_full_path(p, docroot, "Custom.Ads"); _my->log_path = ap_make_full_path(p, docroot, "post.log"); _my->upfgen99 = ap_make_full_path(p, docroot, "upfgen99"); _my->cadgen99 = ap_make_full_path(p, docroot, "cadgen99"); if (apr_pool_create(&(_my->up_pool), p) != APR_SUCCESS) exit(APEXIT_CHILDFATAL); if (apr_pool_create(&(_my->cad_pool), p) != APR_SUCCESS) exit(APEXIT_CHILDFATAL); if (s->next) { fprintf(stderr, "WARNING- this specweb module currently does not support vhosts/services\n" "See %s:%d for what you need to change. The server will continue and assume\n" "the config of the base server\n", __FILE__, __LINE__ + 2); /* * Right now we assume you are specwebbing a whole server install - * as opposed to a host:port:protocol instance tied to a virtual * service. * * To support vhosts - the _my module config needs simply to be moved to * the per server config block (or the per dir block) and the init * and/or any access to it need to either go through the ->nxt list * OR carefull overlay merging needs to be done to a sensible default * for each of the cases. The current simplistic 'docroot' references * are propably no longer going to work and will need explicit config * (e.g. think ~user and other redirect cases with clobber the * concept of a docroot). */ }; /* Re-open the Post log mutex for this child. I assume this does the * initialization of the intra-process part of the global mutex. */ if (apr_global_mutex_child_init(&log_mutex, LOCKFILENAME, p) != APR_SUCCESS) exit(APEXIT_CHILDFATAL); } /* specweb99_child_init */ static int do_housekeeping(request_rec *r) { struct specweb99_module_data *_my = ap_get_module_config(r->server->module_config, &specweb99_module); apr_file_t *f; char *data, *key, *val, *line = ""; apr_table_t *tab; apr_status_t rv; const char *maxload, *maxthread, *pointtime, *urlroot; char *exp, *rootdir, *saveargs; char *cmd1, *cmd2, *c; int cmd1res, cmd2res; const char *docroot = ap_document_root(r); apr_uri_t urlrootrec; /* To parse the urlroot string into */ /* we already know args starts with "command/", so skip over that */ if (!strncmp(r->args + 8, "Fetch", 5)) { returnHTMLPageWithFile(r, _my->log_path); return OK; } else if ((data = strstr(r->args + 8, "Reset"))) { /* * We are sleeping at least one second - to make sure that any * fstat() on mtime will actually yield different values - no matter * how closely spaced the Reset's are issued. (in particular the * spacing between the test reset from the manager and the reset at * the commencing - which normally can be within a second - thus * having identical mtime's on platforms with second granularity * (Solaris,Linux). */ apr_sleep(2 * APR_USEC_PER_SEC); /* * 1 * 012345678901234 * command/Reset&maxload=[MaxLoad]&pttime=[PointTime]&maxthreads=[ * MaxThreads]&exp=[ExpiredList]&urlroot=[UrlRoot] */ data += 6; /* position at start of argument string */ /* Tokenize argument string */ tab = apr_table_make(r->pool, 0); while (*data && (val = ap_getword(r->pool, (const char **) &data, '&'))) { key = ap_getword(r->pool, (const char **) &val, '='); ap_unescape_url(key); ap_unescape_url(val); apr_table_set(tab, key, val); } /* Put arguments in variables */ maxload = apr_table_get(tab, "maxload"); pointtime = apr_table_get(tab, "pttime"); /* * The Run Rules pseudocode is ambivalent about this token name: the * pseudocode says 'maxthreads' but its test command a couple of * lines down says 'maxthread'. Aside from the question whether we * should at all pay attention to the token names, I'm going along * with what the manager script sends which is 'maxthread'. */ maxthread = apr_table_get(tab, "maxthread"); /* * OK, this vexes me. Every shred of documentation about SPECWeb * speaks of a comma-separated list of expired ads, but the cadgen99 * program segfaults if you pass anything but a whitespace- separated * list. The Run Rules explicitly state that the pseudo code is the * definitive Reference By Which This Module Shall Be Coded, yet I * had to yank the following gem from the perl script: */ exp = apr_pstrdup(r->pool, apr_table_get(tab, "exp")); for (c=exp; *c; c++) { if (*c == ',') { *c = ' '; } } urlroot = apr_table_get(tab, "urlroot"); /* * Prep: we got a URI from the request. Need to parse that, extract * the local part and tack that onto docroot. */ rv = apr_uri_parse(r->pool, urlroot, &urlrootrec); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_NOERRNO, rv, r->server, "The URL Root '%s' was invalid", urlroot); returnHTMLPageWithMessage(r, "The UrlRoot passed was invalid"); return OK; } if (!urlrootrec.path) { urlrootrec.path = ""; } rootdir = ap_os_escape_path(r->pool, ap_make_full_path(r->pool, docroot, urlrootrec.path), 0); /* Call upfgen and cadgen */ /* * Keep request arguments around, we need them for eventual response */ saveargs = apr_pstrdup(r->pool, r->args); cmd1 = apr_psprintf(r->pool, "%s -C %s -n %s -t %s", _my->upfgen99, docroot, maxload, maxthread); if((cmd1res = system(cmd1)) != 0) ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, 0, r->server, "Call failed %d=%s",cmd1res,cmd1); else ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, 0, r->server, "Called %s",cmd1); cmd2 = apr_psprintf(r->pool, "%s -C %s -e %s -t %s %s", _my->cadgen99, docroot, pointtime, maxthread, exp); cmd2res = system(cmd2); if((cmd1res = system(cmd2)) != 0) ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, 0, r->server, "Call failed %d=%s",cmd2res,cmd2); else ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, 0, r->server, "Called %s",cmd2); r->args = saveargs; /* * Reset post.log i.e. Truncate, open for writing */ rv = apr_file_open(&f, _my->log_path, APR_WRITE | APR_CREATE | APR_TRUNCATE, APR_OS_DEFAULT, r->pool); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, "Could not open post.log '%s' for writing", _my->log_path); returnHTMLPageWithMessage(r, "Error: couldn't open post.log for writing."); return OK; } line = apr_psprintf(r->pool, "%10d\n", 0); rv = apr_file_write_full(f, line, strlen(line), NULL); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Could not write to post.log '%s'", _my->log_path); returnHTMLPageWithMessage(r, "Error: could not write to post.log."); } else { returnHTMLPageWithMessage(r, "%s\n%d\n%s\n%d\n", cmd1,cmd1res,cmd2,cmd2res); } apr_file_close(f); return OK; } /* Reset Command */ /* Fall through */ returnHTMLPageWithMessage(r, "Error: unrecognized command '%s'", r->args); return OK; } /*********************************************************************** * do_standard_get * ***********************************************************************/ static int do_standard_get(request_rec *r) { char *path; const char *docroot = ap_document_root(r); /* * Construct the path to our file. Note that using ap_document_root() is * not senang. I should do this a subrequest but OTOH that would take * time and we don't have time. */ path = ap_make_full_path(r->pool, docroot, r->args); returnHTMLPageWithFile(r, path); return OK; } /*********************************************************************** * customadscan * ***********************************************************************/ static void customadscan(request_rec *r, char *fname, apr_int16_t adid) { struct apr_finfo_t s; apr_status_t rv; apr_int16_t i; apr_file_t *f; apr_size_t len, l; char *buf; char *index, *N, *X, *Y; N = apr_psprintf(r->pool, "%05d", adid / 36); X = apr_psprintf(r->pool, "%1d", (adid % 36) / 9); Y = apr_psprintf(r->pool, "%1d", adid % 9); rv = apr_file_open(&f, fname, APR_READ, APR_OS_DEFAULT, r->pool); if (rv != APR_SUCCESS) return; if ((rv = apr_file_info_get(&s, APR_FINFO_SIZE, f)) != APR_SUCCESS) return; len = s.size; buf = apr_palloc(r->pool, len + 1); /* On systems with mmap, it might be faster to mmap the file, scan the mmap, * then send down mmap buckets for the unaltered pieces of the the file. * oprofile shows a lot of samples in file_read_actor in the Linux kernel. */ if (((rv = apr_file_read_full(f, buf, len, &l)) != APR_SUCCESS) || (l != len)) return; /* Error on read */ buf[len] = '\0'; /* Null terminate it so the strstr will * halt */ index = buf; /* * It says in the run rules that we are to scan until the end of the * file... what if there are more than one occurrence of the ad (common * disease on todays web pages)? */ while ((index = strstr(index, MARKER)) != NULL) { /* This lands us a new index */ /* * 01234567890123456789012345678901234567890123456789 * 1 2 3 4 */ for (i = 0; i < 5; i++) { *(index + 34 + i) = N[i]; } *(index + 45) = *X; *(index + 47) = *Y; index += 50; /* Put the index past this marker, continue * scanning */ } returnHTMLPageWithBuffer(r, buf, len); } /* customadscan */ /*********************************************************************** * do_cadget * ***********************************************************************/ static int do_cadget(request_rec *r, int my_user, int last_ad, time_t now) { struct specweb99_module_data *_my = ap_get_module_config(r->server->module_config, &specweb99_module); char *cookie_out; const char *docroot = ap_document_root(r); char *filename; apr_int16_t userindex, adindex, expired = 0; apr_uint32_t userdemographics, combineddemographics; /* it's a bitmap */ apr_uint16_t ad_weight; apr_time_t sleep_time = 50000; /* initial sleep time (microseconds) if * getCADfile is running on another thread */ /* * XXX Again, ap_document_root is deprecated. I should probably find the * document root in my init handler and keep it around. */ filename = ap_make_full_path(r->pool, docroot, r->args); #ifdef DEBUG specweb99_debug(r->server, apr_psprintf(r->pool, "Full path is '%s'", filename)); #endif /* * Calculate UserIndex into User.Personality file UserIndex = MyUser - * 10000 */ userindex = my_user - 10000; /* * Find User.Personality record using UserIndex */ if (checkUPFile(r->server, r, _my, now)) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server, "User personality check failed."); returnHTMLPageWithMessage(r, "Error: User personality file check failed."); return HTTP_INTERNAL_SERVER_ERROR; } if (userindex < 0 || userindex >= _my->up_count) { /* Couldn't find it, so let's make our mark and leave */ #ifdef DEBUG specweb99_debug(r->server, "User record not found"); #endif returnHTMLPageWithMessage(r, "User Record %d not found (out of my current range %d .. %d)", userindex + 10000, 10000, _my->up_count + 10000 - 1); ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server, "User Record %d not found (out of my current range %d .. %d)", userindex + 10000, 10000, _my->up_count + 10000 - 1); return OK; } userdemographics = _my->up[userindex]; adindex = (last_ad + 1) % 360; while (!_my->cad) { /* we are probably running threaded on a SMP and another thread is * in getCADfile. Hang out for a while rather than seg fault. * Parsing the cadfile & upfile into shared memory at the end of * command/Reset is better long term. */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "do_cadget: sleeping for %.2f seconds", (double)sleep_time/1000000); apr_sleep(sleep_time); sleep_time += sleep_time; } /* * Do For Each Ad in Custom.Ads starting where Ad_index == Ad_id */ while (1) { /* CombinedDemographics = ( AdDemographics & UserDemographics ) */ combineddemographics = (_my->cad[adindex].addemographics) & userdemographics; /* Ad_weight = 0 */ ad_weight = 0; if (combineddemographics & GENDER_MASK) { ad_weight += _my->cad[adindex].gen_weightings; } if (combineddemographics & AGE_GROUP_MASK) { ad_weight += _my->cad[adindex].age_weightings; } if (combineddemographics & REGION_MASK) { ad_weight += _my->cad[adindex].reg_weightings; } if (combineddemographics & INTEREST1_MASK) { ad_weight += _my->cad[adindex].int1_weightings; } if (combineddemographics & INTEREST2_MASK) { ad_weight += _my->cad[adindex].int2_weightings; } if (ad_weight >= _my->cad[adindex].minimum_match_value) { break; } adindex = (adindex + 1) % 360; if (adindex == last_ad) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server, "Ad to expire not found"); break; } } expired = (now > _my->cad[adindex].expiration_time) ? 1 : 0; ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, 0, r->server, "Found ad %d : expire %s (%d > %d)", adindex, expired ? "yes" : "no", (int) now, _my->cad[adindex].expiration_time); cookie_out = apr_psprintf(r->pool, "found_cookie=Ad_id=%d&Ad_weight=%d&Expired=%d", adindex, ad_weight, expired); apr_table_setn(r->headers_out, "Set-Cookie", cookie_out); if ((strstr(filename, "class1") != NULL) || (strstr(filename, "class2") != NULL)) { customadscan(r, filename, adindex); } else { returnHTMLPageWithFile(r, filename); } return OK; } static char *_log_and_write(struct request_rec *r, apr_file_t * f, char *filename, const char *urlroot, int dirnum, int classnum, int filenum, int clientnum, int uid, time_t stamp) { pid_t pid; apr_uint32_t recnum; char recnumstr[12]; /* ten wide plus return plus \0 */ apr_size_t l; apr_off_t zero = 0; apr_status_t rv; pid = getpid(); if ((rv = apr_file_read_full(f, recnumstr, 11, &l)) != APR_SUCCESS) return "Failed to read recordcount from post.log"; recnumstr[11] = '\0'; recnum = atol(recnumstr) + 1; if ((rv = apr_file_seek(f, APR_SET, &zero)) != APR_SUCCESS) return "Failed to seek 0 to post.log"; if ((rv=(apr_file_printf(f, "%10d", recnum))) < 0) return "Failed to write num to post.log"; if ((rv = apr_file_seek(f, APR_END, &zero)) != APR_SUCCESS) return "Failed to seek end to post.log"; if ((apr_file_printf (f, "%10d %10d %10d %5d %2d %2d %10d %-60.60s %10d %10d\n", recnum, (int) stamp, (int) pid, dirnum, classnum, filenum, clientnum, filename, (int) pid, uid)) < 0) return "Failed to write record to post.log"; apr_file_flush(f); return NULL; } static int do_post(request_rec *r, int uid, time_t now) { struct specweb99_module_data *_my = ap_get_module_config(r->server->module_config, &specweb99_module); const char *urlroot = ""; int dirnum = 0, classnum = 0, filenum = 0, clientnum = 0; char *filename; int posterr; apr_file_t *f; char *data = ""; const char *type, *docroot; char argsbuffer[HUGE_STRING_LEN]; int rsize, len_read, rpos = 0; long length = 0; apr_status_t rv, rv2; docroot = ap_document_root(r); /*Begin:*/ /* Make substitutions in HTML return page for the following:*/ /* Server_Software*/ /* Remote_Addr*/ /* Script_Name*/ /* QueryString*/ /* The above is done in the returnHTMLPageWith... functions */ /* Parse PostInput - a sample format is as follows */ /* (keys may be received in any order):*/ /* urlroot=[urlroot]&dir=[Dir#]&class=[Class#]&num=[File#]&client=[Client#]*/ type = apr_table_get(r->headers_in, "Content-Type"); /* * Scream in protest if the user uses the broken version of SPECWeb99 * manager that doesn't send the Content-Type header. Note that this only * affects the pre-run tests: the regular client does send the header. */ if ((type == NULL) || (strcasecmp(type, DEFAULT_ENCTYPE) != 0)) { ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, r->server, "The client didn't send %s as Content-Type. Version " "1.02 of the SPECWeb does not do this and thus violates " "the HTTP specification. Please apply the following " "patch to your manager script and bitch to SPEC that " "they fix this:\n%s", DEFAULT_ENCTYPE, SPEC_MANAGER_PATCH); return HTTP_INTERNAL_SERVER_ERROR; } if (ap_setup_client_block(r, REQUEST_CHUNKED_ERROR) != OK) { ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, 0, r->server, "Could not setup client block"); returnHTMLPageWithMessage(r, "Couldn't set up client block"); return HTTP_INTERNAL_SERVER_ERROR; } if (!ap_should_client_block(r)) { ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, 0, r->server, "No POST data"); returnHTMLPageWithMessage(r, "No POST data"); return HTTP_INTERNAL_SERVER_ERROR; } length = r->remaining; data = apr_pcalloc(r->pool, length + 1); while ((len_read = ap_get_client_block(r, argsbuffer, sizeof(argsbuffer))) > 0) { if ((rpos + len_read) > length) { rsize = length - rpos; } else { rsize = len_read; } memcpy((char *) data + rpos, argsbuffer, rsize); rpos += rsize; } data[length] = '\0'; posterr = 5; /* Counter to make sure we get all variables * from the CGI post */ while (data) { const char *p = data; data = index(p, '&' /* 0x26 */ ); if (data != NULL) *data++ = '\0'; if (strncmp(p, "urlroot=", 8) == 0) { urlroot = apr_pstrdup(r->pool, p + 8); posterr--; } else if (strncmp(p, "dir=", 4) == 0) { dirnum = atoi(p + 4); posterr--; } else if (strncmp(p, "class=", 6) == 0) { classnum = atoi(p + 6); posterr--; } else if (strncmp(p, "num=", 4) == 0) { filenum = atoi(p + 4); posterr--; } else if (strncmp(p, "client=", 7) == 0) { clientnum = atoi(p + 7); posterr--; } } if (posterr != 0) { ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, 0, r->server, "Did not get all POST arguments"); returnHTMLPageWithMessage(r, "Did not get all POST arguments"); return HTTP_INTERNAL_SERVER_ERROR; } /* Filename = [urlroot]/dir[5-digit Dir#]/class[Class#]_[File#]*/ /* (for example, the POST input of */ /* urlroot=/specweb99/file_set&dir=00123&class=1&num=1&client=10003 */ /* would make Filename = /specweb99/file_set/dir00123/class1_1)*/ filename = ap_make_full_path(r->pool, docroot, apr_psprintf(r->pool, "%s/dir%05d/class%1d_%1d", urlroot, dirnum, classnum, filenum)); /* Do_atomically (for example, using a file lock or other mutex): */ if ((rv = apr_file_open(&f, _my->log_path, APR_READ | APR_WRITE, APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, "Failed to open post.log '%s' for updating", _my->log_path); returnHTMLPageWithMessage(r, "Failed to open post.log file for updating"); return HTTP_INTERNAL_SERVER_ERROR; } if ((rv = apr_global_mutex_lock(log_mutex)) != APR_SUCCESS) { returnHTMLPageWithMessage(r, "Failed to lock post.log file"); } else { char *msg = _log_and_write(r, f, filename, urlroot, dirnum, classnum, filenum, clientnum, uid, now); if (msg) { rv = APR_OS_START_USEERR; ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, msg); returnHTMLPageWithMessage(r, msg); } } if ((rv2 = apr_global_mutex_unlock(log_mutex)) != APR_SUCCESS) { if (rv == APR_SUCCESS) { rv = rv2; ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, "Failed to unlock %s", filename ? filename : ""); returnHTMLPageWithMessage(r, "Failed to lock unpost.log file"); } } apr_file_close(f); if (rv != APR_SUCCESS) return HTTP_INTERNAL_SERVER_ERROR; /* _log_and_write() will have * displayed a page already */ /* * CookieString = "my_cookie=" * * XXX seems from code inspection that this really is not setting again * the cookie - but a change the cookie is to be set to %d of the user * number. */ apr_table_setn(r->headers_out, "Set-Cookie", apr_psprintf(r->pool, "my_cookie=%d", uid)); /* * Return HTML Page with File='RootDir/FileName' and Cookie=CookieString */ returnHTMLPageWithFile(r, filename); return OK; } /* do_post */ static int specweb99_quick_handler(request_rec *r, int lookup) { const char *cookie_in; if (!((strlen(r->uri) == 1) && /* dynamic uri is "/" */ (r->args || /* dynamic GET must have args */ r->method_number == M_POST))) { /* but not POST */ return DECLINED; } cookie_in = apr_table_get(r->headers_in, "Cookie"); if (cookie_in) { int user_id, last_ad; char *end; time_t cur_time; #ifdef DEBUG specweb99_debug(r->server, apr_psprintf(r->pool, "Got a cookie: %s", cookie_in)); #endif /* * get the current time in seconds. This assumes the division done * by the macro is cheaper than a time() syscall (not verified) */ cur_time = apr_time_sec(r->request_time); /* * Parse Cookie string into MyUser and Last_Ad(cadget). The format of the * cookie is as follows (the order of keys and values is fixed): * 1 2 3 4 5 * 012345678901234567890123456789012345678901234567890123456789 * my_cookie=user_id=[MyUser]&last_ad=[Last_ad] */ user_id = strtol(cookie_in + 18, &end, 10); if (r->method_number == M_GET) { last_ad = atoi(end + 9); /* We trust that there is something behind * the last_ad value to stop the conversion */ return do_cadget(r, user_id, last_ad, cur_time); } return do_post(r, user_id, cur_time); } if (r->args) { if (!strncmp(r->args, "command/", 8)) { return do_housekeeping(r); } return do_standard_get(r); } else { /* no cookie, no args, but it's our URI, uh oh... * this isn't coming from the SPECweb99 client! */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "probable non-SPECweb99 request received:" " %s for URI %s with no cookie and no args", r->method, r->uri); return DECLINED; } } /* specweb99_quick_handler */ static void register_hooks(apr_pool_t * p) { ap_hook_post_config(specweb99_module_init, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_child_init(specweb99_child_init, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_quick_handler(specweb99_quick_handler, NULL, NULL, APR_HOOK_MIDDLE); } module AP_MODULE_DECLARE_DATA specweb99_module = { STANDARD20_MODULE_STUFF, NULL, /* dir config creater */ NULL, /* dir merger --- default is to override */ specweb99_server_create,/* server config */ NULL, /* merge server config */ NULL, /* command apr_table_t */ register_hooks /* register hooks */ };