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