mirror of
https://github.com/samba-team/samba.git
synced 2024-12-25 23:21:54 +03:00
e9a589feac
When one of our core tasks fails to initialise it can now ask for the server as a whole to die, rather than limping along in a degraded state.
365 lines
10 KiB
C
365 lines
10 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
|
|
web server startup
|
|
|
|
Copyright (C) Andrew Tridgell 2005
|
|
Copyright (C) Jelmer Vernooij 2008
|
|
|
|
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 "../lib/util/dlinklist.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)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
called when a connection times out. This prevents a stuck connection
|
|
from hanging around forever
|
|
*/
|
|
static void websrv_timeout(struct tevent_context *event_context,
|
|
struct tevent_timer *te,
|
|
struct timeval t, void *private_data)
|
|
{
|
|
struct websrv_context *web = talloc_get_type(private_data, 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");
|
|
}
|
|
|
|
/*
|
|
setup for a raw http level error
|
|
*/
|
|
void http_error(struct websrv_context *web, const char *status, const char *info)
|
|
{
|
|
char *s;
|
|
s = talloc_asprintf(web,"<HTML><HEAD><TITLE>Error %s</TITLE></HEAD><BODY><H1>Error %s</H1><pre>%s</pre><p></BODY></HTML>\r\n\r\n",
|
|
status, status, info);
|
|
if (s == NULL) {
|
|
stream_terminate_connection(web->conn, "http_error: out of memory");
|
|
return;
|
|
}
|
|
websrv_output_headers(web, status, NULL);
|
|
websrv_output(web, s, strlen(s));
|
|
}
|
|
|
|
void websrv_output_headers(struct websrv_context *web, const char *status, struct http_header *headers)
|
|
{
|
|
char *s;
|
|
DATA_BLOB b;
|
|
struct http_header *hdr;
|
|
|
|
s = talloc_asprintf(web, "HTTP/1.0 %s\r\n", status);
|
|
if (s == NULL) return;
|
|
for (hdr = headers; hdr; hdr = hdr->next) {
|
|
s = talloc_asprintf_append_buffer(s, "%s: %s\r\n", hdr->name, hdr->value);
|
|
}
|
|
|
|
s = talloc_asprintf_append_buffer(s, "\r\n");
|
|
|
|
b = web->output.content;
|
|
web->output.content = data_blob_string_const(s);
|
|
websrv_output(web, b.data, b.length);
|
|
data_blob_free(&b);
|
|
}
|
|
|
|
void websrv_output(struct websrv_context *web, void *data, size_t length)
|
|
{
|
|
data_blob_append(web, &web->output.content, data, length);
|
|
EVENT_FD_NOT_READABLE(web->conn->event.fde);
|
|
EVENT_FD_WRITEABLE(web->conn->event.fde);
|
|
web->output.output_pending = true;
|
|
}
|
|
|
|
|
|
/*
|
|
parse one line of header input
|
|
*/
|
|
NTSTATUS http_parse_header(struct websrv_context *web, const char *line)
|
|
{
|
|
if (line[0] == 0) {
|
|
web->input.end_of_headers = true;
|
|
} else if (strncasecmp(line,"GET ", 4)==0) {
|
|
web->input.url = talloc_strndup(web, &line[4], strcspn(&line[4], " \t"));
|
|
} else if (strncasecmp(line,"POST ", 5)==0) {
|
|
web->input.post_request = true;
|
|
web->input.url = talloc_strndup(web, &line[5], strcspn(&line[5], " \t"));
|
|
} else if (strchr(line, ':') == NULL) {
|
|
http_error(web, "400 Bad request", "This server only accepts GET and POST requests");
|
|
return NT_STATUS_INVALID_PARAMETER;
|
|
} else if (strncasecmp(line, "Content-Length: ", 16)==0) {
|
|
web->input.content_length = strtoul(&line[16], NULL, 10);
|
|
} else {
|
|
struct http_header *hdr = talloc_zero(web, struct http_header);
|
|
char *colon = strchr(line, ':');
|
|
if (colon == NULL) {
|
|
http_error(web, "500 Internal Server Error", "invalidly formatted header");
|
|
return NT_STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
hdr->name = talloc_strndup(hdr, line, colon-line);
|
|
hdr->value = talloc_strdup(hdr, colon+1);
|
|
DLIST_ADD(web->input.headers, hdr);
|
|
}
|
|
|
|
/* ignore all other headers for now */
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
/*
|
|
called when a web connection becomes readable
|
|
*/
|
|
static void websrv_recv(struct stream_connection *conn, uint16_t flags)
|
|
{
|
|
struct web_server_data *wdata;
|
|
struct websrv_context *web = talloc_get_type(conn->private_data,
|
|
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;
|
|
wdata = talloc_get_type(web->task->private_data, struct web_server_data);
|
|
if (wdata == NULL) goto failed;
|
|
wdata->http_process_input(wdata, 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_data,
|
|
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;
|
|
|
|
if (web->output.content.length == web->output.nsent) {
|
|
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_data, struct task_server);
|
|
struct web_server_data *wdata = talloc_get_type(task->private_data, struct web_server_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_data = web;
|
|
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(wdata->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;
|
|
struct web_server_data *wdata;
|
|
|
|
task_server_set_title(task, "task[websrv]");
|
|
|
|
/* run the web server as a single process */
|
|
model_ops = process_model_startup(task->event_ctx, "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 */
|
|
wdata = talloc_zero(task, struct web_server_data);
|
|
if (wdata == NULL)goto failed;
|
|
|
|
task->private_data = wdata;
|
|
|
|
wdata->tls_params = tls_initialise(wdata, task->lp_ctx);
|
|
if (wdata->tls_params == NULL) goto failed;
|
|
|
|
if (!wsgi_initialize(wdata)) goto failed;
|
|
|
|
return;
|
|
|
|
failed:
|
|
task_server_terminate(task, "websrv_task_init: failed to startup web server task", true);
|
|
}
|
|
|
|
|
|
/* called at smbd startup - register ourselves as a server service */
|
|
NTSTATUS server_service_web_init(void)
|
|
{
|
|
return register_server_service("web", websrv_task_init);
|
|
}
|