diff options
Diffstat (limited to 'src/afb-session.c')
-rw-r--r-- | src/afb-session.c | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/src/afb-session.c b/src/afb-session.c new file mode 100644 index 00000000..db81457c --- /dev/null +++ b/src/afb-session.c @@ -0,0 +1,461 @@ +/* + * Copyright (C) 2015, 2016, 2017 "IoT.bzh" + * Author "Fulup Ar Foll" + * Author: José Bollo <jose.bollo@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <time.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <uuid/uuid.h> +#include <assert.h> +#include <errno.h> + +#include <json-c/json.h> + +#include "afb-session.h" +#include "verbose.h" + +#define NOW (time(NULL)) + +struct value +{ + void *value; + void (*freecb)(void*); +}; + +struct cookie +{ + struct cookie *next; + const void *key; + void *value; + void (*freecb)(void*); +}; + +struct afb_session +{ + unsigned refcount; + unsigned loa; + int timeout; + time_t expiration; // expiration time of the token + time_t access; + char uuid[37]; // long term authentication of remote client + char token[37]; // short term authentication of remote client + struct value *values; + struct cookie *cookies; +}; + +// Session UUID are store in a simple array [for 10 sessions this should be enough] +static struct { + pthread_mutex_t mutex; // declare a mutex to protect hash table + struct afb_session **store; // sessions store + int count; // current number of sessions + int max; + int timeout; + int apicount; + char initok[37]; +} sessions; + +/* generate a uuid */ +static void new_uuid(char uuid[37]) +{ + uuid_t newuuid; + uuid_generate(newuuid); + uuid_unparse_lower(newuuid, uuid); +} + +// Free context [XXXX Should be protected again memory abort XXXX] +static void free_data (struct afb_session *session) +{ + int idx; + struct cookie *cookie; + + // If application add a handle let's free it now + assert (session->values != NULL); + + // Free session handle with a standard Free function, with app callback or ignore it + for (idx=0; idx < sessions.apicount; idx ++) + afb_session_set_value(session, idx, NULL, NULL); + + // free cookies + cookie = session->cookies; + while (cookie != NULL) { + session->cookies = cookie->next; + if (cookie->value != NULL && cookie->freecb != NULL) + cookie->freecb(cookie->value); + free(cookie); + cookie = session->cookies; + } +} + +// Create a new store in RAM, not that is too small it will be automatically extended +void afb_session_init (int max_session_count, int timeout, const char *initok, int context_count) +{ + // let's create as store as hashtable does not have any + sessions.store = calloc (1 + (unsigned)max_session_count, sizeof(struct afb_session)); + sessions.max = max_session_count; + sessions.timeout = timeout; + sessions.apicount = context_count; + if (initok == NULL) + /* without token, a secret is made to forbid creation of sessions */ + new_uuid(sessions.initok); + else if (strlen(initok) < sizeof(sessions.store[0]->token)) + strcpy(sessions.initok, initok); + else { + ERROR("initial token '%s' too long (max length 36)", initok); + exit(1); + } +} + +static struct afb_session *search (const char* uuid) +{ + int idx; + struct afb_session *session; + + assert (uuid != NULL); + + pthread_mutex_lock(&sessions.mutex); + + for (idx=0; idx < sessions.max; idx++) { + session = sessions.store[idx]; + if (session && (0 == strcmp (uuid, session->uuid))) + goto found; + } + session = NULL; + +found: + pthread_mutex_unlock(&sessions.mutex); + return session; +} + +static int destroy (struct afb_session *session) +{ + int idx; + int status; + + assert (session != NULL); + + pthread_mutex_lock(&sessions.mutex); + + for (idx=0; idx < sessions.max; idx++) { + if (sessions.store[idx] == session) { + sessions.store[idx] = NULL; + sessions.count--; + status = 1; + goto deleted; + } + } + status = 0; +deleted: + pthread_mutex_unlock(&sessions.mutex); + return status; +} + +static int add (struct afb_session *session) +{ + int idx; + int status; + + assert (session != NULL); + + pthread_mutex_lock(&sessions.mutex); + + for (idx=0; idx < sessions.max; idx++) { + if (NULL == sessions.store[idx]) { + sessions.store[idx] = session; + sessions.count++; + status = 1; + goto added; + } + } + status = 0; +added: + pthread_mutex_unlock(&sessions.mutex); + return status; +} + +// Check if context timeout or not +static int is_expired (struct afb_session *ctx, time_t now) +{ + assert (ctx != NULL); + return ctx->expiration < now; +} + +// Check if context is active or not +static int is_active (struct afb_session *ctx, time_t now) +{ + assert (ctx != NULL); + return ctx->uuid[0] != 0 && ctx->expiration >= now; +} + +// Loop on every entry and remove old context sessions.hash +static void cleanup (time_t now) +{ + struct afb_session *ctx; + long idx; + + // Loop on Sessions Table and remove anything that is older than timeout + for (idx=0; idx < sessions.max; idx++) { + ctx = sessions.store[idx]; + if (ctx != NULL && is_expired(ctx, now)) { + afb_session_close (ctx); + } + } +} + +static struct afb_session *make_session (const char *uuid, int timeout, time_t now) +{ + struct afb_session *session; + + /* allocates a new one */ + session = calloc(1, sizeof(struct afb_session) + ((unsigned)sessions.apicount * sizeof(*session->values))); + if (session == NULL) { + errno = ENOMEM; + goto error; + } + session->values = (void*)(session + 1); + + /* generate the uuid */ + if (uuid == NULL) { + new_uuid(session->uuid); + } else { + if (strlen(uuid) >= sizeof session->uuid) { + errno = EINVAL; + goto error2; + } + strcpy(session->uuid, uuid); + } + + /* init the token */ + strcpy(session->token, sessions.initok); + session->timeout = timeout; + if (timeout != 0) + session->expiration = now + timeout; + else { + session->expiration = (time_t)(~(time_t)0); + if (session->expiration < 0) + session->expiration = (time_t)(((unsigned long long)session->expiration) >> 1); + } + if (!add (session)) { + errno = ENOMEM; + goto error2; + } + + session->access = now; + session->refcount = 1; + return session; + +error2: + free(session); +error: + return NULL; +} + +struct afb_session *afb_session_create (const char *uuid, int timeout) +{ + time_t now; + + /* cleaning */ + now = NOW; + cleanup (now); + + /* search for an existing one not too old */ + if (uuid != NULL && search(uuid) != NULL) { + errno = EEXIST; + return NULL; + } + + return make_session(uuid, timeout, now); +} + +// This function will return exiting session or newly created session +struct afb_session *afb_session_get (const char *uuid, int *created) +{ + struct afb_session *session; + time_t now; + + /* cleaning */ + now = NOW; + cleanup (now); + + /* search for an existing one not too old */ + if (uuid != NULL) { + session = search(uuid); + if (session != NULL) { + *created = 0; + session->access = now; + session->refcount++; + return session; + } + } + + *created = 1; + return make_session(uuid, sessions.timeout, now); +} + +struct afb_session *afb_session_addref(struct afb_session *session) +{ + if (session != NULL) + session->refcount++; + return session; +} + +void afb_session_unref(struct afb_session *session) +{ + if (session != NULL) { + assert(session->refcount != 0); + --session->refcount; + if (session->refcount == 0 && session->uuid[0] == 0) { + destroy (session); + free(session); + } + } +} + +// Free Client Session Context +void afb_session_close (struct afb_session *session) +{ + assert(session != NULL); + if (session->uuid[0] != 0) { + session->uuid[0] = 0; + free_data (session); + if (session->refcount == 0) { + destroy (session); + free(session); + } + } +} + +// Sample Generic Ping Debug API +int afb_session_check_token (struct afb_session *session, const char *token) +{ + assert(session != NULL); + assert(token != NULL); + + // compare current token with previous one + if (!is_active (session, NOW)) + return 0; + + if (session->token[0] && strcmp (token, session->token) != 0) + return 0; + + return 1; +} + +// generate a new token and update client context +void afb_session_new_token (struct afb_session *session) +{ + assert(session != NULL); + + // Old token was valid let's regenerate a new one + new_uuid(session->token); + + // keep track of time for session timeout and further clean up + if (session->timeout != 0) + session->expiration = NOW + session->timeout; +} + +const char *afb_session_uuid (struct afb_session *session) +{ + assert(session != NULL); + return session->uuid; +} + +const char *afb_session_token (struct afb_session *session) +{ + assert(session != NULL); + return session->token; +} + +unsigned afb_session_get_LOA (struct afb_session *session) +{ + assert(session != NULL); + return session->loa; +} + +void afb_session_set_LOA (struct afb_session *session, unsigned loa) +{ + assert(session != NULL); + session->loa = loa; +} + +void *afb_session_get_value(struct afb_session *session, int index) +{ + assert(session != NULL); + assert(index >= 0); + assert(index < sessions.apicount); + return session->values[index].value; +} + +void afb_session_set_value(struct afb_session *session, int index, void *value, void (*freecb)(void*)) +{ + struct value prev; + assert(session != NULL); + assert(index >= 0); + assert(index < sessions.apicount); + prev = session->values[index]; + session->values[index] = (struct value){.value = value, .freecb = freecb}; + if (prev.value != NULL && prev.value != value && prev.freecb != NULL) + prev.freecb(prev.value); +} + +void *afb_session_get_cookie(struct afb_session *session, const void *key) +{ + struct cookie *cookie; + + cookie = session->cookies; + while(cookie != NULL) { + if (cookie->key == key) + return cookie->value; + cookie = cookie->next; + } + return NULL; +} + +int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*)) +{ + struct cookie *cookie; + + /* search for a replacement */ + cookie = session->cookies; + while(cookie != NULL) { + if (cookie->key == key) { + if (cookie->value != NULL && cookie->value != value && cookie->freecb != NULL) + cookie->freecb(cookie->value); + cookie->value = value; + cookie->freecb = freecb; + return 0; + } + cookie = cookie->next; + } + + /* allocates */ + cookie = malloc(sizeof *cookie); + if (cookie == NULL) { + errno = ENOMEM; + return -1; + } + + cookie->key = key; + cookie->value = value; + cookie->freecb = freecb; + cookie->next = session->cookies; + session->cookies = cookie; + return 0; +} + |