Allow detection of session closure
[src/app-framework-binder.git] / src / afb-session.c
1 /*
2  * Copyright (C) 2015, 2016, 2017 "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 <string.h>
25 #include <uuid/uuid.h>
26 #include <assert.h>
27 #include <errno.h>
28
29 #include <json-c/json.h>
30
31 #include "afb-session.h"
32 #include "verbose.h"
33
34 #define COOKEYCOUNT  8
35 #define COOKEYMASK   (COOKEYCOUNT - 1)
36
37 #define NOW (time(NULL))
38
39 struct cookie
40 {
41         struct cookie *next;
42         const void *key;
43         void *value;
44         void (*freecb)(void*);
45 };
46
47 struct afb_session
48 {
49         unsigned refcount;
50         unsigned loa;
51         int timeout;
52         time_t expiration;    // expiration time of the token
53         time_t access;
54         char uuid[37];        // long term authentication of remote client
55         char token[37];       // short term authentication of remote client
56         struct cookie *cookies[COOKEYCOUNT];
57 };
58
59 // Session UUID are store in a simple array [for 10 sessions this should be enough]
60 static struct {
61         pthread_mutex_t mutex;          // declare a mutex to protect hash table
62         struct afb_session **store;          // sessions store
63         int count;                      // current number of sessions
64         int max;
65         int timeout;
66         char initok[37];
67 } sessions;
68
69 /**
70  * Get the index of the 'key' in the cookies array.
71  * @param key the key to scan
72  * @return the index of the list for key within cookies
73  */
74 static int cookeyidx(const void *key)
75 {
76         intptr_t x = (intptr_t)key;
77         unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
78         return r & COOKEYMASK;
79 }
80
81 /* generate a uuid */
82 static void new_uuid(char uuid[37])
83 {
84         uuid_t newuuid;
85         uuid_generate(newuuid);
86         uuid_unparse_lower(newuuid, uuid);
87 }
88
89 // Free context [XXXX Should be protected again memory abort XXXX]
90 static void free_data (struct afb_session *session)
91 {
92         int idx;
93         struct cookie *cookie, *next;
94
95         // free cookies
96         for (idx = 0 ; idx < COOKEYCOUNT ; idx++) {
97                 cookie = session->cookies[idx];
98                 session->cookies[idx] = NULL;
99                 while (cookie != NULL) {
100                         next = cookie->next;
101                         if (cookie->freecb != NULL)
102                                 cookie->freecb(cookie->value);
103                         free(cookie);
104                         cookie = next;
105                 }
106         }
107 }
108
109 // Create a new store in RAM, not that is too small it will be automatically extended
110 void afb_session_init (int max_session_count, int timeout, const char *initok)
111 {
112         // let's create as store as hashtable does not have any
113         sessions.store = calloc (1 + (unsigned)max_session_count, sizeof(struct afb_session));
114         sessions.max = max_session_count;
115         sessions.timeout = timeout;
116         if (initok == NULL)
117                 /* without token, a secret is made to forbid creation of sessions */
118                 new_uuid(sessions.initok);
119         else if (strlen(initok) < sizeof(sessions.store[0]->token))
120                 strcpy(sessions.initok, initok);
121         else {
122                 ERROR("initial token '%s' too long (max length 36)", initok);
123                 exit(1);
124         }
125 }
126
127 static struct afb_session *search (const char* uuid)
128 {
129         int  idx;
130         struct afb_session *session;
131
132         assert (uuid != NULL);
133
134         pthread_mutex_lock(&sessions.mutex);
135
136         for (idx=0; idx < sessions.max; idx++) {
137                 session = sessions.store[idx];
138                 if (session && (0 == strcmp (uuid, session->uuid)))
139                         goto found;
140         }
141         session = NULL;
142
143 found:
144         pthread_mutex_unlock(&sessions.mutex);
145         return session;
146 }
147
148 static int destroy (struct afb_session *session)
149 {
150         int idx;
151         int status;
152
153         assert (session != NULL);
154
155         pthread_mutex_lock(&sessions.mutex);
156
157         for (idx=0; idx < sessions.max; idx++) {
158                 if (sessions.store[idx] == session) {
159                         sessions.store[idx] = NULL;
160                         sessions.count--;
161                         status = 1;
162                         goto deleted;
163                 }
164         }
165         status = 0;
166 deleted:
167         pthread_mutex_unlock(&sessions.mutex);
168         return status;
169 }
170
171 static int add (struct afb_session *session)
172 {
173         int idx;
174         int status;
175
176         assert (session != NULL);
177
178         pthread_mutex_lock(&sessions.mutex);
179
180         for (idx=0; idx < sessions.max; idx++) {
181                 if (NULL == sessions.store[idx]) {
182                         sessions.store[idx] = session;
183                         sessions.count++;
184                         status = 1;
185                         goto added;
186                 }
187         }
188         status = 0;
189 added:
190         pthread_mutex_unlock(&sessions.mutex);
191         return status;
192 }
193
194 // Check if context timeout or not
195 static int is_expired (struct afb_session *ctx, time_t now)
196 {
197         assert (ctx != NULL);
198         return ctx->expiration < now;
199 }
200
201 // Check if context is active or not
202 static int is_active (struct afb_session *ctx, time_t now)
203 {
204         assert (ctx != NULL);
205         return ctx->uuid[0] != 0 && ctx->expiration >= now;
206 }
207
208 // Loop on every entry and remove old context sessions.hash
209 static void cleanup (time_t now)
210 {
211         struct afb_session *ctx;
212         long idx;
213
214         // Loop on Sessions Table and remove anything that is older than timeout
215         for (idx=0; idx < sessions.max; idx++) {
216                 ctx = sessions.store[idx];
217                 if (ctx != NULL && is_expired(ctx, now)) {
218                         afb_session_close (ctx);
219                 }
220         }
221 }
222
223 static struct afb_session *make_session (const char *uuid, int timeout, time_t now)
224 {
225         struct afb_session *session;
226
227         /* allocates a new one */
228         session = calloc(1, sizeof(struct afb_session));
229         if (session == NULL) {
230                 errno = ENOMEM;
231                 goto error;
232         }
233
234         /* generate the uuid */
235         if (uuid == NULL) {
236                 new_uuid(session->uuid);
237         } else {
238                 if (strlen(uuid) >= sizeof session->uuid) {
239                         errno = EINVAL;
240                         goto error2;
241                 }
242                 strcpy(session->uuid, uuid);
243         }
244
245         /* init the token */
246         strcpy(session->token, sessions.initok);
247         session->timeout = timeout;
248         if (timeout != 0)
249                 session->expiration = now + timeout;
250         else {
251                 session->expiration = (time_t)(~(time_t)0);
252                 if (session->expiration < 0)
253                         session->expiration = (time_t)(((unsigned long long)session->expiration) >> 1);
254         }
255         if (!add (session)) {
256                 errno = ENOMEM;
257                 goto error2;
258         }
259
260         session->access = now;
261         session->refcount = 1;
262         return session;
263
264 error2:
265         free(session);
266 error:
267         return NULL;
268 }
269
270 struct afb_session *afb_session_create (const char *uuid, int timeout)
271 {
272         time_t now;
273
274         /* cleaning */
275         now = NOW;
276         cleanup (now);
277
278         /* search for an existing one not too old */
279         if (uuid != NULL && search(uuid) != NULL) {
280                 errno = EEXIST;
281                 return NULL;
282         }
283
284         return make_session(uuid, timeout, now);
285 }
286
287 // This function will return exiting session or newly created session
288 struct afb_session *afb_session_get (const char *uuid, int *created)
289 {
290         struct afb_session *session;
291         time_t now;
292
293         /* cleaning */
294         now = NOW;
295         cleanup (now);
296
297         /* search for an existing one not too old */
298         if (uuid != NULL) {
299                 session = search(uuid);
300                 if (session != NULL) {
301                         *created = 0;
302                         session->access = now;
303                         session->refcount++;
304                         return session;
305                 }
306         }
307
308         *created = 1;
309         return make_session(uuid, sessions.timeout, now);
310 }
311
312 struct afb_session *afb_session_addref(struct afb_session *session)
313 {
314         if (session != NULL)
315                 session->refcount++;
316         return session;
317 }
318
319 void afb_session_unref(struct afb_session *session)
320 {
321         if (session != NULL) {
322                 assert(session->refcount != 0);
323                 --session->refcount;
324                 if (session->refcount == 0 && session->uuid[0] == 0) {
325                         destroy (session);
326                         free(session);
327                 }
328         }
329 }
330
331 // Free Client Session Context
332 void afb_session_close (struct afb_session *session)
333 {
334         assert(session != NULL);
335         if (session->uuid[0] != 0) {
336                 session->uuid[0] = 0;
337                 free_data (session);
338                 if (session->refcount == 0) {
339                         destroy (session);
340                         free(session);
341                 }
342         }
343 }
344
345 // Sample Generic Ping Debug API
346 int afb_session_check_token (struct afb_session *session, const char *token)
347 {
348         assert(session != NULL);
349         assert(token != NULL);
350
351         // compare current token with previous one
352         if (!is_active (session, NOW))
353                 return 0;
354
355         if (session->token[0] && strcmp (token, session->token) != 0)
356                 return 0;
357
358         return 1;
359 }
360
361 // generate a new token and update client context
362 void afb_session_new_token (struct afb_session *session)
363 {
364         assert(session != NULL);
365
366         // Old token was valid let's regenerate a new one
367         new_uuid(session->token);
368
369         // keep track of time for session timeout and further clean up
370         if (session->timeout != 0)
371                 session->expiration = NOW + session->timeout;
372 }
373
374 const char *afb_session_uuid (struct afb_session *session)
375 {
376         assert(session != NULL);
377         return session->uuid;
378 }
379
380 const char *afb_session_token (struct afb_session *session)
381 {
382         assert(session != NULL);
383         return session->token;
384 }
385
386 unsigned afb_session_get_LOA (struct afb_session *session)
387 {
388         assert(session != NULL);
389         return session->loa;
390 }
391
392 void afb_session_set_LOA (struct afb_session *session, unsigned loa)
393 {
394         assert(session != NULL);
395         session->loa = loa;
396 }
397
398 void *afb_session_get_cookie(struct afb_session *session, const void *key)
399 {
400         struct cookie *cookie;
401         int idx;
402
403         idx = cookeyidx(key);
404         cookie = session->cookies[idx];
405         while(cookie != NULL) {
406                 if (cookie->key == key)
407                         return cookie->value;
408                 cookie = cookie->next;
409         }
410         return NULL;
411 }
412
413 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
414 {
415         struct cookie *cookie;
416         int idx;
417
418         /* search for a replacement */
419         idx = cookeyidx(key);
420         cookie = session->cookies[idx];
421         while(cookie != NULL) {
422                 if (cookie->key == key) {
423                         if (cookie->value != value && cookie->freecb)
424                                 cookie->freecb(cookie->value);
425                         cookie->value = value;
426                         cookie->freecb = freecb;
427                         return 0;
428                 }
429                 cookie = cookie->next;
430         }
431
432         /* allocates */
433         cookie = malloc(sizeof *cookie);
434         if (cookie == NULL) {
435                 errno = ENOMEM;
436                 return -1;
437         }
438
439         cookie->key = key;
440         cookie->value = value;
441         cookie->freecb = freecb;
442         cookie->next = session->cookies[idx];
443         session->cookies[idx] = cookie;
444         return 0;
445 }
446