1 |
/* thttpd.c - tiny/turbo/throttling HTTP server |
2 |
** |
3 |
** Copyright © 1995,1998,1999,2000,2001 by Jef Poskanzer <jef@acme.com>. |
4 |
** All rights reserved. |
5 |
** |
6 |
** Redistribution and use in source and binary forms, with or without |
7 |
** modification, are permitted provided that the following conditions |
8 |
** are met: |
9 |
** 1. Redistributions of source code must retain the above copyright |
10 |
** notice, this list of conditions and the following disclaimer. |
11 |
** 2. Redistributions in binary form must reproduce the above copyright |
12 |
** notice, this list of conditions and the following disclaimer in the |
13 |
** documentation and/or other materials provided with the distribution. |
14 |
** |
15 |
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
16 |
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
17 |
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
18 |
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
19 |
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
20 |
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
21 |
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
22 |
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
23 |
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
24 |
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
25 |
** SUCH DAMAGE. |
26 |
*/ |
27 |
|
28 |
|
29 |
#include "config.h" |
30 |
#include "version.h" |
31 |
|
32 |
#include <sys/param.h> |
33 |
#include <sys/types.h> |
34 |
#include <sys/time.h> |
35 |
#include <sys/stat.h> |
36 |
#include <sys/uio.h> |
37 |
|
38 |
#include <errno.h> |
39 |
#ifdef HAVE_FCNTL_H |
40 |
#include <fcntl.h> |
41 |
#endif |
42 |
#include <pwd.h> |
43 |
#ifdef HAVE_GRP_H |
44 |
#include <grp.h> |
45 |
#endif |
46 |
#include <signal.h> |
47 |
#include <stdio.h> |
48 |
#include <stdlib.h> |
49 |
#include <string.h> |
50 |
#include <syslog.h> |
51 |
#ifdef TIME_WITH_SYS_TIME |
52 |
#include <time.h> |
53 |
#endif |
54 |
#include <unistd.h> |
55 |
|
56 |
#include "fdwatch.h" |
57 |
#include "libhttpd.h" |
58 |
#include "mmc.h" |
59 |
#include "timers.h" |
60 |
#include "match.h" |
61 |
|
62 |
|
63 |
static char* argv0; |
64 |
static int debug; |
65 |
static int port; |
66 |
static char* dir; |
67 |
static int do_chroot, no_log, no_symlink, do_vhost, do_global_passwd; |
68 |
static char* cgi_pattern; |
69 |
static char* url_pattern; |
70 |
static int no_empty_referers; |
71 |
static char* local_pattern; |
72 |
static char* logfile; |
73 |
static char* throttlefile; |
74 |
static char* hostname; |
75 |
static char* pidfile; |
76 |
static char* user; |
77 |
static char* charset; |
78 |
|
79 |
|
80 |
typedef struct { |
81 |
char* pattern; |
82 |
long limit; |
83 |
long rate; |
84 |
off_t bytes_since_avg; |
85 |
int num_sending; |
86 |
} throttletab; |
87 |
static throttletab* throttles; |
88 |
static int numthrottles, maxthrottles; |
89 |
|
90 |
#define THROTTLE_NOLIMIT 1234567890L /* default limit (if none) */ |
91 |
|
92 |
|
93 |
typedef struct { |
94 |
int conn_state; |
95 |
httpd_conn* hc; |
96 |
int tnums[MAXTHROTTLENUMS]; /* throttle indexes */ |
97 |
int numtnums; |
98 |
long limit; |
99 |
time_t started_at; |
100 |
Timer* idle_read_timer; |
101 |
Timer* idle_send_timer; |
102 |
Timer* wakeup_timer; |
103 |
Timer* linger_timer; |
104 |
long wouldblock_delay; |
105 |
off_t bytes; |
106 |
off_t bytes_sent; |
107 |
off_t bytes_to_send; |
108 |
} connecttab; |
109 |
static connecttab* connects; |
110 |
static int numconnects, maxconnects; |
111 |
static int httpd_conn_count; |
112 |
|
113 |
/* The connection states. */ |
114 |
#define CNST_FREE 0 |
115 |
#define CNST_READING 1 |
116 |
#define CNST_SENDING 2 |
117 |
#define CNST_PAUSING 3 |
118 |
#define CNST_LINGERING 4 |
119 |
|
120 |
|
121 |
static httpd_server* hs = (httpd_server*) 0; |
122 |
int terminate = 0; |
123 |
time_t start_time, stats_time; |
124 |
long stats_connections, stats_bytes; |
125 |
int stats_simultaneous; |
126 |
|
127 |
static int got_usr1; |
128 |
|
129 |
|
130 |
/* Forwards. */ |
131 |
static void parse_args( int argc, char** argv ); |
132 |
static void usage( void ); |
133 |
static void read_config( char* filename ); |
134 |
static void value_required( char* name, char* value ); |
135 |
static void no_value_required( char* name, char* value ); |
136 |
static char* e_strdup( char* oldstr ); |
137 |
static void lookup_hostname( httpd_sockaddr* sa4P, size_t sa4_len, int* gotv4P, httpd_sockaddr* sa6P, size_t sa6_len, int* gotv6P ); |
138 |
static void read_throttlefile( char* throttlefile ); |
139 |
static void shut_down( void ); |
140 |
static int handle_newconnect( struct timeval* tvP, int listen_fd ); |
141 |
static void handle_read( connecttab* c, struct timeval* tvP ); |
142 |
static void handle_send( connecttab* c, struct timeval* tvP ); |
143 |
static void handle_linger( connecttab* c, struct timeval* tvP ); |
144 |
static int check_throttles( connecttab* c ); |
145 |
static void clear_throttles( connecttab* c, struct timeval* tvP ); |
146 |
static void update_throttles( ClientData client_data, struct timeval* nowP ); |
147 |
static void clear_connection( connecttab* c, struct timeval* tvP ); |
148 |
static void really_clear_connection( connecttab* c, struct timeval* tvP ); |
149 |
static void idle_read_connection( ClientData client_data, struct timeval* nowP ); |
150 |
static void idle_send_connection( ClientData client_data, struct timeval* nowP ); |
151 |
static void wakeup_connection( ClientData client_data, struct timeval* nowP ); |
152 |
static void linger_clear_connection( ClientData client_data, struct timeval* nowP ); |
153 |
static void occasional( ClientData client_data, struct timeval* nowP ); |
154 |
#ifdef STATS_TIME |
155 |
static void show_stats( ClientData client_data, struct timeval* nowP ); |
156 |
#endif /* STATS_TIME */ |
157 |
static void logstats( struct timeval* nowP ); |
158 |
static void thttpd_logstats( long secs ); |
159 |
|
160 |
|
161 |
static void |
162 |
handle_term( int sig ) |
163 |
{ |
164 |
shut_down(); |
165 |
syslog( LOG_NOTICE, "exiting due to signal %d", sig ); |
166 |
closelog(); |
167 |
exit( 1 ); |
168 |
} |
169 |
|
170 |
|
171 |
static void |
172 |
handle_hup( int sig ) |
173 |
{ |
174 |
FILE* logfp; |
175 |
|
176 |
if ( no_log ) |
177 |
return; |
178 |
|
179 |
/* Re-open the log file. */ |
180 |
if ( logfile != (char*) 0 ) |
181 |
{ |
182 |
logfp = fopen( logfile, "a" ); |
183 |
if ( logfp == (FILE*) 0 ) |
184 |
{ |
185 |
syslog( LOG_CRIT, "reopening %.80s - %m", logfile ); |
186 |
return; |
187 |
} |
188 |
(void) fcntl( fileno( logfp ), F_SETFD, 1 ); |
189 |
httpd_set_logfp( hs, logfp ); |
190 |
} |
191 |
} |
192 |
|
193 |
|
194 |
static void |
195 |
handle_usr1( int sig ) |
196 |
{ |
197 |
got_usr1 = 1; |
198 |
} |
199 |
|
200 |
|
201 |
static void |
202 |
handle_usr2( int sig ) |
203 |
{ |
204 |
logstats( (struct timeval*) 0 ); |
205 |
} |
206 |
|
207 |
|
208 |
int |
209 |
main( int argc, char** argv ) |
210 |
{ |
211 |
char* cp; |
212 |
struct passwd* pwd; |
213 |
uid_t uid; |
214 |
gid_t gid; |
215 |
char cwd[MAXPATHLEN]; |
216 |
FILE* logfp; |
217 |
int num_ready; |
218 |
int cnum, ridx; |
219 |
connecttab* c; |
220 |
httpd_conn* hc; |
221 |
httpd_sockaddr sa4; |
222 |
httpd_sockaddr sa6; |
223 |
int gotv4, gotv6; |
224 |
struct timeval tv; |
225 |
|
226 |
argv0 = argv[0]; |
227 |
|
228 |
cp = strrchr( argv0, '/' ); |
229 |
if ( cp != (char*) 0 ) |
230 |
++cp; |
231 |
else |
232 |
cp = argv0; |
233 |
openlog( cp, LOG_NDELAY|LOG_PID, LOG_FACILITY ); |
234 |
|
235 |
/* Handle command-line arguments. */ |
236 |
parse_args( argc, argv ); |
237 |
|
238 |
/* Check port number. */ |
239 |
if ( port <= 0 ) |
240 |
{ |
241 |
syslog( LOG_CRIT, "illegal port number" ); |
242 |
(void) fprintf( stderr, "%s: illegal port number\n", argv0 ); |
243 |
exit( 1 ); |
244 |
} |
245 |
|
246 |
/* Read zone info now, in case we chroot(). */ |
247 |
tzset(); |
248 |
|
249 |
/* Look up hostname now, in case we chroot(). */ |
250 |
lookup_hostname( &sa4, sizeof(sa4), &gotv4, &sa6, sizeof(sa6), &gotv6 ); |
251 |
if ( ! ( gotv4 || gotv6 ) ) |
252 |
{ |
253 |
syslog( LOG_ERR, "can't find any valid address" ); |
254 |
(void) fprintf( stderr, "%s: can't find any valid address\n", argv0 ); |
255 |
exit( 1 ); |
256 |
} |
257 |
|
258 |
/* Throttle file. */ |
259 |
numthrottles = 0; |
260 |
maxthrottles = 0; |
261 |
throttles = (throttletab*) 0; |
262 |
if ( throttlefile != (char*) 0 ) |
263 |
read_throttlefile( throttlefile ); |
264 |
|
265 |
/* Log file. */ |
266 |
if ( logfile != (char*) 0 ) |
267 |
{ |
268 |
if ( strcmp( logfile, "/dev/null" ) == 0 ) |
269 |
{ |
270 |
no_log = 1; |
271 |
logfp = (FILE*) 0; |
272 |
} |
273 |
else |
274 |
{ |
275 |
logfp = fopen( logfile, "a" ); |
276 |
if ( logfp == (FILE*) 0 ) |
277 |
{ |
278 |
syslog( LOG_CRIT, "%.80s - %m", logfile ); |
279 |
perror( logfile ); |
280 |
exit( 1 ); |
281 |
} |
282 |
(void) fcntl( fileno( logfp ), F_SETFD, 1 ); |
283 |
} |
284 |
} |
285 |
else |
286 |
logfp = (FILE*) 0; |
287 |
|
288 |
/* Figure out uid/gid from user. */ |
289 |
pwd = getpwnam( user ); |
290 |
if ( pwd == (struct passwd*) 0 ) |
291 |
{ |
292 |
syslog( LOG_CRIT, "unknown user - '%.80s'", user ); |
293 |
(void) fprintf( stderr, "%s: unknown user - '%s'\n", argv0, user ); |
294 |
exit( 1 ); |
295 |
} |
296 |
uid = pwd->pw_uid; |
297 |
gid = pwd->pw_gid; |
298 |
|
299 |
/* Switch directories if requested. */ |
300 |
if ( dir != (char*) 0 ) |
301 |
{ |
302 |
if ( chdir( dir ) < 0 ) |
303 |
{ |
304 |
syslog( LOG_CRIT, "chdir - %m" ); |
305 |
perror( "chdir" ); |
306 |
exit( 1 ); |
307 |
} |
308 |
} |
309 |
#ifdef USE_USER_DIR |
310 |
else if ( getuid() == 0 ) |
311 |
{ |
312 |
/* No explicit directory was specified, we're root, and the |
313 |
** USE_USER_DIR option is set - switch to the specified user's |
314 |
** home dir. |
315 |
*/ |
316 |
if ( chdir( pwd->pw_dir ) < 0 ) |
317 |
{ |
318 |
syslog( LOG_CRIT, "chdir - %m" ); |
319 |
perror( "chdir" ); |
320 |
exit( 1 ); |
321 |
} |
322 |
} |
323 |
#endif /* USE_USER_DIR */ |
324 |
|
325 |
/* Get current directory. */ |
326 |
(void) getcwd( cwd, sizeof(cwd) - 1 ); |
327 |
if ( cwd[strlen( cwd ) - 1] != '/' ) |
328 |
(void) strcat( cwd, "/" ); |
329 |
|
330 |
if ( ! debug ) |
331 |
{ |
332 |
/* We're not going to use stdin stdout or stderr from here on, so close |
333 |
** them to save file descriptors. |
334 |
*/ |
335 |
(void) fclose( stdin ); |
336 |
(void) fclose( stdout ); |
337 |
(void) fclose( stderr ); |
338 |
|
339 |
/* Daemonize - make ourselves a subprocess. */ |
340 |
#ifdef HAVE_DAEMON |
341 |
if ( daemon( 1, 1 ) < 0 ) |
342 |
{ |
343 |
syslog( LOG_CRIT, "daemon - %m" ); |
344 |
exit( 1 ); |
345 |
} |
346 |
#else /* HAVE_DAEMON */ |
347 |
switch ( fork() ) |
348 |
{ |
349 |
case 0: |
350 |
break; |
351 |
case -1: |
352 |
syslog( LOG_CRIT, "fork - %m" ); |
353 |
exit( 1 ); |
354 |
default: |
355 |
exit( 0 ); |
356 |
} |
357 |
#ifdef HAVE_SETSID |
358 |
(void) setsid(); |
359 |
#endif /* HAVE_SETSID */ |
360 |
#endif /* HAVE_DAEMON */ |
361 |
} |
362 |
else |
363 |
{ |
364 |
/* Even if we don't daemonize, we still want to disown our parent |
365 |
** process. |
366 |
*/ |
367 |
#ifdef HAVE_SETSID |
368 |
(void) setsid(); |
369 |
#endif /* HAVE_SETSID */ |
370 |
} |
371 |
|
372 |
if ( pidfile != (char*) 0 ) |
373 |
{ |
374 |
/* Write the PID file. */ |
375 |
FILE* pidfp = fopen( pidfile, "w" ); |
376 |
if ( pidfp == (FILE*) 0 ) |
377 |
{ |
378 |
syslog( LOG_CRIT, "%.80s - %m", pidfile ); |
379 |
exit( 1 ); |
380 |
} |
381 |
(void) fprintf( pidfp, "%d\n", (int) getpid() ); |
382 |
(void) fclose( pidfp ); |
383 |
} |
384 |
|
385 |
/* Chroot if requested. */ |
386 |
if ( do_chroot ) |
387 |
{ |
388 |
if ( chroot( cwd ) < 0 ) |
389 |
{ |
390 |
syslog( LOG_CRIT, "chroot - %m" ); |
391 |
perror( "chroot" ); |
392 |
exit( 1 ); |
393 |
} |
394 |
(void) strcpy( cwd, "/" ); |
395 |
/* Always chdir to / after a chroot. */ |
396 |
if ( chdir( cwd ) < 0 ) |
397 |
{ |
398 |
syslog( LOG_CRIT, "chroot chdir - %m" ); |
399 |
perror( "chroot chdir" ); |
400 |
exit( 1 ); |
401 |
} |
402 |
} |
403 |
|
404 |
/* Set up to catch signals. */ |
405 |
(void) signal( SIGTERM, handle_term ); |
406 |
(void) signal( SIGINT, handle_term ); |
407 |
(void) signal( SIGPIPE, SIG_IGN ); /* get EPIPE instead */ |
408 |
(void) signal( SIGHUP, handle_hup ); |
409 |
got_usr1 = 0; |
410 |
(void) signal( SIGUSR1, handle_usr1 ); |
411 |
(void) signal( SIGUSR2, handle_usr2 ); |
412 |
|
413 |
/* Initialize the timer package. */ |
414 |
tmr_init(); |
415 |
|
416 |
/* Initialize the HTTP layer. Got to do this before giving up root, |
417 |
** so that we can bind to a privileged port. |
418 |
*/ |
419 |
hs = httpd_initialize( |
420 |
hostname, |
421 |
gotv4 ? &sa4 : (httpd_sockaddr*) 0, gotv6 ? &sa6 : (httpd_sockaddr*) 0, |
422 |
port, cgi_pattern, charset, cwd, no_log, logfp, no_symlink, do_vhost, |
423 |
do_global_passwd, url_pattern, local_pattern, no_empty_referers ); |
424 |
if ( hs == (httpd_server*) 0 ) |
425 |
exit( 1 ); |
426 |
|
427 |
/* Set up the occasional timer. */ |
428 |
if ( tmr_create( (struct timeval*) 0, occasional, JunkClientData, OCCASIONAL_TIME * 1000L, 1 ) == (Timer*) 0 ) |
429 |
{ |
430 |
syslog( LOG_CRIT, "tmr_create(occasional) failed" ); |
431 |
exit( 1 ); |
432 |
} |
433 |
if ( numthrottles > 0 ) |
434 |
{ |
435 |
/* Set up the throttles timer. */ |
436 |
if ( tmr_create( (struct timeval*) 0, update_throttles, JunkClientData, THROTTLE_TIME * 1000L, 1 ) == (Timer*) 0 ) |
437 |
{ |
438 |
syslog( LOG_CRIT, "tmr_create(update_throttles) failed" ); |
439 |
exit( 1 ); |
440 |
} |
441 |
} |
442 |
#ifdef STATS_TIME |
443 |
/* Set up the stats timer. */ |
444 |
if ( tmr_create( (struct timeval*) 0, show_stats, JunkClientData, STATS_TIME * 1000L, 1 ) == (Timer*) 0 ) |
445 |
{ |
446 |
syslog( LOG_CRIT, "tmr_create(show_stats) failed" ); |
447 |
exit( 1 ); |
448 |
} |
449 |
#endif /* STATS_TIME */ |
450 |
start_time = stats_time = time( (time_t*) 0 ); |
451 |
stats_connections = stats_bytes = 0L; |
452 |
stats_simultaneous = 0; |
453 |
|
454 |
/* If we're root, try to become someone else. */ |
455 |
if ( getuid() == 0 ) |
456 |
{ |
457 |
/* Set aux groups to null. */ |
458 |
if ( setgroups( 0, (const gid_t*) 0 ) < 0 ) |
459 |
{ |
460 |
syslog( LOG_CRIT, "setgroups - %m" ); |
461 |
exit( 1 ); |
462 |
} |
463 |
/* Set primary group. */ |
464 |
if ( setgid( gid ) < 0 ) |
465 |
{ |
466 |
syslog( LOG_CRIT, "setgid - %m" ); |
467 |
exit( 1 ); |
468 |
} |
469 |
/* Try setting aux groups correctly - not critical if this fails. */ |
470 |
if ( initgroups( user, gid ) < 0 ) |
471 |
syslog( LOG_WARNING, "initgroups - %m" ); |
472 |
#ifdef HAVE_SETLOGIN |
473 |
/* Set login name. */ |
474 |
(void) setlogin( user ); |
475 |
#endif /* HAVE_SETLOGIN */ |
476 |
/* Set uid. */ |
477 |
if ( setuid( uid ) < 0 ) |
478 |
{ |
479 |
syslog( LOG_CRIT, "setuid - %m" ); |
480 |
exit( 1 ); |
481 |
} |
482 |
/* Check for unnecessary security exposure. */ |
483 |
if ( ! do_chroot ) |
484 |
syslog( |
485 |
LOG_CRIT, |
486 |
"started as root without requesting chroot(), warning only" ); |
487 |
} |
488 |
|
489 |
/* Initialize our connections table. */ |
490 |
maxconnects = fdwatch_get_nfiles(); |
491 |
if ( maxconnects < 0 ) |
492 |
{ |
493 |
syslog( LOG_CRIT, "fdwatch initialization failure" ); |
494 |
exit( 1 ); |
495 |
} |
496 |
maxconnects -= SPARE_FDS; |
497 |
#ifdef MMAP_MAX |
498 |
maxconnects = maxconnects / 2; |
499 |
#endif |
500 |
connects = NEW( connecttab, maxconnects ); |
501 |
if ( connects == (connecttab*) 0 ) |
502 |
{ |
503 |
syslog( LOG_CRIT, "out of memory allocating a connecttab" ); |
504 |
exit( 1 ); |
505 |
} |
506 |
for ( cnum = 0; cnum < maxconnects; ++cnum ) |
507 |
{ |
508 |
connects[cnum].conn_state = CNST_FREE; |
509 |
connects[cnum].hc = (httpd_conn*) 0; |
510 |
} |
511 |
numconnects = 0; |
512 |
httpd_conn_count = 0; |
513 |
|
514 |
if ( hs != (httpd_server*) 0 ) |
515 |
{ |
516 |
if ( hs->listen4_fd != -1 ) |
517 |
fdwatch_add_fd( hs->listen4_fd, (void*) 0, FDW_READ ); |
518 |
if ( hs->listen6_fd != -1 ) |
519 |
fdwatch_add_fd( hs->listen6_fd, (void*) 0, FDW_READ ); |
520 |
} |
521 |
|
522 |
/* Main loop. */ |
523 |
(void) gettimeofday( &tv, (struct timezone*) 0 ); |
524 |
while ( ( ! terminate ) || numconnects > 0 ) |
525 |
{ |
526 |
/* Do the fd watch. */ |
527 |
num_ready = fdwatch( tmr_mstimeout( &tv ) ); |
528 |
if ( num_ready < 0 ) |
529 |
{ |
530 |
if ( errno == EINTR ) |
531 |
continue; /* try again */ |
532 |
syslog( LOG_ERR, "fdwatch - %m" ); |
533 |
exit( 1 ); |
534 |
} |
535 |
(void) gettimeofday( &tv, (struct timezone*) 0 ); |
536 |
if ( num_ready == 0 ) |
537 |
{ |
538 |
/* No fd's are ready - run the timers. */ |
539 |
tmr_run( &tv ); |
540 |
continue; |
541 |
} |
542 |
|
543 |
/* Is it a new connection? */ |
544 |
if ( hs != (httpd_server*) 0 && hs->listen6_fd != -1 && |
545 |
fdwatch_check_fd( hs->listen6_fd ) ) |
546 |
{ |
547 |
if ( handle_newconnect( &tv, hs->listen6_fd ) ) |
548 |
/* Go around the loop and do another fdwatch, rather than |
549 |
** dropping through and processing existing connections. |
550 |
** New connections always get priority. |
551 |
*/ |
552 |
continue; |
553 |
} |
554 |
if ( hs != (httpd_server*) 0 && hs->listen4_fd != -1 && |
555 |
fdwatch_check_fd( hs->listen4_fd ) ) |
556 |
{ |
557 |
if ( handle_newconnect( &tv, hs->listen4_fd ) ) |
558 |
/* Go around the loop and do another fdwatch, rather than |
559 |
** dropping through and processing existing connections. |
560 |
** New connections always get priority. |
561 |
*/ |
562 |
continue; |
563 |
} |
564 |
|
565 |
/* Find the connections that need servicing. */ |
566 |
for ( ridx = 0; ridx < num_ready; ++ridx ) |
567 |
{ |
568 |
c = (connecttab*) fdwatch_get_client_data( ridx ); |
569 |
if ( c == (connecttab*) 0 ) |
570 |
continue; |
571 |
hc = c->hc; |
572 |
if ( c->conn_state == CNST_READING && |
573 |
fdwatch_check_fd( hc->conn_fd ) ) |
574 |
handle_read( c, &tv ); |
575 |
else if ( c->conn_state == CNST_SENDING && |
576 |
fdwatch_check_fd( hc->conn_fd ) ) |
577 |
handle_send( c, &tv ); |
578 |
else if ( c->conn_state == CNST_LINGERING && |
579 |
fdwatch_check_fd( hc->conn_fd ) ) |
580 |
handle_linger( c, &tv ); |
581 |
} |
582 |
tmr_run( &tv ); |
583 |
|
584 |
if ( got_usr1 && ! terminate ) |
585 |
{ |
586 |
terminate = 1; |
587 |
if ( hs != (httpd_server*) 0 ) |
588 |
{ |
589 |
httpd_terminate( hs ); |
590 |
hs = (httpd_server*) 0; |
591 |
} |
592 |
} |
593 |
} |
594 |
|
595 |
/* The main loop terminated. */ |
596 |
shut_down(); |
597 |
syslog( LOG_NOTICE, "exiting" ); |
598 |
closelog(); |
599 |
exit( 0 ); |
600 |
} |
601 |
|
602 |
|
603 |
static void |
604 |
parse_args( int argc, char** argv ) |
605 |
{ |
606 |
int argn; |
607 |
|
608 |
debug = 0; |
609 |
port = DEFAULT_PORT; |
610 |
dir = (char*) 0; |
611 |
#ifdef ALWAYS_CHROOT |
612 |
do_chroot = 1; |
613 |
#else /* ALWAYS_CHROOT */ |
614 |
do_chroot = 0; |
615 |
#endif /* ALWAYS_CHROOT */ |
616 |
no_log = 0; |
617 |
no_symlink = do_chroot; |
618 |
#ifdef ALWAYS_VHOST |
619 |
do_vhost = 1; |
620 |
#else /* ALWAYS_VHOST */ |
621 |
do_vhost = 0; |
622 |
#endif /* ALWAYS_VHOST */ |
623 |
#ifdef ALWAYS_GLOBAL_PASSWD |
624 |
do_global_passwd = 1; |
625 |
#else /* ALWAYS_GLOBAL_PASSWD */ |
626 |
do_global_passwd = 0; |
627 |
#endif /* ALWAYS_GLOBAL_PASSWD */ |
628 |
#ifdef CGI_PATTERN |
629 |
cgi_pattern = CGI_PATTERN; |
630 |
#else /* CGI_PATTERN */ |
631 |
cgi_pattern = (char*) 0; |
632 |
#endif /* CGI_PATTERN */ |
633 |
url_pattern = (char*) 0; |
634 |
no_empty_referers = 0; |
635 |
local_pattern = (char*) 0; |
636 |
throttlefile = (char*) 0; |
637 |
hostname = (char*) 0; |
638 |
logfile = (char*) 0; |
639 |
pidfile = (char*) 0; |
640 |
user = DEFAULT_USER; |
641 |
charset = DEFAULT_CHARSET; |
642 |
argn = 1; |
643 |
while ( argn < argc && argv[argn][0] == '-' ) |
644 |
{ |
645 |
if ( strcmp( argv[argn], "-C" ) == 0 && argn + 1 < argc ) |
646 |
{ |
647 |
++argn; |
648 |
read_config( argv[argn] ); |
649 |
} |
650 |
else if ( strcmp( argv[argn], "-p" ) == 0 && argn + 1 < argc ) |
651 |
{ |
652 |
++argn; |
653 |
port = atoi( argv[argn] ); |
654 |
} |
655 |
else if ( strcmp( argv[argn], "-d" ) == 0 && argn + 1 < argc ) |
656 |
{ |
657 |
++argn; |
658 |
dir = argv[argn]; |
659 |
} |
660 |
else if ( strcmp( argv[argn], "-r" ) == 0 ) |
661 |
{ |
662 |
do_chroot = 1; |
663 |
no_symlink = 1; |
664 |
} |
665 |
else if ( strcmp( argv[argn], "-nor" ) == 0 ) |
666 |
{ |
667 |
do_chroot = 0; |
668 |
no_symlink = 0; |
669 |
} |
670 |
else if ( strcmp( argv[argn], "-s" ) == 0 ) |
671 |
no_symlink = 0; |
672 |
else if ( strcmp( argv[argn], "-nos" ) == 0 ) |
673 |
no_symlink = 1; |
674 |
else if ( strcmp( argv[argn], "-u" ) == 0 && argn + 1 < argc ) |
675 |
{ |
676 |
++argn; |
677 |
user = argv[argn]; |
678 |
} |
679 |
else if ( strcmp( argv[argn], "-c" ) == 0 && argn + 1 < argc ) |
680 |
{ |
681 |
++argn; |
682 |
cgi_pattern = argv[argn]; |
683 |
} |
684 |
else if ( strcmp( argv[argn], "-t" ) == 0 && argn + 1 < argc ) |
685 |
{ |
686 |
++argn; |
687 |
throttlefile = argv[argn]; |
688 |
} |
689 |
else if ( strcmp( argv[argn], "-h" ) == 0 && argn + 1 < argc ) |
690 |
{ |
691 |
++argn; |
692 |
hostname = argv[argn]; |
693 |
} |
694 |
else if ( strcmp( argv[argn], "-l" ) == 0 && argn + 1 < argc ) |
695 |
{ |
696 |
++argn; |
697 |
logfile = argv[argn]; |
698 |
} |
699 |
else if ( strcmp( argv[argn], "-v" ) == 0 ) |
700 |
do_vhost = 1; |
701 |
else if ( strcmp( argv[argn], "-nov" ) == 0 ) |
702 |
do_vhost = 0; |
703 |
else if ( strcmp( argv[argn], "-g" ) == 0 ) |
704 |
do_global_passwd = 1; |
705 |
else if ( strcmp( argv[argn], "-nog" ) == 0 ) |
706 |
do_global_passwd = 0; |
707 |
else if ( strcmp( argv[argn], "-i" ) == 0 && argn + 1 < argc ) |
708 |
{ |
709 |
++argn; |
710 |
pidfile = argv[argn]; |
711 |
} |
712 |
else if ( strcmp( argv[argn], "-T" ) == 0 && argn + 1 < argc ) |
713 |
{ |
714 |
++argn; |
715 |
charset = argv[argn]; |
716 |
} |
717 |
else if ( strcmp( argv[argn], "-V" ) == 0 ) |
718 |
{ |
719 |
(void) fprintf( stderr, "%s\n", SERVER_SOFTWARE ); |
720 |
exit( 0 ); |
721 |
} |
722 |
else if ( strcmp( argv[argn], "-D" ) == 0 ) |
723 |
debug = 1; |
724 |
else |
725 |
usage(); |
726 |
++argn; |
727 |
} |
728 |
if ( argn != argc ) |
729 |
usage(); |
730 |
} |
731 |
|
732 |
|
733 |
static void |
734 |
usage( void ) |
735 |
{ |
736 |
(void) fprintf( stderr, |
737 |
"usage: %s [-C configfile] [-p port] [-d dir] [-r|-nor] [-v|-nov] [-g|-nog] [-u user] [-c cgipat] [-t throttles] [-h host] [-l logfile] [-i pidfile] [-T charset] [-V] [-D]\n", |
738 |
argv0 ); |
739 |
exit( 1 ); |
740 |
} |
741 |
|
742 |
|
743 |
static void |
744 |
read_config( char* filename ) |
745 |
{ |
746 |
FILE* fp; |
747 |
char line[10000]; |
748 |
char* cp; |
749 |
char* cp2; |
750 |
char* name; |
751 |
char* value; |
752 |
|
753 |
fp = fopen( filename, "r" ); |
754 |
if ( fp == (FILE*) 0 ) |
755 |
{ |
756 |
perror( filename ); |
757 |
exit( 1 ); |
758 |
} |
759 |
|
760 |
while ( fgets( line, sizeof(line), fp ) != (char*) 0 ) |
761 |
{ |
762 |
/* Trim comments. */ |
763 |
if ( ( cp = strchr( line, '#' ) ) != (char*) 0 ) |
764 |
*cp = '\0'; |
765 |
|
766 |
/* Split line into words. */ |
767 |
for ( cp = line; *cp != '\0'; cp = cp2 ) |
768 |
{ |
769 |
/* Skip leading whitespace. */ |
770 |
cp += strspn( cp, " \t\n\r" ); |
771 |
/* Find next whitespace. */ |
772 |
cp2 = cp + strcspn( cp, " \t\n\r" ); |
773 |
/* Insert EOS and advance next-word pointer. */ |
774 |
while ( *cp2 == ' ' || *cp2 == '\t' || *cp2 == '\n' || *cp2 == '\r' ) |
775 |
*cp2++ = '\0'; |
776 |
/* Split into name and value. */ |
777 |
name = cp; |
778 |
value = strchr( name, '=' ); |
779 |
if ( value != (char*) 0 ) |
780 |
*value++ = '\0'; |
781 |
/* Interpret. */ |
782 |
if ( strcasecmp( name, "debug" ) == 0 ) |
783 |
{ |
784 |
no_value_required( name, value ); |
785 |
debug = 1; |
786 |
} |
787 |
else if ( strcasecmp( name, "port" ) == 0 ) |
788 |
{ |
789 |
value_required( name, value ); |
790 |
port = atoi( value ); |
791 |
} |
792 |
else if ( strcasecmp( name, "dir" ) == 0 ) |
793 |
{ |
794 |
value_required( name, value ); |
795 |
dir = e_strdup( value ); |
796 |
} |
797 |
else if ( strcasecmp( name, "chroot" ) == 0 ) |
798 |
{ |
799 |
no_value_required( name, value ); |
800 |
do_chroot = 1; |
801 |
no_symlink = 1; |
802 |
} |
803 |
else if ( strcasecmp( name, "nochroot" ) == 0 ) |
804 |
{ |
805 |
no_value_required( name, value ); |
806 |
do_chroot = 0; |
807 |
no_symlink = 0; |
808 |
} |
809 |
else if ( strcasecmp( name, "symlink" ) == 0 ) |
810 |
{ |
811 |
no_value_required( name, value ); |
812 |
no_symlink = 0; |
813 |
} |
814 |
else if ( strcasecmp( name, "nosymlink" ) == 0 ) |
815 |
{ |
816 |
no_value_required( name, value ); |
817 |
no_symlink = 1; |
818 |
} |
819 |
else if ( strcasecmp( name, "symlinks" ) == 0 ) |
820 |
{ |
821 |
no_value_required( name, value ); |
822 |
no_symlink = 0; |
823 |
} |
824 |
else if ( strcasecmp( name, "nosymlinks" ) == 0 ) |
825 |
{ |
826 |
no_value_required( name, value ); |
827 |
no_symlink = 1; |
828 |
} |
829 |
else if ( strcasecmp( name, "user" ) == 0 ) |
830 |
{ |
831 |
value_required( name, value ); |
832 |
user = e_strdup( value ); |
833 |
} |
834 |
else if ( strcasecmp( name, "cgipat" ) == 0 ) |
835 |
{ |
836 |
value_required( name, value ); |
837 |
cgi_pattern = e_strdup( value ); |
838 |
} |
839 |
else if ( strcasecmp( name, "urlpat" ) == 0 ) |
840 |
{ |
841 |
value_required( name, value ); |
842 |
url_pattern = e_strdup( value ); |
843 |
} |
844 |
else if ( strcasecmp( name, "noemptyreferers" ) == 0 ) |
845 |
{ |
846 |
no_value_required( name, value ); |
847 |
no_empty_referers = 1; |
848 |
} |
849 |
else if ( strcasecmp( name, "localpat" ) == 0 ) |
850 |
{ |
851 |
value_required( name, value ); |
852 |
local_pattern = e_strdup( value ); |
853 |
} |
854 |
else if ( strcasecmp( name, "throttles" ) == 0 ) |
855 |
{ |
856 |
value_required( name, value ); |
857 |
throttlefile = e_strdup( value ); |
858 |
} |
859 |
else if ( strcasecmp( name, "host" ) == 0 ) |
860 |
{ |
861 |
value_required( name, value ); |
862 |
hostname = e_strdup( value ); |
863 |
} |
864 |
else if ( strcasecmp( name, "logfile" ) == 0 ) |
865 |
{ |
866 |
value_required( name, value ); |
867 |
logfile = e_strdup( value ); |
868 |
} |
869 |
else if ( strcasecmp( name, "vhost" ) == 0 ) |
870 |
{ |
871 |
no_value_required( name, value ); |
872 |
do_vhost = 1; |
873 |
} |
874 |
else if ( strcasecmp( name, "novhost" ) == 0 ) |
875 |
{ |
876 |
no_value_required( name, value ); |
877 |
do_vhost = 0; |
878 |
} |
879 |
else if ( strcasecmp( name, "globalpasswd" ) == 0 ) |
880 |
{ |
881 |
no_value_required( name, value ); |
882 |
do_global_passwd = 1; |
883 |
} |
884 |
else if ( strcasecmp( name, "noglobalpasswd" ) == 0 ) |
885 |
{ |
886 |
no_value_required( name, value ); |
887 |
do_global_passwd = 0; |
888 |
} |
889 |
else if ( strcasecmp( name, "pidfile" ) == 0 ) |
890 |
{ |
891 |
value_required( name, value ); |
892 |
pidfile = e_strdup( value ); |
893 |
} |
894 |
else if ( strcasecmp( name, "charset" ) == 0 ) |
895 |
{ |
896 |
value_required( name, value ); |
897 |
charset = e_strdup( value ); |
898 |
} |
899 |
else |
900 |
{ |
901 |
(void) fprintf( |
902 |
stderr, "%s: unknown config option '%s'\n", argv0, name ); |
903 |
exit( 1 ); |
904 |
} |
905 |
} |
906 |
} |
907 |
|
908 |
(void) fclose( fp ); |
909 |
} |
910 |
|
911 |
|
912 |
static void |
913 |
value_required( char* name, char* value ) |
914 |
{ |
915 |
if ( value == (char*) 0 ) |
916 |
{ |
917 |
(void) fprintf( |
918 |
stderr, "%s: value required for %s option\n", argv0, name ); |
919 |
exit( 1 ); |
920 |
} |
921 |
} |
922 |
|
923 |
|
924 |
static void |
925 |
no_value_required( char* name, char* value ) |
926 |
{ |
927 |
if ( value != (char*) 0 ) |
928 |
{ |
929 |
(void) fprintf( |
930 |
stderr, "%s: no value required for %s option\n", |
931 |
argv0, name ); |
932 |
exit( 1 ); |
933 |
} |
934 |
} |
935 |
|
936 |
|
937 |
static char* |
938 |
e_strdup( char* oldstr ) |
939 |
{ |
940 |
char* newstr; |
941 |
|
942 |
newstr = strdup( oldstr ); |
943 |
if ( newstr == (char*) 0 ) |
944 |
{ |
945 |
syslog( LOG_CRIT, "out of memory copying a string" ); |
946 |
(void) fprintf( stderr, "%s: out of memory copying a string\n", argv0 ); |
947 |
exit( 1 ); |
948 |
} |
949 |
return newstr; |
950 |
} |
951 |
|
952 |
|
953 |
static void |
954 |
lookup_hostname( httpd_sockaddr* sa4P, size_t sa4_len, int* gotv4P, httpd_sockaddr* sa6P, size_t sa6_len, int* gotv6P ) |
955 |
{ |
956 |
#if defined(HAVE_GETADDRINFO) && defined(HAVE_GAI_STRERROR) |
957 |
|
958 |
struct addrinfo hints; |
959 |
struct addrinfo* ai; |
960 |
struct addrinfo* ai2; |
961 |
struct addrinfo* aiv4; |
962 |
struct addrinfo* aiv6; |
963 |
int gaierr; |
964 |
char strport[10]; |
965 |
|
966 |
memset( &hints, 0, sizeof(hints) ); |
967 |
hints.ai_family = AF_UNSPEC; |
968 |
hints.ai_flags = AI_PASSIVE; |
969 |
hints.ai_socktype = SOCK_STREAM; |
970 |
(void) snprintf( strport, sizeof(strport), "%d", port ); |
971 |
if ( (gaierr = getaddrinfo( hostname, strport, &hints, &ai )) != 0 ) |
972 |
{ |
973 |
syslog( |
974 |
LOG_CRIT, "getaddrinfo %.80s - %.80s", |
975 |
hostname, gai_strerror( gaierr ) ); |
976 |
(void) fprintf( |
977 |
stderr, "%s: getaddrinfo %s - %s\n", |
978 |
argv0, hostname, gai_strerror( gaierr ) ); |
979 |
exit( 1 ); |
980 |
} |
981 |
|
982 |
/* Find the first IPv4 and IPv6 entries. */ |
983 |
aiv4 = (struct addrinfo*) 0; |
984 |
aiv6 = (struct addrinfo*) 0; |
985 |
for ( ai2 = ai; ai2 != (struct addrinfo*) 0; ai2 = ai2->ai_next ) |
986 |
{ |
987 |
switch ( ai2->ai_family ) |
988 |
{ |
989 |
case AF_INET: |
990 |
if ( aiv4 == (struct addrinfo*) 0 ) |
991 |
aiv4 = ai2; |
992 |
break; |
993 |
#if defined(AF_INET6) && defined(HAVE_SOCKADDR_IN6) |
994 |
case AF_INET6: |
995 |
if ( aiv6 == (struct addrinfo*) 0 ) |
996 |
aiv6 = ai2; |
997 |
break; |
998 |
#endif /* AF_INET6 && HAVE_SOCKADDR_IN6 */ |
999 |
} |
1000 |
} |
1001 |
|
1002 |
if ( aiv4 == (struct addrinfo*) 0 ) |
1003 |
*gotv4P = 0; |
1004 |
else |
1005 |
{ |
1006 |
if ( sa4_len < aiv4->ai_addrlen ) |
1007 |
{ |
1008 |
syslog( |
1009 |
LOG_CRIT, "%.80s - sockaddr too small (%d < %d)", |
1010 |
hostname, sa4_len, aiv4->ai_addrlen ); |
1011 |
exit( 1 ); |
1012 |
} |
1013 |
memset( sa4P, 0, sa4_len ); |
1014 |
memcpy( sa4P, aiv4->ai_addr, aiv4->ai_addrlen ); |
1015 |
*gotv4P = 1; |
1016 |
} |
1017 |
if ( aiv6 == (struct addrinfo*) 0 ) |
1018 |
*gotv6P = 0; |
1019 |
else |
1020 |
{ |
1021 |
if ( sa6_len < aiv6->ai_addrlen ) |
1022 |
{ |
1023 |
syslog( |
1024 |
LOG_CRIT, "%.80s - sockaddr too small (%d < %d)", |
1025 |
hostname, sa6_len, aiv6->ai_addrlen ); |
1026 |
exit( 1 ); |
1027 |
} |
1028 |
memset( sa6P, 0, sa6_len ); |
1029 |
memcpy( sa6P, aiv6->ai_addr, aiv6->ai_addrlen ); |
1030 |
*gotv6P = 1; |
1031 |
} |
1032 |
|
1033 |
freeaddrinfo( ai ); |
1034 |
|
1035 |
#else /* HAVE_GETADDRINFO && HAVE_GAI_STRERROR */ |
1036 |
|
1037 |
struct hostent* he; |
1038 |
|
1039 |
*gotv6P = 0; |
1040 |
|
1041 |
memset( sa4P, 0, sa4_len ); |
1042 |
#ifdef notdef |
1043 |
/* We don't really need to set sa_len. */ |
1044 |
#ifdef HAVE_SA_LEN |
1045 |
sa4P->sa_len = sa4_len; |
1046 |
#endif /* HAVE_SA_LEN */ |
1047 |
#endif |
1048 |
sa4P->sa.sa_family = AF_INET; |
1049 |
if ( hostname == (char*) 0 ) |
1050 |
sa4P->sa_in.sin_addr.s_addr = htonl( INADDR_ANY ); |
1051 |
else |
1052 |
{ |
1053 |
sa4P->sa_in.sin_addr.s_addr = inet_addr( hostname ); |
1054 |
if ( (int) sa4P->sa_in.sin_addr.s_addr == -1 ) |
1055 |
{ |
1056 |
he = gethostbyname( hostname ); |
1057 |
if ( he == (struct hostent*) 0 ) |
1058 |
{ |
1059 |
#ifdef HAVE_HSTRERROR |
1060 |
syslog( |
1061 |
LOG_CRIT, "gethostbyname %.80s - %.80s", |
1062 |
hostname, hstrerror( h_errno ) ); |
1063 |
(void) fprintf( |
1064 |
stderr, "%s: gethostbyname %s - %s\n", |
1065 |
argv0, hostname, hstrerror( h_errno ) ); |
1066 |
#else /* HAVE_HSTRERROR */ |
1067 |
syslog( LOG_CRIT, "gethostbyname %.80s failed", hostname ); |
1068 |
(void) fprintf( |
1069 |
stderr, "%s: gethostbyname %s failed\n", argv0, hostname ); |
1070 |
#endif /* HAVE_HSTRERROR */ |
1071 |
exit( 1 ); |
1072 |
} |
1073 |
if ( he->h_addrtype != AF_INET ) |
1074 |
{ |
1075 |
syslog( LOG_CRIT, "%.80s - non-IP network address", hostname ); |
1076 |
(void) fprintf( |
1077 |
stderr, "%s: %s - non-IP network address\n", |
1078 |
argv0, hostname ); |
1079 |
exit( 1 ); |
1080 |
} |
1081 |
(void) memcpy( |
1082 |
&sa4P->sa_in.sin_addr.s_addr, he->h_addr, he->h_length ); |
1083 |
} |
1084 |
} |
1085 |
sa4P->sa_in.sin_port = htons( port ); |
1086 |
*gotv4P = 1; |
1087 |
|
1088 |
#endif /* HAVE_GETADDRINFO && HAVE_GAI_STRERROR */ |
1089 |
} |
1090 |
|
1091 |
|
1092 |
static void |
1093 |
read_throttlefile( char* throttlefile ) |
1094 |
{ |
1095 |
FILE* fp; |
1096 |
char buf[5000]; |
1097 |
char* cp; |
1098 |
int len; |
1099 |
char pattern[5000]; |
1100 |
long limit; |
1101 |
struct timeval tv; |
1102 |
|
1103 |
fp = fopen( throttlefile, "r" ); |
1104 |
if ( fp == (FILE*) 0 ) |
1105 |
{ |
1106 |
syslog( LOG_CRIT, "%.80s - %m", throttlefile ); |
1107 |
perror( throttlefile ); |
1108 |
exit( 1 ); |
1109 |
} |
1110 |
|
1111 |
(void) gettimeofday( &tv, (struct timezone*) 0 ); |
1112 |
|
1113 |
while ( fgets( buf, sizeof(buf), fp ) != (char*) 0 ) |
1114 |
{ |
1115 |
/* Nuke comments. */ |
1116 |
cp = strchr( buf, '#' ); |
1117 |
if ( cp != (char*) 0 ) |
1118 |
*cp = '\0'; |
1119 |
|
1120 |
/* Nuke trailing whitespace. */ |
1121 |
len = strlen( buf ); |
1122 |
while ( len > 0 && |
1123 |
( buf[len-1] == ' ' || buf[len-1] == '\t' || |
1124 |
buf[len-1] == '\n' || buf[len-1] == '\r' ) ) |
1125 |
buf[--len] = '\0'; |
1126 |
|
1127 |
/* Ignore empty lines. */ |
1128 |
if ( len == 0 ) |
1129 |
continue; |
1130 |
|
1131 |
/* Parse line. */ |
1132 |
if ( sscanf( buf, " %4900[^ \t] %ld", pattern, &limit ) != 2 || limit <= 0 ) |
1133 |
{ |
1134 |
syslog( LOG_CRIT, |
1135 |
"unparsable line in %.80s - %.80s", throttlefile, buf ); |
1136 |
(void) fprintf( stderr, |
1137 |
"%s: unparsable line in %.80s - %.80s\n", |
1138 |
argv0, throttlefile, buf ); |
1139 |
continue; |
1140 |
} |
1141 |
|
1142 |
/* Nuke any leading slashes in pattern. */ |
1143 |
if ( pattern[0] == '/' ) |
1144 |
(void) strcpy( pattern, &pattern[1] ); |
1145 |
while ( ( cp = strstr( pattern, "|/" ) ) != (char*) 0 ) |
1146 |
(void) strcpy( cp + 1, cp + 2 ); |
1147 |
|
1148 |
/* Check for room in throttles. */ |
1149 |
if ( numthrottles >= maxthrottles ) |
1150 |
{ |
1151 |
if ( maxthrottles == 0 ) |
1152 |
{ |
1153 |
maxthrottles = 100; /* arbitrary */ |
1154 |
throttles = NEW( throttletab, maxthrottles ); |
1155 |
} |
1156 |
else |
1157 |
{ |
1158 |
maxthrottles *= 2; |
1159 |
throttles = RENEW( throttles, throttletab, maxthrottles ); |
1160 |
} |
1161 |
if ( throttles == (throttletab*) 0 ) |
1162 |
{ |
1163 |
syslog( LOG_CRIT, "out of memory allocating a throttletab" ); |
1164 |
(void) fprintf( |
1165 |
stderr, "%s: out of memory allocating a throttletab\n", |
1166 |
argv0 ); |
1167 |
exit( 1 ); |
1168 |
} |
1169 |
} |
1170 |
|
1171 |
/* Add to table. */ |
1172 |
throttles[numthrottles].pattern = strdup( pattern ); |
1173 |
if ( throttles[numthrottles].pattern == (char*) 0 ) |
1174 |
{ |
1175 |
syslog( LOG_CRIT, "out of memory copying a throttle pattern" ); |
1176 |
(void) fprintf( |
1177 |
stderr, "%s: out of memory copying a throttle pattern\n", |
1178 |
argv0 ); |
1179 |
exit( 1 ); |
1180 |
} |
1181 |
throttles[numthrottles].limit = limit; |
1182 |
throttles[numthrottles].rate = 0; |
1183 |
throttles[numthrottles].bytes_since_avg = 0; |
1184 |
throttles[numthrottles].num_sending = 0; |
1185 |
|
1186 |
++numthrottles; |
1187 |
} |
1188 |
(void) fclose( fp ); |
1189 |
} |
1190 |
|
1191 |
|
1192 |
static void |
1193 |
shut_down( void ) |
1194 |
{ |
1195 |
int cnum; |
1196 |
struct timeval tv; |
1197 |
|
1198 |
(void) gettimeofday( &tv, (struct timezone*) 0 ); |
1199 |
logstats( &tv ); |
1200 |
for ( cnum = 0; cnum < maxconnects; ++cnum ) |
1201 |
{ |
1202 |
if ( connects[cnum].conn_state != CNST_FREE ) |
1203 |
httpd_close_conn( connects[cnum].hc, &tv ); |
1204 |
if ( connects[cnum].hc != (httpd_conn*) 0 ) |
1205 |
{ |
1206 |
httpd_destroy_conn( connects[cnum].hc ); |
1207 |
free( (void*) connects[cnum].hc ); |
1208 |
--httpd_conn_count; |
1209 |
connects[cnum].hc = (httpd_conn*) 0; |
1210 |
} |
1211 |
} |
1212 |
if ( hs != (httpd_server*) 0 ) |
1213 |
{ |
1214 |
httpd_server* ths = hs; |
1215 |
hs = (httpd_server*) 0; |
1216 |
httpd_terminate( ths ); |
1217 |
} |
1218 |
mmc_destroy(); |
1219 |
tmr_destroy(); |
1220 |
free( (void*) connects ); |
1221 |
if ( throttles != (throttletab*) 0 ) |
1222 |
free( (void*) throttles ); |
1223 |
} |
1224 |
|
1225 |
|
1226 |
static int |
1227 |
handle_newconnect( struct timeval* tvP, int listen_fd ) |
1228 |
{ |
1229 |
int cnum; |
1230 |
connecttab* c; |
1231 |
ClientData client_data; |
1232 |
|
1233 |
/* This loops until the accept() fails, trying to start new |
1234 |
** connections as fast as possible so we don't overrun the |
1235 |
** listen queue. |
1236 |
*/ |
1237 |
for (;;) |
1238 |
{ |
1239 |
/* Is there room in the connection table? */ |
1240 |
if ( numconnects >= maxconnects ) |
1241 |
{ |
1242 |
/* Out of connection slots. Run the timers, then the |
1243 |
** existing connections, and maybe we'll free up a slot |
1244 |
** by the time we get back here. |
1245 |
**/ |
1246 |
syslog( LOG_WARNING, "too many connections!" ); |
1247 |
tmr_run( tvP ); |
1248 |
return 0; |
1249 |
} |
1250 |
/* Find a free connection entry. */ |
1251 |
for ( cnum = 0; cnum < maxconnects; ++cnum ) |
1252 |
if ( connects[cnum].conn_state == CNST_FREE ) |
1253 |
break; |
1254 |
c = &connects[cnum]; |
1255 |
/* Make the httpd_conn if necessary. */ |
1256 |
if ( c->hc == (httpd_conn*) 0 ) |
1257 |
{ |
1258 |
c->hc = NEW( httpd_conn, 1 ); |
1259 |
if ( c->hc == (httpd_conn*) 0 ) |
1260 |
{ |
1261 |
syslog( LOG_CRIT, "out of memory allocating an httpd_conn" ); |
1262 |
exit( 1 ); |
1263 |
} |
1264 |
c->hc->initialized = 0; |
1265 |
++httpd_conn_count; |
1266 |
} |
1267 |
|
1268 |
/* Get the connection. */ |
1269 |
switch ( httpd_get_conn( hs, listen_fd, c->hc ) ) |
1270 |
{ |
1271 |
case GC_FAIL: |
1272 |
case GC_NO_MORE: |
1273 |
return 1; |
1274 |
} |
1275 |
c->conn_state = CNST_READING; |
1276 |
++numconnects; |
1277 |
client_data.p = c; |
1278 |
c->idle_read_timer = tmr_create( |
1279 |
tvP, idle_read_connection, client_data, IDLE_READ_TIMELIMIT * 1000L, |
1280 |
0 ); |
1281 |
if ( c->idle_read_timer == (Timer*) 0 ) |
1282 |
{ |
1283 |
syslog( LOG_CRIT, "tmr_create(idle_read_connection) failed" ); |
1284 |
exit( 1 ); |
1285 |
} |
1286 |
c->idle_send_timer = (Timer*) 0; |
1287 |
c->wakeup_timer = (Timer*) 0; |
1288 |
c->linger_timer = (Timer*) 0; |
1289 |
c->bytes_sent = 0; |
1290 |
c->numtnums = 0; |
1291 |
|
1292 |
/* Set the connection file descriptor to no-delay mode. */ |
1293 |
httpd_set_ndelay( c->hc->conn_fd ); |
1294 |
|
1295 |
fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ ); |
1296 |
|
1297 |
++stats_connections; |
1298 |
if ( numconnects > stats_simultaneous ) |
1299 |
stats_simultaneous = numconnects; |
1300 |
} |
1301 |
} |
1302 |
|
1303 |
|
1304 |
static void |
1305 |
handle_read( connecttab* c, struct timeval* tvP ) |
1306 |
{ |
1307 |
int sz; |
1308 |
ClientData client_data; |
1309 |
httpd_conn* hc = c->hc; |
1310 |
|
1311 |
/* Is there room in our buffer to read more bytes? */ |
1312 |
if ( hc->read_idx >= hc->read_size ) |
1313 |
{ |
1314 |
if ( hc->read_size > 5000 ) |
1315 |
{ |
1316 |
httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); |
1317 |
clear_connection( c, tvP ); |
1318 |
return; |
1319 |
} |
1320 |
httpd_realloc_str( |
1321 |
&hc->read_buf, &hc->read_size, hc->read_size + 1000 ); |
1322 |
} |
1323 |
|
1324 |
/* Read some more bytes. */ |
1325 |
sz = read( |
1326 |
hc->conn_fd, &(hc->read_buf[hc->read_idx]), |
1327 |
hc->read_size - hc->read_idx ); |
1328 |
/* Ignore EWOULDBLOCK errors. At first glance you would think that |
1329 |
** connections returned by fdwatch as readable should never give an |
1330 |
** EWOULDBLOCK; however, this apparently can happen if a packet gets |
1331 |
** garbled. |
1332 |
*/ |
1333 |
if ( sz == 0 || ( sz < 0 && ( errno != EWOULDBLOCK ) ) ) |
1334 |
{ |
1335 |
httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); |
1336 |
clear_connection( c, tvP ); |
1337 |
return; |
1338 |
} |
1339 |
hc->read_idx += sz; |
1340 |
|
1341 |
/* Do we have a complete request yet? */ |
1342 |
switch ( httpd_got_request( hc ) ) |
1343 |
{ |
1344 |
case GR_NO_REQUEST: |
1345 |
return; |
1346 |
case GR_BAD_REQUEST: |
1347 |
httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); |
1348 |
clear_connection( c, tvP ); |
1349 |
return; |
1350 |
} |
1351 |
|
1352 |
/* Yes. Try parsing and resolving it. */ |
1353 |
if ( httpd_parse_request( hc ) < 0 ) |
1354 |
{ |
1355 |
clear_connection( c, tvP ); |
1356 |
return; |
1357 |
} |
1358 |
|
1359 |
/* Check the throttle table */ |
1360 |
if ( ! check_throttles( c ) ) |
1361 |
{ |
1362 |
httpd_send_err( |
1363 |
hc, 503, httpd_err503title, "", httpd_err503form, hc->encodedurl ); |
1364 |
clear_connection( c, tvP ); |
1365 |
return; |
1366 |
} |
1367 |
|
1368 |
/* Start the connection going. */ |
1369 |
if ( httpd_start_request( hc, tvP ) < 0 ) |
1370 |
{ |
1371 |
/* Something went wrong. Close down the connection. */ |
1372 |
clear_connection( c, tvP ); |
1373 |
return; |
1374 |
} |
1375 |
|
1376 |
/* Fill in bytes_to_send. */ |
1377 |
if ( hc->got_range ) |
1378 |
{ |
1379 |
c->bytes_sent = hc->init_byte_loc; |
1380 |
c->bytes_to_send = hc->end_byte_loc + 1; |
1381 |
} |
1382 |
else |
1383 |
c->bytes_to_send = hc->bytes_to_send; |
1384 |
|
1385 |
/* Check if it's already handled. */ |
1386 |
if ( hc->file_address == (char*) 0 |
1387 |
#ifdef MMAP_MAX |
1388 |
&& hc->file_fd <= 0 |
1389 |
#endif |
1390 |
) |
1391 |
{ |
1392 |
/* No file address means someone else is handling it. */ |
1393 |
c->bytes_sent = hc->bytes_sent; |
1394 |
clear_connection( c, tvP ); |
1395 |
return; |
1396 |
} |
1397 |
if ( c->bytes_sent >= c->bytes_to_send ) |
1398 |
{ |
1399 |
/* There's nothing to send. */ |
1400 |
clear_connection( c, tvP ); |
1401 |
return; |
1402 |
} |
1403 |
|
1404 |
/* Cool, we have a valid connection and a file to send to it. */ |
1405 |
c->conn_state = CNST_SENDING; |
1406 |
c->started_at = tvP->tv_sec; |
1407 |
c->wouldblock_delay = 0; |
1408 |
client_data.p = c; |
1409 |
tmr_cancel( c->idle_read_timer ); |
1410 |
c->idle_read_timer = (Timer*) 0; |
1411 |
c->idle_send_timer = tmr_create( |
1412 |
tvP, idle_send_connection, client_data, IDLE_SEND_TIMELIMIT * 1000L, |
1413 |
0 ); |
1414 |
if ( c->idle_send_timer == (Timer*) 0 ) |
1415 |
{ |
1416 |
syslog( LOG_CRIT, "tmr_create(idle_send_connection) failed" ); |
1417 |
exit( 1 ); |
1418 |
} |
1419 |
|
1420 |
fdwatch_del_fd( hc->conn_fd ); |
1421 |
fdwatch_add_fd( hc->conn_fd, c, FDW_WRITE ); |
1422 |
} |
1423 |
|
1424 |
|
1425 |
static void |
1426 |
handle_send( connecttab* c, struct timeval* tvP ) |
1427 |
{ |
1428 |
int sz, coast; |
1429 |
ClientData client_data; |
1430 |
time_t elapsed; |
1431 |
httpd_conn* hc = c->hc; |
1432 |
|
1433 |
/* Do we need to write the headers first? */ |
1434 |
if ( hc->responselen == 0 ) |
1435 |
{ |
1436 |
/* No, just write the file. */ |
1437 |
#ifdef MMAP_MAX |
1438 |
if (hc->file_address) |
1439 |
{ |
1440 |
#endif |
1441 |
sz = write( |
1442 |
hc->conn_fd, &(hc->file_address[c->bytes_sent]), |
1443 |
MIN( c->bytes_to_send - c->bytes_sent, c->limit ) ); |
1444 |
#ifdef MMAP_MAX |
1445 |
} |
1446 |
else |
1447 |
{ |
1448 |
if (hc->write_ofs >= WRITE_BUFFER) |
1449 |
{ |
1450 |
/* refill buffer */ |
1451 |
lseek (hc->file_fd, c->bytes_sent, SEEK_SET); |
1452 |
read (hc->file_fd, hc->write_buf, WRITE_BUFFER); |
1453 |
hc->write_ofs = 0; |
1454 |
} |
1455 |
|
1456 |
sz = write( |
1457 |
hc->conn_fd, hc->write_buf + hc->write_ofs, |
1458 |
MIN( c->bytes_to_send - c->bytes_sent, MIN( WRITE_BUFFER - hc->write_ofs, c->limit ) ) ); |
1459 |
|
1460 |
if (sz > 0) |
1461 |
hc->write_ofs += sz; |
1462 |
|
1463 |
} |
1464 |
#endif |
1465 |
} |
1466 |
else |
1467 |
{ |
1468 |
/* Yes. We'll combine headers and file into a single writev(), |
1469 |
** hoping that this generates a single packet. |
1470 |
*/ |
1471 |
struct iovec iv[2]; |
1472 |
int ivc; |
1473 |
|
1474 |
iv[0].iov_base = hc->response; |
1475 |
iv[0].iov_len = hc->responselen; |
1476 |
|
1477 |
#ifdef MMAP_MAX |
1478 |
if (hc->file_address) |
1479 |
#endif |
1480 |
{ |
1481 |
iv[1].iov_base = &(hc->file_address[c->bytes_sent]); |
1482 |
iv[1].iov_len = MIN( c->bytes_to_send - c->bytes_sent, c->limit ); |
1483 |
ivc = 2; |
1484 |
} |
1485 |
#ifdef MMAP_MAX |
1486 |
else |
1487 |
ivc = 1; |
1488 |
#endif |
1489 |
sz = writev( hc->conn_fd, iv, ivc ); |
1490 |
} |
1491 |
|
1492 |
if ( sz == 0 || |
1493 |
( sz < 0 && ( errno == EWOULDBLOCK || errno == EAGAIN ) ) ) |
1494 |
{ |
1495 |
/* This shouldn't happen, but some kernels, e.g. |
1496 |
** SunOS 4.1.x, are broken and select() says that |
1497 |
** O_NDELAY sockets are always writable even when |
1498 |
** they're actually not. |
1499 |
** |
1500 |
** Current workaround is to block sending on this |
1501 |
** socket for a brief adaptively-tuned period. |
1502 |
** Fortunately we already have all the necessary |
1503 |
** blocking code, for use with throttling. |
1504 |
*/ |
1505 |
c->wouldblock_delay += MIN_WOULDBLOCK_DELAY; |
1506 |
c->conn_state = CNST_PAUSING; |
1507 |
fdwatch_del_fd( hc->conn_fd ); |
1508 |
client_data.p = c; |
1509 |
c->wakeup_timer = tmr_create( |
1510 |
tvP, wakeup_connection, client_data, c->wouldblock_delay, 0 ); |
1511 |
if ( c->wakeup_timer == (Timer*) 0 ) |
1512 |
{ |
1513 |
syslog( LOG_CRIT, "tmr_create(wakeup_connection) failed" ); |
1514 |
exit( 1 ); |
1515 |
} |
1516 |
return; |
1517 |
} |
1518 |
if ( sz < 0 ) |
1519 |
{ |
1520 |
/* Something went wrong, close this connection. |
1521 |
** |
1522 |
** If it's just an EPIPE, don't bother logging, that |
1523 |
** just means the client hung up on us. |
1524 |
** |
1525 |
** On some systems, write() occasionally gives an EINVAL. |
1526 |
** Dunno why, something to do with the socket going |
1527 |
** bad. Anyway, we don't log those either. |
1528 |
** |
1529 |
** And ECONNRESET isn't interesting either. |
1530 |
*/ |
1531 |
if ( errno != EPIPE && errno != EINVAL && errno != ECONNRESET ) |
1532 |
syslog( LOG_ERR, "write - %m sending %.80s", hc->encodedurl ); |
1533 |
clear_connection( c, tvP ); |
1534 |
return; |
1535 |
} |
1536 |
|
1537 |
/* Ok, we wrote something. */ |
1538 |
tmr_reset( tvP, c->idle_send_timer ); |
1539 |
/* Was this a headers + file writev()? */ |
1540 |
if ( hc->responselen > 0 ) |
1541 |
{ |
1542 |
/* Yes; did we write only part of the headers? */ |
1543 |
if ( sz < hc->responselen ) |
1544 |
{ |
1545 |
/* Yes; move the unwritten part to the front of the buffer. */ |
1546 |
int newlen = hc->responselen - sz; |
1547 |
(void) memcpy( hc->response, &(hc->response[sz]), newlen ); |
1548 |
hc->responselen = newlen; |
1549 |
sz = 0; |
1550 |
} |
1551 |
else |
1552 |
{ |
1553 |
/* Nope, we wrote the full headers, so adjust accordingly. */ |
1554 |
sz -= hc->responselen; |
1555 |
hc->responselen = 0; |
1556 |
} |
1557 |
} |
1558 |
/* And update how much of the file we wrote. */ |
1559 |
c->bytes_sent += sz; |
1560 |
c->hc->bytes_sent += sz; |
1561 |
|
1562 |
/* Are we done? */ |
1563 |
if ( c->bytes_sent >= c->bytes_to_send ) |
1564 |
{ |
1565 |
/* This conection is finished! */ |
1566 |
clear_connection( c, tvP ); |
1567 |
return; |
1568 |
} |
1569 |
|
1570 |
/* Tune the (blockheaded) wouldblock delay. */ |
1571 |
if ( c->wouldblock_delay > MIN_WOULDBLOCK_DELAY ) |
1572 |
c->wouldblock_delay -= MIN_WOULDBLOCK_DELAY; |
1573 |
|
1574 |
/* If we're throttling, check if we're sending too fast. */ |
1575 |
if ( c->limit != THROTTLE_NOLIMIT ) |
1576 |
{ |
1577 |
elapsed = tvP->tv_sec - c->started_at; |
1578 |
if ( elapsed == 0 || c->hc->bytes_sent / elapsed > c->limit ) |
1579 |
{ |
1580 |
c->conn_state = CNST_PAUSING; |
1581 |
fdwatch_del_fd( hc->conn_fd ); |
1582 |
/* When should we send the next c->limit bytes |
1583 |
** to get back on schedule? If less than a second |
1584 |
** (integer math rounding), use 1/8 second. |
1585 |
*/ |
1586 |
coast = ( c->hc->bytes_sent + c->limit ) / c->limit - elapsed; |
1587 |
client_data.p = c; |
1588 |
c->wakeup_timer = tmr_create( |
1589 |
tvP, wakeup_connection, client_data, |
1590 |
coast ? ( coast * 1000L ) : 125L, 0 ); |
1591 |
if ( c->wakeup_timer == (Timer*) 0 ) |
1592 |
{ |
1593 |
syslog( LOG_CRIT, "tmr_create(wakeup_connection) failed" ); |
1594 |
exit( 1 ); |
1595 |
} |
1596 |
} |
1597 |
} |
1598 |
} |
1599 |
|
1600 |
|
1601 |
static void |
1602 |
handle_linger( connecttab* c, struct timeval* tvP ) |
1603 |
{ |
1604 |
char buf[1024]; |
1605 |
int r; |
1606 |
|
1607 |
/* In lingering-close mode we just read and ignore bytes. An error |
1608 |
** or EOF ends things, otherwise we go until a timeout. |
1609 |
*/ |
1610 |
r = read( c->hc->conn_fd, buf, sizeof(buf) ); |
1611 |
if ( r <= 0 ) |
1612 |
really_clear_connection( c, tvP ); |
1613 |
} |
1614 |
|
1615 |
|
1616 |
static int |
1617 |
check_throttles( connecttab* c ) |
1618 |
{ |
1619 |
int tnum; |
1620 |
|
1621 |
c->numtnums = 0; |
1622 |
c->limit = THROTTLE_NOLIMIT; |
1623 |
for ( tnum = 0; tnum < numthrottles && c->numtnums < MAXTHROTTLENUMS; |
1624 |
++tnum ) |
1625 |
if ( match( throttles[tnum].pattern, c->hc->expnfilename ) ) |
1626 |
{ |
1627 |
/* If we're way over the limit, don't even start. */ |
1628 |
if ( throttles[tnum].rate > throttles[tnum].limit * 2 ) |
1629 |
return 0; |
1630 |
if ( throttles[tnum].num_sending < 0 ) |
1631 |
{ |
1632 |
syslog( LOG_ERR, "throttle sending count was negative - shouldn't happen!" ); |
1633 |
throttles[tnum].num_sending = 0; |
1634 |
} |
1635 |
c->tnums[c->numtnums++] = tnum; |
1636 |
++throttles[tnum].num_sending; |
1637 |
c->limit = MIN( |
1638 |
c->limit, throttles[tnum].limit / throttles[tnum].num_sending ); |
1639 |
} |
1640 |
return 1; |
1641 |
} |
1642 |
|
1643 |
|
1644 |
static void |
1645 |
clear_throttles( connecttab* c, struct timeval* tvP ) |
1646 |
{ |
1647 |
int i, tnum; |
1648 |
|
1649 |
for ( i = 0; i < c->numtnums; ++i ) |
1650 |
{ |
1651 |
tnum = c->tnums[i]; |
1652 |
--throttles[tnum].num_sending; |
1653 |
throttles[tnum].bytes_since_avg += c->hc->bytes_sent; |
1654 |
} |
1655 |
} |
1656 |
|
1657 |
|
1658 |
static void |
1659 |
update_throttles( ClientData client_data, struct timeval* nowP ) |
1660 |
{ |
1661 |
int tnum; |
1662 |
|
1663 |
for ( tnum = 0; tnum < numthrottles; ++tnum ) |
1664 |
{ |
1665 |
throttles[tnum].rate = |
1666 |
( 3 * throttles[tnum].rate + |
1667 |
throttles[tnum].bytes_since_avg / THROTTLE_TIME ) / 4; |
1668 |
throttles[tnum].bytes_since_avg = 0; |
1669 |
/* Log a warning message if necessary. */ |
1670 |
if ( throttles[tnum].rate > throttles[tnum].limit ) |
1671 |
{ |
1672 |
if ( throttles[tnum].rate > throttles[tnum].limit * 2 ) |
1673 |
syslog( LOG_NOTICE, "throttle #%d '%.80s' rate %ld GREATLY exceeding limit %ld", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].limit ); |
1674 |
else |
1675 |
syslog( LOG_NOTICE, "throttle #%d '%.80s' rate %ld exceeding limit %ld", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].limit ); |
1676 |
} |
1677 |
} |
1678 |
} |
1679 |
|
1680 |
|
1681 |
static void |
1682 |
clear_connection( connecttab* c, struct timeval* tvP ) |
1683 |
{ |
1684 |
ClientData client_data; |
1685 |
|
1686 |
/* If we haven't actually sent the buffered response yet, do so now. */ |
1687 |
httpd_write_response( c->hc ); |
1688 |
|
1689 |
if ( c->idle_read_timer != (Timer*) 0 ) |
1690 |
{ |
1691 |
tmr_cancel( c->idle_read_timer ); |
1692 |
c->idle_read_timer = 0; |
1693 |
} |
1694 |
if ( c->idle_send_timer != (Timer*) 0 ) |
1695 |
{ |
1696 |
tmr_cancel( c->idle_send_timer ); |
1697 |
c->idle_send_timer = 0; |
1698 |
} |
1699 |
if ( c->wakeup_timer != (Timer*) 0 ) |
1700 |
{ |
1701 |
tmr_cancel( c->wakeup_timer ); |
1702 |
c->wakeup_timer = 0; |
1703 |
} |
1704 |
|
1705 |
/* This is our version of Apache's lingering_close() routine, which is |
1706 |
** their version of the often-broken SO_LINGER socket option. For why |
1707 |
** this is necessary, see http://www.apache.org/docs/misc/fin_wait_2.html |
1708 |
** What we do is delay the actual closing for a few seconds, while reading |
1709 |
** any bytes that come over the connection. However, we don't want to do |
1710 |
** this unless it's necessary, because it ties up a connection slot and |
1711 |
** file descriptor which means our maximum connection-handling rate |
1712 |
** is lower. So, elsewhere we set a flag when we detect the few |
1713 |
** circumstances that make a lingering close necessary. If the flag |
1714 |
** isn't set we do the real close now. |
1715 |
*/ |
1716 |
if ( c->hc->should_linger ) |
1717 |
{ |
1718 |
c->conn_state = CNST_LINGERING; |
1719 |
fdwatch_del_fd( c->hc->conn_fd ); |
1720 |
fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ ); |
1721 |
/* Make sure we are still in no-delay mode. */ |
1722 |
httpd_set_ndelay( c->hc->conn_fd ); |
1723 |
client_data.p = c; |
1724 |
c->linger_timer = tmr_create( |
1725 |
tvP, linger_clear_connection, client_data, LINGER_TIME * 1000L, 0 ); |
1726 |
if ( c->linger_timer == (Timer*) 0 ) |
1727 |
{ |
1728 |
syslog( LOG_CRIT, "tmr_create(linger_clear_connection) failed" ); |
1729 |
exit( 1 ); |
1730 |
} |
1731 |
} |
1732 |
else |
1733 |
really_clear_connection( c, tvP ); |
1734 |
} |
1735 |
|
1736 |
|
1737 |
static void |
1738 |
really_clear_connection( connecttab* c, struct timeval* tvP ) |
1739 |
{ |
1740 |
stats_bytes += c->bytes_to_send; |
1741 |
fdwatch_del_fd( c->hc->conn_fd ); |
1742 |
httpd_close_conn( c->hc, tvP ); |
1743 |
clear_throttles( c, tvP ); |
1744 |
if ( c->linger_timer != (Timer*) 0 ) |
1745 |
{ |
1746 |
tmr_cancel( c->linger_timer ); |
1747 |
c->linger_timer = 0; |
1748 |
} |
1749 |
c->conn_state = CNST_FREE; |
1750 |
--numconnects; |
1751 |
} |
1752 |
|
1753 |
|
1754 |
static void |
1755 |
idle_read_connection( ClientData client_data, struct timeval* nowP ) |
1756 |
{ |
1757 |
connecttab* c; |
1758 |
|
1759 |
c = (connecttab*) client_data.p; |
1760 |
c->idle_read_timer = (Timer*) 0; |
1761 |
if ( c->conn_state != CNST_FREE ) |
1762 |
{ |
1763 |
syslog( LOG_INFO, |
1764 |
"%.80s connection timed out reading", |
1765 |
httpd_ntoa( &c->hc->client_addr ) ); |
1766 |
httpd_send_err( c->hc, 408, httpd_err408title, "", httpd_err408form, "" ); |
1767 |
clear_connection( c, nowP ); |
1768 |
} |
1769 |
} |
1770 |
|
1771 |
|
1772 |
static void |
1773 |
idle_send_connection( ClientData client_data, struct timeval* nowP ) |
1774 |
{ |
1775 |
connecttab* c; |
1776 |
|
1777 |
c = (connecttab*) client_data.p; |
1778 |
c->idle_send_timer = (Timer*) 0; |
1779 |
if ( c->conn_state != CNST_FREE ) |
1780 |
{ |
1781 |
syslog( LOG_INFO, |
1782 |
"%.80s connection timed out sending", |
1783 |
httpd_ntoa( &c->hc->client_addr ) ); |
1784 |
clear_connection( c, nowP ); |
1785 |
} |
1786 |
} |
1787 |
|
1788 |
|
1789 |
static void |
1790 |
wakeup_connection( ClientData client_data, struct timeval* nowP ) |
1791 |
{ |
1792 |
connecttab* c; |
1793 |
|
1794 |
c = (connecttab*) client_data.p; |
1795 |
c->wakeup_timer = (Timer*) 0; |
1796 |
if ( c->conn_state == CNST_PAUSING ) |
1797 |
{ |
1798 |
c->conn_state = CNST_SENDING; |
1799 |
fdwatch_add_fd( c->hc->conn_fd, c, FDW_WRITE ); |
1800 |
} |
1801 |
} |
1802 |
|
1803 |
static void |
1804 |
linger_clear_connection( ClientData client_data, struct timeval* nowP ) |
1805 |
{ |
1806 |
connecttab* c; |
1807 |
|
1808 |
c = (connecttab*) client_data.p; |
1809 |
c->linger_timer = (Timer*) 0; |
1810 |
really_clear_connection( c, nowP ); |
1811 |
} |
1812 |
|
1813 |
|
1814 |
static void |
1815 |
occasional( ClientData client_data, struct timeval* nowP ) |
1816 |
{ |
1817 |
mmc_cleanup( nowP ); |
1818 |
tmr_cleanup(); |
1819 |
} |
1820 |
|
1821 |
|
1822 |
#ifdef STATS_TIME |
1823 |
static void |
1824 |
show_stats( ClientData client_data, struct timeval* nowP ) |
1825 |
{ |
1826 |
logstats( nowP ); |
1827 |
} |
1828 |
#endif /* STATS_TIME */ |
1829 |
|
1830 |
|
1831 |
/* Generate debugging statistics syslog messages for all packages. */ |
1832 |
static void |
1833 |
logstats( struct timeval* nowP ) |
1834 |
{ |
1835 |
struct timeval tv; |
1836 |
time_t now; |
1837 |
long up_secs, stats_secs; |
1838 |
|
1839 |
if ( nowP == (struct timeval*) 0 ) |
1840 |
{ |
1841 |
(void) gettimeofday( &tv, (struct timezone*) 0 ); |
1842 |
nowP = &tv; |
1843 |
} |
1844 |
now = nowP->tv_sec; |
1845 |
up_secs = now - start_time; |
1846 |
stats_secs = now - stats_time; |
1847 |
if ( stats_secs == 0 ) |
1848 |
stats_secs = 1; /* fudge */ |
1849 |
stats_time = now; |
1850 |
syslog( LOG_NOTICE, |
1851 |
"up %ld seconds, stats for %ld seconds:", up_secs, stats_secs ); |
1852 |
|
1853 |
thttpd_logstats( stats_secs ); |
1854 |
httpd_logstats( stats_secs ); |
1855 |
mmc_logstats( stats_secs ); |
1856 |
fdwatch_logstats( stats_secs ); |
1857 |
tmr_logstats( stats_secs ); |
1858 |
} |
1859 |
|
1860 |
|
1861 |
/* Generate debugging statistics syslog message. */ |
1862 |
static void |
1863 |
thttpd_logstats( long secs ) |
1864 |
{ |
1865 |
syslog( LOG_NOTICE, |
1866 |
" thttpd - %ld connections (%g/sec), %d max simultaneous, %ld bytes (%g/sec), %d httpd_conns allocated", |
1867 |
stats_connections, (float) stats_connections / secs, |
1868 |
stats_simultaneous, stats_bytes, (float) stats_bytes / secs, |
1869 |
httpd_conn_count ); |
1870 |
stats_connections = stats_bytes = 0L; |
1871 |
stats_simultaneous = 0; |
1872 |
} |