Index: trunk/willow/src/wlogwriter/Makefile.in |
— | — | @@ -0,0 +1,8 @@ |
| 2 | +PROGRAM = wlogwriter |
| 3 | + |
| 4 | +SRCS = wlogwriter.c |
| 5 | +BINDIR=@libexecdir@ |
| 6 | + |
| 7 | +EXTRA_DIST=Makefile.in |
| 8 | + |
| 9 | +@include@ @q@@top_srcdir@/mk/prog.mk@q@ |
Index: trunk/willow/src/wlogwriter/wlogwriter.c |
— | — | @@ -0,0 +1,58 @@ |
| 2 | +/* @(#) $Header$ */ |
| 3 | +/* This source code is in the public domain. */ |
| 4 | +/* |
| 5 | + * Willow: Lightweight HTTP reverse-proxy. |
| 6 | + * wlogwriter: child process for log writing. |
| 7 | + */ |
| 8 | + |
| 9 | +#if defined __SUNPRO_C || defined __DECC || defined __HP_cc |
| 10 | +# pragma ident "@(#)$Header$" |
| 11 | +#endif |
| 12 | + |
| 13 | +#include <stdio.h> |
| 14 | +#include <unistd.h> |
| 15 | +#include <errno.h> |
| 16 | +#include <string.h> |
| 17 | +#include <stdlib.h> |
| 18 | +#include <signal.h> |
| 19 | + |
| 20 | +#include "config.h" |
| 21 | + |
| 22 | +int |
| 23 | +main(argc, argv) |
| 24 | + int argc; |
| 25 | + char **argv; |
| 26 | +{ |
| 27 | + FILE *outf; |
| 28 | + char *line; |
| 29 | + size_t lnsz; |
| 30 | + |
| 31 | + lnsz = 8192; |
| 32 | + line = malloc(lnsz); |
| 33 | + |
| 34 | + (void)signal(SIGPIPE, SIG_IGN); |
| 35 | + |
| 36 | + if (argc < 2) { |
| 37 | + (void)fprintf(stderr, "not enough arguments\n"); |
| 38 | + exit(8); |
| 39 | + } |
| 40 | + |
| 41 | +#ifdef HAVE_SETPROCTITLE |
| 42 | + setproctitle("log writer: %s", argv[1]); |
| 43 | +#endif |
| 44 | + |
| 45 | + /*LINTED unsafe fopen*/ |
| 46 | + if ((outf = fopen(argv[1], "a")) == NULL) { |
| 47 | + perror(argv[1]); |
| 48 | + exit(8); |
| 49 | + } |
| 50 | + |
| 51 | + while (fgets(line, lnsz, stdin)) { |
| 52 | + if (fputs(line, outf) == EOF || fflush(outf) == EOF) { |
| 53 | + exit(8); |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + exit(0); |
| 58 | + /*NOTREACHED*/ |
| 59 | +} |
Index: trunk/willow/src/willow/wbackend.c |
— | — | @@ -0,0 +1,321 @@ |
| 2 | +/* @(#) $Header$ */ |
| 3 | +/* This source code is in the public domain. */ |
| 4 | +/* |
| 5 | + * Willow: Lightweight HTTP reverse-proxy. |
| 6 | + * wbackend: HTTP backend handling. |
| 7 | + */ |
| 8 | + |
| 9 | +#if defined __SUNPRO_C || defined __DECC || defined __HP_cc |
| 10 | +# pragma ident "@(#)$Header$" |
| 11 | +#endif |
| 12 | + |
| 13 | +#include <sys/types.h> |
| 14 | +#include <sys/socket.h> |
| 15 | + |
| 16 | +#include <arpa/inet.h> |
| 17 | + |
| 18 | +#include <stdlib.h> |
| 19 | +#include <stdio.h> |
| 20 | +#include <string.h> |
| 21 | +#include <errno.h> |
| 22 | +#include <strings.h> |
| 23 | +#include <limits.h> |
| 24 | +#include <math.h> |
| 25 | +#include <time.h> |
| 26 | + |
| 27 | +#include "willow.h" |
| 28 | +#include "wbackend.h" |
| 29 | +#include "wnet.h" |
| 30 | +#include "wlog.h" |
| 31 | +#include "confparse.h" |
| 32 | +#include "wconfig.h" |
| 33 | + |
| 34 | +#define rotl(i,r) (((i) << (r)) | ((i) >> (sizeof(i)*CHAR_BIT-(r)))) |
| 35 | + |
| 36 | +static struct backend **backends; |
| 37 | +int nbackends; |
| 38 | + |
| 39 | +static struct backend *new_backend(const char *, const char *, int); |
| 40 | +static void backend_read(struct fde *); |
| 41 | +static struct backend *next_backend(const char *url); |
| 42 | +static uint32_t carp_urlhash(const char *); |
| 43 | +static uint32_t carp_hosthash(const char *); |
| 44 | +static uint32_t carp_combine(const char *url, uint32_t host); |
| 45 | +static void carp_recalc(const char *url); |
| 46 | +static void carp_calc(void); |
| 47 | +static int becalc_cmp(const void *a, const void *b); |
| 48 | + |
| 49 | +struct backend_cb_data { |
| 50 | +struct backend *bc_backend; |
| 51 | + backend_cb bc_func; |
| 52 | + void *bc_data; |
| 53 | +const char *bc_url; |
| 54 | +}; |
| 55 | + |
| 56 | +static struct backend * |
| 57 | +new_backend(name, addr, port) |
| 58 | + const char *name, *addr; |
| 59 | + int port; |
| 60 | +{ |
| 61 | +struct backend *nb; |
| 62 | + |
| 63 | + if ((nb = wcalloc(1, sizeof(*nb))) == NULL) |
| 64 | + outofmemory(); |
| 65 | + |
| 66 | + nb->be_port = port; |
| 67 | + nb->be_straddr = wstrdup(addr); |
| 68 | + nb->be_name = wstrdup(name); |
| 69 | + nb->be_addr.sin_family = AF_INET; |
| 70 | + nb->be_addr.sin_port = htons(nb->be_port); |
| 71 | + nb->be_addr.sin_addr.s_addr = inet_addr(nb->be_name); |
| 72 | + nb->be_dead = 0; |
| 73 | + nb->be_hash = carp_hosthash(nb->be_name); |
| 74 | + nb->be_load = 1.0; |
| 75 | + return nb; |
| 76 | +} |
| 77 | + |
| 78 | +void |
| 79 | +add_backend(addr, port) |
| 80 | + const char *addr; |
| 81 | + int port; |
| 82 | +{ |
| 83 | + if (port < 1 || port > 65535) { |
| 84 | + conf_report_error("invalid backend port: %d", port); |
| 85 | + nerrors++; |
| 86 | + return; |
| 87 | + } |
| 88 | + |
| 89 | + if ((backends = wrealloc(backends, sizeof(struct backend*) * ++nbackends)) == NULL) |
| 90 | + outofmemory(); |
| 91 | + backends[nbackends - 1] = new_backend(addr, addr, port); |
| 92 | + carp_calc(); |
| 93 | + wlog(WLOG_NOTICE, "backend: %s:%d", addr, port); |
| 94 | +} |
| 95 | + |
| 96 | +#if 0 |
| 97 | +void |
| 98 | +backend_file(file) |
| 99 | + char *file; |
| 100 | +{ |
| 101 | + FILE *f; |
| 102 | + char line[1024]; |
| 103 | + |
| 104 | + if ((f = fopen(file, "r")) == NULL) { |
| 105 | + perror(file); |
| 106 | + exit(8); |
| 107 | + } |
| 108 | + |
| 109 | + while (fgets(line, sizeof line, f)) { |
| 110 | + line[strlen(line) - 1] = '\0'; |
| 111 | + add_backend(line); |
| 112 | + } |
| 113 | + |
| 114 | + (void)fclose(f); |
| 115 | +} |
| 116 | +#endif |
| 117 | + |
| 118 | +int |
| 119 | +get_backend(url, func, data, flags) |
| 120 | + const char *url; |
| 121 | + backend_cb func; |
| 122 | + void *data; |
| 123 | + int flags; |
| 124 | +{ |
| 125 | +struct backend_cb_data *cbd; |
| 126 | + int s; |
| 127 | + |
| 128 | + WDEBUG((WLOG_DEBUG, "get_backend: called")); |
| 129 | + |
| 130 | + if ((cbd = wmalloc(sizeof(*cbd))) == NULL) |
| 131 | + outofmemory(); |
| 132 | + |
| 133 | + cbd->bc_func = func; |
| 134 | + cbd->bc_data = data; |
| 135 | + cbd->bc_url = url; |
| 136 | + |
| 137 | + for (;;) { |
| 138 | + cbd->bc_backend = next_backend(url); |
| 139 | + |
| 140 | + if (cbd->bc_backend == NULL) { |
| 141 | + wfree(cbd); |
| 142 | + return -1; |
| 143 | + } |
| 144 | + |
| 145 | + if ((s = wnet_open("backend connection")) == -1) { |
| 146 | + wlog(WLOG_WARNING, "opening backend socket: %s", strerror(errno)); |
| 147 | + wfree(cbd); |
| 148 | + return -1; |
| 149 | + } |
| 150 | + |
| 151 | + if (connect(s, (struct sockaddr *)&cbd->bc_backend->be_addr, |
| 152 | + sizeof(cbd->bc_backend->be_addr)) == 0) { |
| 153 | + WDEBUG((WLOG_DEBUG, "get_backend: connection completed immediately")); |
| 154 | + func(cbd->bc_backend, &fde_table[s], data); |
| 155 | + wfree(cbd); |
| 156 | + return 0; |
| 157 | + } |
| 158 | + |
| 159 | + if (errno != EINPROGRESS) { |
| 160 | + time_t retry = time(NULL) + config.backend_retry; |
| 161 | + wnet_close(s); |
| 162 | + wlog(WLOG_WARNING, "%s: %s; retry in %d seconds", |
| 163 | + cbd->bc_backend->be_name, strerror(errno), config.backend_retry); |
| 164 | + cbd->bc_backend->be_dead = 1; |
| 165 | + cbd->bc_backend->be_time = retry; |
| 166 | + continue; |
| 167 | + } |
| 168 | + |
| 169 | + WDEBUG((WLOG_DEBUG, "get_backend: waiting for connection to complete")); |
| 170 | + wnet_register(s, FDE_WRITE, backend_read, cbd); |
| 171 | + return 0; |
| 172 | + } |
| 173 | +} |
| 174 | + |
| 175 | +static void |
| 176 | +backend_read(e) |
| 177 | + struct fde *e; |
| 178 | +{ |
| 179 | +struct backend_cb_data *cbd = e->fde_rdata; |
| 180 | + int error = 0; |
| 181 | + socklen_t len = sizeof(error); |
| 182 | + |
| 183 | + getsockopt(e->fde_fd, SOL_SOCKET, SO_ERROR, &error, &len); |
| 184 | + |
| 185 | + if (error && error != EINPROGRESS) { |
| 186 | + time_t retry = time(NULL) + config.backend_retry; |
| 187 | + wnet_close(e->fde_fd); |
| 188 | + wlog(WLOG_WARNING, "%s: [%d] %s; retry in %d seconds", |
| 189 | + cbd->bc_backend->be_name, error, strerror(error), config.backend_retry); |
| 190 | + cbd->bc_backend->be_dead = 1; |
| 191 | + cbd->bc_backend->be_time = time(NULL) + config.backend_retry; |
| 192 | + if (get_backend(cbd->bc_url, cbd->bc_func, cbd->bc_data, 0) == -1) { |
| 193 | + cbd->bc_func(NULL, NULL, cbd->bc_data); |
| 194 | + } |
| 195 | + |
| 196 | + wfree(cbd); |
| 197 | + return; |
| 198 | + } |
| 199 | + |
| 200 | + /* |
| 201 | + * After handing the fd off to the caller, we don't care about it |
| 202 | + * any more. |
| 203 | + */ |
| 204 | + wnet_register(e->fde_fd, FDE_WRITE, NULL, NULL); |
| 205 | + |
| 206 | + cbd->bc_func(cbd->bc_backend, e, cbd->bc_data); |
| 207 | + wfree(cbd); |
| 208 | +} |
| 209 | + |
| 210 | +static struct backend * |
| 211 | +next_backend(url) |
| 212 | + const char *url; |
| 213 | +{ |
| 214 | +static int cur = 0; |
| 215 | + int tried = 0; |
| 216 | + |
| 217 | + if (config.use_carp) { |
| 218 | + carp_recalc(url); |
| 219 | + // cur = 0; |
| 220 | + } |
| 221 | + |
| 222 | + WDEBUG((WLOG_DEBUG, "next_backend: url=[%s]", url)); |
| 223 | + |
| 224 | + while (tried++ <= nbackends) { |
| 225 | + time_t now = time(NULL); |
| 226 | + |
| 227 | + if (cur >= nbackends) |
| 228 | + cur = 0; |
| 229 | + |
| 230 | + if (backends[cur]->be_dead && now >= backends[cur]->be_time) |
| 231 | + backends[cur]->be_dead = 0; |
| 232 | + |
| 233 | + if (backends[cur]->be_dead) { |
| 234 | + cur++; |
| 235 | + continue; |
| 236 | + } |
| 237 | + |
| 238 | + if (config.use_carp) |
| 239 | + cur = 0; |
| 240 | + return backends[cur++]; |
| 241 | + } |
| 242 | + |
| 243 | + return NULL; |
| 244 | +} |
| 245 | + |
| 246 | +static uint32_t |
| 247 | +carp_urlhash(str) |
| 248 | + const char *str; |
| 249 | +{ |
| 250 | + const char *ostr = str; |
| 251 | + uint32_t h = 0; |
| 252 | + for (; *str; ++str) |
| 253 | + h += (rotl(h, 19) + *str); |
| 254 | + WDEBUG((WLOG_DEBUG, "hash(%s) = %d", ostr, h)); |
| 255 | + return h; |
| 256 | +} |
| 257 | + |
| 258 | +static uint32_t |
| 259 | +carp_hosthash(str) |
| 260 | + const char *str; |
| 261 | +{ |
| 262 | + uint32_t h = carp_urlhash(str) * 0x62531965; |
| 263 | + return rotl(h, 21); |
| 264 | +} |
| 265 | + |
| 266 | +static uint32_t |
| 267 | +carp_combine(url, host) |
| 268 | + const char *url; |
| 269 | + uint32_t host; |
| 270 | +{ |
| 271 | + uint32_t c = carp_urlhash(url) ^ host; |
| 272 | + c += c * 0x62531965; |
| 273 | + return rotl(c, 21); |
| 274 | +} |
| 275 | + |
| 276 | +static void |
| 277 | +carp_calc(void) |
| 278 | +{ |
| 279 | +struct backend *be, *prev; |
| 280 | + int i, j; |
| 281 | + |
| 282 | + backends[0]->be_carp = pow((nbackends * backends[0]->be_load), 1.0 / nbackends); |
| 283 | + backends[0]->be_carplfm = 1.0; |
| 284 | + for (i = 1; i < nbackends; ++i) { |
| 285 | + float l = 0; |
| 286 | + be = backends[i]; |
| 287 | + prev = backends[i - 1]; |
| 288 | + be->be_carplfm = 1.0 + ((nbackends-i+1) * (be->be_load - prev->be_load)); |
| 289 | + WDEBUG((WLOG_DEBUG, "carp_calc: %s lfm = %f", be->be_name, be->be_carplfm)); |
| 290 | + for (j = 0; j < i; ++j) |
| 291 | + l *= backends[j]->be_carp; |
| 292 | + be->be_carp /= l; |
| 293 | + be->be_carp += pow(prev->be_carp, nbackends-i+1); |
| 294 | + be->be_carp = pow(be->be_carp, 1/(nbackends-i+1)); |
| 295 | + } |
| 296 | +} |
| 297 | + |
| 298 | +static void |
| 299 | +carp_recalc(url) |
| 300 | + const char *url; |
| 301 | +{ |
| 302 | + uint32_t hash; |
| 303 | + int i; |
| 304 | + for (i = 0; i < nbackends; ++i) { |
| 305 | + hash = carp_urlhash(url) ^ backends[i]->be_hash; |
| 306 | + hash += hash * 0x62531965; |
| 307 | + hash = rotl(hash, 21); |
| 308 | + hash *= backends[i]->be_carplfm; |
| 309 | + WDEBUG((WLOG_DEBUG, "%s: hash = %lu, lfm = %f", backends[i]->be_name, hash, backends[i]->be_carplfm)); |
| 310 | + backends[i]->be_carp = hash; |
| 311 | + } |
| 312 | + qsort(backends, nbackends, sizeof(struct backend *), becalc_cmp); |
| 313 | + WDEBUG((WLOG_DEBUG, "first backend = %s", backends[0]->be_name)); |
| 314 | +} |
| 315 | + |
| 316 | +static int |
| 317 | +becalc_cmp(a, b) |
| 318 | +const void *a, *b; |
| 319 | +{ |
| 320 | +struct backend **ba = a, **bb = b; |
| 321 | + return (*ba)->be_carp - (*bb)->be_carp; |
| 322 | +} |
Index: trunk/willow/src/willow/confparse.c |
— | — | @@ -0,0 +1,515 @@ |
| 2 | +/* @(#) $Header$ */ |
| 3 | +/* From ircd-ratbox: newconf.c,v 7.209 2005/04/05 01:22:57 leeh Exp */ |
| 4 | +/* This source code is in the public domain. */ |
| 5 | +/* |
| 6 | + * Willow: Lightweight HTTP reverse-proxy. |
| 7 | + * confparse: config parser implementation. |
| 8 | + */ |
| 9 | + |
| 10 | +#if defined __SUNPRO_C || defined __DECC || defined __HP_cc |
| 11 | +# pragma ident "@(#)$Header$" |
| 12 | +#endif |
| 13 | + |
| 14 | +#include <stdarg.h> |
| 15 | +#include <stdio.h> |
| 16 | +#include <string.h> |
| 17 | +#include <syslog.h> |
| 18 | + |
| 19 | +#include "willow.h" |
| 20 | +#include "confparse.h" |
| 21 | +#include "queue.h" |
| 22 | +#include "wlog.h" |
| 23 | +#include "wbackend.h" |
| 24 | +#include "wconfig.h" |
| 25 | + |
| 26 | +#define CF_TYPE(x) ((x) & CF_MTYPE) |
| 27 | + |
| 28 | +int nerrors; |
| 29 | +struct top_conf *conf_cur_block; |
| 30 | +char *conf_cur_block_name; |
| 31 | + |
| 32 | +static LIST_HEAD(conf_items_head, top_conf) conf_items = |
| 33 | + LIST_HEAD_INITIALIZER(conf_items); |
| 34 | + |
| 35 | +static struct conf_entry *find_conf_item(struct top_conf *top, const char *); |
| 36 | +static void conf_set_generic_int(void *, void *); |
| 37 | +static void conf_set_generic_string(void *, int, void *); |
| 38 | +static void add_top_conf(const char *name, int (*) (struct top_conf *), |
| 39 | + int (*) (struct top_conf *), struct conf_entry *); |
| 40 | + |
| 41 | +void |
| 42 | +conf_report_error(const char *fmt, ...) |
| 43 | +{ |
| 44 | + char buf[1024]; |
| 45 | + va_list ap; |
| 46 | + |
| 47 | + va_start(ap, fmt); |
| 48 | + (void)vsnprintf(buf, 1024, fmt, ap); |
| 49 | + va_end(ap); |
| 50 | + |
| 51 | + wlog(WLOG_ERROR, "\"%s\", line %d: %s", current_file, lineno, buf); |
| 52 | +} |
| 53 | + |
| 54 | +void |
| 55 | +yyerror(msg) |
| 56 | + const char *msg; |
| 57 | +{ |
| 58 | + conf_report_error("%s", msg); |
| 59 | +} |
| 60 | + |
| 61 | +static const char * |
| 62 | +conf_strtype(type) |
| 63 | + int type; |
| 64 | +{ |
| 65 | + switch (type & CF_MTYPE) |
| 66 | + { |
| 67 | + case CF_INT: |
| 68 | + return "integer value"; |
| 69 | + case CF_STRING: |
| 70 | + return "unquoted string"; |
| 71 | + case CF_YESNO: |
| 72 | + return "yes/no value"; |
| 73 | + case CF_QSTRING: |
| 74 | + return "quoted string"; |
| 75 | + case CF_TIME: |
| 76 | + return "time/size value"; |
| 77 | + default: |
| 78 | + return "unknown type"; |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +static void |
| 83 | +add_top_conf(name, sfunc, efunc, items) |
| 84 | + const char *name; |
| 85 | + int (*sfunc) (struct top_conf *); |
| 86 | + int (*efunc) (struct top_conf *); |
| 87 | + struct conf_entry *items; |
| 88 | +{ |
| 89 | +struct top_conf *tc; |
| 90 | + |
| 91 | + tc = wmalloc(sizeof(struct top_conf)); |
| 92 | + |
| 93 | + tc->tc_name = wstrdup(name); |
| 94 | + tc->tc_sfunc = sfunc; |
| 95 | + tc->tc_efunc = efunc; |
| 96 | + tc->tc_entries = items; |
| 97 | + |
| 98 | + LIST_INSERT_HEAD(&conf_items, tc, entries); |
| 99 | +} |
| 100 | + |
| 101 | +static struct top_conf * |
| 102 | +find_top_conf(name) |
| 103 | + const char *name; |
| 104 | +{ |
| 105 | +struct top_conf *tc; |
| 106 | + |
| 107 | + LIST_FOREACH(tc, &conf_items, entries) { |
| 108 | + if (!strcasecmp(tc->tc_name, name)) |
| 109 | + return tc; |
| 110 | + } |
| 111 | + |
| 112 | + return NULL; |
| 113 | +} |
| 114 | + |
| 115 | +static struct conf_entry * |
| 116 | +find_conf_item(top, name) |
| 117 | + struct top_conf *top; |
| 118 | + const char *name; |
| 119 | +{ |
| 120 | +struct conf_entry *cf; |
| 121 | + |
| 122 | + if (top->tc_entries) { |
| 123 | + int i; |
| 124 | + |
| 125 | + for(i = 0; top->tc_entries[i].cf_type; i++) |
| 126 | + { |
| 127 | + cf = &top->tc_entries[i]; |
| 128 | + |
| 129 | + if(!strcasecmp(cf->cf_name, name)) |
| 130 | + return cf; |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + LIST_FOREACH(cf, &top->tc_items, entries) { |
| 135 | + if(strcasecmp(cf->cf_name, name) == 0) |
| 136 | + return cf; |
| 137 | + } |
| 138 | + |
| 139 | + return NULL; |
| 140 | +} |
| 141 | + |
| 142 | +/*ARGSUSED*/ |
| 143 | +int |
| 144 | +conf_call_set(tc, item, value, type) |
| 145 | + struct top_conf *tc; |
| 146 | + char *item; |
| 147 | + conf_parm_t *value; |
| 148 | + int type; |
| 149 | +{ |
| 150 | +struct conf_entry *cf; |
| 151 | + conf_parm_t *cp; |
| 152 | + |
| 153 | + if (!tc) |
| 154 | + return -1; |
| 155 | + |
| 156 | + if ((cf = find_conf_item(tc, item)) == NULL) { |
| 157 | + conf_report_error("Non-existant configuration setting %s::%s.", |
| 158 | + tc->tc_name, (char *) item); |
| 159 | + nerrors++; |
| 160 | + return -1; |
| 161 | + } |
| 162 | + |
| 163 | + /* |
| 164 | + * If it takes one thing, make sure they only passed one thing, |
| 165 | + * and handle as needed. |
| 166 | + */ |
| 167 | + if (value->type & CF_FLIST && !cf->cf_type & CF_FLIST) { |
| 168 | + conf_report_error("Option %s::%s does not take a list of values.", |
| 169 | + tc->tc_name, item); |
| 170 | + nerrors++; |
| 171 | + return -1; |
| 172 | + } |
| 173 | + |
| 174 | + cp = value->v.list; |
| 175 | + |
| 176 | + if (CF_TYPE(value->v.list->type) != CF_TYPE(cf->cf_type)) { |
| 177 | + /* |
| 178 | + * If it expects a string value, but we got a yesno, |
| 179 | + * convert it back |
| 180 | + */ |
| 181 | + if((CF_TYPE(value->v.list->type) == CF_YESNO) && |
| 182 | + (CF_TYPE(cf->cf_type) == CF_STRING)) { |
| 183 | + value->v.list->type = CF_STRING; |
| 184 | + |
| 185 | + if(cp->v.number == 1) |
| 186 | + cp->v.string = wstrdup("yes"); |
| 187 | + else |
| 188 | + cp->v.string = wstrdup("no"); |
| 189 | + } |
| 190 | + |
| 191 | + /* |
| 192 | + * Maybe it's a CF_TIME and they passed CF_INT -- |
| 193 | + * should still be valid. |
| 194 | + */ |
| 195 | + else if(!((CF_TYPE(value->v.list->type) == CF_INT) && |
| 196 | + (CF_TYPE(cf->cf_type) == CF_TIME))) { |
| 197 | + conf_report_error("Wrong type for %s::%s (expected %s, got %s)", |
| 198 | + tc->tc_name, (char *) item, |
| 199 | + conf_strtype(cf->cf_type), conf_strtype(value->v.list->type)); |
| 200 | + nerrors++; |
| 201 | + return -1; |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + if (cf->cf_type & CF_FLIST) { |
| 206 | + /* just pass it the extended argument list */ |
| 207 | + cf->cf_func(value->v.list); |
| 208 | + } else { |
| 209 | + /* it's old-style, needs only one arg */ |
| 210 | + switch (cf->cf_type) { |
| 211 | + case CF_INT: |
| 212 | + case CF_TIME: |
| 213 | + case CF_YESNO: |
| 214 | + if(cf->cf_arg) |
| 215 | + conf_set_generic_int(&cp->v.number, cf->cf_arg); |
| 216 | + else |
| 217 | + cf->cf_func(&cp->v.number); |
| 218 | + break; |
| 219 | + case CF_STRING: |
| 220 | + case CF_QSTRING: |
| 221 | + if(!*cp->v.string) |
| 222 | + conf_report_error("Ignoring %s::%s -- empty field", |
| 223 | + tc->tc_name, item); |
| 224 | + else if(cf->cf_arg) |
| 225 | + conf_set_generic_string(cp->v.string, cf->cf_len, cf->cf_arg); |
| 226 | + else |
| 227 | + cf->cf_func(cp->v.string); |
| 228 | + break; |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | + return 0; |
| 233 | +} |
| 234 | + |
| 235 | +int |
| 236 | +conf_start_block(block, name) |
| 237 | + const char *block, *name; |
| 238 | +{ |
| 239 | + if ((conf_cur_block = find_top_conf(block)) == NULL) { |
| 240 | + conf_report_error("Configuration block '%s' is not defined.", block); |
| 241 | + nerrors++; |
| 242 | + return -1; |
| 243 | + } |
| 244 | + |
| 245 | + if (name) |
| 246 | + conf_cur_block_name = wstrdup(name); |
| 247 | + else |
| 248 | + conf_cur_block_name = NULL; |
| 249 | + |
| 250 | + if (conf_cur_block->tc_sfunc) |
| 251 | + if (conf_cur_block->tc_sfunc(conf_cur_block) < 0) |
| 252 | + return -1; |
| 253 | + |
| 254 | + return 0; |
| 255 | +} |
| 256 | + |
| 257 | +int |
| 258 | +conf_end_block(tc) |
| 259 | + struct top_conf *tc; |
| 260 | +{ |
| 261 | + if(tc->tc_efunc) |
| 262 | + return tc->tc_efunc(tc); |
| 263 | + |
| 264 | + wfree(conf_cur_block_name); |
| 265 | + return 0; |
| 266 | +} |
| 267 | + |
| 268 | + |
| 269 | +static void |
| 270 | +conf_set_generic_int(data, location) |
| 271 | + void *data, *location; |
| 272 | +{ |
| 273 | + *((int *) location) = *((unsigned int *) data); |
| 274 | +} |
| 275 | + |
| 276 | +static void |
| 277 | +conf_set_generic_string(data, len, location) |
| 278 | + void *data, *location; |
| 279 | + int len; |
| 280 | +{ |
| 281 | + char **loc = location; |
| 282 | + char *input = data; |
| 283 | + |
| 284 | + if(len && strlen(input) > len) |
| 285 | + input[len] = '\0'; |
| 286 | + |
| 287 | + wfree(*loc); |
| 288 | + *loc = wstrdup(input); |
| 289 | +} |
| 290 | + |
| 291 | +static int backend_port; |
| 292 | + |
| 293 | +/*ARGSUSED*/ |
| 294 | +static int |
| 295 | +conf_begin_backend(tc) |
| 296 | + struct top_conf *tc; |
| 297 | +{ |
| 298 | + if (conf_cur_block_name == NULL) { |
| 299 | + conf_report_error("backend name not specified"); |
| 300 | + nerrors++; |
| 301 | + return -1; |
| 302 | + } |
| 303 | + |
| 304 | + return 0; |
| 305 | +} |
| 306 | + |
| 307 | +/*ARGSUSED*/ |
| 308 | +static int |
| 309 | +conf_end_backend(tc) |
| 310 | + struct top_conf *tc; |
| 311 | +{ |
| 312 | + add_backend(conf_cur_block_name, backend_port); |
| 313 | + backend_port = 0; |
| 314 | + |
| 315 | + return 0; |
| 316 | +} |
| 317 | + |
| 318 | +static int listen_port; |
| 319 | + |
| 320 | +/*ARGSUSED*/ |
| 321 | +static int |
| 322 | +conf_begin_listen(tc) |
| 323 | + struct top_conf *tc; |
| 324 | +{ |
| 325 | + if (conf_cur_block_name == NULL) { |
| 326 | + conf_report_error("listener host not specified"); |
| 327 | + nerrors++; |
| 328 | + return -1; |
| 329 | + } |
| 330 | + |
| 331 | + return 0; |
| 332 | +} |
| 333 | + |
| 334 | +/*ARGSUSED*/ |
| 335 | +static int |
| 336 | +conf_end_listen(tc) |
| 337 | + struct top_conf *tc; |
| 338 | +{ |
| 339 | + add_listener(conf_cur_block_name, listen_port); |
| 340 | + listen_port = 0; |
| 341 | + |
| 342 | + return 0; |
| 343 | +} |
| 344 | + |
| 345 | +static int cachedir_size; |
| 346 | + |
| 347 | +/*ARGSUSED*/ |
| 348 | +static int |
| 349 | +conf_begin_cachedir(tc) |
| 350 | + struct top_conf *tc; |
| 351 | +{ |
| 352 | + if (conf_cur_block_name == NULL) { |
| 353 | + conf_report_error("cache directory not specified"); |
| 354 | + nerrors++; |
| 355 | + return -1; |
| 356 | + } |
| 357 | + |
| 358 | + return 0; |
| 359 | +} |
| 360 | + |
| 361 | +/*ARGSUSED*/ |
| 362 | +static int |
| 363 | +conf_end_cachedir(tc) |
| 364 | + struct top_conf *tc; |
| 365 | +{ |
| 366 | + add_cachedir(conf_cur_block_name, cachedir_size); |
| 367 | + cachedir_size = 0; |
| 368 | + |
| 369 | + return 0; |
| 370 | +} |
| 371 | + |
| 372 | +static char *log_file, *log_syslog_facility, *log_access_log; |
| 373 | +static int log_level, log_syslog; |
| 374 | + |
| 375 | +static struct syslog_facility { |
| 376 | + char *name; |
| 377 | + int fac; |
| 378 | +} syslog_facilities[] = { |
| 379 | + {"user", LOG_USER}, |
| 380 | + {"mail", LOG_MAIL}, |
| 381 | + {"daemon", LOG_DAEMON}, |
| 382 | + {"auth", LOG_AUTH}, |
| 383 | + {"lpr", LOG_LPR}, |
| 384 | + {"news", LOG_NEWS}, |
| 385 | + {"uucp", LOG_UUCP}, |
| 386 | + {"cron", LOG_CRON}, |
| 387 | +#ifdef LOG_AUDIT |
| 388 | + {"audit", LOG_AUDIT}, |
| 389 | +#endif |
| 390 | + {"local0", LOG_LOCAL0}, |
| 391 | + {"local1", LOG_LOCAL0}, |
| 392 | + {"local2", LOG_LOCAL0}, |
| 393 | + {"local3", LOG_LOCAL0}, |
| 394 | + {"local4", LOG_LOCAL0}, |
| 395 | + {"local5", LOG_LOCAL0}, |
| 396 | + {"local6", LOG_LOCAL0}, |
| 397 | + {"local7", LOG_LOCAL0}, |
| 398 | + {NULL, 0}, |
| 399 | +}; |
| 400 | + |
| 401 | +/*ARGSUSED*/ |
| 402 | +static int |
| 403 | +conf_end_log(tc) |
| 404 | + struct top_conf *tc; |
| 405 | +{ |
| 406 | + if (log_file) { |
| 407 | + logging.file = wstrdup(log_file); |
| 408 | + } |
| 409 | + |
| 410 | + if (log_syslog) { |
| 411 | + struct syslog_facility *fac = syslog_facilities; |
| 412 | + |
| 413 | + logging.syslog = 1; |
| 414 | + |
| 415 | + if (log_syslog_facility) { |
| 416 | + for (; fac->name; fac++) { |
| 417 | + if (!strcmp(fac->name, log_syslog_facility)) { |
| 418 | + logging.facility = fac->fac; |
| 419 | + break; |
| 420 | + } |
| 421 | + } |
| 422 | + if (!fac->name) { |
| 423 | + conf_report_error("unrecognised syslog facility \"%s\"", log_syslog_facility); |
| 424 | + nerrors++; |
| 425 | + } |
| 426 | + } else |
| 427 | + logging.facility = LOG_DAEMON; |
| 428 | + } |
| 429 | + |
| 430 | + logging.level = log_level; |
| 431 | + if (log_access_log) |
| 432 | + config.access_log = wstrdup(log_access_log); |
| 433 | + return 0; |
| 434 | +} |
| 435 | + |
| 436 | +static int cache_expire_threshold = 25; |
| 437 | +static int cache_expire_every = 60; |
| 438 | +static int cache_compress, cache_complevel = 6, cache_private, cache_use_carp; |
| 439 | +static int backend_retry; |
| 440 | +char *cache_user, *cache_group; |
| 441 | + |
| 442 | +static int |
| 443 | +conf_end_cache(tc) |
| 444 | + struct top_conf *tc; |
| 445 | +{ |
| 446 | + if (cache_expire_threshold < 0 || cache_expire_threshold > 100) { |
| 447 | + conf_report_error("cache::expire_threshold must be between 0 and 100"); |
| 448 | + nerrors++; |
| 449 | + } |
| 450 | + if (cache_expire_every < 0) { |
| 451 | + conf_report_error("cache::expire_every must be greater than 0"); |
| 452 | + nerrors++; |
| 453 | + } |
| 454 | + |
| 455 | + config.cache_expevery = cache_expire_every; |
| 456 | + config.cache_expthresh = cache_expire_threshold; |
| 457 | + config.suid = cache_user; |
| 458 | + config.sgid = cache_group; |
| 459 | + config.compress = cache_compress; |
| 460 | + if (cache_complevel < 1 || cache_complevel > 9) { |
| 461 | + conf_report_error("cache::compress_level must be between 1 and 9"); |
| 462 | + nerrors++; |
| 463 | + } |
| 464 | + config.complevel = cache_complevel; |
| 465 | + config.backend_retry = backend_retry; |
| 466 | + config.cache_private = cache_private; |
| 467 | + config.use_carp = cache_use_carp; |
| 468 | + return 0; |
| 469 | +} |
| 470 | + |
| 471 | +static struct conf_entry conf_backend_table[] = { |
| 472 | + { "port", CF_INT, NULL, 0, &backend_port }, |
| 473 | + { NULL, 0, NULL, 0, NULL } |
| 474 | +}; |
| 475 | + |
| 476 | +static struct conf_entry conf_listen_table[] = { |
| 477 | + { "port", CF_INT, NULL, 0, &listen_port }, |
| 478 | + { NULL, 0, NULL, 0, NULL } |
| 479 | +}; |
| 480 | + |
| 481 | +static struct conf_entry conf_cachedir_table[] = { |
| 482 | + { "size", CF_TIME, NULL, 0, &cachedir_size }, |
| 483 | + { NULL, 0, NULL, 0, NULL } |
| 484 | +}; |
| 485 | + |
| 486 | +static struct conf_entry conf_log_table[] = { |
| 487 | + { "level", CF_INT, NULL, 0, &log_level }, |
| 488 | + { "file", CF_QSTRING, NULL, 0, &log_file }, |
| 489 | + { "syslog", CF_YESNO, NULL, 0, &log_syslog }, |
| 490 | + { "facility", CF_STRING, NULL, 0, &log_syslog_facility }, |
| 491 | + { "access-log", CF_QSTRING, NULL, 0, &log_access_log }, |
| 492 | + { NULL, 0, NULL, 0, NULL } |
| 493 | +}; |
| 494 | + |
| 495 | +static struct conf_entry conf_cache_table[] = { |
| 496 | + { "expire_every", CF_TIME, NULL, 0, &cache_expire_every }, |
| 497 | + { "expire_threshold", CF_INT, NULL, 0, &cache_expire_threshold }, |
| 498 | + { "user", CF_STRING, NULL, 0, &cache_user }, |
| 499 | + { "group", CF_STRING, NULL, 0, &cache_group }, |
| 500 | + { "compress", CF_YESNO, NULL, 0, &cache_compress }, |
| 501 | + { "compress_level", CF_INT, NULL, 0, &cache_complevel }, |
| 502 | + { "backend_retry", CF_TIME, NULL, 0, &backend_retry }, |
| 503 | + { "cache_private", CF_YESNO, NULL, 0, &cache_private }, |
| 504 | + { "use_carp", CF_YESNO, NULL, 0, &cache_use_carp }, |
| 505 | + { NULL, 0, NULL, 0, NULL } |
| 506 | +}; |
| 507 | + |
| 508 | +void |
| 509 | +newconf_init(void) |
| 510 | +{ |
| 511 | + add_top_conf("backend", conf_begin_backend, conf_end_backend, conf_backend_table); |
| 512 | + add_top_conf("listen", conf_begin_listen, conf_end_listen, conf_listen_table); |
| 513 | + add_top_conf("cache-dir", conf_begin_cachedir, conf_end_cachedir, conf_cachedir_table); |
| 514 | + add_top_conf("log", NULL, conf_end_log, conf_log_table); |
| 515 | + add_top_conf("cache", NULL, conf_end_cache, conf_cache_table); |
| 516 | +} |
Index: trunk/willow/src/willow/strlcpy.c |
— | — | @@ -0,0 +1,61 @@ |
| 2 | +/* @(#) $Header$ */ |
| 3 | +/* $NetBSD: strlcpy.c,v 1.14 2003/10/27 00:12:42 lukem Exp $ */ |
| 4 | +/* $OpenBSD: strlcpy.c,v 1.7 2003/04/12 21:56:39 millert Exp $ */ |
| 5 | + |
| 6 | +/* |
| 7 | + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> |
| 8 | + * |
| 9 | + * Permission to use, copy, modify, and distribute this software for any |
| 10 | + * purpose with or without fee is hereby granted, provided that the above |
| 11 | + * copyright notice and this permission notice appear in all copies. |
| 12 | + * |
| 13 | + * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL |
| 14 | + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES |
| 15 | + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE |
| 16 | + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 17 | + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| 18 | + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| 19 | + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 20 | + */ |
| 21 | + |
| 22 | +#if defined __SUNPRO_C || defined __DECC || defined __HP_cc |
| 23 | +# pragma ident "@(#) $Header$" |
| 24 | +#endif |
| 25 | + |
| 26 | +#include <sys/types.h> |
| 27 | +#include <assert.h> |
| 28 | +#include <string.h> |
| 29 | + |
| 30 | +/* |
| 31 | + * Copy src to string dst of size siz. At most siz-1 characters |
| 32 | + * will be copied. Always NUL terminates (unless siz == 0). |
| 33 | + * Returns strlen(src); if retval >= siz, truncation occurred. |
| 34 | + */ |
| 35 | +size_t |
| 36 | +strlcpy(dst, src, siz) |
| 37 | + char *dst; |
| 38 | + const char *src; |
| 39 | + size_t siz; |
| 40 | +{ |
| 41 | + char *d = dst; |
| 42 | + const char *s = src; |
| 43 | + size_t n = siz; |
| 44 | + |
| 45 | + /* Copy as many bytes as will fit */ |
| 46 | + if (n != 0 && --n != 0) { |
| 47 | + do { |
| 48 | + if ((*d++ = *s++) == 0) |
| 49 | + break; |
| 50 | + } while (--n != 0); |
| 51 | + } |
| 52 | + |
| 53 | + /* Not enough room in dst, add NUL and traverse rest of src */ |
| 54 | + if (n == 0) { |
| 55 | + if (siz != 0) |
| 56 | + *d = '\0'; /* NUL-terminate dst */ |
| 57 | + while (*s++) |
| 58 | + ; |
| 59 | + } |
| 60 | + |
| 61 | + return(s - src - 1); /* count does not include NUL */ |
| 62 | +} |
Index: trunk/willow/src/willow/Makefile.in |
— | — | @@ -0,0 +1,30 @@ |
| 2 | +PROGRAM = willow |
| 3 | +BINDIR = @bindir@ |
| 4 | +CPPFLAGS = -I../include |
| 5 | + |
| 6 | +BASESRCS = willow.c wconfig.c whttp.c wbackend.c whttp_entity.c wcache.c confparse.c |
| 7 | +SRCS=$(BASESRCS) |
| 8 | +OBJADD = @LIBOBJS@ y.tab.o lex.yy.o |
| 9 | + |
| 10 | +EXTRA_DIST= strlcat.c strlcpy.c \ |
| 11 | + lexer.l parser.y \ |
| 12 | + daemon.c \ |
| 13 | + Makefile.in |
| 14 | + |
| 15 | +LDFLAGS = -L$(SRCROOT)/src/lib/wnet -lwillownet \ |
| 16 | + -L$(SRCROOT)/src/lib/wlog -lwillowlog \ |
| 17 | + $(LIBOBJS) |
| 18 | + |
| 19 | +default: all |
| 20 | + |
| 21 | +y.tab.o: y.tab.c |
| 22 | +lex.yy.o: lex.yy.c y.tab.h |
| 23 | + |
| 24 | +y.tab.c y.tab.h: parser.y |
| 25 | + @echo " $(_YACC) -d parser.y" |
| 26 | + @$(_YACC) -d parser.y |
| 27 | +lex.yy.c: lexer.l y.tab.h |
| 28 | + @echo " $(_LEX) lexer.l" |
| 29 | + @$(_LEX) lexer.l || rm -f lex.yy.c |
| 30 | + |
| 31 | +@include@ @q@@top_srcdir@/mk/prog.mk@q@ |
Index: trunk/willow/src/willow/parser.y |
— | — | @@ -0,0 +1,317 @@ |
| 2 | +/* @(#) $Header$ */ |
| 3 | +/* From: $Nightmare: nightmare/src/main/parser.y,v 1.2.2.1.2.1 2002/07/02 03:42:10 ejb Exp $ */ |
| 4 | +/* From: ircd_parser.y,v 1.281 2005/03/22 16:27:48 androsyn Exp */ |
| 5 | +/* This source code is in the public domain. */ |
| 6 | +/* |
| 7 | + * Willow: Lightweight HTTP reverse-proxy. |
| 8 | + * parser: configuration file parser. |
| 9 | + */ |
| 10 | + |
| 11 | +%{ |
| 12 | +#if defined __SUNPRO_CC || defined __DECC || defined __HP_cc |
| 13 | +# pragma ident "@(#)$Header$" |
| 14 | +#endif |
| 15 | + |
| 16 | +#include <sys/types.h> |
| 17 | +#include <sys/stat.h> |
| 18 | + |
| 19 | +#include <netinet/in.h> |
| 20 | + |
| 21 | +#include <string.h> |
| 22 | +#include <stdlib.h> |
| 23 | +#include <stdarg.h> |
| 24 | +#include <stdio.h> |
| 25 | + |
| 26 | +#include "willow.h" |
| 27 | +#include "confparse.h" |
| 28 | + |
| 29 | +#define YY_NO_UNPUT |
| 30 | + |
| 31 | +int yyparse(); |
| 32 | +int yyerror(const char *); |
| 33 | +int yylex(); |
| 34 | + |
| 35 | +static time_t conf_find_time(const char*); |
| 36 | +static void add_cur_list(int, const char *, int); |
| 37 | + |
| 38 | +static struct { |
| 39 | + const char * name; |
| 40 | + const char * plural; |
| 41 | + time_t val; |
| 42 | +} conf_times[] = { |
| 43 | + {"second", "seconds", 1}, |
| 44 | + {"minute", "minutes", 60}, |
| 45 | + {"hour", "hours", 60 * 60}, |
| 46 | + {"day", "days", 60 * 60 * 24}, |
| 47 | + {"week", "weeks", 60 * 60 * 24 * 7}, |
| 48 | + {"fortnight", "fortnights", 60 * 60 * 24 * 14}, |
| 49 | + {"month", "months", 60 * 60 * 24 * 7 * 4}, |
| 50 | + {"year", "years", 60 * 60 * 24 * 365}, |
| 51 | + /* ok-- we now do sizes here too. they aren't times, but |
| 52 | + it's close enough */ |
| 53 | + {"byte", "bytes", 1}, |
| 54 | + {"kb", NULL, 1024}, |
| 55 | + {"kbyte", "kbytes", 1024}, |
| 56 | + {"kilobyte", "kilebytes", 1024}, |
| 57 | + {"mb", NULL, 1024 * 1024}, |
| 58 | + {"mbyte", "mbytes", 1024 * 1024}, |
| 59 | + {"megabyte", "megabytes", 1024 * 1024}, |
| 60 | + {NULL, NULL, 0}, |
| 61 | +}; |
| 62 | + |
| 63 | +static time_t |
| 64 | +conf_find_time(name) |
| 65 | + const char *name; |
| 66 | +{ |
| 67 | + int i; |
| 68 | + |
| 69 | + for (i = 0; conf_times[i].name; ++i) { |
| 70 | + if (!strcasecmp(conf_times[i].name, name) || |
| 71 | + (conf_times[i].plural && !strcasecmp(conf_times[i].plural, name))) |
| 72 | + return conf_times[i].val; |
| 73 | + } |
| 74 | + |
| 75 | + return 0; |
| 76 | +} |
| 77 | + |
| 78 | +static struct { |
| 79 | +const char *word; |
| 80 | + int yesno; |
| 81 | +} yesno[] = { |
| 82 | + {"yes", 1}, |
| 83 | + {"no", 0}, |
| 84 | + {"true", 1}, |
| 85 | + {"false", 0}, |
| 86 | + {"on", 1}, |
| 87 | + {"off", 0}, |
| 88 | + {NULL, 0} |
| 89 | +}; |
| 90 | + |
| 91 | +static int |
| 92 | +conf_get_yesno_value(str) |
| 93 | + const char *str; |
| 94 | +{ |
| 95 | + int i; |
| 96 | + |
| 97 | + for (i = 0; yesno[i].word; i++) |
| 98 | + if (!strcasecmp(str, yesno[i].word)) |
| 99 | + return yesno[i].yesno; |
| 100 | + |
| 101 | + return -1; |
| 102 | +} |
| 103 | + |
| 104 | +static void |
| 105 | +free_cur_list(list) |
| 106 | + conf_parm_t *list; |
| 107 | +{ |
| 108 | + switch (list->type & CF_MTYPE) { |
| 109 | + case CF_STRING: |
| 110 | + case CF_QSTRING: |
| 111 | + wfree(list->v.string); |
| 112 | + break; |
| 113 | + case CF_LIST: |
| 114 | + free_cur_list(list->v.list); |
| 115 | + break; |
| 116 | + default: break; |
| 117 | + } |
| 118 | + |
| 119 | + if (list->next) |
| 120 | + free_cur_list(list->next); |
| 121 | +} |
| 122 | + |
| 123 | + |
| 124 | +conf_parm_t * cur_list = NULL; |
| 125 | + |
| 126 | +static void |
| 127 | +add_cur_list_cpt(new) |
| 128 | + conf_parm_t *new; |
| 129 | +{ |
| 130 | + if (cur_list == NULL) { |
| 131 | + cur_list = wcalloc(1, sizeof(conf_parm_t)); |
| 132 | + cur_list->type |= CF_FLIST; |
| 133 | + cur_list->v.list = new; |
| 134 | + } else { |
| 135 | + new->next = cur_list->v.list; |
| 136 | + cur_list->v.list = new; |
| 137 | + } |
| 138 | +} |
| 139 | + |
| 140 | +static void |
| 141 | +add_cur_list(type, str, number) |
| 142 | + int type, number; |
| 143 | + const char *str; |
| 144 | +{ |
| 145 | + conf_parm_t *new; |
| 146 | + |
| 147 | + new = wmalloc(sizeof(conf_parm_t)); |
| 148 | + new->next = NULL; |
| 149 | + new->type = type; |
| 150 | + |
| 151 | + switch(type) { |
| 152 | + case CF_INT: |
| 153 | + case CF_TIME: |
| 154 | + case CF_YESNO: |
| 155 | + new->v.number = number; |
| 156 | + break; |
| 157 | + case CF_STRING: |
| 158 | + case CF_QSTRING: |
| 159 | + new->v.string = wstrdup(str); |
| 160 | + break; |
| 161 | + } |
| 162 | + |
| 163 | + add_cur_list_cpt(new); |
| 164 | +} |
| 165 | + |
| 166 | + |
| 167 | +%} |
| 168 | + |
| 169 | +%union { |
| 170 | + int number; |
| 171 | + char *string; |
| 172 | + conf_parm_t *conf_parm; |
| 173 | +} |
| 174 | + |
| 175 | +%token TWODOTS |
| 176 | + |
| 177 | +%token <string> QSTRING STRING |
| 178 | +%token <number> NUMBER |
| 179 | + |
| 180 | +%type <string> qstring string |
| 181 | +%type <number> number timespec |
| 182 | +%type <conf_parm> oneitem single itemlist |
| 183 | + |
| 184 | +%start conf |
| 185 | + |
| 186 | +%% |
| 187 | + |
| 188 | +conf: |
| 189 | + | conf conf_item |
| 190 | + | error |
| 191 | + ; |
| 192 | + |
| 193 | +conf_item: block |
| 194 | + ; |
| 195 | + |
| 196 | +block: string |
| 197 | + { |
| 198 | + conf_start_block($1, NULL); |
| 199 | + wfree($1); |
| 200 | + } |
| 201 | + '{' block_items '}' ';' |
| 202 | + { |
| 203 | + if (conf_cur_block) |
| 204 | + conf_end_block(conf_cur_block); |
| 205 | + } |
| 206 | + | string qstring |
| 207 | + { |
| 208 | + conf_start_block($1, $2); |
| 209 | + wfree($1); |
| 210 | + wfree($2); |
| 211 | + } |
| 212 | + '{' block_items '}' ';' |
| 213 | + { |
| 214 | + if (conf_cur_block) |
| 215 | + conf_end_block(conf_cur_block); |
| 216 | + } |
| 217 | + ; |
| 218 | + |
| 219 | +block_items: block_items block_item |
| 220 | + | block_item |
| 221 | + ; |
| 222 | + |
| 223 | +block_item: string '=' itemlist ';' |
| 224 | + { |
| 225 | + conf_call_set(conf_cur_block, $1, cur_list, CF_LIST); |
| 226 | + free_cur_list(cur_list); |
| 227 | + cur_list = NULL; |
| 228 | + wfree($1); |
| 229 | + } |
| 230 | + ; |
| 231 | + |
| 232 | +itemlist: itemlist ',' single |
| 233 | + | single |
| 234 | + ; |
| 235 | + |
| 236 | +single: oneitem |
| 237 | + { |
| 238 | + add_cur_list_cpt($1); |
| 239 | + } |
| 240 | + | oneitem TWODOTS oneitem |
| 241 | + { |
| 242 | + /* "1 .. 5" meaning 1,2,3,4,5 - only valid for integers */ |
| 243 | + if (($1->type & CF_MTYPE) != CF_INT || |
| 244 | + ($3->type & CF_MTYPE) != CF_INT) { |
| 245 | + conf_report_error("Both arguments in '..' notation must be integers."); |
| 246 | + break; |
| 247 | + } else { |
| 248 | + int i; |
| 249 | + |
| 250 | + for (i = $1->v.number; i <= $3->v.number; i++) { |
| 251 | + add_cur_list(CF_INT, 0, i); |
| 252 | + } |
| 253 | + } |
| 254 | + } |
| 255 | + ; |
| 256 | + |
| 257 | +oneitem: qstring |
| 258 | + { |
| 259 | + $$ = wmalloc(sizeof(conf_parm_t)); |
| 260 | + $$->type = CF_QSTRING; |
| 261 | + $$->v.string = wstrdup($1); |
| 262 | + wfree($1); |
| 263 | + } |
| 264 | + | timespec |
| 265 | + { |
| 266 | + $$ = wmalloc(sizeof(conf_parm_t)); |
| 267 | + $$->type = CF_TIME; |
| 268 | + $$->v.number = $1; |
| 269 | + } |
| 270 | + | number |
| 271 | + { |
| 272 | + $$ = wmalloc(sizeof(conf_parm_t)); |
| 273 | + $$->type = CF_INT; |
| 274 | + $$->v.number = $1; |
| 275 | + } |
| 276 | + | string |
| 277 | + { |
| 278 | + /* a 'string' could also be a yes/no value .. |
| 279 | + so pass it as that, if so */ |
| 280 | + int val = conf_get_yesno_value($1); |
| 281 | + |
| 282 | + $$ = wmalloc(sizeof(conf_parm_t)); |
| 283 | + |
| 284 | + if (val != -1) { |
| 285 | + $$->type = CF_YESNO; |
| 286 | + $$->v.number = val; |
| 287 | + } else { |
| 288 | + $$->type = CF_STRING; |
| 289 | + $$->v.string = wstrdup($1); |
| 290 | + } |
| 291 | + wfree($1); |
| 292 | + } |
| 293 | + ; |
| 294 | + |
| 295 | +qstring: QSTRING { strcpy($$, $1); } ; |
| 296 | +string: STRING { strcpy($$, $1); } ; |
| 297 | +number: NUMBER { $$ = $1; } ; |
| 298 | + |
| 299 | +timespec: number string |
| 300 | + { |
| 301 | + time_t t; |
| 302 | + |
| 303 | + if ((t = conf_find_time($2)) == 0) { |
| 304 | + conf_report_error("Unrecognised time type/size '%s'", $2); |
| 305 | + t = 1; |
| 306 | + } |
| 307 | + |
| 308 | + $$ = $1 * t; |
| 309 | + } |
| 310 | + | timespec timespec |
| 311 | + { |
| 312 | + $$ = $1 + $2; |
| 313 | + } |
| 314 | + | timespec number |
| 315 | + { |
| 316 | + $$ = $1 + $2; |
| 317 | + } |
| 318 | + ; |
Index: trunk/willow/src/willow/whttp_entity.c |
— | — | @@ -0,0 +1,1255 @@ |
| 2 | +/* @(#) $Header$ */ |
| 3 | +/* This source code is in the public domain. */ |
| 4 | +/* |
| 5 | + * Willow: Lightweight HTTP reverse-proxy. |
| 6 | + * whttp_entity: HTTP entity handling. |
| 7 | + */ |
| 8 | + |
| 9 | +#if defined __SUNPRO_C || defined __DECC || defined __HP_cc |
| 10 | +# pragma ident "@(#)$Header$" |
| 11 | +#endif |
| 12 | + |
| 13 | +/* How does this work? |
| 14 | + * |
| 15 | + * Each HTTP request can be divided into two entities: the request and the |
| 16 | + * response. The client sends the request, i.e. the headers and possibly |
| 17 | + * a body, to the server, which considers it and sends a reply. |
| 18 | + * |
| 19 | + * Internally, we read the request headers and ignore the |
| 20 | + * body [entity_read_headers]. We then examine the headers |
| 21 | + * [whttp:client_read_done] and decide if it has a body. We modify |
| 22 | + * the entry slightly, and send it to the backend with either no source |
| 23 | + * or, if it had a body, the client's FDE as the source [entity_send]. |
| 24 | + * We then wait for the server to reply with its header. When it does |
| 25 | + * [whttp:backend_headers_done], we send the request to the client, using |
| 26 | + * the backend's FDE as the body, if it has one, and close it. |
| 27 | + * |
| 28 | + * See "Entity sending" below for a detailed description of how entity |
| 29 | + * sending works. |
| 30 | + * |
| 31 | + * TODO: We don't have to buffer the headers, _but_ it makes things easier |
| 32 | + * for now and doesn't cost much. if we start not buffering we need to |
| 33 | + * decide what to do when the client goes away unexpectedly. probably it's |
| 34 | + * easiest to just drop the backend connection (this is wasteful of backends |
| 35 | + * but we don't cache them at the moment anyway). what do we do when the |
| 36 | + * client sends "Foo: bar\r\n baz\r\n" and we decide after baz that we |
| 37 | + * shouldn't send that header after all? |
| 38 | + * |
| 39 | + * There is a trade-off in some places between excessive copying and |
| 40 | + * excessive syscalls. In some cases we copy data (headers) when we could |
| 41 | + * undo the parser mangling and send them as-is. IMO this is not likely |
| 42 | + * to be a worthwhile optimisation, needs profiling. |
| 43 | + * |
| 44 | + * As for FDE backending, Unix sucks: |
| 45 | + * |
| 46 | + * The sendfile() function copies data from in_fd to out_fd starting |
| 47 | + * at offset off and of length len bytes. The in_fd argument should |
| 48 | + * be a file descriptor to a regular file opened for reading. |
| 49 | + */ |
| 50 | + |
| 51 | +#include <sys/uio.h> |
| 52 | + |
| 53 | +#include <unistd.h> |
| 54 | +#include <errno.h> |
| 55 | +#include <string.h> |
| 56 | +#include <stdlib.h> |
| 57 | +#include <stdio.h> |
| 58 | +#include <assert.h> |
| 59 | +/*LINTED*/ |
| 60 | +#include <fcntl.h> |
| 61 | +#include <strings.h> |
| 62 | +#include <event.h> |
| 63 | +#include <ctype.h> |
| 64 | +#include <zlib.h> |
| 65 | + |
| 66 | +#include "willow.h" |
| 67 | +#include "whttp.h" |
| 68 | +#include "whttp_entity.h" |
| 69 | +#include "wnet.h" |
| 70 | +#include "wlog.h" |
| 71 | +#include "wconfig.h" |
| 72 | + |
| 73 | +#define ENTITY_STATE_START 0 |
| 74 | +#define ENTITY_STATE_HDR 1 |
| 75 | +#define ENTITY_STATE_DONE 2 |
| 76 | +#define ENTITY_STATE_SEND_HDR 3 |
| 77 | +#define ENTITY_STATE_SEND_BODY 4 |
| 78 | +#define ENTITY_STATE_SEND_BUF 5 |
| 79 | + |
| 80 | +#define ZLIB_BLOCK (1024 * 32) |
| 81 | + |
| 82 | +static void entity_error_callback(struct bufferevent *, short, void *); |
| 83 | +static void entity_read_callback(struct bufferevent *, void *); |
| 84 | +static int parse_headers(struct http_entity *); |
| 85 | +static int parse_reqtype(struct http_entity *); |
| 86 | +static int validhost(const char *); |
| 87 | +static int via_includes_me(const char *); |
| 88 | +static int write_zlib_eof(struct http_entity *); |
| 89 | +static int write_data(struct http_entity *, void *buf, size_t len); |
| 90 | + |
| 91 | +static void entity_send_headers_done(struct fde *, void *, int); |
| 92 | +static void entity_send_fde_write_done(struct fde *, void *, int); |
| 93 | +static void entity_send_buf_done(struct fde *, void *, int); |
| 94 | +static void entity_send_fde_read(struct fde *); |
| 95 | +static void entity_send_file_done(struct fde *, void *, int); |
| 96 | +static void entity_send_target_read(struct bufferevent *, void *); |
| 97 | +static void entity_send_target_write(struct bufferevent *, void *); |
| 98 | +static void entity_send_target_error(struct bufferevent *, short, void *); |
| 99 | + |
| 100 | +const char *ent_errors[] = { |
| 101 | + /* 0 */ "Unknown error", |
| 102 | + /* -1 */ "Read error", |
| 103 | + /* -2 */ "Could not parse request headers", |
| 104 | + /* -3 */ "Invalid Host", |
| 105 | + /* -4 */ "Invalid request type", |
| 106 | + /* -5 */ "Too many headers", |
| 107 | + /* -6 */ "Forwarding loop detected", |
| 108 | +}; |
| 109 | + |
| 110 | +const char *ent_encodings[] = { |
| 111 | + "identity", |
| 112 | + "deflate", |
| 113 | + "x-deflate", |
| 114 | + "gzip", |
| 115 | + "x-gzip", |
| 116 | +}; |
| 117 | + |
| 118 | +void |
| 119 | +entity_free(entity) |
| 120 | + struct http_entity *entity; |
| 121 | +{ |
| 122 | + WDEBUG((WLOG_DEBUG, "free entity @ %p", entity)); |
| 123 | + |
| 124 | + header_free(&entity->he_headers); |
| 125 | + if (entity->_he_frombuf) { |
| 126 | + bufferevent_disable(entity->_he_frombuf, EV_READ | EV_WRITE); |
| 127 | + bufferevent_free(entity->_he_frombuf); |
| 128 | + } |
| 129 | + if (entity->_he_tobuf) { |
| 130 | + bufferevent_disable(entity->_he_tobuf, EV_READ | EV_WRITE); |
| 131 | + bufferevent_free(entity->_he_tobuf); |
| 132 | + } |
| 133 | + if (entity->he_reqstr) |
| 134 | + wfree(entity->he_reqstr); |
| 135 | + if (!entity->he_flags.response) { |
| 136 | + if (entity->he_rdata.request.host) |
| 137 | + wfree(entity->he_rdata.request.host); |
| 138 | + if (entity->he_rdata.request.path) |
| 139 | + wfree(entity->he_rdata.request.path); |
| 140 | + } |
| 141 | + bzero(entity, sizeof(*entity)); |
| 142 | +} |
| 143 | + |
| 144 | +void |
| 145 | +entity_set_response(ent, isresp) |
| 146 | + struct http_entity *ent; |
| 147 | + int isresp; |
| 148 | +{ |
| 149 | + if (isresp) { |
| 150 | + if (ent->he_flags.response) |
| 151 | + return; |
| 152 | + if (ent->he_rdata.request.path) |
| 153 | + wfree(ent->he_rdata.request.path); |
| 154 | + if (ent->he_rdata.request.host) |
| 155 | + wfree(ent->he_rdata.request.host); |
| 156 | + bzero(&ent->he_rdata.response, sizeof(ent->he_rdata.response)); |
| 157 | + ent->he_flags.response = 1; |
| 158 | + } else { |
| 159 | + if (!ent->he_flags.response) |
| 160 | + return; |
| 161 | + bzero(&ent->he_rdata.request, sizeof(ent->he_rdata.request)); |
| 162 | + ent->he_flags.response = 0; |
| 163 | + } |
| 164 | +} |
| 165 | + |
| 166 | +void |
| 167 | +entity_read_headers(entity, func, udata) |
| 168 | + struct http_entity *entity; |
| 169 | + header_cb func; |
| 170 | + void *udata; |
| 171 | +{ |
| 172 | + entity->_he_cbdata = udata; |
| 173 | + entity->_he_func = func; |
| 174 | + entity->he_flags.hdr_only = 1; |
| 175 | + |
| 176 | + WDEBUG((WLOG_DEBUG, "entity_read_headers: starting, source %d", |
| 177 | + entity->he_source.fde.fde->fde_fd)); |
| 178 | + /* XXX source for an entity header read is _always_ an fde */ |
| 179 | + entity->_he_frombuf = bufferevent_new(entity->he_source.fde.fde->fde_fd, |
| 180 | + entity_read_callback, NULL, entity_error_callback, entity); |
| 181 | + bufferevent_disable(entity->_he_frombuf, EV_WRITE); |
| 182 | + bufferevent_enable(entity->_he_frombuf, EV_READ); |
| 183 | +// wnet_register(entity->he_source.fde.fde->fde_fd, FDE_READ, entity_read_callback, entity); |
| 184 | + //entity_read_callback(entity->he_source.fde); |
| 185 | +} |
| 186 | + |
| 187 | +void |
| 188 | +entity_send(fde, entity, cb, data, flags) |
| 189 | + struct fde *fde; |
| 190 | + struct http_entity *entity; |
| 191 | + header_cb cb; |
| 192 | + void *data; |
| 193 | + int flags; |
| 194 | +{ |
| 195 | + char status[4]; |
| 196 | + int wn_flags = 0; |
| 197 | + char *hdr; |
| 198 | +struct header_list *hl; |
| 199 | + int window = 15; |
| 200 | + |
| 201 | + errno = 0; |
| 202 | + |
| 203 | + entity->_he_func = cb; |
| 204 | + entity->_he_cbdata = data; |
| 205 | + entity->_he_target = fde; |
| 206 | + entity->he_flags.hdr_only = 0; |
| 207 | + if (!entity->he_flags.response && entity->he_rdata.request.reqtype == REQTYPE_POST) { |
| 208 | + entity->he_source.fde._wrt = entity->he_rdata.request.contlen; |
| 209 | + } |
| 210 | + |
| 211 | + entity->_he_tobuf = bufferevent_new(entity->_he_target->fde_fd, |
| 212 | + NULL, entity_send_target_write, |
| 213 | + entity_send_target_error, entity); |
| 214 | + bufferevent_disable(entity->_he_tobuf, EV_READ); |
| 215 | + bufferevent_enable(entity->_he_tobuf, EV_WRITE); |
| 216 | + if (entity->_he_frombuf) { |
| 217 | + bufferevent_disable(entity->_he_frombuf, EV_READ); |
| 218 | + bufferevent_disable(entity->_he_frombuf, EV_WRITE); |
| 219 | + } |
| 220 | + entity->_he_state = ENTITY_STATE_SEND_HDR; |
| 221 | + |
| 222 | + WDEBUG((WLOG_DEBUG, "entity_send: writing to %d [%s], enc=%d", fde->fde_fd, fde->fde_desc, |
| 223 | + entity->he_encoding)); |
| 224 | + |
| 225 | + if (entity->he_flags.response) { |
| 226 | + evbuffer_add_printf(entity->_he_tobuf->output, "HTTP/1.1 %d %s\r\n", |
| 227 | + entity->he_rdata.response.status, entity->he_rdata.response.status_str); |
| 228 | + } else { |
| 229 | + evbuffer_add_printf(entity->_he_tobuf->output, "%s %s HTTP/1.1\r\n", |
| 230 | + request_string[entity->he_rdata.request.reqtype], |
| 231 | + entity->he_rdata.request.path); |
| 232 | + } |
| 233 | + |
| 234 | + if (flags & ENT_CHUNKED_OKAY) { |
| 235 | + struct header_list *contlen; |
| 236 | + entity->he_flags.chunked = 1; |
| 237 | + if (!entity->he_h_transfer_encoding) |
| 238 | + header_add(&entity->he_headers, wstrdup("Transfer-Encoding"), wstrdup("chunked")); |
| 239 | + if ((contlen = header_find(&entity->he_headers, "Content-Length")) != NULL) |
| 240 | + header_remove(&entity->he_headers, contlen); |
| 241 | + } |
| 242 | + |
| 243 | + switch (entity->he_encoding) { |
| 244 | + case E_NONE: |
| 245 | + break; |
| 246 | + case E_GZIP: case E_X_GZIP: |
| 247 | + window += 16; |
| 248 | + case E_DEFLATE: case E_X_DEFLATE: { |
| 249 | + int err; |
| 250 | + |
| 251 | + if ((err = deflateInit2(&entity->_he_zbuf, config.complevel, Z_DEFLATED, |
| 252 | + window, 8, Z_DEFAULT_STRATEGY)) != Z_OK) { |
| 253 | + wlog(WLOG_WARNING, "deflateInit: %s", zError(err)); |
| 254 | + entity->he_encoding = E_NONE; |
| 255 | + } |
| 256 | + break; |
| 257 | + } |
| 258 | + } |
| 259 | + header_add(&entity->he_headers, wstrdup("Content-Encoding"), |
| 260 | + wstrdup(ent_encodings[entity->he_encoding])); |
| 261 | + for (hl = entity->he_headers.hl_next; hl; hl = hl->hl_next) |
| 262 | + evbuffer_add_printf(entity->_he_tobuf->output, "%s: %s\r\n", hl->hl_name, hl->hl_value); |
| 263 | + bufferevent_write(entity->_he_tobuf, "\r\n", 2); |
| 264 | +} |
| 265 | + |
| 266 | +static void |
| 267 | +entity_error_callback(struct bufferevent *be, short what, void *d) |
| 268 | +{ |
| 269 | +struct http_entity *entity = d; |
| 270 | + |
| 271 | + /* |
| 272 | + * Some kind of error occured while we were reading from the backend. |
| 273 | + */ |
| 274 | + WDEBUG((WLOG_DEBUG, "entity_error_callback called, what=%hd errno=%d %s", what, |
| 275 | + errno, strerror(errno))); |
| 276 | + |
| 277 | + if (what & EVBUFFER_EOF) { |
| 278 | + /* |
| 279 | + * End of file from backend. |
| 280 | + */ |
| 281 | + if (entity->he_encoding) { |
| 282 | + if (write_zlib_eof(entity) == -1) { |
| 283 | + entity->_he_func(entity, entity->_he_cbdata, -1); |
| 284 | + return; |
| 285 | + } |
| 286 | + } |
| 287 | + |
| 288 | + if (entity->he_flags.chunked && !entity->he_flags.eof) { |
| 289 | + WDEBUG((WLOG_DEBUG, "writing chunked data, append EOF")); |
| 290 | + entity->he_flags.eof = 1; |
| 291 | + bufferevent_enable(entity->_he_tobuf, EV_WRITE); |
| 292 | + bufferevent_write(entity->_he_tobuf, "0\r\n", 3); |
| 293 | + return; |
| 294 | + } |
| 295 | + WDEBUG((WLOG_DEBUG, "entity_error_callback: EOF")); |
| 296 | + entity->_he_func(entity, entity->_he_cbdata, 1); |
| 297 | + //entity->he_flags.eof = 1; |
| 298 | + return; |
| 299 | + } |
| 300 | + |
| 301 | + entity->he_flags.error = 1; |
| 302 | + entity->_he_func(entity, entity->_he_cbdata, -1); |
| 303 | +} |
| 304 | + |
| 305 | +static int |
| 306 | +write_zlib_eof(entity) |
| 307 | + struct http_entity *entity; |
| 308 | +{ |
| 309 | + int zerr; |
| 310 | + unsigned char zbuf[16384]; |
| 311 | + int n; |
| 312 | + |
| 313 | + entity->_he_zbuf.avail_in = 0; |
| 314 | + entity->_he_zbuf.next_out = zbuf; |
| 315 | + entity->_he_zbuf.avail_out = sizeof zbuf; |
| 316 | + |
| 317 | + zerr = deflate(&entity->_he_zbuf, Z_FINISH); |
| 318 | + |
| 319 | + if (zerr != Z_STREAM_END) { |
| 320 | + wlog(WLOG_WARNING, "deflate: %s", zError(zerr)); |
| 321 | + deflateEnd(&entity->_he_zbuf); |
| 322 | + return -1; |
| 323 | + } |
| 324 | + n = sizeof zbuf - entity->_he_zbuf.avail_out; |
| 325 | + WDEBUG((WLOG_DEBUG, "writing zlib, append finish, left=%d avail=%d", |
| 326 | + n, entity->_he_zbuf.avail_out)); |
| 327 | + if (n) { |
| 328 | + bufferevent_enable(entity->_he_tobuf, EV_WRITE); |
| 329 | + if (entity->he_flags.chunked) |
| 330 | + evbuffer_add_printf(entity->_he_tobuf->output, "%x\r\n", n); |
| 331 | + bufferevent_write(entity->_he_tobuf, zbuf, n); |
| 332 | + if (entity->he_flags.chunked) |
| 333 | + evbuffer_add_printf(entity->_he_tobuf->output, "\r\n"); |
| 334 | + } |
| 335 | + deflateEnd(&entity->_he_zbuf); |
| 336 | + return 0; |
| 337 | +} |
| 338 | + |
| 339 | +static void |
| 340 | +entity_read_callback(be, d) |
| 341 | + struct bufferevent *be; |
| 342 | + void *d; |
| 343 | +{ |
| 344 | +struct http_entity *entity = d; |
| 345 | + int i; |
| 346 | +#define RD_BUFSZ 16386 |
| 347 | + char buf[RD_BUFSZ]; |
| 348 | + int wrote = 0, contdone = 0; |
| 349 | + |
| 350 | + /* |
| 351 | + * Data was available from the backend. If state is ENTITY_STATE_SEND_BODY, |
| 352 | + * we're moving the request from backend->client, so do that. Otherwise, |
| 353 | + * we're still reading header information. |
| 354 | + */ |
| 355 | + WDEBUG((WLOG_DEBUG, "entity_read_callback: called, source %d", |
| 356 | + entity->he_source.fde.fde->fde_fd)); |
| 357 | + |
| 358 | + if (entity->_he_state < ENTITY_STATE_SEND_BODY) { |
| 359 | + if ((i = parse_headers(entity)) < 0) { |
| 360 | + WDEBUG((WLOG_DEBUG, "entity_read_callback: parse_headers returned -1")); |
| 361 | + entity->he_flags.error = 1; |
| 362 | + entity->_he_func(entity, entity->_he_cbdata, i); |
| 363 | + return; |
| 364 | + } |
| 365 | + |
| 366 | + WDEBUG((WLOG_DEBUG, "parse_headers returned; client now %d", entity->_he_state)); |
| 367 | + |
| 368 | + if (entity->_he_state == ENTITY_STATE_DONE) { |
| 369 | + if (entity->he_flags.hdr_only) { |
| 370 | + WDEBUG((WLOG_DEBUG, "entity_read_callback: client is ENTITY_STATE_DONE")); |
| 371 | + bufferevent_disable(entity->_he_frombuf, EV_READ); |
| 372 | + entity->_he_func(entity, entity->_he_cbdata, 0); |
| 373 | + return; |
| 374 | + } else |
| 375 | + entity->_he_state = ENTITY_STATE_SEND_BODY; |
| 376 | + } |
| 377 | + //bufferevent_disable(entity->_he_frombuf, EV_READ); |
| 378 | + //if (entity->he_flags.hdr_only) |
| 379 | + return; |
| 380 | + } |
| 381 | + |
| 382 | + if (entity->he_flags.eof) { |
| 383 | + entity->_he_func(entity, entity->_he_cbdata, 0); |
| 384 | + return; |
| 385 | + } |
| 386 | + |
| 387 | + bufferevent_enable(entity->_he_tobuf, EV_WRITE); |
| 388 | + |
| 389 | + if (entity->he_flags.eof) { |
| 390 | + /* |
| 391 | + * This means the last chunk was written (see below). |
| 392 | + */ |
| 393 | + bufferevent_disable(entity->_he_frombuf, EV_READ); |
| 394 | + entity->_he_func(entity, entity->_he_cbdata, 0); |
| 395 | + bufferevent_enable(entity->_he_tobuf, EV_WRITE); |
| 396 | + return; |
| 397 | + } |
| 398 | + |
| 399 | + /* |
| 400 | + * While data is available, read it and forward. If we're using chunked encoding, |
| 401 | + * don't read past the end of the chunk. |
| 402 | + */ |
| 403 | + for (;;) { |
| 404 | + size_t read; |
| 405 | + size_t want = RD_BUFSZ; |
| 406 | + |
| 407 | + /* |
| 408 | + * If we're reading chunked data, check if we're starting a new chunk. |
| 409 | + */ |
| 410 | + if ((entity->he_te & TE_CHUNKED) && entity->_he_chunk_size == 0) { |
| 411 | + char *chunks; |
| 412 | + if ((chunks = evbuffer_readline(entity->_he_frombuf->input)) == NULL) |
| 413 | + return; |
| 414 | + entity->_he_chunk_size = strtol(chunks, NULL, 16); |
| 415 | + free(chunks); |
| 416 | + WDEBUG((WLOG_DEBUG, "new chunk, size=%d", entity->_he_chunk_size)); |
| 417 | + if (entity->_he_chunk_size == 0) { |
| 418 | + /* |
| 419 | + * Zero-sized chunk = end of request. |
| 420 | + * |
| 421 | + * If this client is receiving TE:chunked data, we have to write |
| 422 | + * the terminating block and finish up the next time round. If |
| 423 | + * not, mark it finished now. |
| 424 | + */ |
| 425 | + int more = 0; |
| 426 | + |
| 427 | + if (entity->he_encoding) { |
| 428 | + bufferevent_enable(entity->_he_tobuf, EV_WRITE); |
| 429 | + write_zlib_eof(entity); |
| 430 | + more = 1; |
| 431 | + } |
| 432 | + |
| 433 | + if (entity->he_flags.chunked) { |
| 434 | + bufferevent_disable(entity->_he_frombuf, EV_READ); |
| 435 | + bufferevent_enable(entity->_he_tobuf, EV_WRITE); |
| 436 | + bufferevent_write(entity->_he_tobuf, "0\r\n", 3); |
| 437 | + more = 1; |
| 438 | + } |
| 439 | + |
| 440 | + if (more) { |
| 441 | + entity->he_flags.eof = 1; |
| 442 | + return; |
| 443 | + } |
| 444 | + |
| 445 | + bufferevent_disable(entity->_he_frombuf, EV_READ); |
| 446 | + if (!wrote) |
| 447 | + entity->_he_func(entity, entity->_he_cbdata, 0); |
| 448 | + else |
| 449 | + bufferevent_enable(entity->_he_tobuf, EV_WRITE); |
| 450 | + return; |
| 451 | + } |
| 452 | + /* +2 for CRLF */ |
| 453 | + entity->_he_chunk_size += 2; |
| 454 | + } |
| 455 | + |
| 456 | + want = RD_BUFSZ; |
| 457 | + if (entity->_he_chunk_size) |
| 458 | + want = entity->_he_chunk_size; |
| 459 | + else if (entity->he_source.fde._wrt) |
| 460 | + want = entity->_he_chunk_size; |
| 461 | + |
| 462 | + want = entity->_he_chunk_size ? entity->_he_chunk_size : RD_BUFSZ; |
| 463 | + |
| 464 | + read = bufferevent_read(entity->_he_frombuf, buf, want); |
| 465 | + WDEBUG((WLOG_DEBUG, "rw %d, got %d wrote=%d wrt=%d", want, read, wrote, |
| 466 | + entity->he_source.fde._wrt)); |
| 467 | + if (read == 0) { |
| 468 | + if (!wrote) |
| 469 | + bufferevent_enable(entity->_he_frombuf, EV_READ); |
| 470 | + else |
| 471 | + bufferevent_disable(entity->_he_frombuf, EV_READ); |
| 472 | + return; |
| 473 | + } |
| 474 | + |
| 475 | + if (entity->_he_chunk_size) |
| 476 | + entity->_he_chunk_size -= read; |
| 477 | + if (entity->he_source.fde._wrt) { |
| 478 | + entity->he_source.fde._wrt -= read; |
| 479 | + if (entity->he_source.fde._wrt == 0) |
| 480 | + contdone = 1; |
| 481 | + } |
| 482 | + |
| 483 | + if ((entity->he_te & TE_CHUNKED) && entity->_he_chunk_size == 0) |
| 484 | + /* subtract the +2 we added above */ |
| 485 | + read -= 2; |
| 486 | + |
| 487 | + entity->he_size += read; |
| 488 | + |
| 489 | + if (entity->he_cache_callback) { |
| 490 | + entity->he_cache_callback(buf, read, entity->he_cache_callback_data); |
| 491 | + } |
| 492 | + bufferevent_enable(entity->_he_tobuf, EV_WRITE); |
| 493 | + |
| 494 | + if (write_data(entity, buf, read) == -1) { |
| 495 | + entity->_he_func(entity, entity->_he_cbdata, -1); |
| 496 | + return; |
| 497 | + } |
| 498 | + |
| 499 | + wrote++; |
| 500 | + if (contdone) { |
| 501 | + bufferevent_disable(entity->_he_frombuf, EV_READ); |
| 502 | + //entity->_he_func(entity, entity->_he_cbdata, 0); |
| 503 | + entity->he_flags.eof = 1; |
| 504 | + return; |
| 505 | + } |
| 506 | + } |
| 507 | + |
| 508 | + bufferevent_disable(entity->_he_frombuf, EV_READ); |
| 509 | +} |
| 510 | + |
| 511 | +static int |
| 512 | +write_data(entity, buf, len) |
| 513 | +struct http_entity *entity; |
| 514 | + void *buf; |
| 515 | + size_t len; |
| 516 | +{ |
| 517 | +static unsigned char zbuf[ZLIB_BLOCK * 2]; |
| 518 | + |
| 519 | + switch (entity->he_encoding) { |
| 520 | + case E_NONE: |
| 521 | + if (entity->he_flags.chunked) |
| 522 | + evbuffer_add_printf(entity->_he_tobuf->output, "%x\r\n", len); |
| 523 | + bufferevent_write(entity->_he_tobuf, buf, len); |
| 524 | + if (entity->he_flags.chunked) |
| 525 | + evbuffer_add_printf(entity->_he_tobuf->output, "\r\n"); |
| 526 | + return 0; |
| 527 | + |
| 528 | + case E_DEFLATE: case E_X_DEFLATE: case E_GZIP: case E_X_GZIP: { |
| 529 | + int zerr; |
| 530 | + |
| 531 | + entity->_he_zbuf.next_in = (unsigned char *)buf; |
| 532 | + entity->_he_zbuf.avail_in = len; |
| 533 | + entity->_he_zbuf.next_out = zbuf; |
| 534 | + entity->_he_zbuf.avail_out = sizeof zbuf; |
| 535 | + while (entity->_he_zbuf.avail_in) { |
| 536 | + zerr = deflate(&entity->_he_zbuf, Z_SYNC_FLUSH); |
| 537 | + if (zerr != Z_OK) { |
| 538 | + wlog(WLOG_WARNING, "deflate: %s", zError(zerr)); |
| 539 | + return -1; |
| 540 | + } |
| 541 | + WDEBUG((WLOG_DEBUG, "avail_in=%d avail_out=%d", |
| 542 | + entity->_he_zbuf.avail_in, entity->_he_zbuf.avail_out)); |
| 543 | + if (entity->he_flags.chunked) |
| 544 | + evbuffer_add_printf(entity->_he_tobuf->output, "%x\r\n", |
| 545 | + (sizeof zbuf - entity->_he_zbuf.avail_out)); |
| 546 | + bufferevent_write(entity->_he_tobuf, zbuf, |
| 547 | + (sizeof zbuf - entity->_he_zbuf.avail_out)); |
| 548 | + if (entity->he_flags.chunked) |
| 549 | + evbuffer_add_printf(entity->_he_tobuf->output, "\r\n"); |
| 550 | + entity->_he_zbuf.next_out = zbuf; |
| 551 | + entity->_he_zbuf.avail_out = sizeof zbuf; |
| 552 | + } |
| 553 | + return 0; |
| 554 | + } |
| 555 | + } |
| 556 | + abort(); |
| 557 | +} |
| 558 | + |
| 559 | +static void |
| 560 | +entity_send_target_read(struct bufferevent *buf, void *d) |
| 561 | +{ |
| 562 | + /* |
| 563 | + * Read from target possible. This never happens. |
| 564 | + */ |
| 565 | +} |
| 566 | + |
| 567 | +static void |
| 568 | +entity_send_target_write(struct bufferevent *buf, void *d) |
| 569 | +{ |
| 570 | +struct http_entity *entity = d; |
| 571 | +static char fbuf[ZLIB_BLOCK]; |
| 572 | + |
| 573 | + WDEBUG((WLOG_DEBUG, "entity_send_target_write: eof=%d, state=%d", |
| 574 | + entity->he_flags.eof, entity->_he_state)); |
| 575 | + |
| 576 | + if (entity->he_flags.eof) { |
| 577 | + if (entity->_he_frombuf) |
| 578 | + bufferevent_disable(entity->_he_frombuf, EV_READ); |
| 579 | + bufferevent_disable(entity->_he_tobuf, EV_WRITE); |
| 580 | + entity->_he_func(entity, entity->_he_cbdata, 0); |
| 581 | + return; |
| 582 | + } |
| 583 | + |
| 584 | + /* |
| 585 | + * Write to target completed. |
| 586 | + */ |
| 587 | + if (entity->_he_state == ENTITY_STATE_SEND_HDR) { |
| 588 | + /* |
| 589 | + * Sending headers completed. Decide what to do next. |
| 590 | + */ |
| 591 | + switch (entity->he_source_type) { |
| 592 | + case ENT_SOURCE_NONE: |
| 593 | + /* no body for this request */ |
| 594 | + WDEBUG((WLOG_DEBUG, "entity_send_target_write: no body, return immediately")); |
| 595 | + entity->_he_func(entity, entity->_he_cbdata, 0); |
| 596 | + return; |
| 597 | + |
| 598 | + case ENT_SOURCE_BUFFER: |
| 599 | + /* write buffer, callback when done */ |
| 600 | + WDEBUG((WLOG_DEBUG, "entity_send_target_write: source is buffer, %d bytes", |
| 601 | + entity->he_source.buffer.len)); |
| 602 | + entity->_he_state = ENTITY_STATE_SEND_BUF; |
| 603 | + bufferevent_write(entity->_he_tobuf, |
| 604 | + (void *)entity->he_source.buffer.addr, |
| 605 | + entity->he_source.buffer.len); |
| 606 | + return; |
| 607 | + |
| 608 | + case ENT_SOURCE_FILE: |
| 609 | + /* write file */ |
| 610 | + //entity->_he_tobuf = entity->_he_frombuf = NULL; |
| 611 | + |
| 612 | + /* |
| 613 | + * Compressed data can't be written using sendfile |
| 614 | + */ |
| 615 | + entity->_he_state = ENTITY_STATE_SEND_BODY; |
| 616 | + |
| 617 | + if (entity->he_encoding) { |
| 618 | + bufferevent_enable(entity->_he_tobuf, EV_WRITE); |
| 619 | + /* The write handler does this for us */ |
| 620 | + return; |
| 621 | + } |
| 622 | + |
| 623 | + bufferevent_disable(entity->_he_tobuf, EV_WRITE); |
| 624 | + |
| 625 | + if (wnet_sendfile(entity->_he_target->fde_fd, entity->he_source.fd.fd, |
| 626 | + entity->he_source.fd.size - entity->he_source.fd.off, |
| 627 | + entity->he_source.fd.off, entity_send_file_done, entity, 0) == -1) { |
| 628 | + entity->_he_func(entity, entity->_he_cbdata, -1); |
| 629 | + } |
| 630 | + return; |
| 631 | + } |
| 632 | + entity->_he_state = ENTITY_STATE_SEND_BODY; |
| 633 | + } |
| 634 | + |
| 635 | + if (entity->_he_state == ENTITY_STATE_SEND_BUF) { |
| 636 | + /* |
| 637 | + * Writing buffer completed. |
| 638 | + */ |
| 639 | + bufferevent_disable(entity->_he_tobuf, EV_WRITE); |
| 640 | + bufferevent_disable(entity->_he_frombuf, EV_READ); |
| 641 | + entity->_he_func(entity, entity->_he_cbdata, 0); |
| 642 | + return; |
| 643 | + } |
| 644 | + |
| 645 | + if (entity->he_source_type == ENT_SOURCE_FILE) { |
| 646 | + /* |
| 647 | + * We only get here if we're writing deflate data. |
| 648 | + */ |
| 649 | + ssize_t i; |
| 650 | + |
| 651 | + WDEBUG((WLOG_DEBUG, "write file, eof=%d, size=%d, off=%d", |
| 652 | + entity->he_flags.eof, entity->he_source.fd.size, |
| 653 | + entity->he_source.fd.off)); |
| 654 | + |
| 655 | + if (entity->he_flags.eof) { |
| 656 | + bufferevent_disable(entity->_he_tobuf, EV_WRITE); |
| 657 | + entity->_he_func(entity, entity->_he_cbdata, 0); |
| 658 | + return; |
| 659 | + } |
| 660 | + |
| 661 | + if ((i = read(entity->he_source.fd.fd, fbuf, sizeof fbuf)) == -1) { |
| 662 | + bufferevent_disable(entity->_he_tobuf, EV_WRITE); |
| 663 | + entity->_he_func(entity, entity->_he_cbdata, -1); |
| 664 | + return; |
| 665 | + } |
| 666 | + if (write_data(entity, fbuf, i) == -1) { |
| 667 | + bufferevent_disable(entity->_he_tobuf, EV_WRITE); |
| 668 | + entity->_he_func(entity, entity->_he_cbdata, -1); |
| 669 | + return; |
| 670 | + } |
| 671 | + entity->he_source.fd.off += i; |
| 672 | + if (entity->he_source.fd.off == entity->he_source.fd.size) { |
| 673 | + entity->he_flags.eof = 1; |
| 674 | + write_zlib_eof(entity); |
| 675 | + return; |
| 676 | + } |
| 677 | + |
| 678 | + return; |
| 679 | + } |
| 680 | + |
| 681 | + WDEBUG((WLOG_DEBUG, "entity_send_target_write: FDE")); |
| 682 | + |
| 683 | + /* |
| 684 | + * Otherwise, we're sending from an FDE, and the last write completed. |
| 685 | + */ |
| 686 | + bufferevent_enable(entity->_he_frombuf, EV_READ); |
| 687 | + bufferevent_disable(entity->_he_tobuf, EV_WRITE); |
| 688 | + if (!entity->he_flags.drained) { |
| 689 | + entity->he_flags.drained = 1; |
| 690 | + entity_read_callback(entity->_he_frombuf, entity); |
| 691 | + } |
| 692 | +} |
| 693 | + |
| 694 | +static void |
| 695 | +entity_send_target_error(struct bufferevent *buf, short err, void *d) |
| 696 | +{ |
| 697 | +struct http_entity *entity = d; |
| 698 | + |
| 699 | + /* |
| 700 | + * Writing to target produced an error. |
| 701 | + */ |
| 702 | + entity->_he_func(entity, entity->_he_cbdata, -1); |
| 703 | +} |
| 704 | + |
| 705 | +/*ARGSUSED*/ |
| 706 | +static void |
| 707 | +entity_send_file_done(fde, data, res) |
| 708 | + struct fde *fde; |
| 709 | + void *data; |
| 710 | + int res; |
| 711 | +{ |
| 712 | +struct http_entity *entity = data; |
| 713 | + |
| 714 | + WDEBUG((WLOG_DEBUG, "entity_send_file_done: called for %d [%s], res=%d", |
| 715 | + fde->fde_fd, fde->fde_desc, res)); |
| 716 | + entity->_he_func(entity, entity->_he_cbdata, res); |
| 717 | + return; |
| 718 | +} |
| 719 | + |
| 720 | +static int |
| 721 | +validhost(host) |
| 722 | + const char *host; |
| 723 | +{ |
| 724 | + for (; *host; ++host) { |
| 725 | + if (!(char_table[(unsigned char)*host] & CHAR_HOST)) |
| 726 | + return 0; |
| 727 | + } |
| 728 | + return 1; |
| 729 | +} |
| 730 | + |
| 731 | +static int |
| 732 | +via_includes_me(s) |
| 733 | + const char *s; |
| 734 | +{ |
| 735 | + char *orig = wstrdup(s); |
| 736 | + char *via = orig, *comma, *space; |
| 737 | + |
| 738 | + do { |
| 739 | + comma = strchr(via, ','); |
| 740 | + if (comma) |
| 741 | + *comma++ = '\0'; |
| 742 | + via = strchr(via, ' '); |
| 743 | + if (!via) |
| 744 | + break; |
| 745 | + while (*via == ' ') |
| 746 | + ++via; |
| 747 | + space = strchr(via, ' '); |
| 748 | + if (!space) { |
| 749 | + wfree(orig); |
| 750 | + return 0; |
| 751 | + } |
| 752 | + *space = '\0'; |
| 753 | + if (!strcmp(via, my_hostname)) { |
| 754 | + wfree(orig); |
| 755 | + return 1; |
| 756 | + } |
| 757 | + via = comma; |
| 758 | + } while (comma); |
| 759 | + wfree(orig); |
| 760 | + return 0; |
| 761 | +} |
| 762 | + |
| 763 | +#ifdef __lint |
| 764 | +# pragma error_messages(off, E_GLOBAL_COULD_BE_STATIC) |
| 765 | +#endif |
| 766 | +/* |
| 767 | + * Header handling. |
| 768 | + */ |
| 769 | +void |
| 770 | +header_free(head) |
| 771 | + struct header_list *head; |
| 772 | +{ |
| 773 | +struct header_list *next = head->hl_next; |
| 774 | + |
| 775 | + while (next) { |
| 776 | + struct header_list *this = next; |
| 777 | + next = this->hl_next; |
| 778 | + wfree((char *)this->hl_name); |
| 779 | + wfree((char *)this->hl_value); |
| 780 | + wfree(this); |
| 781 | + } |
| 782 | + |
| 783 | + bzero(head, sizeof(*head)); |
| 784 | +} |
| 785 | + |
| 786 | +#ifdef __lint |
| 787 | +# pragma error_messages(default, E_GLOBAL_COULD_BE_STATIC) |
| 788 | +#endif |
| 789 | + |
| 790 | +void |
| 791 | +header_add(head, name, value) |
| 792 | + struct header_list *head; |
| 793 | + char *name, *value; |
| 794 | +{ |
| 795 | +struct header_list *new = head; |
| 796 | + |
| 797 | + head->hl_num++; |
| 798 | + |
| 799 | + if (head->hl_tail) |
| 800 | + new = head->hl_tail; |
| 801 | + else |
| 802 | + while (new->hl_next) |
| 803 | + new = new->hl_next; |
| 804 | + new->hl_next = wmalloc(sizeof(*head->hl_next)); |
| 805 | + head->hl_tail = new->hl_next; |
| 806 | + head->hl_len += strlen(name) + strlen(value) + 4; |
| 807 | + new = new->hl_next; |
| 808 | + new->hl_name = name; |
| 809 | + new->hl_value = value; |
| 810 | + new->hl_next = new->hl_tail = NULL; |
| 811 | + new->hl_flags = 0; |
| 812 | +} |
| 813 | + |
| 814 | +void |
| 815 | +header_append_last(head, append) |
| 816 | + struct header_list *head; |
| 817 | + const char *append; |
| 818 | +{ |
| 819 | +struct header_list *last = head; |
| 820 | + char *cur; |
| 821 | + |
| 822 | + assert(last->hl_next); |
| 823 | + |
| 824 | + while (last->hl_next) |
| 825 | + last = last->hl_next; |
| 826 | + |
| 827 | + cur = last->hl_value; |
| 828 | + last->hl_value = wmalloc(strlen(cur) + 1 + strlen(append) + 1); |
| 829 | + sprintf(last->hl_value, "%s %s", cur, append); |
| 830 | + wfree(cur); |
| 831 | +} |
| 832 | + |
| 833 | + |
| 834 | +void |
| 835 | +header_remove(head, it) |
| 836 | + struct header_list *head, *it; |
| 837 | +{ |
| 838 | +struct header_list *jt; |
| 839 | + |
| 840 | + jt = head; |
| 841 | + while (jt->hl_next && jt->hl_next != it) |
| 842 | + jt = jt->hl_next; |
| 843 | + jt->hl_next = jt->hl_next->hl_next; |
| 844 | + if (it == head->hl_tail) |
| 845 | + head->hl_tail = jt; |
| 846 | + wfree(it->hl_name); |
| 847 | + wfree(it->hl_value); |
| 848 | + wfree(it); |
| 849 | +} |
| 850 | + |
| 851 | +struct header_list * |
| 852 | +header_find(head, name) |
| 853 | + struct header_list *head; |
| 854 | + const char *name; |
| 855 | +{ |
| 856 | +struct header_list *it; |
| 857 | + |
| 858 | + for (it = head->hl_next; it; it = it->hl_next) |
| 859 | + if (!strcasecmp(name, it->hl_name)) |
| 860 | + return it; |
| 861 | + return NULL; |
| 862 | +} |
| 863 | + |
| 864 | +#ifdef __lint |
| 865 | +# pragma error_messages(off, E_GLOBAL_COULD_BE_STATIC) |
| 866 | +#endif |
| 867 | +char * |
| 868 | +header_build(head) |
| 869 | + struct header_list *head; |
| 870 | +{ |
| 871 | + char *buf; |
| 872 | + size_t bufsz; |
| 873 | + size_t buflen = 0; |
| 874 | + |
| 875 | + bufsz = head->hl_len + 3; |
| 876 | + if ((buf = wmalloc(bufsz)) == NULL) |
| 877 | + outofmemory(); |
| 878 | + |
| 879 | + *buf = '\0'; |
| 880 | + while (head->hl_next) { |
| 881 | + head = head->hl_next; |
| 882 | + buflen += snprintf(buf + buflen, bufsz - buflen - 1, "%s: %s\r\n", head->hl_name, head->hl_value); |
| 883 | + } |
| 884 | + if (strlcat(buf, "\r\n", bufsz) >= bufsz) |
| 885 | + abort(); |
| 886 | + |
| 887 | + return buf; |
| 888 | +} |
| 889 | +#ifdef __lint |
| 890 | +# pragma error_messages(default, E_GLOBAL_COULD_BE_STATIC) |
| 891 | +#endif |
| 892 | + |
| 893 | +void |
| 894 | +header_dump(head, fd) |
| 895 | + struct header_list *head; |
| 896 | + int fd; |
| 897 | +{ |
| 898 | + int i = 0; |
| 899 | +struct header_list *h; |
| 900 | + |
| 901 | + h = head->hl_next; |
| 902 | + while (h) { |
| 903 | + h = h->hl_next; |
| 904 | + ++i; |
| 905 | + } |
| 906 | + |
| 907 | + write(fd, &i, sizeof(i)); |
| 908 | + |
| 909 | + while (head->hl_next) { |
| 910 | + int i, j; |
| 911 | + head = head->hl_next; |
| 912 | + i = strlen(head->hl_name); |
| 913 | + write(fd, &i, sizeof(i)); |
| 914 | + j = strlen(head->hl_value); |
| 915 | + write(fd, &j, sizeof(j)); |
| 916 | + write(fd, head->hl_name, i); |
| 917 | + write(fd, head->hl_value, j); |
| 918 | + } |
| 919 | +} |
| 920 | + |
| 921 | +int |
| 922 | +header_undump(head, fd, len) |
| 923 | + struct header_list *head; |
| 924 | + int fd; |
| 925 | + off_t *len; |
| 926 | +{ |
| 927 | + int i = 0, j = 0, n = 0; |
| 928 | +struct header_list *it = head; |
| 929 | + ssize_t r; |
| 930 | + |
| 931 | + *len = 0; |
| 932 | + bzero(head, sizeof(*head)); |
| 933 | + if ((r = read(fd, &n, sizeof(n))) < 0) { |
| 934 | + wlog(WLOG_WARNING, "reading cache file: %s", strerror(errno)); |
| 935 | + return -1; /* XXX */ |
| 936 | + } |
| 937 | + |
| 938 | + *len += r; |
| 939 | + WDEBUG((WLOG_DEBUG, "header_undump: %d entries", n)); |
| 940 | + |
| 941 | + while (n--) { |
| 942 | + char *n, *v, *s; |
| 943 | + int k; |
| 944 | + |
| 945 | + if ((it->hl_next = wcalloc(1, sizeof(struct header_list))) == NULL) |
| 946 | + outofmemory(); |
| 947 | + it = it->hl_next; |
| 948 | + *len += read(fd, &i, sizeof(i)); |
| 949 | + *len += read(fd, &j, sizeof(j)); |
| 950 | + WDEBUG((WLOG_DEBUG, "header_undump: i=%d j=%d", i, j)); |
| 951 | + n = wmalloc(i + j + 2); |
| 952 | + i = read(fd, n, i); |
| 953 | + *len += i; |
| 954 | + s = n + i; |
| 955 | + *s++ = '\0'; |
| 956 | + v = s; |
| 957 | + k = read(fd, s, j); |
| 958 | + *len += k; |
| 959 | + s += k; |
| 960 | + *s = '\0'; |
| 961 | + it->hl_name = n; |
| 962 | + it->hl_value = wstrdup(v); |
| 963 | + head->hl_len += i + j + 4; |
| 964 | + } |
| 965 | + |
| 966 | + head->hl_tail = it; |
| 967 | + return 0; |
| 968 | +} |
| 969 | + |
| 970 | +static int |
| 971 | +parse_headers(entity) |
| 972 | + struct http_entity *entity; |
| 973 | +{ |
| 974 | + char *line; |
| 975 | + |
| 976 | + while ((line = evbuffer_readline(entity->_he_frombuf->input)) != NULL) { |
| 977 | + char **hdr = NULL; |
| 978 | + char *value = NULL; |
| 979 | + int error = 1; |
| 980 | + |
| 981 | + if (!line) |
| 982 | + return 0; |
| 983 | + |
| 984 | + if (!*line) { |
| 985 | + if (!entity->he_reqstr) { |
| 986 | + free(line); |
| 987 | + return ENT_ERR_INVREQ; |
| 988 | + } |
| 989 | + |
| 990 | + entity->_he_state = ENTITY_STATE_DONE; |
| 991 | + free(line); |
| 992 | + return 0; |
| 993 | + } |
| 994 | + |
| 995 | + switch(entity->_he_state) { |
| 996 | + case ENTITY_STATE_START: |
| 997 | + entity->he_reqstr = wstrdup(line); |
| 998 | + if (parse_reqtype(entity) == -1) { |
| 999 | + free(line); |
| 1000 | + return ENT_ERR_INVREQ; |
| 1001 | + } |
| 1002 | + entity->_he_state = ENTITY_STATE_HDR; |
| 1003 | + break; |
| 1004 | + case ENTITY_STATE_HDR: |
| 1005 | + if (isspace(*line)) { |
| 1006 | + char *s = line; |
| 1007 | + if (!entity->he_headers.hl_next) { |
| 1008 | + error = -1; |
| 1009 | + goto error; |
| 1010 | + } |
| 1011 | + while (isspace(*s)) |
| 1012 | + s++; |
| 1013 | + header_append_last(&entity->he_headers, s); |
| 1014 | + free(line); |
| 1015 | + continue; |
| 1016 | + } |
| 1017 | + |
| 1018 | + hdr = wstrvec(line, ":", 2); |
| 1019 | + |
| 1020 | + if (!hdr[0] || !hdr[1]) { |
| 1021 | + error = ENT_ERR_INVREQ; |
| 1022 | + goto error; |
| 1023 | + } |
| 1024 | + |
| 1025 | + value = hdr[1]; |
| 1026 | + while (isspace(*value)) |
| 1027 | + ++value; |
| 1028 | + value = wstrdup(value); |
| 1029 | + |
| 1030 | + WDEBUG((WLOG_DEBUG, "header: from [%s], [%s] = [%s]", |
| 1031 | + line, hdr[0], value)); |
| 1032 | + |
| 1033 | + if (++entity->he_headers.hl_num > MAX_HEADERS) { |
| 1034 | + error = ENT_ERR_2MANY; |
| 1035 | + goto error; |
| 1036 | + } |
| 1037 | + if (!strcasecmp(hdr[0], "Host")) { |
| 1038 | + if (!validhost(value)) { |
| 1039 | + error = ENT_ERR_INVHOST; |
| 1040 | + goto error; |
| 1041 | + } |
| 1042 | + header_add(&entity->he_headers, wstrdup(hdr[0]), value); |
| 1043 | + entity->he_rdata.request.host = wstrdup(value); |
| 1044 | + } else if (!strcasecmp(hdr[0], "Content-Length")) { |
| 1045 | + header_add(&entity->he_headers, wstrdup(hdr[0]), value); |
| 1046 | + entity->he_rdata.request.contlen = atoi(value); |
| 1047 | + } else if (!strcasecmp(hdr[0], "Via")) { |
| 1048 | + if (via_includes_me(value)) { |
| 1049 | + error = ENT_ERR_LOOP; |
| 1050 | + goto error; |
| 1051 | + } |
| 1052 | + header_add(&entity->he_headers, wstrdup(hdr[0]), value); |
| 1053 | + } else if (!strcasecmp(hdr[0], "transfer-encoding")) { |
| 1054 | + /* XXX */ |
| 1055 | + if (!strcasecmp(value, "chunked")) { |
| 1056 | + entity->he_te |= TE_CHUNKED; |
| 1057 | + } |
| 1058 | + /* Don't forward transfer-encoding... */ |
| 1059 | + wfree(value); |
| 1060 | + } else if (!strcasecmp(hdr[0], "Accept-Encoding")) { |
| 1061 | + if (!entity->he_flags.response && |
| 1062 | + qvalue_parse(&entity->he_rdata.request.accept_encoding, value) == -1) { |
| 1063 | + error = -1; |
| 1064 | + WDEBUG((WLOG_DEBUG, "a-e parse failed")); |
| 1065 | + goto error; |
| 1066 | + } |
| 1067 | + } else if (!strcasecmp(hdr[0], "Pragma")) { |
| 1068 | + entity->he_h_pragma = wstrdup(value); |
| 1069 | + header_add(&entity->he_headers, wstrdup(hdr[0]), entity->he_h_pragma); |
| 1070 | + } else if (!strcasecmp(hdr[0], "Cache-Control")) { |
| 1071 | + entity->he_h_cache_control = wstrdup(value); |
| 1072 | + header_add(&entity->he_headers, wstrdup(hdr[0]), entity->he_h_cache_control); |
| 1073 | + } else if (!strcasecmp(hdr[0], "If-Modified-Since")) { |
| 1074 | + entity->he_h_if_modified_since = wstrdup(value); |
| 1075 | + header_add(&entity->he_headers, wstrdup(hdr[0]), entity->he_h_if_modified_since); |
| 1076 | + } else if (!strcasecmp(hdr[0], "Transfer-Encoding")) { |
| 1077 | + entity->he_h_transfer_encoding = wstrdup(value); |
| 1078 | + header_add(&entity->he_headers, wstrdup(hdr[0]), entity->he_h_transfer_encoding); |
| 1079 | + } else if (!strcasecmp(hdr[0], "Last-Modified")) { |
| 1080 | + entity->he_h_last_modified = wstrdup(value); |
| 1081 | + header_add(&entity->he_headers, wstrdup(hdr[0]), entity->he_h_last_modified); |
| 1082 | + } else |
| 1083 | + header_add(&entity->he_headers, wstrdup(hdr[0]), value); |
| 1084 | + |
| 1085 | + wstrvecfree(hdr); |
| 1086 | + break; |
| 1087 | + error: |
| 1088 | + if (hdr) |
| 1089 | + wstrvecfree(hdr); |
| 1090 | + if (value) |
| 1091 | + wfree(value); |
| 1092 | + free(line); |
| 1093 | + return -1; |
| 1094 | + } |
| 1095 | + |
| 1096 | + free(line); |
| 1097 | + } |
| 1098 | + return 0; |
| 1099 | +} |
| 1100 | + |
| 1101 | +static int |
| 1102 | +parse_reqtype(entity) |
| 1103 | + struct http_entity *entity; |
| 1104 | +{ |
| 1105 | + char *p, *s, *t; |
| 1106 | + char *request = entity->he_reqstr;; |
| 1107 | + int i; |
| 1108 | + |
| 1109 | + WDEBUG((WLOG_DEBUG, "parse_reqtype: called, response=%d", (int)entity->he_flags.response)); |
| 1110 | + |
| 1111 | + /* |
| 1112 | + * These probably shouldn't be handled in the same function. |
| 1113 | + */ |
| 1114 | + if (entity->he_flags.response) { |
| 1115 | + /* |
| 1116 | + * HTTP/1.0 |
| 1117 | + */ |
| 1118 | + if ((p = strchr(request, ' ')) == NULL) |
| 1119 | + return -1; |
| 1120 | + *p++ = '\0'; |
| 1121 | + |
| 1122 | + /* 200 */ |
| 1123 | + if ((s = strchr(p, ' ')) == NULL) |
| 1124 | + return -1; |
| 1125 | + *s++ = '\0'; |
| 1126 | + entity->he_rdata.response.status = atoi(p); |
| 1127 | + |
| 1128 | + /* OK */ |
| 1129 | + entity->he_rdata.response.status_str = s; |
| 1130 | + |
| 1131 | + WDEBUG((WLOG_DEBUG, "parse_reqtype: \"%s\" \"%d\" \"%s\"", |
| 1132 | + request, entity->he_rdata.response.status, |
| 1133 | + entity->he_rdata.response.status_str)); |
| 1134 | + return 0; |
| 1135 | + } |
| 1136 | + |
| 1137 | + /* GET */ |
| 1138 | + if ((p = strchr(request, ' ')) == NULL) |
| 1139 | + return -1; |
| 1140 | + |
| 1141 | + *p++ = '\0'; |
| 1142 | + |
| 1143 | + for (i = 0; supported_reqtypes[i].name; i++) |
| 1144 | + if (!strcmp(request, supported_reqtypes[i].name)) |
| 1145 | + break; |
| 1146 | + |
| 1147 | + entity->he_rdata.request.reqtype = supported_reqtypes[i].type; |
| 1148 | + if (entity->he_rdata.request.reqtype == REQTYPE_INVALID) |
| 1149 | + return -1; |
| 1150 | + |
| 1151 | + /* /path/to/file */ |
| 1152 | + if ((s = strchr(p, ' ')) == NULL) |
| 1153 | + return -1; |
| 1154 | + |
| 1155 | + *s++ = '\0'; |
| 1156 | + if (*p != '/') { |
| 1157 | + /* |
| 1158 | + * This normally means the request URI was of the form |
| 1159 | + * "http://host.tld/file". Clients don't send this, but |
| 1160 | + * Squid does when it thinks we're a proxy. |
| 1161 | + * |
| 1162 | + * Extract the host and set it now. If there's another host |
| 1163 | + * later, it'll overwrite it, but if the client sends two |
| 1164 | + * different hosts it's probably broken anyway... |
| 1165 | + * |
| 1166 | + * We could handle non-http URIs here, but there's not much |
| 1167 | + * points, the backend will reject it anyway. |
| 1168 | + */ |
| 1169 | + if (strncmp(p, "http://", 7)) |
| 1170 | + return -1; |
| 1171 | + p += 7; |
| 1172 | + t = strchr(p, '/'); |
| 1173 | + if (t == NULL) |
| 1174 | + return -1; |
| 1175 | + entity->he_rdata.request.path = wstrdup(t); |
| 1176 | + *t = '\0'; |
| 1177 | + entity->he_rdata.request.host = p; |
| 1178 | + } else { |
| 1179 | + entity->he_rdata.request.path = wstrdup(p); |
| 1180 | + } |
| 1181 | + |
| 1182 | + /* HTTP/1.0 */ |
| 1183 | + if (sscanf(s, "HTTP/%d.%d", &entity->he_rdata.request.httpmaj, |
| 1184 | + &entity->he_rdata.request.httpmin) != 2) |
| 1185 | + return -1; |
| 1186 | + |
| 1187 | + return 0; |
| 1188 | +} |
| 1189 | + |
| 1190 | +int |
| 1191 | +qvalue_parse(list, header) |
| 1192 | + struct qvalue_head *list; |
| 1193 | + const char *header; |
| 1194 | +{ |
| 1195 | + char **values; |
| 1196 | + char **value; |
| 1197 | + |
| 1198 | + TAILQ_INIT(list); |
| 1199 | + |
| 1200 | + values = wstrvec(header, ",", 0); |
| 1201 | + for (value = values; *value; ++value) { |
| 1202 | + char **bits; |
| 1203 | + struct qvalue *entry; |
| 1204 | + bits = wstrvec(*value, ";", 0); |
| 1205 | + entry = wcalloc(1, sizeof(*entry)); |
| 1206 | + entry->name = wstrdup(bits[0]); |
| 1207 | + if (bits[1]) |
| 1208 | + entry->val = atof(bits[1]); |
| 1209 | + else |
| 1210 | + entry->val = 1.0; |
| 1211 | + TAILQ_INSERT_TAIL(list, entry, entries); |
| 1212 | + wstrvecfree(bits); |
| 1213 | + } |
| 1214 | + |
| 1215 | + wstrvecfree(values); |
| 1216 | + return 0; |
| 1217 | +} |
| 1218 | + |
| 1219 | +struct qvalue * |
| 1220 | +qvalue_remove_best(list) |
| 1221 | + struct qvalue_head *list; |
| 1222 | +{ |
| 1223 | + struct qvalue *entry, *best = NULL; |
| 1224 | + float bestf = 0; |
| 1225 | + TAILQ_FOREACH(entry, list, entries) { |
| 1226 | + if (entry->val > bestf) { |
| 1227 | + best = entry; |
| 1228 | + bestf = entry->val; |
| 1229 | + } |
| 1230 | + } |
| 1231 | + if (!best) |
| 1232 | + return NULL; |
| 1233 | + TAILQ_REMOVE(list, best, entries); |
| 1234 | + return best; |
| 1235 | +} |
| 1236 | + |
| 1237 | +enum encoding |
| 1238 | +accept_encoding(enc) |
| 1239 | + const char *enc; |
| 1240 | +{ |
| 1241 | +static struct { |
| 1242 | + const char *name; |
| 1243 | + enum encoding value; |
| 1244 | +} encs[] = { |
| 1245 | + { "deflate", E_DEFLATE }, |
| 1246 | + { "x-deflate", E_X_DEFLATE }, |
| 1247 | + { "identity", E_NONE }, |
| 1248 | + { "gzip", E_GZIP }, |
| 1249 | + { "x-gzip", E_X_GZIP }, |
| 1250 | + { NULL, 0 } |
| 1251 | +}, *s; |
| 1252 | + for (s = encs; s->name; s++) |
| 1253 | + if (!strcasecmp(s->name, enc)) |
| 1254 | + return s->value; |
| 1255 | + return 0; |
| 1256 | +} |
Index: trunk/willow/src/willow/daemon.c |
— | — | @@ -0,0 +1,71 @@ |
| 2 | +/* $Header$ */ |
| 3 | +/* $NetBSD: daemon.c,v 1.9 2003/08/07 16:42:46 agc Exp $ */ |
| 4 | + |
| 5 | +/*- |
| 6 | + * Copyright (c) 1990, 1993 |
| 7 | + * The Regents of the University of California. All rights reserved. |
| 8 | + * |
| 9 | + * Redistribution and use in source and binary forms, with or without |
| 10 | + * modification, are permitted provided that the following conditions |
| 11 | + * are met: |
| 12 | + * 1. Redistributions of source code must retain the above copyright |
| 13 | + * notice, this list of conditions and the following disclaimer. |
| 14 | + * 2. Redistributions in binary form must reproduce the above copyright |
| 15 | + * notice, this list of conditions and the following disclaimer in the |
| 16 | + * documentation and/or other materials provided with the distribution. |
| 17 | + * 3. Neither the name of the University nor the names of its contributors |
| 18 | + * may be used to endorse or promote products derived from this software |
| 19 | + * without specific prior written permission. |
| 20 | + * |
| 21 | + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
| 22 | + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 23 | + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 24 | + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
| 25 | + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 26 | + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| 27 | + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| 28 | + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| 29 | + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| 30 | + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| 31 | + * SUCH DAMAGE. |
| 32 | + */ |
| 33 | + |
| 34 | +#if defined __SUNPRO_C || defined __DECC || defined __HP_cc |
| 35 | +# pragma ident "@(#)$Header$" |
| 36 | +# pragma ident "$NetBSD: daemon.c,v 1.9 2003/08/07 16:42:46 agc Exp $" |
| 37 | +#endif |
| 38 | + |
| 39 | +#include <fcntl.h> |
| 40 | +#include <stdlib.h> |
| 41 | +#include <unistd.h> |
| 42 | + |
| 43 | +int |
| 44 | +daemon(nochdir, noclose) |
| 45 | + int nochdir, noclose; |
| 46 | +{ |
| 47 | + int fd; |
| 48 | + |
| 49 | + switch (fork()) { |
| 50 | + case -1: |
| 51 | + return (-1); |
| 52 | + case 0: |
| 53 | + break; |
| 54 | + default: |
| 55 | + _exit(0); |
| 56 | + } |
| 57 | + |
| 58 | + if (setsid() == -1) |
| 59 | + return (-1); |
| 60 | + |
| 61 | + if (!nochdir) |
| 62 | + (void)chdir("/"); |
| 63 | + |
| 64 | + if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) { |
| 65 | + (void)dup2(fd, STDIN_FILENO); |
| 66 | + (void)dup2(fd, STDOUT_FILENO); |
| 67 | + (void)dup2(fd, STDERR_FILENO); |
| 68 | + if (fd > STDERR_FILENO) |
| 69 | + (void)close(fd); |
| 70 | + } |
| 71 | + return (0); |
| 72 | +} |
Index: trunk/willow/src/willow/wlogwriter.c |
— | — | @@ -0,0 +1,83 @@ |
| 2 | +/* @(#) $Header$ */ |
| 3 | +/* This source code is in the public domain. */ |
| 4 | +/* |
| 5 | + * Willow: Lightweight HTTP reverse-proxy. |
| 6 | + * wlogwriter: child process for log writing. |
| 7 | + */ |
| 8 | + |
| 9 | +#if defined __SUNPRO_C || defined __DECC || defined __HP_cc |
| 10 | +# pragma ident "@(#)$Header$" |
| 11 | +#endif |
| 12 | + |
| 13 | +#include <stdio.h> |
| 14 | +#include <unistd.h> |
| 15 | +#include <errno.h> |
| 16 | +#include <string.h> |
| 17 | +#include <stdlib.h> |
| 18 | +#include <signal.h> |
| 19 | + |
| 20 | +#include "willow.h" |
| 21 | +#include "wlogwriter.h" |
| 22 | +#include "wconfig.h" |
| 23 | +#include "wlog.h" |
| 24 | + |
| 25 | +static void wlogwriter_run(int); |
| 26 | + |
| 27 | +void |
| 28 | +wlogwriter_start(fd) |
| 29 | + int *fd; |
| 30 | +{ |
| 31 | + switch (fork()) { |
| 32 | + case -1: |
| 33 | + wlog(WLOG_ERROR, "fork: %s", strerror(errno)); |
| 34 | + exit(8); |
| 35 | + /*NOTREACHED*/ |
| 36 | + case 0: |
| 37 | + (void)close(fd[0]); |
| 38 | + wlogwriter_run(fd[1]); |
| 39 | + break; |
| 40 | + default: |
| 41 | + (void)close(fd[1]); |
| 42 | + break; |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +static void |
| 47 | +wlogwriter_run(pipe) |
| 48 | + int pipe; |
| 49 | +{ |
| 50 | + FILE *inf, *outf; |
| 51 | + char *line; |
| 52 | + size_t lnsz; |
| 53 | + |
| 54 | + lnsz = 8192; |
| 55 | + line = malloc(lnsz); |
| 56 | + |
| 57 | + (void)signal(SIGPIPE, SIG_IGN); |
| 58 | + |
| 59 | +#ifdef HAVE_SETPROCTITLE |
| 60 | + setproctitle("log writer: %s", config.access_log); |
| 61 | +#endif |
| 62 | + wlog(WLOG_NOTICE, "wlogwriter starting (pid %d) for %s", (int)getpid(), config.access_log); |
| 63 | + |
| 64 | + if ((inf = fdopen(pipe, "r")) == NULL) { |
| 65 | + perror("wlogwriter: fdopen"); |
| 66 | + exit(8); |
| 67 | + } |
| 68 | + /*LINTED unsafe fopen*/ |
| 69 | + if ((outf = fopen(config.access_log, "a")) == NULL) { |
| 70 | + perror(config.access_log); |
| 71 | + exit(8); |
| 72 | + } |
| 73 | + |
| 74 | + while (fgets(line, lnsz, inf)) { |
| 75 | + if (fputs(line, outf) == EOF || fflush(outf) == EOF) { |
| 76 | + wlog(WLOG_NOTICE, "fatal: writing access log: %s", strerror(errno)); |
| 77 | + exit(8); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + wlog(WLOG_NOTICE, "wlogwriter terminating"); |
| 82 | + exit(0); |
| 83 | +} |
| 84 | + |
Index: trunk/willow/src/willow/whttp.c |
— | — | @@ -0,0 +1,848 @@ |
| 2 | +/* @(#) $Header$ */ |
| 3 | +/* This source code is in the public domain. */ |
| 4 | +/* |
| 5 | + * Willow: Lightweight HTTP reverse-proxy. |
| 6 | + * whttp: HTTP implementation. |
| 7 | + */ |
| 8 | + |
| 9 | +#if defined __SUNPRO_C || defined __DECC || defined __HP_cc |
| 10 | +# pragma ident "@(#)$Header$" |
| 11 | +#endif |
| 12 | + |
| 13 | +/* |
| 14 | + * The logic of whttp is explained in whttp_entity.c |
| 15 | + */ |
| 16 | + |
| 17 | +#define _GNU_SOURCE /* glibc strptime */ |
| 18 | + |
| 19 | +#include <sys/types.h> |
| 20 | +#include <sys/stat.h> |
| 21 | +#include <sys/param.h> |
| 22 | + |
| 23 | +#include <stdlib.h> |
| 24 | +#include <stdio.h> |
| 25 | +#include <string.h> |
| 26 | +#include <unistd.h> |
| 27 | +#include <errno.h> |
| 28 | +#include <netdb.h> |
| 29 | +#include <fcntl.h> |
| 30 | +#include <strings.h> |
| 31 | +#include <assert.h> |
| 32 | +#include <time.h> |
| 33 | + |
| 34 | +#include "willow.h" |
| 35 | +#include "whttp.h" |
| 36 | +#include "wnet.h" |
| 37 | +#include "wbackend.h" |
| 38 | +#include "wconfig.h" |
| 39 | +#include "wlogwriter.h" |
| 40 | +#include "whttp_entity.h" |
| 41 | +#include "wlog.h" |
| 42 | +#include "wcache.h" |
| 43 | + |
| 44 | +#ifndef MAXHOSTNAMELEN |
| 45 | +# define MAXHOSTNAMELEN HOST_NAME_MAX /* SysV / BSD disagreement */ |
| 46 | +#endif |
| 47 | + |
| 48 | +/* |
| 49 | + * Error handling. |
| 50 | + */ |
| 51 | +#define ERR_GENERAL 0 |
| 52 | +#define ERR_BADREQUEST 1 |
| 53 | +#define ERR_BADRESPONSE 2 |
| 54 | +#define ERR_CACHE_IO 3 |
| 55 | + |
| 56 | +static const char *error_files[] = { |
| 57 | + /* ERR_GENERAL */ DATADIR "/errors/ERR_GENERAL", |
| 58 | + /* ERR_BADREQUEST */ DATADIR "/errors/ERR_BADREQUEST", |
| 59 | + /* ERR_BADRESPONSE */ DATADIR "/errors/ERR_BADRESPONSE", |
| 60 | + /* ERR_CACHE_IO */ DATADIR "/errors/ERR_CACHE_IO", |
| 61 | +}; |
| 62 | + |
| 63 | +const char *request_string[] = { |
| 64 | + "GET", |
| 65 | + "POST", |
| 66 | + "HEAD", |
| 67 | + "TRACE", |
| 68 | + "OPTIONS", |
| 69 | +}; |
| 70 | + |
| 71 | +struct request_type supported_reqtypes[] = { |
| 72 | + { "GET", 3, REQTYPE_GET }, |
| 73 | + { "POST", 4, REQTYPE_POST }, |
| 74 | + { "HEAD", 4, REQTYPE_HEAD }, |
| 75 | + { "TRACE", 5, REQTYPE_TRACE }, |
| 76 | + { "OPTIONS", 7, REQTYPE_OPTIONS }, |
| 77 | + { NULL, 0, REQTYPE_INVALID } |
| 78 | +}; |
| 79 | + |
| 80 | +struct http_client { |
| 81 | +struct fde *cl_fde; /* backref to fd */ |
| 82 | + int cl_reqtype; /* request type or 0 */ |
| 83 | + char *cl_path; /* path they want */ |
| 84 | + char *cl_wrtbuf; /* write buf (either to client or be) */ |
| 85 | +struct backend *cl_backend; /* backend servicing this client */ |
| 86 | +struct fde *cl_backendfde; /* fde for backend */ |
| 87 | +struct http_entity cl_entity; /* reply to send back */ |
| 88 | + |
| 89 | + /* Cache-related data */ |
| 90 | + int cl_cfd; /* FD of cache file for writing, or 0 */ |
| 91 | +struct cache_object *cl_co; /* Cache object */ |
| 92 | + struct { |
| 93 | + int f_cached:1; |
| 94 | + int f_closed:1; |
| 95 | + int f_http11:1; /* Client understands HTTP/1.1 */ |
| 96 | + } cl_flags; |
| 97 | + size_t cl_dsize; /* Object size */ |
| 98 | +enum encoding cl_enc; |
| 99 | +struct http_client *fe_next; /* freelist */ |
| 100 | +}; |
| 101 | + |
| 102 | +static struct http_client freelist; |
| 103 | +static struct http_client deadlist; |
| 104 | + |
| 105 | +static void client_close(struct http_client *); |
| 106 | +static void proxy_start_backend(struct backend *, struct fde *, void *); |
| 107 | +static void client_read_done(struct http_entity *, void *, int); |
| 108 | +static void client_response_done(struct http_entity *, void *, int); |
| 109 | +static void backend_headers_done(struct http_entity *, void *, int); |
| 110 | +static void client_headers_done(struct http_entity *, void *, int); |
| 111 | +static void client_write_cached(struct http_client *); |
| 112 | +static int removable_header(const char *); |
| 113 | + |
| 114 | +static void client_send_error(struct http_client *, int errcode, const char *error, |
| 115 | + int status, const char *statusstr); |
| 116 | +static void client_log_request(struct http_client *); |
| 117 | + |
| 118 | +static void do_cache_write(const char *, size_t, void *); |
| 119 | + |
| 120 | +static char via_hdr[1024]; |
| 121 | +static char *cache_hit_hdr; |
| 122 | +static char *cache_miss_hdr; |
| 123 | + |
| 124 | +char my_hostname[MAXHOSTNAMELEN + 1]; |
| 125 | +static char my_version[64]; |
| 126 | +static int logwr_pipe[2]; |
| 127 | +static FILE *alf; |
| 128 | + |
| 129 | +/* |
| 130 | + * Initialize whttp, start loggers. |
| 131 | + */ |
| 132 | +void |
| 133 | +whttp_init(void) |
| 134 | +{ |
| 135 | + int hsize; |
| 136 | + |
| 137 | + if (gethostname(my_hostname, MAXHOSTNAMELEN) < 0) { |
| 138 | + perror("gethostname"); |
| 139 | + exit(8); |
| 140 | + } |
| 141 | + |
| 142 | + (void)strlcpy(my_version, "Willow/" PACKAGE_VERSION, 64); |
| 143 | + snprintf(via_hdr, sizeof(via_hdr), "1.1 %s (%s)", my_hostname, my_version); |
| 144 | + |
| 145 | + hsize = sizeof("MISS from ") + strlen(my_hostname); |
| 146 | + cache_hit_hdr = wmalloc(hsize + 1); |
| 147 | + cache_miss_hdr = wmalloc(hsize + 1); |
| 148 | + |
| 149 | + if (cache_hit_hdr == NULL || cache_miss_hdr == NULL) |
| 150 | + outofmemory(); |
| 151 | + |
| 152 | + snprintf(cache_hit_hdr, hsize, "HIT from %s", my_hostname); |
| 153 | + snprintf(cache_miss_hdr, hsize, "MISS from %s", my_hostname); |
| 154 | + |
| 155 | + /* |
| 156 | + * Fork the logwriter. |
| 157 | + */ |
| 158 | + |
| 159 | + if (config.access_log) { |
| 160 | + if (pipe(logwr_pipe) < 0) { |
| 161 | + perror("pipe"); |
| 162 | + exit(8); |
| 163 | + } |
| 164 | + switch (fork()) { |
| 165 | + case -1: |
| 166 | + wlog(WLOG_ERROR, "starting logwriter: %s", strerror(errno)); |
| 167 | + exit(8); |
| 168 | + /*NOTREACHED*/ |
| 169 | + case 0: |
| 170 | + (void)dup2(logwr_pipe[0], STDIN_FILENO); |
| 171 | + /*LINTED execl*/ |
| 172 | + (void)execl(LIBEXECDIR "/wlogwriter", "wlogwriter", config.access_log, NULL); |
| 173 | + exit(8); |
| 174 | + /*NOTREACHED*/ |
| 175 | + default: |
| 176 | + break; |
| 177 | + } |
| 178 | + if ((alf = fdopen(logwr_pipe[1], "a")) == NULL) { |
| 179 | + perror("whttp: fdopen"); |
| 180 | + exit(8); |
| 181 | + } |
| 182 | + } |
| 183 | +} |
| 184 | + |
| 185 | +void |
| 186 | +whttp_shutdown(void) |
| 187 | +{ |
| 188 | + wfree(cache_hit_hdr); |
| 189 | + wfree(cache_miss_hdr); |
| 190 | +} |
| 191 | + |
| 192 | +/* |
| 193 | + * Create a new client associated with the FDE 'e'. |
| 194 | + */ |
| 195 | +static struct http_client * |
| 196 | +new_client(e) |
| 197 | + struct fde *e; |
| 198 | +{ |
| 199 | +struct http_client *cl; |
| 200 | + |
| 201 | + if (freelist.fe_next) { |
| 202 | + cl = freelist.fe_next; |
| 203 | + freelist.fe_next = cl->fe_next; |
| 204 | + bzero(cl, sizeof(*cl)); |
| 205 | + } else { |
| 206 | + if ((cl = wcalloc(1, sizeof(*cl))) == NULL) |
| 207 | + outofmemory(); |
| 208 | + } |
| 209 | + |
| 210 | + cl->cl_fde = e; |
| 211 | + return cl; |
| 212 | +} |
| 213 | + |
| 214 | +/* |
| 215 | + * Called by wnet_accept to regiister a new client. Reads the request headers |
| 216 | + * from the client. |
| 217 | + */ |
| 218 | +void |
| 219 | +http_new(e) |
| 220 | + struct fde *e; |
| 221 | +{ |
| 222 | +struct http_client *cl; |
| 223 | + |
| 224 | + cl = new_client(e); |
| 225 | + cl->cl_entity.he_source_type = ENT_SOURCE_FDE; |
| 226 | + cl->cl_entity.he_source.fde.fde = e; |
| 227 | + cl->cl_entity.he_rdata.request.contlen = -1; |
| 228 | + |
| 229 | + WDEBUG((WLOG_DEBUG, "http_new: starting header read for %d", cl->cl_fde->fde_fd)); |
| 230 | + entity_read_headers(&cl->cl_entity, client_read_done, cl); |
| 231 | +} |
| 232 | + |
| 233 | +/* |
| 234 | + * Called when the initial request has been read. Checks if the object is |
| 235 | + * cached, and starts a backend request if not. If it it, sends the cached |
| 236 | + * object to the client. |
| 237 | + */ |
| 238 | +/*ARGSUSED*/ |
| 239 | +static void |
| 240 | +client_read_done(entity, data, res) |
| 241 | + struct http_entity *entity; |
| 242 | + void *data; |
| 243 | + int res; |
| 244 | +{ |
| 245 | +struct http_client *client = data; |
| 246 | +struct cache_object *cobj; |
| 247 | + char *pragma, *cache_control, *ifmod; |
| 248 | +struct qvalue_head *acceptenc; |
| 249 | +struct qvalue *val; |
| 250 | + int cacheable = 1; |
| 251 | + |
| 252 | + WDEBUG((WLOG_DEBUG, "client_read_done: called, res=%d", res)); |
| 253 | + |
| 254 | + if (res < -1) { |
| 255 | + client_send_error(client, ERR_BADREQUEST, ent_errors[-res], 400, "Bad request (#10.4.1)"); |
| 256 | + return; |
| 257 | + } else if (res == -1) { |
| 258 | + client_close(client); |
| 259 | + return; |
| 260 | + } else if (res == 1) { |
| 261 | + client_close(client); |
| 262 | + return; |
| 263 | + } |
| 264 | + |
| 265 | + if (client->cl_entity.he_rdata.request.httpmaj >= 1 && |
| 266 | + client->cl_entity.he_rdata.request.httpmin >= 1) |
| 267 | + client->cl_flags.f_http11 = 1; |
| 268 | + |
| 269 | + if (client->cl_entity.he_rdata.request.host == NULL) |
| 270 | + client->cl_path = wstrdup(client->cl_entity.he_rdata.request.path); |
| 271 | + else { |
| 272 | + int len; |
| 273 | + |
| 274 | + len = strlen(client->cl_entity.he_rdata.request.host) + |
| 275 | + strlen(client->cl_entity.he_rdata.request.path) + 7; |
| 276 | + |
| 277 | + client->cl_path = wmalloc(len + 1); |
| 278 | + if (client->cl_path == NULL) |
| 279 | + outofmemory(); |
| 280 | + snprintf(client->cl_path, len + 1, "http://%s%s", |
| 281 | + client->cl_entity.he_rdata.request.host, |
| 282 | + client->cl_entity.he_rdata.request.path); |
| 283 | + } |
| 284 | + |
| 285 | + client->cl_reqtype = client->cl_entity.he_rdata.request.reqtype; |
| 286 | + |
| 287 | + pragma = entity->he_h_pragma; |
| 288 | + cache_control = entity->he_h_cache_control; |
| 289 | + |
| 290 | + if (pragma) { |
| 291 | + char **pragmas = wstrvec(pragma, ",", 0); |
| 292 | + char **s; |
| 293 | + for (s = pragmas; *s; ++s) { |
| 294 | + if (!strcasecmp(*s, "no-cache")) { |
| 295 | + cacheable = 0; |
| 296 | + break; |
| 297 | + } |
| 298 | + } |
| 299 | + wstrvecfree(pragmas); |
| 300 | + } |
| 301 | + |
| 302 | + if (cache_control) { |
| 303 | + char **cache_controls = wstrvec(cache_control, ",", 0); |
| 304 | + char **s; |
| 305 | + for (s = cache_controls; *s; ++s) { |
| 306 | + if (!strcasecmp(*s, "no-cache")) { |
| 307 | + cacheable = 0; |
| 308 | + break; |
| 309 | + } |
| 310 | + } |
| 311 | + wstrvecfree(cache_controls); |
| 312 | + } |
| 313 | + |
| 314 | + acceptenc = &entity->he_rdata.request.accept_encoding; |
| 315 | + while ((val = qvalue_remove_best(acceptenc)) != NULL) { |
| 316 | + WDEBUG((WLOG_DEBUG, "client offers [%s] q=%f", val->name, (double) val->val)); |
| 317 | + if ((client->cl_enc = accept_encoding(val->name)) != E_NONE) { |
| 318 | + wfree(val); |
| 319 | + break; |
| 320 | + } |
| 321 | + wfree(val); |
| 322 | + } |
| 323 | + |
| 324 | + /* |
| 325 | + * Check for cached object. |
| 326 | + */ |
| 327 | + if (config.ncaches && client->cl_reqtype == REQTYPE_GET) { |
| 328 | + if (cacheable) |
| 329 | + client->cl_co = wcache_find_object(client->cl_path, &client->cl_cfd, |
| 330 | + WCACHE_RDWR); |
| 331 | + else |
| 332 | + client->cl_co = wcache_find_object(client->cl_path, &client->cl_cfd, |
| 333 | + WCACHE_WRONLY); |
| 334 | + |
| 335 | + if (cacheable && client->cl_co && client->cl_co->co_time && |
| 336 | + (ifmod = entity->he_h_if_modified_since)) { |
| 337 | + char *s; |
| 338 | + time_t t; |
| 339 | + struct tm m; |
| 340 | + s = strptime(ifmod, "%a, %d %b %Y %H:%M:%S", &m); |
| 341 | + if (s) { |
| 342 | + t = mktime(&m); |
| 343 | + WDEBUG((WLOG_DEBUG, "if-mod: %d, last-mod: %d", t, client->cl_co->co_time)); |
| 344 | + if (t >= client->cl_co->co_time) { |
| 345 | + /* |
| 346 | + * Not modified |
| 347 | + */ |
| 348 | + client_send_error(client, -1, NULL, 304, "Not modified (#10.3.5)"); |
| 349 | + return; |
| 350 | + } |
| 351 | + } |
| 352 | + } |
| 353 | + |
| 354 | + if (client->cl_co && client->cl_co->co_complete) { |
| 355 | + WDEBUG((WLOG_DEBUG, "client_read_done: object %s cached", client->cl_path)); |
| 356 | + client_write_cached(client); |
| 357 | + return; |
| 358 | + } |
| 359 | + WDEBUG((WLOG_DEBUG, "client_read_done: %s not cached", client->cl_path)); |
| 360 | + } |
| 361 | + |
| 362 | + /* |
| 363 | + * Not cached. Find a backend. |
| 364 | + */ |
| 365 | + if (get_backend(client->cl_path, proxy_start_backend, client, 0) == -1) { |
| 366 | + client_send_error(client, ERR_GENERAL, |
| 367 | + "No backends were available to service your request", 503, |
| 368 | + "Service unavailable (#10.5.4)"); |
| 369 | + return; |
| 370 | + } |
| 371 | +} |
| 372 | + |
| 373 | +/* |
| 374 | + * Called when backend is ready. backend==NULL if none was found. |
| 375 | + */ |
| 376 | +static void |
| 377 | +proxy_start_backend(backend, e, data) |
| 378 | + struct backend *backend; |
| 379 | + struct fde *e; |
| 380 | + void *data; |
| 381 | +{ |
| 382 | +struct http_client *client = data; |
| 383 | +struct header_list *it; |
| 384 | + int error = 0; |
| 385 | + socklen_t len = sizeof(error); |
| 386 | + |
| 387 | + WDEBUG((WLOG_DEBUG, "proxy_start_backend: called; for client=%d", client->cl_fde->fde_fd)); |
| 388 | + |
| 389 | + if (backend == NULL) { |
| 390 | + client_send_error(client, ERR_GENERAL, |
| 391 | + "No backends were available to service your request", |
| 392 | + 503, "Service unavailable (#10.5.4)"); |
| 393 | + return; |
| 394 | + } |
| 395 | + |
| 396 | + client->cl_backend = backend; |
| 397 | + client->cl_backendfde = e; |
| 398 | + |
| 399 | + getsockopt(e->fde_fd, SOL_SOCKET, SO_ERROR, &error, &len); |
| 400 | + if (error) { |
| 401 | + client_send_error(client, ERR_GENERAL, strerror(error), 503, |
| 402 | + "Service unavailable (#10.5.4)"); |
| 403 | + return; |
| 404 | + } |
| 405 | + |
| 406 | + for (it = client->cl_entity.he_headers.hl_next; it; it = it->hl_next) { |
| 407 | + if (removable_header(it->hl_name)) { |
| 408 | + header_remove(&client->cl_entity.he_headers, it); |
| 409 | + it = client->cl_entity.he_headers.hl_next; |
| 410 | + continue; |
| 411 | + } |
| 412 | + } |
| 413 | + |
| 414 | + header_add(&client->cl_entity.he_headers, wstrdup("X-Forwarded-For"), wstrdup(client->cl_fde->fde_straddr)); |
| 415 | + header_add(&client->cl_entity.he_headers, wstrdup("Connection"), wstrdup("Close")); |
| 416 | + /* |
| 417 | + * POST requests require Content-Length. |
| 418 | + */ |
| 419 | + if (client->cl_reqtype == REQTYPE_POST) { |
| 420 | + if (client->cl_entity.he_rdata.request.contlen == -1) { |
| 421 | + client_send_error(client, ERR_BADREQUEST, "POST request without Content-Length", |
| 422 | + 411, "Length required (#10.4.12)"); |
| 423 | + return; |
| 424 | + } |
| 425 | + |
| 426 | + WDEBUG((WLOG_DEBUG, "client content-length=%d", client->cl_entity.he_rdata.request.contlen)); |
| 427 | + client->cl_entity.he_source_type = ENT_SOURCE_FDE; |
| 428 | + client->cl_entity.he_source.fde.fde = client->cl_fde; |
| 429 | + client->cl_entity.he_source.fde.len = client->cl_entity.he_rdata.request.contlen; |
| 430 | + } else |
| 431 | + client->cl_entity.he_source_type = ENT_SOURCE_NONE; |
| 432 | + |
| 433 | + entity_send(e, &client->cl_entity, backend_headers_done, client, 0); |
| 434 | +} |
| 435 | + |
| 436 | +/* |
| 437 | + * Called when clients request was written to the backend. |
| 438 | + */ |
| 439 | +/*ARGSUSED*/ |
| 440 | +static void |
| 441 | +backend_headers_done(entity, data, res) |
| 442 | + struct http_entity *entity; |
| 443 | + void *data; |
| 444 | + int res; |
| 445 | +{ |
| 446 | +struct http_client *client = data; |
| 447 | + |
| 448 | + WDEBUG((WLOG_DEBUG, "backend_headers_done: called")); |
| 449 | + if (res == -1) { |
| 450 | + client_send_error(client, ERR_GENERAL, strerror(errno), 503, |
| 451 | + "Service unavailable (#10.5.4)"); |
| 452 | + return; |
| 453 | + } |
| 454 | + |
| 455 | + entity_free(&client->cl_entity); |
| 456 | + bzero(&client->cl_entity, sizeof(client->cl_entity)); |
| 457 | + client->cl_entity.he_source_type = ENT_SOURCE_FDE; |
| 458 | + client->cl_entity.he_source.fde.fde = client->cl_backendfde; |
| 459 | + client->cl_entity.he_source.fde.len = -1; |
| 460 | + |
| 461 | + entity_set_response(&client->cl_entity, 1); |
| 462 | + |
| 463 | + /* |
| 464 | + * This should probably be handled somewhere inside |
| 465 | + * whttp_entity.c ... |
| 466 | + */ |
| 467 | + entity_read_headers(&client->cl_entity, client_headers_done, client); |
| 468 | +} |
| 469 | + |
| 470 | +/* |
| 471 | + * Called when backend's headers are finished reading. |
| 472 | + */ |
| 473 | +static void |
| 474 | +client_headers_done(entity, data, res) |
| 475 | + struct http_entity *entity; |
| 476 | + void *data; |
| 477 | + int res; |
| 478 | +{ |
| 479 | +struct http_client *client = data; |
| 480 | + char *cache_path; |
| 481 | + size_t plen; |
| 482 | + |
| 483 | + WDEBUG((WLOG_DEBUG, "client_headers_done: called")); |
| 484 | + |
| 485 | + if (res == -1) { |
| 486 | + client_close(client); |
| 487 | + return; |
| 488 | + } else if (res < -1) { |
| 489 | + client_send_error(client, ERR_GENERAL, ent_errors[-res], 503, |
| 490 | + "Service unavailable (#10.5.4)"); |
| 491 | + return; |
| 492 | + } |
| 493 | + |
| 494 | + /* |
| 495 | + * If cachable, open the cache file and write data. |
| 496 | + * |
| 497 | + * Don't cache responses to non-GET requests, or non-200 replies. |
| 498 | + */ |
| 499 | + if (client->cl_reqtype != REQTYPE_GET || entity->he_rdata.response.status != 200 |
| 500 | + || !config.ncaches || !client->cl_co) { |
| 501 | + if (client->cl_co) |
| 502 | + wcache_release(client->cl_co, 0); |
| 503 | + } else if (client->cl_co) { |
| 504 | + char *lastmod; |
| 505 | + |
| 506 | + entity->he_cache_callback = do_cache_write; |
| 507 | + entity->he_cache_callback_data = client; |
| 508 | + header_dump(&client->cl_entity.he_headers, client->cl_cfd); |
| 509 | + |
| 510 | + /* |
| 511 | + * Look for last-modified |
| 512 | + */ |
| 513 | + if ((lastmod = entity->he_h_last_modified) != NULL) { |
| 514 | + struct tm tim; |
| 515 | + char *res; |
| 516 | + res = strptime(lastmod, "%a, %d %b %Y %H:%M:%S", &tim); |
| 517 | + if (res) { |
| 518 | + WDEBUG((WLOG_DEBUG, "last-modified: %d", mktime(&tim))); |
| 519 | + client->cl_co->co_time = mktime(&tim); |
| 520 | + } |
| 521 | + } |
| 522 | + } |
| 523 | + |
| 524 | + header_add(&client->cl_entity.he_headers, wstrdup("Via"), wstrdup(via_hdr)); |
| 525 | + header_add(&client->cl_entity.he_headers, wstrdup("X-Cache"), wstrdup(cache_miss_hdr)); |
| 526 | + client->cl_entity.he_source.fde.len = -1; |
| 527 | + if (config.compress) |
| 528 | + client->cl_entity.he_encoding = client->cl_enc; |
| 529 | + |
| 530 | + if (!HAS_BODY(client->cl_entity.he_rdata.response.status)) |
| 531 | + client->cl_entity.he_source_type = ENT_SOURCE_NONE; |
| 532 | + |
| 533 | + entity_send(client->cl_fde, &client->cl_entity, client_response_done, client, |
| 534 | + client->cl_flags.f_http11 ? ENT_CHUNKED_OKAY : 0); |
| 535 | +} |
| 536 | + |
| 537 | +/* |
| 538 | + * Write a cached object to the client. |
| 539 | + */ |
| 540 | +static void |
| 541 | +client_write_cached(client) |
| 542 | + struct http_client *client; |
| 543 | +{ |
| 544 | + size_t plen; |
| 545 | + char *cache_path; |
| 546 | +struct stat sb; |
| 547 | + char size[64]; |
| 548 | + |
| 549 | + plen = strlen(config.caches[0].dir) + client->cl_co->co_plen + 12 + 2; |
| 550 | + if ((cache_path = wcalloc(1, plen + 1)) == NULL) |
| 551 | + outofmemory(); |
| 552 | + |
| 553 | + if (fstat(client->cl_cfd, &sb) < 0) { |
| 554 | + wlog(WLOG_WARNING, "stat(%s): %s", cache_path, strerror(errno)); |
| 555 | + client_send_error(client, ERR_CACHE_IO, strerror(errno), |
| 556 | + 500, "Internal server error (#10.5.1)"); |
| 557 | + wfree(cache_path); |
| 558 | + return; |
| 559 | + } |
| 560 | + |
| 561 | + wfree(cache_path); |
| 562 | + |
| 563 | + entity_free(&client->cl_entity); |
| 564 | + bzero(&client->cl_entity, sizeof(client->cl_entity)); |
| 565 | + header_undump(&client->cl_entity.he_headers, client->cl_cfd, &client->cl_entity.he_source.fd.off); |
| 566 | + header_add(&client->cl_entity.he_headers, wstrdup("Via"), wstrdup(via_hdr)); |
| 567 | + header_add(&client->cl_entity.he_headers, wstrdup("X-Cache"), wstrdup(cache_hit_hdr)); |
| 568 | + if (!client->cl_enc && !header_find(&client->cl_entity.he_headers, "Content-Length")) { |
| 569 | + snprintf(size, sizeof(size), "%d", client->cl_co->co_size); |
| 570 | + header_add(&client->cl_entity.he_headers, wstrdup("Content-Length"), wstrdup(size)); |
| 571 | + } |
| 572 | + |
| 573 | + entity_set_response(&client->cl_entity, 1); |
| 574 | + client->cl_entity.he_rdata.response.status = 200; |
| 575 | + client->cl_entity.he_rdata.response.status_str = "OK"; |
| 576 | + |
| 577 | + client->cl_entity.he_source.fd.fd = client->cl_cfd; |
| 578 | + client->cl_entity.he_source.fd.size = sb.st_size; |
| 579 | + if (config.compress) |
| 580 | + client->cl_entity.he_encoding = client->cl_enc; |
| 581 | + |
| 582 | + client->cl_entity.he_source_type = ENT_SOURCE_FILE; |
| 583 | + |
| 584 | + client->cl_flags.f_cached = 1; |
| 585 | + entity_send(client->cl_fde, &client->cl_entity, client_response_done, client, 0); |
| 586 | +} |
| 587 | + |
| 588 | +/* |
| 589 | + * Called when response was finished writing to the client. |
| 590 | + */ |
| 591 | +/*ARGSUSED*/ |
| 592 | +static void |
| 593 | +client_response_done(entity, data, res) |
| 594 | + struct http_entity *entity; |
| 595 | + void *data; |
| 596 | + int res; |
| 597 | +{ |
| 598 | +struct http_client *client = data; |
| 599 | + |
| 600 | + WDEBUG((WLOG_DEBUG, "client_response_done: called, res=%d", res)); |
| 601 | + |
| 602 | + if (client->cl_cfd) { |
| 603 | + if (close(client->cl_cfd) < 0) { |
| 604 | + wlog(WLOG_WARNING, "closing cache file: %s", strerror(errno)); |
| 605 | + } |
| 606 | + } |
| 607 | + |
| 608 | + if (client->cl_co) { |
| 609 | + int complete = (res != -1); |
| 610 | + struct header_list *hdr; |
| 611 | + |
| 612 | + /* |
| 613 | + * The server may have indicated that we shouldn't cache this document. |
| 614 | + * If so, release it in an incomplete state so it gets evicted. |
| 615 | + */ |
| 616 | + |
| 617 | + /* |
| 618 | + * HTTP/1.0 Pragma |
| 619 | + */ |
| 620 | + hdr = header_find(&client->cl_entity.he_headers, "Pragma"); |
| 621 | + if (hdr) { |
| 622 | + char **pragmas = wstrvec(hdr->hl_value, ",", 0); |
| 623 | + char **s; |
| 624 | + for (s = pragmas; *s; ++s) { |
| 625 | + if (!strcasecmp(*s, "no-cache")) { |
| 626 | + complete = 0; |
| 627 | + break; |
| 628 | + } |
| 629 | + } |
| 630 | + wstrvecfree(pragmas); |
| 631 | + } |
| 632 | + |
| 633 | + /* |
| 634 | + * HTTP/1.1 Cache-Control |
| 635 | + */ |
| 636 | + hdr = header_find(&client->cl_entity.he_headers, "Cache-Control"); |
| 637 | + if (hdr) { |
| 638 | + char **controls = wstrvec(hdr->hl_value, ",", 0); |
| 639 | + char **s; |
| 640 | + for (s = controls; *s; ++s) { |
| 641 | + /* |
| 642 | + * According to the standard, we can still cache no-cache |
| 643 | + * documents, but we have to revalidate them on every request. |
| 644 | + */ |
| 645 | + if (!strcasecmp(*s, "no-cache") || |
| 646 | + (!config.cache_private && !strcasecmp(*s, "private")) || |
| 647 | + !strcasecmp(*s, "no-store")) { |
| 648 | + complete = 0; |
| 649 | + break; |
| 650 | + } |
| 651 | + } |
| 652 | + wstrvecfree(controls); |
| 653 | + } |
| 654 | + |
| 655 | + wcache_release(client->cl_co, complete); |
| 656 | + } |
| 657 | + |
| 658 | + client_log_request(client); |
| 659 | + client_close(client); |
| 660 | +} |
| 661 | + |
| 662 | +static void |
| 663 | +client_close(client) |
| 664 | + struct http_client *client; |
| 665 | +{ |
| 666 | + WDEBUG((WLOG_DEBUG, "close client %d", client->cl_fde->fde_fd)); |
| 667 | + assert(!client->cl_flags.f_closed); |
| 668 | + client->cl_flags.f_closed = 1; |
| 669 | + if (client->cl_wrtbuf) |
| 670 | + wfree(client->cl_wrtbuf); |
| 671 | + if (client->cl_path) |
| 672 | + wfree(client->cl_path); |
| 673 | + entity_free(&client->cl_entity); |
| 674 | + wnet_close(client->cl_fde->fde_fd); |
| 675 | + if (client->cl_backendfde) |
| 676 | + wnet_close(client->cl_backendfde->fde_fd); |
| 677 | + |
| 678 | + client->fe_next = freelist.fe_next; |
| 679 | + freelist.fe_next = client; |
| 680 | +} |
| 681 | + |
| 682 | +static void |
| 683 | +client_send_error(client, errnum, errdata, status, statstr) |
| 684 | + struct http_client *client; |
| 685 | + int errnum, status; |
| 686 | + const char *errdata, *statstr; |
| 687 | +{ |
| 688 | + FILE *errfile; |
| 689 | + char errbuf[8192]; |
| 690 | + char *p = errbuf, *u; |
| 691 | + ssize_t size; |
| 692 | + |
| 693 | + if (client->cl_co) |
| 694 | + wcache_release(client->cl_co, 0); |
| 695 | + |
| 696 | + if (errnum >= 0) { |
| 697 | + if ((errfile = fopen(error_files[errnum], "r")) == NULL) { |
| 698 | + client_close(client); |
| 699 | + return; |
| 700 | + } |
| 701 | + |
| 702 | + if ((size = fread(errbuf, 1, sizeof(errbuf) - 1, errfile)) < 0) { |
| 703 | + (void)fclose(errfile); |
| 704 | + client_close(client); |
| 705 | + return; |
| 706 | + } |
| 707 | + |
| 708 | + (void)fclose(errfile); |
| 709 | + errbuf[size] = '\0'; |
| 710 | + |
| 711 | + if (!errdata) |
| 712 | + errdata = "Unknown error"; |
| 713 | + if (!client->cl_path) |
| 714 | + client->cl_path = wstrdup("NONE"); |
| 715 | + |
| 716 | + u = NULL; |
| 717 | + |
| 718 | + while (*p) { |
| 719 | + switch(*p) { |
| 720 | + case '%': |
| 721 | + switch (*++p) { |
| 722 | + case 'U': |
| 723 | + realloc_strcat(&u, client->cl_path); |
| 724 | + break; |
| 725 | + case 'D': |
| 726 | + realloc_strcat(&u, current_time_str); |
| 727 | + break; |
| 728 | + case 'H': |
| 729 | + realloc_strcat(&u, my_hostname); |
| 730 | + break; |
| 731 | + case 'E': |
| 732 | + realloc_strcat(&u, errdata); |
| 733 | + break; |
| 734 | + case 'V': |
| 735 | + realloc_strcat(&u, my_version); |
| 736 | + break; |
| 737 | + case 'C': { |
| 738 | + char *s = wmalloc(4); |
| 739 | + sprintf(s, "%d", status); |
| 740 | + realloc_strcat(&u, s); |
| 741 | + wfree(s); |
| 742 | + break; |
| 743 | + } |
| 744 | + case 'S': |
| 745 | + realloc_strcat(&u, statstr); |
| 746 | + break; |
| 747 | + default: |
| 748 | + break; |
| 749 | + } |
| 750 | + p++; |
| 751 | + continue; |
| 752 | + default: |
| 753 | + realloc_addchar(&u, *p); |
| 754 | + break; |
| 755 | + } |
| 756 | + ++p; |
| 757 | + } |
| 758 | + |
| 759 | + |
| 760 | + client->cl_wrtbuf = u; |
| 761 | + } |
| 762 | + |
| 763 | + header_free(&client->cl_entity.he_headers); |
| 764 | + bzero(&client->cl_entity.he_headers, sizeof(client->cl_entity.he_headers)); |
| 765 | + header_add(&client->cl_entity.he_headers, wstrdup("Date"), wstrdup(current_time_str)); |
| 766 | + header_add(&client->cl_entity.he_headers, wstrdup("Expires"), wstrdup(current_time_str)); |
| 767 | + header_add(&client->cl_entity.he_headers, wstrdup("Server"), wstrdup(my_version)); |
| 768 | + header_add(&client->cl_entity.he_headers, wstrdup("Connection"), wstrdup("close")); |
| 769 | + |
| 770 | + entity_set_response(&client->cl_entity, 1); |
| 771 | + client->cl_entity.he_rdata.response.status = status; |
| 772 | + client->cl_entity.he_rdata.response.status_str = statstr; |
| 773 | + if (errnum >= 0) { |
| 774 | + header_add(&client->cl_entity.he_headers, wstrdup("Content-Type"), wstrdup("text/html")); |
| 775 | + client->cl_entity.he_source_type = ENT_SOURCE_BUFFER; |
| 776 | + client->cl_entity.he_source.buffer.addr = client->cl_wrtbuf; |
| 777 | + client->cl_entity.he_source.buffer.len = strlen(client->cl_wrtbuf); |
| 778 | + } else { |
| 779 | + client->cl_entity.he_source_type = ENT_SOURCE_NONE; |
| 780 | + } |
| 781 | + |
| 782 | + client->cl_entity.he_flags.cachable = 0; |
| 783 | + entity_send(client->cl_fde, &client->cl_entity, client_response_done, client, 0); |
| 784 | +} |
| 785 | + |
| 786 | +static void |
| 787 | +client_log_request(client) |
| 788 | + struct http_client *client; |
| 789 | +{ |
| 790 | + int i; |
| 791 | + |
| 792 | + if (!config.access_log) |
| 793 | + return; |
| 794 | + |
| 795 | + i = fprintf(alf, "[%s] %s %s \"%s\" %d %s %s\n", |
| 796 | + current_time_short, client->cl_fde->fde_straddr, |
| 797 | + request_string[client->cl_reqtype], |
| 798 | + client->cl_path, client->cl_entity.he_rdata.response.status, |
| 799 | + client->cl_backend ? client->cl_backend->be_name : "-", |
| 800 | + client->cl_flags.f_cached ? "HIT" : "MISS"); |
| 801 | + if (i < 0) { |
| 802 | + wlog(WLOG_ERROR, "writing logfile: %s", strerror(errno)); |
| 803 | + exit(8); |
| 804 | + } |
| 805 | + |
| 806 | + if (fflush(alf) == EOF) { |
| 807 | + wlog(WLOG_ERROR, "flushing logfile: %s", strerror(errno)); |
| 808 | + exit(8); |
| 809 | + } |
| 810 | +} |
| 811 | + |
| 812 | +static void |
| 813 | +do_cache_write(buf, len, data) |
| 814 | + const char *buf; |
| 815 | + size_t len; |
| 816 | + void *data; |
| 817 | +{ |
| 818 | +struct http_client *client = data; |
| 819 | + |
| 820 | + if (write(client->cl_cfd, buf, len) < 0) { |
| 821 | + /*EMPTY*/ |
| 822 | + WDEBUG((WLOG_WARNING, "writing cached data: %s", strerror(errno))); |
| 823 | + } |
| 824 | + client->cl_co->co_size += len; |
| 825 | +} |
| 826 | + |
| 827 | +static int |
| 828 | +removable_header(hdr) |
| 829 | + const char *hdr; |
| 830 | +{ |
| 831 | +static const char *removable_headers[] = { |
| 832 | + "Connection", |
| 833 | + "Keep-Alive", |
| 834 | + "Proxy-Authenticate", |
| 835 | + "Proxy-Authorization", |
| 836 | + "TE", |
| 837 | + "Trailers", |
| 838 | + "Transfer-Encoding", |
| 839 | + "Upgrade", |
| 840 | + NULL, |
| 841 | +}; |
| 842 | + const char **s; |
| 843 | + for (s = &removable_headers[0]; *s; s++) |
| 844 | + if (!strcasecmp(*s, hdr)) |
| 845 | + return 1; |
| 846 | + return 0; |
| 847 | +} |
| 848 | + |
| 849 | + |
Index: trunk/willow/src/willow/lexer.l |
— | — | @@ -0,0 +1,66 @@ |
| 2 | +%{ |
| 3 | +/* @(#) $Header$ */ |
| 4 | +/* This source code is in the public domain. */ |
| 5 | +/* |
| 6 | + * Willow: Lightweight HTTP reverse-proxy. |
| 7 | + * lexer: configuration file lexer. |
| 8 | + */ |
| 9 | + |
| 10 | +#if defined __SUNPRO_C || defined __DECC || defined __HP_cc |
| 11 | +# pragma ident "@(#)$Header$" |
| 12 | +#endif |
| 13 | + |
| 14 | +#include <string.h> |
| 15 | +#include <errno.h> |
| 16 | + |
| 17 | +#include "willow.h" |
| 18 | +#include "confparse.h" |
| 19 | +#include "y.tab.h" |
| 20 | + |
| 21 | +int lineno = 1; |
| 22 | + |
| 23 | +int |
| 24 | +yywrap(void) |
| 25 | +{ |
| 26 | + return 1; |
| 27 | +} |
| 28 | +%} |
| 29 | + |
| 30 | +%x COMMENT |
| 31 | + |
| 32 | +ws [ \t]* |
| 33 | +number [0-9][0-9]* |
| 34 | +comment #.* |
| 35 | +qstring \"[^\"\n]*[\"] |
| 36 | +string [a-zA-Z_\~][a-zA-Z0-9_-]* |
| 37 | + |
| 38 | +%% |
| 39 | + |
| 40 | +^[ \t]*"/*" { BEGIN COMMENT; } |
| 41 | +^[ \t]*"/*".*"*/"[ \t]*\n { lineno++; } |
| 42 | + |
| 43 | +<COMMENT>"*/"[ \t]*\n { BEGIN 0; lineno++; break; } |
| 44 | +<COMMENT>"*/" { BEGIN 0; break; } |
| 45 | +<COMMENT>\n { lineno++; break; } |
| 46 | +<COMMENT>.\n { lineno++; break; } |
| 47 | +<COMMENT>. { break; } |
| 48 | +\n { lineno++; } |
| 49 | +{ws} ; |
| 50 | +{comment} ; |
| 51 | +{number} { char *s; |
| 52 | + yylval.number = strtol(yytext, &s, 0); |
| 53 | + if (*s) { |
| 54 | + conf_report_error("invalid number '%s': %s", yytext, strerror(errno)); |
| 55 | + break; |
| 56 | + } |
| 57 | + return NUMBER; |
| 58 | + } |
| 59 | +{string} { yylval.string = wstrdup(yytext); |
| 60 | + return STRING; |
| 61 | + } |
| 62 | +{qstring} { yylval.string = wstrdup(yytext + 1); |
| 63 | + yylval.string[yyleng - 2] = '\0'; |
| 64 | + return QSTRING; |
| 65 | + } |
| 66 | +. { return yytext[0]; |
| 67 | + } |
Index: trunk/willow/src/willow/wcache.c |
— | — | @@ -0,0 +1,546 @@ |
| 2 | +/* @(#) $Header$ */ |
| 3 | +/* This source code is in the public domain. */ |
| 4 | +/* |
| 5 | + * Willow: Lightweight HTTP reverse-proxy. |
| 6 | + * wcache: entity caching. |
| 7 | + */ |
| 8 | + |
| 9 | +#if defined __SUNPRO_C || defined __DECC || defined __HP_cc |
| 10 | +# pragma ident "@(#)$Header$" |
| 11 | +#endif |
| 12 | + |
| 13 | +#include <sys/types.h> |
| 14 | +#include <sys/stat.h> |
| 15 | + |
| 16 | +#include <string.h> |
| 17 | +#include <errno.h> |
| 18 | +#include <stdlib.h> |
| 19 | +#include <math.h> |
| 20 | +#include <strings.h> |
| 21 | +#include <limits.h> |
| 22 | +#include <assert.h> |
| 23 | +#include <unistd.h> |
| 24 | +#include <fcntl.h> |
| 25 | +#include <event.h> |
| 26 | + |
| 27 | +#include "wcache.h" |
| 28 | +#include "wlog.h" |
| 29 | +#include "wconfig.h" |
| 30 | +#include "willow.h" |
| 31 | + |
| 32 | +#define CACHEDIR "__objects__" |
| 33 | + |
| 34 | +static void dberror(const char *, int); |
| 35 | +static int cache_next_id(void); |
| 36 | +static void run_expiry(int, short, void*); |
| 37 | +static void wcache_evict(struct cache_object *); |
| 38 | +static void cache_getstate(struct cache_state *); |
| 39 | +static struct cache_object *wcache_new_object(const char *); |
| 40 | +static void wcache_free_object(struct cache_object *); |
| 41 | +static int cache_open(struct cache_object *, int, int); |
| 42 | +static void cache_unlink(struct cache_object *); |
| 43 | +static void cache_writestate(struct cache_state *state); |
| 44 | +static void expire_sched(void); |
| 45 | +static void idx_add(struct cache_object *); |
| 46 | +static void idx_rem(struct cache_object *); |
| 47 | +static struct cache_object *idx_find(const char *key); |
| 48 | + |
| 49 | +typedef unsigned long int ub4; /* unsigned 4-byte quantities */ |
| 50 | +typedef unsigned char ub1; /* unsigned 1-byte quantities */ |
| 51 | +static ub4 hash(const ub1 *); |
| 52 | + |
| 53 | +#define hashsize(n) ((ub4)1<<(n)) |
| 54 | +#define hashmask(n) (hashsize(n)-1) |
| 55 | + |
| 56 | +#define HASH_BITS 20 |
| 57 | +#define HASH_ELEMS hashsize(HASH_BITS) |
| 58 | + |
| 59 | +static struct cache_state state; |
| 60 | +static struct event expire_ev; |
| 61 | +static struct timeval expire_tv; |
| 62 | + |
| 63 | +static int int_max_len; |
| 64 | + |
| 65 | +struct key_idx_entry { |
| 66 | + struct cache_object *obj; |
| 67 | + LIST_ENTRY(key_idx_entry) entries; |
| 68 | +}; |
| 69 | + |
| 70 | +struct key_idx_bucket { |
| 71 | + LIST_HEAD(key_idx_head, key_idx_entry) head; |
| 72 | +} key_idx[HASH_ELEMS]; |
| 73 | + |
| 74 | +TAILQ_HEAD(objlist, cache_object) objects; |
| 75 | + |
| 76 | +static int |
| 77 | +cache_open(obj, flags, mode) |
| 78 | + struct cache_object *obj; |
| 79 | + int flags, mode; |
| 80 | +{ |
| 81 | + char *path; |
| 82 | + int plen; |
| 83 | + int i; |
| 84 | + |
| 85 | + plen = strlen(config.caches[0].dir) + 1 + sizeof(CACHEDIR) + 1 + 6 + int_max_len; |
| 86 | + path = wmalloc(plen + 1); |
| 87 | + sprintf(path, "%s/%s/%s", config.caches[0].dir, CACHEDIR, obj->co_path); |
| 88 | + if (mode) |
| 89 | + unlink(path); |
| 90 | + i = open(path, flags, mode); |
| 91 | + wfree(path); |
| 92 | + return i; |
| 93 | +} |
| 94 | + |
| 95 | +static void |
| 96 | +cache_unlink(obj) |
| 97 | + struct cache_object *obj; |
| 98 | +{ |
| 99 | + char *path; |
| 100 | + int plen; |
| 101 | + int i; |
| 102 | + |
| 103 | + plen = strlen(config.caches[0].dir) + 1 + sizeof(CACHEDIR) + 1 + 6 + int_max_len; |
| 104 | + path = wmalloc(plen + 1); |
| 105 | + sprintf(path, "%s/%s/%s", config.caches[0].dir, CACHEDIR, obj->co_path); |
| 106 | + unlink(path); |
| 107 | + wfree(path); |
| 108 | + return; |
| 109 | +} |
| 110 | + |
| 111 | +void |
| 112 | +wcache_setupfs(void) |
| 113 | +{ |
| 114 | + int i, j, k; |
| 115 | +struct cachedir *cd; |
| 116 | +struct cache_state state; |
| 117 | + |
| 118 | + for (cd = config.caches; cd < config.caches + config.ncaches; ++cd) { |
| 119 | + size_t len, dlen; |
| 120 | + char *dir; |
| 121 | + |
| 122 | + dlen = strlen(cd->dir) + sizeof(CACHEDIR) + 2 + 6 /* 0/1/2/ */; |
| 123 | + if ((dir = wmalloc(dlen)) == NULL) |
| 124 | + outofmemory(); |
| 125 | + |
| 126 | + snprintf(dir, dlen, "%s/%s", cd->dir, CACHEDIR); |
| 127 | + |
| 128 | + /* create base directory if it doesn't exist */ |
| 129 | + /*LINTED unsafe mkdir*/ |
| 130 | + if (mkdir(cd->dir, 0700) < 0 || mkdir(dir, 0700) < 0) { |
| 131 | + wlog(WLOG_ERROR, "%s: mkdir: %s", cd->dir, strerror(errno)); |
| 132 | + exit(8); |
| 133 | + } |
| 134 | + |
| 135 | + for (i = 0; i < 10; ++i) { |
| 136 | + snprintf(dir, dlen, "%s/%s/%d", cd->dir, CACHEDIR, i); |
| 137 | + |
| 138 | + /*LINTED unsafe mkdir*/ |
| 139 | + if (mkdir(dir, 0700) < 0) { |
| 140 | + wlog(WLOG_ERROR, "%s: mkdir: %s", dir, strerror(errno)); |
| 141 | + exit(8); |
| 142 | + } |
| 143 | + |
| 144 | + for (j = 0; j < 10; ++j) { |
| 145 | + snprintf(dir, dlen, "%s/%s/%d/%d", cd->dir, CACHEDIR, i, j); |
| 146 | + /*LINTED unsafe mkdir*/ |
| 147 | + if (mkdir(dir, 0700) < 0) { |
| 148 | + wlog(WLOG_ERROR, "%s: mkdir: %s", dir, strerror(errno)); |
| 149 | + exit(8); |
| 150 | + } |
| 151 | + for (k = 0; k < 10; ++k) { |
| 152 | + snprintf(dir, dlen, "%s/%s/%d/%d/%d", cd->dir, CACHEDIR, i, j, k); |
| 153 | + /*LINTED unsafe mkdir*/ |
| 154 | + if (mkdir(dir, 0700) < 0) { |
| 155 | + wlog(WLOG_ERROR, "%s: mkdir: %s", dir, strerror(errno)); |
| 156 | + exit(8); |
| 157 | + } |
| 158 | + } |
| 159 | + } |
| 160 | + } |
| 161 | + wfree(dir); |
| 162 | + wlog(WLOG_NOTICE, "created cache directory structure for %s", cd->dir); |
| 163 | + } |
| 164 | + wcache_init(0); |
| 165 | + |
| 166 | + bzero(&state, sizeof(state)); |
| 167 | + state.cs_id = 1000; |
| 168 | + state.cs_size = 0; |
| 169 | + cache_writestate(&state); |
| 170 | + |
| 171 | + wlog(WLOG_NOTICE, "wrote initial cache state"); |
| 172 | + wcache_shutdown(); |
| 173 | +} |
| 174 | + |
| 175 | +void |
| 176 | +wcache_shutdown(void) |
| 177 | +{ |
| 178 | + cache_writestate(&state); |
| 179 | +} |
| 180 | + |
| 181 | +void |
| 182 | +wcache_init(readstate) |
| 183 | + int readstate; |
| 184 | +{ |
| 185 | +struct cachedir *cd; |
| 186 | + int i; |
| 187 | + |
| 188 | + if (config.ncaches == 0) { |
| 189 | + wlog(WLOG_WARNING, "no cache directories specified"); |
| 190 | + return; |
| 191 | + } |
| 192 | + |
| 193 | + /* only one cache dir supported for now... */ |
| 194 | + for (cd = config.caches; cd < config.caches + config.ncaches; ++cd) { |
| 195 | + } |
| 196 | + |
| 197 | + int_max_len = (int) log10((double) INT_MAX) + 1; |
| 198 | + |
| 199 | + TAILQ_INIT(&objects); |
| 200 | + |
| 201 | + cache_getstate(&state); |
| 202 | + if (readstate) { |
| 203 | + expire_sched(); |
| 204 | + } |
| 205 | +} |
| 206 | + |
| 207 | +static void |
| 208 | +expire_sched() |
| 209 | +{ |
| 210 | + expire_tv.tv_usec = 0; |
| 211 | + expire_tv.tv_sec = config.cache_expevery; |
| 212 | + evtimer_set(&expire_ev, run_expiry, NULL); |
| 213 | + event_add(&expire_ev, &expire_tv); |
| 214 | +} |
| 215 | + |
| 216 | +void |
| 217 | +wcache_release(obj, comp) |
| 218 | + struct cache_object *obj; |
| 219 | + int comp; |
| 220 | +{ |
| 221 | + WDEBUG((WLOG_DEBUG, "release %s, comp=%d", obj->co_key, comp)); |
| 222 | + |
| 223 | + if (comp) { |
| 224 | + if (!obj->co_complete) |
| 225 | + state.cs_size += obj->co_size; |
| 226 | + obj->co_complete = 1; |
| 227 | + } else if (!obj->co_complete) { |
| 228 | + wcache_evict(obj); |
| 229 | + } |
| 230 | +} |
| 231 | + |
| 232 | +static void |
| 233 | +wcache_evict(obj) |
| 234 | + struct cache_object *obj; |
| 235 | +{ |
| 236 | + TAILQ_REMOVE(&objects, obj, entries); |
| 237 | + cache_unlink(obj); |
| 238 | + state.cs_size -= obj->co_size; |
| 239 | + idx_rem(obj); |
| 240 | + wcache_free_object(obj); |
| 241 | +} |
| 242 | + |
| 243 | +struct cache_object * |
| 244 | +wcache_find_object(key, fd, flags) |
| 245 | + const char *key; |
| 246 | + int *fd, flags; |
| 247 | +{ |
| 248 | +struct cache_object *co; |
| 249 | + |
| 250 | + WDEBUG((WLOG_DEBUG, "wcache_find_object: looking for %s", key)); |
| 251 | + co = idx_find(key); |
| 252 | + |
| 253 | + if (co) { |
| 254 | + /* |
| 255 | + * If they only want it for writing, fail if it exists |
| 256 | + */ |
| 257 | + if (flags & WCACHE_WRONLY) |
| 258 | + return NULL; |
| 259 | + |
| 260 | + if (!strcmp(key, co->co_key)) { |
| 261 | + if (!co->co_complete) { |
| 262 | + return NULL; |
| 263 | + } |
| 264 | + *fd = cache_open(co, O_RDONLY, 0); |
| 265 | + WDEBUG((WLOG_DEBUG, "found! fd=%d", *fd)); |
| 266 | + if (*fd == -1) { |
| 267 | + wlog(WLOG_WARNING, "opening cache file %s: %s", co->co_path, strerror(errno)); |
| 268 | + co = NULL; |
| 269 | + } |
| 270 | + return co; |
| 271 | + } |
| 272 | + } |
| 273 | + |
| 274 | + co = wcache_new_object(key); |
| 275 | + idx_add(co); |
| 276 | + if ((*fd = cache_open(co, O_WRONLY | O_CREAT | O_EXCL, 0600)) == -1) { |
| 277 | + wlog(WLOG_WARNING, "opening cached file: %s", strerror(errno)); |
| 278 | + wcache_free_object(co); |
| 279 | + return NULL; |
| 280 | + } |
| 281 | + |
| 282 | + return co; |
| 283 | +} |
| 284 | + |
| 285 | +static void |
| 286 | +cache_writestate(state) |
| 287 | + struct cache_state *state; |
| 288 | +{ |
| 289 | + FILE *stfil; |
| 290 | + char *stpath; |
| 291 | + int stlen; |
| 292 | +struct cache_object *obj; |
| 293 | + |
| 294 | + stlen = strlen(config.caches[0].dir) + 1 + 5 + 1; |
| 295 | + if ((stpath = wmalloc(stlen)) == NULL) |
| 296 | + outofmemory(); |
| 297 | + snprintf(stpath, stlen, "%s/%s", config.caches[0].dir, "state"); |
| 298 | + if ((stfil = fopen(stpath, "w")) == NULL) { |
| 299 | + wlog(WLOG_WARNING, "opening cache dir %s: %s", stpath, strerror(errno)); |
| 300 | + exit(8); |
| 301 | + } |
| 302 | + fprintf(stfil, "%lld %lld\n", state->cs_id, state->cs_size); |
| 303 | + TAILQ_FOREACH(obj, &objects, entries) { |
| 304 | + if (!obj->co_complete) |
| 305 | + continue; |
| 306 | + fprintf(stfil, "%s %s %d %lu %lu %d %lu\n", obj->co_key, obj->co_path, obj->co_size, |
| 307 | + (unsigned long) obj->co_time, (unsigned long) obj->co_lru, obj->co_id, |
| 308 | + (unsigned long) obj->co_expires); |
| 309 | + } |
| 310 | + fclose(stfil); |
| 311 | +} |
| 312 | + |
| 313 | +static void |
| 314 | +cache_getstate(state) |
| 315 | + struct cache_state *state; |
| 316 | +{ |
| 317 | + FILE *stfil; |
| 318 | + char *stpath; |
| 319 | + int stlen; |
| 320 | +struct cache_object *obj; |
| 321 | + int i; |
| 322 | + char *s; |
| 323 | + size_t l; |
| 324 | + |
| 325 | + stlen = strlen(config.caches[0].dir) + 1 + 5 + 1; |
| 326 | + if ((stpath = wmalloc(stlen)) == NULL) |
| 327 | + outofmemory(); |
| 328 | + snprintf(stpath, stlen, "%s/%s", config.caches[0].dir, "state"); |
| 329 | + if ((stfil = fopen(stpath, "r")) == NULL) { |
| 330 | + wlog(WLOG_WARNING, "opening cache state %s: %s", stpath, strerror(errno)); |
| 331 | + wlog(WLOG_WARNING, "using default cache state"); |
| 332 | + state->cs_id = 1000; |
| 333 | + return; |
| 334 | + } |
| 335 | + |
| 336 | + if (fscanf(stfil, "%lld %lld\n", &state->cs_id, &state->cs_size) != 2) { |
| 337 | + wlog(WLOG_ERROR, "data format error in cache state file %s", stpath); |
| 338 | + exit(8); |
| 339 | + } |
| 340 | + |
| 341 | + s = wmalloc(65535); |
| 342 | + while (fgets(s, 65534, stfil)) { |
| 343 | + char url[65535], path[128]; |
| 344 | + int size, time, lru, id, expires; |
| 345 | + struct cache_object *obj; |
| 346 | + if (sscanf(s, "%65534s %127s %d %d %d %d %d", url, path, &size, &time, &lru, &id, &expires) != 7) { |
| 347 | + wlog(WLOG_ERROR, "data format error in cache state file %s", stpath); |
| 348 | + exit(8); |
| 349 | + } |
| 350 | + obj = wmalloc(sizeof(*obj)); |
| 351 | + bzero(obj, sizeof(*obj)); |
| 352 | + obj->co_key = wstrdup(url); |
| 353 | + obj->co_size = size; |
| 354 | + obj->co_path = wstrdup(path); |
| 355 | + obj->co_complete = 1; |
| 356 | + obj->co_time = time; |
| 357 | + obj->co_lru = lru; |
| 358 | + obj->co_id = id; |
| 359 | + obj->co_expires = expires; |
| 360 | + TAILQ_INSERT_TAIL(&objects, obj, entries); |
| 361 | + idx_add(obj); |
| 362 | + WDEBUG((WLOG_DEBUG, "load %s %s from cache", obj->co_key, obj->co_path)); |
| 363 | + } |
| 364 | + |
| 365 | +} |
| 366 | + |
| 367 | +static int |
| 368 | +cache_next_id(void) |
| 369 | +{ |
| 370 | + int i; |
| 371 | + i = state.cs_id; |
| 372 | + ++state.cs_id; |
| 373 | + return i; |
| 374 | +} |
| 375 | + |
| 376 | +static struct cache_object * |
| 377 | +wcache_new_object(key) |
| 378 | + const char *key; |
| 379 | +{ |
| 380 | +struct cache_object *ret; |
| 381 | + int i; |
| 382 | + char *p, *s, a[11]; |
| 383 | + |
| 384 | + if ((ret = wcalloc(1, sizeof(*ret))) == NULL) { |
| 385 | + outofmemory(); |
| 386 | + /*NOTREACHED*/ |
| 387 | + } |
| 388 | + |
| 389 | + ret->co_id = cache_next_id(); |
| 390 | + ret->co_key = wstrdup(key); |
| 391 | + |
| 392 | + assert(ret->co_id > 999); |
| 393 | + ret->co_plen = int_max_len + 6; |
| 394 | + if ((ret->co_path = wmalloc(ret->co_plen + 1)) == NULL) { |
| 395 | + outofmemory(); |
| 396 | + /*NOTREACHED*/ |
| 397 | + } |
| 398 | + p = ret->co_path; |
| 399 | + snprintf(a, 10, "%d", ret->co_id); |
| 400 | + s = a + strlen(a) - 1; |
| 401 | + WDEBUG((WLOG_DEBUG, "id=%d a=%s", ret->co_id, a)); |
| 402 | + |
| 403 | + for (i = 0; i < 3; ++i) { |
| 404 | + *p++ = *s--; |
| 405 | + *p++ = '/'; |
| 406 | + } |
| 407 | + *p = '\0'; |
| 408 | + if (strlcat(ret->co_path, a, ret->co_plen + 1) >= ret->co_plen + 1) |
| 409 | + abort(); |
| 410 | + WDEBUG((WLOG_DEBUG, "new object path is [%s], len %d", ret->co_path, ret->co_plen)); |
| 411 | + |
| 412 | + TAILQ_INSERT_HEAD(&objects, ret, entries); |
| 413 | + return ret; |
| 414 | +} |
| 415 | + |
| 416 | +static void |
| 417 | +wcache_free_object(obj) |
| 418 | + struct cache_object *obj; |
| 419 | +{ |
| 420 | +} |
| 421 | + |
| 422 | +static void |
| 423 | +run_expiry(int fd, short ev, void *data) |
| 424 | +{ |
| 425 | + w_size_t wantsize; |
| 426 | + int i; |
| 427 | +struct cache_object *obj; |
| 428 | + |
| 429 | + WDEBUG((WLOG_DEBUG, "expire: start, run every %d, cache is %lld bytes large", |
| 430 | + config.cache_expevery, state.cs_size)); |
| 431 | + |
| 432 | + cache_writestate(&state); |
| 433 | + wantsize = config.caches[0].maxsize * ((100.0-config.cache_expthresh)/100); |
| 434 | + if (state.cs_size <= wantsize) { |
| 435 | + WDEBUG((WLOG_DEBUG, "expire: cache only %lld bytes large", state.cs_size)); |
| 436 | + expire_sched(); |
| 437 | + return; |
| 438 | + } |
| 439 | + while (state.cs_size > wantsize) { |
| 440 | + struct cache_object *obj = TAILQ_LAST(&objects, objlist); |
| 441 | + WDEBUG((WLOG_DEBUG, "expiring some objects, size=%lld, want=%lld, obj=%p, last=%p, *last=%p", |
| 442 | + state.cs_size, wantsize, obj, objects.tqh_last, *objects.tqh_last)); |
| 443 | + if (!obj) |
| 444 | + break; |
| 445 | + wcache_evict(obj); |
| 446 | + } |
| 447 | + expire_sched(); |
| 448 | +} |
| 449 | + |
| 450 | +static void |
| 451 | +idx_add(obj) |
| 452 | + struct cache_object *obj; |
| 453 | +{ |
| 454 | + struct key_idx_head *head = &key_idx[hash((ub1 *)obj->co_key)].head; |
| 455 | + struct key_idx_entry *entry = wmalloc(sizeof(*entry)); |
| 456 | + bzero(entry, sizeof(*entry)); |
| 457 | + entry->obj = obj; |
| 458 | + LIST_INSERT_HEAD(head, entry, entries); |
| 459 | +} |
| 460 | + |
| 461 | +static struct cache_object * |
| 462 | +idx_find(key) |
| 463 | + const char *key; |
| 464 | +{ |
| 465 | + struct key_idx_head *head = &key_idx[hash((ub1 *)key)].head; |
| 466 | + struct key_idx_entry *entry; |
| 467 | + LIST_FOREACH(entry, head, entries) |
| 468 | + if (!strcmp(entry->obj->co_key, key)) |
| 469 | + return entry->obj; |
| 470 | + return NULL; |
| 471 | +} |
| 472 | + |
| 473 | +static void |
| 474 | +idx_rem(obj) |
| 475 | + struct cache_object *obj; |
| 476 | +{ |
| 477 | + struct key_idx_head *head = &key_idx[hash((ub1 *)obj->co_key)].head; |
| 478 | + struct key_idx_entry *entry; |
| 479 | + LIST_FOREACH(entry, head, entries) |
| 480 | + if (entry->obj == obj) |
| 481 | + LIST_REMOVE(entry, entries); |
| 482 | +} |
| 483 | + |
| 484 | +#define mix(a,b,c) \ |
| 485 | +{ \ |
| 486 | + a -= b; a -= c; a ^= (c>>13); \ |
| 487 | + b -= c; b -= a; b ^= (a<<8); \ |
| 488 | + c -= a; c -= b; c ^= (b>>13); \ |
| 489 | + a -= b; a -= c; a ^= (c>>12); \ |
| 490 | + b -= c; b -= a; b ^= (a<<16); \ |
| 491 | + c -= a; c -= b; c ^= (b>>5); \ |
| 492 | + a -= b; a -= c; a ^= (c>>3); \ |
| 493 | + b -= c; b -= a; b ^= (a<<10); \ |
| 494 | + c -= a; c -= b; c ^= (b>>15); \ |
| 495 | +} |
| 496 | + |
| 497 | +/* |
| 498 | + * By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net. You may use this |
| 499 | + * code any way you wish, private, educational, or commercial. It's free. |
| 500 | + * |
| 501 | + * See http://burtleburtle.net/bob/hash/evahash.html |
| 502 | + */ |
| 503 | + |
| 504 | +ub4 hash(k) |
| 505 | +register const ub1 *k; /* the key */ |
| 506 | +{ |
| 507 | + register ub4 a,b,c,len; |
| 508 | + register ub4 length = strlen((char *)k); |
| 509 | + register ub4 initval = 0; |
| 510 | + |
| 511 | + /* Set up the internal state */ |
| 512 | + len = length; |
| 513 | + a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ |
| 514 | + c = initval; /* the previous hash value */ |
| 515 | + |
| 516 | + /*---------------------------------------- handle most of the key */ |
| 517 | + while (len >= 12) |
| 518 | + { |
| 519 | + a += (k[0] +((ub4)k[1]<<8) +((ub4)k[2]<<16) +((ub4)k[3]<<24)); |
| 520 | + b += (k[4] +((ub4)k[5]<<8) +((ub4)k[6]<<16) +((ub4)k[7]<<24)); |
| 521 | + c += (k[8] +((ub4)k[9]<<8) +((ub4)k[10]<<16)+((ub4)k[11]<<24)); |
| 522 | + mix(a,b,c); |
| 523 | + k += 12; len -= 12; |
| 524 | + } |
| 525 | + |
| 526 | + /*------------------------------------- handle the last 11 bytes */ |
| 527 | + c += length; |
| 528 | + switch(len) /* all the case statements fall through */ |
| 529 | + { |
| 530 | + case 11: c+=((ub4)k[10]<<24); |
| 531 | + case 10: c+=((ub4)k[9]<<16); |
| 532 | + case 9 : c+=((ub4)k[8]<<8); |
| 533 | + /* the first byte of c is reserved for the length */ |
| 534 | + case 8 : b+=((ub4)k[7]<<24); |
| 535 | + case 7 : b+=((ub4)k[6]<<16); |
| 536 | + case 6 : b+=((ub4)k[5]<<8); |
| 537 | + case 5 : b+=k[4]; |
| 538 | + case 4 : a+=((ub4)k[3]<<24); |
| 539 | + case 3 : a+=((ub4)k[2]<<16); |
| 540 | + case 2 : a+=((ub4)k[1]<<8); |
| 541 | + case 1 : a+=k[0]; |
| 542 | + /* case 0: nothing left to add */ |
| 543 | + } |
| 544 | + mix(a,b,c); |
| 545 | + /*-------------------------------------------- report the result */ |
| 546 | + return c & hashmask(HASH_BITS); |
| 547 | +} |
Index: trunk/willow/src/willow/strlcat.c |
— | — | @@ -0,0 +1,65 @@ |
| 2 | +/* @(#)$Header$ */ |
| 3 | +/* $NetBSD: strlcat.c,v 1.16 2003/10/27 00:12:42 lukem Exp $ */ |
| 4 | +/* $OpenBSD: strlcat.c,v 1.10 2003/04/12 21:56:39 millert Exp $ */ |
| 5 | + |
| 6 | +/* |
| 7 | + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> |
| 8 | + * |
| 9 | + * Permission to use, copy, modify, and distribute this software for any |
| 10 | + * purpose with or without fee is hereby granted, provided that the above |
| 11 | + * copyright notice and this permission notice appear in all copies. |
| 12 | + * |
| 13 | + * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL |
| 14 | + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES |
| 15 | + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE |
| 16 | + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 17 | + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| 18 | + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| 19 | + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 20 | + */ |
| 21 | + |
| 22 | +#if defined __SUNPRO_C || defined __DECC || defined __HP_cc |
| 23 | +# pragma ident "@(#)$Header$" |
| 24 | +#endif |
| 25 | + |
| 26 | +#include <sys/types.h> |
| 27 | +#include <assert.h> |
| 28 | +#include <string.h> |
| 29 | + |
| 30 | +/* |
| 31 | + * Appends src to string dst of size siz (unlike strncat, siz is the |
| 32 | + * full size of dst, not space left). At most siz-1 characters |
| 33 | + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). |
| 34 | + * Returns strlen(src) + MIN(siz, strlen(initial dst)). |
| 35 | + * If retval >= siz, truncation occurred. |
| 36 | + */ |
| 37 | +size_t |
| 38 | +strlcat(dst, src, siz) |
| 39 | + char *dst; |
| 40 | + const char *src; |
| 41 | + size_t siz; |
| 42 | +{ |
| 43 | + char *d = dst; |
| 44 | + const char *s = src; |
| 45 | + size_t n = siz; |
| 46 | + size_t dlen; |
| 47 | + |
| 48 | + /* Find the end of dst and adjust bytes left but don't go past end */ |
| 49 | + while (n-- != 0 && *d != '\0') |
| 50 | + d++; |
| 51 | + dlen = d - dst; |
| 52 | + n = siz - dlen; |
| 53 | + |
| 54 | + if (n == 0) |
| 55 | + return(dlen + strlen(s)); |
| 56 | + while (*s != '\0') { |
| 57 | + if (n != 1) { |
| 58 | + *d++ = *s; |
| 59 | + n--; |
| 60 | + } |
| 61 | + s++; |
| 62 | + } |
| 63 | + *d = '\0'; |
| 64 | + |
| 65 | + return(dlen + (s - src)); /* count does not include NUL */ |
| 66 | +} |
Index: trunk/willow/src/willow/wconfig.c |
— | — | @@ -0,0 +1,128 @@ |
| 2 | +/* @(#) $Header$ */ |
| 3 | +/* This source code is in the public domain. */ |
| 4 | +/* |
| 5 | + * Willow: Lightweight HTTP reverse-proxy. |
| 6 | + * wconfig: configuration. |
| 7 | + */ |
| 8 | + |
| 9 | +#if defined __SUNPRO_C || defined __DECC || defined __HP_cc |
| 10 | +# pragma ident "@(#)$Header$" |
| 11 | +#endif |
| 12 | + |
| 13 | +#include <sys/types.h> |
| 14 | +#include <sys/socket.h> |
| 15 | + |
| 16 | +#include <netinet/in.h> |
| 17 | + |
| 18 | +#include <arpa/inet.h> |
| 19 | + |
| 20 | +#include <stdlib.h> |
| 21 | +#include <stdio.h> |
| 22 | +#include <string.h> |
| 23 | +#include <syslog.h> |
| 24 | +#include <errno.h> |
| 25 | +#include <strings.h> |
| 26 | + |
| 27 | +#include "willow.h" |
| 28 | +#include "wconfig.h" |
| 29 | +#include "wbackend.h" |
| 30 | +#include "wlog.h" |
| 31 | +#include "confparse.h" |
| 32 | + |
| 33 | +#define CONFIGFILE SYSCONFDIR "/willow.conf" |
| 34 | + |
| 35 | +int yyparse(); |
| 36 | + |
| 37 | +struct listener **listeners; |
| 38 | +int nlisteners; |
| 39 | +struct configuration config; |
| 40 | + |
| 41 | +const char *current_file; |
| 42 | + |
| 43 | +void |
| 44 | +wconfig_init(const char *file) |
| 45 | +{ |
| 46 | + FILE *cfg; |
| 47 | +extern FILE *yyin; |
| 48 | + |
| 49 | + if (file == NULL) |
| 50 | + file = CONFIGFILE; |
| 51 | + current_file = file; |
| 52 | + |
| 53 | + if ((cfg = fopen(file, "r")) == NULL) { |
| 54 | + perror(file); |
| 55 | + exit(8); |
| 56 | + } |
| 57 | + wlog(WLOG_NOTICE, "loading configuration from %s", current_file); |
| 58 | + yyin = cfg; |
| 59 | + newconf_init(); |
| 60 | + |
| 61 | + if (yyparse()) { |
| 62 | + wlog(WLOG_ERROR, "could not parse configuration file"); |
| 63 | + nerrors++; |
| 64 | + } |
| 65 | + if (!nlisteners) { |
| 66 | + wlog(WLOG_ERROR, "no listeners defined"); |
| 67 | + nerrors++; |
| 68 | + } |
| 69 | + if (!nbackends) { |
| 70 | + wlog(WLOG_ERROR, "no backends defined"); |
| 71 | + nerrors++; |
| 72 | + } |
| 73 | + if (nerrors) { |
| 74 | + wlog(WLOG_ERROR, "%d error(s) in configuration file. cannot continue.", nerrors); |
| 75 | + exit(8); |
| 76 | + } |
| 77 | + |
| 78 | + (void)fclose(cfg); |
| 79 | +} |
| 80 | + |
| 81 | +int |
| 82 | +add_listener(addr, port) |
| 83 | + const char *addr; |
| 84 | + int port; |
| 85 | +{ |
| 86 | +struct listener *nl; |
| 87 | + |
| 88 | + if (port < 0 || port > 65535) { |
| 89 | + conf_report_error("invalid listener port %d", port); |
| 90 | + nerrors++; |
| 91 | + return -1; |
| 92 | + } |
| 93 | + |
| 94 | + if ((nl = wcalloc(1, sizeof(*nl))) == NULL) |
| 95 | + outofmemory(); |
| 96 | + |
| 97 | + if ((listeners = wrealloc(listeners, sizeof(struct listener *) * ++nlisteners)) == NULL) |
| 98 | + outofmemory(); |
| 99 | + |
| 100 | + nl->port = port; |
| 101 | + nl->name = wstrdup(addr); |
| 102 | + nl->addr.sin_family = AF_INET; |
| 103 | + nl->addr.sin_port = htons(nl->port); |
| 104 | + nl->addr.sin_addr.s_addr = inet_addr(nl->name); |
| 105 | + listeners[nlisteners - 1] = nl; |
| 106 | + wlog(WLOG_NOTICE, "listening on %s:%d", addr, port); |
| 107 | + return 0; |
| 108 | +} |
| 109 | + |
| 110 | +int |
| 111 | +add_cachedir(dir, size) |
| 112 | + const char *dir; |
| 113 | + int size; |
| 114 | +{ |
| 115 | + if (size < 1) { |
| 116 | + conf_report_error("invalid cache size %d\n", size); |
| 117 | + nerrors++; |
| 118 | + return -1; |
| 119 | + } |
| 120 | + |
| 121 | + config.caches = wrealloc(config.caches, sizeof(*config.caches) * (config.ncaches + 1)); |
| 122 | + config.caches[config.ncaches].dir = wstrdup(dir); |
| 123 | + config.caches[config.ncaches].maxsize = size; |
| 124 | + wlog(WLOG_NOTICE, "cache dir \"%s\", size %d bytes", |
| 125 | + config.caches[config.ncaches].dir, |
| 126 | + config.caches[config.ncaches].maxsize); |
| 127 | + config.ncaches++; |
| 128 | + return 0; |
| 129 | +} |
Index: trunk/willow/src/willow/willow.c |
— | — | @@ -0,0 +1,528 @@ |
| 2 | +/* @(#) $Header$ */ |
| 3 | +/* This source code is in the public domain. */ |
| 4 | +/* |
| 5 | + * Willow: Lightweight HTTP reverse-proxy. |
| 6 | + */ |
| 7 | + |
| 8 | +#if defined __SUNPRO_C || defined __DECC || defined __HP_cc |
| 9 | +# pragma ident "@(#)$Header$" |
| 10 | +#endif |
| 11 | + |
| 12 | +#include <sys/mman.h> |
| 13 | + |
| 14 | +#include <stdio.h> |
| 15 | +#include <stdlib.h> |
| 16 | +#include <signal.h> |
| 17 | +#include <stdarg.h> |
| 18 | +#include <string.h> |
| 19 | +#include <unistd.h> |
| 20 | +#include <errno.h> |
| 21 | +#include <pwd.h> |
| 22 | +#include <grp.h> |
| 23 | +#include <strings.h> |
| 24 | +#include <ctype.h> |
| 25 | + |
| 26 | +#include "wlog.h" |
| 27 | +#include "wnet.h" |
| 28 | +#include "wconfig.h" |
| 29 | +#include "willow.h" |
| 30 | +#include "whttp.h" |
| 31 | +#include "wcache.h" |
| 32 | + |
| 33 | +#ifdef WDEBUG_ALLOC |
| 34 | +static void ae_checkleaks(void); |
| 35 | +static void segv_action(int, siginfo_t *, void *); |
| 36 | +#endif |
| 37 | + |
| 38 | +static const char *progname; |
| 39 | + |
| 40 | +#define min(x,y) ((x) < (y) ? (x) : (y)) |
| 41 | + |
| 42 | +/*ARGSUSED*/ |
| 43 | +static void |
| 44 | +sig_exit(s) |
| 45 | + int s; |
| 46 | +{ |
| 47 | + wnet_exit = 1; |
| 48 | +} |
| 49 | + |
| 50 | +static void |
| 51 | +usage(void) |
| 52 | +{ |
| 53 | + (void)fprintf(stderr, "usage: %s [-fzv]\n" |
| 54 | + "\t-f\trun in foreground (don't detach)\n" |
| 55 | + "\t-z\tcreate cache directory structure and exit\n" |
| 56 | + "\t-v\tprint version number and exit\n" |
| 57 | + , progname); |
| 58 | +} |
| 59 | + |
| 60 | +#ifdef __lint |
| 61 | +# pragma error_messages(off, E_H_C_CHECK2) |
| 62 | +#endif |
| 63 | + |
| 64 | +int |
| 65 | +main(argc, argv) |
| 66 | + char *argv[]; |
| 67 | + int argc; |
| 68 | +{ |
| 69 | + int i; |
| 70 | + int zflag = 0; |
| 71 | + char *cfg = NULL; |
| 72 | + |
| 73 | +#ifdef WDEBUG_ALLOC |
| 74 | +struct sigaction segv_act; |
| 75 | + bzero(&segv_act, sizeof(segv_act)); |
| 76 | + segv_act.sa_sigaction = segv_action; |
| 77 | + segv_act.sa_flags = SA_SIGINFO; |
| 78 | + |
| 79 | + sigaction(SIGSEGV, &segv_act, NULL); |
| 80 | +#endif |
| 81 | + |
| 82 | + progname = argv[0]; |
| 83 | + |
| 84 | + while ((i = getopt(argc, argv, "fzvc:")) != -1) { |
| 85 | + switch (i) { |
| 86 | + case 'z': |
| 87 | + zflag++; |
| 88 | + /*FALLTHRU*/ |
| 89 | + case 'f': |
| 90 | + config.foreground = 1; |
| 91 | + break; |
| 92 | + case 'v': |
| 93 | + (void)fprintf(stderr, "%s\n", PACKAGE_VERSION); |
| 94 | + exit(0); |
| 95 | + /*NOTREACHED*/ |
| 96 | + case 'c': |
| 97 | + cfg = optarg; |
| 98 | + break; |
| 99 | + default: |
| 100 | + usage(); |
| 101 | + exit(8); |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + argv += optind; |
| 106 | + argc -= optind; |
| 107 | + |
| 108 | + if (argc) { |
| 109 | + (void)fprintf(stderr, "%s: too many argments\n", progname); |
| 110 | + usage(); |
| 111 | + exit(8); |
| 112 | + } |
| 113 | + |
| 114 | + wnet_set_time(); |
| 115 | + |
| 116 | + wconfig_init(cfg); |
| 117 | + |
| 118 | + if (config.sgid) { |
| 119 | + struct group *group = getgrnam(config.sgid); |
| 120 | + if (!group) { |
| 121 | + fprintf(stderr, "group %s does not exist", config.sgid); |
| 122 | + exit(8); |
| 123 | + } |
| 124 | + if (setgid(group->gr_gid) < 0) { |
| 125 | + perror("setgid"); |
| 126 | + exit(8); |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + if (config.suid) { |
| 131 | + struct passwd *user = getpwnam(config.suid); |
| 132 | + if (!user) { |
| 133 | + fprintf(stderr, "user %s does not exist", config.suid); |
| 134 | + exit(8); |
| 135 | + } |
| 136 | + if (setuid(user->pw_uid) < 0) { |
| 137 | + perror("setuid"); |
| 138 | + exit(8); |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + wlog_init(); |
| 143 | + if (zflag) { |
| 144 | + wcache_setupfs(); |
| 145 | + exit(0); |
| 146 | + } |
| 147 | + |
| 148 | + /* |
| 149 | + * HTTP should be initialised before the network so that |
| 150 | + * the wlogwriter exits cleanly. |
| 151 | + */ |
| 152 | + whttp_init(); |
| 153 | + wnet_init(); |
| 154 | + wcache_init(1); |
| 155 | + |
| 156 | + (void)signal(SIGINT, sig_exit); |
| 157 | + (void)signal(SIGTERM, sig_exit); |
| 158 | + |
| 159 | + wlog(WLOG_NOTICE, "running"); |
| 160 | + |
| 161 | +#ifdef WDEBUG_ALLOC |
| 162 | + (void)fprintf(stderr, "debug allocator enabled, assuming -f\n"); |
| 163 | + config.foreground = 1; |
| 164 | +#endif |
| 165 | + |
| 166 | + if (!config.foreground) |
| 167 | + daemon(0, 0); |
| 168 | + |
| 169 | + wnet_run(); |
| 170 | + wlog_close(); |
| 171 | + wcache_shutdown(); |
| 172 | + whttp_shutdown(); |
| 173 | + |
| 174 | +#ifdef WDEBUG_ALLOC |
| 175 | + ae_checkleaks(); |
| 176 | +#endif |
| 177 | + return EXIT_SUCCESS; |
| 178 | +} |
| 179 | + |
| 180 | +#ifdef __lint |
| 181 | +# pragma error_messages(default, E_H_C_CHECK2) |
| 182 | +#endif |
| 183 | + |
| 184 | +void |
| 185 | +outofmemory(void) |
| 186 | +{ |
| 187 | + static int count; |
| 188 | + |
| 189 | + if (count++) |
| 190 | + abort(); |
| 191 | + |
| 192 | + wlog(WLOG_ERROR, "fatal: out of memory. exiting."); |
| 193 | + exit(8); |
| 194 | +} |
| 195 | + |
| 196 | +void |
| 197 | +realloc_addchar(sp, c) |
| 198 | + char **sp; |
| 199 | + int c; |
| 200 | +{ |
| 201 | + char *p; |
| 202 | + int len; |
| 203 | + |
| 204 | + if (*sp) |
| 205 | + len = strlen(*sp); |
| 206 | + else |
| 207 | + len = 0; |
| 208 | + |
| 209 | + if ((*sp = wrealloc(*sp, len + 2)) == NULL) |
| 210 | + outofmemory(); |
| 211 | + p = *sp + len; |
| 212 | + *p++ = (char) c; |
| 213 | + *p++ = '\0'; |
| 214 | +} |
| 215 | + |
| 216 | +void |
| 217 | +realloc_strcat(sp, s) |
| 218 | + char **sp; |
| 219 | + const char *s; |
| 220 | +{ |
| 221 | + int len; |
| 222 | + |
| 223 | + if (*sp) |
| 224 | + len = strlen(*sp); |
| 225 | + else |
| 226 | + len = 1; |
| 227 | + if ((*sp = wrealloc(*sp, len + strlen(s) + 1)) == NULL) |
| 228 | + outofmemory(); |
| 229 | + (void)strcat(*sp, s); |
| 230 | +} |
| 231 | + |
| 232 | +char ** |
| 233 | +wstrvec(str, sep, lim) |
| 234 | + const char *str, *sep; |
| 235 | + int lim; |
| 236 | +{ |
| 237 | + char **result = NULL; |
| 238 | + int nres = 0; |
| 239 | + char *s; |
| 240 | +const char *st = str; |
| 241 | + |
| 242 | + while ((!lim || --lim) && (s = strstr(st, sep))) { |
| 243 | + result = wrealloc(result, ++nres * sizeof(char *)); |
| 244 | + while (isspace(*st)) |
| 245 | + st++; |
| 246 | + result[nres - 1] = wmalloc((s - st) + 1); |
| 247 | + memcpy(result[nres - 1], st, s - st); |
| 248 | + result[nres - 1][s - st] = '\0'; |
| 249 | + st = s + strlen(sep); |
| 250 | + } |
| 251 | + |
| 252 | + result = wrealloc(result, ++nres * sizeof(char *)); |
| 253 | + while (isspace(*st)) |
| 254 | + st++; |
| 255 | + result[nres - 1] = wstrdup(st); |
| 256 | + |
| 257 | + result = wrealloc(result, (nres + 1) * sizeof(char *)); |
| 258 | + result[nres] = NULL; |
| 259 | + return result; |
| 260 | +} |
| 261 | + |
| 262 | +void wstrvecfree(vec) |
| 263 | + char **vec; |
| 264 | +{ |
| 265 | + char **s = vec; |
| 266 | + while (*s) { |
| 267 | + wfree(*s); |
| 268 | + s++; |
| 269 | + } |
| 270 | + wfree(vec); |
| 271 | +} |
| 272 | + |
| 273 | + |
| 274 | +int char_table[256] = { |
| 275 | + /* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 276 | + /* 8 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 277 | + /* 16 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 278 | + /* 24 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 279 | + /* 32 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 280 | + /* 40 */ 0, 0, 0, 0, 0, 0, CHAR_HOST, 0, |
| 281 | + /* 48 */ CHAR_HOST, CHAR_HOST, CHAR_HOST, CHAR_HOST, |
| 282 | + /* 52 */ CHAR_HOST, CHAR_HOST, CHAR_HOST, CHAR_HOST, |
| 283 | + /* 56 */ CHAR_HOST, CHAR_HOST, CHAR_HOST, 0, |
| 284 | + /* 60 */ 0, 0, 0, 0, |
| 285 | + /* 64 */ 0, CHAR_HOST, CHAR_HOST, CHAR_HOST, |
| 286 | + /* 68 */ CHAR_HOST, CHAR_HOST, CHAR_HOST, CHAR_HOST, |
| 287 | + /* 72 */ CHAR_HOST, CHAR_HOST, CHAR_HOST, CHAR_HOST, |
| 288 | + /* 76 */ CHAR_HOST, CHAR_HOST, CHAR_HOST, CHAR_HOST, |
| 289 | + /* 80 */ CHAR_HOST, CHAR_HOST, CHAR_HOST, CHAR_HOST, |
| 290 | + /* 84 */ CHAR_HOST, CHAR_HOST, CHAR_HOST, CHAR_HOST, |
| 291 | + /* 88 */ CHAR_HOST, CHAR_HOST, CHAR_HOST, 0, |
| 292 | + /* 92 */ 0, 0, 0, 0, |
| 293 | + /* 96 */ 0, CHAR_HOST, CHAR_HOST, CHAR_HOST, |
| 294 | + /* 100 */ CHAR_HOST, CHAR_HOST, CHAR_HOST, CHAR_HOST, |
| 295 | + /* 104 */ CHAR_HOST, CHAR_HOST, CHAR_HOST, CHAR_HOST, |
| 296 | + /* 108 */ CHAR_HOST, CHAR_HOST, CHAR_HOST, CHAR_HOST, |
| 297 | + /* 112 */ CHAR_HOST, CHAR_HOST, CHAR_HOST, CHAR_HOST, |
| 298 | + /* 116 */ CHAR_HOST, CHAR_HOST, CHAR_HOST, CHAR_HOST, |
| 299 | + /* 120 */ CHAR_HOST, CHAR_HOST, 0, 0, |
| 300 | + /* 124 */ 0, 0, 0, 0, |
| 301 | + /* 136 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 302 | + /* 144 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 303 | + /* 152 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 304 | + /* 160 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 305 | + /* 168 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 306 | + /* 176 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 307 | + /* 184 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 308 | + /* 192 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 309 | + /* 200 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 310 | + /* 208 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 311 | + /* 216 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 312 | + /* 224 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 313 | + /* 232 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 314 | + /* 240 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 315 | + /* 248 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| 316 | +}; |
| 317 | + |
| 318 | +#ifdef WDEBUG_ALLOC |
| 319 | + |
| 320 | +struct alloc_entry { |
| 321 | + char *ae_addr; |
| 322 | + char *ae_mapping; |
| 323 | + size_t ae_mapsize; |
| 324 | + size_t ae_size; |
| 325 | + int ae_freed; |
| 326 | + const char *ae_freed_file; |
| 327 | + int ae_freed_line; |
| 328 | + const char *ae_alloced_file; |
| 329 | + int ae_alloced_line; |
| 330 | +struct alloc_entry *ae_next; |
| 331 | +}; |
| 332 | + |
| 333 | +static struct alloc_entry allocs; |
| 334 | +static int pgsize; |
| 335 | + |
| 336 | +static void |
| 337 | +segv_action(sig, si, data) |
| 338 | + int sig; |
| 339 | + siginfo_t *si; |
| 340 | + void *data; |
| 341 | +{ |
| 342 | +struct alloc_entry *ae; |
| 343 | + |
| 344 | + /* |
| 345 | + * This is mostly non-standard, unportable and unreliable, but if the debug allocator |
| 346 | + * is enabled, it's more important to produce useful errors than conform to the letter |
| 347 | + * of the law. |
| 348 | + */ |
| 349 | + (void)fprintf(stderr, "SEGV at %p%s (pid %d)\n", si->si_addr, |
| 350 | +#ifdef SI_NOINFO |
| 351 | + si->si_code == SI_NOINFO ? " [SI_NOINFO]" : "", |
| 352 | +#else |
| 353 | + "", |
| 354 | +#endif |
| 355 | + (int) getpid()); |
| 356 | + for (ae = allocs.ae_next; ae; ae = ae->ae_next) |
| 357 | + if (/*!ae->ae_freed &&*/ (char *)si->si_addr > ae->ae_mapping && |
| 358 | + (char *)si->si_addr < ae->ae_mapping + ae->ae_mapsize) { |
| 359 | + (void)fprintf(stderr, "\t%p [map @ %p size %d] from %s:%d\n", ae->ae_addr, ae->ae_mapping, |
| 360 | + ae->ae_mapsize, ae->ae_alloced_file, ae->ae_alloced_line); |
| 361 | + break; |
| 362 | + } |
| 363 | + if (ae == NULL) |
| 364 | + (void)fprintf(stderr, "\tunknown address\n"); |
| 365 | + abort(); |
| 366 | + _exit(1); |
| 367 | +} |
| 368 | + |
| 369 | +static void |
| 370 | +ae_checkleaks(void) |
| 371 | +{ |
| 372 | +struct alloc_entry *ae; |
| 373 | + |
| 374 | + for (ae = allocs.ae_next; ae; ae = ae->ae_next) |
| 375 | + if (!ae->ae_freed) |
| 376 | + (void)fprintf(stderr, "%p @ %s:%d\n", ae->ae_addr, ae->ae_alloced_file, ae->ae_alloced_line); |
| 377 | +} |
| 378 | + |
| 379 | +void * |
| 380 | +internal_wmalloc(size, file, line) |
| 381 | + size_t size; |
| 382 | + const char *file; |
| 383 | + int line; |
| 384 | +{ |
| 385 | + void *p; |
| 386 | +struct alloc_entry *ae; |
| 387 | + size_t mapsize; |
| 388 | + |
| 389 | + if (pgsize == 0) |
| 390 | + pgsize = sysconf(_SC_PAGESIZE); |
| 391 | + |
| 392 | + mapsize = (size/pgsize + 2) * pgsize; |
| 393 | + if ((p = mmap(NULL, mapsize, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0)) == (void *)-1) { |
| 394 | + (void)fprintf(stderr, "mmap: %s\n", strerror(errno)); |
| 395 | + return NULL; |
| 396 | + } |
| 397 | + |
| 398 | + for (ae = &allocs; ae->ae_next; ae = ae->ae_next) |
| 399 | + if (ae->ae_next->ae_mapping == p) |
| 400 | + break; |
| 401 | + |
| 402 | + if (!ae->ae_next) { |
| 403 | + if ((ae->ae_next = malloc(sizeof(struct alloc_entry))) == NULL) { |
| 404 | + (void)fputs("out of memory\n", stderr); |
| 405 | + abort(); |
| 406 | + } |
| 407 | + bzero(ae->ae_next, sizeof(struct alloc_entry)); |
| 408 | + } |
| 409 | + |
| 410 | + ae = ae->ae_next; |
| 411 | + ae->ae_addr = ((char *)p + (mapsize - pgsize)) - size; |
| 412 | + ae->ae_mapping = p; |
| 413 | + ae->ae_mapsize = mapsize; |
| 414 | + ae->ae_size = size; |
| 415 | + ae->ae_freed = 0; |
| 416 | + ae->ae_alloced_file = file; |
| 417 | + ae->ae_alloced_line = line; |
| 418 | +#if 0 |
| 419 | + (void)fprintf(stderr, "alloc %d @ %p [map @ %p:%p, size %d] at %s:%d\n", size, ae->ae_addr, |
| 420 | + ae->ae_mapping, ae->ae_mapping + ae->ae_mapsize, ae->ae_mapsize, file, line); |
| 421 | +#endif |
| 422 | + if (mprotect(ae->ae_addr + size, pgsize, PROT_NONE) < 0) { |
| 423 | + (void)fprintf(stderr, "mprotect(0x%p, %d, PROT_NONE): %s\n", ae->ae_addr + size, pgsize, strerror(errno)); |
| 424 | + exit(8); |
| 425 | + } |
| 426 | + |
| 427 | + return ae->ae_addr; |
| 428 | +} |
| 429 | + |
| 430 | +void |
| 431 | +internal_wfree(p, file, line) |
| 432 | + void *p; |
| 433 | + const char *file; |
| 434 | + int line; |
| 435 | +{ |
| 436 | +struct alloc_entry *ae; |
| 437 | + |
| 438 | +#if 0 |
| 439 | + (void)fprintf(stderr, "free %p @ %s:%d\n", p, file, line); |
| 440 | +#endif |
| 441 | + if (!p) |
| 442 | + return; |
| 443 | + |
| 444 | + for (ae = allocs.ae_next; ae; ae = ae->ae_next) { |
| 445 | + if (ae->ae_addr == p) { |
| 446 | + if (ae->ae_freed) { |
| 447 | + (void)fprintf(stderr, "wfree: ptr %p already freed @ %s:%d! [alloced at %s:%d]\n", |
| 448 | + p, ae->ae_freed_file, ae->ae_freed_line, |
| 449 | + ae->ae_alloced_file, ae->ae_alloced_line); |
| 450 | + ae_checkleaks(); |
| 451 | + abort(); |
| 452 | + } |
| 453 | + ae->ae_freed = 1; |
| 454 | + ae->ae_freed_file = file; |
| 455 | + ae->ae_freed_line = line; |
| 456 | + if (mprotect(ae->ae_addr + ae->ae_size, pgsize, PROT_READ | PROT_WRITE) < 0) { |
| 457 | + (void)fprintf(stderr, "mprotect(0x%p, %d, PROT_READ | PROT_WRITE): %s\n", |
| 458 | + ae->ae_addr + ae->ae_size, pgsize, strerror(errno)); |
| 459 | + exit(8); |
| 460 | + } |
| 461 | + munmap(ae->ae_mapping, ae->ae_mapsize); |
| 462 | + return; |
| 463 | + } |
| 464 | + } |
| 465 | + |
| 466 | + (void)fprintf(stderr, "wfree: ptr %p never malloced! [%s:%d]\n", p, file, line); |
| 467 | + ae_checkleaks(); |
| 468 | + abort(); |
| 469 | +} |
| 470 | + |
| 471 | +char * |
| 472 | +internal_wstrdup(s, file, line) |
| 473 | + const char *s, *file; |
| 474 | + int line; |
| 475 | +{ |
| 476 | + char *ret = internal_wmalloc(strlen(s) + 1, file, line); |
| 477 | + (void)strcpy(ret, s); |
| 478 | + return ret; |
| 479 | +} |
| 480 | + |
| 481 | +void * |
| 482 | +internal_wrealloc(p, size, file, line) |
| 483 | + void *p; |
| 484 | + const char *file; |
| 485 | + int line; |
| 486 | + size_t size; |
| 487 | +{ |
| 488 | + void *new; |
| 489 | +struct alloc_entry *ae; |
| 490 | + size_t osize = 0; |
| 491 | + |
| 492 | + if (!p) |
| 493 | + return internal_wmalloc(size, file, line); |
| 494 | + |
| 495 | + for (ae = allocs.ae_next; ae; ae = ae->ae_next) |
| 496 | + if (ae->ae_addr == p) { |
| 497 | + osize = ae->ae_size; |
| 498 | + break; |
| 499 | + } |
| 500 | + |
| 501 | + if (osize == 0) { |
| 502 | + (void)fprintf(stderr, "wrealloc: ptr %p never malloced!\n", p); |
| 503 | + ae_checkleaks(); |
| 504 | + abort(); |
| 505 | + } |
| 506 | + |
| 507 | + new = internal_wmalloc(size, file, line); |
| 508 | + bcopy(p, new, min(osize, size)); |
| 509 | + internal_wfree(p, file, line); |
| 510 | + |
| 511 | + return new; |
| 512 | +} |
| 513 | + |
| 514 | +void * |
| 515 | +internal_wcalloc(num, size, file, line) |
| 516 | + size_t num, size; |
| 517 | + const char *file; |
| 518 | + int line; |
| 519 | +{ |
| 520 | + size_t t = size * num; |
| 521 | + void *p; |
| 522 | + |
| 523 | + if ((p = internal_wmalloc(t, __FILE__, __LINE__)) == NULL) |
| 524 | + return NULL; |
| 525 | + bzero(p, t); |
| 526 | + return p; |
| 527 | +} |
| 528 | + |
| 529 | +#endif |