root/xthttpd.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. propagate_handle
  2. config_handle
  3. handle_term
  4. handle_chld
  5. handle_hup
  6. handle_usr1
  7. handle_usr2
  8. handle_alrm
  9. re_open_logfile
  10. main
  11. reset_args
  12. parse_args
  13. usage
  14. fork_config
  15. m4_interpose
  16. read_config
  17. value_required
  18. no_value_required
  19. e_strdup
  20. lookup_hostname
  21. read_throttlefile
  22. shut_down
  23. handle_newconnect
  24. handle_read
  25. handle_send
  26. handle_linger
  27. check_throttles
  28. clear_throttles
  29. update_throttles
  30. force_update_throttles
  31. finish_connection
  32. clear_connection
  33. really_clear_connection
  34. idle
  35. wakeup_connection
  36. linger_clear_connection
  37. mspoll
  38. occasional
  39. show_stats
  40. logstats
  41. xthttpd_logstats

   1 /* xthttpd.c - eXtended tiny/turbo/throttling HTTP server
   2 **
   3 ** Copyright (c) 1995,1998,1999,2000,2001,2015
   4 **     Jef Poskanzer <jef@mail.acme.com>.  All rights reserved.
   5 ** Copyright (c) 2023,2024,2025
   6 **     Amelia Zabardast Ziabari <ame@psianesia.org>.  All rigts reserved.
   7 **
   8 ** Redistribution and use in source and binary forms, with or without
   9 ** modification, are permitted provided that the following conditions
  10 ** are met:
  11 ** 1. Redistributions of source code must retain the above copyright
  12 **    notice, this list of conditions and the following disclaimer.
  13 ** 2. Redistributions in binary form must reproduce the above copyright
  14 **    notice, this list of conditions and the following disclaimer in the
  15 **    documentation and/or other materials provided with the distribution.
  16 **
  17 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  18 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  19 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  20 ** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  21 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  22 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  23 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  24 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  25 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  26 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  27 ** SUCH DAMAGE.
  28 */
  29 
  30 
  31 #include "config.h"
  32 #include "version.h"
  33 
  34 #include <sys/param.h>
  35 #include <sys/types.h>
  36 #include <sys/stat.h>
  37 #include <sys/wait.h>
  38 #include <sys/uio.h>
  39 
  40 #include <errno.h>
  41 #ifdef HAVE_FCNTL_H
  42 #include <fcntl.h>
  43 #endif
  44 #include <pwd.h>
  45 #ifdef HAVE_GRP_H
  46 #include <grp.h>
  47 #endif
  48 #include <signal.h>
  49 #include <stdio.h>
  50 #include <stdlib.h>
  51 #include <string.h>
  52 #include <syslog.h>
  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 #ifndef SHUT_WR
  62 #define SHUT_WR 1
  63 #endif
  64 
  65 #ifndef HAVE_INT64T
  66 typedef long long int64_t;
  67 #endif
  68 
  69 
  70 static char* argv0;
  71 static char* configfile;
  72 static char pident[256];
  73 static int multiserv;
  74 static pid_t eldest_child;
  75 static pid_t parent;
  76 static int debug;
  77 static unsigned short port;
  78 static char* dir;
  79 static char* data_dir;
  80 static int do_chroot, no_log, no_symlink_check, do_vhost;
  81 static int do_global_passwd, do_fwdhdr;
  82 static char* cgi_pattern;
  83 static char* cgi_wrap;
  84 static int cgi_limit;
  85 static char* mime_encf;
  86 static char* mime_typf;
  87 static char* url_pattern;
  88 static int no_empty_referrers;
  89 static char* local_pattern;
  90 static char* logfile;
  91 static char* throttlefile;
  92 static char* hostname;
  93 static char* pidfile;
  94 static char* user;
  95 static char* charset;
  96 static char* p3p;
  97 static int max_age;
  98 static char** exargs = NULL;
  99 
 100 typedef struct {
 101     char* pattern;
 102     long max_limit, min_limit;
 103     long rate;
 104     off_t bytes_since_avg;
 105     int num_sending;
 106     } throttletab;
 107 static throttletab* throttles;
 108 static int numthrottles, maxthrottles;
 109 
 110 #define THROTTLE_NOLIMIT -1
 111 
 112 
 113 typedef struct {
 114     int conn_state;
 115     int next_free_connect;
 116     httpd_conn* hc;
 117     int tnums[MAXTHROTTLENUMS];         /* throttle indexes */
 118     int numtnums;
 119     long max_limit, min_limit;
 120     time_t started_at, active_at;
 121     Timer* wakeup_timer;
 122     Timer* linger_timer;
 123     long wouldblock_delay;
 124     long restart_delay;
 125     off_t bytes;
 126     off_t end_byte_index;
 127     off_t next_byte_index;
 128     } connecttab;
 129 static connecttab* connects;
 130 static int num_connects, max_connects, first_free_connect;
 131 static int httpd_conn_count;
 132 
 133 /* The connection states. */
 134 #define CNST_FREE 0
 135 #define CNST_READING 1
 136 #define CNST_SENDING 2
 137 #define CNST_PAUSING 3
 138 #define CNST_LINGERING 4
 139 #define CNST_RESTARTING 5
 140 
 141 
 142 static httpd_server* hs = (httpd_server*) 0;
 143 int terminate = 0;
 144 time_t start_time, stats_time;
 145 long stats_connections;
 146 off_t stats_bytes;
 147 int stats_simultaneous;
 148 
 149 static volatile int got_hup, got_usr1, watchdog_flag;
 150 
 151 
 152 /* Forwards. */
 153 static void reset_args( void );
 154 static void parse_args( int argc, char** argv );
 155 static void usage( void );
 156 static void fork_config( char* identifier );
 157 static void read_config( char* filename );
 158 static void value_required( char* name, char* value );
 159 static void no_value_required( char* name, char* value );
 160 static char* e_strdup( char* oldstr );
 161 static void lookup_hostname( httpd_sockaddr* sa4P, size_t sa4_len,
 162                              int* gotv4P,
 163                              httpd_sockaddr* sa6P, size_t sa6_len,
 164                              int* gotv6P );
 165 static FILE* read_throttlefile( char* tf, FILE* fp );
 166 static void shut_down( void );
 167 static int handle_newconnect( struct timeval* tvP, int listen_fd );
 168 static void handle_read( connecttab* c, struct timeval* tvP );
 169 static void handle_send( connecttab* c, struct timeval* tvP );
 170 static void handle_linger( connecttab* c, struct timeval* tvP );
 171 static int check_throttles( connecttab* c );
 172 static void clear_throttles( connecttab* c, struct timeval* tvP );
 173 static void update_throttles( ClientData client_data, struct timeval* nowP );
 174 static void force_update_throttles( void );
 175 static void finish_connection( connecttab* c, struct timeval* tvP );
 176 static void clear_connection( connecttab* c, struct timeval* tvP );
 177 static void really_clear_connection( connecttab* c, struct timeval* tvP );
 178 static void idle( ClientData client_data, struct timeval* nowP );
 179 static void wakeup_connection( ClientData client_data,
 180                                struct timeval* nowP );
 181 static void linger_clear_connection( ClientData client_data,
 182                                      struct timeval* nowP );
 183 static void mspoll( ClientData client_data, struct timeval* nowP );
 184 static void occasional( ClientData client_data, struct timeval* nowP );
 185 #ifdef STATS_TIME
 186 static void show_stats( ClientData client_data, struct timeval* nowP );
 187 #endif /* STATS_TIME */
 188 static void logstats( struct timeval* nowP );
 189 static void xthttpd_logstats( long secs );
 190 
 191 
 192 /* Some signals must be propagated to the parent's children in a multiserv
 193 ** environment, to ensure (for example) that log files are reopened.
 194 */
 195 static void
 196 propagate_handle( int sig )
 197     {
 198     const int oerrno = errno;
 199 
 200     /* Set up handler again. */
 201     (void) signal( sig, propagate_handle );
 202 
 203     /* Feed the recieved signal to the children PGID (based on eldest child) */
 204     kill( -eldest_child, sig );
 205 
 206     /* Restore previous errno. */
 207     errno = oerrno;
 208     }
 209 
 210 
 211 /* Signal handler to reset the multiserv count back to 1, so that a child
 212 ** process can free the parent from halt-state 3.
 213 */
 214 static void
 215 config_handle( int sig )
 216     {
 217     multiserv = 1;
 218     }
 219 
 220 
 221 /* SIGTERM and SIGINT say to exit immediately. */
 222 static void
 223 handle_term( int sig )
 224     {
 225     /* Don't need to set up the handler again, since it's a one-shot. */
 226 
 227     shut_down();
 228     syslog( LOG_NOTICE, "exiting due to signal %d", sig );
 229     closelog();
 230     exit( 1 );
 231     }
 232 
 233 
 234 /* SIGCHLD - a chile process exitted, so we need to reap the zombie */
 235 static void
 236 handle_chld( int sig )
 237     {
 238     const int oerrno = errno;
 239     pid_t pid;
 240     int status;
 241 
 242     (void) sig; /* XXX: gcc */
 243 
 244     /* Set up handler again. */
 245     (void) signal( SIGCHLD, handle_chld );
 246 
 247     /* Reap defunct children until there aren't any more. */
 248     for (;;)
 249         {
 250 #ifdef HAVE_WAITPID
 251         pid = waitpid( (pid_t) -1, &status, WNOHANG );
 252 #else /* HAVE_WAITPID */
 253         pid = wait3( &status, WNOHANG, (struct rusage*) 0 );
 254 #endif /* HAVE_WAITPID */
 255         if ( (int) pid == 0 )           /* none left */
 256             break;
 257         if ( (int) pid < 0 )
 258             {
 259             if ( errno == EINTR || errno == EAGAIN )
 260                 continue;
 261             /* ECHILD shouldn't happen with the WNOHANG option,
 262             ** but with some kernels it does anyway.  Ignore it.
 263             */
 264             if ( errno != ECHILD )
 265                 syslog( LOG_ERR, "child wait - %m" );
 266             break;
 267             }
 268         /* Decrement the CGI count.  Note that this is not accurate, since
 269         ** each CGI can involve two or even three child processes.
 270         ** Decrementing for each child means that when there is heavy CGI
 271         ** activity, the count will be lower than it should be, and therefore
 272         ** more CGIs will be allowed than should be.
 273         */
 274         if ( hs != (httpd_server*) 0 )
 275             {
 276             --hs->cgi_count;
 277             if ( hs->cgi_count < 0 )
 278                 hs->cgi_count = 0;
 279             }
 280         }
 281 
 282     /* Restore previous errno. */
 283     errno = oerrno;
 284     }
 285 
 286 
 287 /* SIGHUP says to re-open the log file. */
 288 static void
 289 handle_hup( int sig )
 290     {
 291     const int oerrno = errno;
 292 
 293     (void) sig; /* XXX: gcc */
 294 
 295     /* Set up handler again. */
 296     (void) signal( SIGHUP, handle_hup );
 297 
 298     /* Just set a flag that we got the signal. */
 299     got_hup = 1;
 300 
 301     /* Restore previous errno. */
 302     errno = oerrno;
 303     }
 304 
 305 
 306 /* SIGUSR1 says to exit as soon as all current connections are done. */
 307 static void
 308 handle_usr1( int sig )
 309     {
 310     /* Don't need to set up the handler again, since it's a one-shot. */
 311 
 312     (void) sig; /* XXX: gcc */
 313 
 314     if ( num_connects == 0 )
 315         {
 316         /* If there are no active connections we want to exit immediately
 317         ** here.  Not only is it faster, but without any connections the
 318         ** main loop won't wake up until the next new connection.
 319         */
 320         shut_down();
 321         syslog( LOG_NOTICE, "exiting" );
 322         closelog();
 323         exit( 0 );
 324         }
 325 
 326     /* Otherwise, just set a flag that we got the signal. */
 327     got_usr1 = 1;
 328 
 329     /* Don't need to restore old errno, since we didn't do any syscalls. */
 330     }
 331 
 332 
 333 /* SIGUSR2 says to generate the stats syslogs immediately. */
 334 static void
 335 handle_usr2( int sig )
 336     {
 337     const int oerrno = errno;
 338 
 339     (void) sig; /* XXX: gcc */
 340 
 341     /* Set up handler again. */
 342     (void) signal( SIGUSR2, handle_usr2 );
 343 
 344     logstats( (struct timeval*) 0 );
 345 
 346     /* Restore previous errno. */
 347     errno = oerrno;
 348     }
 349 
 350 
 351 /* SIGALRM is used as a watchdog. */
 352 static void
 353 handle_alrm( int sig )
 354     {
 355     const int oerrno = errno;
 356 
 357     (void) sig; /* XXX: gcc */
 358 
 359     /* If nothing has been happening */
 360     if ( ! watchdog_flag )
 361         {
 362         /* Try changing dirs to someplace we can write. */
 363         (void) chdir( "/tmp" );
 364         /* Dump core. */
 365         abort();
 366         }
 367     watchdog_flag = 0;
 368 
 369     /* Set up handler again. */
 370     (void) signal( SIGALRM, handle_alrm );
 371 
 372     /* Set up alarm again. */
 373     (void) alarm( OCCASIONAL_TIME * 3 );
 374 
 375     /* Restore previous errno. */
 376     errno = oerrno;
 377     }
 378 
 379 
 380 static void
 381 re_open_logfile( void )
 382     {
 383     FILE* logfp;
 384     int retchmod;
 385 
 386     if ( no_log || hs == (httpd_server*) 0 )
 387         return;
 388 
 389     /* Re-open the log file. */
 390     if ( logfile != (char*) 0 && strcmp( logfile, "-" ) != 0 )
 391         {
 392         syslog( LOG_NOTICE, "re-opening logfile" );
 393         logfp = fopen( logfile, "a" );
 394         retchmod = chmod( logfile, S_IRUSR|S_IWUSR );
 395         if ( logfp == (FILE*) 0 || retchmod != 0 )
 396             {
 397             syslog( LOG_CRIT, "re-opening %.80s - %m", logfile );
 398             return;
 399             }
 400         (void) fcntl( fileno( logfp ), F_SETFD, 1 );
 401         httpd_set_logfp( hs, logfp );
 402         }
 403     }
 404 
 405 
 406 int
 407 main( int argc, char** argv )
 408     {
 409     char* cp;
 410     struct passwd* pwd;
 411     uid_t uid = 32767;
 412     gid_t gid = 32767;
 413     char cwd[MAXPATHLEN+1];
 414     FILE* logfp;
 415     int retchmod;
 416     int num_ready;
 417     int cnum;
 418     connecttab* c;
 419     httpd_conn* hc;
 420     httpd_sockaddr sa4;
 421     httpd_sockaddr sa6;
 422     int gotv4, gotv6;
 423     struct timeval tv;
 424 
 425     multiserv = 0;
 426     argv0 = argv[0];
 427 
 428     cp = strrchr( argv0, '/' );
 429     if ( cp != (char*) 0 )
 430         ++cp;
 431     else
 432         cp = argv0;
 433 
 434     /* Initialize the default server identifier with no extra identification
 435     ** (e.g for servers with multiple processes handling different things)
 436     */
 437     memset( pident, 0, sizeof(pident) - 1 );
 438     strncat( pident, cp, sizeof(pident) - 1 );
 439 
 440     /* Prepare for throttle file. */
 441     numthrottles = 0;
 442     maxthrottles = 0;
 443     throttles = (throttletab*) 0;
 444 
 445     /* Handle command-line arguments. */
 446     parse_args( argc, argv );
 447 
 448     if ( ! debug )
 449         {
 450         /* Daemonize - make ourselves a subprocess. */
 451 #ifdef HAVE_DAEMON
 452         if ( daemon( 1, 1 ) < 0 )
 453             {
 454             syslog( LOG_CRIT, "daemon - %m" );
 455             exit( 1 );
 456             }
 457 #else /* HAVE_DAEMON */
 458         switch ( fork() )
 459             {
 460             case 0:
 461             break;
 462             case -1:
 463             syslog( LOG_CRIT, "fork - %m" );
 464             exit( 1 );
 465             default:
 466             exit( 0 );
 467             }
 468 #ifdef HAVE_SETSID
 469         (void) setsid();
 470 #endif /* HAVE_SETSID */
 471 #endif /* HAVE_DAEMON */
 472         }
 473     else
 474         {
 475         /* Even if we don't daemonize, we still want to disown our parent
 476         ** process.
 477         */
 478 #ifdef HAVE_SETSID
 479         (void) setsid();
 480 #endif /* HAVE_SETSID */
 481         }
 482 
 483     parent = getpid(); /* Make multiserv children aware of our PID. */
 484 
 485     if ( configfile != (char*) 0 )
 486         read_config( configfile );
 487 
 488     /* Handle the throttlefile if it's a regular file */
 489     if ( throttlefile != (char*) 0 )
 490         fclose(read_throttlefile( throttlefile, fopen( throttlefile, "r" ) ));
 491 
 492     /* Open the syslog now, after the current server has been determined from
 493     ** having parsed the arguments and config file.
 494     */
 495     openlog( pident, LOG_NDELAY|LOG_PID, LOG_FACILITY );
 496 
 497     /* Read zone info now, in case we chroot(). */
 498     tzset();
 499 
 500     /* Look up hostname now, in case we chroot(). */
 501     lookup_hostname( &sa4, sizeof(sa4), &gotv4, &sa6, sizeof(sa6), &gotv6 );
 502     if ( ! ( gotv4 || gotv6 ) )
 503         {
 504         syslog( LOG_ERR, "can't find any valid address" );
 505         (void) fprintf( stderr, "%s: can't find any valid address\n", argv0 );
 506         exit( 1 );
 507         }
 508 
 509     /* If we're root and we're going to become another user, get the uid/gid
 510     ** now.
 511     */
 512     if ( getuid() == 0 )
 513         {
 514         pwd = getpwnam( user );
 515         if ( pwd == (struct passwd*) 0 )
 516             {
 517             syslog( LOG_CRIT, "unknown user - '%.80s'", user );
 518             (void) fprintf( stderr, "%s: unknown user - '%s'\n", argv0, user );
 519             exit( 1 );
 520             }
 521         uid = pwd->pw_uid;
 522         gid = pwd->pw_gid;
 523         }
 524 
 525     /* Log file. */
 526     if ( logfile != (char*) 0 )
 527         {
 528         if ( strcmp( logfile, "/dev/null" ) == 0 )
 529             {
 530             no_log = 1;
 531             logfp = (FILE*) 0;
 532             }
 533         else if ( strcmp( logfile, "-" ) == 0 )
 534             {
 535             if ( ! debug )
 536                 {
 537                 syslog( LOG_CRIT, "tried to use stdout as log without debug" );
 538                 (void) fprintf( stderr,
 539                                 "tried to use stdout as log without debug" );
 540                 exit( 1 );
 541                 }
 542             logfp = stdout;
 543             }
 544         else
 545             {
 546             logfp = fopen( logfile, "a" );
 547             retchmod = chmod( logfile, S_IRUSR|S_IWUSR );
 548             if ( logfp == (FILE*) 0 || retchmod != 0 )
 549                 {
 550                 syslog( LOG_CRIT, "%.80s - %m", logfile );
 551                 perror( logfile );
 552                 exit( 1 );
 553                 }
 554             if ( logfile[0] != '/' )
 555                 {
 556                 syslog( LOG_WARNING, "logfile is not an absolute path, you "
 557                         "may not be able to re-open it" );
 558                 (void) fprintf( stderr, "%s: logfile is not an absolute path, "
 559                                 "you may not be able to re-open it\n", argv0 );
 560                 }
 561             (void) fcntl( fileno( logfp ), F_SETFD, 1 );
 562             if ( getuid() == 0 )
 563                 {
 564                 /* If we are root then we chown the log file to the user we'll
 565                 ** be switching to.
 566                 */
 567                 if ( fchown( fileno( logfp ), uid, gid ) < 0 )
 568                     {
 569                     syslog( LOG_WARNING, "fchown logfile - %m" );
 570                     perror( "fchown logfile" );
 571                     }
 572                 }
 573             }
 574         }
 575     else
 576         logfp = (FILE*) 0;
 577 
 578     /* Switch directories if requested. */
 579     if ( dir != (char*) 0 )
 580         {
 581         if ( chdir( dir ) < 0 )
 582             {
 583             syslog( LOG_CRIT, "chdir - %m" );
 584             perror( "chdir" );
 585             exit( 1 );
 586             }
 587         }
 588 #ifdef USE_USER_DIR
 589     else if ( getuid() == 0 )
 590         {
 591         /* No explicit directory was specified, we're root, and the
 592         ** USE_USER_DIR option is set - switch to the specified user's
 593         ** home dir.
 594         */
 595         if ( chdir( pwd->pw_dir ) < 0 )
 596             {
 597             syslog( LOG_CRIT, "chdir - %m" );
 598             perror( "chdir" );
 599             exit( 1 );
 600             }
 601         }
 602 #endif /* USE_USER_DIR */
 603 
 604     /* Get current directory. */
 605     (void) getcwd( cwd, sizeof(cwd) - 1 );
 606     if ( cwd[strlen( cwd ) - 1] != '/' )
 607         (void) strcat( cwd, "/" );
 608 
 609     if ( multiserv < 2 && pidfile != (char*) 0 )
 610         {
 611         /* Write the PID file, if we are a parent process. */
 612         FILE* pidfp = fopen( pidfile, "w" );
 613         if ( pidfp == (FILE*) 0 )
 614             {
 615             syslog( LOG_CRIT, "%.80s - %m", pidfile );
 616             exit( 1 );
 617             }
 618         (void) fprintf( pidfp, "%d\n", (int) getpid() );
 619         (void) fclose( pidfp );
 620         }
 621 
 622     /* Become the multiserv parent, endlessly waiting for children to die. */
 623     if ( multiserv == 1 )
 624         {
 625         /* Set up to catch signals. (multiserv propagation) */
 626         (void) signal( SIGTERM, propagate_handle );
 627         (void) signal( SIGINT, propagate_handle );
 628         (void) signal( SIGHUP, propagate_handle );
 629         (void) signal( SIGUSR1, propagate_handle );
 630         (void) signal( SIGUSR2, propagate_handle );
 631 
 632         while ( 1 )
 633             {
 634             int cstatus;
 635             pid_t cpid;
 636 
 637             if ( ( cpid = wait( &cstatus ) ) < 0 && errno == ECHILD )
 638                 {
 639                 syslog( LOG_ERR, "could not find any multiserv children" );
 640                 exit( 1 );
 641                 }
 642             if ( WIFEXITED(cstatus) && WEXITSTATUS(cstatus) > 0 )
 643                 {
 644                 syslog( LOG_ERR,
 645                     "child process %d returned non-zero exit code %d",
 646                     (int) cpid, cstatus );
 647                 kill( -eldest_child, SIGTERM );
 648                 }
 649             }
 650         }
 651 
 652     /* Initialize the fdwatch package.  Have to do this before chroot,
 653     ** if /dev/poll is used.
 654     */
 655     max_connects = fdwatch_get_nfiles();
 656     if ( max_connects < 0 )
 657         {
 658         syslog( LOG_CRIT, "fdwatch initialization failure" );
 659         exit( 1 );
 660         }
 661     max_connects -= SPARE_FDS;
 662 
 663     /* Chroot if requested. */
 664     if ( do_chroot )
 665         {
 666         if ( chroot( cwd ) < 0 )
 667             {
 668             syslog( LOG_CRIT, "chroot - %m" );
 669             perror( "chroot" );
 670             exit( 1 );
 671             }
 672         /* If we're logging and the logfile's pathname begins with the
 673         ** chroot tree's pathname, then elide the chroot pathname so
 674         ** that the logfile pathname still works from inside the chroot
 675         ** tree.
 676         */
 677         if ( logfile != (char*) 0 && strcmp( logfile, "-" ) != 0 )
 678             {
 679             if ( strncmp( logfile, cwd, strlen( cwd ) ) == 0 )
 680                 {
 681                 (void) ol_strcpy( logfile, &logfile[strlen( cwd ) - 1] );
 682                 /* (We already guaranteed that cwd ends with a slash, so
 683                 ** leaving that slash in logfile makes it an absolute pathname
 684                 ** within the chroot tree.)
 685                 */
 686                 }
 687             else
 688                 {
 689                 syslog( LOG_WARNING, "logfile is not within the chroot tree, "
 690                         "you will not be able to re-open it" );
 691                 (void) fprintf( stderr, "%s: logfile is not within the chroot "
 692                                 "tree, you will not be able to re-open it\n",
 693                                 argv0 );
 694                 }
 695             }
 696         (void) strcpy( cwd, "/" );
 697         /* Always chdir to / after a chroot. */
 698         if ( chdir( cwd ) < 0 )
 699             {
 700             syslog( LOG_CRIT, "chroot chdir - %m" );
 701             perror( "chroot chdir" );
 702             exit( 1 );
 703             }
 704         }
 705 
 706     /* Switch directories again if requested. */
 707     if ( data_dir != (char*) 0 )
 708         {
 709         if ( chdir( data_dir ) < 0 )
 710             {
 711             syslog( LOG_CRIT, "data_dir chdir - %m" );
 712             perror( "data_dir chdir" );
 713             exit( 1 );
 714             }
 715         }
 716 
 717     if ( ! debug )
 718         {
 719         /* We're not going to use stdin stdout or stderr from here on, so close
 720         ** them to save file descriptors.
 721         */
 722         (void) fclose( stdin );
 723         (void) fclose( stdout );
 724         (void) fclose( stderr );
 725         }
 726 
 727     /* Set up to catch signals. */
 728     (void) signal( SIGTERM, handle_term );
 729     (void) signal( SIGINT, handle_term );
 730     (void) signal( SIGCHLD, handle_chld );
 731     (void) signal( SIGPIPE, SIG_IGN );          /* get EPIPE instead */
 732     (void) signal( SIGHUP, handle_hup );
 733     (void) signal( SIGUSR1, handle_usr1 );
 734     (void) signal( SIGUSR2, handle_usr2 );
 735     (void) signal( SIGALRM, handle_alrm );
 736 
 737     got_hup = 0;
 738     got_usr1 = 0;
 739     watchdog_flag = 0;
 740     (void) alarm( OCCASIONAL_TIME * 3 );
 741 
 742     /* Initialize the timer package. */
 743     tmr_init();
 744 
 745     /* Initialize the HTTP layer.  Got to do this before giving up root,
 746     ** so that we can bind to a privileged port.
 747     */
 748     hs = httpd_initialize(
 749         hostname,
 750         gotv4 ? &sa4 : (httpd_sockaddr*) 0, gotv6 ? &sa6 : (httpd_sockaddr*) 0,
 751         port, cgi_pattern, cgi_limit, charset, p3p, max_age, cwd, no_log,
 752         logfp, no_symlink_check, do_vhost, do_global_passwd, url_pattern,
 753         local_pattern, no_empty_referrers, do_fwdhdr, cgi_wrap, mime_encf,
 754         mime_typf );
 755     if ( hs == (httpd_server*) 0 )
 756         exit( 1 );
 757 
 758     /* Set up the multiserv parent-check timer. */
 759     if ( multiserv == 2 )
 760         {
 761         if ( tmr_create( (struct timeval*) 0, mspoll,
 762                          JunkClientData, 1000L, 1 ) == (Timer*) 0 )
 763             {
 764             syslog( LOG_CRIT, "tmr_create(multiserv) failed" );
 765             exit( 1 );
 766             }
 767         }
 768     /* Set up the occasional timer. */
 769     if ( tmr_create( (struct timeval*) 0, occasional,
 770                      JunkClientData, OCCASIONAL_TIME * 1000L, 1 )
 771          == (Timer*) 0 )
 772         {
 773         syslog( LOG_CRIT, "tmr_create(occasional) failed" );
 774         exit( 1 );
 775         }
 776     /* Set up the idle timer. */
 777     if ( tmr_create( (struct timeval*) 0, idle,
 778                      JunkClientData, 5 * 1000L, 1 ) == (Timer*) 0 )
 779         {
 780         syslog( LOG_CRIT, "tmr_create(idle) failed" );
 781         exit( 1 );
 782         }
 783     if ( numthrottles > 0 )
 784         {
 785         /* Set up the throttles timer. */
 786         if ( tmr_create( (struct timeval*) 0, update_throttles,
 787                          JunkClientData, THROTTLE_TIME * 1000L, 1 )
 788              == (Timer*) 0 )
 789             {
 790             syslog( LOG_CRIT, "tmr_create(update_throttles) failed" );
 791             exit( 1 );
 792             }
 793         }
 794 #ifdef STATS_TIME
 795     /* Set up the stats timer. */
 796     if ( tmr_create( (struct timeval*) 0, show_stats,
 797                      JunkClientData, STATS_TIME * 1000L, 1 )
 798          == (Timer*) 0 )
 799         {
 800         syslog( LOG_CRIT, "tmr_create(show_stats) failed" );
 801         exit( 1 );
 802         }
 803 #endif /* STATS_TIME */
 804     start_time = stats_time = time( (time_t*) 0 );
 805     stats_connections = 0;
 806     stats_bytes = 0;
 807     stats_simultaneous = 0;
 808 
 809     /* If we're root, try to become someone else. */
 810     if ( getuid() == 0 )
 811         {
 812         /* Set aux groups to null. */
 813         if ( setgroups( 0, (const gid_t*) 0 ) < 0 )
 814             {
 815             syslog( LOG_CRIT, "setgroups - %m" );
 816             exit( 1 );
 817             }
 818         /* Set primary group. */
 819         if ( setgid( gid ) < 0 )
 820             {
 821             syslog( LOG_CRIT, "setgid - %m" );
 822             exit( 1 );
 823             }
 824         /* Try setting aux groups correctly - not critical if this fails. */
 825         if ( initgroups( user, gid ) < 0 )
 826             syslog( LOG_WARNING, "initgroups - %m" );
 827 #ifdef HAVE_SETLOGIN
 828         /* Set login name. */
 829         (void) setlogin( user );
 830 #endif /* HAVE_SETLOGIN */
 831         /* Set uid. */
 832         if ( setuid( uid ) < 0 )
 833             {
 834             syslog( LOG_CRIT, "setuid - %m" );
 835             exit( 1 );
 836             }
 837         /* Check for unnecessary security exposure. */
 838         if ( ! do_chroot )
 839             syslog(
 840                 LOG_WARNING,
 841                 "started as root without requesting chroot(), warning only" );
 842         }
 843 
 844     /* Initialize our connections table. */
 845     connects = NEW( connecttab, max_connects );
 846     if ( connects == (connecttab*) 0 )
 847         {
 848         syslog( LOG_CRIT, "out of memory allocating a connecttab" );
 849         exit( 1 );
 850         }
 851     for ( cnum = 0; cnum < max_connects; ++cnum )
 852         {
 853         connects[cnum].conn_state = CNST_FREE;
 854         connects[cnum].next_free_connect = cnum + 1;
 855         connects[cnum].hc = (httpd_conn*) 0;
 856         }
 857     connects[max_connects - 1].next_free_connect = -1;  /* end of link list */
 858     first_free_connect = 0;
 859     num_connects = 0;
 860     httpd_conn_count = 0;
 861 
 862     if ( hs != (httpd_server*) 0 )
 863         {
 864         if ( hs->listen4_fd != -1 )
 865             fdwatch_add_fd( hs->listen4_fd, (void*) 0, FDW_READ );
 866         if ( hs->listen6_fd != -1 )
 867             fdwatch_add_fd( hs->listen6_fd, (void*) 0, FDW_READ );
 868         }
 869 
 870     /* Main loop. */
 871     tmr_prepare_timeval( &tv );
 872     while ( ( ! terminate ) || num_connects > 0 )
 873         {
 874         /* Do we need to re-open the log file? */
 875         if ( got_hup )
 876             {
 877             re_open_logfile();
 878             got_hup = 0;
 879             }
 880 
 881         /* Do the fd watch. */
 882         num_ready = fdwatch( tmr_mstimeout( &tv ) );
 883         if ( num_ready < 0 )
 884             {
 885             if ( errno == EINTR || errno == EAGAIN )
 886                 continue;       /* try again */
 887             syslog( LOG_ERR, "fdwatch - %m" );
 888             exit( 1 );
 889             }
 890         tmr_prepare_timeval( &tv );
 891 
 892         if ( num_ready == 0 )
 893             {
 894             /* No fd's are ready - run the timers. */
 895             tmr_run( &tv );
 896             continue;
 897             }
 898 
 899         /* Is it a new connection? */
 900         if ( hs != (httpd_server*) 0 && hs->listen6_fd != -1 &&
 901              fdwatch_check_fd( hs->listen6_fd ) )
 902             {
 903             if ( handle_newconnect( &tv, hs->listen6_fd ) )
 904                 /* Go around the loop and do another fdwatch, rather than
 905                 ** dropping through and processing existing connections.
 906                 ** New connections always get priority.
 907                 */
 908                 continue;
 909             }
 910         if ( hs != (httpd_server*) 0 && hs->listen4_fd != -1 &&
 911              fdwatch_check_fd( hs->listen4_fd ) )
 912             {
 913             if ( handle_newconnect( &tv, hs->listen4_fd ) )
 914                 /* Go around the loop and do another fdwatch, rather than
 915                 ** dropping through and processing existing connections.
 916                 ** New connections always get priority.
 917                 */
 918                 continue;
 919             }
 920 
 921         /* Find the connections that need servicing. */
 922         while ( ( c = (connecttab*) fdwatch_get_next_client_data() )
 923                 != (connecttab*) -1 )
 924             {
 925             if ( c == (connecttab*) 0 )
 926                 continue;
 927             hc = c->hc;
 928             if ( ! fdwatch_check_fd( hc->conn_fd ) )
 929                 /* Something went wrong. */
 930                 clear_connection( c, &tv );
 931             else
 932                 switch ( c->conn_state )
 933                     {
 934                     case CNST_READING: handle_read( c, &tv ); break;
 935                     case CNST_SENDING: handle_send( c, &tv ); break;
 936                     case CNST_LINGERING: handle_linger( c, &tv ); break;
 937                     }
 938             }
 939         tmr_run( &tv );
 940 
 941         if ( got_usr1 && ! terminate )
 942             {
 943             terminate = 1;
 944             if ( hs != (httpd_server*) 0 )
 945                 {
 946                 if ( hs->listen4_fd != -1 )
 947                     fdwatch_del_fd( hs->listen4_fd );
 948                 if ( hs->listen6_fd != -1 )
 949                     fdwatch_del_fd( hs->listen6_fd );
 950                 httpd_unlisten( hs );
 951                 }
 952             }
 953         }
 954 
 955     /* The main loop terminated. */
 956     shut_down();
 957     syslog( LOG_NOTICE, "exiting" );
 958     closelog();
 959     exit( 0 );
 960     }
 961 
 962 
 963 static void
 964 reset_args( void )
 965     {
 966     debug = 0;
 967     port = DEFAULT_PORT;
 968     dir = (char*) 0;
 969     data_dir = (char*) 0;
 970 #ifdef ALWAYS_CHROOT
 971     do_chroot = 1;
 972 #else /* ALWAYS_CHROOT */
 973     do_chroot = 0;
 974 #endif /* ALWAYS_CHROOT */
 975     no_log = 0;
 976     no_symlink_check = do_chroot;
 977 #ifdef ALWAYS_VHOST
 978     do_vhost = 1;
 979 #else /* ALWAYS_VHOST */
 980     do_vhost = 0;
 981 #endif /* ALWAYS_VHOST */
 982 #ifdef ALWAYS_FWDHDR
 983     do_fwdhdr = 1;
 984 #else /* ALWAYS_FWDHDR */
 985     do_fwdhdr = 0;
 986 #endif /* ALWAYS_FWDHDR */
 987 #ifdef ALWAYS_GLOBAL_PASSWD
 988     do_global_passwd = 1;
 989 #else /* ALWAYS_GLOBAL_PASSWD */
 990     do_global_passwd = 0;
 991 #endif /* ALWAYS_GLOBAL_PASSWD */
 992 #ifdef CGI_PATTERN
 993     cgi_pattern = CGI_PATTERN;
 994 #else /* CGI_PATTERN */
 995     cgi_pattern = (char*) 0;
 996 #endif /* CGI_PATTERN */
 997 #ifdef CGI_LIMIT
 998     cgi_limit = CGI_LIMIT;
 999 #else /* CGI_LIMIT */
1000     cgi_limit = 0;
1001 #endif /* CGI_LIMIT */
1002 #ifdef CGI_WRAP
1003     cgi_wrap = CGI_WRAP;
1004 #else
1005     cgi_wrap = (char*) 0;
1006 #endif
1007     mime_encf = (char*) 0;
1008     mime_typf = (char*) 0;
1009     url_pattern = (char*) 0;
1010     no_empty_referrers = 0;
1011     local_pattern = (char*) 0;
1012     throttlefile = (char*) 0;
1013     hostname = (char*) 0;
1014     logfile = (char*) 0;
1015     pidfile = (char*) 0;
1016     user = DEFAULT_USER;
1017     charset = DEFAULT_CHARSET;
1018     p3p = "";
1019     max_age = -1;
1020     }
1021 
1022 
1023 static void
1024 parse_args( int argc, char** argv )
1025     {
1026     int argn;
1027 
1028     configfile = (char*) 0;
1029     reset_args( );
1030 
1031     argn = 1;
1032     while ( argn < argc && argv[argn][0] == '-' )
1033         {
1034         if ( strcmp( argv[argn], "-V" ) == 0 )
1035             {
1036             (void) printf( "%s\n", SERVER_SOFTWARE );
1037             exit( 0 );
1038             }
1039         else if ( strcmp( argv[argn], "-C" ) == 0 && argn + 1 < argc )
1040             {
1041             ++argn;
1042             configfile = argv[argn];
1043             }
1044         else if ( strcmp( argv[argn], "-p" ) == 0 && argn + 1 < argc )
1045             {
1046             ++argn;
1047             port = (unsigned short) atoi( argv[argn] );
1048             }
1049         else if ( strcmp( argv[argn], "-d" ) == 0 && argn + 1 < argc )
1050             {
1051             ++argn;
1052             dir = argv[argn];
1053             }
1054         else if ( strcmp( argv[argn], "-r" ) == 0 )
1055             {
1056             do_chroot = 1;
1057             no_symlink_check = 1;
1058             }
1059         else if ( strcmp( argv[argn], "-nor" ) == 0 )
1060             {
1061             do_chroot = 0;
1062             no_symlink_check = 0;
1063             }
1064         else if ( strcmp( argv[argn], "-dd" ) == 0 && argn + 1 < argc )
1065             {
1066             ++argn;
1067             data_dir = argv[argn];
1068             }
1069         else if ( strcmp( argv[argn], "-s" ) == 0 )
1070             no_symlink_check = 0;
1071         else if ( strcmp( argv[argn], "-nos" ) == 0 )
1072             no_symlink_check = 1;
1073         else if ( strcmp( argv[argn], "-u" ) == 0 && argn + 1 < argc )
1074             {
1075             ++argn;
1076             user = argv[argn];
1077             }
1078         else if ( strcmp( argv[argn], "-c" ) == 0 && argn + 1 < argc )
1079             {
1080             ++argn;
1081             cgi_pattern = argv[argn];
1082             }
1083         else if ( strcmp( argv[argn], "-w" ) == 0 && argn + 1 < argc )
1084             {
1085             ++argn;
1086             cgi_wrap = argv[argn];
1087             }
1088         else if ( strcmp( argv[argn], "-t" ) == 0 && argn + 1 < argc )
1089             {
1090             ++argn;
1091             throttlefile = argv[argn];
1092             }
1093         else if ( strcmp( argv[argn], "-h" ) == 0 && argn + 1 < argc )
1094             {
1095             ++argn;
1096             hostname = argv[argn];
1097             }
1098         else if ( strcmp( argv[argn], "-l" ) == 0 && argn + 1 < argc )
1099             {
1100             ++argn;
1101             logfile = argv[argn];
1102             }
1103         else if ( strcmp( argv[argn], "-E" ) == 0 && argn + 1 < argc )
1104             {
1105             ++argn;
1106             mime_typf = argv[argn];
1107             }
1108         else if ( strcmp( argv[argn], "-e" ) == 0 && argn + 1 < argc )
1109             {
1110             ++argn;
1111             mime_encf = argv[argn];
1112             }
1113         else if ( strcmp( argv[argn], "-v" ) == 0 )
1114             do_vhost = 1;
1115         else if ( strcmp( argv[argn], "-nov" ) == 0 )
1116             do_vhost = 0;
1117         else if ( strcmp( argv[argn], "-f" ) == 0 )
1118             do_fwdhdr = 1;
1119         else if ( strcmp( argv[argn], "-nof" ) == 0 )
1120             do_fwdhdr = 0;
1121         else if ( strcmp( argv[argn], "-g" ) == 0 )
1122             do_global_passwd = 1;
1123         else if ( strcmp( argv[argn], "-nog" ) == 0 )
1124             do_global_passwd = 0;
1125         else if ( strcmp( argv[argn], "-i" ) == 0 && argn + 1 < argc )
1126             {
1127             ++argn;
1128             pidfile = argv[argn];
1129             }
1130         else if ( strcmp( argv[argn], "-T" ) == 0 && argn + 1 < argc )
1131             {
1132             ++argn;
1133             charset = argv[argn];
1134             }
1135         else if ( strcmp( argv[argn], "-P" ) == 0 && argn + 1 < argc )
1136             {
1137             ++argn;
1138             p3p = argv[argn];
1139             }
1140         else if ( strcmp( argv[argn], "-M" ) == 0 && argn + 1 < argc )
1141             {
1142             ++argn;
1143             max_age = atoi( argv[argn] );
1144             }
1145         else if ( strcmp( argv[argn], "-D" ) == 0 )
1146             debug = 1;
1147         else if ( strcmp( argv[argn], "--" ) == 0 )
1148             {
1149             ++argn;
1150             exargs = NEW( char*, (argc - argn) + 1 );
1151             memcpy( exargs + 1, &argv[argn], sizeof(char*) * ( argc - argn ) );
1152             argc = argn;
1153             break;
1154             }
1155         else
1156             usage();
1157         ++argn;
1158         }
1159     if ( argn != argc )
1160         usage();
1161     }
1162 
1163 
1164 static void
1165 usage( void )
1166     {
1167     (void) fprintf( stderr,
1168                     "usage:  %s [-C configfile] [-p port] [-d dir] [-r|-nor] "
1169                     "[-dd data_dir] [-s|-nos] [-v|-nov] [-g|-nog] [-f|-nof] "
1170                     "[-u user] [-c cgipat] [-w cgiwrap] [-E typefile] "
1171                     "[-e encfile] [-t throttles] [-h host] [-l logfile] "
1172                     "[-i pidfile] [-T charset] [-P P3P] [-M maxage] "
1173                     "[-V] [-D]\n",
1174         argv0 );
1175     exit( 1 );
1176     }
1177 
1178 
1179 static void
1180 fork_config( char* identifier )
1181     {
1182     int r;
1183     static int t = 0;
1184 
1185     r = fork( );
1186     if (r < 0)
1187         {
1188         (void) fprintf(
1189             stderr, "%s: unable to fork for multiserv %s\n",
1190             argv0, identifier );
1191         exit( 1 );
1192         }
1193     else if (r == 0)
1194         {
1195         /* Child process */
1196         multiserv = 2;
1197 
1198         strncat( pident, "-", sizeof( pident ) - strlen( pident ) - 1 );
1199         strncat( pident, identifier, sizeof( pident ) - strlen( pident ) - 1 );
1200 
1201         reset_args( );
1202         }
1203     else
1204         {
1205         if (t == 0)
1206             {
1207             eldest_child = r;
1208             }
1209 
1210         /* Move all children into process group of the eldest child, so that we
1211         ** can kill(); them later in order to propagate some signals.
1212         */
1213         if ( setpgid(r, eldest_child) < 0 )
1214             {
1215             syslog( LOG_CRIT, "setpgid - %m" );
1216             exit( 1 );
1217             }
1218 
1219         t++;
1220         }
1221     }
1222 
1223 
1224 #ifdef PROCESS_CONFIG_M4
1225 static void
1226 m4_interpose( FILE* conf )
1227     {
1228     int cpipe[2];
1229     int r;
1230 
1231     if ( pipe( cpipe ) < 0 ) goto interpose_fail;
1232 
1233     r = fork( );
1234     if ( r < 0 ) goto interpose_fail;
1235     else if ( r == 0 )
1236         {
1237         close( cpipe[0] );
1238         dup2( fileno(conf), 0 ); /* stdin -> config */
1239         dup2( cpipe[1], 1 ); /* stdout -> pipe */
1240         close( cpipe[1] );
1241         if (!exargs)
1242             execlp( "m4", "m4", NULL );
1243         else
1244             {
1245             exargs[0] = "m4";
1246             execvp( "m4", exargs );
1247             }
1248         (void) fprintf(
1249             stderr, "%s: could not find/execute m4\n", argv0 );
1250         kill( parent, SIGTERM );
1251         _exit( 1 );
1252         }
1253     else
1254         {
1255         close( cpipe[1] );
1256         dup2( cpipe[0], fileno(conf) );
1257         close( cpipe[0] );
1258         }
1259     return;
1260 
1261 interpose_fail:
1262     (void) fprintf(
1263         stderr, "%s: unable to interpose config with m4 process\n", argv0 );
1264     exit( 1 );
1265     }
1266 #endif
1267 
1268 
1269 static void
1270 read_config( char* filename )
1271     {
1272     FILE* fp;
1273     char line[4096];
1274     char* cp;
1275     char* cp2;
1276     char* name;
1277     char* value;
1278 
1279     fp = fopen( filename, "r" );
1280     if ( fp == (FILE*) 0 )
1281         {
1282         perror( filename );
1283         exit( 1 );
1284         }
1285 
1286    #ifdef PROCESS_CONFIG_M4
1287    m4_interpose(fp);
1288    #endif
1289 
1290     while ( fgets( line, sizeof(line), fp ) != (char*) 0 )
1291         {
1292         /* Trim comments. */
1293         if ( ( cp = strchr( line, '#' ) ) != (char*) 0 )
1294             *cp = '\0';
1295 
1296         /* Handle multiserv instances */
1297         if ( ( cp = strchr( line, '<' ) ) != (char*) 0 )
1298             {
1299             *cp++ = '\0';
1300 
1301             if ( ( cp2 = strchr( cp, '$' ) ) != (char*) 0 )
1302                 {
1303                 *cp2 = '\0';
1304                 if ( multiserv == 2 )
1305                     {
1306                     (void) fprintf(
1307                         stderr, "%s: attempted to create multiserv '%s' "
1308                         "without closing previous block\n", argv0, cp );
1309                     (void) kill( parent, SIGTERM );
1310                     exit( 1 );
1311                     }
1312 
1313                 multiserv = 3;
1314                 (void) signal( SIGURG, config_handle );
1315                 fork_config( cp );
1316 
1317                 while (multiserv == 3)
1318                     {
1319                     int cstatus;
1320                     pid_t cpid;
1321 
1322                     /* Reap children on failure */
1323                     if ( ( cpid = waitpid( -eldest_child,
1324                                            &cstatus, WNOHANG ) ) < 0
1325                          && errno == ECHILD )
1326                         {
1327                         exit( 1 );
1328                         }
1329                     if ( cpid
1330                          && WIFEXITED(cstatus)
1331                          && WEXITSTATUS(cstatus) > 0 )
1332                         {
1333                         (void) signal( SIGURG, SIG_IGN );
1334                         kill( -eldest_child, SIGTERM );
1335                         multiserv = 3;
1336                         }
1337                     }
1338                 }
1339             }
1340         if ( multiserv == 2 && ( cp = strchr( line, '$' ) ) != (char*) 0 )
1341             if ( strchr( ++cp, '>' ) != (char*) 0 ) break;
1342         if ( multiserv == 1 ) continue;
1343 
1344         /* Skip leading whitespace. */
1345         cp = line;
1346         cp += strspn( cp, " \t\n\r" );
1347 
1348         /* Split line into words. */
1349         while ( *cp != '\0' )
1350             {
1351             /* Find next whitespace. */
1352             cp2 = cp + strcspn( cp, " \t\n\r" );
1353             /* Insert EOS and advance next-word pointer. */
1354             while ( *cp2 == ' ' || *cp2 == '\t'
1355                     || *cp2 == '\n' || *cp2 == '\r' )
1356                 *cp2++ = '\0';
1357             /* Split into name and value. */
1358             name = cp;
1359             value = strchr( name, '=' );
1360             if ( value != (char*) 0 )
1361                 *value++ = '\0';
1362             /* XXX: printf( "[%d %d %s] %s=%s\n" ,
1363                multiserv, getpid( ), pident, name, value ); */
1364             /* Interpret. */
1365             if ( strcasecmp( name, "debug" ) == 0 )
1366                 {
1367                 no_value_required( name, value );
1368                 debug = 1;
1369                 }
1370             else if ( strcasecmp( name, "port" ) == 0 )
1371                 {
1372                 value_required( name, value );
1373                 port = (unsigned short) atoi( value );
1374                 }
1375             else if ( strcasecmp( name, "dir" ) == 0 )
1376                 {
1377                 value_required( name, value );
1378                 dir = e_strdup( value );
1379                 }
1380             else if ( strcasecmp( name, "chroot" ) == 0 )
1381                 {
1382                 no_value_required( name, value );
1383                 do_chroot = 1;
1384                 no_symlink_check = 1;
1385                 }
1386             else if ( strcasecmp( name, "nochroot" ) == 0 )
1387                 {
1388                 no_value_required( name, value );
1389                 do_chroot = 0;
1390                 no_symlink_check = 0;
1391                 }
1392             else if ( strcasecmp( name, "data_dir" ) == 0 )
1393                 {
1394                 value_required( name, value );
1395                 data_dir = e_strdup( value );
1396                 }
1397             else if ( strcasecmp( name, "nosymlinkcheck" ) == 0 )
1398                 {
1399                 no_value_required( name, value );
1400                 no_symlink_check = 1;
1401                 }
1402             else if ( strcasecmp( name, "symlinkcheck" ) == 0 )
1403                 {
1404                 no_value_required( name, value );
1405                 no_symlink_check = 0;
1406                 }
1407             else if ( strcasecmp( name, "user" ) == 0 )
1408                 {
1409                 value_required( name, value );
1410                 user = e_strdup( value );
1411                 }
1412             else if ( strcasecmp( name, "cgipat" ) == 0 )
1413                 {
1414                 value_required( name, value );
1415                 cgi_pattern = e_strdup( value );
1416                 }
1417             else if ( strcasecmp( name, "cgiwrap" ) == 0 )
1418                 {
1419                 value_required( name, value );
1420                 cgi_wrap = e_strdup( value );
1421                 }
1422             else if ( strcasecmp( name, "cgilimit" ) == 0 )
1423                 {
1424                 value_required( name, value );
1425                 cgi_limit = atoi( value );
1426                 }
1427             else if ( strcasecmp( name, "typefile" ) == 0 )
1428                 {
1429                 value_required( name, value );
1430                 mime_typf = e_strdup( value );
1431                 }
1432             else if ( strcasecmp( name, "encfile" ) == 0 )
1433                 {
1434                 value_required( name, value );
1435                 mime_encf = e_strdup( value );
1436                 }
1437             else if ( strcasecmp( name, "urlpat" ) == 0 )
1438                 {
1439                 value_required( name, value );
1440                 url_pattern = e_strdup( value );
1441                 }
1442             else if ( strcasecmp( name, "noemptyreferers" ) == 0 ||
1443                       strcasecmp( name, "noemptyreferrers" ) == 0 )
1444                 {
1445                 no_value_required( name, value );
1446                 no_empty_referrers = 1;
1447                 }
1448             else if ( strcasecmp( name, "localpat" ) == 0 )
1449                 {
1450                 value_required( name, value );
1451                 local_pattern = e_strdup( value );
1452                 }
1453             else if ( strcasecmp( name, "throttles" ) == 0 )
1454                 {
1455                 value_required( name, value );
1456                 if ( strchr( value, '{' ) )
1457                     (void)read_throttlefile( filename, fp );
1458                 else
1459                     throttlefile = e_strdup( value );
1460                 }
1461             else if ( strcasecmp( name, "host" ) == 0 )
1462                 {
1463                 value_required( name, value );
1464                 hostname = e_strdup( value );
1465                 }
1466             else if ( strcasecmp( name, "logfile" ) == 0 )
1467                 {
1468                 value_required( name, value );
1469                 logfile = e_strdup( value );
1470                 }
1471             else if ( strcasecmp( name, "vhost" ) == 0 )
1472                 {
1473                 no_value_required( name, value );
1474                 do_vhost = 1;
1475                 }
1476             else if ( strcasecmp( name, "novhost" ) == 0 )
1477                 {
1478                 no_value_required( name, value );
1479                 do_vhost = 0;
1480                 }
1481             else if ( strcasecmp( name, "fwdhdr" ) == 0 )
1482                 {
1483                 no_value_required( name, value );
1484                 do_fwdhdr = 1;
1485                 }
1486             else if ( strcasecmp( name, "nofwdhdr" ) == 0 )
1487                 {
1488                 no_value_required( name, value );
1489                 do_fwdhdr = 0;
1490                 }
1491             else if ( strcasecmp( name, "globalpasswd" ) == 0 )
1492                 {
1493                 no_value_required( name, value );
1494                 do_global_passwd = 1;
1495                 }
1496             else if ( strcasecmp( name, "noglobalpasswd" ) == 0 )
1497                 {
1498                 no_value_required( name, value );
1499                 do_global_passwd = 0;
1500                 }
1501             else if ( strcasecmp( name, "pidfile" ) == 0 )
1502                 {
1503                 value_required( name, value );
1504                 pidfile = e_strdup( value );
1505                 }
1506             else if ( strcasecmp( name, "charset" ) == 0 )
1507                 {
1508                 value_required( name, value );
1509                 charset = e_strdup( value );
1510                 }
1511             else if ( strcasecmp( name, "p3p" ) == 0 )
1512                 {
1513                 value_required( name, value );
1514                 p3p = e_strdup( value );
1515                 }
1516             else if ( strcasecmp( name, "max_age" ) == 0 )
1517                 {
1518                 value_required( name, value );
1519                 max_age = atoi( value );
1520                 }
1521             else
1522                 {
1523                 (void) fprintf(
1524                     stderr, "%s: unknown config option '%s'\n", argv0, name );
1525                 exit( 1 );
1526                 }
1527 
1528             /* Advance to next word. */
1529             cp = cp2;
1530             cp += strspn( cp, " \t\n\r" );
1531             }
1532         }
1533 
1534     if (multiserv == 2) (void) kill( parent, SIGURG );
1535     (void) fclose( fp );
1536     }
1537 
1538 
1539 static void
1540 value_required( char* name, char* value )
1541     {
1542     if ( value == (char*) 0 )
1543         {
1544         (void) fprintf(
1545             stderr, "%s: value required for %s option\n", argv0, name );
1546         exit( 1 );
1547         }
1548     }
1549 
1550 
1551 static void
1552 no_value_required( char* name, char* value )
1553     {
1554     if ( value != (char*) 0 )
1555         {
1556         (void) fprintf(
1557             stderr, "%s: no value required for %s option\n",
1558             argv0, name );
1559         exit( 1 );
1560         }
1561     }
1562 
1563 
1564 static char*
1565 e_strdup( char* oldstr )
1566     {
1567     char* newstr;
1568 
1569     newstr = strdup( oldstr );
1570     if ( newstr == (char*) 0 )
1571         {
1572         (void) fprintf( stderr, "%s: out of memory copying a string\n",
1573                         argv0 );
1574         exit( 1 );
1575         }
1576     return newstr;
1577     }
1578 
1579 
1580 static void
1581 lookup_hostname( httpd_sockaddr* sa4P, size_t sa4_len, int* gotv4P,
1582                  httpd_sockaddr* sa6P, size_t sa6_len, int* gotv6P )
1583     {
1584 #ifdef USE_IPV6
1585 
1586     struct addrinfo hints;
1587     char portstr[10];
1588     int gaierr;
1589     struct addrinfo* ai;
1590     struct addrinfo* ai2;
1591     struct addrinfo* aiv6;
1592     struct addrinfo* aiv4;
1593 
1594     (void) memset( &hints, 0, sizeof(hints) );
1595     hints.ai_family = PF_UNSPEC;
1596     hints.ai_flags = AI_PASSIVE;
1597     hints.ai_socktype = SOCK_STREAM;
1598     (void) snprintf( portstr, sizeof(portstr), "%d", (int) port );
1599     if ( (gaierr = getaddrinfo( hostname, portstr, &hints, &ai )) != 0 )
1600         {
1601         syslog(
1602             LOG_CRIT, "getaddrinfo %.80s - %.80s",
1603             hostname, gai_strerror( gaierr ) );
1604         (void) fprintf(
1605             stderr, "%s: getaddrinfo %s - %s\n",
1606             argv0, hostname, gai_strerror( gaierr ) );
1607         exit( 1 );
1608         }
1609 
1610     /* Find the first IPv6 and IPv4 entries. */
1611     aiv6 = (struct addrinfo*) 0;
1612     aiv4 = (struct addrinfo*) 0;
1613     for ( ai2 = ai; ai2 != (struct addrinfo*) 0; ai2 = ai2->ai_next )
1614         {
1615         switch ( ai2->ai_family )
1616             {
1617             case AF_INET6:
1618             if ( aiv6 == (struct addrinfo*) 0 )
1619                 aiv6 = ai2;
1620             break;
1621             case AF_INET:
1622             if ( aiv4 == (struct addrinfo*) 0 )
1623                 aiv4 = ai2;
1624             break;
1625             }
1626         }
1627 
1628     if ( aiv6 == (struct addrinfo*) 0 )
1629         *gotv6P = 0;
1630     else
1631         {
1632         if ( sa6_len < aiv6->ai_addrlen )
1633             {
1634             syslog(
1635                 LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)",
1636                 hostname, (unsigned long) sa6_len,
1637                 (unsigned long) aiv6->ai_addrlen );
1638             exit( 1 );
1639             }
1640         (void) memset( sa6P, 0, sa6_len );
1641         (void) memmove( sa6P, aiv6->ai_addr, aiv6->ai_addrlen );
1642         *gotv6P = 1;
1643         }
1644 
1645     if ( aiv4 == (struct addrinfo*) 0 )
1646         *gotv4P = 0;
1647     else
1648         {
1649         if ( sa4_len < aiv4->ai_addrlen )
1650             {
1651             syslog(
1652                 LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)",
1653                 hostname, (unsigned long) sa4_len,
1654                 (unsigned long) aiv4->ai_addrlen );
1655             exit( 1 );
1656             }
1657         (void) memset( sa4P, 0, sa4_len );
1658         (void) memmove( sa4P, aiv4->ai_addr, aiv4->ai_addrlen );
1659         *gotv4P = 1;
1660         }
1661 
1662     freeaddrinfo( ai );
1663 
1664 #else /* USE_IPV6 */
1665 
1666     struct hostent* he;
1667 
1668     *gotv6P = 0;
1669 
1670     (void) memset( sa4P, 0, sa4_len );
1671     sa4P->sa.sa_family = AF_INET;
1672     if ( hostname == (char*) 0 )
1673         sa4P->sa_in.sin_addr.s_addr = htonl( INADDR_ANY );
1674     else
1675         {
1676         sa4P->sa_in.sin_addr.s_addr = inet_addr( hostname );
1677         if ( (int) sa4P->sa_in.sin_addr.s_addr == -1 )
1678             {
1679             he = gethostbyname( hostname );
1680             if ( he == (struct hostent*) 0 )
1681                 {
1682 #ifdef HAVE_HSTRERROR
1683                 syslog(
1684                     LOG_CRIT, "gethostbyname %.80s - %.80s",
1685                     hostname, hstrerror( h_errno ) );
1686                 (void) fprintf(
1687                     stderr, "%s: gethostbyname %s - %s\n",
1688                     argv0, hostname, hstrerror( h_errno ) );
1689 #else /* HAVE_HSTRERROR */
1690                 syslog( LOG_CRIT, "gethostbyname %.80s failed", hostname );
1691                 (void) fprintf(
1692                     stderr, "%s: gethostbyname %s failed\n", argv0, hostname );
1693 #endif /* HAVE_HSTRERROR */
1694                 exit( 1 );
1695                 }
1696             if ( he->h_addrtype != AF_INET )
1697                 {
1698                 syslog( LOG_CRIT, "%.80s - non-IP network address", hostname );
1699                 (void) fprintf(
1700                     stderr, "%s: %s - non-IP network address\n",
1701                     argv0, hostname );
1702                 exit( 1 );
1703                 }
1704             (void) memmove(
1705                 &sa4P->sa_in.sin_addr.s_addr, he->h_addr, he->h_length );
1706             }
1707         }
1708     sa4P->sa_in.sin_port = htons( port );
1709     *gotv4P = 1;
1710 
1711 #endif /* USE_IPV6 */
1712     }
1713 
1714 
1715 static FILE*
1716 read_throttlefile( char* tf, FILE* fp )
1717     {
1718     char buf[5000];
1719     char* cp;
1720     int len;
1721     char pattern[5000];
1722     long max_limit, min_limit;
1723     struct timeval tv;
1724 
1725     if ( fp == (FILE*) 0 )
1726         {
1727         syslog( LOG_CRIT, "%.80s - %m", tf );
1728         perror( tf );
1729         exit( 1 );
1730         }
1731 
1732     (void) gettimeofday( &tv, (struct timezone*) 0 );
1733 
1734     while ( fgets( buf, sizeof(buf), fp ) != (char*) 0 )
1735         {
1736         /* Nuke comments. */
1737         cp = strchr( buf, '#' );
1738         if ( cp != (char*) 0 )
1739             *cp = '\0';
1740 
1741         /* Nuke trailing whitespace. */
1742         len = strlen( buf );
1743         while ( len > 0 &&
1744                 ( buf[len-1] == ' ' || buf[len-1] == '\t' ||
1745                   buf[len-1] == '\n' || buf[len-1] == '\r' ) )
1746             buf[--len] = '\0';
1747 
1748         /* Ignore empty lines. */
1749         if ( len == 0 )
1750             continue;
1751 
1752         /* Look for the end of an inline throttle block ({...})
1753         ** XXX: Find a cleaner way to do this.
1754         */
1755         if ( strchr( buf, '}' ) )
1756             break;
1757 
1758         /* Parse line. */
1759         if ( sscanf( buf, " %4900[^ \t] %ld-%ld",
1760                      pattern, &min_limit, &max_limit ) == 3 )
1761             {}
1762         else if ( sscanf( buf, " %4900[^ \t] %ld",
1763                           pattern, &max_limit ) == 2 )
1764             min_limit = 0;
1765         else
1766             {
1767             syslog( LOG_CRIT,
1768                 "unparsable line in %.80s - %.80s", tf, buf );
1769             (void) fprintf( stderr,
1770                 "%s: unparsable line in %.80s - %.80s\n",
1771                 argv0, tf, buf );
1772             continue;
1773             }
1774 
1775         /* Nuke any leading slashes in pattern. */
1776         if ( pattern[0] == '/' )
1777             (void) ol_strcpy( pattern, &pattern[1] );
1778         while ( ( cp = strstr( pattern, "|/" ) ) != (char*) 0 )
1779             (void) ol_strcpy( cp + 1, cp + 2 );
1780 
1781         /* Check for room in throttles. */
1782         if ( numthrottles >= maxthrottles )
1783             {
1784             if ( maxthrottles == 0 )
1785                 {
1786                 maxthrottles = 100;     /* arbitrary */
1787                 throttles = NEW( throttletab, maxthrottles );
1788                 }
1789             else
1790                 {
1791                 maxthrottles *= 2;
1792                 throttles = RENEW( throttles, throttletab, maxthrottles );
1793                 }
1794             if ( throttles == (throttletab*) 0 )
1795                 {
1796                 syslog( LOG_CRIT, "out of memory allocating a throttletab" );
1797                 (void) fprintf(
1798                     stderr, "%s: out of memory allocating a throttletab\n",
1799                     argv0 );
1800                 exit( 1 );
1801                 }
1802             }
1803 
1804         /* Add to table. */
1805         throttles[numthrottles].pattern = e_strdup( pattern );
1806         throttles[numthrottles].max_limit = max_limit;
1807         throttles[numthrottles].min_limit = min_limit;
1808         throttles[numthrottles].rate = 0;
1809         throttles[numthrottles].bytes_since_avg = 0;
1810         throttles[numthrottles].num_sending = 0;
1811 
1812         ++numthrottles;
1813         }
1814     return fp;
1815     }
1816 
1817 
1818 static void
1819 shut_down( void )
1820     {
1821     int cnum;
1822     struct timeval tv;
1823 
1824     (void) gettimeofday( &tv, (struct timezone*) 0 );
1825     logstats( &tv );
1826     for ( cnum = 0; cnum < max_connects; ++cnum )
1827         {
1828         if ( connects[cnum].conn_state != CNST_FREE )
1829             httpd_close_conn( connects[cnum].hc, &tv );
1830         if ( connects[cnum].hc != (httpd_conn*) 0 )
1831             {
1832             httpd_destroy_conn( connects[cnum].hc );
1833             free(connects[cnum].hc);
1834             --httpd_conn_count;
1835             connects[cnum].hc = (httpd_conn*) 0;
1836             }
1837         }
1838     if ( hs != (httpd_server*) 0 )
1839         {
1840         httpd_server* ths = hs;
1841         hs = (httpd_server*) 0;
1842         if ( ths->listen4_fd != -1 )
1843             fdwatch_del_fd( ths->listen4_fd );
1844         if ( ths->listen6_fd != -1 )
1845             fdwatch_del_fd( ths->listen6_fd );
1846         httpd_terminate( ths );
1847         }
1848     mmc_term();
1849     tmr_term();
1850     free(connects);
1851     if ( throttles != (throttletab*) 0 )
1852         free(throttles);
1853     }
1854 
1855 
1856 static int
1857 handle_newconnect( struct timeval* tvP, int listen_fd )
1858     {
1859     connecttab* c;
1860     ClientData client_data;
1861 
1862     /* This loops until the accept() fails, trying to start new
1863     ** connections as fast as possible so we don't overrun the
1864     ** listen queue.
1865     */
1866     for (;;)
1867         {
1868         /* Is there room in the connection table? */
1869         if ( num_connects >= max_connects )
1870             {
1871             /* Out of connection slots.  Run the timers, then the
1872             ** existing connections, and maybe we'll free up a slot
1873             ** by the time we get back here.
1874             */
1875             syslog( LOG_WARNING, "too many connections!" );
1876             tmr_run( tvP );
1877             return 0;
1878             }
1879         /* Get the first free connection entry off the free list. */
1880         if ( first_free_connect == -1
1881              || connects[first_free_connect].conn_state != CNST_FREE )
1882             {
1883             syslog( LOG_CRIT, "the connects free list is messed up" );
1884             exit( 1 );
1885             }
1886         c = &connects[first_free_connect];
1887         /* Make the httpd_conn if necessary. */
1888         if ( c->hc == (httpd_conn*) 0 )
1889             {
1890             c->hc = NEW( httpd_conn, 1 );
1891             if ( c->hc == (httpd_conn*) 0 )
1892                 {
1893                 syslog( LOG_CRIT, "out of memory allocating an httpd_conn" );
1894                 exit( 1 );
1895                 }
1896             c->hc->initialized = 0;
1897             ++httpd_conn_count;
1898             }
1899 
1900         /* Get the connection. */
1901         switch ( httpd_get_conn( hs, listen_fd, c->hc ) )
1902             {
1903             /* Some error happened.  Run the timers, then the
1904             ** existing connections.  Maybe the error will clear.
1905             */
1906             case GC_FAIL:
1907             tmr_run( tvP );
1908             return 0;
1909 
1910             /* No more connections to accept for now. */
1911             case GC_NO_MORE:
1912             return 1;
1913             }
1914         c->conn_state = CNST_READING;
1915         /* Pop it off the free list. */
1916         first_free_connect = c->next_free_connect;
1917         c->next_free_connect = -1;
1918         ++num_connects;
1919         client_data.p = c;
1920         c->active_at = tvP->tv_sec;
1921         c->wakeup_timer = (Timer*) 0;
1922         c->linger_timer = (Timer*) 0;
1923         c->restart_delay = 0;
1924         c->next_byte_index = 0;
1925         c->numtnums = 0;
1926 
1927         /* Set the connection file descriptor to no-delay mode. */
1928         httpd_set_ndelay( c->hc->conn_fd );
1929 
1930         fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ );
1931 
1932         ++stats_connections;
1933         if ( num_connects > stats_simultaneous )
1934             stats_simultaneous = num_connects;
1935         }
1936     }
1937 
1938 
1939 static void
1940 handle_read( connecttab* c, struct timeval* tvP )
1941     {
1942     int sz, rt;
1943     ClientData client_data;
1944     httpd_conn* hc = c->hc;
1945 
1946     /* If the connection has the indications of an on-going restart, we can
1947     ** just skip to the "start request" stage, since we already have the data.
1948     */
1949     if ( c->restart_delay > 0 ) goto req_restart;
1950 
1951     /* Is there room in our buffer to read more bytes? */
1952     if ( hc->read_idx >= hc->read_size )
1953         {
1954         if ( hc->read_size > 5000 )
1955             {
1956             httpd_send_err( hc, 400,
1957                             httpd_err400title, "", httpd_err400form, "" );
1958             finish_connection( c, tvP );
1959             return;
1960             }
1961         httpd_realloc_str(
1962             &hc->read_buf, &hc->read_size, hc->read_size + 1000 );
1963         }
1964 
1965     /* Read some more bytes. */
1966     sz = read(
1967         hc->conn_fd, &(hc->read_buf[hc->read_idx]),
1968         hc->read_size - hc->read_idx );
1969     if ( sz == 0 )
1970         {
1971         httpd_send_err( hc, 400,
1972                         httpd_err400title, "", httpd_err400form, "" );
1973         finish_connection( c, tvP );
1974         return;
1975         }
1976     if ( sz < 0 )
1977         {
1978         /* Ignore EINTR and EAGAIN.  Also ignore EWOULDBLOCK.  At first glance
1979         ** you would think that connections returned by fdwatch as readable
1980         ** should never give an EWOULDBLOCK; however, this apparently can
1981         ** happen if a packet gets garbled.
1982         */
1983         if ( errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK )
1984             return;
1985         httpd_send_err(
1986             hc, 400, httpd_err400title, "", httpd_err400form, "" );
1987         finish_connection( c, tvP );
1988         return;
1989         }
1990     hc->read_idx += sz;
1991     c->active_at = tvP->tv_sec;
1992 
1993     /* Do we have a complete request yet? */
1994     switch ( httpd_got_request( hc ) )
1995         {
1996         case GR_NO_REQUEST:
1997         return;
1998         case GR_BAD_REQUEST:
1999         httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" );
2000         finish_connection( c, tvP );
2001         return;
2002         }
2003 
2004     /* Yes.  Try parsing and resolving it. */
2005     if ( httpd_parse_request( hc ) < 0 )
2006         {
2007         finish_connection( c, tvP );
2008         return;
2009         }
2010 
2011     /* Check the throttle table */
2012     req_restart: ;
2013     if ( ! check_throttles( c ) )
2014         {
2015         rt = -503;
2016         }
2017     else
2018         {
2019         /* Start the connection going. */
2020         rt = httpd_start_request( hc, tvP );
2021         }
2022 
2023     if ( rt == -503 )
2024         {
2025         /* Schedule the request to be attempted again later, in the event of a
2026         ** "soft failure".
2027         */
2028 
2029         if ( ( c->restart_delay += MIN_RESTART_DELAY ) >= MAX_RESTART_DELAY )
2030             {
2031             httpd_send_err(
2032                 hc, 503,
2033                 httpd_err503title, "", httpd_err503form, hc->encodedurl );
2034             finish_connection( c, tvP );
2035             return;
2036             }
2037 
2038         syslog( LOG_NOTICE, "rescheduling request (%.80s) for %ldms due to "
2039                 "503 resource exhaustion",
2040                 httpd_ntoa( &c->hc->client_addr ), c->restart_delay );
2041 
2042         c->conn_state = CNST_RESTARTING;
2043         fdwatch_del_fd( hc->conn_fd );
2044         client_data.p = c;
2045 
2046         /* Re-use c->wakeup_timer so that clear_connection cancels it */
2047         if ( c->wakeup_timer != (Timer*) 0 )
2048             syslog( LOG_ERR, "replacing non-null wakeup_timer!" );
2049         c->wakeup_timer = tmr_create(
2050             tvP, wakeup_connection, client_data, c->restart_delay, 0 );
2051         if ( c->wakeup_timer == (Timer*) 0 )
2052             {
2053             syslog( LOG_CRIT, "tmr_create(wakeup_connection) failed" );
2054             exit( 1 );
2055             }
2056         return;
2057         }
2058     else if ( rt < 0 )
2059         {
2060         /* Something went wrong.  Close down the connection. */
2061         finish_connection( c, tvP );
2062         return;
2063         }
2064 
2065     c->restart_delay = 0;
2066 
2067     /* Fill in end_byte_index. */
2068     if ( hc->got_range )
2069         {
2070         c->next_byte_index = hc->first_byte_index;
2071         c->end_byte_index = hc->last_byte_index + 1;
2072         }
2073     else if ( hc->bytes_to_send < 0 )
2074         c->end_byte_index = 0;
2075     else
2076         c->end_byte_index = hc->bytes_to_send;
2077 
2078     /* Check if it's already handled. */
2079     if ( hc->file_address == (char*) 0 )
2080         {
2081         /* No file address means someone else is handling it. */
2082         int tind;
2083         for ( tind = 0; tind < c->numtnums; ++tind )
2084             throttles[c->tnums[tind]].bytes_since_avg += hc->bytes_sent;
2085         c->next_byte_index = hc->bytes_sent;
2086         finish_connection( c, tvP );
2087         return;
2088         }
2089     if ( c->next_byte_index >= c->end_byte_index )
2090         {
2091         /* There's nothing to send. */
2092         finish_connection( c, tvP );
2093         return;
2094         }
2095 
2096     /* Cool, we have a valid connection and a file to send to it. */
2097     c->conn_state = CNST_SENDING;
2098     c->started_at = tvP->tv_sec;
2099     c->wouldblock_delay = 0;
2100 
2101     fdwatch_del_fd( hc->conn_fd );
2102     fdwatch_add_fd( hc->conn_fd, c, FDW_WRITE );
2103     }
2104 
2105 
2106 static void
2107 handle_send( connecttab* c, struct timeval* tvP )
2108     {
2109     size_t max_bytes;
2110     int sz, coast;
2111     ClientData client_data;
2112     time_t elapsed;
2113     httpd_conn* hc = c->hc;
2114     int tind;
2115 
2116     if ( c->max_limit == THROTTLE_NOLIMIT )
2117         max_bytes = 1000000000L;
2118     else
2119         max_bytes = c->max_limit / 4;   /* send at most 1/4 seconds worth */
2120 
2121     /* Do we need to write the headers first? */
2122     if ( hc->responselen == 0 )
2123         {
2124         /* No, just write the file. */
2125         sz = write(
2126             hc->conn_fd, &(hc->file_address[c->next_byte_index]),
2127             MIN( c->end_byte_index - c->next_byte_index, (off_t)max_bytes ) );
2128         }
2129     else
2130         {
2131         /* Yes.  We'll combine headers and file into a single writev(),
2132         ** hoping that this generates a single packet.
2133         */
2134         struct iovec iv[2];
2135 
2136         iv[0].iov_base = hc->response;
2137         iv[0].iov_len = hc->responselen;
2138         iv[1].iov_base = &(hc->file_address[c->next_byte_index]);
2139         iv[1].iov_len = MIN( c->end_byte_index - c->next_byte_index,
2140                              (off_t)max_bytes );
2141         sz = writev( hc->conn_fd, iv, 2 );
2142         }
2143 
2144     if ( sz < 0 && errno == EINTR )
2145         return;
2146 
2147     if ( sz == 0 ||
2148          ( sz < 0 && ( errno == EWOULDBLOCK || errno == EAGAIN ) ) )
2149         {
2150         /* This shouldn't happen, but some kernels, e.g.
2151         ** SunOS 4.1.x, are broken and select() says that
2152         ** O_NDELAY sockets are always writable even when
2153         ** they're actually not.
2154         **
2155         ** Current workaround is to block sending on this
2156         ** socket for a brief adaptively-tuned period.
2157         ** Fortunately we already have all the necessary
2158         ** blocking code, for use with throttling.
2159         */
2160         c->wouldblock_delay += MIN_WOULDBLOCK_DELAY;
2161         c->conn_state = CNST_PAUSING;
2162         fdwatch_del_fd( hc->conn_fd );
2163         client_data.p = c;
2164         if ( c->wakeup_timer != (Timer*) 0 )
2165             syslog( LOG_ERR, "replacing non-null wakeup_timer!" );
2166         c->wakeup_timer = tmr_create(
2167             tvP, wakeup_connection, client_data, c->wouldblock_delay, 0 );
2168         if ( c->wakeup_timer == (Timer*) 0 )
2169             {
2170             syslog( LOG_CRIT, "tmr_create(wakeup_connection) failed" );
2171             exit( 1 );
2172             }
2173         return;
2174         }
2175 
2176     if ( sz < 0 )
2177         {
2178         /* Something went wrong, close this connection.
2179         **
2180         ** If it's just an EPIPE, don't bother logging, that
2181         ** just means the client hung up on us.
2182         **
2183         ** On some systems, write() occasionally gives an EINVAL.
2184         ** Dunno why, something to do with the socket going
2185         ** bad.  Anyway, we don't log those either.
2186         **
2187         ** And ECONNRESET isn't interesting either.
2188         */
2189         if ( errno != EPIPE && errno != EINVAL && errno != ECONNRESET )
2190             syslog( LOG_ERR, "write - %m sending %.80s", hc->encodedurl );
2191         clear_connection( c, tvP );
2192         return;
2193         }
2194 
2195     /* Ok, we wrote something. */
2196     c->active_at = tvP->tv_sec;
2197     /* Was this a headers + file writev()? */
2198     if ( hc->responselen > 0 )
2199         {
2200         /* Yes; did we write only part of the headers? */
2201         if ( (size_t) sz < hc->responselen )
2202             {
2203             /* Yes; move the unwritten part to the front of the buffer. */
2204             int newlen = hc->responselen - sz;
2205             (void) memmove( hc->response, &(hc->response[sz]), newlen );
2206             hc->responselen = newlen;
2207             sz = 0;
2208             }
2209         else
2210             {
2211             /* Nope, we wrote the full headers, so adjust accordingly. */
2212             sz -= hc->responselen;
2213             hc->responselen = 0;
2214             }
2215         }
2216     /* And update how much of the file we wrote. */
2217     c->next_byte_index += sz;
2218     c->hc->bytes_sent += sz;
2219     for ( tind = 0; tind < c->numtnums; ++tind )
2220         throttles[c->tnums[tind]].bytes_since_avg += sz;
2221 
2222     /* Are we done? */
2223     if ( c->next_byte_index >= c->end_byte_index )
2224         {
2225         /* This connection is finished! */
2226         finish_connection( c, tvP );
2227         return;
2228         }
2229 
2230     /* Tune the (blockheaded) wouldblock delay. */
2231     if ( c->wouldblock_delay > MIN_WOULDBLOCK_DELAY )
2232         c->wouldblock_delay -= MIN_WOULDBLOCK_DELAY;
2233 
2234     /* If we're throttling, check if we're sending too fast. */
2235     if ( c->max_limit != THROTTLE_NOLIMIT )
2236         {
2237         elapsed = tvP->tv_sec - c->started_at;
2238         if ( elapsed == 0 )
2239             elapsed = 1;        /* count at least one second */
2240         if ( c->hc->bytes_sent / elapsed > c->max_limit )
2241             {
2242             c->conn_state = CNST_PAUSING;
2243             fdwatch_del_fd( hc->conn_fd );
2244             /* How long should we wait to get back on schedule?  If less
2245             ** than a second (integer math rounding), use 1/2 second.
2246             */
2247             coast = c->hc->bytes_sent / c->max_limit - elapsed;
2248             client_data.p = c;
2249             if ( c->wakeup_timer != (Timer*) 0 )
2250                 syslog( LOG_ERR, "replacing non-null wakeup_timer!" );
2251             c->wakeup_timer = tmr_create(
2252                 tvP, wakeup_connection, client_data,
2253                 coast > 0 ? ( coast * 1000L ) : 500L, 0 );
2254             if ( c->wakeup_timer == (Timer*) 0 )
2255                 {
2256                 syslog( LOG_CRIT, "tmr_create(wakeup_connection) failed" );
2257                 exit( 1 );
2258                 }
2259             }
2260         }
2261     /* (No check on min_limit here, that only controls connection startups.) */
2262     }
2263 
2264 
2265 static void
2266 handle_linger( connecttab* c, struct timeval* tvP )
2267     {
2268     httpd_conn *hc = c->hc;
2269 
2270     c->conn_state = CNST_READING;
2271     c->next_byte_index = 0;
2272 
2273     if (c->linger_timer)
2274         tmr_reset(tvP, c->linger_timer);
2275 
2276     /* release file memory */
2277     if (hc->file_address)
2278         {
2279         mmc_unmap(hc->file_address, &hc->sb, tvP);
2280         hc->file_address = NULL;
2281         }
2282 
2283     /* release httpd_conn auxiliary memory */
2284     httpd_destroy_conn(hc);
2285 
2286     /* reinitialize httpd_conn */
2287     httpd_init_conn_mem(hc);
2288     httpd_init_conn_content(hc);
2289     }
2290 
2291 
2292 static int
2293 check_throttles( connecttab* c )
2294     {
2295     int tnum;
2296     long l;
2297 
2298     c->numtnums = 0;
2299     c->max_limit = c->min_limit = THROTTLE_NOLIMIT;
2300     for ( tnum = 0; tnum < numthrottles && c->numtnums < MAXTHROTTLENUMS;
2301           ++tnum )
2302         if ( match( throttles[tnum].pattern, c->hc->expnfilename ) )
2303             {
2304             /* If we're way over the limit, don't even start. */
2305             if ( throttles[tnum].rate > throttles[tnum].max_limit * 2 )
2306                 return 0;
2307             /* Also don't start if we're under the minimum. */
2308             if ( throttles[tnum].rate < throttles[tnum].min_limit )
2309                 return 0;
2310             if ( throttles[tnum].num_sending < 0 )
2311                 {
2312                 syslog( LOG_ERR, "throttle sending count was "
2313                         "negative - shouldn't happen!" );
2314                 throttles[tnum].num_sending = 0;
2315                 }
2316             c->tnums[c->numtnums++] = tnum;
2317             ++throttles[tnum].num_sending;
2318             l = throttles[tnum].max_limit / throttles[tnum].num_sending;
2319             if ( c->max_limit == THROTTLE_NOLIMIT )
2320                 c->max_limit = l;
2321             else
2322                 c->max_limit = MIN( c->max_limit, l );
2323             l = throttles[tnum].min_limit;
2324             if ( c->min_limit == THROTTLE_NOLIMIT )
2325                 c->min_limit = l;
2326             else
2327                 c->min_limit = MAX( c->min_limit, l );
2328             }
2329     return 1;
2330     }
2331 
2332 
2333 static void
2334 clear_throttles( connecttab* c, struct timeval* tvP )
2335     {
2336     int tind;
2337 
2338     (void) tvP; /* XXX: gcc */
2339 
2340     for ( tind = 0; tind < c->numtnums; ++tind )
2341         --throttles[c->tnums[tind]].num_sending;
2342     }
2343 
2344 
2345 static void
2346 update_throttles( ClientData client_data, struct timeval* nowP )
2347     {
2348     int tnum, tind;
2349     int cnum;
2350     connecttab* c;
2351     long l;
2352     double lastcalc;
2353     static struct timeval lastP = { .tv_sec  = 0, .tv_usec = 0 };
2354 
2355     (void) client_data; /* XXX: gcc */
2356 
2357     if ( lastP.tv_sec == 0 && lastP.tv_usec == 0 )
2358         {
2359         lastP.tv_sec  = nowP->tv_sec - THROTTLE_TIME;
2360         lastP.tv_usec = nowP->tv_usec;
2361         }
2362 
2363     /* Change in system time could cause massive differences between
2364     ** nowP and lastP, so we need to normalize it.  Changing the
2365     ** system time hopefully doesn't happen very often, so it doesnt
2366     ** matter if the actual value is incorrect just one time.
2367     */
2368     lastcalc = MAX( MIN( ( (double) ( nowP->tv_usec - lastP.tv_usec )
2369                            / 1000000.0
2370                            + (double) ( nowP->tv_sec - lastP.tv_sec ) ),
2371                          (double) THROTTLE_TIME ),
2372                     0.0001 ); /* Avoid divide by zero, but still accurate */
2373 
2374     /* Don't update throttles so frequently that they become meaningless.  The
2375     ** whole reason to have such a large default delay to begin with is for the
2376     ** accuracy it provides.
2377     */
2378     if ( lastcalc < (double) MIN_RESTART_DELAY / 1500.0 ) return;
2379 
2380     /* Update the last time with the current time now that we've made use of
2381     ** the time of the previous call already.
2382     */
2383     lastP = *nowP;
2384 
2385     /* Update the average sending rate for each throttle.  This is only used
2386     ** when new connections start up.
2387     */
2388     for ( tnum = 0; tnum < numthrottles; ++tnum )
2389         {
2390         if ( throttles[tnum].num_sending < 1 )
2391             {
2392             /* If nothing is being sent, the rate is always zero.  However,
2393             ** if a minimum throttle limit is in place, it always must be set
2394             ** above it at this point, otherwise the rate will get stuck below
2395             ** the minimum as no new connections can be initiated under that
2396             ** condition.
2397             */
2398             throttles[tnum].rate = throttles[tnum].min_limit > 0
2399                 ? throttles[tnum].min_limit + 1 : 0;
2400             }
2401         else
2402             {
2403             /* This calculation was originally done under the assumption that
2404             ** the time between calls of update_throttles would always be
2405             ** THROTTLE_TIME seconds, but has since been adapted to handle any
2406             ** interval shorter than THROTTLE_TIME, because restarting
2407             ** connections can trigger this function in order to prevent
2408             ** getting stuck above the request cancellation rate limit.
2409             **
2410             ** Most of the complication arises from the fact that the previous
2411             ** version of this code was able to work entirely with integer math
2412             ** because the amount of seconds between each call could never be
2413             ** below 1 second.
2414             */
2415             throttles[tnum].rate = (long) ( (
2416                 /* Original rate has greater weight */
2417                 2.0 * (double) throttles[tnum].rate
2418                 + ( (double) throttles[tnum].bytes_since_avg
2419                     / lastcalc )
2420             ) / 3.0 );
2421             throttles[tnum].bytes_since_avg = 0;
2422             }
2423         /* Log a warning message if necessary. */
2424         if ( throttles[tnum].rate > throttles[tnum].max_limit
2425              && throttles[tnum].num_sending != 0 )
2426             {
2427             if ( throttles[tnum].rate > throttles[tnum].max_limit * 2 )
2428                 syslog( LOG_NOTICE, "throttle #%d '%.80s' rate %ld greatly "
2429                         "exceeding limit %ld; %d sending",
2430                         tnum, throttles[tnum].pattern, throttles[tnum].rate,
2431                         throttles[tnum].max_limit,
2432                         throttles[tnum].num_sending );
2433             else
2434                 syslog( LOG_INFO, "throttle #%d '%.80s' rate %ld exceeding "
2435                         "limit %ld; %d sending", tnum, throttles[tnum].pattern,
2436                         throttles[tnum].rate, throttles[tnum].max_limit,
2437                         throttles[tnum].num_sending );
2438             }
2439         if ( throttles[tnum].rate < throttles[tnum].min_limit
2440              && throttles[tnum].num_sending != 0 )
2441             {
2442             syslog( LOG_NOTICE, "throttle #%d '%.80s' rate %ld lower than "
2443                     "minimum %ld; %d sending", tnum, throttles[tnum].pattern,
2444                     throttles[tnum].rate, throttles[tnum].min_limit,
2445                     throttles[tnum].num_sending );
2446             }
2447         }
2448 
2449     /* Now update the sending rate on all the currently-sending connections,
2450     ** redistributing it evenly.
2451     */
2452     for ( cnum = 0; cnum < max_connects; ++cnum )
2453         {
2454         c = &connects[cnum];
2455         if ( c->conn_state == CNST_SENDING || c->conn_state == CNST_PAUSING )
2456             {
2457             c->max_limit = THROTTLE_NOLIMIT;
2458             for ( tind = 0; tind < c->numtnums; ++tind )
2459                 {
2460                 tnum = c->tnums[tind];
2461                 l = throttles[tnum].max_limit / throttles[tnum].num_sending;
2462                 if ( c->max_limit == THROTTLE_NOLIMIT )
2463                     c->max_limit = l;
2464                 else
2465                     c->max_limit = MIN( c->max_limit, l );
2466                 }
2467             }
2468         }
2469     }
2470 
2471 
2472 static void
2473 force_update_throttles( void )
2474     {
2475     struct timeval tv;
2476 
2477     tmr_prepare_timeval( &tv );
2478     update_throttles( JunkClientData, &tv );
2479     }
2480 
2481 
2482 static void
2483 finish_connection( connecttab* c, struct timeval* tvP )
2484     {
2485     /* If we haven't actually sent the buffered response yet, do so now. */
2486     httpd_write_response( c->hc );
2487 
2488     /* And clear. */
2489     clear_connection( c, tvP );
2490     }
2491 
2492 
2493 static void
2494 clear_connection( connecttab* c, struct timeval* tvP )
2495     {
2496     ClientData client_data;
2497 
2498     if ( c->wakeup_timer != (Timer*) 0 )
2499         {
2500         tmr_cancel( c->wakeup_timer );
2501         c->wakeup_timer = 0;
2502         }
2503 
2504     /* This is our version of Apache's lingering_close() routine, which is
2505     ** their version of the often-broken SO_LINGER socket option.  For why
2506     ** this is necessary, see http://www.apache.org/docs/misc/fin_wait_2.html
2507     ** What we do is delay the actual closing for a few seconds, while reading
2508     ** any bytes that come over the connection.  However, we don't want to do
2509     ** this unless it's necessary, because it ties up a connection slot and
2510     ** file descriptor which means our maximum connection-handling rate
2511     ** is lower.  So, elsewhere we set a flag when we detect the few
2512     ** circumstances that make a lingering close necessary.  If the flag
2513     ** isn't set we do the real close now.
2514     */
2515     if ( c->conn_state == CNST_LINGERING )
2516         {
2517         /* If we were already lingering, shut down for real. */
2518         tmr_cancel( c->linger_timer );
2519         c->linger_timer = (Timer*) 0;
2520         c->hc->should_linger = 0;
2521         }
2522     if ( c->hc->should_linger )
2523         {
2524         if ( c->conn_state != CNST_PAUSING )
2525             fdwatch_del_fd( c->hc->conn_fd );
2526         c->conn_state = CNST_LINGERING;
2527         fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ );
2528         client_data.p = c;
2529         if (c->linger_timer)
2530             tmr_reset(tvP,  c->linger_timer);
2531         else
2532             {
2533             c->linger_timer = tmr_create(tvP, linger_clear_connection,
2534                                          client_data, LINGER_TIME, 0);
2535             if (!c->linger_timer)
2536                 {
2537                 syslog(LOG_CRIT, "tmr_create(linger_clear_connection) failed");
2538                 exit(1);
2539                 }
2540             }
2541         }
2542     else
2543         really_clear_connection( c, tvP );
2544     }
2545 
2546 
2547 static void
2548 really_clear_connection( connecttab* c, struct timeval* tvP )
2549     {
2550     stats_bytes += c->hc->bytes_sent;
2551     if ( c->conn_state != CNST_PAUSING )
2552         fdwatch_del_fd( c->hc->conn_fd );
2553     httpd_close_conn( c->hc, tvP );
2554     clear_throttles( c, tvP );
2555     if ( c->linger_timer != (Timer*) 0 )
2556         {
2557         tmr_cancel( c->linger_timer );
2558         c->linger_timer = 0;
2559         }
2560     c->conn_state = CNST_FREE;
2561     c->next_free_connect = first_free_connect;
2562     first_free_connect = c - connects;  /* division by sizeof is implied */
2563     --num_connects;
2564     }
2565 
2566 
2567 static void
2568 idle( ClientData client_data, struct timeval* nowP )
2569     {
2570     int cnum;
2571     connecttab* c;
2572 
2573     (void) client_data; /* XXX: gcc */
2574 
2575     for ( cnum = 0; cnum < max_connects; ++cnum )
2576         {
2577         c = &connects[cnum];
2578         switch ( c->conn_state )
2579             {
2580             case CNST_READING:
2581             case CNST_RESTARTING:
2582             if ( nowP->tv_sec - c->active_at >= IDLE_READ_TIMELIMIT )
2583                 {
2584                 syslog( LOG_INFO,
2585                     "%.80s connection timed out reading",
2586                     httpd_ntoa( &c->hc->client_addr ) );
2587                 httpd_send_err(
2588                     c->hc, 408, httpd_err408title, "", httpd_err408form, "" );
2589                 finish_connection( c, nowP );
2590                 }
2591             break;
2592             case CNST_SENDING:
2593             case CNST_PAUSING:
2594             if ( nowP->tv_sec - c->active_at >= IDLE_SEND_TIMELIMIT )
2595                 {
2596                 syslog( LOG_INFO,
2597                     "%.80s connection timed out sending",
2598                     httpd_ntoa( &c->hc->client_addr ) );
2599                 clear_connection( c, nowP );
2600                 }
2601             break;
2602             }
2603         }
2604     }
2605 
2606 
2607 static void
2608 wakeup_connection( ClientData client_data, struct timeval* nowP )
2609     {
2610     connecttab* c;
2611 
2612     (void) nowP; /* XXX: gcc */
2613 
2614     c = (connecttab*) client_data.p;
2615     c->wakeup_timer = (Timer*) 0;
2616     if ( c->conn_state == CNST_PAUSING )
2617         {
2618         c->conn_state = CNST_SENDING;
2619         fdwatch_add_fd( c->hc->conn_fd, c, FDW_WRITE );
2620         }
2621     else if ( c->conn_state == CNST_RESTARTING )
2622         {
2623         /* Don't want to enter a loop constantly waiting for throttle rate to
2624         ** be valid when we're backing off due to a throttle limit.
2625         */
2626         force_update_throttles( );
2627 
2628         c->conn_state = CNST_READING;
2629         fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ );
2630         }
2631     }
2632 
2633 
2634 static void
2635 linger_clear_connection( ClientData client_data, struct timeval* nowP )
2636     {
2637     connecttab* c;
2638 
2639     c = (connecttab*) client_data.p;
2640     c->linger_timer = (Timer*) 0;
2641     really_clear_connection( c, nowP );
2642     }
2643 
2644 
2645 static void
2646 mspoll( ClientData client_data, struct timeval* nowP )
2647     {
2648     static pid_t parent2, parent = 0;
2649 
2650     (void) client_data; /* XXX: gcc */
2651     (void) nowP; /* XXX: gcc */
2652 
2653     parent2 = getppid( );
2654     if ( ( parent2 == parent && parent2 != 1 ) || parent == 0 )
2655         parent = parent2;
2656     else
2657         {
2658         shut_down( );
2659         syslog( LOG_NOTICE, "parent has exit, so stopping gracefully" );
2660         closelog( );
2661         exit( 0 );
2662         }
2663     }
2664 
2665 
2666 static void
2667 occasional( ClientData client_data, struct timeval* nowP )
2668     {
2669     (void) client_data; /* XXX: gcc */
2670 
2671     mmc_cleanup( nowP );
2672     tmr_cleanup();
2673     watchdog_flag = 1;          /* let the watchdog know that we are alive */
2674     }
2675 
2676 
2677 #ifdef STATS_TIME
2678 static void
2679 show_stats( ClientData client_data, struct timeval* nowP )
2680     {
2681     (void) client_data; /* XXX: gcc */
2682 
2683     logstats( nowP );
2684     }
2685 #endif /* STATS_TIME */
2686 
2687 
2688 /* Generate debugging statistics syslog messages for all packages. */
2689 static void
2690 logstats( struct timeval* nowP )
2691     {
2692     struct timeval tv;
2693     time_t now;
2694     long up_secs, stats_secs;
2695 
2696     if ( nowP == (struct timeval*) 0 )
2697         {
2698         (void) gettimeofday( &tv, (struct timezone*) 0 );
2699         nowP = &tv;
2700         }
2701     now = nowP->tv_sec;
2702     up_secs = now - start_time;
2703     stats_secs = now - stats_time;
2704     if ( stats_secs == 0 )
2705         stats_secs = 1; /* fudge */
2706     stats_time = now;
2707     syslog( LOG_NOTICE,
2708         "up %ld seconds, stats for %ld seconds:", up_secs, stats_secs );
2709 
2710     xthttpd_logstats( stats_secs );
2711     httpd_logstats( stats_secs );
2712     mmc_logstats( stats_secs );
2713     fdwatch_logstats( stats_secs );
2714     tmr_logstats( stats_secs );
2715     }
2716 
2717 
2718 /* Generate debugging statistics syslog message. */
2719 static void
2720 xthttpd_logstats( long secs )
2721     {
2722     if ( secs > 0 )
2723         syslog( LOG_NOTICE,
2724                 "  xthttpd - %ld connections (%g/sec), %d max simultaneous, "
2725                 "%lld bytes (%g/sec), %d httpd_conns allocated",
2726             stats_connections, (float) stats_connections / secs,
2727             stats_simultaneous, (long long) stats_bytes,
2728             (float) stats_bytes / secs, httpd_conn_count );
2729     stats_connections = 0;
2730     stats_bytes = 0;
2731     stats_simultaneous = 0;
2732     }

/* [previous][next][first][last][top][bottom][index][help] */