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