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