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