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