Removes uses of readdir_r
[src/app-framework-binder.git] / src / session.c
1 /*
2  * Copyright (C) 2015, 2016, 2017 "IoT.bzh"
3  * Author "Fulup Ar Foll"
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #define _GNU_SOURCE
19 #include <stdio.h>
20 #include <time.h>
21 #include <pthread.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <uuid/uuid.h>
25 #include <assert.h>
26 #include <errno.h>
27
28 #include <json-c/json.h>
29
30 #include "session.h"
31 #include "verbose.h"
32
33 #define NOW (time(NULL))
34
35 struct client_value
36 {
37         void *value;
38         void (*free_value)(void*);
39 };
40
41 struct cookie
42 {
43         struct cookie *next;
44         const void *key;
45         void *value;
46         void (*free_value)(void*);
47 };
48
49 struct AFB_clientCtx
50 {
51         unsigned refcount;
52         unsigned loa;
53         int timeout;
54         time_t expiration;    // expiration time of the token
55         time_t access;
56         char uuid[37];        // long term authentication of remote client
57         char token[37];       // short term authentication of remote client
58         struct client_value *values;
59         struct cookie *cookies;
60 };
61
62 // Session UUID are store in a simple array [for 10 sessions this should be enough]
63 static struct {
64   pthread_mutex_t mutex;          // declare a mutex to protect hash table
65   struct AFB_clientCtx **store;          // sessions store
66   int count;                      // current number of sessions
67   int max;
68   int timeout;
69   int apicount;
70   char initok[37];
71 } sessions;
72
73 /* generate a uuid */
74 static void new_uuid(char uuid[37])
75 {
76         uuid_t newuuid;
77         uuid_generate(newuuid);
78         uuid_unparse_lower(newuuid, uuid);
79 }
80
81 // Free context [XXXX Should be protected again memory abort XXXX]
82 static void ctxUuidFreeCB (struct AFB_clientCtx *client)
83 {
84         int idx;
85         struct cookie *cookie;
86
87         // If application add a handle let's free it now
88         assert (client->values != NULL);
89
90         // Free client handle with a standard Free function, with app callback or ignore it
91         for (idx=0; idx < sessions.apicount; idx ++)
92                 ctxClientValueSet(client, idx, NULL, NULL);
93
94         // free cookies
95         cookie = client->cookies;
96         while (cookie != NULL) {
97                 client->cookies = cookie->next;
98                 if (cookie->value != NULL && cookie->free_value != NULL)
99                         cookie->free_value(cookie->value);
100                 free(cookie);
101                 cookie = client->cookies;
102         }
103 }
104
105 // Create a new store in RAM, not that is too small it will be automatically extended
106 void ctxStoreInit (int max_session_count, int timeout, const char *initok, int context_count)
107 {
108         // let's create as store as hashtable does not have any
109         sessions.store = calloc (1 + (unsigned)max_session_count, sizeof(struct AFB_clientCtx));
110         sessions.max = max_session_count;
111         sessions.timeout = timeout;
112         sessions.apicount = context_count;
113         if (initok == NULL)
114                 /* without token, a secret is made to forbid creation of sessions */
115                 new_uuid(sessions.initok);
116         else if (strlen(initok) < sizeof(sessions.store[0]->token))
117                 strcpy(sessions.initok, initok);
118         else {
119                 ERROR("initial token '%s' too long (max length 36)", initok);
120                 exit(1);
121         }
122 }
123
124 static struct AFB_clientCtx *ctxStoreSearch (const char* uuid)
125 {
126     int  idx;
127     struct AFB_clientCtx *client;
128
129     assert (uuid != NULL);
130
131     pthread_mutex_lock(&sessions.mutex);
132
133     for (idx=0; idx < sessions.max; idx++) {
134         client = sessions.store[idx];
135         if (client && (0 == strcmp (uuid, client->uuid)))
136                 goto found;
137     }
138     client = NULL;
139
140 found:
141     pthread_mutex_unlock(&sessions.mutex);
142     return client;
143 }
144
145 static int ctxStoreDel (struct AFB_clientCtx *client)
146 {
147     int idx;
148     int status;
149
150     assert (client != NULL);
151
152     pthread_mutex_lock(&sessions.mutex);
153
154     for (idx=0; idx < sessions.max; idx++) {
155         if (sessions.store[idx] == client) {
156                 sessions.store[idx] = NULL;
157                 sessions.count--;
158                 status = 1;
159                 goto deleted;
160         }
161     }
162     status = 0;
163 deleted:
164     pthread_mutex_unlock(&sessions.mutex);
165     return status;
166 }
167
168 static int ctxStoreAdd (struct AFB_clientCtx *client)
169 {
170     int idx;
171     int status;
172
173     assert (client != NULL);
174
175     pthread_mutex_lock(&sessions.mutex);
176
177     for (idx=0; idx < sessions.max; idx++) {
178         if (NULL == sessions.store[idx]) {
179                 sessions.store[idx] = client;
180                 sessions.count++;
181                 status = 1;
182                 goto added;
183         }
184     }
185     status = 0;
186 added:
187     pthread_mutex_unlock(&sessions.mutex);
188     return status;
189 }
190
191 // Check if context timeout or not
192 static int ctxStoreTooOld (struct AFB_clientCtx *ctx, time_t now)
193 {
194     assert (ctx != NULL);
195     return ctx->expiration < now;
196 }
197
198 // Check if context is active or not
199 static int ctxIsActive (struct AFB_clientCtx *ctx, time_t now)
200 {
201     assert (ctx != NULL);
202     return ctx->uuid[0] != 0 && ctx->expiration >= now;
203 }
204
205 // Loop on every entry and remove old context sessions.hash
206 static void ctxStoreCleanUp (time_t now)
207 {
208         struct AFB_clientCtx *ctx;
209         long idx;
210
211         // Loop on Sessions Table and remove anything that is older than timeout
212         for (idx=0; idx < sessions.max; idx++) {
213                 ctx = sessions.store[idx];
214                 if (ctx != NULL && ctxStoreTooOld(ctx, now)) {
215                         ctxClientClose (ctx);
216                 }
217         }
218 }
219
220 static struct AFB_clientCtx *new_context (const char *uuid, int timeout, time_t now)
221 {
222         struct AFB_clientCtx *clientCtx;
223
224         /* allocates a new one */
225         clientCtx = calloc(1, sizeof(struct AFB_clientCtx) + ((unsigned)sessions.apicount * sizeof(*clientCtx->values)));
226         if (clientCtx == NULL) {
227                 errno = ENOMEM;
228                 goto error;
229         }
230         clientCtx->values = (void*)(clientCtx + 1);
231
232         /* generate the uuid */
233         if (uuid == NULL) {
234                 new_uuid(clientCtx->uuid);
235         } else {
236                 if (strlen(uuid) >= sizeof clientCtx->uuid) {
237                         errno = EINVAL;
238                         goto error2;
239                 }
240                 strcpy(clientCtx->uuid, uuid);
241         }
242
243         /* init the token */
244         strcpy(clientCtx->token, sessions.initok);
245         clientCtx->timeout = timeout;
246         if (timeout != 0)
247                 clientCtx->expiration = now + timeout;
248         else {
249                 clientCtx->expiration = (time_t)(~(time_t)0);
250                 if (clientCtx->expiration < 0)
251                         clientCtx->expiration = (time_t)(((unsigned long long)clientCtx->expiration) >> 1);
252         }
253         if (!ctxStoreAdd (clientCtx)) {
254                 errno = ENOMEM;
255                 goto error2;
256         }
257
258         clientCtx->access = now;
259         clientCtx->refcount = 1;
260         return clientCtx;
261
262 error2:
263         free(clientCtx);
264 error:
265         return NULL;
266 }
267
268 struct AFB_clientCtx *ctxClientCreate (const char *uuid, int timeout)
269 {
270         time_t now;
271
272         /* cleaning */
273         now = NOW;
274         ctxStoreCleanUp (now);
275
276         /* search for an existing one not too old */
277         if (uuid != NULL && ctxStoreSearch(uuid) != NULL) {
278                 errno = EEXIST;
279                 return NULL;
280         }
281
282         return new_context(uuid, timeout, now);
283 }
284
285 // This function will return exiting client context or newly created client context
286 struct AFB_clientCtx *ctxClientGetSession (const char *uuid, int *created)
287 {
288         struct AFB_clientCtx *clientCtx;
289         time_t now;
290
291         /* cleaning */
292         now = NOW;
293         ctxStoreCleanUp (now);
294
295         /* search for an existing one not too old */
296         if (uuid != NULL) {
297                 clientCtx = ctxStoreSearch(uuid);
298                 if (clientCtx != NULL) {
299                         *created = 0;
300                         clientCtx->access = now;
301                         clientCtx->refcount++;
302                         return clientCtx;
303                 }
304         }
305
306         *created = 1;
307         return new_context(uuid, sessions.timeout, now);
308 }
309
310 struct AFB_clientCtx *ctxClientAddRef(struct AFB_clientCtx *clientCtx)
311 {
312         if (clientCtx != NULL)
313                 clientCtx->refcount++;
314         return clientCtx;
315 }
316
317 void ctxClientUnref(struct AFB_clientCtx *clientCtx)
318 {
319         if (clientCtx != NULL) {
320                 assert(clientCtx->refcount != 0);
321                 --clientCtx->refcount;
322                 if (clientCtx->refcount == 0 && clientCtx->uuid[0] == 0) {
323                         ctxStoreDel (clientCtx);
324                         free(clientCtx);
325                 }
326         }
327 }
328
329 // Free Client Session Context
330 void ctxClientClose (struct AFB_clientCtx *clientCtx)
331 {
332         assert(clientCtx != NULL);
333         if (clientCtx->uuid[0] != 0) {
334                 clientCtx->uuid[0] = 0;
335                 ctxUuidFreeCB (clientCtx);
336                 if (clientCtx->refcount == 0) {
337                         ctxStoreDel (clientCtx);
338                         free(clientCtx);
339                 }
340         }
341 }
342
343 // Sample Generic Ping Debug API
344 int ctxTokenCheck (struct AFB_clientCtx *clientCtx, const char *token)
345 {
346         assert(clientCtx != NULL);
347         assert(token != NULL);
348
349         // compare current token with previous one
350         if (!ctxIsActive (clientCtx, NOW))
351                 return 0;
352
353         if (clientCtx->token[0] && strcmp (token, clientCtx->token) != 0)
354                 return 0;
355
356         return 1;
357 }
358
359 // generate a new token and update client context
360 void ctxTokenNew (struct AFB_clientCtx *clientCtx)
361 {
362         assert(clientCtx != NULL);
363
364         // Old token was valid let's regenerate a new one
365         new_uuid(clientCtx->token);
366
367         // keep track of time for session timeout and further clean up
368         if (clientCtx->timeout != 0)
369                 clientCtx->expiration = NOW + clientCtx->timeout;
370 }
371
372 const char *ctxClientGetUuid (struct AFB_clientCtx *clientCtx)
373 {
374         assert(clientCtx != NULL);
375         return clientCtx->uuid;
376 }
377
378 const char *ctxClientGetToken (struct AFB_clientCtx *clientCtx)
379 {
380         assert(clientCtx != NULL);
381         return clientCtx->token;
382 }
383
384 unsigned ctxClientGetLOA (struct AFB_clientCtx *clientCtx)
385 {
386         assert(clientCtx != NULL);
387         return clientCtx->loa;
388 }
389
390 void ctxClientSetLOA (struct AFB_clientCtx *clientCtx, unsigned loa)
391 {
392         assert(clientCtx != NULL);
393         clientCtx->loa = loa;
394 }
395
396 void *ctxClientValueGet(struct AFB_clientCtx *clientCtx, int index)
397 {
398         assert(clientCtx != NULL);
399         assert(index >= 0);
400         assert(index < sessions.apicount);
401         return clientCtx->values[index].value;
402 }
403
404 void ctxClientValueSet(struct AFB_clientCtx *clientCtx, int index, void *value, void (*free_value)(void*))
405 {
406         struct client_value prev;
407         assert(clientCtx != NULL);
408         assert(index >= 0);
409         assert(index < sessions.apicount);
410         prev = clientCtx->values[index];
411         clientCtx->values[index] = (struct client_value){.value = value, .free_value = free_value};
412         if (prev.value != NULL && prev.value != value && prev.free_value != NULL)
413                 prev.free_value(prev.value);
414 }
415
416 void *ctxClientCookieGet(struct AFB_clientCtx *clientCtx, const void *key)
417 {
418         struct cookie *cookie;
419
420         cookie = clientCtx->cookies;
421         while(cookie != NULL) {
422                 if (cookie->key == key)
423                         return cookie->value;
424                 cookie = cookie->next;
425         }
426         return NULL;
427 }
428
429 int ctxClientCookieSet(struct AFB_clientCtx *clientCtx, const void *key, void *value, void (*free_value)(void*))
430 {
431         struct cookie *cookie;
432
433         /* search for a replacement */
434         cookie = clientCtx->cookies;
435         while(cookie != NULL) {
436                 if (cookie->key == key) {
437                         if (cookie->value != NULL && cookie->value != value && cookie->free_value != NULL)
438                                 cookie->free_value(cookie->value);
439                         cookie->value = value;
440                         cookie->free_value = free_value;
441                         return 0;
442                 }
443                 cookie = cookie->next;
444         }
445
446         /* allocates */
447         cookie = malloc(sizeof *cookie);
448         if (cookie == NULL) {
449                 errno = ENOMEM;
450                 return -1;
451         }
452
453         cookie->key = key;
454         cookie->value = value;
455         cookie->free_value = free_value;
456         cookie->next = clientCtx->cookies;
457         clientCtx->cookies = cookie;
458         return 0;
459 }
460