uuid: Add module for UUID generation
[src/app-framework-binder.git] / src / afb-session.c
1 /*
2  * Copyright (C) 2015-2018 "IoT.bzh"
3  * Author "Fulup Ar Foll"
4  * Author: José Bollo <jose.bollo@iot.bzh>
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 #define _GNU_SOURCE
20 #include <stdio.h>
21 #include <time.h>
22 #include <pthread.h>
23 #include <stdlib.h>
24 #include <stdint.h>
25 #include <limits.h>
26 #include <string.h>
27 #include <errno.h>
28
29 #include "afb-session.h"
30 #include "afb-hook.h"
31 #include "verbose.h"
32 #include "pearson.h"
33 #include "uuid.h"
34
35 #define HEADCOUNT       16
36 #define COOKIECOUNT     8
37 #define COOKIEMASK      (COOKIECOUNT - 1)
38
39 #define _MAXEXP_        ((time_t)(~(time_t)0))
40 #define _MAXEXP2_       ((time_t)((((unsigned long long)_MAXEXP_) >> 1)))
41 #define MAX_EXPIRATION  (_MAXEXP_ >= 0 ? _MAXEXP_ : _MAXEXP2_)
42 #define NOW             (time_now())
43
44 /**
45  * structure for a cookie added to sessions
46  */
47 struct cookie
48 {
49         struct cookie *next;    /**< link to next cookie */
50         const void *key;        /**< pointer key */
51         void *value;            /**< value */
52         void (*freecb)(void*);  /**< function to call when session is closed */
53 };
54
55 /**
56  * structure for session
57  */
58 struct afb_session
59 {
60         struct afb_session *next; /**< link to the next */
61         unsigned refcount;      /**< count of reference to the session */
62         int timeout;            /**< timeout of the session */
63         time_t expiration;      /**< expiration time of the token */
64         pthread_mutex_t mutex;  /**< mutex of the session */
65         struct cookie *cookies[COOKIECOUNT]; /**< cookies of the session */
66         char *lang;             /**< current language setting for the session */
67         uint8_t closed: 1;      /**< is the session closed ? */
68         uint8_t autoclose: 1;   /**< close the session when unreferenced */
69         uint8_t notinset: 1;    /**< session removed from the set of sessions */
70         uuid_stringz_t uuid;    /**< long term authentication of remote client */
71         uuid_stringz_t token;   /**< short term authentication of remote client */
72 };
73
74 /**
75  * structure for managing sessions
76  */
77 static struct {
78         int count;              /**< current number of sessions */
79         int max;                /**< maximum count of sessions */
80         int timeout;            /**< common initial timeout */
81         struct afb_session *heads[HEADCOUNT]; /**< sessions */
82         uuid_stringz_t initok;  /**< common initial token */
83         pthread_mutex_t mutex;  /**< declare a mutex to protect hash table */
84 } sessions = {
85         .count = 0,
86         .max = 10,
87         .timeout = 3600,
88         .heads = { 0 },
89         .initok = { 0 },
90         .mutex = PTHREAD_MUTEX_INITIALIZER
91 };
92
93 /**
94  * Get the actual raw time
95  */
96 static inline time_t time_now()
97 {
98         struct timespec ts;
99         clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
100         return ts.tv_sec;
101 }
102
103 /* lock the set of sessions for exclusive access */
104 static inline void sessionset_lock()
105 {
106         pthread_mutex_lock(&sessions.mutex);
107 }
108
109 /* unlock the set of sessions of exclusive access */
110 static inline void sessionset_unlock()
111 {
112         pthread_mutex_unlock(&sessions.mutex);
113 }
114
115 /*
116  * search within the set of sessions the session of 'uuid'.
117  * 'hashidx' is the precomputed hash for 'uuid'
118  * return the session or NULL
119  */
120 static struct afb_session *sessionset_search(const char *uuid, uint8_t hashidx)
121 {
122         struct afb_session *session;
123
124         session = sessions.heads[hashidx];
125         while (session && strcmp(uuid, session->uuid))
126                 session = session->next;
127
128         return session;
129 }
130
131 /* add 'session' to the set of sessions */
132 static int sessionset_add(struct afb_session *session, uint8_t hashidx)
133 {
134         /* check availability */
135         if (sessions.max && sessions.count >= sessions.max) {
136                 errno = EBUSY;
137                 return -1;
138         }
139
140         /* add the session */
141         session->next = sessions.heads[hashidx];
142         sessions.heads[hashidx] = session;
143         sessions.count++;
144         return 0;
145 }
146
147 /* make a new uuid not used in the set of sessions */
148 static uint8_t sessionset_make_uuid (uuid_stringz_t uuid)
149 {
150         uint8_t hashidx;
151
152         do {
153                 uuid_new_stringz(uuid);
154                 hashidx = pearson4(uuid);
155         } while(sessionset_search(uuid, hashidx));
156         return hashidx;
157 }
158
159 /* lock the 'session' for exclusive access */
160 static inline void session_lock(struct afb_session *session)
161 {
162         pthread_mutex_lock(&session->mutex);
163 }
164
165 /* unlock the 'session' of exclusive access */
166 static inline void session_unlock(struct afb_session *session)
167 {
168         pthread_mutex_unlock(&session->mutex);
169 }
170
171 /* close the 'session' */
172 static void session_close(struct afb_session *session)
173 {
174         int idx;
175         struct cookie *cookie;
176
177         /* close only one time */
178         if (!session->closed) {
179                 /* close it now */
180                 session->closed = 1;
181
182                 /* emit the hook */
183                 afb_hook_session_close(session);
184
185                 /* release cookies */
186                 for (idx = 0 ; idx < COOKIECOUNT ; idx++) {
187                         while ((cookie = session->cookies[idx])) {
188                                 session->cookies[idx] = cookie->next;
189                                 if (cookie->freecb != NULL)
190                                         cookie->freecb(cookie->value);
191                                 free(cookie);
192                         }
193                 }
194         }
195 }
196
197 /* destroy the 'session' */
198 static void session_destroy (struct afb_session *session)
199 {
200         afb_hook_session_destroy(session);
201         pthread_mutex_destroy(&session->mutex);
202         free(session->lang);
203         free(session);
204 }
205
206 /* update expiration of 'session' according to 'now' */
207 static void session_update_expiration(struct afb_session *session, time_t now)
208 {
209         time_t expiration;
210
211         /* compute expiration */
212         expiration = now + afb_session_timeout(session);
213         if (expiration < 0)
214                 expiration = MAX_EXPIRATION;
215
216         /* record the expiration */
217         session->expiration = expiration;
218 }
219
220 /*
221  * Add a new session with the 'uuid' (of 'hashidx')
222  * and the 'timeout' starting from 'now'.
223  * Add it to the set of sessions
224  * Return the created session
225  */
226 static struct afb_session *session_add(const char *uuid, int timeout, time_t now, uint8_t hashidx)
227 {
228         struct afb_session *session;
229
230         /* check arguments */
231         if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
232          || (uuid && strlen(uuid) >= sizeof session->uuid)) {
233                 errno = EINVAL;
234                 return NULL;
235         }
236
237         /* allocates a new one */
238         session = calloc(1, sizeof *session);
239         if (session == NULL) {
240                 errno = ENOMEM;
241                 return NULL;
242         }
243
244         /* initialize */
245         pthread_mutex_init(&session->mutex, NULL);
246         session->refcount = 1;
247         strcpy(session->uuid, uuid);
248         strcpy(session->token, sessions.initok);
249         session->timeout = timeout;
250         session_update_expiration(session, now);
251
252         /* add */
253         if (sessionset_add(session, hashidx)) {
254                 free(session);
255                 return NULL;
256         }
257
258         afb_hook_session_create(session);
259
260         return session;
261 }
262
263 /* Remove expired sessions and return current time (now) */
264 static time_t sessionset_cleanup (int force)
265 {
266         struct afb_session *session, **prv;
267         int idx;
268         time_t now;
269
270         /* Loop on Sessions Table and remove anything that is older than timeout */
271         now = NOW;
272         for (idx = 0 ; idx < HEADCOUNT; idx++) {
273                 prv = &sessions.heads[idx];
274                 while ((session = *prv)) {
275                         session_lock(session);
276                         if (force || session->expiration < now)
277                                 session_close(session);
278                         if (!session->closed)
279                                 prv = &session->next;
280                         else {
281                                 *prv = session->next;
282                                 sessions.count--;
283                                 session->notinset = 1;
284                                 if ( !session->refcount) {
285                                         session_destroy(session);
286                                         continue;
287                                 }
288                         }
289                         session_unlock(session);
290                 }
291         }
292         return now;
293 }
294
295 /**
296  * Initialize the session manager with a 'max_session_count',
297  * an initial common 'timeout' and an initial common token 'initok'.
298  *
299  * @param max_session_count  maximum allowed session count in the same time
300  * @param timeout            the initial default timeout of sessions
301  * @param initok             the initial default token of sessions
302  *
303  */
304 int afb_session_init (int max_session_count, int timeout, const char *initok)
305 {
306         /* check parameters */
307         if (initok && strlen(initok) >= sizeof sessions.initok) {
308                 ERROR("initial token '%s' too long (max length %d)",
309                         initok, ((int)(sizeof sessions.initok)) - 1);
310                 errno = EINVAL;
311                 return -1;
312         }
313
314         /* init the sessionset (after cleanup) */
315         sessionset_lock();
316         sessionset_cleanup(1);
317         sessions.max = max_session_count;
318         sessions.timeout = timeout;
319         if (initok == NULL)
320                 uuid_new_stringz(sessions.initok);
321         else
322                 strcpy(sessions.initok, initok);
323         sessionset_unlock();
324         return 0;
325 }
326
327 /**
328  * Iterate the sessions and call 'callback' with
329  * the 'closure' for each session.
330  */
331 void afb_session_foreach(void (*callback)(void *closure, struct afb_session *session), void *closure)
332 {
333         struct afb_session *session;
334         int idx;
335
336         /* Loop on Sessions Table and remove anything that is older than timeout */
337         sessionset_lock();
338         for (idx = 0 ; idx < HEADCOUNT; idx++) {
339                 session = sessions.heads[idx];
340                 while (session) {
341                         if (!session->closed)
342                                 callback(closure, session);
343                         session = session->next;
344                 }
345         }
346         sessionset_unlock();
347 }
348
349 /**
350  * Cleanup the sessionset of its closed or expired sessions
351  */
352 void afb_session_purge()
353 {
354         sessionset_lock();
355         sessionset_cleanup(0);
356         sessionset_unlock();
357 }
358
359 /**
360  * @return the initial token set at initialization
361  */
362 const char *afb_session_initial_token()
363 {
364         return sessions.initok;
365 }
366
367 /* Searchs the session of 'uuid' */
368 struct afb_session *afb_session_search (const char *uuid)
369 {
370         struct afb_session *session;
371
372         sessionset_lock();
373         sessionset_cleanup(0);
374         session = sessionset_search(uuid, pearson4(uuid));
375         session = afb_session_addref(session);
376         sessionset_unlock();
377         return session;
378
379 }
380
381 /**
382  * Creates a new session with 'timeout'
383  */
384 struct afb_session *afb_session_create (int timeout)
385 {
386         return afb_session_get(NULL, timeout, NULL);
387 }
388
389 /**
390  * Returns the timeout of 'session' in seconds
391  */
392 int afb_session_timeout(struct afb_session *session)
393 {
394         int timeout;
395
396         /* compute timeout */
397         timeout = session->timeout;
398         if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
399                 timeout = sessions.timeout;
400         if (timeout < 0)
401                 timeout = INT_MAX;
402         return timeout;
403 }
404
405 /**
406  * Returns the second remaining before expiration of 'session'
407  */
408 int afb_session_what_remains(struct afb_session *session)
409 {
410         return (int)(session->expiration - NOW);
411 }
412
413 /* This function will return exiting session or newly created session */
414 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
415 {
416         uuid_stringz_t _uuid_;
417         uint8_t hashidx;
418         struct afb_session *session;
419         time_t now;
420         int c;
421
422         /* cleaning */
423         sessionset_lock();
424         now = sessionset_cleanup(0);
425
426         /* search for an existing one not too old */
427         if (!uuid) {
428                 hashidx = sessionset_make_uuid(_uuid_);
429                 uuid = _uuid_;
430         } else {
431                 hashidx = pearson4(uuid);
432                 session = sessionset_search(uuid, hashidx);
433                 if (session) {
434                         /* session found */
435                         afb_session_addref(session);
436                         c = 0;
437                         goto end;
438                 }
439         }
440         /* create the session */
441         session = session_add(uuid, timeout, now, hashidx);
442         c = 1;
443 end:
444         sessionset_unlock();
445         if (created)
446                 *created = c;
447
448         return session;
449 }
450
451 /* increase the use count on 'session' (can be NULL) */
452 struct afb_session *afb_session_addref(struct afb_session *session)
453 {
454         if (session != NULL) {
455                 afb_hook_session_addref(session);
456                 session_lock(session);
457                 session->refcount++;
458                 session_unlock(session);
459         }
460         return session;
461 }
462
463 /* decrease the use count of 'session' (can be NULL) */
464 void afb_session_unref(struct afb_session *session)
465 {
466         if (session == NULL)
467                 return;
468
469         afb_hook_session_unref(session);
470         session_lock(session);
471         if (!--session->refcount) {
472                 if (session->autoclose)
473                         session_close(session);
474                 if (session->notinset) {
475                         session_destroy(session);
476                         return;
477                 }
478         }
479         session_unlock(session);
480 }
481
482 /* close 'session' */
483 void afb_session_close (struct afb_session *session)
484 {
485         session_lock(session);
486         session_close(session);
487         session_unlock(session);
488 }
489
490 /**
491  * Set the 'autoclose' flag of the 'session'
492  *
493  * A session whose autoclose flag is true will close as
494  * soon as it is no more referenced.
495  *
496  * @param session    the session to set
497  * @param autoclose  the value to set
498  */
499 void afb_session_set_autoclose(struct afb_session *session, int autoclose)
500 {
501         session->autoclose = !!autoclose;
502 }
503
504 /* is 'session' closed? */
505 int afb_session_is_closed (struct afb_session *session)
506 {
507         return session->closed;
508 }
509
510 /*
511  * check whether the token of 'session' is 'token'
512  * return 1 if true or 0 otherwise
513  */
514 int afb_session_check_token (struct afb_session *session, const char *token)
515 {
516         int r;
517
518         session_lock(session);
519         r = !session->closed
520           && session->expiration >= NOW
521           && !(session->token[0] && strcmp (token, session->token));
522         session_unlock(session);
523         return r;
524 }
525
526 /* generate a new token and update client context */
527 void afb_session_new_token (struct afb_session *session)
528 {
529         session_lock(session);
530         uuid_new_stringz(session->token);
531         session_update_expiration(session, NOW);
532         afb_hook_session_renew(session);
533         session_unlock(session);
534 }
535
536 /* Returns the uuid of 'session' */
537 const char *afb_session_uuid (struct afb_session *session)
538 {
539         return session->uuid;
540 }
541
542 /* Returns the token of 'session' */
543 const char *afb_session_token (struct afb_session *session)
544 {
545         return session->token;
546 }
547
548 /**
549  * Get the index of the 'key' in the cookies array.
550  * @param key the key to scan
551  * @return the index of the list for key within cookies
552  */
553 static int cookeyidx(const void *key)
554 {
555         intptr_t x = (intptr_t)key;
556         unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
557         return r & COOKIEMASK;
558 }
559
560 /**
561  * Set, get, replace, remove a cookie of 'key' for the 'session'
562  *
563  * The behaviour of this function depends on its parameters:
564  *
565  * @param session       the session
566  * @param key           the key of the cookie
567  * @param makecb        the creation function or NULL
568  * @param freecb        the release function or NULL
569  * @param closure       an argument for makecb or the value if makecb==NULL
570  * @param replace       a boolean enforcing replecement of the previous value
571  *
572  * @return the value of the cookie
573  *
574  * The 'key' is a pointer and compared as pointers.
575  *
576  * For getting the current value of the cookie:
577  *
578  *   afb_session_cookie(session, key, NULL, NULL, NULL, 0)
579  *
580  * For storing the value of the cookie
581  *
582  *   afb_session_cookie(session, key, NULL, NULL, value, 1)
583  */
584 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace)
585 {
586         int idx;
587         void *value;
588         struct cookie *cookie, **prv;
589
590         /* get key hashed index */
591         idx = cookeyidx(key);
592
593         /* lock session and search for the cookie of 'key' */
594         session_lock(session);
595         prv = &session->cookies[idx];
596         for (;;) {
597                 cookie = *prv;
598                 if (!cookie) {
599                         /* 'key' not found, create value using 'closure' and 'makecb' */
600                         value = makecb ? makecb(closure) : closure;
601                         /* store the the only if it has some meaning */
602                         if (replace || makecb || freecb) {
603                                 cookie = malloc(sizeof *cookie);
604                                 if (!cookie) {
605                                         errno = ENOMEM;
606                                         /* calling freecb if there is no makecb may have issue */
607                                         if (makecb && freecb)
608                                                 freecb(value);
609                                         value = NULL;
610                                 } else {
611                                         cookie->key = key;
612                                         cookie->value = value;
613                                         cookie->freecb = freecb;
614                                         cookie->next = NULL;
615                                         *prv = cookie;
616                                 }
617                         }
618                         break;
619                 } else if (cookie->key == key) {
620                         /* cookie of key found */
621                         if (!replace)
622                                 /* not replacing, get the value */
623                                 value = cookie->value;
624                         else {
625                                 /* create value using 'closure' and 'makecb' */
626                                 value = makecb ? makecb(closure) : closure;
627
628                                 /* free previous value is needed */
629                                 if (cookie->value != value && cookie->freecb)
630                                         cookie->freecb(cookie->value);
631
632                                 /* if both value and freecb are NULL drop the cookie */
633                                 if (!value && !freecb) {
634                                         *prv = cookie->next;
635                                         free(cookie);
636                                 } else {
637                                         /* store the value and its releaser */
638                                         cookie->value = value;
639                                         cookie->freecb = freecb;
640                                 }
641                         }
642                         break;
643                 } else {
644                         prv = &(cookie->next);
645                 }
646         }
647
648         /* unlock the session and return the value */
649         session_unlock(session);
650         return value;
651 }
652
653 /**
654  * Get the cookie of 'key' in the 'session'.
655  *
656  * @param session  the session to search in
657  * @param key      the key of the data to retrieve
658  *
659  * @return the data staored for the key or NULL if the key isn't found
660  */
661 void *afb_session_get_cookie(struct afb_session *session, const void *key)
662 {
663         return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
664 }
665
666 /**
667  * Set the cookie of 'key' in the 'session' to the 'value' that can be
668  * cleaned using 'freecb' (if not null).
669  *
670  * @param session  the session to set
671  * @param key      the key of the data to store
672  * @param value    the value to store at key
673  * @param freecb   a function to use when the cookie value is to remove (or null)
674  *
675  * @return 0 in case of success or -1 in case of error
676  */
677 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
678 {
679         return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));
680 }
681
682 /**
683  * Set the language attached to the session
684  *
685  * @param session the session to set
686  * @param lang    the language specifiction to set to session
687  *
688  * @return 0 in case of success or -1 in case of error
689  */
690 int afb_session_set_language(struct afb_session *session, const char *lang)
691 {
692         char *oldl, *newl;
693
694         newl = strdup(lang);
695         if (newl == NULL)
696                 return -1;
697
698         oldl = session->lang;
699         session->lang = newl;
700         free(oldl);
701         return 0;
702 }
703
704 /**
705  * Get the language attached to the session
706  *
707  * @param session the session to query
708  * @param lang    a default language specifiction
709  *
710  * @return the langauage specification to use for session
711  */
712 const char *afb_session_get_language(struct afb_session *session, const char *lang)
713 {
714         return session->lang ?: lang;
715 }