diff options
Diffstat (limited to 'source4/web_server/web_server.c')
-rw-r--r-- | source4/web_server/web_server.c | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/source4/web_server/web_server.c b/source4/web_server/web_server.c new file mode 100644 index 0000000000..ac83a3384d --- /dev/null +++ b/source4/web_server/web_server.c @@ -0,0 +1,297 @@ +/* + Unix SMB/CIFS implementation. + + web server startup + + Copyright (C) Andrew Tridgell 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "smbd/service_task.h" +#include "smbd/service_stream.h" +#include "smbd/service.h" +#include "web_server/web_server.h" +#include "lib/events/events.h" +#include "system/filesys.h" +#include "system/network.h" +#include "lib/socket/netif.h" +#include "lib/tls/tls.h" +#include "param/param.h" + +/* don't allow connections to hang around forever */ +#define HTTP_TIMEOUT 120 + +/* + destroy a web connection +*/ +static int websrv_destructor(struct websrv_context *web) +{ + if (web->output.fd != -1) { + close(web->output.fd); + } + return 0; +} + +/* + called when a connection times out. This prevents a stuck connection + from hanging around forever +*/ +static void websrv_timeout(struct event_context *event_context, + struct timed_event *te, + struct timeval t, void *private) +{ + struct websrv_context *web = talloc_get_type(private, struct websrv_context); + struct stream_connection *conn = web->conn; + web->conn = NULL; + /* TODO: send a message to any running esp context on this connection + to stop running */ + stream_terminate_connection(conn, "websrv_timeout: timed out"); +} + +/* + called when a web connection becomes readable +*/ +static void websrv_recv(struct stream_connection *conn, uint16_t flags) +{ + struct websrv_context *web = talloc_get_type(conn->private, + struct websrv_context); + NTSTATUS status; + uint8_t buf[1024]; + size_t nread; + uint8_t *p; + DATA_BLOB b; + + /* not the most efficient http parser ever, but good enough for us */ + status = socket_recv(conn->socket, buf, sizeof(buf), &nread); + if (NT_STATUS_IS_ERR(status)) goto failed; + if (!NT_STATUS_IS_OK(status)) return; + + if (!data_blob_append(web, &web->input.partial, buf, nread)) + goto failed; + + /* parse any lines that are available */ + b = web->input.partial; + while (!web->input.end_of_headers && + (p=(uint8_t *)memchr(b.data, '\n', b.length))) { + const char *line = (const char *)b.data; + *p = 0; + if (p != b.data && p[-1] == '\r') { + p[-1] = 0; + } + status = http_parse_header(web, line); + if (!NT_STATUS_IS_OK(status)) return; + b.length -= (p - b.data) + 1; + b.data = p+1; + } + + /* keep any remaining bytes in web->input.partial */ + if (b.length == 0) { + b.data = NULL; + } + b = data_blob_talloc(web, b.data, b.length); + data_blob_free(&web->input.partial); + web->input.partial = b; + + /* we finish when we have both the full headers (terminated by + a blank line) and any post data, as indicated by the + content_length */ + if (web->input.end_of_headers && + web->input.partial.length >= web->input.content_length) { + if (web->input.partial.length > web->input.content_length) { + web->input.partial.data[web->input.content_length] = 0; + } + EVENT_FD_NOT_READABLE(web->conn->event.fde); + + /* the reference/unlink code here is quite subtle. It + is needed because the rendering of the web-pages, and + in particular the esp/ejs backend, is semi-async. So + we could well end up in the connection timeout code + while inside http_process_input(), but we must not + destroy the stack variables being used by that + rendering process when we handle the timeout. */ + if (!talloc_reference(web->task, web)) goto failed; + http_process_input(web); + talloc_unlink(web->task, web); + } + return; + +failed: + stream_terminate_connection(conn, "websrv_recv: failed"); +} + + +/* + called when a web connection becomes writable +*/ +static void websrv_send(struct stream_connection *conn, uint16_t flags) +{ + struct websrv_context *web = talloc_get_type(conn->private, + struct websrv_context); + NTSTATUS status; + size_t nsent; + DATA_BLOB b; + + b = web->output.content; + b.data += web->output.nsent; + b.length -= web->output.nsent; + + status = socket_send(conn->socket, &b, &nsent); + if (NT_STATUS_IS_ERR(status)) { + stream_terminate_connection(web->conn, "socket_send: failed"); + return; + } + if (!NT_STATUS_IS_OK(status)) { + return; + } + + web->output.nsent += nsent; + + /* possibly read some more raw data from a file */ + if (web->output.content.length == web->output.nsent && + web->output.fd != -1) { + uint8_t buf[2048]; + ssize_t nread; + + data_blob_free(&web->output.content); + web->output.nsent = 0; + + nread = read(web->output.fd, buf, sizeof(buf)); + if (nread == -1 && errno == EINTR) { + return; + } + if (nread <= 0) { + close(web->output.fd); + web->output.fd = -1; + nread = 0; + } + web->output.content = data_blob_talloc(web, buf, nread); + } + + if (web->output.content.length == web->output.nsent && + web->output.fd == -1) { + stream_terminate_connection(web->conn, "websrv_send: finished sending"); + } +} + +/* + establish a new connection to the web server +*/ +static void websrv_accept(struct stream_connection *conn) +{ + struct task_server *task = talloc_get_type(conn->private, struct task_server); + struct esp_data *edata = talloc_get_type(task->private, struct esp_data); + struct websrv_context *web; + struct socket_context *tls_socket; + + web = talloc_zero(conn, struct websrv_context); + if (web == NULL) goto failed; + + web->task = task; + web->conn = conn; + conn->private = web; + web->output.fd = -1; + talloc_set_destructor(web, websrv_destructor); + + event_add_timed(conn->event.ctx, web, + timeval_current_ofs(HTTP_TIMEOUT, 0), + websrv_timeout, web); + + /* Overwrite the socket with a (possibly) TLS socket */ + tls_socket = tls_init_server(edata->tls_params, conn->socket, + conn->event.fde, "GPHO"); + /* We might not have TLS, or it might not have initilised */ + if (tls_socket) { + talloc_unlink(conn, conn->socket); + talloc_steal(conn, tls_socket); + conn->socket = tls_socket; + } else { + DEBUG(3, ("TLS not available for web_server connections\n")); + } + + return; + +failed: + talloc_free(conn); +} + + +static const struct stream_server_ops web_stream_ops = { + .name = "web", + .accept_connection = websrv_accept, + .recv_handler = websrv_recv, + .send_handler = websrv_send, +}; + +/* + startup the web server task +*/ +static void websrv_task_init(struct task_server *task) +{ + NTSTATUS status; + uint16_t port = lp_web_port(task->lp_ctx); + const struct model_ops *model_ops; + + task_server_set_title(task, "task[websrv]"); + + /* run the web server as a single process */ + model_ops = process_model_byname("single"); + if (!model_ops) goto failed; + + if (lp_interfaces(task->lp_ctx) && lp_bind_interfaces_only(task->lp_ctx)) { + int num_interfaces; + int i; + struct interface *ifaces; + + load_interfaces(NULL, lp_interfaces(task->lp_ctx), &ifaces); + + num_interfaces = iface_count(ifaces); + for(i = 0; i < num_interfaces; i++) { + const char *address = iface_n_ip(ifaces, i); + status = stream_setup_socket(task->event_ctx, + task->lp_ctx, model_ops, + &web_stream_ops, + "ipv4", address, + &port, lp_socket_options(task->lp_ctx), + task); + if (!NT_STATUS_IS_OK(status)) goto failed; + } + + talloc_free(ifaces); + } else { + status = stream_setup_socket(task->event_ctx, task->lp_ctx, + model_ops, &web_stream_ops, + "ipv4", lp_socket_address(task->lp_ctx), + &port, lp_socket_options(task->lp_ctx), task); + if (!NT_STATUS_IS_OK(status)) goto failed; + } + + /* startup the esp processor - unfortunately we can't do this + per connection as that wouldn't allow for session variables */ + status = http_setup_esp(task); + if (!NT_STATUS_IS_OK(status)) goto failed; + + return; + +failed: + task_server_terminate(task, "websrv_task_init: failed to startup web server task"); +} + + +/* called at smbd startup - register ourselves as a server service */ +NTSTATUS server_service_web_init(void) +{ + return register_server_service("web", websrv_task_init); +} |