/** Run the event loop once. * This function will run send any data that has been queued by * xmpp_send and related functions and run through the Strophe even * loop a single time, and will not wait more than timeout * milliseconds for events. This is provided to support integration * with event loops outside the library, and if used, should be * called regularly to achieve low latency event handling. * * @param ctx a Strophe context object * @param timeout time to wait for events in milliseconds * * @ingroup EventLoop */ void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout) { xmpp_connlist_t *connitem; xmpp_conn_t *conn; fd_set rfds, wfds; sock_t max = 0; int ret; struct timeval tv; xmpp_send_queue_t *sq, *tsq; int towrite; char buf[4096]; uint64_t next; long usec; int tls_read_bytes = 0; if (ctx->loop_status == XMPP_LOOP_QUIT) return; ctx->loop_status = XMPP_LOOP_RUNNING; /* send queued data */ connitem = ctx->connlist; while (connitem) { conn = connitem->conn; if (conn->state != XMPP_STATE_CONNECTED) { connitem = connitem->next; continue; } /* if we're running tls, there may be some remaining data waiting to * be sent, so push that out */ if (conn->tls) { ret = tls_clear_pending_write(conn->tls); if (ret < 0 && !tls_is_recoverable(tls_error(conn->tls))) { /* an error occured */ xmpp_debug(ctx, "xmpp", "Send error occured, disconnecting."); conn->error = ECONNABORTED; conn_disconnect(conn); } } /* write all data from the send queue to the socket */ sq = conn->send_queue_head; while (sq) { towrite = sq->len - sq->written; if (conn->tls) { ret = tls_write(conn->tls, &sq->data[sq->written], towrite); if (ret < 0 && !tls_is_recoverable(tls_error(conn->tls))) { /* an error occured */ conn->error = tls_error(conn->tls); break; } else if (ret < towrite) { /* not all data could be sent now */ if (ret >= 0) sq->written += ret; break; } } else { ret = sock_write(conn->sock, &sq->data[sq->written], towrite); if (ret < 0 && !sock_is_recoverable(sock_error())) { /* an error occured */ conn->error = sock_error(); break; } else if (ret < towrite) { /* not all data could be sent now */ if (ret >= 0) sq->written += ret; break; } } /* all data for this queue item written, delete and move on */ xmpp_free(ctx, sq->data); tsq = sq; sq = sq->next; xmpp_free(ctx, tsq); /* pop the top item */ conn->send_queue_head = sq; /* if we've sent everything update the tail */ if (!sq) conn->send_queue_tail = NULL; } /* tear down connection on error */ if (conn->error) { /* FIXME: need to tear down send queues and random other things * maybe this should be abstracted */ xmpp_debug(ctx, "xmpp", "Send error occured, disconnecting."); conn->error = ECONNABORTED; conn_disconnect(conn); } connitem = connitem->next; } /* reset parsers if needed */ for (connitem = ctx->connlist; connitem; connitem = connitem->next) { if (connitem->conn->reset_parser) conn_parser_reset(connitem->conn); } /* fire any ready timed handlers, then make sure we don't wait past the time when timed handlers need to be called */ next = handler_fire_timed(ctx); usec = ((next < timeout) ? next : timeout) * 1000; tv.tv_sec = usec / 1000000; tv.tv_usec = usec % 1000000; FD_ZERO(&rfds); FD_ZERO(&wfds); /* find events to watch */ connitem = ctx->connlist; while (connitem) { conn = connitem->conn; switch (conn->state) { case XMPP_STATE_CONNECTING: /* connect has been called and we're waiting for it to complete */ /* connection will give us write or error events */ /* make sure the timeout hasn't expired */ if (time_elapsed(conn->timeout_stamp, time_stamp()) <= conn->connect_timeout) FD_SET(conn->sock, &wfds); else { conn->error = ETIMEDOUT; xmpp_info(ctx, "xmpp", "Connection attempt timed out."); conn_disconnect(conn); } break; case XMPP_STATE_CONNECTED: FD_SET(conn->sock, &rfds); break; case XMPP_STATE_DISCONNECTED: /* do nothing */ default: break; } /* Check if there is something in the SSL buffer. */ if (conn->tls) { tls_read_bytes += tls_pending(conn->tls); } if (conn->state != XMPP_STATE_DISCONNECTED && conn->sock > max) max = conn->sock; connitem = connitem->next; } /* check for events */ if (max > 0) ret = select(max + 1, &rfds, &wfds, NULL, &tv); else { if (timeout > 0) _sleep(timeout); return; } /* select errored */ if (ret < 0) { if (!sock_is_recoverable(sock_error())) xmpp_error(ctx, "xmpp", "event watcher internal error %d", sock_error()); return; } /* no events happened */ if (ret == 0 && tls_read_bytes == 0) return; /* process events */ connitem = ctx->connlist; while (connitem) { conn = connitem->conn; switch (conn->state) { case XMPP_STATE_CONNECTING: if (FD_ISSET(conn->sock, &wfds)) { /* connection complete */ /* check for error */ ret = sock_connect_error(conn->sock); if (ret != 0) { /* connection failed */ xmpp_debug(ctx, "xmpp", "connection failed, error %d", ret); conn_disconnect(conn); break; } conn->state = XMPP_STATE_CONNECTED; xmpp_debug(ctx, "xmpp", "connection successful"); if (conn->tls_legacy_ssl) { xmpp_debug(ctx, "xmpp", "using legacy SSL connection"); ret = conn_tls_start(conn); if (ret != 0) { conn_disconnect(conn); break; } } /* send stream init */ conn_open_stream(conn); } break; case XMPP_STATE_CONNECTED: if (FD_ISSET(conn->sock, &rfds) || (conn->tls && tls_pending(conn->tls))) { if (conn->tls) { ret = tls_read(conn->tls, buf, 4096); } else { ret = sock_read(conn->sock, buf, 4096); } if (ret > 0) { ret = parser_feed(conn->parser, buf, ret); if (!ret) { /* parse error, we need to shut down */ /* FIXME */ xmpp_debug(ctx, "xmpp", "parse error, disconnecting"); conn_disconnect(conn); } } else { if (conn->tls) { if (!tls_is_recoverable(tls_error(conn->tls))) { xmpp_debug(ctx, "xmpp", "Unrecoverable TLS error, %d.", tls_error(conn->tls)); conn->error = tls_error(conn->tls); conn_disconnect(conn); } } else { /* return of 0 means socket closed by server */ xmpp_debug(ctx, "xmpp", "Socket closed by remote host."); conn->error = ECONNRESET; conn_disconnect(conn); } } } break; case XMPP_STATE_DISCONNECTED: /* do nothing */ default: break; } connitem = connitem->next; } /* fire any ready handlers */ handler_fire_timed(ctx); }
/* Read the request from a client socket. */ int fetch_request(t_session *session) { char *new_reqbuf, *strstart, *strend; long max_request_size, bytes_read, header_length = -1, content_length = -1, chunk_size_pos = 0; int result = 200, write_bytes, poll_result, upload_handle = -1, retval; time_t deadline; struct pollfd poll_data; bool keep_reading = true, store_on_disk = false, chunked_request = false; if (session->request_limit == false) { if (session->binding->time_for_request > NO_REQUEST_LIMIT_TIME) { deadline = session->time + session->binding->time_for_request; } else { deadline = session->time + NO_REQUEST_LIMIT_TIME; } if (session->binding->max_request_size > NO_REQUEST_LIMIT_SIZE) { max_request_size = session->binding->max_request_size; } else { max_request_size = NO_REQUEST_LIMIT_SIZE; } } else if (session->kept_alive == 0) { deadline = session->time + session->binding->time_for_1st_request; max_request_size = session->binding->max_request_size; } else { deadline = session->time + session->binding->time_for_request; max_request_size = session->binding->max_request_size; } do { /* Check if requestbuffer contains a complete request. */ if (session->request != NULL) { if (header_length == -1) { if ((strstart = strstr(session->request, "\r\n\r\n")) != NULL) { *(strstart + 2) = '\0'; header_length = strstart + 4 - session->request; session->header_length = header_length; determine_request_method(session); store_on_disk = (session->request_method == PUT) && session->binding->enable_alter; if (store_on_disk) { if ((session->uploaded_file = (char*)malloc(session->config->upload_directory_len + 15)) != NULL) { memcpy(session->uploaded_file, session->config->upload_directory, session->config->upload_directory_len); memcpy(session->uploaded_file + session->config->upload_directory_len, "/upload_XXXXXX", 15); umask(S_IWGRP | S_IWOTH); if ((upload_handle = mkstemp(session->uploaded_file)) == -1) { free(session->uploaded_file); session->uploaded_file = NULL; } } if (session->uploaded_file == NULL) { log_error(session, "can't create temporary file for PUT request"); result = 500; break; } session->uploaded_size = session->bytes_in_buffer - header_length; if (write_buffer(upload_handle, session->request + header_length, session->uploaded_size) == -1) { result = 500; break; } session->bytes_in_buffer = header_length; } /* Handle 100-continue */ if (strnstr(session->request, "Expect: 100-continue\r\n", header_length) != NULL) { send_buffer(session, "HTTP/1.1 100 Continue\r\n\r\n", 25); send_buffer(session, NULL, 0); } } } if (header_length != -1) { if ((content_length == -1) && (chunked_request == false)) { if ((strstart = strcasestr(session->request, hs_conlen)) != NULL) { /* Request has Content-Length */ strstart += 16; if ((strend = strstr(strstart, "\r\n")) != NULL) { *strend = '\0'; content_length = str_to_int(strstart); *strend = '\r'; if ((content_length < 0) || (INT_MAX - content_length - 2 <= header_length)) { result = 500; break; } if (store_on_disk) { /* Write to file on disk */ session->content_length = 0; if (content_length > session->binding->max_upload_size) { result = 413; break; } session->buffer_size = header_length + REQUEST_BUFFER_CHUNK; if ((new_reqbuf = (char*)realloc(session->request, session->buffer_size + 1)) != NULL) { session->request = new_reqbuf; } else { session->error_cause = ec_SOCKET_READ_ERROR; result = -1; break; } } else { /* Read into memory */ session->content_length = content_length; if (header_length + content_length > max_request_size) { session->error_cause = ec_MAX_REQUESTSIZE; result = -1; break; } if (header_length + content_length > session->buffer_size) { session->buffer_size = header_length + content_length; if ((new_reqbuf = (char*)realloc(session->request, session->buffer_size + 1)) != NULL) { session->request = new_reqbuf; } else { session->error_cause = ec_SOCKET_READ_ERROR; result = -1; break; } } } } } else if (strcasestr(session->request, hs_chunked) != NULL) { /* Chunked transfer encoding */ if (store_on_disk) { log_error(session, "Chunked transfer encoding for PUT requests not supported."); result = -1; break; } chunked_request = true; chunk_size_pos = 0; } else { /* No content */ session->content_length = 0; if (store_on_disk) { result = 411; } break; } } if (content_length > -1) { if (store_on_disk) { if (session->uploaded_size == content_length) { /* Received a complete PUT request */ break; } } else { if (session->bytes_in_buffer >= header_length + content_length) { /* Received a complete request */ break; } } } else if (chunked_request) { /* All chunks uploaded */ retval = all_chunks_uploaded(session->request + session->header_length, session->bytes_in_buffer - session->header_length, &chunk_size_pos); if (retval == -1) { result = 400; break; } else if (retval == 1) { if ((session->content_length = merge_chunks(session->request + session->header_length, session->bytes_in_buffer - session->header_length, &(session->bytes_in_buffer))) == -1) { result = -1; } break; } } } } #ifdef ENABLE_TLS poll_result = session->binding->use_tls ? tls_pending(&(session->tls_context)) : 0; if (poll_result == 0) { #endif poll_data.fd = session->client_socket; poll_data.events = POLL_EVENT_BITS; poll_result = poll(&poll_data, 1, 1000); #ifdef ENABLE_TLS } #endif switch (poll_result) { case -1: if (errno != EINTR) { if (session->bytes_in_buffer == 0) { session->error_cause = ec_CLIENT_DISCONNECTED; } else { session->error_cause = ec_SOCKET_READ_ERROR; } result = -1; keep_reading = false; } break; case 0: if (session->force_quit) { session->error_cause = ec_FORCE_QUIT; result = -1; keep_reading = false; } else if (time(NULL) > deadline) { session->error_cause = ec_TIMEOUT; result = -1; keep_reading = false; } break; default: if ((content_length == -1) && ((session->buffer_size - session->bytes_in_buffer) < 256)) { session->buffer_size += REQUEST_BUFFER_CHUNK; if ((new_reqbuf = (char*)realloc(session->request, session->buffer_size + 1)) != NULL) { session->request = new_reqbuf; } else { session->error_cause = ec_SOCKET_READ_ERROR; result = -1; keep_reading = false; break; } } /* Read from socket. */ #ifdef ENABLE_TLS if (session->binding->use_tls) { bytes_read = tls_receive(&(session->tls_context), session->request + session->bytes_in_buffer, session->buffer_size - session->bytes_in_buffer); } else #endif bytes_read = recv(session->client_socket, session->request + session->bytes_in_buffer, session->buffer_size - session->bytes_in_buffer, 0); switch (bytes_read) { case -1: if (errno != EINTR) { if (session->bytes_in_buffer == 0) { session->error_cause = ec_CLIENT_DISCONNECTED; } else { session->error_cause = ec_SOCKET_READ_ERROR; } result = -1; keep_reading = false; } break; case 0: session->error_cause = ec_CLIENT_DISCONNECTED; result = -1; keep_reading = false; break; default: if (store_on_disk) { /* Write to file on disk */ write_bytes = bytes_read; if (session->uploaded_size + bytes_read > content_length) { write_bytes -= ((session->uploaded_size + bytes_read) - content_length); } if (write_buffer(upload_handle, session->request + header_length, write_bytes) == -1) { result = 500; keep_reading = false; break; } if ((session->uploaded_size += write_bytes) > session->binding->max_upload_size) { keep_reading = false; result = 413; break; } if (write_bytes < bytes_read) { memmove(session->request + header_length, session->request + header_length + write_bytes, bytes_read - write_bytes); session->bytes_in_buffer += bytes_read - write_bytes; keep_reading = false; } } else { /* Read into memory */ session->bytes_in_buffer += bytes_read; *(session->request + session->bytes_in_buffer) = '\0'; if (session->bytes_in_buffer > max_request_size) { keep_reading = false; session->error_cause = ec_MAX_REQUESTSIZE; result = -1; break; } } } } } while (keep_reading); if (upload_handle != -1) { fsync(upload_handle); close(upload_handle); } #ifdef ENABLE_TOMAHAWK increment_transfer(TRANSFER_RECEIVED, header_length + content_length); #endif return result; }