예제 #1
0
/* Give the process appropriate permissions for a child process.
   This is like user_access, but you can't get back to make_access.  */
void
child_access (void)
{
#ifdef  GETLOADAVG_PRIVILEGED

  if (!access_inited)
    abort ();

  /* Set both the real and effective UID and GID to the user's.
     They cannot be changed back to make's.  */

#ifndef HAVE_SETREUID
  if (setuid (user_uid) < 0)
    pfatal_with_name ("child_access: setuid");
#else
  if (setreuid (user_uid, user_uid) < 0)
    pfatal_with_name ("child_access: setreuid");
#endif

#ifndef HAVE_SETREGID
  if (setgid (user_gid) < 0)
    pfatal_with_name ("child_access: setgid");
#else
  if (setregid (user_gid, user_gid) < 0)
    pfatal_with_name ("child_access: setregid");
#endif

  log_access (_("Child access"));

#endif  /* GETLOADAVG_PRIVILEGED */
}
예제 #2
0
// Process a command.
int pcom(char **cmd, int size)
{
  int cret = -2;
  struct passwd *pw = getpwuid(geteuid());

  if (strcmp(*cmd, "greet") == 0)
    cret = greet(pw->pw_name, pw->pw_dir);

  else if (strcmp(*cmd, "log_access") == 0)
    cret = log_access(pw->pw_name);

  else if (strcmp(*cmd, "cla") == 0)
    cret = cla();

  else if (strcmp(*cmd, "cls") == 0)
    cret = cls();

  if (cret == -2) {
    fprintf(stderr, "Unrecognized command '%s'.\n", *cmd);
    return -2;
  }

  bzero(*cmd, size);

  if (cret < 0) {
    strcpy(*cmd, "failure");
    return -1;
  }
  
  strcpy(*cmd, "success");
  return 0;
}
예제 #3
0
파일: nope.c 프로젝트: janus/nope.c
void process(int fd,  fd_set *pMaster, struct sockaddr_in *clientaddr){
    printf("accept request, fd is %d, pid is %d\n", fd, getpid());

    int status = 200;

	char buf[1024];
	u_int numchars;
	char method[255];
	char url[1024];
	size_t i, j;
    int client=fd;

	numchars = getLine(client, buf, sizeof(buf));

	i = 0; j = 0;
	while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
	{
		method[i] = buf[j];
		i++; j++;

	}
	method[i] = '\0';

	if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
	{
		unimplemented(client);
		return;
	}


	i = 0;
	while (ISspace(buf[j]) && (j < sizeof(buf)))
		j++;
	while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
	{
		url[i] = buf[j];
		i++; j++;
	}
	url[i] = '\0';

	Request request;
	request.client=client;
	request.reqStr=url;
	request.method=method;

	/*server(client,url,method);*/

	server(request);

	close(client);

	http_request req;
	strncpy(req.filename, url, sizeof req.filename);
    log_access(status, clientaddr, &req);
}
예제 #4
0
// handle one HTTP request/response transaction
void process(int fd, struct sockaddr_in *clientaddr){
	printf("Accept Request, fd is %d, pid is %d\n", fd, getpid());

	http_request req;
	memset(&req, 0, sizeof(req));
	if (-1 == parse_request(fd, &req)) {
		//bad socket, don't record
		printf("Invalid socket!\n");
		return;
	}
	
	//update recent visited client information
	char browser[10][100];
	char ip[10][100];
	memset(browser, 0, 1000);
	memset(ip, 0, 1000);
	strcpy(browser[0], browser_name[req.browser_index]);
	strcpy(ip[0], inet_ntoa(clientaddr->sin_addr));
	update(browser, ip);
	

	struct stat sbuf;
	int status = 200; //server status init as 200
	int ffd = open(req.filename, O_RDONLY, 0);
	if(ffd <= 0){
		// detect 404 error and print error log
		status = 404;
		client_error(fd, status, "Not Found", "File Not Found");
		
	} else {
		// get descriptor status
		fstat(ffd, &sbuf);
		if(S_ISREG(sbuf.st_mode)){
			// server serves static content
			serve_static(fd, ffd, &req, status);
		} else if(S_ISDIR(sbuf.st_mode)){
			// server handle directory request
			handle_directory_request(fd, status, browser, ip);
		} else {
			// detect 400 error and print error log
			status = 400;
			client_error(fd, status, "Bad Request", "Bad Request");
		}
		close(ffd);
	}
	
	// print log/status on the terminal
	log_access(status, clientaddr, &req);
}
예제 #5
0
/* Give the process appropriate permissions for access to
   make data (i.e., the load average).  */
