s4:service_stream: s/private/private_data
[sfrench/samba-autobuild/.git] / source4 / web_server / web_server.c
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 */
42 static 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 */
51 static 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 */
66 void 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
79 void 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
99 void 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 */
111 NTSTATUS 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 */
145 static 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
212 failed:
213         stream_terminate_connection(conn, "websrv_recv: failed");
214 }
215
216
217
218 /*
219   called when a web connection becomes writable
220 */
221 static 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 */
252 static 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
285 failed:
286         talloc_free(conn);
287 }
288
289
290 static 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 */
300 static 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
355 failed:
356         task_server_terminate(task, "websrv_task_init: failed to startup web server task");
357 }
358
359
360 /* called at smbd startup - register ourselves as a server service */
361 NTSTATUS server_service_web_init(void)
362 {
363         return register_server_service("web", websrv_task_init);
364 }