1/* 2 Unix SMB/CIFS implementation. 3 4 web server startup 5 6 Copyright (C) Andrew Tridgell 2005 7 Copyright (C) Jelmer Vernooij 2008 8 9 This program is free software; you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation; either version 3 of the License, or 12 (at your option) any later version. 13 14 This program is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with this program. If not, see <http://www.gnu.org/licenses/>. 21*/ 22 23#include "includes.h" 24#include "smbd/service_task.h" 25#include "smbd/service_stream.h" 26#include "smbd/service.h" 27#include "web_server/web_server.h" 28#include "lib/events/events.h" 29#include "system/filesys.h" 30#include "system/network.h" 31#include "lib/socket/netif.h" 32#include "lib/tls/tls.h" 33#include "../lib/util/dlinklist.h" 34#include "param/param.h" 35 36/* don't allow connections to hang around forever */ 37#define HTTP_TIMEOUT 120 38 39/* 40 destroy a web connection 41*/ 42static int websrv_destructor(struct websrv_context *web) 43{ 44 return 0; 45} 46 47/* 48 called when a connection times out. This prevents a stuck connection 49 from hanging around forever 50*/ 51static void websrv_timeout(struct tevent_context *event_context, 52 struct tevent_timer *te, 53 struct timeval t, void *private_data) 54{ 55 struct websrv_context *web = talloc_get_type(private_data, struct websrv_context); 56 struct stream_connection *conn = web->conn; 57 web->conn = NULL; 58 /* TODO: send a message to any running esp context on this connection 59 to stop running */ 60 stream_terminate_connection(conn, "websrv_timeout: timed out"); 61} 62 63/* 64 setup for a raw http level error 65*/ 66void http_error(struct websrv_context *web, const char *status, const char *info) 67{ 68 char *s; 69 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", 70 status, status, info); 71 if (s == NULL) { 72 stream_terminate_connection(web->conn, "http_error: out of memory"); 73 return; 74 } 75 websrv_output_headers(web, status, NULL); 76 websrv_output(web, s, strlen(s)); 77} 78 79void websrv_output_headers(struct websrv_context *web, const char *status, struct http_header *headers) 80{ 81 char *s; 82 DATA_BLOB b; 83 struct http_header *hdr; 84 85 s = talloc_asprintf(web, "HTTP/1.0 %s\r\n", status); 86 if (s == NULL) return; 87 for (hdr = headers; hdr; hdr = hdr->next) { 88 s = talloc_asprintf_append_buffer(s, "%s: %s\r\n", hdr->name, hdr->value); 89 } 90 91 s = talloc_asprintf_append_buffer(s, "\r\n"); 92 93 b = web->output.content; 94 web->output.content = data_blob_string_const(s); 95 websrv_output(web, b.data, b.length); 96 data_blob_free(&b); 97} 98 99void websrv_output(struct websrv_context *web, void *data, size_t length) 100{ 101 data_blob_append(web, &web->output.content, data, length); 102 EVENT_FD_NOT_READABLE(web->conn->event.fde); 103 EVENT_FD_WRITEABLE(web->conn->event.fde); 104 web->output.output_pending = true; 105} 106 107 108/* 109 parse one line of header input 110*/ 111NTSTATUS http_parse_header(struct websrv_context *web, const char *line) 112{ 113 if (line[0] == 0) { 114 web->input.end_of_headers = true; 115 } else if (strncasecmp(line,"GET ", 4)==0) { 116 web->input.url = talloc_strndup(web, &line[4], strcspn(&line[4], " \t")); 117 } else if (strncasecmp(line,"POST ", 5)==0) { 118 web->input.post_request = true; 119 web->input.url = talloc_strndup(web, &line[5], strcspn(&line[5], " \t")); 120 } else if (strchr(line, ':') == NULL) { 121 http_error(web, "400 Bad request", "This server only accepts GET and POST requests"); 122 return NT_STATUS_INVALID_PARAMETER; 123 } else if (strncasecmp(line, "Content-Length: ", 16)==0) { 124 web->input.content_length = strtoul(&line[16], NULL, 10); 125 } else { 126 struct http_header *hdr = talloc_zero(web, struct http_header); 127 char *colon = strchr(line, ':'); 128 if (colon == NULL) { 129 http_error(web, "500 Internal Server Error", "invalidly formatted header"); 130 return NT_STATUS_INVALID_PARAMETER; 131 } 132 133 hdr->name = talloc_strndup(hdr, line, colon-line); 134 hdr->value = talloc_strdup(hdr, colon+1); 135 DLIST_ADD(web->input.headers, hdr); 136 } 137 138 /* ignore all other headers for now */ 139 return NT_STATUS_OK; 140} 141 142/* 143 called when a web connection becomes readable 144*/ 145static void websrv_recv(struct stream_connection *conn, uint16_t flags) 146{ 147 struct web_server_data *wdata; 148 struct websrv_context *web = talloc_get_type(conn->private_data, 149 struct websrv_context); 150 NTSTATUS status; 151 uint8_t buf[1024]; 152 size_t nread; 153 uint8_t *p; 154 DATA_BLOB b; 155 156 /* not the most efficient http parser ever, but good enough for us */ 157 status = socket_recv(conn->socket, buf, sizeof(buf), &nread); 158 if (NT_STATUS_IS_ERR(status)) goto failed; 159 if (!NT_STATUS_IS_OK(status)) return; 160 161 if (!data_blob_append(web, &web->input.partial, buf, nread)) 162 goto failed; 163 164 /* parse any lines that are available */ 165 b = web->input.partial; 166 while (!web->input.end_of_headers && 167 (p=(uint8_t *)memchr(b.data, '\n', b.length))) { 168 const char *line = (const char *)b.data; 169 *p = 0; 170 if (p != b.data && p[-1] == '\r') { 171 p[-1] = 0; 172 } 173 status = http_parse_header(web, line); 174 if (!NT_STATUS_IS_OK(status)) return; 175 b.length -= (p - b.data) + 1; 176 b.data = p+1; 177 } 178 179 /* keep any remaining bytes in web->input.partial */ 180 if (b.length == 0) { 181 b.data = NULL; 182 } 183 b = data_blob_talloc(web, b.data, b.length); 184 data_blob_free(&web->input.partial); 185 web->input.partial = b; 186 187 /* we finish when we have both the full headers (terminated by 188 a blank line) and any post data, as indicated by the 189 content_length */ 190 if (web->input.end_of_headers && 191 web->input.partial.length >= web->input.content_length) { 192 if (web->input.partial.length > web->input.content_length) { 193 web->input.partial.data[web->input.content_length] = 0; 194 } 195 EVENT_FD_NOT_READABLE(web->conn->event.fde); 196 197 /* the reference/unlink code here is quite subtle. It 198 is needed because the rendering of the web-pages, and 199 in particular the esp/ejs backend, is semi-async. So 200 we could well end up in the connection timeout code 201 while inside http_process_input(), but we must not 202 destroy the stack variables being used by that 203 rendering process when we handle the timeout. */ 204 if (!talloc_reference(web->task, web)) goto failed; 205 wdata = talloc_get_type(web->task->private_data, struct web_server_data); 206 if (wdata == NULL) goto failed; 207 wdata->http_process_input(wdata, web); 208 talloc_unlink(web->task, web); 209 } 210 return; 211 212failed: 213 stream_terminate_connection(conn, "websrv_recv: failed"); 214} 215 216 217 218/* 219 called when a web connection becomes writable 220*/ 221static void websrv_send(struct stream_connection *conn, uint16_t flags) 222{ 223 struct websrv_context *web = talloc_get_type(conn->private_data, 224 struct websrv_context); 225 NTSTATUS status; 226 size_t nsent; 227 DATA_BLOB b; 228 229 b = web->output.content; 230 b.data += web->output.nsent; 231 b.length -= web->output.nsent; 232 233 status = socket_send(conn->socket, &b, &nsent); 234 if (NT_STATUS_IS_ERR(status)) { 235 stream_terminate_connection(web->conn, "socket_send: failed"); 236 return; 237 } 238 if (!NT_STATUS_IS_OK(status)) { 239 return; 240 } 241 242 web->output.nsent += nsent; 243 244 if (web->output.content.length == web->output.nsent) { 245 stream_terminate_connection(web->conn, "websrv_send: finished sending"); 246 } 247} 248 249/* 250 establish a new connection to the web server 251*/ 252static void websrv_accept(struct stream_connection *conn) 253{ 254 struct task_server *task = talloc_get_type(conn->private_data, struct task_server); 255 struct web_server_data *wdata = talloc_get_type(task->private_data, struct web_server_data); 256 struct websrv_context *web; 257 struct socket_context *tls_socket; 258 259 web = talloc_zero(conn, struct websrv_context); 260 if (web == NULL) goto failed; 261 262 web->task = task; 263 web->conn = conn; 264 conn->private_data = web; 265 talloc_set_destructor(web, websrv_destructor); 266 267 event_add_timed(conn->event.ctx, web, 268 timeval_current_ofs(HTTP_TIMEOUT, 0), 269 websrv_timeout, web); 270 271 /* Overwrite the socket with a (possibly) TLS socket */ 272 tls_socket = tls_init_server(wdata->tls_params, conn->socket, 273 conn->event.fde, "GPHO"); 274 /* We might not have TLS, or it might not have initilised */ 275 if (tls_socket) { 276 talloc_unlink(conn, conn->socket); 277 talloc_steal(conn, tls_socket); 278 conn->socket = tls_socket; 279 } else { 280 DEBUG(3, ("TLS not available for web_server connections\n")); 281 } 282 283 return; 284 285failed: 286 talloc_free(conn); 287} 288 289 290static const struct stream_server_ops web_stream_ops = { 291 .name = "web", 292 .accept_connection = websrv_accept, 293 .recv_handler = websrv_recv, 294 .send_handler = websrv_send, 295}; 296 297/* 298 startup the web server task 299*/ 300static void websrv_task_init(struct task_server *task) 301{ 302 NTSTATUS status; 303 uint16_t port = lp_web_port(task->lp_ctx); 304 const struct model_ops *model_ops; 305 struct web_server_data *wdata; 306 307 task_server_set_title(task, "task[websrv]"); 308 309 /* run the web server as a single process */ 310 model_ops = process_model_startup(task->event_ctx, "single"); 311 if (!model_ops) goto failed; 312 313 if (lp_interfaces(task->lp_ctx) && lp_bind_interfaces_only(task->lp_ctx)) { 314 int num_interfaces; 315 int i; 316 struct interface *ifaces; 317 318 load_interfaces(NULL, lp_interfaces(task->lp_ctx), &ifaces); 319 320 num_interfaces = iface_count(ifaces); 321 for(i = 0; i < num_interfaces; i++) { 322 const char *address = iface_n_ip(ifaces, i); 323 status = stream_setup_socket(task->event_ctx, 324 task->lp_ctx, model_ops, 325 &web_stream_ops, 326 "ipv4", address, 327 &port, lp_socket_options(task->lp_ctx), 328 task); 329 if (!NT_STATUS_IS_OK(status)) goto failed; 330 } 331 332 talloc_free(ifaces); 333 } else { 334 status = stream_setup_socket(task->event_ctx, task->lp_ctx, 335 model_ops, &web_stream_ops, 336 "ipv4", lp_socket_address(task->lp_ctx), 337 &port, lp_socket_options(task->lp_ctx), task); 338 if (!NT_STATUS_IS_OK(status)) goto failed; 339 } 340 341 /* startup the esp processor - unfortunately we can't do this 342 per connection as that wouldn't allow for session variables */ 343 wdata = talloc_zero(task, struct web_server_data); 344 if (wdata == NULL)goto failed; 345 346 task->private_data = wdata; 347 348 wdata->tls_params = tls_initialise(wdata, task->lp_ctx); 349 if (wdata->tls_params == NULL) goto failed; 350 351 if (!wsgi_initialize(wdata)) goto failed; 352 353 return; 354 355failed: 356 task_server_terminate(task, "websrv_task_init: failed to startup web server task", true); 357} 358 359 360/* called at smbd startup - register ourselves as a server service */ 361NTSTATUS server_service_web_init(void) 362{ 363 return register_server_service("web", websrv_task_init); 364} 365