void
make_access (void)
{
#ifdef  GETLOADAVG_PRIVILEGED

  if (!access_inited)
    init_access ();

  if (current_access == make)
    return;

  /* See comments in user_access, above.  */

#ifdef  HAVE_SETEUID
  if (seteuid (make_uid) < 0)
    pfatal_with_name ("make_access: seteuid");
#else
#ifndef HAVE_SETREUID
  if (setuid (make_uid) < 0)
    pfatal_with_name ("make_access: setuid");
#else
  if (setreuid (user_uid, make_uid) < 0)
    pfatal_with_name ("make_access: setreuid");
#endif
#endif

#ifdef  HAVE_SETEGID
  if (setegid (make_gid) < 0)
    pfatal_with_name ("make_access: setegid");
#else
#ifndef HAVE_SETREGID
  if (setgid (make_gid) < 0)
    pfatal_with_name ("make_access: setgid");
#else
  if (setregid (user_gid, make_gid) < 0)
    pfatal_with_name ("make_access: setregid");
#endif
#endif

  current_access = make;

  log_access (_("Make access"));

#endif  /* GETLOADAVG_PRIVILEGED */
}
예제 #6
0
static void
init_access (void)
{
#ifndef VMS
  user_uid = getuid ();
  user_gid = getgid ();

  make_uid = geteuid ();
  make_gid = getegid ();

  /* Do these ever fail?  */
  if (user_uid == -1 || user_gid == -1 || make_uid == -1 || make_gid == -1)
    pfatal_with_name ("get{e}[gu]id");

  log_access (_("Initialized access"));

  current_access = make;
#endif
}
예제 #7
0
static void log_connection(struct web_client *w, const char *msg) {
    log_access("%llu: %d '[%s]:%s' '%s'", w->id, gettid(), w->client_ip, w->client_port, msg);
}
예제 #8
0
void free_request(request ** list_head_addr, request * req)
{
	if (req->buffer_end)
		return;

	dequeue(list_head_addr, req);	/* dequeue from ready or block list */

	if (req->buffer_end)
		FD_CLR(req->fd, &block_write_fdset);
	else {
		switch (req->status) {
		case PIPE_WRITE:
		case WRITE:
			FD_CLR(req->fd, &block_write_fdset);
			break;
		case PIPE_READ:
			FD_CLR(req->data_fd, &block_read_fdset);
			break;
		case BODY_WRITE:
			FD_CLR(req->post_data_fd, &block_write_fdset);
			break;
		default:
			FD_CLR(req->fd, &block_read_fdset);
		}
	}

	if (req->logline)			/* access log */
		log_access(req);

	if (req->data_mem)
		munmap(req->data_mem, req->filesize);
	
	if (req->data_fd)
		close(req->data_fd);
	
	if (req->response_status >= 400)
		status.errors++;

	if ((req->keepalive == KA_ACTIVE) &&
		(req->response_status < 400) &&
		(++req->kacount < ka_max)) {

		request *conn;

		conn = new_request();
		conn->fd = req->fd;
		conn->status = READ_HEADER;
		conn->header_line = conn->client_stream;
		conn->time_last = time_counter;
		conn->kacount = req->kacount;
#ifdef SERVER_SSL
		conn->ssl = req->ssl; /*MN*/
#endif /*SERVER_SSL*/
		
		/* we don't need to reset the fd parms for conn->fd because
		   we already did that for req */
		/* for log file and possible use by CGI programs */
		
		strcpy(conn->remote_ip_addr, req->remote_ip_addr);

		/* for possible use by CGI programs */
		conn->remote_port = req->remote_port;
		
		if (req->local_ip_addr)
			conn->local_ip_addr = strdup(req->local_ip_addr);

		status.requests++;
		
		if (conn->kacount + 1 == ka_max)
			SQUASH_KA(conn);
				
		conn->pipeline_start = req->client_stream_pos - 
								req->pipeline_start;
		
		if (conn->pipeline_start) {
			memcpy(conn->client_stream,
				req->client_stream + req->pipeline_start,
				conn->pipeline_start);			
			enqueue(&request_ready, conn);				
		} else
			block_request(conn);
	} else{
		if (req->fd != -1) {
			status.connections--;
			safe_close(req->fd);
		}
		req->fd = -1;
#ifdef SERVER_SSL
		SSL_free(req->ssl);
#endif /*SERVER_SSL*/
	}

	if (req->cgi_env) {
		int i = COMMON_CGI_VARS;
		req->cgi_env[req->cgi_env_index]=0;
		while (req->cgi_env[i])
		{
			free(req->cgi_env[i++]);
		}
		free(req->cgi_env);
	}
	if (req->pathname)
		free(req->pathname);
	if (req->path_info)
		free(req->path_info);
	if (req->path_translated)
		free(req->path_translated);
	if (req->script_name)
		free(req->script_name);
	if (req->query_string)
		free(req->query_string);
	if (req->local_ip_addr)
		free(req->local_ip_addr);
/*
 *	need to clean up if anything went wrong
 */
	if (req->post_file_name) {
		unlink(req->post_file_name);
		free(req->post_file_name);
		close(req->post_data_fd);
		req->post_data_fd = -1;
		req->post_file_name = NULL;
	}

	enqueue(&request_free, req);	/* put request on the free list */

	return;
}
예제 #9
0
/* Give the process appropriate permissions for access to
   user data (i.e., to stat files, or to spawn a child process).  */
