8c6cb1fa3a0ed4524fd0b4463cb97e8fc8b3491e
[samba.git] / source4 / web_server / http.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    http handling code
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 "web_server/web_server.h"
26 #include "smbd/service_stream.h"
27 #include "smbd/service.h"
28 #include "lib/events/events.h"
29 #include "system/time.h"
30 #include "system/wait.h"
31 #include "lib/appweb/esp/esp.h"
32 #include "lib/appweb/ejs/ejsInternal.h"
33 #include "lib/util/dlinklist.h"
34 #include "lib/tls/tls.h"
35 #include "scripting/ejs/smbcalls.h"
36
37 #define SAMBA_SESSION_KEY "SambaSessionId"
38 #define HTTP_PREAUTH_URI  "/scripting/preauth.esp"
39 #define JSONRPC_REQUEST   "/services"
40 #define JSONRPC_SERVER    "/request.esp"
41
42 /* state of the esp subsystem for a specific request */
43 struct esp_state {
44         struct websrv_context *web;
45         struct EspRequest *req;
46         struct MprVar variables[ESP_OBJ_MAX];
47         struct session_data *session;
48 };
49
50 /*
51   output the http headers
52 */
53 static void http_output_headers(struct websrv_context *web)
54 {
55         int i;
56         char *s;
57         DATA_BLOB b;
58         uint32_t content_length = 0;
59         const char *response_string = "Unknown Code";
60         const struct {
61                 unsigned code;
62                 const char *response_string;
63         } codes[] = {
64                 { 200, "OK" },
65                 { 301, "Moved" },
66                 { 302, "Found" },
67                 { 303, "Method" },
68                 { 304, "Not Modified" },
69                 { 400, "Bad request" },
70                 { 401, "Unauthorized" },
71                 { 403, "Forbidden" },
72                 { 404, "Not Found" },
73                 { 500, "Internal Server Error" },
74                 { 501, "Not implemented" }
75         };
76         for (i=0;i<ARRAY_SIZE(codes);i++) {
77                 if (codes[i].code == web->output.response_code) {
78                         response_string = codes[i].response_string;
79                 }
80         }
81
82         if (web->output.headers == NULL) return;
83         s = talloc_asprintf(web, "HTTP/1.0 %u %s\r\n", 
84                             web->output.response_code, response_string);
85         if (s == NULL) return;
86         for (i=0;web->output.headers[i];i++) {
87                 s = talloc_asprintf_append(s, "%s\r\n", web->output.headers[i]);
88         }
89
90         /* work out the content length */
91         content_length = web->output.content.length;
92         if (web->output.fd != -1) {
93                 struct stat st;
94                 fstat(web->output.fd, &st);
95                 content_length += st.st_size;
96         }
97         s = talloc_asprintf_append(s, "Content-Length: %u\r\n\r\n", content_length);
98         if (s == NULL) return;
99
100         b = web->output.content;
101         web->output.content = data_blob_string_const(s);
102         data_blob_append(web, &web->output.content, b.data, b.length);
103         data_blob_free(&b);
104 }
105
106 /*
107   return the local path for a URL
108 */
109 static const char *http_local_path(struct websrv_context *web,
110                                    const char *url,
111                                    const char *base_dir)
112 {
113         int i;
114         char *path;
115
116         /* check that the url is OK */
117         if (url[0] != '/') return NULL;
118
119         for (i=0;url[i];i++) {
120                 if ((!isalnum((unsigned char)url[i]) && !strchr("./_-", url[i])) ||
121                     (url[i] == '.' && strchr("/.", url[i+1]))) {
122                         return NULL;
123                 }
124         }
125
126         path = talloc_asprintf(web, "%s/%s", base_dir, url+1);
127         if (path == NULL) return NULL;
128
129         if (directory_exist(path)) {
130                 path = talloc_asprintf_append(path, "/index.esp");
131         }
132         return path;
133 }
134
135 /*
136   called when esp wants to read a file to support include() calls
137 */
138 static int http_readFile(EspHandle handle,
139                          char **buf,
140                          int *len,
141                          const char *path,
142                          const char *base_dir)
143 {
144         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
145         int fd = -1;
146         struct stat st;
147         *buf = NULL;
148
149         path = http_local_path(web, path, base_dir);
150         if (path == NULL) goto failed;
151
152         fd = open(path, O_RDONLY);
153         if (fd == -1 || fstat(fd, &st) != 0 || !S_ISREG(st.st_mode)) goto failed;
154
155         *buf = talloc_size(handle, st.st_size+1);
156         if (*buf == NULL) goto failed;
157
158         if (read(fd, *buf, st.st_size) != st.st_size) goto failed;
159
160         (*buf)[st.st_size] = 0;
161
162         close(fd);
163         *len = st.st_size;
164         return 0;
165
166 failed:
167         DEBUG(0,("Failed to read file %s - %s\n", path, strerror(errno)));
168         if (fd != -1) close(fd);
169         talloc_free(*buf);
170         *buf = NULL;
171         return -1;
172 }
173
174 static int http_readFileFromWebappsDir(EspHandle handle,
175                                        char **buf,
176                                        int *len,
177                                        const char *path)
178 {
179     return http_readFile(handle, buf, len, path, lp_webapps_directory());
180 }
181
182
183
184 /*
185   called when esp wants to find the real path of a file
186 */
187 static int http_mapToStorage(EspHandle handle, char *path, int len, const char *uri, int flags)
188 {
189         if (uri == NULL || strlen(uri) >= len) return -1;
190         strncpy(path, uri, len);
191         return 0;
192 }
193
194 /*
195   called when esp wants to output something
196 */
197 static int http_writeBlock(EspHandle handle, const char *buf, int size)
198 {
199         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
200         NTSTATUS status;
201         status = data_blob_append(web, &web->output.content, buf, size);
202         if (!NT_STATUS_IS_OK(status)) return -1;
203         return size;
204 }
205
206
207 /*
208   set a http header
209 */
210 static void http_setHeader(EspHandle handle, const char *value, bool allowMultiple)
211 {
212         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
213         char *p = strchr(value, ':');
214
215         if (p && !allowMultiple && web->output.headers) {
216                 int i;
217                 for (i=0;web->output.headers[i];i++) {
218                         if (strncmp(web->output.headers[i], value, (p+1)-value) == 0) {
219                                 web->output.headers[i] = talloc_strdup(web, value);
220                                 return;
221                         }
222                 }
223         }
224
225         web->output.headers = str_list_add(web->output.headers, value);
226         talloc_steal(web, web->output.headers);
227 }
228
229 /*
230   set a http response code
231 */
232 static void http_setResponseCode(EspHandle handle, int code)
233 {
234         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
235         web->output.response_code = code;
236 }
237
238 /*
239   redirect to another web page
240  */
241 static void http_redirect(EspHandle handle, int code, char *url)
242 {
243         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
244         const char *host = web->input.host;
245         
246         /* form the full url, unless it already looks like a url */
247         if (strchr(url, ':') == NULL) {
248                 if (host == NULL) {
249                         struct socket_address *socket_address = socket_get_my_addr(web->conn->socket, web);
250                         if (socket_address == NULL) goto internal_error;
251                         host = talloc_asprintf(web, "%s:%u",
252                                                socket_address->addr, socket_address->port);
253                 }
254                 if (host == NULL) goto internal_error;
255                 if (url[0] != '/') {
256                         char *p = strrchr(web->input.url, '/');
257                         if (p == web->input.url) {
258                                 url = talloc_asprintf(web, "http%s://%s/%s", 
259                                                       tls_enabled(web->conn->socket)?"s":"",
260                                                       host, url);
261                         } else {
262                                 int dirlen = p - web->input.url;
263                                 url = talloc_asprintf(web, "http%s://%s%*.*s/%s",
264                                                       tls_enabled(web->conn->socket)?"s":"",
265                                                       host, 
266                                                       dirlen, dirlen, web->input.url,
267                                                       url);
268                         }
269                         if (url == NULL) goto internal_error;
270                 }
271         }
272
273         http_setHeader(handle, talloc_asprintf(web, "Location: %s", url), 0);
274
275         /* make sure we give a valid redirect code */
276         if (code >= 300 && code < 400) {
277                 http_setResponseCode(handle, code);
278         } else {
279                 http_setResponseCode(handle, 302);
280         }
281         return;
282
283 internal_error:
284         http_error(web, 500, "Internal server error");
285 }
286
287
288 /*
289   setup a cookie
290 */
291 static void http_setCookie(EspHandle handle, const char *name, const char *value, 
292                            int lifetime, const char *path, bool secure)
293 {
294         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
295         char *buf;
296         
297         if (lifetime > 0) {
298                 buf = talloc_asprintf(web, "Set-Cookie: %s=%s; path=%s; Expires=%s; %s",
299                                       name, value, path?path:"/", 
300                                       http_timestring(web, time(NULL)+lifetime),
301                                       secure?"secure":"");
302         } else {
303                 buf = talloc_asprintf(web, "Set-Cookie: %s=%s; path=%s; %s",
304                                       name, value, path?path:"/", 
305                                       secure?"secure":"");
306         }
307         http_setHeader(handle, "Cache-control: no-cache=\"set-cookie\"", 0);
308         http_setHeader(handle, buf, 0);
309         talloc_free(buf);
310 }
311
312 /*
313   return the session id
314 */
315 static const char *http_getSessionId(EspHandle handle)
316 {
317         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
318         return web->session->id;
319 }
320
321 /*
322   setup a session
323 */
324 static void http_createSession(EspHandle handle, int timeout)
325 {
326         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
327         if (web->session) {
328                 web->session->lifetime = timeout;
329                 http_setCookie(web, SAMBA_SESSION_KEY, web->session->id, 
330                                web->session->lifetime, "/", 0);
331         }
332 }
333
334 /*
335   destroy a session
336 */
337 static void http_destroySession(EspHandle handle)
338 {
339         struct websrv_context *web = talloc_get_type(handle, struct websrv_context);
340         talloc_free(web->session);
341         web->session = NULL;
342 }
343
344
345 /*
346   setup for a raw http level error
347 */
348 void http_error(struct websrv_context *web, int code, const char *info)
349 {
350         char *s;
351         s = talloc_asprintf(web,"<HTML><HEAD><TITLE>Error %u</TITLE></HEAD><BODY><H1>Error %u</H1><pre>%s</pre><p></BODY></HTML>\r\n\r\n", 
352                             code, code, info);
353         if (s == NULL) {
354                 stream_terminate_connection(web->conn, "http_error: out of memory");
355                 return;
356         }
357         http_writeBlock(web, s, strlen(s));
358         http_setResponseCode(web, code);
359         http_output_headers(web);
360         EVENT_FD_NOT_READABLE(web->conn->event.fde);
361         EVENT_FD_WRITEABLE(web->conn->event.fde);
362         web->output.output_pending = True;
363 }
364
365 /*
366   map a unix error code to a http error
367 */
368 void http_error_unix(struct websrv_context *web, const char *info)
369 {
370         int code = 500;
371         switch (errno) {
372         case ENOENT:
373         case EISDIR:
374                 code = 404;
375                 break;
376         case EACCES:
377                 code = 403;
378                 break;
379         }
380         info = talloc_asprintf(web, "%s<p>%s<p>\n", info, strerror(errno));
381         http_error(web, code, info);
382 }
383
384
385 /*
386   a simple file request
387 */
388 static void http_simple_request(struct websrv_context *web)
389 {
390         const char *url = web->input.url;
391         const char *path;
392         struct stat st;
393
394         path = http_local_path(web, url, lp_webapps_directory());
395         if (path == NULL) goto invalid;
396
397         /* looks ok */
398         web->output.fd = open(path, O_RDONLY);
399         if (web->output.fd == -1) {
400                 DEBUG(0,("Failed to read file %s - %s\n", path, strerror(errno)));
401                 http_error_unix(web, path);
402                 return;
403         }
404
405         if (fstat(web->output.fd, &st) != 0 || !S_ISREG(st.st_mode)) {
406                 close(web->output.fd);
407                 goto invalid;
408         }
409
410         return;
411
412 invalid:
413         http_error(web, 400, "Malformed URL");
414 }
415
416 /*
417   setup the standard ESP arrays
418 */
419 static void http_setup_arrays(struct esp_state *esp)
420 {
421         struct websrv_context *web = esp->web;
422         struct esp_data *edata = talloc_get_type(web->task->private, struct esp_data);
423         struct EspRequest *req = esp->req;
424         struct socket_address *socket_address = socket_get_my_addr(web->conn->socket, esp);
425         struct socket_address *peer_address = socket_get_peer_addr(web->conn->socket, esp);
426         char *p;
427
428 #define SETVAR(type, name, value) do { \
429                 const char *v = value; \
430                 if (v) espSetStringVar(req, type, name, v); \
431 } while (0)
432
433         SETVAR(ESP_REQUEST_OBJ, "CONTENT_LENGTH", 
434                talloc_asprintf(esp, "%u", web->input.content_length));
435         SETVAR(ESP_REQUEST_OBJ, "QUERY_STRING", web->input.query_string);
436         SETVAR(ESP_REQUEST_OBJ, "POST_DATA",
437                talloc_strndup(esp,
438                               web->input.partial.data,
439                               web->input.partial.length));
440         SETVAR(ESP_REQUEST_OBJ, "REQUEST_METHOD", web->input.post_request?"POST":"GET");
441         SETVAR(ESP_REQUEST_OBJ, "REQUEST_URI", web->input.url);
442         p = strrchr(web->input.url, '/');
443         SETVAR(ESP_REQUEST_OBJ, "SCRIPT_NAME", p+1);
444         SETVAR(ESP_REQUEST_OBJ, "SCRIPT_FILENAME", web->input.url);
445         if (peer_address) {
446                 struct MprVar mpv = mprObject("socket_address");
447                 mprSetPtrChild(&mpv, "socket_address", peer_address);
448                 espSetVar(req, ESP_REQUEST_OBJ, "REMOTE_SOCKET_ADDRESS", mpv);
449                 SETVAR(ESP_REQUEST_OBJ, "REMOTE_ADDR", peer_address->addr);
450         }
451         p = socket_get_peer_name(web->conn->socket, esp);
452         SETVAR(ESP_REQUEST_OBJ, "REMOTE_HOST", p);
453         SETVAR(ESP_REQUEST_OBJ, "REMOTE_USER", "");
454         SETVAR(ESP_REQUEST_OBJ, "CONTENT_TYPE", web->input.content_type);
455         if (web->session) {
456                 SETVAR(ESP_REQUEST_OBJ, "SESSION_ID", web->session->id);
457         }
458         SETVAR(ESP_REQUEST_OBJ, "COOKIE_SUPPORT", web->input.cookie?"True":"False");
459
460         SETVAR(ESP_HEADERS_OBJ, "HTTP_REFERER", web->input.referer);
461         SETVAR(ESP_HEADERS_OBJ, "HOST", web->input.host);
462         SETVAR(ESP_HEADERS_OBJ, "ACCEPT_ENCODING", web->input.accept_encoding);
463         SETVAR(ESP_HEADERS_OBJ, "ACCEPT_LANGUAGE", web->input.accept_language);
464         SETVAR(ESP_HEADERS_OBJ, "ACCEPT_CHARSET", web->input.accept_charset);
465         SETVAR(ESP_HEADERS_OBJ, "COOKIE", web->input.cookie);
466         SETVAR(ESP_HEADERS_OBJ, "USER_AGENT", web->input.user_agent);
467
468         if (socket_address) {
469                 SETVAR(ESP_SERVER_OBJ, "SERVER_ADDR", socket_address->addr);
470                 SETVAR(ESP_SERVER_OBJ, "SERVER_NAME", socket_address->addr);
471                 SETVAR(ESP_SERVER_OBJ, "SERVER_HOST", socket_address->addr);
472                 SETVAR(ESP_SERVER_OBJ, "SERVER_PORT", 
473                        talloc_asprintf(esp, "%u", socket_address->port));
474         }
475
476         SETVAR(ESP_SERVER_OBJ, "DOCUMENT_ROOT", lp_webapps_directory());
477         SETVAR(ESP_SERVER_OBJ, "SERVER_PROTOCOL", tls_enabled(web->conn->socket)?"https":"http");
478         SETVAR(ESP_SERVER_OBJ, "SERVER_SOFTWARE", "SAMBA");
479         SETVAR(ESP_SERVER_OBJ, "GATEWAY_INTERFACE", "CGI/1.1");
480         SETVAR(ESP_SERVER_OBJ, "TLS_SUPPORT", tls_support(edata->tls_params)?"True":"False");
481 }
482
483 #if HAVE_SETJMP_H
484 /* the esp scripting lirary generates exceptions when
485    it hits a major error. We need to catch these and
486    report a internal server error via http
487 */
488 static jmp_buf ejs_exception_buf;
489 static const char *exception_reason;
490
491 static void web_server_ejs_exception(const char *reason)
492 {
493         Ejs *ep = ejsPtr(0);
494         if (ep) {
495                 ejsSetErrorMsg(0, "%s", reason);
496                 exception_reason = ep->error;
497         } else {
498                 exception_reason = reason;
499         }
500         DEBUG(0,("%s", exception_reason));
501         longjmp(ejs_exception_buf, -1);
502 }
503 #else
504 static void web_server_ejs_exception(const char *reason)
505 {
506         DEBUG(0,("%s", reason));
507         smb_panic(reason);
508 }
509 #endif
510
511 /*
512   process a esp request
513 */
514 static void esp_request(struct esp_state *esp, const char *url)
515 {
516         struct websrv_context *web = esp->web;
517         int size;
518         int res;
519         char *emsg = NULL, *buf;
520
521         if (http_readFile(web, &buf, &size, url, lp_webapps_directory()) != 0) {
522                 http_error_unix(web, url);
523                 return;
524         }
525
526 #if HAVE_SETJMP_H
527         if (setjmp(ejs_exception_buf) != 0) {
528                 http_error(web, 500, exception_reason);
529                 return;
530         }
531 #endif
532
533         res = espProcessRequest(esp->req, url, buf, &emsg);
534         if (res != 0 && emsg) {
535                 http_writeBlock(web, "<pre>", 5);
536                 http_writeBlock(web, emsg, strlen(emsg));
537                 http_writeBlock(web, "</pre>", 6);
538         }
539         talloc_free(buf);
540 }
541
542 /*
543   process a JSON RPC request
544 */
545 static void jsonrpc_request(struct esp_state *esp)
546 {
547         struct websrv_context *web = esp->web;
548         const char *path = http_local_path(web,
549                                            JSONRPC_SERVER,
550                                            lp_jsonrpc_services_dir());
551         MprVar *global;
552         MprVar v;
553         MprVar temp;
554         int size;
555         int res;
556         char *emsg = NULL;
557         char *emsg2 = NULL;
558         char *buf;
559         char *error_script =
560                 "error.setOrigin(jsonrpc.Constant.ErrorOrigin.Server); "
561                 "error.setError(jsonrpc.Constant.ErrorCode.UnexpectedOutput, "
562                 "               global.errorString);"
563                 "error.Send();";
564
565         /* Ensure we got a valid path. */
566         if (path == NULL) {
567                 /* should never occur */
568                 http_error(esp->web, 500, "Internal server error");
569                 return;
570         }
571
572         /* Ensure that the JSON-RPC server request script exists */
573         if (!file_exist(path)) {
574                 http_error_unix(esp->web, path);
575                 return;
576         }
577
578         /* Call the server request script */
579         if (http_readFile(web, &buf, &size,
580                           JSONRPC_SERVER, lp_jsonrpc_services_dir()) != 0) {
581                 http_error_unix(web, JSONRPC_SERVER);
582                 return;
583         }
584
585 #if HAVE_SETJMP_H
586         if (setjmp(ejs_exception_buf) != 0) {
587                 http_error(web, 500, exception_reason);
588                 return;
589         }
590 #endif
591
592         res = espProcessRequest(esp->req, JSONRPC_SERVER, buf, &emsg);
593         if (res != 0 && emsg) {
594                 /* Save the error in a string accessible from javascript */
595                 global = ejsGetGlobalObject(0);
596                 v = mprString(emsg);
597                 mprCreateProperty(global, "errorString", &v);
598
599                 /* Create and send a JsonRpcError object */
600                 if (ejsEvalScript(0,
601                                   error_script,
602                                   &temp,
603                                   &emsg2) != 0) {
604                         http_writeBlock(web, "<pre>", 5);
605                         http_writeBlock(web, emsg, strlen(emsg));
606                         http_writeBlock(web, "</pre>", 6);
607                 }
608         }
609         talloc_free(buf);
610 }
611
612 /*
613   perform pre-authentication on every page if /scripting/preauth.esp
614   exists.  If this script generates any non-whitepace output at all,
615   then we don't run the requested URL.
616
617   note that the preauth is run even for static pages such as images, but not
618   for JSON-RPC service requests which do their own authentication via the
619   JSON-RPC server.
620 */
621 static BOOL http_preauth(struct esp_state *esp)
622 {
623         const char *path = http_local_path(esp->web,
624                                            HTTP_PREAUTH_URI,
625                                            lp_webapps_directory());
626         int i;
627         if (path == NULL) {
628                 http_error(esp->web, 500, "Internal server error");
629                 return False;
630         }
631         if (!file_exist(path)) {
632                 /* if the preath script is not installed then allow access */
633                 return True;
634         }
635         esp_request(esp, HTTP_PREAUTH_URI);
636         for (i=0;i<esp->web->output.content.length;i++) {
637                 if (!isspace(esp->web->output.content.data[i])) {
638                         /* if the preauth has generated content, then force it
639                            to be html, so that we can show the login page for
640                            failed access to images */
641                         http_setHeader(esp->web, "Content-Type: text/html", 0);
642                         return False;
643                 }
644         }
645         data_blob_free(&esp->web->output.content);
646         return True;
647 }
648
649
650 /* 
651    handling of + and % escapes in http variables 
652 */
653 static const char *http_unescape(TALLOC_CTX *mem_ctx, const char *p)
654 {
655         char *s0 = talloc_strdup(mem_ctx, p);
656         char *s = s0;
657         if (s == NULL) return NULL;
658
659         while (*s) {
660                 unsigned v;
661                 if (*s == '+') *s = ' ';
662                 if (*s == '%' && sscanf(s+1, "%02x", &v) == 1) {
663                         *s = (char)v;
664                         memmove(s+1, s+3, strlen(s+3)+1);
665                 }
666                 s++;
667         }
668
669         return s0;
670 }
671
672 /*
673   set a form or GET variable
674 */
675 static void esp_putvar(struct esp_state *esp, const char *var, const char *value)
676 {
677         if (strcasecmp(var, SAMBA_SESSION_KEY) == 0) {
678                 /* special case support for browsers without cookie
679                  support */
680                 esp->web->input.session_key = talloc_strdup(esp, value);
681         } else {
682                 mprSetPropertyValue(&esp->variables[ESP_FORM_OBJ], 
683                                     http_unescape(esp, var),
684                                     mprCreateStringVar(http_unescape(esp, value), 0));
685         }
686 }
687
688
689 /*
690   parse the variables in a POST style request
691 */
692 static NTSTATUS http_parse_post(struct esp_state *esp)
693 {
694         DATA_BLOB b = esp->web->input.partial;
695
696         while (b.length) {
697                 char *p, *line;
698                 size_t len;
699
700                 p = memchr(b.data, '&', b.length);
701                 if (p == NULL) {
702                         len = b.length;
703                 } else {
704                         len = p - (char *)b.data;
705                 }
706                 line = talloc_strndup(esp, (char *)b.data, len);
707                 NT_STATUS_HAVE_NO_MEMORY(line);
708                                      
709                 p = strchr(line,'=');
710                 if (p) {
711                         *p = 0;
712                         esp_putvar(esp, line, p+1);
713                 }
714                 talloc_free(line);
715                 b.length -= len;
716                 b.data += len;
717                 if (b.length > 0) {
718                         b.length--;
719                         b.data++;
720                 }
721         }
722
723         return NT_STATUS_OK;
724 }
725
726 /*
727   parse the variables in a GET style request
728 */
729 static NTSTATUS http_parse_get(struct esp_state *esp)
730 {
731         struct websrv_context *web = esp->web;
732         char *p, *s, *tok;
733         char *pp;
734
735         p = strchr(web->input.url, '?');
736         web->input.query_string = p+1;
737         *p = 0;
738
739         s = talloc_strdup(esp, esp->web->input.query_string);
740         NT_STATUS_HAVE_NO_MEMORY(s);
741
742         for (tok=strtok_r(s,"&;", &pp);tok;tok=strtok_r(NULL,"&;", &pp)) {
743                 p = strchr(tok,'=');
744                 if (p) {
745                         *p = 0;
746                         esp_putvar(esp, tok, p+1);
747                 }
748         }
749         return NT_STATUS_OK;
750 }
751
752 /*
753   called when a session times out
754 */
755 static void session_timeout(struct event_context *ev, struct timed_event *te, 
756                             struct timeval t, void *private)
757 {
758         struct session_data *s = talloc_get_type(private, struct session_data);
759         talloc_free(s);
760 }
761
762 /*
763   destroy a session
764  */
765 static int session_destructor(struct session_data *s)
766 {
767         DLIST_REMOVE(s->edata->sessions, s);
768         return 0;
769 }
770
771 /*
772   setup the session for this request
773 */
774 static void http_setup_session(struct esp_state *esp)
775 {
776         const char *session_key = SAMBA_SESSION_KEY;
777         char *p;
778         const char *cookie = esp->web->input.cookie;
779         const char *key = NULL;
780         struct esp_data *edata = talloc_get_type(esp->web->task->private, struct esp_data);
781         struct session_data *s;
782         BOOL generated_key = False;
783
784         /* look for our session key */
785         if (cookie && (p = strstr(cookie, session_key)) && 
786             p[strlen(session_key)] == '=') {
787                 p += strlen(session_key)+1;
788                 key = talloc_strndup(esp, p, strcspn(p, ";"));
789         }
790
791         if (key == NULL && esp->web->input.session_key) {
792                 key = esp->web->input.session_key;
793         } else if (key == NULL) {
794                 key = generate_random_str_list(esp, 16, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
795                 generated_key = True;
796         }
797
798         /* try to find this session in the existing session list */
799         for (s=edata->sessions;s;s=s->next) {
800                 if (strcmp(key, s->id) == 0) {
801                         break;
802                 }
803         }
804
805         if (s == NULL) {
806                 /* create a new session */
807                 s = talloc_zero(edata, struct session_data);
808                 s->id = talloc_steal(s, key);
809                 s->data = NULL;
810                 s->te = NULL;
811                 s->edata = edata;
812                 s->lifetime = lp_parm_int(-1, "web", "sessiontimeout", 900);
813                 DLIST_ADD(edata->sessions, s);
814                 talloc_set_destructor(s, session_destructor);
815                 if (!generated_key) {
816                         mprSetPropertyValue(&esp->variables[ESP_REQUEST_OBJ], 
817                                             "SESSION_EXPIRED", mprCreateStringVar("True", 0));
818                 }
819         }
820
821         http_setCookie(esp->web, session_key, key, s->lifetime, "/", 0);
822
823         if (s->data) {
824                 mprCopyVar(&esp->variables[ESP_SESSION_OBJ], s->data, MPR_DEEP_COPY);
825         }
826
827         esp->web->session = s;
828 }
829
830
831 /* callbacks for esp processing */
832 static const struct Esp esp_control = {
833         .maxScriptSize   = 60000,
834         .writeBlock      = http_writeBlock,
835         .setHeader       = http_setHeader,
836         .redirect        = http_redirect,
837         .setResponseCode = http_setResponseCode,
838         .readFile        = http_readFileFromWebappsDir,
839         .mapToStorage    = http_mapToStorage,
840         .setCookie       = http_setCookie,
841         .createSession   = http_createSession,
842         .destroySession  = http_destroySession,
843         .getSessionId    = http_getSessionId
844 };
845
846 /*
847   process a complete http request
848 */
849 void http_process_input(struct websrv_context *web)
850 {
851         NTSTATUS status;
852         struct esp_state *esp = NULL;
853         struct esp_data *edata = talloc_get_type(web->task->private, struct esp_data);
854         struct smbcalls_context *smbcalls_ctx;
855         char *p;
856         void *save_mpr_ctx = mprMemCtx();
857         void *ejs_save = ejs_save_state();
858         int i;
859         const char *file_type = NULL;
860         enum page_type {
861                 page_type_simple,
862                 page_type_esp,
863                 page_type_jsonrpc
864         };
865         enum page_type page_type;
866         const struct {
867                 const char *extension;
868                 const char *mime_type;
869                 enum page_type page_type;
870         } mime_types[] = {
871                 {"gif",  "image/gif"},
872                 {"png",  "image/png"},
873                 {"jpg",  "image/jpeg"},
874                 {"txt",  "text/plain"},
875                 {"ico",  "image/x-icon"},
876                 {"css",  "text/css"},
877                 {"esp",  "text/html", True}
878         };
879
880         /*
881          * give the smbcalls a chance to find the event context
882          * and messaging context 
883          */
884         smbcalls_ctx = talloc(web, struct smbcalls_context);
885         if (smbcalls_ctx == NULL) goto internal_error;
886         smbcalls_ctx->event_ctx = web->conn->event.ctx;
887         smbcalls_ctx->msg_ctx = web->conn->msg_ctx;
888
889         esp = talloc_zero(smbcalls_ctx, struct esp_state);
890         if (esp == NULL) goto internal_error;
891
892         esp->web = web;
893
894         mprSetCtx(esp);
895
896         if (espOpen(&esp_control) != 0) goto internal_error;
897
898         for (i=0;i<ARRAY_SIZE(esp->variables);i++) {
899                 esp->variables[i] = mprCreateUndefinedVar();
900         }
901         esp->variables[ESP_HEADERS_OBJ]     = mprCreateObjVar("headers", ESP_HASH_SIZE);
902         esp->variables[ESP_FORM_OBJ]        = mprCreateObjVar("form", ESP_HASH_SIZE);
903         esp->variables[ESP_APPLICATION_OBJ] = mprCreateObjVar("application", ESP_HASH_SIZE);
904         esp->variables[ESP_COOKIES_OBJ]     = mprCreateObjVar("cookies", ESP_HASH_SIZE);
905         esp->variables[ESP_FILES_OBJ]       = mprCreateObjVar("files", ESP_HASH_SIZE);
906         esp->variables[ESP_REQUEST_OBJ]     = mprCreateObjVar("request", ESP_HASH_SIZE);
907         esp->variables[ESP_SERVER_OBJ]      = mprCreateObjVar("server", ESP_HASH_SIZE);
908         esp->variables[ESP_SESSION_OBJ]     = mprCreateObjVar("session", ESP_HASH_SIZE);
909
910         if (edata->application_data) {
911                 mprCopyVar(&esp->variables[ESP_APPLICATION_OBJ], 
912                            edata->application_data, MPR_DEEP_COPY);
913         }
914
915         smb_setup_ejs_functions(web_server_ejs_exception);
916
917         if (web->input.url == NULL) {
918                 http_error(web, 400, "You must specify a GET or POST request");
919                 mprSetCtx(save_mpr_ctx);
920                 ejs_restore_state(ejs_save);
921                 return;
922         }
923         
924         /* parse any form or get variables */
925         if (web->input.post_request) {
926                 status = http_parse_post(esp);
927                 if (!NT_STATUS_IS_OK(status)) {
928                         http_error(web, 400, "Malformed POST data");
929                         mprSetCtx(save_mpr_ctx);
930                         ejs_restore_state(ejs_save);
931                         return;
932                 }
933         } 
934         if (strchr(web->input.url, '?')) {
935                 status = http_parse_get(esp);
936                 if (!NT_STATUS_IS_OK(status)) {
937                         http_error(web, 400, "Malformed GET data");
938                         mprSetCtx(save_mpr_ctx);
939                         ejs_restore_state(ejs_save);
940                         return;
941                 }
942         }
943
944         http_setup_session(esp);
945
946         esp->req = espCreateRequest(web, web->input.url, esp->variables);
947         if (esp->req == NULL) goto internal_error;
948
949         /*
950          * Work out the mime type.  First, we see if the request is a JSON-RPC
951          * service request.  If not, we look at the extension.
952          */
953         if (strncmp(web->input.url,
954                     JSONRPC_REQUEST,
955                     sizeof(JSONRPC_REQUEST) - 1) == 0 &&
956             (web->input.url[sizeof(JSONRPC_REQUEST) - 1] == '\0' ||
957              web->input.url[sizeof(JSONRPC_REQUEST) - 1] == '/')) {
958             page_type = page_type_jsonrpc;
959             file_type = "text/json";
960             
961         } else {
962             p = strrchr(web->input.url, '.');
963             if (p == NULL) {
964                     page_type = page_type_esp;
965                     file_type = "text/html";
966             }
967             for (i=0;p && i<ARRAY_SIZE(mime_types);i++) {
968                 if (strcmp(mime_types[i].extension, p+1) == 0) {
969                     page_type = mime_types[i].page_type;
970                     file_type = mime_types[i].mime_type;
971                 }
972             }
973             if (file_type == NULL) {
974                 page_type = page_type_simple;
975                 file_type = "text/html";
976             }
977         }
978
979         /* setup basic headers */
980         http_setResponseCode(web, 200);
981         http_setHeader(web, talloc_asprintf(esp, "Date: %s", 
982                                             http_timestring(esp, time(NULL))), 0);
983         http_setHeader(web, "Server: Samba", 0);
984         http_setHeader(web, "Connection: close", 0);
985         http_setHeader(web, talloc_asprintf(esp, "Content-Type: %s", file_type), 0);
986
987         http_setup_arrays(esp);
988
989         /*
990          * Do pre-authentication.  If pre-authentication succeeds, do
991          * page-type-specific processing.
992          */
993         switch(page_type)
994         {
995         case page_type_simple:
996                 if (http_preauth(esp)) {
997                         http_simple_request(web);
998                 }
999                 break;
1000
1001         case page_type_esp:
1002                 if (http_preauth(esp)) {
1003                         esp_request(esp, web->input.url);
1004                 }
1005                 break;
1006
1007         case page_type_jsonrpc:
1008                 jsonrpc_request(esp);
1009                 break;
1010         }
1011
1012         if (web->conn == NULL) {
1013                 /* the connection has been terminated above us, probably
1014                    via a timeout */
1015                 goto internal_error;
1016         }
1017
1018         if (!web->output.output_pending) {
1019                 http_output_headers(web);
1020                 EVENT_FD_WRITEABLE(web->conn->event.fde);
1021                 web->output.output_pending = True;
1022         }
1023
1024         /* copy any application data to long term storage in edata */
1025         talloc_free(edata->application_data);
1026         edata->application_data = talloc_zero(edata, struct MprVar);
1027         mprSetCtx(edata->application_data);
1028         mprCopyVar(edata->application_data, &esp->variables[ESP_APPLICATION_OBJ], 
1029                    MPR_DEEP_COPY);
1030         mprSetCtx(esp);
1031
1032         /* copy any session data */
1033         if (web->session) {
1034                 talloc_free(web->session->data);
1035                 web->session->data = talloc_zero(web->session, struct MprVar);
1036                 if (esp->variables[ESP_SESSION_OBJ].properties == NULL ||
1037                     esp->variables[ESP_SESSION_OBJ].properties[0].numItems == 0) {
1038                         talloc_free(web->session);
1039                         web->session = NULL;
1040                 } else {
1041                         mprSetCtx(web->session->data);
1042                         mprCopyVar(web->session->data, &esp->variables[ESP_SESSION_OBJ], 
1043                                    MPR_DEEP_COPY);
1044                         /* setup the timeout for the session data */
1045                         mprSetCtx(esp);
1046                         talloc_free(web->session->te);
1047                         web->session->te = event_add_timed(web->conn->event.ctx, web->session, 
1048                                                            timeval_current_ofs(web->session->lifetime, 0), 
1049                                                            session_timeout, web->session);
1050                 }
1051         }
1052
1053         talloc_free(esp);
1054         mprSetCtx(save_mpr_ctx);
1055         ejs_restore_state(ejs_save);
1056         return;
1057         
1058 internal_error:
1059         mprSetCtx(esp);
1060         talloc_free(esp);
1061         if (web->conn != NULL) {
1062                 http_error(web, 500, "Internal server error");
1063         }
1064         mprSetCtx(save_mpr_ctx);
1065         ejs_restore_state(ejs_save);
1066 }
1067
1068
1069 /*
1070   parse one line of header input
1071 */
1072 NTSTATUS http_parse_header(struct websrv_context *web, const char *line)
1073 {
1074         if (line[0] == 0) {
1075                 web->input.end_of_headers = True;
1076         } else if (strncasecmp(line,"GET ", 4)==0) {
1077                 web->input.url = talloc_strndup(web, &line[4], strcspn(&line[4], " \t"));
1078         } else if (strncasecmp(line,"POST ", 5)==0) {
1079                 web->input.post_request = True;
1080                 web->input.url = talloc_strndup(web, &line[5], strcspn(&line[5], " \t"));
1081         } else if (strchr(line, ':') == NULL) {
1082                 http_error(web, 400, "This server only accepts GET and POST requests");
1083                 return NT_STATUS_INVALID_PARAMETER;
1084         } else if (strncasecmp(line,"Content-Length: ", 16)==0) {
1085                 web->input.content_length = strtoul(&line[16], NULL, 10);
1086         } else {
1087 #define PULL_HEADER(v, s) do { \
1088         if (strncmp(line, s, strlen(s)) == 0) { \
1089                 web->input.v = talloc_strdup(web, &line[strlen(s)]); \
1090                 return NT_STATUS_OK; \
1091         } \
1092 } while (0)
1093                 PULL_HEADER(content_type, "Content-Type: ");
1094                 PULL_HEADER(user_agent, "User-Agent: ");
1095                 PULL_HEADER(referer, "Referer: ");
1096                 PULL_HEADER(host, "Host: ");
1097                 PULL_HEADER(accept_encoding, "Accept-Encoding: ");
1098                 PULL_HEADER(accept_language, "Accept-Language: ");
1099                 PULL_HEADER(accept_charset, "Accept-Charset: ");
1100                 PULL_HEADER(cookie, "Cookie: ");
1101         }
1102
1103         /* ignore all other headers for now */
1104         return NT_STATUS_OK;
1105 }
1106
1107
1108 /*
1109   setup the esp processor - called at task initialisation
1110 */
1111 NTSTATUS http_setup_esp(struct task_server *task)
1112 {
1113         struct esp_data *edata;
1114
1115         edata = talloc_zero(task, struct esp_data);
1116         NT_STATUS_HAVE_NO_MEMORY(edata);
1117
1118         task->private = edata;
1119
1120         edata->tls_params = tls_initialise(edata);
1121         NT_STATUS_HAVE_NO_MEMORY(edata->tls_params);
1122
1123         return NT_STATUS_OK;
1124 }