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