Fedora 30 packaging fix issu
[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 #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                 /* emit the hook */
244                 afb_hook_session_close(session);
245
246                 /* release cookies */
247                 for (idx = 0 ; idx < COOKIECOUNT ; idx++) {
248                         while ((cookie = session->cookies[idx])) {
249                                 session->cookies[idx] = cookie->next;
250                                 if (cookie->freecb != NULL)
251                                         cookie->freecb(cookie->value);
252                                 free(cookie);
253                         }
254                 }
255         }
256 }
257
258 /* destroy the 'session' */
259 static void session_destroy (struct afb_session *session)
260 {
261         afb_hook_session_destroy(session);
262         pthread_mutex_destroy(&session->mutex);
263         free(session->lang);
264         free(session);
265 }
266
267 /* update expiration of 'session' according to 'now' */
268 static void session_update_expiration(struct afb_session *session, time_t now)
269 {
270         time_t expiration;
271
272         /* compute expiration */
273         expiration = now + afb_session_timeout(session);
274         if (expiration < 0)
275                 expiration = MAX_EXPIRATION;
276
277         /* record the expiration */
278         session->expiration = expiration;
279 }
280
281 /*
282  * Add a new session with the 'uuid' (of 'hashidx')
283  * and the 'timeout' starting from 'now'.
284  * Add it to the set of sessions
285  * Return the created session
286  */
287 static struct afb_session *session_add(const char *uuid, int timeout, time_t now, uint8_t hashidx)
288 {
289         struct afb_session *session;
290
291         /* check arguments */
292         if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
293          || (uuid && strlen(uuid) >= sizeof session->uuid)) {
294                 errno = EINVAL;
295                 return NULL;
296         }
297
298         /* allocates a new one */
299         session = calloc(1, sizeof *session);
300         if (session == NULL) {
301                 errno = ENOMEM;
302                 return NULL;
303         }
304
305         /* initialize */
306         pthread_mutex_init(&session->mutex, NULL);
307         session->refcount = 1;
308         strcpy(session->uuid, uuid);
309         strcpy(session->token, sessions.initok);
310         session->timeout = timeout;
311         session_update_expiration(session, now);
312
313         /* add */
314         if (sessionset_add(session, hashidx)) {
315                 free(session);
316                 return NULL;
317         }
318
319         afb_hook_session_create(session);
320
321         return session;
322 }
323
324 /* Remove expired sessions and return current time (now) */
325 static time_t sessionset_cleanup (int force)
326 {
327         struct afb_session *session, **prv;
328         int idx;
329         time_t now;
330
331         /* Loop on Sessions Table and remove anything that is older than timeout */
332         now = NOW;
333         for (idx = 0 ; idx < HEADCOUNT; idx++) {
334                 prv = &sessions.heads[idx];
335                 while ((session = *prv)) {
336                         session_lock(session);
337                         if (force || session->expiration < now)
338                                 session_close(session);
339                         if (!session->closed)
340                                 prv = &session->next;
341                         else {
342                                 *prv = session->next;
343                                 sessions.count--;
344                                 session->notinset = 1;
345                                 if ( !session->refcount) {
346                                         session_destroy(session);
347                                         continue;
348                                 }
349                         }
350                         session_unlock(session);
351                 }
352         }
353         return now;
354 }
355
356 /**
357  * Initialize the session manager with a 'max_session_count',
358  * an initial common 'timeout' and an initial common token 'initok'.
359  *
360  * @param max_session_count  maximum allowed session count in the same time
361  * @param timeout            the initial default timeout of sessions
362  * @param initok             the initial default token of sessions
363  *
364  */
365 int afb_session_init (int max_session_count, int timeout, const char *initok)
366 {
367         /* check parameters */
368         if (initok && strlen(initok) >= sizeof sessions.initok) {
369                 ERROR("initial token '%s' too long (max length %d)",
370                         initok, ((int)(sizeof sessions.initok)) - 1);
371                 errno = EINVAL;
372                 return -1;
373         }
374
375         /* init the sessionset (after cleanup) */
376         sessionset_lock();
377         sessionset_cleanup(1);
378         sessions.max = max_session_count;
379         sessions.timeout = timeout;
380         if (initok == NULL)
381                 new_uuid(sessions.initok);
382         else
383                 strcpy(sessions.initok, initok);
384         sessionset_unlock();
385         return 0;
386 }
387
388 /**
389  * Iterate the sessions and call 'callback' with
390  * the 'closure' for each session.
391  */
392 void afb_session_foreach(void (*callback)(void *closure, struct afb_session *session), void *closure)
393 {
394         struct afb_session *session;
395         int idx;
396
397         /* Loop on Sessions Table and remove anything that is older than timeout */
398         sessionset_lock();
399         for (idx = 0 ; idx < HEADCOUNT; idx++) {
400                 session = sessions.heads[idx];
401                 while (session) {
402                         if (!session->closed)
403                                 callback(closure, session);
404                         session = session->next;
405                 }
406         }
407         sessionset_unlock();
408 }
409
410 /**
411  * Cleanup the sessionset of its closed or expired sessions
412  */
413 void afb_session_purge()
414 {
415         sessionset_lock();
416         sessionset_cleanup(0);
417         sessionset_unlock();
418 }
419
420 /**
421  * @return the initial token set at initialization
422  */
423 const char *afb_session_initial_token()
424 {
425         return sessions.initok;
426 }
427
428 /* Searchs the session of 'uuid' */
429 struct afb_session *afb_session_search (const char *uuid)
430 {
431         struct afb_session *session;
432
433         sessionset_lock();
434         sessionset_cleanup(0);
435         session = sessionset_search(uuid, pearson4(uuid));
436         session = afb_session_addref(session);
437         sessionset_unlock();
438         return session;
439
440 }
441
442 /**
443  * Creates a new session with 'timeout'
444  */
445 struct afb_session *afb_session_create (int timeout)
446 {
447         return afb_session_get(NULL, timeout, NULL);
448 }
449
450 /**
451  * Returns the timeout of 'session' in seconds
452  */
453 int afb_session_timeout(struct afb_session *session)
454 {
455         int timeout;
456
457         /* compute timeout */
458         timeout = session->timeout;
459         if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
460                 timeout = sessions.timeout;
461         if (timeout < 0)
462                 timeout = INT_MAX;
463         return timeout;
464 }
465
466 /**
467  * Returns the second remaining before expiration of 'session'
468  */
469 int afb_session_what_remains(struct afb_session *session)
470 {
471         return (int)(session->expiration - NOW);
472 }
473
474 /* This function will return exiting session or newly created session */
475 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
476 {
477         char _uuid_[SIZEUUID];
478         uint8_t hashidx;
479         struct afb_session *session;
480         time_t now;
481         int c;
482
483         /* cleaning */
484         sessionset_lock();
485         now = sessionset_cleanup(0);
486
487         /* search for an existing one not too old */
488         if (!uuid) {
489                 hashidx = sessionset_make_uuid(_uuid_);
490                 uuid = _uuid_;
491         } else {
492                 hashidx = pearson4(uuid);
493                 session = sessionset_search(uuid, hashidx);
494                 if (session) {
495                         /* session found */
496                         afb_session_addref(session);
497                         c = 0;
498                         goto end;
499                 }
500         }
501         /* create the session */
502         session = session_add(uuid, timeout, now, hashidx);
503         c = 1;
504 end:
505         sessionset_unlock();
506         if (created)
507                 *created = c;
508
509         return session;
510 }
511
512 /* increase the use count on 'session' (can be NULL) */
513 struct afb_session *afb_session_addref(struct afb_session *session)
514 {
515         if (session != NULL) {
516                 afb_hook_session_addref(session);
517                 session_lock(session);
518                 session->refcount++;
519                 session_unlock(session);
520         }
521         return session;
522 }
523
524 /* decrease the use count of 'session' (can be NULL) */
525 void afb_session_unref(struct afb_session *session)
526 {
527         if (session == NULL)
528                 return;
529
530         afb_hook_session_unref(session);
531         session_lock(session);
532         if (!--session->refcount) {
533                 if (session->autoclose)
534                         session_close(session);
535                 if (session->notinset) {
536                         session_destroy(session);
537                         return;
538                 }
539         }
540         session_unlock(session);
541 }
542
543 /* close 'session' */
544 void afb_session_close (struct afb_session *session)
545 {
546         session_lock(session);
547         session_close(session);
548         session_unlock(session);
549 }
550
551 /**
552  * Set the 'autoclose' flag of the 'session'
553  *
554  * A session whose autoclose flag is true will close as
555  * soon as it is no more referenced.
556  *
557  * @param session    the session to set
558  * @param autoclose  the value to set
559  */
560 void afb_session_set_autoclose(struct afb_session *session, int autoclose)
561 {
562         session->autoclose = !!autoclose;
563 }
564
565 /* is 'session' closed? */
566 int afb_session_is_closed (struct afb_session *session)
567 {
568         return session->closed;
569 }
570
571 /*
572  * check whether the token of 'session' is 'token'
573  * return 1 if true or 0 otherwise
574  */
575 int afb_session_check_token (struct afb_session *session, const char *token)
576 {
577         int r;
578
579         session_lock(session);
580         r = !session->closed
581           && session->expiration >= NOW
582           && !(session->token[0] && strcmp (token, session->token));
583         session_unlock(session);
584         return r;
585 }
586
587 /* generate a new token and update client context */
588 void afb_session_new_token (struct afb_session *session)
589 {
590         session_lock(session);
591         new_uuid(session->token);
592         session_update_expiration(session, NOW);
593         afb_hook_session_renew(session);
594         session_unlock(session);
595 }
596
597 /* Returns the uuid of 'session' */
598 const char *afb_session_uuid (struct afb_session *session)
599 {
600         return session->uuid;
601 }
602
603 /* Returns the token of 'session' */
604 const char *afb_session_token (struct afb_session *session)
605 {
606         return session->token;
607 }
608
609 /**
610  * Get the index of the 'key' in the cookies array.
611  * @param key the key to scan
612  * @return the index of the list for key within cookies
613  */
614 static int cookeyidx(const void *key)
615 {
616         intptr_t x = (intptr_t)key;
617         unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
618         return r & COOKIEMASK;
619 }
620
621 /**
622  * Set, get, replace, remove a cookie of 'key' for the 'session'
623  *
624  * The behaviour of this function depends on its parameters:
625  *
626  * @param session       the session
627  * @param key           the key of the cookie
628  * @param makecb        the creation function or NULL
629  * @param freecb        the release function or NULL
630  * @param closure       an argument for makecb or the value if makecb==NULL
631  * @param replace       a boolean enforcing replecement of the previous value
632  *
633  * @return the value of the cookie
634  *
635  * The 'key' is a pointer and compared as pointers.
636  *
637  * For getting the current value of the cookie:
638  *
639  *   afb_session_cookie(session, key, NULL, NULL, NULL, 0)
640  *
641  * For storing the value of the cookie
642  *
643  *   afb_session_cookie(session, key, NULL, NULL, value, 1)
644  */
645 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace)
646 {
647         int idx;
648         void *value;
649         struct cookie *cookie, **prv;
650
651         /* get key hashed index */
652         idx = cookeyidx(key);
653
654         /* lock session and search for the cookie of 'key' */
655         session_lock(session);
656         prv = &session->cookies[idx];
657         for (;;) {
658                 cookie = *prv;
659                 if (!cookie) {
660                         /* 'key' not found, create value using 'closure' and 'makecb' */
661                         value = makecb ? makecb(closure) : closure;
662                         /* store the the only if it has some meaning */
663                         if (replace || makecb || freecb) {
664                                 cookie = malloc(sizeof *cookie);
665                                 if (!cookie) {
666                                         errno = ENOMEM;
667                                         /* calling freecb if there is no makecb may have issue */
668                                         if (makecb && freecb)
669                                                 freecb(value);
670                                         value = NULL;
671                                 } else {
672                                         cookie->key = key;
673                                         cookie->value = value;
674                                         cookie->freecb = freecb;
675                                         cookie->next = NULL;
676                                         *prv = cookie;
677                                 }
678                         }
679                         break;
680                 } else if (cookie->key == key) {
681                         /* cookie of key found */
682                         if (!replace)
683                                 /* not replacing, get the value */
684                                 value = cookie->value;
685                         else {
686                                 /* create value using 'closure' and 'makecb' */
687                                 value = makecb ? makecb(closure) : closure;
688
689                                 /* free previous value is needed */
690                                 if (cookie->value != value && cookie->freecb)
691                                         cookie->freecb(cookie->value);
692
693                                 /* if both value and freecb are NULL drop the cookie */
694                                 if (!value && !freecb) {
695                                         *prv = cookie->next;
696                                         free(cookie);
697                                 } else {
698                                         /* store the value and its releaser */
699                                         cookie->value = value;
700                                         cookie->freecb = freecb;
701                                 }
702                         }
703                         break;
704                 } else {
705                         prv = &(cookie->next);
706                 }
707         }
708
709         /* unlock the session and return the value */
710         session_unlock(session);
711         return value;
712 }
713
714 /**
715  * Get the cookie of 'key' in the 'session'.
716  *
717  * @param session  the session to search in
718  * @param key      the key of the data to retrieve
719  *
720  * @return the data staored for the key or NULL if the key isn't found
721  */
722 void *afb_session_get_cookie(struct afb_session *session, const void *key)
723 {
724         return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
725 }
726
727 /**
728  * Set the cookie of 'key' in the 'session' to the 'value' that can be
729  * cleaned using 'freecb' (if not null).
730  *
731  * @param session  the session to set
732  * @param key      the key of the data to store
733  * @param value    the value to store at key
734  * @param freecb   a function to use when the cookie value is to remove (or null)
735  *
736  * @return 0 in case of success or -1 in case of error
737  */
738 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
739 {
740         return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));
741 }
742
743 /**
744  * Set the language attached to the session
745  *
746  * @param session the session to set
747  * @param lang    the language specifiction to set to session
748  *
749  * @return 0 in case of success or -1 in case of error
750  */
751 int afb_session_set_language(struct afb_session *session, const char *lang)
752 {
753         char *oldl, *newl;
754
755         newl = strdup(lang);
756         if (newl == NULL)
757                 return -1;
758
759         oldl = session->lang;
760         session->lang = newl;
761         free(oldl);
762         return 0;
763 }
764
765 /**
766  * Get the language attached to the session
767  *
768  * @param session the session to query
769  * @param lang    a default language specifiction
770  *
771  * @return the langauage specification to use for session
772  */
773 const char *afb_session_get_language(struct afb_session *session, const char *lang)
774 {
775         return session->lang ?: lang;
776 }