Migrated libfast to INIT/METHOD macros
[strongswan.git] / src / libfast / dispatcher.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 #include "dispatcher.h"
17
18 #include "request.h"
19 #include "session.h"
20
21 #include <fcgiapp.h>
22 #include <signal.h>
23 #include <unistd.h>
24
25 #include <debug.h>
26 #include <threading/thread.h>
27 #include <threading/condvar.h>
28 #include <threading/mutex.h>
29 #include <utils/linked_list.h>
30 #include <utils/hashtable.h>
31
32 /** Intervall to check for expired sessions, in seconds */
33 #define CLEANUP_INTERVAL 30
34
35 typedef struct private_dispatcher_t private_dispatcher_t;
36
37 /**
38 * private data of the task manager
39 */
40 struct private_dispatcher_t {
41
42 /**
43 * public functions
44 */
45 dispatcher_t public;
46
47 /**
48 * fcgi socket fd
49 */
50 int fd;
51
52 /**
53 * thread list
54 */
55 thread_t **threads;
56
57 /**
58 * number of threads in "threads"
59 */
60 int thread_count;
61
62 /**
63 * session locking mutex
64 */
65 mutex_t *mutex;
66
67 /**
68 * Hahstable with active sessions
69 */
70 hashtable_t *sessions;
71
72 /**
73 * session timeout
74 */
75 time_t timeout;
76
77 /**
78 * timestamp of last session cleanup round
79 */
80 time_t last_cleanup;
81
82 /**
83 * running in debug mode?
84 */
85 bool debug;
86
87 /**
88 * List of controllers controller_constructor_t
89 */
90 linked_list_t *controllers;
91
92 /**
93 * List of filters filter_constructor_t
94 */
95 linked_list_t *filters;
96
97 /**
98 * constructor function to create session context (in controller_entry_t)
99 */
100 context_constructor_t context_constructor;
101
102 /**
103 * user param to context constructor
104 */
105 void *param;
106 };
107
108 typedef struct {
109 /** constructor function */
110 controller_constructor_t constructor;
111 /** parameter to constructor */
112 void *param;
113 } controller_entry_t;
114
115 typedef struct {
116 /** constructor function */
117 filter_constructor_t constructor;
118 /** parameter to constructor */
119 void *param;
120 } filter_entry_t;
121
122 typedef struct {
123 /** session instance */
124 session_t *session;
125 /** condvar to wait for session */
126 condvar_t *cond;
127 /** client host address, to prevent session hijacking */
128 char *host;
129 /** TRUE if session is in use */
130 bool in_use;
131 /** last use of the session */
132 time_t used;
133 /** has the session been closed by the handler? */
134 bool closed;
135 } session_entry_t;
136
137 /**
138 * create a session and instanciate controllers
139 */
140 static session_t* load_session(private_dispatcher_t *this)
141 {
142 enumerator_t *enumerator;
143 controller_entry_t *centry;
144 filter_entry_t *fentry;
145 session_t *session;
146 context_t *context = NULL;
147 controller_t *controller;
148 filter_t *filter;
149
150 if (this->context_constructor)
151 {
152 context = this->context_constructor(this->param);
153 }
154 session = session_create(context);
155
156 enumerator = this->controllers->create_enumerator(this->controllers);
157 while (enumerator->enumerate(enumerator, &centry))
158 {
159 controller = centry->constructor(context, centry->param);
160 session->add_controller(session, controller);
161 }
162 enumerator->destroy(enumerator);
163
164 enumerator = this->filters->create_enumerator(this->filters);
165 while (enumerator->enumerate(enumerator, &fentry))
166 {
167 filter = fentry->constructor(context, fentry->param);
168 session->add_filter(session, filter);
169 }
170 enumerator->destroy(enumerator);
171
172 return session;
173 }
174
175 /**
176 * create a new session entry
177 */
178 static session_entry_t *session_entry_create(private_dispatcher_t *this,
179 char *host)
180 {
181 session_entry_t *entry;
182
183 INIT(entry,
184 .cond = condvar_create(CONDVAR_TYPE_DEFAULT),
185 .session = load_session(this),
186 .used = time_monotonic(NULL),
187 .host = strdup(host),
188 );
189 return entry;
190 }
191
192 /**
193 * destroy a session
194 */
195 static void session_entry_destroy(session_entry_t *entry)
196 {
197 entry->session->destroy(entry->session);
198 entry->cond->destroy(entry->cond);
199 free(entry->host);
200 free(entry);
201 }
202
203 METHOD(dispatcher_t, add_controller, void,
204 private_dispatcher_t *this, controller_constructor_t constructor,
205 void *param)
206 {
207 controller_entry_t *entry;
208
209 INIT(entry,
210 .constructor = constructor,
211 .param = param,
212 );
213 this->controllers->insert_last(this->controllers, entry);
214 }
215
216 METHOD(dispatcher_t, add_filter, void,
217 private_dispatcher_t *this, filter_constructor_t constructor, void *param)
218 {
219 filter_entry_t *entry;
220
221 INIT(entry,
222 .constructor = constructor,
223 .param = param,
224 );
225 this->filters->insert_last(this->filters, entry);
226 }
227
228 /**
229 * Hashtable hash function
230 */
231 static u_int session_hash(char *sid)
232 {
233 return chunk_hash(chunk_create(sid, strlen(sid)));
234 }
235
236 /**
237 * Hashtable equals function
238 */
239 static bool session_equals(char *sid1, char *sid2)
240 {
241 return streq(sid1, sid2);
242 }
243
244 /**
245 * Cleanup unused sessions
246 */
247 static void cleanup_sessions(private_dispatcher_t *this, time_t now)
248 {
249 if (this->last_cleanup < now - CLEANUP_INTERVAL)
250 {
251 char *sid;
252 session_entry_t *entry;
253 enumerator_t *enumerator;
254 linked_list_t *remove;
255
256 this->last_cleanup = now;
257 remove = linked_list_create();
258 enumerator = this->sessions->create_enumerator(this->sessions);
259 while (enumerator->enumerate(enumerator, &sid, &entry))
260 {
261 /* check all sessions for timeout or close flag */
262 if (!entry->in_use &&
263 (entry->used < now - this->timeout || entry->closed))
264 {
265 remove->insert_last(remove, sid);
266 }
267 }
268 enumerator->destroy(enumerator);
269
270 while (remove->remove_last(remove, (void**)&sid) == SUCCESS)
271 {
272 entry = this->sessions->remove(this->sessions, sid);
273 if (entry)
274 {
275 session_entry_destroy(entry);
276 }
277 }
278 remove->destroy(remove);
279 }
280 }
281
282 /**
283 * Actual dispatching code
284 */
285 static void dispatch(private_dispatcher_t *this)
286 {
287 thread_cancelability(FALSE);
288
289 while (TRUE)
290 {
291 request_t *request;
292 session_entry_t *found = NULL;
293 time_t now;
294 char *sid;
295
296 thread_cancelability(TRUE);
297 request = request_create(this->fd, this->debug);
298 thread_cancelability(FALSE);
299
300 if (request == NULL)
301 {
302 continue;
303 }
304 now = time_monotonic(NULL);
305 sid = request->get_cookie(request, "SID");
306
307 this->mutex->lock(this->mutex);
308 if (sid)
309 {
310 found = this->sessions->get(this->sessions, sid);
311 }
312 if (found && !streq(found->host, request->get_host(request)))
313 {
314 found = NULL;
315 }
316 if (found)
317 {
318 /* wait until session is unused */
319 while (found->in_use)
320 {
321 found->cond->wait(found->cond, this->mutex);
322 }
323 }
324 else
325 { /* create a new session if not found */
326 found = session_entry_create(this, request->get_host(request));
327 sid = found->session->get_sid(found->session);
328 this->sessions->put(this->sessions, sid, found);
329 }
330 found->in_use = TRUE;
331 this->mutex->unlock(this->mutex);
332
333 /* start processing */
334 found->session->process(found->session, request);
335 found->used = time_monotonic(NULL);
336
337 /* release session */
338 this->mutex->lock(this->mutex);
339 found->in_use = FALSE;
340 found->closed = request->session_closed(request);
341 found->cond->signal(found->cond);
342 cleanup_sessions(this, now);
343 this->mutex->unlock(this->mutex);
344
345 request->destroy(request);
346 }
347 }
348
349 METHOD(dispatcher_t, run, void,
350 private_dispatcher_t *this, int threads)
351 {
352 this->thread_count = threads;
353 this->threads = malloc(sizeof(thread_t*) * threads);
354 while (threads)
355 {
356 this->threads[threads - 1] = thread_create((thread_main_t)dispatch,
357 this);
358 if (this->threads[threads - 1])
359 {
360 threads--;
361 }
362 }
363 }
364
365 METHOD(dispatcher_t, waitsignal, void,
366 private_dispatcher_t *this)
367 {
368 sigset_t set;
369 int sig;
370
371 sigemptyset(&set);
372 sigaddset(&set, SIGINT);
373 sigaddset(&set, SIGTERM);
374 sigaddset(&set, SIGHUP);
375 sigprocmask(SIG_BLOCK, &set, NULL);
376 sigwait(&set, &sig);
377 }
378
379 METHOD(dispatcher_t, destroy, void,
380 private_dispatcher_t *this)
381 {
382 char *sid;
383 session_entry_t *entry;
384 enumerator_t *enumerator;
385
386 FCGX_ShutdownPending();
387 while (this->thread_count--)
388 {
389 thread_t *thread = this->threads[this->thread_count];
390 thread->cancel(thread);
391 thread->join(thread);
392 }
393 enumerator = this->sessions->create_enumerator(this->sessions);
394 while (enumerator->enumerate(enumerator, &sid, &entry))
395 {
396 session_entry_destroy(entry);
397 }
398 enumerator->destroy(enumerator);
399 this->sessions->destroy(this->sessions);
400 this->controllers->destroy_function(this->controllers, free);
401 this->filters->destroy_function(this->filters, free);
402 this->mutex->destroy(this->mutex);
403 free(this->threads);
404 free(this);
405 }
406
407 /*
408 * see header file
409 */
410 dispatcher_t *dispatcher_create(char *socket, bool debug, int timeout,
411 context_constructor_t constructor, void *param)
412 {
413 private_dispatcher_t *this;
414
415 INIT(this,
416 .public = {
417 .add_controller = _add_controller,
418 .add_filter = _add_filter,
419 .run = _run,
420 .waitsignal = _waitsignal,
421 .destroy = _destroy,
422 },
423 .sessions = hashtable_create((void*)session_hash,
424 (void*)session_equals, 4096),
425 .controllers = linked_list_create(),
426 .filters = linked_list_create(),
427 .context_constructor = constructor,
428 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
429 .param = param,
430 .timeout = timeout,
431 .last_cleanup = time_monotonic(NULL),
432 .debug = debug,
433 );
434
435 FCGX_Init();
436
437 if (socket)
438 {
439 unlink(socket);
440 this->fd = FCGX_OpenSocket(socket, 10);
441 }
442 return &this->public;
443 }
444