r20519: Web Application Framework
[jelmer/samba4-debian.git] / source / web_server / web_server.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    web server startup
5
6    Copyright (C) Andrew Tridgell 2005
7    
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 2 of the License, or
11    (at your option) any later version.
12    
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17    
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
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
34 /* don't allow connections to hang around forever */
35 #define HTTP_TIMEOUT 120
36
37 /*
38   destroy a web connection
39 */
40 static int websrv_destructor(struct websrv_context *web)
41 {
42         if (web->output.fd != -1) {
43                 close(web->output.fd);
44         }
45         return 0;
46 }
47
48 /*
49   called when a connection times out. This prevents a stuck connection
50   from hanging around forever
51 */
52 static void websrv_timeout(struct event_context *event_context, 
53                            struct timed_event *te, 
54                            struct timeval t, void *private)
55 {
56         struct websrv_context *web = talloc_get_type(private, struct websrv_context);
57         struct stream_connection *conn = web->conn;
58         web->conn = NULL;
59         /* TODO: send a message to any running esp context on this connection
60            to stop running */
61         stream_terminate_connection(conn, "websrv_timeout: timed out"); 
62 }
63
64 /*
65   called when a web connection becomes readable
66 */
67 static void websrv_recv(struct stream_connection *conn, uint16_t flags)
68 {
69         struct websrv_context *web = talloc_get_type(conn->private, 
70                                                      struct websrv_context);
71         NTSTATUS status;
72         uint8_t buf[1024];
73         size_t nread;
74         uint8_t *p;
75         DATA_BLOB b;
76
77         /* not the most efficient http parser ever, but good enough for us */
78         status = socket_recv(conn->socket, buf, sizeof(buf), &nread);
79         if (NT_STATUS_IS_ERR(status)) goto failed;
80         if (!NT_STATUS_IS_OK(status)) return;
81
82         status = data_blob_append(web, &web->input.partial, buf, nread);
83         if (!NT_STATUS_IS_OK(status)) goto failed;
84
85         /* parse any lines that are available */
86         b = web->input.partial;
87         while (!web->input.end_of_headers &&
88                (p=memchr(b.data, '\n', b.length))) {
89                 const char *line = (const char *)b.data;
90                 *p = 0;
91                 if (p != b.data && p[-1] == '\r') {
92                         p[-1] = 0;
93                 }
94                 status = http_parse_header(web, line);
95                 if (!NT_STATUS_IS_OK(status)) return;
96                 b.length -= (p - b.data) + 1;
97                 b.data = p+1;
98         }
99
100         /* keep any remaining bytes in web->input.partial */
101         if (b.length == 0) {
102                 b.data = NULL;
103         }
104         b = data_blob_talloc(web, b.data, b.length);
105         data_blob_free(&web->input.partial);
106         web->input.partial = b;
107
108         /* we finish when we have both the full headers (terminated by
109            a blank line) and any post data, as indicated by the
110            content_length */
111         if (web->input.end_of_headers &&
112             web->input.partial.length >= web->input.content_length) {
113                 if (web->input.partial.length > web->input.content_length) {
114                         web->input.partial.data[web->input.content_length] = 0;
115                 }
116                 EVENT_FD_NOT_READABLE(web->conn->event.fde);
117
118                 /* the reference/unlink code here is quite subtle. It
119                  is needed because the rendering of the web-pages, and
120                  in particular the esp/ejs backend, is semi-async.  So
121                  we could well end up in the connection timeout code
122                  while inside http_process_input(), but we must not
123                  destroy the stack variables being used by that
124                  rendering process when we handle the timeout. */
125                 if (!talloc_reference(web->task, web)) goto failed;
126                 http_process_input(web);
127                 talloc_unlink(web->task, web);
128         }
129         return;
130
131 failed:
132         stream_terminate_connection(conn, "websrv_recv: failed");
133 }
134
135
136 /*
137   called when a web connection becomes writable
138 */
139 static void websrv_send(struct stream_connection *conn, uint16_t flags)
140 {
141         struct websrv_context *web = talloc_get_type(conn->private, 
142                                                      struct websrv_context);
143         NTSTATUS status;
144         size_t nsent;
145         DATA_BLOB b;
146
147         b = web->output.content;
148         b.data += web->output.nsent;
149         b.length -= web->output.nsent;
150
151         status = socket_send(conn->socket, &b, &nsent);
152         if (NT_STATUS_IS_ERR(status)) {
153                 stream_terminate_connection(web->conn, "socket_send: failed");
154                 return;
155         }
156         if (!NT_STATUS_IS_OK(status)) {
157                 return;
158         }
159
160         web->output.nsent += nsent;
161
162         /* possibly read some more raw data from a file */
163         if (web->output.content.length == web->output.nsent && 
164             web->output.fd != -1) {
165                 uint8_t buf[2048];
166                 ssize_t nread;
167
168                 data_blob_free(&web->output.content);
169                 web->output.nsent = 0;
170
171                 nread = read(web->output.fd, buf, sizeof(buf));
172                 if (nread == -1 && errno == EINTR) {
173                         return;
174                 }
175                 if (nread <= 0) {
176                         close(web->output.fd);
177                         web->output.fd = -1;
178                         nread = 0;
179                 }
180                 web->output.content = data_blob_talloc(web, buf, nread);
181         }
182
183         if (web->output.content.length == web->output.nsent && 
184             web->output.fd == -1) {
185                 stream_terminate_connection(web->conn, "websrv_send: finished sending");
186         }
187 }
188
189 /*
190   establish a new connection to the web server
191 */
192 static void websrv_accept(struct stream_connection *conn)
193 {
194         struct task_server *task = talloc_get_type(conn->private, struct task_server);
195         struct esp_data *edata = talloc_get_type(task->private, struct esp_data);
196         struct websrv_context *web;
197         struct socket_context *tls_socket;
198
199         web = talloc_zero(conn, struct websrv_context);
200         if (web == NULL) goto failed;
201
202         web->task = task;
203         web->conn = conn;
204         conn->private = web;
205         web->output.fd = -1;
206         talloc_set_destructor(web, websrv_destructor);
207
208         event_add_timed(conn->event.ctx, web, 
209                         timeval_current_ofs(HTTP_TIMEOUT, 0),
210                         websrv_timeout, web);
211
212         /* Overwrite the socket with a (possibly) TLS socket */
213         tls_socket = tls_init_server(edata->tls_params, conn->socket, 
214                                      conn->event.fde, "GPHO");
215         /* We might not have TLS, or it might not have initilised */
216         if (tls_socket) {
217                 talloc_unlink(conn, conn->socket);
218                 talloc_steal(conn, tls_socket);
219                 conn->socket = tls_socket;
220         } else {
221                 DEBUG(3, ("TLS not available for web_server connections\n"));
222         }
223
224         return;
225
226 failed:
227         talloc_free(conn);
228 }
229
230
231 static const struct stream_server_ops web_stream_ops = {
232         .name                   = "web",
233         .accept_connection      = websrv_accept,
234         .recv_handler           = websrv_recv,
235         .send_handler           = websrv_send,
236 };
237
238 /*
239   startup the web server task
240 */
241 static void websrv_task_init(struct task_server *task)
242 {
243         NTSTATUS status;
244         uint16_t port = lp_web_port();
245         const struct model_ops *model_ops;
246
247         task_server_set_title(task, "task[websrv]");
248
249         /* run the web server as a single process */
250         model_ops = process_model_byname("single");
251         if (!model_ops) goto failed;
252
253         if (lp_interfaces() && lp_bind_interfaces_only()) {
254                 int num_interfaces = iface_count();
255                 int i;
256                 for(i = 0; i < num_interfaces; i++) {
257                         const char *address = iface_n_ip(i);
258                         status = stream_setup_socket(task->event_ctx, model_ops, 
259                                                      &web_stream_ops, 
260                                                      "ipv4", address, 
261                                                      &port, task);
262                         if (!NT_STATUS_IS_OK(status)) goto failed;
263                 }
264         } else {
265                 status = stream_setup_socket(task->event_ctx, model_ops, 
266                                              &web_stream_ops, 
267                                              "ipv4", lp_socket_address(), 
268                                              &port, task);
269                 if (!NT_STATUS_IS_OK(status)) goto failed;
270         }
271
272         /* startup the esp processor - unfortunately we can't do this
273            per connection as that wouldn't allow for session variables */
274         status = http_setup_esp(task);
275         if (!NT_STATUS_IS_OK(status)) goto failed;
276
277         return;
278
279 failed:
280         task_server_terminate(task, "websrv_task_init: failed to startup web server task");
281 }
282
283
284 /*
285   called on startup of the web server service It's job is to start
286   listening on all configured sockets
287 */
288 static NTSTATUS websrv_init(struct event_context *event_context, 
289                             const struct model_ops *model_ops)
290 {       
291         return task_server_startup(event_context, model_ops, websrv_task_init);
292 }
293
294 /* called at smbd startup - register ourselves as a server service */
295 NTSTATUS server_service_web_init(void)
296 {
297         return register_server_service("web", websrv_init);
298 }