static void idle(ClientData client_data, struct timeval *nowP) { int cnum; struct connect_s *conn; for (cnum = 0; cnum < AVAILABLE_FDS; ++cnum) { conn = &connects[cnum]; switch (conn->conn_state) { case CNST_READING: if (nowP->tv_sec - conn->active_at >= CONFIG_THTTPD_IDLE_READ_LIMIT_SEC) { ndbg("%s connection timed out reading\n", httpd_ntoa(&conn->hc->client_addr)); httpd_send_err(conn->hc, 408, httpd_err408title, "", httpd_err408form, ""); finish_connection(conn, nowP); } break; case CNST_SENDING: if (nowP->tv_sec - conn->active_at >= CONFIG_THTTPD_IDLE_SEND_LIMIT_SEC) { ndbg("%s connection timed out sending\n", httpd_ntoa(&conn->hc->client_addr)); clear_connection(conn, nowP); } break; } } }
static void idle( ClientData client_data, struct timeval* nowP ) { int cnum; connecttab* c; for ( cnum = 0; cnum < max_connects; ++cnum ) { c = &connects[cnum]; switch ( c->conn_state ) { case CNST_READING: if ( nowP->tv_sec - c->active_at >= IDLE_READ_TIMELIMIT ) { httpd_send_err( c->hc, 408, httpd_err408title, "", httpd_err408form, "" ); finish_connection( c, nowP ); } break; case CNST_SENDING: case CNST_PAUSING: if ( nowP->tv_sec - c->active_at >= IDLE_SEND_TIMELIMIT ) { clear_connection( c, nowP ); } break; case CNST_SLEEPING:; } } }
static void reconnect( void *user_data ) { UNUSED( user_data ); bool ret = try_connect(); if ( ret == false ) { error( "Failed to reconnect." ); clear_connection(); } }
static void finish_connection( connecttab* c, struct timeval* tvP ) { /* If we haven't actually sent the buffered response yet, do so now. */ httpd_write_response( c->hc ); /* And clear. */ clear_connection( c, tvP ); }
static void disconnected() { transit_state( DISCONNECTED ); if ( connection.disconnected_callback != NULL ) { connection.disconnected_callback(); } clear_connection(); }
static void finish_connection(struct connect_s *conn, struct timeval *tv) { /* If we haven't actually sent the buffered response yet, do so now */ httpd_write_response(conn->hc); /* And clear */ clear_connection(conn, tv); }
static void idle_send_connection( ClientData client_data, struct timeval* nowP ) { connecttab* c; c = (connecttab*) client_data.p; c->idle_send_timer = (Timer*) 0; if ( c->conn_state != CNST_FREE ) { syslog( LOG_INFO, "%.80s connection timed out sending", httpd_ntoa( &c->hc->client_addr ) ); clear_connection( c, nowP ); } }
static void check_connected( void *user_data ) { UNUSED( user_data ); debug( "Checking a connection ( fd = %d, ip = %#x, port = %u ).", connection.fd, connection.ip, connection.port ); assert( secure_channel_initialized ); assert( connection.fd >= 0 ); set_writable( connection.fd, false ); delete_fd_handler( connection.fd ); int err = 0; socklen_t length = sizeof( error ); int ret = getsockopt( connection.fd, SOL_SOCKET, SO_ERROR, &err, &length ); if ( ret < 0 ) { error( "Failed to retrieve error code ( fd = %d, ret = %d, errno = %s [%d] ).", connection.fd, ret, strerror( errno ), errno ); return; } switch ( err ) { case 0: connected(); break; case EINTR: case EAGAIN: case ECONNREFUSED: case ENETUNREACH: case ETIMEDOUT: warn( "Failed to connect ( fd = %d, errno = %s [%d] ).", connection.fd, strerror( err ), err ); backoff(); return; case EINPROGRESS: set_fd_handler( connection.fd, NULL, NULL, ( event_fd_callback ) check_connected, NULL ); set_writable( connection.fd, true ); break; default: error( "Failed to connect ( fd = %d, errno = %s [%d] ).", connection.fd, strerror( err ), err ); clear_connection(); return; } }
bool finalize_secure_channel() { assert( secure_channel_initialized ); clear_connection(); if ( send_queue != NULL ) { delete_message_queue( send_queue ); send_queue = NULL; } if ( recv_queue != NULL ) { delete_message_queue( recv_queue ); recv_queue = NULL; } secure_channel_initialized = false; return true; }
bool init_secure_channel( uint32_t ip, uint16_t port, connected_handler connected_callback, disconnected_handler disconnected_callback ) { assert( !secure_channel_initialized ); connection.ip = ip; connection.port = port; connection.fd = -1; connection.connected_callback = connected_callback; connection.disconnected_callback = disconnected_callback; bool ret = try_connect(); if ( ret == false ) { clear_connection(); return false; } send_queue = create_message_queue(); recv_queue = create_message_queue(); secure_channel_initialized = true; return true; }
static bool try_connect() { assert( connection.state != CONNECTED ); int fd = socket( PF_INET, SOCK_STREAM, 0 ); if ( fd < 0 ) { error( "Failed to create a socket ( ret = %d, errno = %s [%d] ).", fd, strerror( errno ), errno ); return false; } int flag = 1; int ret = setsockopt( fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof( flag ) ); if ( ret < 0 ) { error( "Failed to set socket options ( fd = %d, ret = %d, errno = %s [%d] ).", fd, ret, strerror( errno ), errno ); return false; } ret = fcntl( fd, F_SETFL, O_NONBLOCK ); if ( ret < 0 ) { error( "Failed to enable non-blocking mode ( fd = %d, ret = %d, errno = %s [%d] ).", fd, ret, strerror( errno ), errno ); close( fd ); return false; } connection.fd = fd; struct sockaddr_in addr; memset( &addr, 0, sizeof( struct sockaddr_in ) ); addr.sin_family = AF_INET; addr.sin_port = htons( connection.port ); addr.sin_addr.s_addr = htonl( connection.ip ); transit_state( CONNECTING ); ret = connect( connection.fd, ( struct sockaddr * ) &addr, sizeof( struct sockaddr_in ) ); if ( ret < 0 ) { switch ( errno ) { case EINTR: case EAGAIN: case ECONNREFUSED: case ENETUNREACH: case ETIMEDOUT: warn( "Failed to connect ( fd = %d, ret = %d, errno = %s [%d] ).", connection.fd, ret, strerror( errno ), errno ); backoff(); return true; case EINPROGRESS: break; default: error( "Failed to connect ( fd = %d, ret = %d, errno = %s [%d] ).", connection.fd, ret, strerror( errno ), errno ); clear_connection(); return false; } } set_fd_handler( connection.fd, NULL, NULL, ( event_fd_callback ) check_connected, NULL ); set_writable( connection.fd, true ); return true; }
static void handle_send(struct connect_s *conn, struct timeval *tv) { httpd_conn *hc = conn->hc; int nwritten; int nread; /* Read until the entire file is sent -- this could take awhile!! */ while (conn->offset < conn->end_offset) { nvdbg("offset: %d end_offset: %d bytes_sent: %d\n", conn->offset, conn->end_offset, conn->hc->bytes_sent); /* Fill the rest of the response buffer with file data */ nread = read_buffer(conn); if (nread < 0) { ndbg("File read error: %d\n", errno); goto errout_clear_connection; } nvdbg("Read %d bytes, buflen %d\n", nread, hc->buflen); /* Send the buffer */ if (hc->buflen > 0) { /* httpd_write does not return until all bytes have been sent * (or an error occurs). */ nwritten = httpd_write(hc->conn_fd, hc->buffer, hc->buflen); if (nwritten < 0) { ndbg("Error sending %s: %d\n", hc->encodedurl, errno); goto errout_clear_connection; } /* We wrote one full buffer of data (httpd_write does not * return until the full buffer is written (or an error occurs). */ conn->active_at = tv->tv_sec; hc->buflen = 0; /* And update how much of the file we wrote */ conn->offset += nwritten; conn->hc->bytes_sent += nwritten; nvdbg("Wrote %d bytes\n", nwritten); } } /* The file transfer is complete -- finish the connection */ nvdbg("Finish connection\n"); finish_connection(conn, tv); return; errout_clear_connection: ndbg("Clear connection\n"); clear_connection(conn, tv); return; }
static void handle_send( connecttab* c, struct timeval* tvP ) { size_t max_bytes; int sz, coast; ClientData client_data; time_t elapsed; httpd_conn* hc = c->hc; int tind; max_bytes = 1000000000L; /* Do we need to write the headers first? */ if ( hc->responselen == 0 ) { /* No, just write the file. */ sz = write( hc->conn_fd, &(hc->body_data[c->next_byte_index]), MIN( c->end_byte_index - c->next_byte_index, max_bytes ) ); } else { /* Yes. We'll combine headers and file into a single writev(), ** hoping that this generates a single packet. */ struct iovec iv[2]; iv[0].iov_base = hc->response; iv[0].iov_len = hc->responselen; iv[1].iov_base = &(hc->body_data[c->next_byte_index]); iv[1].iov_len = MIN( c->end_byte_index - c->next_byte_index, max_bytes ); sz = writev( hc->conn_fd, iv, 2 ); } if ( sz < 0 && errno == EINTR ) return; if ( sz == 0 || ( sz < 0 && ( errno == EWOULDBLOCK || errno == EAGAIN ) ) ) { /* This shouldn't happen, but some kernels, e.g. ** SunOS 4.1.x, are broken and select() says that ** O_NDELAY sockets are always writable even when ** they're actually not. ** ** Current workaround is to block sending on this ** socket for a brief adaptively-tuned period. ** Fortunately we already have all the necessary ** blocking code, for use with throttling. */ c->wouldblock_delay += MIN_WOULDBLOCK_DELAY; c->conn_state = CNST_PAUSING; fdwatch_del_fd( hc->conn_fd ); client_data.p = c; c->wakeup_timer = tmr_create( tvP, wakeup_connection, client_data, c->wouldblock_delay, 0 ); if ( c->wakeup_timer == (Timer*) 0 ) { return; } return; } if ( sz < 0 ) { /* Something went wrong, close this connection. */ clear_connection( c, tvP ); return; } /* Ok, we wrote something. */ c->active_at = tvP->tv_sec; /* Was this a headers + file writev()? */ if ( hc->responselen > 0 ) { /* Yes; did we write only part of the headers? */ if ( sz < hc->responselen ) { /* Yes; move the unwritten part to the front of the buffer. */ int newlen = hc->responselen - sz; (void) memmove( hc->response, &(hc->response[sz]), newlen ); hc->responselen = newlen; sz = 0; } else { /* Nope, we wrote the full headers, so adjust accordingly. */ sz -= hc->responselen; hc->responselen = 0; } } /* And update how much of the file we wrote. */ c->next_byte_index += sz; c->hc->bytes_sent += sz; /* Are we done? */ if ( c->next_byte_index >= c->end_byte_index ) { /* This connection is finished! */ finish_connection( c, tvP ); return; } /* Tune the (blockheaded) wouldblock delay. */ if ( c->wouldblock_delay > MIN_WOULDBLOCK_DELAY ) c->wouldblock_delay -= MIN_WOULDBLOCK_DELAY; /* (No check on min_limit here, that only controls connection startups.) */ }
int thttpd_run(void) { char* cp; struct passwd* pwd; uid_t uid = 32767; gid_t gid = 32767; int num_ready; int cnum; connecttab* c; httpd_conn* hc; httpd_sockaddr sa4; httpd_sockaddr sa6; int gotv4, gotv6; struct timeval tv; cp = getenv( "GHTTPPORT" ); if ( cp ) port = atoi( cp ); if( port == 0 ) port = 9999; /* Read zone info now, in case we chroot(). */ tzset(); /* Look up hostname now, in case we chroot(). */ lookup_hostname( &sa4, sizeof(sa4), &gotv4, &sa6, sizeof(sa6), &gotv6 ); if ( ! ( gotv4 || gotv6 ) ) { memset(&sa4, 0, sizeof sa4); gotv4 = 1; } /* Initialize the fdwatch package. Have to do this before chroot, ** if /dev/poll is used. */ max_connects = fdwatch_get_nfiles(); if ( max_connects < 0 ) { return; } max_connects -= SPARE_FDS; /* Set up to catch signals. */ #ifdef HAVE_SIGSET (void) sigset( SIGPIPE, SIG_IGN ); /* get EPIPE instead */ #else /* HAVE_SIGSET */ (void) signal( SIGPIPE, SIG_IGN ); /* get EPIPE instead */ #endif /* HAVE_SIGSET */ /* Initialize the timer package. */ tmr_init(); /* Initialize the HTTP layer. Got to do this before giving up root, ** so that we can bind to a privileged port. */ hs = httpd_initialize( hostname, gotv4 ? &sa4 : (httpd_sockaddr*) 0, gotv6 ? &sa6 : (httpd_sockaddr*) 0, port, cgi_pattern, cgi_limit, charset, p3p, max_age, "/", no_log, no_symlink_check, do_vhost, do_global_passwd, url_pattern, local_pattern, no_empty_referers ); if ( hs == (httpd_server*) 0 ) exit( 1 ); /* Set up the occasional timer. */ if ( tmr_create( (struct timeval*) 0, occasional, JunkClientData, OCCASIONAL_TIME * 1000L, 1 ) == (Timer*) 0 ) { return; } /* Set up the idle timer. */ if ( tmr_create( (struct timeval*) 0, idle, JunkClientData, 5 * 1000L, 1 ) == (Timer*) 0 ) { return; } start_time = stats_time = time( (time_t*) 0 ); stats_connections = 0; stats_bytes = 0; stats_simultaneous = 0; /* Initialize our connections table. */ connects = NEW( connecttab, max_connects ); if ( connects == (connecttab*) 0 ) { return; } for ( cnum = 0; cnum < max_connects; ++cnum ) { connects[cnum].conn_state = CNST_FREE; connects[cnum].next_free_connect = cnum + 1; connects[cnum].hc = (httpd_conn*) 0; } connects[max_connects - 1].next_free_connect = -1; /* end of link list */ first_free_connect = 0; num_connects = 0; httpd_conn_count = 0; if ( hs != (httpd_server*) 0 ) { if ( hs->listen4_fd != -1 ) fdwatch_add_fd( hs->listen4_fd, (void*) 0, FDW_READ ); if ( hs->listen6_fd != -1 ) fdwatch_add_fd( hs->listen6_fd, (void*) 0, FDW_READ ); } /* Main loop. */ (void) gettimeofday( &tv, (struct timezone*) 0 ); while ( ( ! terminate ) || num_connects > 0 ) { /* Do the fd watch. */ num_ready = fdwatch( tmr_mstimeout( &tv ) ); if ( num_ready < 0 ) { if ( errno == EINTR || errno == EAGAIN ) continue; /* try again */ return; } (void) gettimeofday( &tv, (struct timezone*) 0 ); if ( num_ready == 0 ) { /* No fd's are ready - run the timers. */ tmr_run( &tv ); continue; } /* Is it a new connection? */ if ( hs != (httpd_server*) 0 && hs->listen6_fd != -1 && fdwatch_check_fd( hs->listen6_fd ) ) { if ( handle_newconnect( &tv, hs->listen6_fd ) ) /* Go around the loop and do another fdwatch, rather than ** dropping through and processing existing connections. ** New connections always get priority. */ continue; } if ( hs != (httpd_server*) 0 && hs->listen4_fd != -1 && fdwatch_check_fd( hs->listen4_fd ) ) { if ( handle_newconnect( &tv, hs->listen4_fd ) ) /* Go around the loop and do another fdwatch, rather than ** dropping through and processing existing connections. ** New connections always get priority. */ continue; } /* Find the connections that need servicing. */ while ( ( c = (connecttab*) fdwatch_get_next_client_data() ) != (connecttab*) -1 ) { if ( c == (connecttab*) 0 ) continue; hc = c->hc; if ( ! fdwatch_check_fd( hc->conn_fd ) ) /* Something went wrong. */ clear_connection( c, &tv ); else switch ( c->conn_state ) { case CNST_READING: handle_read( c, &tv ); break; case CNST_SENDING: handle_send( c, &tv ); break; case CNST_LINGERING: handle_linger( c, &tv ); break; } } tmr_run( &tv ); } /* The main loop terminated. */ shut_down(); return 0; }
static void handle_send( connecttab* c, struct timeval* tvP ) { int sz, coast; ClientData client_data; time_t elapsed; httpd_conn* hc = c->hc; /* Do we need to write the headers first? */ if ( hc->responselen == 0 ) { /* No, just write the file. */ sz = write( hc->conn_fd, &(hc->file_address[c->bytes_sent]), MIN( c->bytes_to_send - c->bytes_sent, c->limit ) ); } else { /* Yes. We'll combine headers and file into a single writev(), ** hoping that this generates a single packet. */ struct iovec iv[2]; iv[0].iov_base = hc->response; iv[0].iov_len = hc->responselen; iv[1].iov_base = &(hc->file_address[c->bytes_sent]); iv[1].iov_len = MIN( c->bytes_to_send - c->bytes_sent, c->limit ); sz = writev( hc->conn_fd, iv, 2 ); } if ( sz == 0 || ( sz < 0 && ( errno == EWOULDBLOCK || errno == EAGAIN ) ) ) { /* This shouldn't happen, but some kernels, e.g. ** SunOS 4.1.x, are broken and select() says that ** O_NDELAY sockets are always writable even when ** they're actually not. ** ** Current workaround is to block sending on this ** socket for a brief adaptively-tuned period. ** Fortunately we already have all the necessary ** blocking code, for use with throttling. */ c->wouldblock_delay += MIN_WOULDBLOCK_DELAY; c->conn_state = CNST_PAUSING; fdwatch_del_fd( hc->conn_fd ); client_data.p = c; c->wakeup_timer = tmr_create( tvP, wakeup_connection, client_data, c->wouldblock_delay, 0 ); if ( c->wakeup_timer == (Timer*) 0 ) { syslog( LOG_CRIT, "tmr_create(wakeup_connection) failed" ); exit( 1 ); } return; } if ( sz < 0 ) { /* Something went wrong, close this connection. ** ** If it's just an EPIPE, don't bother logging, that ** just means the client hung up on us. ** ** On some systems, write() occasionally gives an EINVAL. ** Dunno why, something to do with the socket going ** bad. Anyway, we don't log those either. ** ** And ECONNRESET isn't interesting either. */ if ( errno != EPIPE && errno != EINVAL && errno != ECONNRESET ) syslog( LOG_ERR, "write - %m sending %.80s", hc->encodedurl ); clear_connection( c, tvP ); return; } /* Ok, we wrote something. */ tmr_reset( tvP, c->idle_send_timer ); /* Was this a headers + file writev()? */ if ( hc->responselen > 0 ) { /* Yes; did we write only part of the headers? */ if ( sz < hc->responselen ) { /* Yes; move the unwritten part to the front of the buffer. */ int newlen = hc->responselen - sz; (void) memcpy( hc->response, &(hc->response[sz]), newlen ); hc->responselen = newlen; sz = 0; } else { /* Nope, we wrote the full headers, so adjust accordingly. */ sz -= hc->responselen; hc->responselen = 0; } } /* And update how much of the file we wrote. */ c->bytes_sent += sz; c->hc->bytes_sent += sz; /* Are we done? */ if ( c->bytes_sent >= c->bytes_to_send ) { /* This conection is finished! */ clear_connection( c, tvP ); return; } /* Tune the (blockheaded) wouldblock delay. */ if ( c->wouldblock_delay > MIN_WOULDBLOCK_DELAY ) c->wouldblock_delay -= MIN_WOULDBLOCK_DELAY; /* If we're throttling, check if we're sending too fast. */ if ( c->limit != THROTTLE_NOLIMIT ) { elapsed = tvP->tv_sec - c->started_at; if ( elapsed == 0 || c->hc->bytes_sent / elapsed > c->limit ) { c->conn_state = CNST_PAUSING; fdwatch_del_fd( hc->conn_fd ); /* When should we send the next c->limit bytes ** to get back on schedule? If less than a second ** (integer math rounding), use 1/8 second. */ coast = ( c->hc->bytes_sent + c->limit ) / c->limit - elapsed; client_data.p = c; c->wakeup_timer = tmr_create( tvP, wakeup_connection, client_data, coast ? ( coast * 1000L ) : 125L, 0 ); if ( c->wakeup_timer == (Timer*) 0 ) { syslog( LOG_CRIT, "tmr_create(wakeup_connection) failed" ); exit( 1 ); } } } }
static void handle_read( connecttab* c, struct timeval* tvP ) { int sz; ClientData client_data; httpd_conn* hc = c->hc; /* Is there room in our buffer to read more bytes? */ if ( hc->read_idx >= hc->read_size ) { if ( hc->read_size > 5000 ) { httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); clear_connection( c, tvP ); return; } httpd_realloc_str( &hc->read_buf, &hc->read_size, hc->read_size + 1000 ); } /* Read some more bytes. */ sz = read( hc->conn_fd, &(hc->read_buf[hc->read_idx]), hc->read_size - hc->read_idx ); /* Ignore EWOULDBLOCK errors. At first glance you would think that ** connections returned by fdwatch as readable should never give an ** EWOULDBLOCK; however, this apparently can happen if a packet gets ** garbled. */ if ( sz == 0 || ( sz < 0 && ( errno != EWOULDBLOCK ) ) ) { httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); clear_connection( c, tvP ); return; } hc->read_idx += sz; /* Do we have a complete request yet? */ switch ( httpd_got_request( hc ) ) { case GR_NO_REQUEST: return; case GR_BAD_REQUEST: httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); clear_connection( c, tvP ); return; } /* Yes. Try parsing and resolving it. */ if ( httpd_parse_request( hc ) < 0 ) { clear_connection( c, tvP ); return; } /* Check the throttle table */ if ( ! check_throttles( c ) ) { httpd_send_err( hc, 503, httpd_err503title, "", httpd_err503form, hc->encodedurl ); clear_connection( c, tvP ); return; } /* Start the connection going. */ if ( httpd_start_request( hc, tvP ) < 0 ) { /* Something went wrong. Close down the connection. */ clear_connection( c, tvP ); return; } /* Fill in bytes_to_send. */ if ( hc->got_range ) { c->bytes_sent = hc->init_byte_loc; c->bytes_to_send = hc->end_byte_loc + 1; } else c->bytes_to_send = hc->bytes_to_send; /* Check if it's already handled. */ if ( hc->file_address == (char*) 0 ) { /* No file address means someone else is handling it. */ c->bytes_sent = hc->bytes_sent; clear_connection( c, tvP ); return; } if ( c->bytes_sent >= c->bytes_to_send ) { /* There's nothing to send. */ clear_connection( c, tvP ); return; } /* Cool, we have a valid connection and a file to send to it. */ c->conn_state = CNST_SENDING; c->started_at = tvP->tv_sec; c->wouldblock_delay = 0; client_data.p = c; tmr_cancel( c->idle_read_timer ); c->idle_read_timer = (Timer*) 0; c->idle_send_timer = tmr_create( tvP, idle_send_connection, client_data, IDLE_SEND_TIMELIMIT * 1000L, 0 ); if ( c->idle_send_timer == (Timer*) 0 ) { syslog( LOG_CRIT, "tmr_create(idle_send_connection) failed" ); exit( 1 ); } fdwatch_del_fd( hc->conn_fd ); fdwatch_add_fd( hc->conn_fd, c, FDW_WRITE ); }