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