refactored PA-TNC message handling by IMVs
[strongswan.git] / src / libfast / request.c
1 /*
2 * Copyright (C) 2007 Martin Willi
3 * Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #define _GNU_SOURCE
17
18 #include "request.h"
19
20 #include <library.h>
21 #include <debug.h>
22 #include <stdlib.h>
23 #include <pthread.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <sys/mman.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29
30 #include <ClearSilver/ClearSilver.h>
31
32 #include <threading/thread.h>
33 #include <threading/thread_value.h>
34
35 typedef struct private_request_t private_request_t;
36
37 /**
38 * private data of the task manager
39 */
40 struct private_request_t {
41
42 /**
43 * public functions
44 */
45 request_t public;
46
47 /**
48 * FastCGI request object
49 */
50 FCGX_Request req;
51
52 /**
53 * length of the req.envp array
54 */
55 int req_env_len;
56
57 /**
58 * ClearSilver CGI Kit context
59 */
60 CGI *cgi;
61
62 /**
63 * ClearSilver HDF dataset for this request
64 */
65 HDF *hdf;
66
67 /**
68 * close the session?
69 */
70 bool closed;
71
72 /**
73 * reference count
74 */
75 refcount_t ref;
76 };
77
78 /**
79 * ClearSilver cgiwrap is not threadsave, so we use a private
80 * context for each thread.
81 */
82 static thread_value_t *thread_this;
83
84 /**
85 * control variable for pthread_once
86 */
87 pthread_once_t once = PTHREAD_ONCE_INIT;
88
89 /**
90 * fcgiwrap read callback
91 */
92 static int read_cb(void *null, char *buf, int size)
93 {
94 private_request_t *this = (private_request_t*)thread_this->get(thread_this);
95
96 return FCGX_GetStr(buf, size, this->req.in);
97 }
98
99 /**
100 * fcgiwrap writef callback
101 */
102 static int writef_cb(void *null, const char *format, va_list args)
103 {
104 private_request_t *this = (private_request_t*)thread_this->get(thread_this);
105
106 FCGX_VFPrintF(this->req.out, format, args);
107 return 0;
108 }
109 /**
110 * fcgiwrap write callback
111 */
112 static int write_cb(void *null, const char *buf, int size)
113 {
114 private_request_t *this = (private_request_t*)thread_this->get(thread_this);
115
116 return FCGX_PutStr(buf, size, this->req.out);
117 }
118
119 /**
120 * fcgiwrap getenv callback
121 */
122 static char *getenv_cb(void *null, const char *key)
123 {
124 char *value;
125 private_request_t *this = (private_request_t*)thread_this->get(thread_this);
126
127 value = FCGX_GetParam(key, this->req.envp);
128 return strdupnull(value);
129 }
130
131 /**
132 * fcgiwrap getenv callback
133 */
134 static int putenv_cb(void *null, const char *key, const char *value)
135 {
136 /* not supported */
137 return 1;
138 }
139
140 /**
141 * fcgiwrap iterenv callback
142 */
143 static int iterenv_cb(void *null, int num, char **key, char **value)
144 {
145 *key = NULL;
146 *value = NULL;
147 private_request_t *this = (private_request_t*)thread_this->get(thread_this);
148 if (num < this->req_env_len)
149 {
150 char *eq;
151
152 eq = strchr(this->req.envp[num], '=');
153 if (eq)
154 {
155 *key = strndup(this->req.envp[num], eq - this->req.envp[num]);
156 *value = strdup(eq + 1);
157 }
158 if (*key == NULL || *value == NULL)
159 {
160 free(*key);
161 free(*value);
162 return 1;
163 }
164 }
165 return 0;
166 }
167
168 METHOD(request_t, get_cookie, char*,
169 private_request_t *this, char *name)
170 {
171 return hdf_get_valuef(this->hdf, "Cookie.%s", name);
172 }
173
174 METHOD(request_t, get_path, char*,
175 private_request_t *this)
176 {
177 char * path = FCGX_GetParam("PATH_INFO", this->req.envp);
178 return path ? path : "";
179 }
180
181 METHOD(request_t, get_host, char*,
182 private_request_t *this)
183 {
184 char *addr = FCGX_GetParam("REMOTE_ADDR", this->req.envp);
185 return addr ? addr : "";
186 }
187
188 METHOD(request_t, get_user_agent, char*,
189 private_request_t *this)
190 {
191 char *agent = FCGX_GetParam("HTTP_USER_AGENT", this->req.envp);
192 return agent ? agent : "";
193 }
194
195 METHOD(request_t, get_query_data, char*,
196 private_request_t *this, char *name)
197 {
198 return hdf_get_valuef(this->hdf, "Query.%s", name);
199 }
200
201 METHOD(request_t, get_env_var, char*,
202 private_request_t *this, char *name)
203 {
204 return FCGX_GetParam(name, this->req.envp);
205 }
206
207 METHOD(request_t, read_data, int,
208 private_request_t *this, char *buf, int len)
209 {
210 return FCGX_GetStr(buf, len, this->req.in);
211 }
212
213 METHOD(request_t, get_base, char*,
214 private_request_t *this)
215 {
216 return FCGX_GetParam("SCRIPT_NAME", this->req.envp);
217 }
218
219 METHOD(request_t, add_cookie, void,
220 private_request_t *this, char *name, char *value)
221 {
222 thread_this->set(thread_this, this);
223 cgi_cookie_set(this->cgi, name, value, NULL, NULL, NULL, 0, 0);
224 }
225
226 METHOD(request_t, redirect, void,
227 private_request_t *this, char *fmt, ...)
228 {
229 va_list args;
230
231 FCGX_FPrintF(this->req.out, "Status: 303 See Other\n");
232 FCGX_FPrintF(this->req.out, "Location: %s%s", get_base(this),
233 *fmt == '/' ? "" : "/");
234 va_start(args, fmt);
235 FCGX_VFPrintF(this->req.out, fmt, args);
236 va_end(args);
237 FCGX_FPrintF(this->req.out, "\n\n");
238 }
239
240 METHOD(request_t, get_referer, char*,
241 private_request_t *this)
242 {
243 return FCGX_GetParam("HTTP_REFERER", this->req.envp);
244 }
245
246 METHOD(request_t, to_referer, void,
247 private_request_t *this)
248 {
249 char *referer;
250
251 referer = get_referer(this);
252 if (referer)
253 {
254 FCGX_FPrintF(this->req.out, "Status: 303 See Other\n");
255 FCGX_FPrintF(this->req.out, "Location: %s\n\n", referer);
256 }
257 else
258 {
259 redirect(this, "/");
260 }
261 }
262
263 METHOD(request_t, session_closed, bool,
264 private_request_t *this)
265 {
266 return this->closed;
267 }
268
269 METHOD(request_t, close_session, void,
270 private_request_t *this)
271 {
272 this->closed = TRUE;
273 }
274
275 METHOD(request_t, serve, void,
276 private_request_t *this, char *headers, chunk_t chunk)
277 {
278 FCGX_FPrintF(this->req.out, "%s\n\n", headers);
279
280 FCGX_PutStr(chunk.ptr, chunk.len, this->req.out);
281 }
282
283 METHOD(request_t, sendfile, bool,
284 private_request_t *this, char *path, char *mime)
285 {
286 struct stat sb;
287 chunk_t data;
288 void *addr;
289 int fd, written;
290 char buf[24];
291
292 fd = open(path, O_RDONLY);
293 if (fd == -1)
294 {
295 return FALSE;
296 }
297 if (fstat(fd, &sb) == -1)
298 {
299 close(fd);
300 return FALSE;
301 }
302 addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
303 if (addr == MAP_FAILED)
304 {
305 close(fd);
306 return FALSE;
307 }
308
309 /* FCGX does not like large integers, print to a buffer using libc */
310 snprintf(buf, sizeof(buf), "%lld", (int64_t)sb.st_size);
311 FCGX_FPrintF(this->req.out, "Content-Length: %s\n", buf);
312 if (mime)
313 {
314 FCGX_FPrintF(this->req.out, "Content-Type: %s\n", mime);
315 }
316 FCGX_FPrintF(this->req.out, "\n");
317
318 data = chunk_create(addr, sb.st_size);
319
320 while (data.len)
321 {
322 written = FCGX_PutStr(data.ptr, data.len, this->req.out);
323 if (written == -1)
324 {
325 munmap(addr, sb.st_size);
326 close(fd);
327 return FALSE;
328 }
329 data = chunk_skip(data, written);
330 }
331
332 munmap(addr, sb.st_size);
333 close(fd);
334 return TRUE;
335 }
336
337 METHOD(request_t, render, void,
338 private_request_t *this, char *template)
339 {
340 NEOERR* err;
341
342 thread_this->set(thread_this, this);
343 err = cgi_display(this->cgi, template);
344 if (err)
345 {
346 cgi_neo_error(this->cgi, err);
347 nerr_log_error(err);
348 }
349 }
350
351 METHOD(request_t, streamf, int,
352 private_request_t *this, char *format, ...)
353 {
354 va_list args;
355 int written;
356
357 va_start(args, format);
358 written = FCGX_VFPrintF(this->req.out, format, args);
359 va_end(args);
360 if (written >= 0 &&
361 FCGX_FFlush(this->req.out) == -1)
362 {
363 return -1;
364 }
365 return written;
366 }
367
368 METHOD(request_t, set, void,
369 private_request_t *this, char *key, char *value)
370 {
371 hdf_set_value(this->hdf, key, value);
372 }
373
374 METHOD(request_t, setf, void,
375 private_request_t *this, char *format, ...)
376 {
377 va_list args;
378
379 va_start(args, format);
380 hdf_set_valuevf(this->hdf, format, args);
381 va_end(args);
382 }
383
384 METHOD(request_t, get_ref, request_t*,
385 private_request_t *this)
386 {
387 ref_get(&this->ref);
388 return &this->public;
389 }
390
391 METHOD(request_t, destroy, void,
392 private_request_t *this)
393 {
394 if (ref_put(&this->ref))
395 {
396 thread_this->set(thread_this, this);
397 cgi_destroy(&this->cgi);
398 FCGX_Finish_r(&this->req);
399 free(this);
400 }
401 }
402
403 /**
404 * This initialization method is guaranteed to run only once
405 * for all threads.
406 */
407 static void init(void)
408 {
409 cgiwrap_init_emu(NULL, read_cb, writef_cb, write_cb,
410 getenv_cb, putenv_cb, iterenv_cb);
411 thread_this = thread_value_create(NULL);
412 }
413
414 /*
415 * see header file
416 */
417 request_t *request_create(int fd, bool debug)
418 {
419 NEOERR* err;
420 private_request_t *this;
421 bool failed = FALSE;
422
423 INIT(this,
424 .public = {
425 .get_path = _get_path,
426 .get_base = _get_base,
427 .get_host = _get_host,
428 .get_user_agent = _get_user_agent,
429 .add_cookie = _add_cookie,
430 .get_cookie = _get_cookie,
431 .get_query_data = _get_query_data,
432 .get_env_var = _get_env_var,
433 .read_data = _read_data,
434 .session_closed = _session_closed,
435 .close_session = _close_session,
436 .redirect = _redirect,
437 .get_referer = _get_referer,
438 .to_referer = _to_referer,
439 .render = _render,
440 .streamf = _streamf,
441 .serve = _serve,
442 .sendfile = _sendfile,
443 .set = _set,
444 .setf = _setf,
445 .get_ref = _get_ref,
446 .destroy = _destroy,
447 },
448 .ref = 1,
449 );
450
451 thread_cleanup_push(free, this);
452 if (FCGX_InitRequest(&this->req, fd, 0) != 0 ||
453 FCGX_Accept_r(&this->req) != 0)
454 {
455 failed = TRUE;
456 }
457 thread_cleanup_pop(failed);
458 if (failed)
459 {
460 return NULL;
461 }
462
463 pthread_once(&once, init);
464 thread_this->set(thread_this, this);
465
466 while (this->req.envp[this->req_env_len] != NULL)
467 {
468 this->req_env_len++;
469 }
470
471 err = hdf_init(&this->hdf);
472 if (!err)
473 {
474 hdf_set_value(this->hdf, "base", get_base(this));
475 hdf_set_value(this->hdf, "Config.NoCache", "true");
476 if (!debug)
477 {
478 hdf_set_value(this->hdf, "Config.TimeFooter", "0");
479 hdf_set_value(this->hdf, "Config.CompressionEnabled", "1");
480 hdf_set_value(this->hdf, "Config.WhiteSpaceStrip", "2");
481 }
482
483 err = cgi_init(&this->cgi, this->hdf);
484 if (!err)
485 {
486 err = cgi_parse(this->cgi);
487 if (!err)
488 {
489 return &this->public;
490 }
491 cgi_destroy(&this->cgi);
492 }
493 }
494 nerr_log_error(err);
495 FCGX_Finish_r(&this->req);
496 free(this);
497 return NULL;
498 }
499