void
user_access (void)
{
#ifdef  GETLOADAVG_PRIVILEGED

  if (!access_inited)
    init_access ();

  if (current_access == user)
    return;

  /* We are in "make access" mode.  This means that the effective user and
     group IDs are those of make (if it was installed setuid or setgid).
     We now want to set the effective user and group IDs to the real IDs,
     which are the IDs of the process that exec'd make.  */

#ifdef  HAVE_SETEUID

  /* Modern systems have the seteuid/setegid calls which set only the
     effective IDs, which is ideal.  */

  if (seteuid (user_uid) < 0)
    pfatal_with_name ("user_access: seteuid");

#else   /* Not HAVE_SETEUID.  */

#ifndef HAVE_SETREUID

  /* System V has only the setuid/setgid calls to set user/group IDs.
     There is an effective ID, which can be set by setuid/setgid.
     It can be set (unless you are root) only to either what it already is
     (returned by geteuid/getegid, now in make_uid/make_gid),
     the real ID (return by getuid/getgid, now in user_uid/user_gid),
     or the saved set ID (what the effective ID was before this set-ID
     executable (make) was exec'd).  */

  if (setuid (user_uid) < 0)
    pfatal_with_name ("user_access: setuid");

#else   /* HAVE_SETREUID.  */

  /* In 4BSD, the setreuid/setregid calls set both the real and effective IDs.
     They may be set to themselves or each other.  So you have two alternatives
     at any one time.  If you use setuid/setgid, the effective will be set to
     the real, leaving only one alternative.  Using setreuid/setregid, however,
     you can toggle between your two alternatives by swapping the values in a
     single setreuid or setregid call.  */

  if (setreuid (make_uid, user_uid) < 0)
    pfatal_with_name ("user_access: setreuid");

#endif  /* Not HAVE_SETREUID.  */
#endif  /* HAVE_SETEUID.  */

#ifdef  HAVE_SETEGID
  if (setegid (user_gid) < 0)
    pfatal_with_name ("user_access: setegid");
#else
#ifndef HAVE_SETREGID
  if (setgid (user_gid) < 0)
    pfatal_with_name ("user_access: setgid");
#else
  if (setregid (make_gid, user_gid) < 0)
    pfatal_with_name ("user_access: setregid");
#endif
#endif

  current_access = user;

  log_access (_("User access"));

#endif  /* GETLOADAVG_PRIVILEGED */
}
예제 #10
0
int thread_main(server_decl_t * srv)
{
     ci_connection_t con;
     char clientname[CI_MAXHOSTNAMELEN + 1];
     int ret, request_status = CI_NO_STATUS;
     int keepalive_reqs;
//***********************
     thread_signals(0);
//*************************
     srv->srv_id = getpid();    //Setting my pid ...

     for (;;) {
          /*
             If we must shutdown IMEDIATELLY it is time to leave the server
             else if we are going to shutdown GRACEFULLY we are going to die 
             only if there are not any accepted connections
           */
          if (child_data->to_be_killed == IMMEDIATELY) {
               srv->running = 0;
               return 1;
          }

          if ((ret = get_from_queue(con_queue, &con)) == 0) {
               if (child_data->to_be_killed) {
                    srv->running = 0;
                    return 1;
               }
               ret = wait_for_queue(con_queue);
               continue;
          }

          if (ret < 0) {        //An error has occured
               ci_debug_printf(1,
                               "Fatal Error!!! Error getting a connection from connections queue!!!\n");
               break;
          }

          ci_thread_mutex_lock(&counters_mtx);  /*Update counters as soon as possible */
          (child_data->freeservers)--;
          (child_data->usedservers)++;
          ci_thread_mutex_unlock(&counters_mtx);

          ci_netio_init(con.fd);
          ret = 1;
          if (srv->current_req == NULL)
               srv->current_req = newrequest(&con);
          else
               ret = recycle_request(srv->current_req, &con);

          if (srv->current_req == NULL || ret == 0) {
               ci_sockaddr_t_to_host(&(con.claddr), clientname,
                                     CI_MAXHOSTNAMELEN);
               ci_debug_printf(1, "Request from %s denied...\n", clientname);
               hard_close_connection((&con));
               goto end_of_main_loop_thread;    /*The request rejected. Log an error and continue ... */
          }

          keepalive_reqs = 0;
          do {
               if (MAX_KEEPALIVE_REQUESTS > 0
                   && keepalive_reqs >= MAX_KEEPALIVE_REQUESTS)
                    srv->current_req->keepalive = 0;    /*do not keep alive connection */
               if (child_data->to_be_killed)    /*We are going to die do not keep-alive */
                    srv->current_req->keepalive = 0;

               if ((request_status = process_request(srv->current_req)) == CI_NO_STATUS) {
                    ci_debug_printf(5,
                                    "Process request timeout or interrupted....\n");
                    ci_request_reset(srv->current_req);
                    break;
               }
               srv->served_requests++;
               srv->served_requests_no_reallocation++;
               keepalive_reqs++;

               /*Increase served requests. I dont like this. The delay is small but I don't like... */
               ci_thread_mutex_lock(&counters_mtx);
               (child_data->requests)++;
               ci_thread_mutex_unlock(&counters_mtx);

               log_access(srv->current_req, request_status);
//             break; //No keep-alive ......

               if (child_data->to_be_killed  == IMMEDIATELY)
                    break;      //Just exiting the keep-alive loop

               /*if we are going to term gracefully we will try to keep our promice for
                 keepalived request....
                */
               if (child_data->to_be_killed  == GRACEFULLY && 
                   srv->current_req->keepalive == 0)
                    break;

               ci_debug_printf(8, "Keep-alive:%d\n",
                               srv->current_req->keepalive);
               if (srv->current_req->keepalive && keepalive_request(srv->current_req)) {
                   ci_debug_printf(8,
                                   "Server %d going to serve new request from client (keep-alive) \n",
                                   srv->srv_id);
               }
               else
                    break;
          } while (1);

          if (srv->current_req) {
               if (request_status != CI_OK || child_data->to_be_killed) {
                    hard_close_connection(srv->current_req->connection);
               }
               else {
                    close_connection(srv->current_req->connection);
               }
          }
          if (srv->served_requests_no_reallocation >
              MAX_REQUESTS_BEFORE_REALLOCATE_MEM) {
               ci_debug_printf(5,
                               "Max requests reached, reallocate memory and buffers .....\n");
               ci_request_destroy(srv->current_req);
               srv->current_req = NULL;
               srv->served_requests_no_reallocation = 0;
          }


        end_of_main_loop_thread:
          ci_thread_mutex_lock(&counters_mtx);
          (child_data->freeservers)++;
          (child_data->usedservers)--;
          ci_thread_mutex_unlock(&counters_mtx);
          ci_thread_cond_signal(&free_server_cond);

     }
     srv->running = 0;
     return 0;
}
예제 #11
0
int thread_main(server_decl_t *srv){
     ci_connection_t con;
     ci_thread_mutex_t cont_mtx;
     char clientname[CI_MAXHOSTNAMELEN+1];
     int max,ret,request_status=0;
     request_t *tmp;
//***********************
     thread_signals();
//*************************
     srv->srv_id=getpid(); //Setting my pid ...
     srv->srv_pthread=pthread_self();

     for(;;){
	  if(child_data->to_be_killed)
	       return; //Exiting thread.....
	  
	  if((ret=get_from_queue(con_queue,&con))==0){
	       wait_for_queue(con_queue); //It is better that the wait_for_queue to be 
                                          //moved into the get_from_queue 
	       continue; 
	  }
	  ci_thread_mutex_lock(&counters_mtx);
	  (child_data->freeservers)--;
	  (child_data->usedservers)++;
	  ci_thread_mutex_unlock(&counters_mtx);


	  if(ret<0){ //An error has occured
	       debug_printf(1,"Error getting from connections queue\n");
	       break;
	  }

/*
	  icap_addrtohost(&(con.claddr.sin_addr),clientname, CI_MAXHOSTNAMELEN);
	  debug_printf(1,"Client name: %s server %d\n",clientname,srv->srv_id);
*/
	  icap_netio_init(con.fd);

	  if(srv->current_req==NULL)
	       srv->current_req=newrequest(&con);
	  else
	       recycle_request(srv->current_req,&con);

	  do{
	       if((request_status=process_request(srv->current_req))<0){
		    debug_printf(5,"Process request timeout or interupted....\n");
		    reset_request(srv->current_req);
		    break;//
	       }
	       srv->served_requests++;
	       srv->served_requests_no_reallocation++;

    /*Increase served requests. I dont like this. The delay is small but I don't like...*/
	       ci_thread_mutex_lock(&counters_mtx); 
	       (child_data->requests)++; 
	       ci_thread_mutex_unlock(&counters_mtx);



	       log_access(srv->current_req,request_status);
//	       break; //No keep-alive ......

	       if(child_data->to_be_killed)
		    return; //Exiting thread.....
	      
               debug_printf(8,"Keep-alive:%d\n",srv->current_req->keepalive); 
	       if(srv->current_req->keepalive && check_for_keepalive_data(srv->current_req->connection->fd)){
		    reset_request(srv->current_req);
		    debug_printf(8,"Server %d going to serve new request from client(keep-alive) \n",
				 srv->srv_id);
	       }
	       else
		    break;
	  }while(1);
	  
	  if(srv->current_req){
	       if(request_status<0)
		    hard_close_connection(srv->current_req->connection);
	       else
		    close_connection(srv->current_req->connection);
	  }
	  if(srv->served_requests_no_reallocation > MAX_REQUESTS_BEFORE_REALLOCATE_MEM){
	       debug_printf(5,"Max requests reached, reallocate memory and buffers .....\n");
	       destroy_request(srv->current_req);
	       srv->current_req=NULL;
	       srv->served_requests_no_reallocation=0;
	  }

	  ci_thread_mutex_lock(&counters_mtx);
	  (child_data->freeservers)++;
	  (child_data->usedservers)--;
	  ci_thread_mutex_unlock(&counters_mtx);
	  ci_thread_cond_signal(&free_server_cond);

     }
     return 0;
}
예제 #12
0
static void free_request(request * req)
{
    int i;
    /* free_request should *never* get called by anything but
       process_requests */

    if (req->buffer_end && req->status < TIMED_OUT) {
        /*
         WARN("request sent to free_request before DONE.");
         */
        req->status = DONE;

        /* THIS IS THE SAME CODE EXECUTED BY THE 'DONE' SECTION
         * of process_requests. It must be exactly the same!
         */
        i = req_flush(req);
        /*
         * retval can be -2=error, -1=blocked, or bytes left
         */
        if (i == -2) {          /* error */
            req->status = DEAD;
        } else if (i > 0) {
            return;
        }
    }
    /* put request on the free list */
    dequeue(&request_ready, req); /* dequeue from ready or block list */

    /* set response status to 408 if the client has timed out */
    if (req->status == TIMED_OUT && req->response_status == 0)
        req->response_status = 408;

    /* always log */
    log_access(req);

    if (req->mmap_entry_var)
        release_mmap(req->mmap_entry_var);
    else if (req->data_mem)
        munmap(req->data_mem, req->filesize);

    if (req->data_fd) {
        close(req->data_fd);
        BOA_FD_CLR(req, req->data_fd, BOA_READ);
    }

    if (req->post_data_fd) {
        close(req->post_data_fd);
        BOA_FD_CLR(req, req->post_data_fd, BOA_WRITE);
    }

    if (req->response_status >= 400)
        status.errors++;

    for (i = COMMON_CGI_COUNT; i < req->cgi_env_index; ++i) {
        if (req->cgi_env[i]) {
            free(req->cgi_env[i]);
        } else {
            log_error_time();
            fprintf(stderr, "Warning: CGI Environment contains NULL value"
                    "(index %d of %d).\n", i, req->cgi_env_index);
        }
    }

    if (req->pathname)
        free(req->pathname);
    if (req->path_info)
        free(req->path_info);
    if (req->path_translated)
        free(req->path_translated);
    if (req->script_name)
        free(req->script_name);
    if (req->host)
        free(req->host);
    if (req->ranges)
        ranges_reset(req);

    if (req->status < TIMED_OUT && (req->keepalive == KA_ACTIVE) &&
        (req->response_status < 500 && req->response_status != 0) && req->kacount > 0) {
        sanitize_request(req, 0);

        --(req->kacount);

        status.requests++;
        enqueue(&request_block, req);
        BOA_FD_SET(req, req->fd, BOA_READ);
        BOA_FD_CLR(req, req->fd, BOA_WRITE);
        return;
    }

    /*
       While debugging some weird errors, Jon Nelson learned that
       some versions of Netscape Navigator break the
       HTTP specification.

       Some research on the issue brought up:

       http://www.apache.org/docs/misc/known_client_problems.html

       As quoted here:

       "
       Trailing CRLF on POSTs

       This is a legacy issue. The CERN webserver required POST
       data to have an extra CRLF following it. Thus many
       clients send an extra CRLF that is not included in the
       Content-Length of the request. Apache works around this
       problem by eating any empty lines which appear before a
       request.
       "

       Boa will (for now) hack around this stupid bug in Netscape
       (and Internet Exploder)
       by reading up to 32k after the connection is all but closed.
       This should eliminate any remaining spurious crlf sent
       by the client.

       Building bugs *into* software to be compatible is
       just plain wrong
     */

    if (req->method == M_POST) {
        char buf[32768];
        read(req->fd, buf, sizeof(buf));
    }
    close(req->fd);
    BOA_FD_CLR(req, req->fd, BOA_READ);
    BOA_FD_CLR(req, req->fd, BOA_WRITE);
    total_connections--;

    enqueue(&request_free, req);

    return;
}
예제 #13
0
파일: request.c 프로젝트: dulton/boa_onvif
static void free_request(request ** list_head_addr, request * req)
{
    int i;
    /* free_request should *never* get called by anything but
       process_requests */

    if (req->buffer_end && req->status != DEAD) {
        req->status = DONE;
        return;
    }
    /* put request on the free list */
    dequeue(list_head_addr, req); /* dequeue from ready or block list */

    if (req->logline)           /* access log */
        log_access(req);

    if (req->mmap_entry_var)
        release_mmap(req->mmap_entry_var);
    else if (req->data_mem)
        munmap(req->data_mem, req->filesize);

    if (req->data_fd)
        close(req->data_fd);

    if (req->post_data_fd)
        close(req->post_data_fd);

    if (req->response_status >= 400)
        status.errors++;

    for (i = COMMON_CGI_COUNT; i < req->cgi_env_index; ++i) {
        if (req->cgi_env[i]) {
            free(req->cgi_env[i]);
        } else {
            log_error_time();
            fprintf(stderr, "Warning: CGI Environment contains NULL value" \
                    "(index %d of %d).\n", i, req->cgi_env_index);
        }
    }

    if (req->pathname)
        free(req->pathname);
    if (req->path_info)
        free(req->path_info);
    if (req->path_translated)
        free(req->path_translated);
    if (req->script_name)
        free(req->script_name);

    if ((req->keepalive == KA_ACTIVE) &&
            (req->response_status < 500) && req->kacount > 0) {
        int bytes_to_move;

        request *conn = new_request();
        if (!conn) {
            /* errors already reported */
            enqueue(&request_free, req);
            close(req->fd);
            total_connections--;
            return;
        }
        conn->fd = req->fd;
        conn->status = READ_HEADER;
        conn->header_line = conn->client_stream;
        conn->kacount = req->kacount - 1;

        /* close enough and we avoid a call to time(NULL) */
        conn->time_last = req->time_last;

        /* for log file and possible use by CGI programs */
        memcpy(conn->remote_ip_addr, req->remote_ip_addr, NI_MAXHOST);
        memcpy(conn->local_ip_addr, req->local_ip_addr, NI_MAXHOST);

        /* for possible use by CGI programs */
        conn->remote_port = req->remote_port;

        status.requests++;

        /* we haven't parsed beyond req->parse_pos, so... */
        bytes_to_move = req->client_stream_pos - req->parse_pos;

        if (bytes_to_move) {
            memcpy(conn->client_stream,
                    req->client_stream + req->parse_pos, bytes_to_move);
            conn->client_stream_pos = bytes_to_move;
        }
        enqueue(&request_block, conn);
        BOA_FD_SET(conn->fd, &block_read_fdset);

        enqueue(&request_free, req);
        return;
    }

    /*
       While debugging some weird errors, Jon Nelson learned that
       some versions of Netscape Navigator break the
       HTTP specification.

       Some research on the issue brought up:

http://www.apache.org/docs/misc/known_client_problems.html

As quoted here:

"
Trailing CRLF on POSTs

This is a legacy issue. The CERN webserver required POST
data to have an extra CRLF following it. Thus many
clients send an extra CRLF that is not included in the
Content-Length of the request. Apache works around this
problem by eating any empty lines which appear before a
request.
"

Boa will (for now) hack around this stupid bug in Netscape
(and Internet Exploder)
by reading up to 32k after the connection is all but closed.
This should eliminate any remaining spurious crlf sent
by the client.

Building bugs *into* software to be compatable is
just plain wrong
*/

    if (req->method == M_POST) {
        char buf[32768];
        read(req->fd, buf, 32768);
    }
    close(req->fd);
    total_connections--;

    enqueue(&request_free, req);

    return;
}