/* * 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 */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SOLARIS2 #include #endif #ifndef LOCK_SH /* normally in sys/file.h (in ucbinclude on solaris 2)*/ #define LOCK_SH 1 /* shared lock */ #define LOCK_EX 2 /* exclusive lock */ #define LOCK_NB 4 /* don't block when locking */ #define LOCK_UN 8 /* unlock */ #endif #include "httpd.h" #include "http_core.h" #include "http_config.h" #include "http_log.h" #include "http_protocol.h" #include "http_main.h" #include "util_script.h" #include "ap_config.h" /* Set to augment fstat() based 'reset' checks. As per run rules * the file is still stat()-ed. #define SEMCNTR */ #ifdef SEMCNTR #include #include #define SEMKEY (5365200) typedef union semun { int val; struct semid_ds *buf; unsigned short *array; } semun; #endif /* 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 "1.00" #include "mod_specweb99.h" module MODULE_VAR_EXPORT specweb99_module; enum ltype { SHLOCK, EXLOCK, UNLOCK }; /* * Global variable: pointer to structure that holds pointers * to share memory buffers for user profiles and custom ad data. */ MM *globalBuffer; bufs *rec_buffer; #define _rlock(s,r,fg,file) (_dolock(s,r,fg,SHLOCK,file)) #define _wlock(s,r,fg,file) (_dolock(s,r,fg,EXLOCK,file)) #define _unlock(s,r,fg,file) (_dolock(s,r,fg,UNLOCK,file)) /* on some OS-es (linux) fwrite(2c)/fwrite(2c) seem * to be able to ruturn with EINTR. */ #define _WINTR(x) { while(((x)<0) && (errno == EINTR)) { /* NOP */ }; } static int _lockop(int fd, enum ltype type, int noblock) { int e; #ifdef SOLARIS2 struct flock lock; if (type == EXLOCK) lock.l_type = F_WRLCK; else if (type == SHLOCK) lock.l_type = F_RDLCK; else lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; /* * On Solaris and HPUX - fcntl returns -1 on error and just about any * other value on success. I.e. not always 0. */ _WINTR(e = (fcntl(fd, (noblock) ? F_SETLK : F_SETLKW, &lock))); if (e == -1) { if ((errno = EAGAIN) && (noblock)) return -2; return e; } return 0; #else int lock_type; if (type == SHLOCK) lock_type = LOCK_SH; if (type == EXLOCK) lock_type = LOCK_EX; else lock_type = LOCK_UN; if (noblock) lock_type |= LOCK_NB; _WINTR(e = flock(fd, lock_type)); if (e) { if ((errno == EWOULDBLOCK) && (noblock)) return -2; else return -1; } #endif return 0; } /* 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, FILE *f, enum ltype type, char *file) { int fd = fileno(f); 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 = _lockop(fd, type, 1)) == -2) { if (r) ap_hard_timeout("specweb lock", r); if (s) ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, s, "Camping out %s%s for a %s lock", file ? "on " : "", file, ((type == EXLOCK) ? "write" : ((type == SHLOCK) ? "read" : "un"))); e = _lockop(fd, type, 0); if (r) ap_kill_timeout(r); } /* Trap both first/second flock() error. */ if (e) { if (s) ap_log_error(APLOG_MARK, APLOG_ERR, s, "Failed to %s" "lock%s%s", ((type == EXLOCK) ? "write " : ((type == SHLOCK) ? "read " : "un")), file ? ": " : "", file); return -1; } return 0; } /*********************************************************************** * returnHTMLPageWithFile * ***********************************************************************/ static char *returnHTMLPageHead(request_rec *r, int len) { char *bp_head; /* Fill up the boilerplate with info */ bp_head = ap_psprintf(r->pool, BOILERPLATE_START, ap_get_server_version(), ap_get_remote_host(r->connection, NULL, REMOTE_NOLOOKUP), r->uri, r->args ? r->args : ""); ap_set_content_length(r, strlen(bp_head) + strlen(BOILERPLATE_END) + len); r->content_type = "text/html"; ap_send_http_header(r); return bp_head; }; /*********************************************************************** * returnHTMLPageWithMessage * ***********************************************************************/ static void returnHTMLPageWithMessage(request_rec *r, char *fmt,...) { va_list args; char *m; va_start(args, fmt); m = ap_pvsprintf(r->pool, fmt, args); va_end(args); returnHTMLPageWithBuffer(r, m); } /* returnHTMLPageWithMessage */ /*********************************************************************** * returnHTMLPageWithBuffer * ***********************************************************************/ static void returnHTMLPageWithBuffer(request_rec *r, char *buf) { char *bp_head; if (buf == NULL) { returnHTMLPageWithMessage(r, "Error: tried to send buffer but it is NULL"); return; }; bp_head = returnHTMLPageHead(r, strlen(buf)); if (r->header_only) return; ap_hard_timeout("send html page with buffer", r); ap_rputs(bp_head, r); ap_rputs(buf, r); /* Hope this makes no assumptions on the * nature of the string it sends: I sure as * heck have no way of caring! */ ap_rputs(BOILERPLATE_END, r); ap_kill_timeout(r); } /* returnHTMLPageWithBuffer */ static void returnHTMLPageWithFile(request_rec *r, FILE *f) { char *bp_head; struct stat s; /* * How big is the file? Use a regular file system call because ap_ * doesn't provide one. */ if ((fstat(fileno(f), &s))) { returnHTMLPageWithMessage(r, "Error: Failed to stat the file"); return; } bp_head = returnHTMLPageHead(r, s.st_size); if (r->header_only) return; ap_hard_timeout("send html page with file", r); ap_rputs(bp_head, r); ap_send_fd(f, r); ap_rputs(BOILERPLATE_END, r); ap_kill_timeout(r); } /* returnHTMLPageWithFile */ /*********************************************************************** * checkUPFile * ***********************************************************************/ int16_t checkUPFile(struct server_rec *sv, struct request_rec *r, struct specweb99_module_data * _my) { struct stat s; int16_t numrecords, up_uid; int e = 0; FILE *f; char up_record[UPRLENGTH]; #ifdef SEMCNTR int semval; #endif /* stat it, compare to stored stat */ if (stat(_my->up_path, &s) != 0) { ap_log_error(APLOG_MARK, APLOG_ERR, sv, "Could not stat User.Personality file '%s'", _my->up_path); return 1; }; if (EQMODTIME(s, _my->up_lastmod)) { #ifdef SEMCNTR semun semv; #if 0 int id = semget(SEMKEY, 1, 0666); if (id < 0) { ap_log_error(APLOG_MARK, APLOG_ERR, sv, "Could not find sem in checkUPfile"); return 1; }; #endif if ((semctl(SEMKEY, _my->semid, GETVAL, &semv)) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR, sv, "Could not get sem in checkUPfile"); return 1; }; #ifdef DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, sv, "Sem cntr %d == %d\n", semv.val, _my->semcntr); #endif semval = semv.val; if (semval == _my->semcntr) #endif return 0; } numrecords = s.st_size / 15; /* * 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) */ ap_clear_pool(_my->up_pool); _my->up = ap_palloc(_my->up_pool, numrecords * sizeof(u_int32_t)); _my->up_count = numrecords; } /* * open the file, with memory from the request pool because we will not * need it very long. */ if ((f = fopen(_my->up_path, "r")) == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, 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, sv, "Failed to lock User.Personality file '%s'", _my->up_path); fclose(f); return 1; } /* Read every record, parse, put user demographics in array */ up_uid = 0; while (1) { int id, dem; _WINTR(e = fread((void *) up_record, sizeof(up_record), 1, f)); if (e != 1) break; /* on error or EOF */ if (sscanf(up_record, "%d %x", &id, &dem) != 2) { ap_log_error(APLOG_MARK, APLOG_ERR, sv, "corrupted entry in U file"); e = 1; } if (up_uid != id) { ap_log_error(APLOG_MARK, APLOG_ERR, sv, "user id out of sync in UP file"); e = 1; } _my->up[up_uid] = dem; up_uid++; }; if (ferror(f)) { ap_log_error(APLOG_MARK, APLOG_ERR, sv, "Failed read from User.Personality file '%s'", _my->up_path); e = 1; } if (_unlock(sv, r, f, _my->up_path)) { ap_log_error(APLOG_MARK, APLOG_ERR, sv, "Failed to unlock User.Personality file '%s'", _my->up_path); e = 1; }; /* Close file */ fclose(f); /* Store last modified date assuming no errors. */ if (!e) { _my->up_lastmod = MODTIME(s); #ifdef DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, sv, "checkUPFile: File re-read. Date=%ld", (long) MODTIME(s)); #endif } return e; } /* checkUPFile */ /*********************************************************************** * checkCADFile * ***********************************************************************/ int16_t checkCADFile(struct server_rec *sv, struct request_rec *r, struct specweb99_module_data * _my) { struct stat s; size_t numrecords; FILE *f; char cadline[CADRLENGTH]; u_int16_t cad_uid; int e = 0; #ifdef SEMCNTR int semval = 0; #endif /* Stat it, compare to stored modification time */ if (stat(_my->cad_path, &s) != 0) { ap_log_error(APLOG_MARK, APLOG_ERR, sv, "Failed to stat CAD file '%s'", _my->cad_path); return 1; }; if (EQMODTIME(s, _my->cad_lastmod)) { #ifdef SEMCNTR /* also check sem's */ semun semv; #if 0 int id = semget(SEMKEY, 1, 0666); if (id < 0) { ap_log_error(APLOG_MARK, APLOG_ERR, sv, "Could not find sem in CADfile"); return 1; }; #endif if ((semctl(SEMKEY, _my->semid, GETVAL, &semv)) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR, sv, "Could not get sem in CADfile"); return 1; }; #ifdef DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, sv, "Sem cntr %d == %d\n", semv.val, _my->semcntr); #endif semval = semv.val; if (semval == _my->semcntr) #endif return 0; } /* * Need to read file into memory - and re-allocate the array if the size * has changed. */ numrecords = s.st_size / CADRLENGTH; if (numrecords > _my->cad_count) { ap_clear_pool(_my->cad_pool); _my->cad = ap_palloc(_my->cad_pool, numrecords * sizeof(struct cadrec)); _my->cad_count = numrecords; } f = fopen(_my->cad_path, "r"); if (f == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, 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, sv, "Failed to lock CAD file '%s'", _my->cad_path); return 1; } cad_uid = 0; while (cad_uid < numrecords) { int l, id, dem, adw, adm, exp; _WINTR(l = fread(cadline, CADRLENGTH, 1, f)); if (l != 1) break; /* on EOF and on error */ /* * 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, sv, "Entry CAD file corrupted"); continue; } if (cad_uid != id) { ap_log_error(APLOG_MARK, APLOG_ERR, 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 (ferror(f)) { ap_log_error(APLOG_MARK, APLOG_ERR, sv, "Failed to read from CAD file '%s'", _my->cad_path); e = 1; }; if (_unlock(sv, r, f, _my->cad_path)) { ap_log_error(APLOG_MARK, APLOG_ERR, sv, "Failed to unlock the CAD file '%s'", _my->cad_path); e = 1; }; fclose(f); if (!e) { _my->cad_lastmod = MODTIME(s); #ifdef DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, sv, "checkCADFile: File re-read. Date=%ld", (long) MODTIME(s)); #endif } return e; } /* checkCADFile */ /*********************************************************************** * specweb99_module_init * ***********************************************************************/ static void *specweb99_server_create(pool *p, server_rec *s) { struct specweb99_module_data *_my; _my = (struct specweb99_module_data *) ap_pcalloc(p, sizeof(struct specweb99_module_data)); #ifdef SOLARIS2 _my->up_lastmod = (time_t) 0L; _my->cad_lastmod = (time_t) 0L; #else #ifdef LINUX _my->up_lastmod = (time_t) 0L; _my->cad_lastmod = (time_t) 0L; #else _my->up_lastmod.tv_sec = _my->up_lastmod.tv_nsec = 0; _my->cad_lastmod.tv_sec = _my->cad_lastmod.tv_nsec = 0; #endif /* LINUX */ #endif /* SOLARIS2 */ _my->up = NULL; _my->cad = NULL; _my->up_count = 0; _my->cad_count = 0; #ifdef SEMCNTR /* * XXX not sure if this is correct. The code I stole this from did not * fork; i.e. we only get the sem id here; and then store it. That might * not be good enough; i.e. we might have to do a semget() each time; or * at fork time. */ { semun argument; /* make sure that we have a semaphore. */ if ((_my->semid = semget(SEMKEY, 1, 0666 | IPC_CREAT)) < 0) { ap_log_error(APLOG_MARK, APLOG_CRIT, s, "Failed to create semaphore"); return NULL; }; _my->semcntr = 0; argument.val = _my->semcntr + 1; /* make sure they are not the * same ! */ if ((semctl(_my->semid, 0, SETVAL, &argument)) < 0) { ap_log_error(APLOG_MARK, APLOG_CRIT, s, "Failed to set semaphore"); return NULL; }; }; #endif return (void *) _my; } static void specweb99_module_init(server_rec *s, ap_pool * p) { char fname[255]; ap_add_version_component(NAME "/" VERSION); ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, s, NAME "/" VERSION " module: Compiled on %s at %s", __DATE__, __TIME__); ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, s, "%s", "$Id: mod_specweb99.c,v 1.7 2004/02/08 17:10:05 sctemme Exp $"); /* * Make a shared memory buffer to hold all our User Profile and Custom * Ads activity. The size of this buffer is hardwired for the maximum * number of SPECWeb connections we expect to handle. This is * unfortunate, but I don't really want to deal with multiple shared * memory buffers at this time. */ sprintf(fname, "/tmp/specweb99buf.%ld", getpid()); globalBuffer = mm_create( sizeof(bufs) + sizeof(u_int32_t) * MAXUSERS + 360 * CADRLENGTH, fname); if (globalBuffer == NULL) { /* What do we do from the init? */ ap_log_error(APLOG_MARK, APLOG_EMERG | APLOG_NOERRNO, s, "Couldn't allocate shared memory: mm error %s", mm_error()); exit(1); } #ifdef DEBUG ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, s, "Allocated shared memory at %x", globalBuffer); mm_display_info(globalBuffer); #endif rec_buffer = mm_malloc(globalBuffer, sizeof(bufs)); if (rec_buffer == NULL) { ap_log_error(APLOG_MARK, APLOG_EMERG | APLOG_NOERRNO, s, "Couldn't allocate rec_buffer in shared memory: mm error %s", mm_error()); exit(1); } rec_buffer->cad_buffer = NULL; rec_buffer->up_buffer = NULL; } /* specweb99_module_init */ static void specweb99_child_init(server_rec *s, ap_pool * p) { 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->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"); _my->up_pool = ap_make_sub_pool(p); _my->cad_pool = ap_make_sub_pool(p); 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). */ }; #ifdef DEBUG ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, s, "Child %ld initialized. MM at %x and rec_buffer at %x.", getpid(), globalBuffer, rec_buffer); #endif } /* specweb99_module_init */ /*********************************************************************** * call_specweb_util * ***********************************************************************/ static int call_specweb_util(void *rp, child_info * pinfo) { char **env; int child_pid; request_rec *r = (request_rec *) rp; env = (char **) ap_create_environment(r->pool, r->subprocess_env); ap_error_log2stderr(r->server); ap_cleanup_for_exec(); if (!r->args) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r->server, "No rargs: %p %p", r->prev, r->prev ? r->prev->prev : 0); r->args = r->prev->args; }; child_pid = ap_call_exec(r, pinfo, r->filename, env, 0); #ifdef WIN32 return (child_pid); #else ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "exec of %s failed", r->filename); exit(0); /* NOT REACHED */ return (0); #endif } /* call_specweb_util */ /*********************************************************************** * specweb99_hk_handler * ***********************************************************************/ static int specweb99_hk_handler(request_rec *r) { struct specweb99_module_data *_my = ap_get_module_config(r->server->module_config, &specweb99_module); FILE *f; char *data, *key, *val, *line, *expcomma, *newexp = ""; table *tab; size_t err; int16_t maxload, maxthread; int32_t pointtime; char *exp, *urlroot, *rootdir; const char *docroot = ap_document_root(r); uri_components *urlrootrec; /* To parse the urlroot string into */ int result; /* To keep mm_(un)lock result in */ char cadline[CADRLENGTH], up_record[UPRLENGTH]; u_int16_t cad_uid, up_uid; specweb99_info(r->server, ap_psprintf(r->pool, "Housekeeping routine called with %s", r->args)); if (strstr(r->args, "command/Fetch") != NULL) { f = ap_pfopen(r->pool, _my->log_path, "r"); if (f == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Could no open post.log file '%s' for reading", _my->log_path); returnHTMLPageWithMessage(r, "Error: could not open post.log for reading."); return OK; } returnHTMLPageWithFile(r, f); ap_pfclose(r->pool, f); return OK; } else if ((data = strstr(r->args, "command/Reset"))) { /* * 1 012345678901234 * command/Reset&maxload=[MaxLoad]&pttime=[PointTime]&maxthreads=[ * MaxThreads]&exp=[ExpiredList]&urlroot=[UrlRoot] */ data += 14; /* position at start of argument string */ /* Tokenize argument string */ tab = ap_make_table(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); ap_table_set(tab, key, val); } /* Put arguments in variables */ maxload = atoi(ap_table_get(tab, "maxload")); pointtime = atol(ap_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 = atoi(ap_table_get(tab, "maxthread")); exp = ap_psprintf(r->pool, "%s", ap_table_get(tab, "exp")); /* * 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: */ if ((expcomma = strstr(exp, ",")) != NULL) { #if _DEBUG newexp = ap_psprintf(r->pool, "%d %d", atoi(exp), atoi(expcomma + 1)); #else newexp = ap_psprintf(r->pool, "%d %d", atoi(exp), atoi(expcomma + 1)); #endif }; urlroot = ap_psprintf(r->pool, "%s", ap_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. */ urlrootrec = ap_palloc(r->pool, sizeof(uri_components)); if (ap_parse_uri_components(r->pool, urlroot, urlrootrec) !=HTTP_OK) { ap_log_error(APLOG_MARK, APLOG_NOERRNO, r->server, "The URL Root '%s' was invalid", urlroot); returnHTMLPageWithMessage(r, "The UrlRoot passed was invalid"); return OK; } rootdir = ap_os_escape_path(r->pool, ap_make_full_path(r->pool, docroot, urlrootrec->path), 0); /* * Remove Custom.Ads and User.Personality files before regenerating * them. This will reveal any problems running the aux programs */ if (unlink(_my->up_path) != 0) { ap_log_error(APLOG_MARK, APLOG_WARNING, r->server, "Could not delete file %s", _my->up_path); } if (unlink(_my->cad_path) != 0) { ap_log_error(APLOG_MARK, APLOG_WARNING, r->server, "Could not delete file %s", _my->cad_path); } #if 1 { int e; char *cmd = ap_psprintf(r->pool, "%s -n %d -t %d -C %s", _my->upfgen99, maxload, maxthread, rootdir); ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, r->server, "Runnng %s", cmd); e = system(cmd); switch (e) { case 127: ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "couldn't start shell for %s", cmd); returnHTMLPageWithMessage(r, "Couldn't spawn child process upfgen99"); return OK; break; case -1: ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "couldn't fork()/waitpid() for %s", cmd); returnHTMLPageWithMessage(r, "Couldn't spawn child process upfgen99"); return OK; break; default: if ((!(WIFEXITED(e))) || (WEXITSTATUS(e))) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "CMD %s failed", cmd); returnHTMLPageWithMessage(r, "Couldn't spawn child process upfgen99"); return OK; } break; } } { int e; char *cmd = ap_psprintf(r->pool, "%s -C %s -e %ld -t %d %s", _my->cadgen99, rootdir, (long int) pointtime, maxthread, newexp); ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, r->server, "Runnng %s", cmd); e = system(cmd); switch (e) { case 127: ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "couldn't start shell for %s", cmd); returnHTMLPageWithMessage(r, "Couldn't spawn child process cadgen"); return OK; break; case -1: ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "couldn't fork()/waitpid() for %s", cmd); returnHTMLPageWithMessage(r, "Couldn't spawn child process cadgen99"); return OK; break; default: if ((!(WIFEXITED(e))) || (WEXITSTATUS(e))) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "CMD %s failed", cmd); returnHTMLPageWithMessage(r, "Couldn't spawn child process cadgen99"); return OK; } break; } } #else { char *saveargs; /* Call upfgen and cadgen */ r->filename = _my->upfgen99; /* * Keep request arguments around, we need them for eventual response */ saveargs = ap_pstrdup(r->pool, r->args); r->args = ap_psprintf(r->pool, "-n+%d+-t+%d+-C+%s", maxload, maxthread, rootdir); #ifdef DEBUG specweb99_debug(r->server, ap_psprintf(r->pool, "Calling %s", r->filename)); #endif if (!ap_bspawn_child(r->pool, call_specweb_util, (void *) r, kill_after_timeout, NULL, NULL, NULL)) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "couldn't spawn child process: %s", r->filename); r->args = saveargs; returnHTMLPageWithMessage(r, "Couldn't spawn child process upfgen99"); return OK; } r->filename = _my->cadgen99; r->args = ap_psprintf(r->pool, "-C+%s+-e+%ld+-t+%d+%s", rootdir, (long int) pointtime, maxthread, newexp); #ifdef DEBUG specweb99_debug(r->server, ap_psprintf(r->pool, "Calling %s", r->filename)); #endif if (!ap_bspawn_child(r->pool, call_specweb_util, (void *) r, kill_after_timeout, NULL, NULL, NULL)) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "couldn't spawn child process: %s", r->filename); r->args = saveargs; returnHTMLPageWithMessage(r, "Couldn't spawn child process upfgen99"); return OK; } r->args = saveargs; } #endif /* * We are sleeping at least one second - to make sure that any * fstat() on mtime will actually yeild different values - no matter * how closely spaced the Reset's are issued. (in particular the * spacing between the test reset form the manager and the reset at * the commencing - which normally can be within a second - thus * having identical mtime's on platforms with second granuaarity * (Solaris,Lnux). */ sleep(2); /* * Now that we have run the two auxillary programs, read * the results back into our shared memory buffer. */ /* * We want absolute exclusive access to this buffer during * this exercise. Wonder what any other processes do when * they try to read the buffer. Do they camp out? * Undocumented: mm_lock and mm_unlock return TRUE or FALSE * that is 1 or 0: presumably TRUE or 1 means the lock was * acquired/released correctly. */ result = mm_lock(globalBuffer, MM_LOCK_RW); if (result == 0) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r->server, "Locking shared memory segment failed! MM error: %s", mm_error()); return HTTP_INTERNAL_SERVER_ERROR; } /* * If the Custom Ads buffer does not exist, allocate it. If not, * reallocate. Reallocating should not upset things because the size * never changes. If the location changes, we might have to stop * doing reallocation to avoid fragmentation */ if (rec_buffer->cad_buffer == NULL) { rec_buffer->cad_buffer = mm_malloc(globalBuffer, CADRLENGTH * 360); if (rec_buffer->cad_buffer == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r->server, "Allocating CAD buffer in shared memory failed! MM error: %s", mm_error()); return HTTP_INTERNAL_SERVER_ERROR; } } else { rec_buffer->cad_buffer = mm_realloc(globalBuffer, rec_buffer->cad_buffer, CADRLENGTH * 360); if (rec_buffer->cad_buffer == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r->server, "Reallocating CAD buffer in shared memory failed! MM error: %s", mm_error()); return HTTP_INTERNAL_SERVER_ERROR; } } /* * Same for User Profile array. This changes size for practically every * run. Gets size parameter from the command/Reset parameter string. */ if (rec_buffer->up_buffer == NULL) { rec_buffer->up_buffer = mm_malloc(globalBuffer, maxload * sizeof(u_int32_t)); if (rec_buffer->up_buffer == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r->server, "Allocating UP buffer in shared memory failed! MM error: %s", mm_error()); return HTTP_INTERNAL_SERVER_ERROR; } } else { rec_buffer->up_buffer = mm_realloc(globalBuffer, rec_buffer->up_buffer, maxload * sizeof(u_int32_t)); if (rec_buffer->up_buffer == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r->server, "Reallocating UP buffer in shared memory failed! MM error: %s", mm_error()); return HTTP_INTERNAL_SERVER_ERROR; } } /* Open CAD file, read, parse and stuff into buffer, close. */ f = fopen(_my->cad_path, "r"); if (f == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Failed to open CAD file '%s'", _my->cad_path); return HTTP_INTERNAL_SERVER_ERROR; }; if (_rlock(r->server, r, f, _my->cad_path)) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Failed to lock CAD file '%s'", _my->cad_path); return HTTP_INTERNAL_SERVER_ERROR; } cad_uid = 0; result = 0; while (cad_uid < NUMCADRECORDS) { int l, id, dem, adw, adm, exp; _WINTR(l = fread(cadline, CADRLENGTH, 1, f)); if (l != 1) break; /* on EOF and on error */ /* * 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, r->server, "Entry CAD file corrupted"); continue; } if (cad_uid != id) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Entry CAD file Id# out of sync"); continue; } rec_buffer->cad_buffer[cad_uid].addemographics = dem; rec_buffer->cad_buffer[cad_uid].gen_weightings = (adw & 0x00f0000) >> 16; rec_buffer->cad_buffer[cad_uid].age_weightings = (adw & 0x000f000) >> 12; rec_buffer->cad_buffer[cad_uid].reg_weightings = (adw & 0x0000f00) >> 8; rec_buffer->cad_buffer[cad_uid].int1_weightings = (adw & 0x00000f0) >> 4; rec_buffer->cad_buffer[cad_uid].int2_weightings = (adw & 0x000000f); rec_buffer->cad_buffer[cad_uid].minimum_match_value = adm; rec_buffer->cad_buffer[cad_uid].expiration_time = exp; cad_uid++; } if (ferror(f)) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Failed to read from CAD file '%s'", _my->cad_path); result = 1; }; if (_unlock(r->server, r, f, _my->cad_path)) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Failed to unlock the CAD file '%s'", _my->cad_path); result = 1; }; fclose(f); if (result) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Error occurred during CAD read"); } _my->cad_count = NUMCADRECORDS; /* It's constant. Keeping it around * in a variable is not really * necessary and might someday be * elliminated. */ /* Open UP file */ /* Read lines, parse and stuff into buffer */ /* Close UP file */ /* * open the file, with memory from the request pool because we will * not need it very long. */ if ((f = fopen(_my->up_path, "r")) == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Could not open User.Personality file '%s'", _my->up_path); return HTTP_INTERNAL_SERVER_ERROR; } if (_rlock(r->server, r, f, _my->up_path)) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Failed to lock User.Personality file '%s'", _my->up_path); fclose(f); return HTTP_INTERNAL_SERVER_ERROR; } /* Read every record, parse, put user demographics in array */ up_uid = 0; while (1) { int id, dem; _WINTR(result = fread((void *) up_record, sizeof(up_record), 1, f)); if (result != 1) break; /* on error or EOF */ if (sscanf(up_record, "%d %x", &id, &dem) != 2) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "corrupted entry in U file"); result = 1; } if (up_uid != id) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "user id out of sync in UP file"); result = 1; } rec_buffer->up_buffer[up_uid] = dem; up_uid++; }; if (ferror(f)) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Failed read from User.Personality file '%s'", _my->up_path); result = 1; } if (_unlock(r->server, r, f, _my->up_path)) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Failed to unlock User.Personality file '%s'", _my->up_path); result = 1; }; /* Close file */ fclose(f); rec_buffer->up_count = up_uid + 1; /* User ID is zero-based */ /* We're done, let the other children in again. */ result = mm_unlock(globalBuffer); if (result == 0) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r->server, "Unlocking shared memory segment failed! MM error: %s", mm_error()); return HTTP_INTERNAL_SERVER_ERROR; } /* Reset post.log */ f = ap_pfopen(r->pool, _my->log_path, "w"); /* Truncate, open for * writing */ if (f == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, 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 = ap_psprintf(r->pool, "%10d\n", 0); _WINTR(err = fwrite(line, strlen(line), 1, f)); if (err == -1) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Could not write to post.log '%s'", _my->log_path); returnHTMLPageWithMessage(r, "Error: could not write to post.log."); } else { returnHTMLPageWithMessage(r, ""); /* No news is good news. */ } /* Write to post.log */ ap_pfclose(r->pool, f); #ifdef SEMCNTR { struct sembuf ops[1]; #if 0 /* get our counter. */ int id; if ((id = semget(SEMKEY, 0, 0666)) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Could not get sem"); returnHTMLPageWithMessage(r, "Error: could not get sem"); }; #endif /* and increment */ ops[0].sem_num = 0; ops[0].sem_op = 1; ops[0].sem_flg = 0; if ((semop(_my->semid, ops, 1)) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Could not inc sem"); returnHTMLPageWithMessage(r, "Error: could not get sem"); }; ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Inced the sem"); } #endif return OK; } /* Reset Command */ /* Fall through */ returnHTMLPageWithMessage(r, "Error: unrecognized command '%s'", r->args); return OK; } /*********************************************************************** * specweb99_get_handler * ***********************************************************************/ static int specweb99_get_handler(request_rec *r) { char *path; FILE *thefile; const char *docroot = ap_document_root(r); /* * This should not be tested for every request. Eventually, this code * will find its way to the housekeeping command handler, but that does * not exist yet. */ /* * 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); /* use ap_ call to open the file */ if (!(thefile = ap_pfopen(r->pool, path, "r"))) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Could not open File '%s' (get handler)", path); returnHTMLPageWithMessage(r, "Could not open '%s': %s", path, strerror(errno)); return HTTP_INTERNAL_SERVER_ERROR; }; returnHTMLPageWithFile(r, thefile); ap_pfclose(r->pool, thefile); return OK; } /* specweb99_get_handler */ /*********************************************************************** * customadscan * ***********************************************************************/ caddr_t customadscan(request_rec *r, FILE *f, int16_t adid) { struct stat s; int16_t fd, i; size_t len; char *index, *N, *X, *Y; char *buf; /* pinpoint the file from the ad ID */ N = ap_psprintf(r->pool, "%05d", adid / 36); X = ap_psprintf(r->pool, "%1d", (adid % 36) / 9); Y = ap_psprintf(r->pool, "%1d", adid % 9); /* File is already open */ fd = fileno(f); if (fstat(fd, &s) == -1) return NULL; len = (size_t) s.st_size; buf = ap_palloc(r->pool, len + 1); if (len) { char *bufp = buf; int l, left = len; do { l = read(fd, bufp, left); if (l == -1) { if ((errno == EINTR) || (errno == EAGAIN)) continue; return NULL; /* Error on read */ } else if (l == 0) return NULL; /* Premature end */ left -= l; bufp += l; } while (left); } buf[len] = '\0'; /* Null-terminate it so that everybody else * who has to handle this knows how long the * buffer is. */ 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 */ } return buf; } /* customadscan */ /*********************************************************************** * specweb99_cadget_handler * ***********************************************************************/ static int specweb99_cadget_handler(request_rec *r) { struct specweb99_module_data *_my = ap_get_module_config(r->server->module_config, &specweb99_module); char *cookie_in, *cookie_out, *end; const char *docroot = ap_document_root(r); char *filename; int16_t my_user, last_ad, userindex, adindex, expired = 0; u_int32_t userdemographics, combineddemographics; /* it's a bitmap */ u_int16_t ad_weight; FILE *page; /* * This should not be tested for every request. Eventually, this code * will find its way to the housekeeping command handler, but that does * not exist yet. */ /* Get the cookie */ cookie_in = (char *) ap_table_get(r->headers_in, "Cookie"); #ifdef DEBUG specweb99_debug(r->server, ap_psprintf(r->pool, "Got cadget request, cookie is: %s", cookie_in)); #endif /* * 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 ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_DEBUG, r->server, "Full path is '%s'", filename); #endif if ((page = ap_pfopen(r->pool, filename, "r")) == NULL) { returnHTMLPageWithMessage(r, "File '%s' not found", filename); ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Could not open File '%s' (cadget handler)", filename); return OK; } /* * Parse Cookie string into MyUser and Last_Ad. 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] */ if (!cookie_in) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r->server, "Error: expected cookie but found none"); returnHTMLPageWithMessage(r, "Error: expected cookie but found none"); return HTTP_INTERNAL_SERVER_ERROR; } /* Is there a cookie */ /* Parse the incoming Cookie data */ /* Format of input: user_id=&last_ad= */ my_user = strtol(cookie_in + 18, &end, 10); last_ad = atoi(end + 9); /* We trust that there is something behind * the last_ad value to stop the conversion */ #ifdef DEBUG specweb99_debug(r->server, ap_psprintf(r->pool, "UserID: %d, LastAd: %d, Valid: %s", my_user, last_ad, (cookie_in == end) ? "no" : "yes")); #endif /* * Calculate UserIndex into User.Personality file UserIndex = MyUser - * 10000 */ userindex = my_user - 10000; /* * Find User.Personality record using UserIndex */ #if 0 if (checkUPFile(r->server, r, _my)) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r->server, "User personality check failed."); returnHTMLPageWithMessage(r, "Error: User personality file check failed."); return HTTP_INTERNAL_SERVER_ERROR; } #endif if (userindex < 0 || userindex >= rec_buffer->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, rec_buffer->up_count + 10000 - 1); ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r->server, "User Record %d not found (out of my current range %d .. %d)", userindex + 10000, 10000, rec_buffer->up_count + 10000 - 1); return OK; } /* If case, old behaviour. Else, use shm buffer */ #if 0 userdemographics = _my->up[userindex]; #else userdemographics = rec_buffer->up_buffer[userindex]; #endif #if 0 if (checkCADFile(r->server, r, _my)) { returnHTMLPageWithMessage(r, "Error: Ad file check failed."); ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r->server, "Ad file check failed"); return HTTP_INTERNAL_SERVER_ERROR; } #endif adindex = (last_ad + 1) % 360; /* Do For Each Ad in Custom.Ads starting where Ad_index == Ad_id */ while (1) { #if 0 /* This is the old per-process buffer case */ /* 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, r->server, "Ad to expire not found"); break; } } expired = ((time((time_t *) NULL) > _my->cad[adindex].expiration_time)) ? 1 : 0; #if DEBUG ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, r->server, "Found ad %d : expire %s (%d > %d)", adindex, expired ? "yes" : "no", (int) time((time_t *) NULL), _my->cad[adindex].expiration_time); #endif #else /* This is the shared memory case */ /* CombinedDemographics = ( AdDemographics & UserDemographics ) */ combineddemographics = (rec_buffer->cad_buffer[adindex].addemographics) & userdemographics; /* Ad_weight = 0 */ ad_weight = 0; if (combineddemographics & GENDER_MASK) { ad_weight += rec_buffer->cad_buffer[adindex].gen_weightings; } if (combineddemographics & AGE_GROUP_MASK) { ad_weight += rec_buffer->cad_buffer[adindex].age_weightings; } if (combineddemographics & REGION_MASK) { ad_weight += rec_buffer->cad_buffer[adindex].reg_weightings; } if (combineddemographics & INTEREST1_MASK) { ad_weight += rec_buffer->cad_buffer[adindex].int1_weightings; } if (combineddemographics & INTEREST2_MASK) { ad_weight += rec_buffer->cad_buffer[adindex].int2_weightings; } if (ad_weight >= rec_buffer->cad_buffer[adindex].minimum_match_value) { break; } adindex = (adindex + 1) % 360; if (adindex == last_ad) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r->server, "Ad to expire not found"); break; } } expired = ((time((time_t *) NULL) > rec_buffer->cad_buffer[adindex].expiration_time)) ? 1 : 0; #if DEBUG ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, r->server, "Found ad %d : expire %s (%d > %d)", adindex, expired ? "yes" : "no", (int) time((time_t *) NULL), rec_buffer->cad_buffer[adindex].expiration_time); #endif #endif cookie_out = ap_psprintf(r->pool, "found_cookie=Ad_id=%d&Ad_weight=%d&Expired=%d", adindex, ad_weight, expired); ap_table_setn(r->headers_out, "Set-Cookie", cookie_out); if ((strstr(filename, "class1") != NULL) || (strstr(filename, "class2") != NULL)) { caddr_t buf = customadscan(r, page, adindex); returnHTMLPageWithBuffer(r, (char *) buf); } else { returnHTMLPageWithFile(r, page); } ap_pfclose(r->pool, page); return OK; } /* specweb99_cadget_handler */ #if 0 /*********************************************************************** * util_parse_cookie * ***********************************************************************/ table *util_parse_cookie(request_rec *r) { const char *data = ap_table_get(r->headers_in, "Cookie"); table *cookies; const char *pair; if (!data) return NULL; cookies = ap_make_table(r->pool, 2); while (*data && (pair = ap_getword(r->pool, &data, ';'))) { const char *name, *value; while (*data == ' ') ++data; name = ap_getword(r->pool, &pair, '='); while (*pair && (value = ap_getword(r->pool, &pair, '&'))) { ap_unescape_url((char *) value); ap_table_add(cookies, name, value); } } return cookies; } /* util_parse_cookies */ #endif /*********************************************************************** * specweb99_post_handler * ***********************************************************************/ static int _log_and_write( struct request_rec *r, FILE *f, char *filename, const char *urlroot, int dirnum, int classnum, int filenum, int clientnum, int uid ) { pid_t pid; time_t stamp; int e; u_int32_t recnum; char recnumstr[12]; /* ten wide plus return plus \0 */ stamp = time(NULL); pid = getpid(); /* "%10d %10d %10d %5d %2d %2d %10d %-60.60s %10d %10d\n", * RecordNum, TimeStamp, Pid, Dir#, Class#, File#, Client#, * FileName, Pid, MyCookie */ /* Increment PostLog RecordCount record and rewrite PostLog*/ _WINTR(e = fread(recnumstr, (size_t) 1, (size_t) 11, f)); if (e != 11) { /* Scream and shout */ specweb99_error(r->server, "Failed to read recordcount from post.log"); returnHTMLPageWithMessage(r, "Failed to read recordcount from post.log"); return HTTP_INTERNAL_SERVER_ERROR; } recnum = atol(recnumstr) + 1; /* protected by trailing return */ fseek(f, 0, SEEK_SET); if (fprintf(f, "%10d\n", recnum) != 11) { specweb99_error(r->server, "Failed to write new recordcount to post.log"); returnHTMLPageWithMessage(r, "Failed write new recordcount to post.log"); return HTTP_INTERNAL_SERVER_ERROR; } fflush(f); /* Append new PostLog Record to end of PostLog*/ fseek(f, 0, SEEK_END); /* write 139 bytes */ if (fprintf(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) != 139) { specweb99_error(r->server, "Failed to write record to post.log"); returnHTMLPageWithMessage(r, "Failed write record to post.log"); return HTTP_INTERNAL_SERVER_ERROR; } /* If writing PostLog gets errors*/ /* Return HTML Page with Message = error_message*/ /* Endif*/ /* (refer to Post Log Format section to see required format)*/ fflush(f); return OK; } static int specweb99_post_handler(request_rec *r) { struct specweb99_module_data *_my = ap_get_module_config(r->server->module_config, &specweb99_module); int posterr, e, e2; const char *urlroot = ""; int dirnum = 0, classnum = 0, filenum = 0, clientnum = 0, uid = 0; char *filename; const char *cookie_in; FILE *f; char *data = ""; const char *type, *docroot; char argsbuffer[HUGE_STRING_LEN]; int rsize, len_read, rpos = 0; long length = 0; 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 = ap_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, 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, 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, r->server, "No POST data"); returnHTMLPageWithMessage(r, "No POST data"); return HTTP_INTERNAL_SERVER_ERROR; } length = r->remaining; data = ap_pcalloc(r->pool, length + 1); ap_hard_timeout("specweb99_post_handler read", r); while ((len_read = ap_get_client_block(r, argsbuffer, sizeof(argsbuffer))) > 0) { ap_reset_timeout(r); if ((rpos + len_read) > length) { rsize = length - rpos; } else { rsize = len_read; } memcpy((char *) data + rpos, argsbuffer, rsize); rpos += rsize; } ap_kill_timeout(r); 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 = ap_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, r->server, "Did not get all POST arguments"); returnHTMLPageWithMessage(r, "Did not get all POST arguments"); return HTTP_INTERNAL_SERVER_ERROR; } /* Parse Cookie string to get MyCookie. The format is as */ /* follows (the order of the keys and values is fixed):*/ /* * 1 2 012345678901234567890123 * my_cookie=user_id=[MyCookie]&last_ad=[IgnoredField] */ cookie_in = ap_table_get(r->headers_in, "Cookie"); if (cookie_in == NULL) { ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r->server, "No cookie"); returnHTMLPageWithMessage(r, "Invalid Cookie"); return HTTP_INTERNAL_SERVER_ERROR; } uid = atoi(cookie_in + 18); /* 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, ap_psprintf(r->pool, "%s/dir%05d/class%1d_%1d", urlroot, dirnum, classnum, filenum)); /* Do_atomically (for example, using a file lock or other mutex): */ f = ap_pfopen(r->pool, _my->log_path, "r+"); if (f == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, 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 ((e = _wlock(r->server, r, f, _my->log_path)) != 0) returnHTMLPageWithMessage(r, "Failed to lock post.log file"); if (e == 0) e = _log_and_write(r, f, filename, urlroot, dirnum, classnum, filenum, clientnum, uid); if ((e2 = _unlock(r->server, r, f, _my->log_path)) != OK) { if (e == 0) { e = e2; returnHTMLPageWithMessage(r, "Failed to lock post.log file"); } } /* End Do_atomically*/ ap_pfclose(r->pool, f); if (e != 0) return HTTP_INTERNAL_SERVER_ERROR; /* _log_and_write() will have * displayed a page already */ /* * gettimeofday(&afterlock, NULL); lockstr = ap_psprintf(r->pool, "Entire * logfile op took: %ld microseconds", (afterlock.tv_sec - * beforelock.tv_sec) * 1000000 + afterlock.tv_usec - * beforelock.tv_usec); specweb99_info(lockstr); */ /* Access file 'RootDir/Filename'*/ f = ap_pfopen(r->pool, filename, "r"); /* If error found while accessing file then*/ if (f == NULL) { /* Return HTML Page with Message = error_message*/ ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Could not open File '%s' (post handler)", filename); returnHTMLPageWithMessage(r, "Could not open file"); return HTTP_INTERNAL_SERVER_ERROR; } /* 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. */ ap_table_setn(r->headers_out, "Set-Cookie", ap_psprintf(r->pool, "my_cookie=%d", uid)); /* Return HTML Page with File='RootDir/FileName' and Cookie=CookieString */ returnHTMLPageWithFile(r, f); ap_pfclose(r->pool, f); return OK; } /* specweb99_post_handler */ /*! @const specweb99_handlers @discussion Dispatch list of handler function pointers to let the Apache core know which incantation is handled by what function */ static const handler_rec specweb99_handlers[] = { {"specweb99hk", specweb99_hk_handler}, {"specweb99get", specweb99_get_handler}, {"specweb99cadget", specweb99_cadget_handler}, {"specweb99post", specweb99_post_handler}, {NULL, NULL} }; /*! @const specweb99_module @discussion Dispatch list of Apache API hook callbacks */ module MODULE_VAR_EXPORT specweb99_module = { STANDARD_MODULE_STUFF, specweb99_module_init, /* module initializer */ NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ specweb99_server_create, /* create per-server config structures */ NULL, /* merge per-server config structures */ NULL, /* table of config file commands */ specweb99_handlers, /* [#8] MIME-typed-dispatched handlers */ NULL, /* [#1] URI to filename translation */ NULL, /* [#4] validate user id from request */ NULL, /* [#5] check if the user is ok _here_ */ NULL, /* [#3] check access by host address */ NULL, /* [#6] determine MIME type */ NULL, /* [#7] pre-run fixups */ NULL, /* [#9] log a transaction */ NULL, /* [#2] header parser */ specweb99_child_init, /* child_init */ NULL, /* child_exit */ NULL, /* [#0] post read-request */ #ifdef EAPI NULL, /* EAPI: add_module */ NULL, /* EAPI: remove_module */ NULL, /* EAPI: rewrite_command */ NULL, /* EAPI: new_connection */ #endif };