void h2o__proxy_process_request(h2o_req_t *req) { h2o_req_overrides_t *overrides = req->overrides; h2o_http1client_ctx_t *client_ctx = get_client_ctx(req); struct rp_generator_t *self; if (overrides != NULL) { if (overrides->socketpool != NULL) { self = proxy_send_prepare(req, 1); h2o_http1client_connect_with_pool(&self->client, self, client_ctx, overrides->socketpool, on_connect); return; } else if (overrides->hostport.host.base != NULL) { self = proxy_send_prepare(req, 0); h2o_http1client_connect(&self->client, self, client_ctx, req->overrides->hostport.host, req->overrides->hostport.port, 0, on_connect); return; } } { /* default logic */ h2o_iovec_t host; uint16_t port; if (h2o_url_parse_hostport(req->authority.base, req->authority.len, &host, &port) == NULL) { h2o_req_log_error(req, "lib/core/proxy.c", "invalid URL supplied for internal redirection:%s://%.*s%.*s", req->scheme->name.base, (int)req->authority.len, req->authority.base, (int)req->path.len, req->path.base); h2o_send_error(req, 502, "Gateway Error", "internal error", 0); return; } if (port == 65535) port = req->scheme->default_port; self = proxy_send_prepare(req, 0); h2o_http1client_connect(&self->client, self, client_ctx, host, port, req->scheme == &H2O_URL_SCHEME_HTTPS, on_connect); return; } }
void on_connect(EV_P_ struct ev_io *io, int revents) { while (1) { struct sockaddr_in client_addr; socklen_t len = sizeof (struct sockaddr_in); int client_sock = accept(io->fd, (struct sockaddr *) &client_addr, &len); if (client_sock >= 0) { if (set_nonblock(client_sock) == -1) { shutdown_printerr(client_sock, "can't set the socket mode O_NONBLOCK for client\n"); return; } if (set_linger(client_sock) == -1) { shutdown_printerr(client_sock, "can't set SO_LINGER sock option for client\n"); return; } if (set_keepalive(client_sock) == -1) { shutdown_printerr(client_sock, "can't set SO_KEEPALIVE sock option for client\n"); return; } server_ctx_t *srv_ctx = (server_ctx_t *) ev_userdata(loop); client_ctx_t* cli_ctx = get_client_ctx(srv_ctx); cli_ctx->io.ctx = cli_ctx; cli_ctx->connected_at = time(NULL); uuid_generate(cli_ctx->uuid); memcpy(&cli_ctx->client_addr, &client_addr, sizeof (struct sockaddr_in)); ev_io_init((ev_io *) & cli_ctx->io, client_read_write, client_sock, EV_READ); ev_io_start(loop, (ev_io *) & cli_ctx->io); char time_buff[32]; strftime(time_buff, sizeof (time_buff), "%Y-%m-%d %H:%M:%S %Z", localtime(&cli_ctx->connected_at)); char *addr = inet_ntoa(cli_ctx->client_addr.sin_addr); char uuid_buff[37]; uuid_unparse_lower(cli_ctx->uuid, (char *) &uuid_buff); printf("client accepted %s:%hu %s at %s\n", addr, client_addr.sin_port, &uuid_buff, &time_buff); char *welcome_msg = server_welcome(srv_ctx, cli_ctx); send_message(loop, cli_ctx->uuid, welcome_msg, strlen(welcome_msg)); free(welcome_msg); char *new_client_msg = server_client_connected(cli_ctx); for (ssize_t i = 0; i < srv_ctx->clients_count; i++) { if (uuid_compare(srv_ctx->clients[i]->uuid, cli_ctx->uuid) != 0) { send_message(loop, srv_ctx->clients[i]->uuid, new_client_msg, strlen(new_client_msg)); } } free(new_client_msg); } else { if (errno == EAGAIN) return; if (errno == EMFILE || errno == ENFILE) { fprintf(stderr, "out of file descriptors\n"); return; } else if (errno != EINTR) { fprintf(stderr, "accept connections error\n"); return; } } } }
static struct rp_generator_t *proxy_send_prepare(h2o_req_t *req, int keepalive) { struct rp_generator_t *self = h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose); h2o_http1client_ctx_t *client_ctx = get_client_ctx(req); self->super.proceed = do_proceed; self->super.stop = do_close; self->src_req = req; if (client_ctx->websocket_timeout != NULL && h2o_lcstris(req->upgrade.base, req->upgrade.len, H2O_STRLIT("websocket"))) { self->is_websocket_handshake = 1; } else { self->is_websocket_handshake = 0; } self->up_req.bufs[0] = build_request(req, keepalive, self->is_websocket_handshake); self->up_req.bufs[1] = req->entity; self->up_req.is_head = h2o_memis(req->method.base, req->method.len, H2O_STRLIT("HEAD")); h2o_buffer_init(&self->last_content_before_send, &h2o_socket_buffer_prototype); h2o_doublebuffer_init(&self->sending, &h2o_socket_buffer_prototype); return self; }
/* * This function is used by the server side of the EST proxy to respond to an * incoming CSR Attributes request. This function is similar to the Client API * function, est_client_get_csrattrs(). */ static int est_proxy_handle_csr_attrs (EST_CTX *ctx, void *http_ctx) { int rv = EST_ERR_NONE; int pop_present; char *csr_data, *csr_data_pop; int csr_len, csr_pop_len; EST_CTX *client_ctx; /* * get the client context for this thread */ client_ctx = get_client_ctx(ctx); if (!client_ctx) { EST_LOG_ERR("Unable to obtain client context for proxy operation"); return (EST_ERR_NO_CTX); } /* * Invoke client code to retrieve the CSR attributes. * Note: there is no need to authenticate the client (see sec 4.5) */ EST_LOG_INFO("Proxy get csr attributes"); rv = est_client_get_csrattrs(client_ctx, (unsigned char **)&csr_data, &csr_len); /* * csr_data points to the memory allocated to hold the csr attributes, * which will be freed in this call stack. To prevent a double-free * we null the to pointer on the client context. */ client_ctx->retrieved_csrattrs = NULL; client_ctx->retrieved_csrattrs_len = 0; if (rv == EST_ERR_NONE) { ctx->csr_pop_present = 0; if (ctx->server_enable_pop) { rv = est_is_challengePassword_present(csr_data, csr_len, &pop_present); if (rv != EST_ERR_NONE) { EST_LOG_ERR("Error during PoP/sanity check"); est_send_http_error(ctx, http_ctx, EST_ERR_HTTP_NO_CONTENT); return (EST_ERR_NONE); } ctx->csr_pop_present = pop_present; if (!ctx->csr_pop_present) { if (csr_len == 0) { csr_data = malloc(EST_CSRATTRS_POP_LEN + 1); if (!csr_data) { return (EST_ERR_MALLOC); } strncpy(csr_data, EST_CSRATTRS_POP, EST_CSRATTRS_POP_LEN); csr_data[EST_CSRATTRS_POP_LEN] = 0; csr_len = EST_CSRATTRS_POP_LEN; return (est_send_csrattr_data(ctx, csr_data, csr_len, http_ctx)); } rv = est_add_challengePassword(csr_data, csr_len, &csr_data_pop, &csr_pop_len); if (rv != EST_ERR_NONE) { if (csr_data) { free(csr_data); } EST_LOG_ERR("Error during add PoP"); est_send_http_error(ctx, http_ctx, EST_ERR_HTTP_NO_CONTENT); return (EST_ERR_NONE); } if (csr_data) { free(csr_data); } csr_data = csr_data_pop; csr_len = csr_pop_len; } } } else { EST_LOG_ERR("Server not reachable or sent corrupt attributes"); est_send_http_error(ctx, http_ctx, EST_ERR_HTTP_NO_CONTENT); return (EST_ERR_NONE); } return (est_send_csrattr_data(ctx, csr_data, csr_len, http_ctx)); }
/* * This function is used by the server side of the EST proxy to respond to an * incoming Simple Enroll request. This function is similar to the Client API * function, est_client_enroll_req(), except it bypasses some things that are * not done when functioning as a proxy, such as signing the CSR, not * inserting the TLS unique id and instead including the id-kp-cmcRA usage * extension. */ static EST_ERROR est_proxy_handle_simple_enroll (EST_CTX *ctx, void *http_ctx, SSL *ssl, const char *ct, char *body, int body_len, int reenroll) { EST_ERROR rv; BUF_MEM *pkcs10; unsigned char *pkcs7; int pkcs7_len = 0; X509_REQ *csr = NULL; EST_CTX *client_ctx; /* * Make sure the client has sent us a PKCS10 CSR request */ if (strcmp(ct, "application/pkcs10")) { return (EST_ERR_BAD_CONTENT_TYPE); } /* * Authenticate the client */ switch (est_enroll_auth(ctx, http_ctx, ssl, reenroll)) { case EST_HTTP_AUTH: case EST_SRP_AUTH: case EST_CERT_AUTH: break; case EST_HTTP_AUTH_PENDING: return (EST_ERR_AUTH_PENDING); break; case EST_UNAUTHORIZED: default: return (EST_ERR_AUTH_FAIL); break; } /* * Parse the PKCS10 CSR from the client */ csr = est_server_parse_csr((unsigned char*)body, body_len); if (!csr) { EST_LOG_ERR("Unable to parse the PKCS10 CSR sent by the client"); return (EST_ERR_BAD_PKCS10); } /* * Perform a sanity check on the CSR */ if (est_server_check_csr(csr)) { EST_LOG_ERR("PKCS10 CSR sent by the client failed sanity check"); X509_REQ_free(csr); return (EST_ERR_BAD_PKCS10); } /* * Do the PoP check (Proof of Possession). The challenge password * in the pkcs10 request should match the TLS unique ID. */ rv = est_tls_uid_auth(ctx, ssl, csr); X509_REQ_free(csr); if (rv != EST_ERR_NONE) { return (EST_ERR_AUTH_FAIL_TLSUID); } /* * body now points to the pkcs10 data, pass * this to the enrollment routine. Need to hi-jack * a BUF_MEM. Attach the body to a new BUF_MEM */ pkcs10 = BUF_MEM_new(); pkcs10->data = body; pkcs10->length = body_len; pkcs10->max = body_len; /* * get the client context for this thread */ client_ctx = get_client_ctx(ctx); if (!client_ctx) { EST_LOG_ERR("Unable to obtain client context for proxy operation"); est_proxy_free_ossl_bufmem(pkcs10); return (EST_ERR_NO_CTX); } /* * Allocate some space to hold the cert that we * expect to receive from the EST server. */ pkcs7 = malloc(EST_CA_MAX); /* * Attempt to enroll the CSR from the client */ rv = est_proxy_send_enroll_request(client_ctx, pkcs10, pkcs7, &pkcs7_len, reenroll); /* * Handle any errors that likely occurred */ switch (rv) { case EST_ERR_AUTH_FAIL: /* Try one more time if we're doing Digest auth */ if ((ctx->auth_mode == AUTH_DIGEST || ctx->auth_mode == AUTH_BASIC || ctx->auth_mode == AUTH_TOKEN)) { EST_LOG_INFO("HTTP Auth failed, trying again with digest/basic parameters"); rv = est_proxy_send_enroll_request(client_ctx, pkcs10, pkcs7, &pkcs7_len, reenroll); if (rv == EST_ERR_CA_ENROLL_RETRY) { rv = est_proxy_propagate_retry(client_ctx, http_ctx); } else if (rv != EST_ERR_NONE) { EST_LOG_WARN("EST enrollment failed, error code is %d", rv); } } break; case EST_ERR_CA_ENROLL_RETRY: rv = est_proxy_propagate_retry(client_ctx, http_ctx); break; default: EST_LOG_WARN("Initial EST enrollment request error code is %d", rv); break; } client_ctx->auth_mode = AUTH_NONE; /* * Prevent OpenSSL from freeing our data */ est_proxy_free_ossl_bufmem(pkcs10); /* * If we have a cert response from the EST server, let's forward * it back to the EST client */ if (pkcs7_len > 0) { rv = est_proxy_propagate_pkcs7(http_ctx, pkcs7, pkcs7_len); } free(pkcs7); return (rv); }
/* * est_proxy_retrieve_cacerts() issues a request to the server to obtain the * CA Certs chain to be used for Get CA Certs requests from clients. * The CA Cert chain returned from the server are passed back to the caller. * * It's the responsibility of the caller to free up this buffer. */ EST_ERROR est_proxy_retrieve_cacerts (EST_CTX *ctx, unsigned char **cacerts_rtn, int *cacerts_rtn_len) { EST_CTX *client_ctx; EST_ERROR rv; int rcvd_cacerts_len; unsigned char *rcvd_cacerts; if (ctx == NULL) { EST_LOG_ERR("Ctx not passed to %s", __FUNCTION__); return (EST_ERR_NO_CTX); } if (cacerts_rtn == NULL || cacerts_rtn_len == NULL) { EST_LOG_ERR("Ctx not passed to %s", __FUNCTION__); return (EST_ERR_INVALID_PARAMETERS); } *cacerts_rtn = NULL; *cacerts_rtn_len = 0; /* * Get the client context for this thread */ client_ctx = get_client_ctx(ctx); if (!client_ctx) { EST_LOG_ERR("Unable to obtain client context for proxy operation"); return (EST_ERR_NO_CTX); } rv = est_client_get_cacerts(client_ctx, &rcvd_cacerts_len); if (rv != EST_ERR_NONE) { EST_LOG_ERR("Unable to retrieve CA Certs from upstream server RC = %s", EST_ERR_NUM_TO_STR(rv)); return (rv); } /* * Allocate a buffer to retrieve the CA certs * and get them copied in */ rcvd_cacerts = malloc(rcvd_cacerts_len); if (rcvd_cacerts == NULL) { EST_LOG_ERR("Unable to malloc buffer for cacerts received from server"); return (EST_ERR_MALLOC); } rv = est_client_copy_cacerts(client_ctx, rcvd_cacerts); if (rv != EST_ERR_NONE) { EST_LOG_ERR("Unable to copy CA Certs from upstream server RC = %s", EST_ERR_NUM_TO_STR(rv)); free(rcvd_cacerts); return (rv); } /* * The retrieving of the CA certs through the normal client * interface causes the client to go back into an uninitialized state. * In this case though, we're getting it just for passing it back * to the downstream clients, so we're going to put this client * context back into the initialized state */ client_ctx->est_client_initialized = 1; *cacerts_rtn = rcvd_cacerts; *cacerts_rtn_len = rcvd_cacerts_len; return (EST_ERR_NONE); }
static h2o_http1client_body_cb on_head(h2o_http1client_t *client, const char *errstr, int minor_version, int status, h2o_iovec_t msg, struct phr_header *headers, size_t num_headers) { struct rp_generator_t *self = client->data; h2o_req_t *req = self->src_req; size_t i; if (errstr != NULL && errstr != h2o_http1client_error_is_eos) { self->client = NULL; h2o_req_log_error(req, "lib/core/proxy.c", "%s", errstr); h2o_send_error(req, 502, "Gateway Error", errstr, 0); return NULL; } /* copy the response (note: all the headers must be copied; http1client discards the input once we return from this callback) */ req->res.status = status; req->res.reason = h2o_strdup(&req->pool, msg.base, msg.len).base; for (i = 0; i != num_headers; ++i) { const h2o_token_t *token = h2o_lookup_token(headers[i].name, headers[i].name_len); h2o_iovec_t value; if (token != NULL) { if (token->proxy_should_drop) { goto Skip; } if (token == H2O_TOKEN_CONTENT_LENGTH) { if (req->res.content_length != SIZE_MAX || (req->res.content_length = h2o_strtosize(headers[i].value, headers[i].value_len)) == SIZE_MAX) { self->client = NULL; h2o_req_log_error(req, "lib/core/proxy.c", "%s", "invalid response from upstream (malformed content-length)"); h2o_send_error(req, 502, "Gateway Error", "invalid response from upstream", 0); return NULL; } goto Skip; } else if (token == H2O_TOKEN_LOCATION) { if (req->res_is_delegated && (300 <= status && status <= 399) && status != 304) { self->client = NULL; h2o_iovec_t method = h2o_get_redirect_method(req->method, status); h2o_send_redirect_internal(req, method, headers[i].value, headers[i].value_len, 1); return NULL; } if (req->overrides != NULL && req->overrides->location_rewrite.match != NULL) { value = rewrite_location(&req->pool, headers[i].value, headers[i].value_len, req->overrides->location_rewrite.match, req->input.scheme, req->input.authority, req->overrides->location_rewrite.path_prefix); if (value.base != NULL) goto AddHeader; } goto AddHeaderDuped; } else if (token == H2O_TOKEN_LINK) { h2o_puth_path_in_link_header(req, headers[i].value, headers[i].value_len); } /* default behaviour, transfer the header downstream */ AddHeaderDuped: value = h2o_strdup(&req->pool, headers[i].value, headers[i].value_len); AddHeader: h2o_add_header(&req->pool, &req->res.headers, token, value.base, value.len); Skip: ; } else { h2o_iovec_t name = h2o_strdup(&req->pool, headers[i].name, headers[i].name_len); h2o_iovec_t value = h2o_strdup(&req->pool, headers[i].value, headers[i].value_len); h2o_add_header_by_str(&req->pool, &req->res.headers, name.base, name.len, 0, value.base, value.len); } } if (self->is_websocket_handshake && req->res.status == 101) { h2o_http1client_ctx_t *client_ctx = get_client_ctx(req); assert(client_ctx->websocket_timeout != NULL); h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_UPGRADE, H2O_STRLIT("websocket")); on_websocket_upgrade(self, client_ctx->websocket_timeout); self->client = NULL; return NULL; } /* declare the start of the response */ h2o_start_response(req, &self->super); if (errstr == h2o_http1client_error_is_eos) { self->client = NULL; h2o_send(req, NULL, 0, 1); return NULL; } return on_body; }