/* sessions.c Sessions plugin for thread-safe server-side HTTP session management. by Chris Moutsos & Robert van Engelen gSOAP XML Web services tools Copyright (C) 2000-2016, Robert van Engelen, Genivia Inc., All Rights Reserved. This part of the software is released under one of the following licenses: GPL or the gSOAP public license. -------------------------------------------------------------------------------- gSOAP public license. The contents of this file are subject to the gSOAP Public License Version 1.3 (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.cs.fsu.edu/~engelen/soaplicense.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Initial Developer of the Original Code is Robert A. van Engelen. Copyright (C) 2000-2016, Robert van Engelen, Genivia Inc., All Rights Reserved. -------------------------------------------------------------------------------- GPL license. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Author contact information: engelen@genivia.com / engelen@acm.org This program is released under the GPL with the additional exemption that compiling, linking, and/or using OpenSSL is allowed. -------------------------------------------------------------------------------- A commercial use license is available from Genivia, Inc., contact@genivia.com -------------------------------------------------------------------------------- */ /** @mainpage The Sessions Plugin @section title HTTP Server Session Management Plugin [TOC] By Chris Moutsos and Robert van Engelen. @section overview Overview The HTTP server session management plugin for gSOAP enables servers to store a database of sessions to keep track of client data across multiple requests. Client data is stored per session as name and value pairs that can be set to any valid string. Sessions are tracked with unique session IDs (sometimes called tokens) that are passed between the server and client with HTTP cookies. The sessions plugin is MT safe. @section features Features - Stores sessions identified by random 128-bit session IDs managed with HTTP cookies - Session validity can be restricted based on domain and path - Each session stores a list of session variables (name/value string pairs) - Can easily create, modify, delete, and retrieve session variables - Configurable settings - Rolling sessions (re-set session ID cookie with every response) - Session ID cookie `Max-Age` and `Expire` attribute values - Maximum stale time before a session expires server-side - Time interval between purging expired sessions - Max number of sessions allowed in database - Thread-safe - Can be used with C or C++ projects @section setup Setup To set up the sessions plugin on the server-side: -# Make sure to compile your program with plugin/sessions.c and plugin/threads.c. -# Enable cookies by compiling all gSOAP source code with `-DWITH_COOKIES`. Cookies are required for tracking session IDs. -# Enable HTTPS (compile with `-DWITH_OPENSSL` or `-DWITH_GNUTLS` or link with `-lgsoapssl`/`-lgsoapssl++`). @note While it is not a requirement to use HTTPS with this plugin, it is **strongly recommended** to use HTTPS. This is because the session ID cookies can be intercepted if not encrypted with SSL/TLS. Cookies are automatically sent with the Secure flag enabled when SSL is enabled. The HttpOnly flag is automatically enabled for all cookies in order to prevent XSS attacks. @section usage Basic Usage Client-side usage is automatic, provided that the client supports cookies and the server has properly configured the plugin. If the client is a gSOAP application, make sure to compile with `-DWITH_COOKIES`. For server-side usage, register the plugin with: @code #include "plugin/sessions.h" struct soap *soap = soap_new(); soap_register_plugin(soap, sessions); @endcode If you are using a server object in C++ generated with soappcpp2 option `-j`: @code #include "plugin/sessions.h" soap_register_plugin(server.soap, sessions); @endcode For brevity, the `struct soap*` version will be used below. Replace `soap` with your server object's `soap` member if necessary. Start a new session with: @code struct soap_session *session = soap_start_session(soap, "domain", "path"); @endcode This creates a new session with a random 128-bit session ID and the default settings (see section: @ref settings). If the client already has a session ID cookie with name `SESSION_COOKIE_NAME` and the given `domain` and `path`, and that ID refers to a valid session, that existing session will be returned instead. @note For all of the sessions plugin function calls, `domain` and `path` arguments are required. These must match the values that were given when the session was created, otherwise the client's session ID cookie won't be found. The `domain` and `path` arguments may be set to `NULL` to use the current values given by the `soap` context's `cookie_domain` and `cookie_path` members: @note | Attribute | Value | --------------------------- | ------------------------------------------------ | `const char *cookie_domain` | MUST be set to the domain (host) of the service | `const char *cookie_path` | MAY be set to the default path to the service (defaults to '`/`') @note These rules also apply to the cookies used for tracking session IDs. See the [gSOAP User Guide](http://genivia.com/doc/soapdoc2.html) for more information on how gSOAP handles cookies. For example, to create a session on localhost:8080 that is valid on any path: @code soap->cookie_domain = "localhost:8080"; struct soap_session *session = soap_start_session(soap, NULL, NULL); @endcode To create a new session variable or modify an existing session variable inside that session: @code soap_set_session_var(soap, "session_var_name", "session_var_value", NULL, NULL); @endcode To access the value of that session variable: @code const char *value = soap_get_session_var(soap, "session_var_name", NULL, NULL); @endcode To delete that session variable: @code soap_clr_session_var(soap, "session_var_name", NULL, NULL); @endcode Consider changing the session's `rolling`, `cookie_maxage`, `max_inactive_secs` settings from their defaults. Read more about these in the @ref settings section. @section example Simple Calculator Server (C) Example We will show how to extend the simple calculator server example (in C) to keep track of the client's last calculated value with a session variable. Make sure to register the plugin and set the default `domain` and `path`. After calling `soap_destroy` and `soap_end`, we should also free the internal cookie database (or call `soap_done`): @code #include "plugin/session.h" ... struct soap soap; soap_init(&soap); soap_register_plugin(&soap, sessions); soap.cookie_domain = "localhost:8080"; soap.cookie_path = "/"; ... // set up the service and serve requests soap_destroy(&soap); soap_end(&soap); soap_free_cookies(&soap); @endcode Now in each of our calculator service operations (`ns__add`, `ns__sub`, etc.), we can update the `lastCalculation` session variable in our current session: @code int ns__add(struct soap *soap, double a, double b, double *result) { (void)soap; *result = a + b; // make sure we have a session if (!soap_get_session(soap, NULL, NULL)) { // no current session, create a new one if (!soap_start_session(soap, NULL, NULL)) return SOAP_ERR; // session creation failed // configure session settings soap_set_session_rolling(soap, 1, NULL, NULL); soap_set_session_cookie_maxage(soap, 30, NULL, NULL); soap_set_session_max_inactive_secs(soap, 30, NULL, NULL); } // update `lastCalculation` session variable char calculation[80]; sprintf(calculation, "add %f %f, result = %f", a, b, *result); soap_set_session_var(soap, "lastCalculation", calculation, NULL, NULL); return SOAP_OK; } @endcode If we want to allow the client to access their last calculation, add another service operation to retrieve the `lastCalculation` session variable: @code int ns__getLastCalculation(struct soap *soap, char **r) { const char *lastCalculation = soap_get_session_var(soap, "lastCalculation", NULL, NULL); if (!lastCalculation) lastCalculation = "None"; if ((*r = (char*)soap_malloc(soap, strlen(lastCalculation)+1))) strcpy(*r, lastCalculation); return SOAP_OK; } @endcode @section api Sessions Plugin API This section describes the Sessions Plugin API. @note The term *session ID cookie* refers to a cookie with the name `SESSION_COOKIE_NAME` (see section: @ref settings), the given `domain` and `path` (see section: @ref usage), and a random 128-bit session ID as the value. The term *current session* refers to the session in the database identified by the client's session ID cookie. @note The arguments `domain` and `path` may be set to `NULL` to use the current values given by `soap->cookie_domain` and `soap->cookie_path` (see section: @ref usage). The sessions plugin provides the following API for server-side session management:
Session Plugin Function Description
struct soap_session* soap_start_session(struct soap * soap, const char *domain, const char *path);\n Create a session in the database and a session ID cookie. Behaves the same as `soap_get_session` if the client already has a valid session ID cookie. Returns pointer to the session node, or `NULL` if the session could not be created.
struct soap_session* soap_get_session(struct soap *soap, const char *domain, const char *path);\n Get the current session. Can be used as a 'touch'. Returns pointer to the session node, or `NULL` if the session is not found or is expired.
int soap_get_num_sessions(struct soap *soap, const char *domain, const char *path);\n Get the number of sessions in the database. Returns `-1` if the session is not found or is expired.
struct soap_session* soap_regenerate_session_id(struct soap * soap, const char *domain, const char *path);\n Create a new session ID for the current session and update the session ID cookie. Returns pointer to the session node, or `NULL` if the session is not found or is expired.
int soap_get_num_session_vars(struct soap *soap, const char *domain, const char *path);\n Get the number of session variables in the current session. Returns `-1` if the session is not found or is expired.
void soap_end_session(struct soap *soap, const char * domain, const char *path);\n Delete the current session from the database and clear the client's session ID cookie.
void soap_end_sessions(struct soap *soap, const char * domain, const char *path);\n Delete all sessions in the database and clear the current session ID cookie.
struct soap_session_var* soap_set_session_var(struct soap * soap, const char *name, const char *value, const char *domain, const char *path);\n Set a session variable with the given `name` and `value` in the current session, overwring any old `value`. Returns pointer to the session variable node, or `NULL` if the session is not found or is expired.
const char* soap_get_session_var(struct soap *soap, const char *name, const char *domain, const char *path); \n Get the `value` of the session variable with the given `name` in the current session. Returns `NULL` if the session is not found or is expired.
void soap_clr_session_var(struct soap *soap, const char *name, const char *domain, const char *path);\n Delete the session variable with the given `name` in the current session.
void soap_clr_session_vars(struct soap *soap, const char *domain, const char *path); \n Delete all session variables in the current session.
For managing session settings (see section: @ref settings), the following API is provided:
Session Plugin Function Description (Settings / Misc.)
int soap_set_session_rolling(struct soap * soap, int rolling, const char *domain, const char * path);\n Set the `rolling` setting for the current session. Zero (non-rolling) means the session ID cookie is only set when the session is first created. Non-zero (rolling) means the session ID cookie will be re-set on every response. If successful, returns SOAP_OK, or SOAP_ERR otherwise.
int soap_get_session_rolling(struct soap * soap, const char *domain, const char *path);\n Get the `rolling` setting for the current session. Returns `-1` if the session is not found or is expired.
int soap_set_session_cookie_maxage(struct soap * soap, long maxage, const char *domain, const char * path);\n Set the `cookie_maxage` member for the current session. This will be used to set the session ID cookie's `Max-Age` and `Expires` attributes. `-1` means the cookie will have no `Max-Age` or `Expires` attributes. If successful, returns SOAP_OK, or SOAP_ERR otherwise.
long soap_get_session_cookie_maxage(struct soap * soap, const char *domain, const char *path);\n Get the `cookie_maxage` member for the current session. Returns `0` if the session is not found or is expired.
int soap_set_session_max_inactive_secs(struct soap * soap, long max, const char *domain, const char * path);\n Set the `max_inactive_secs` member for the current session. This is the maximum amount of stale time in seconds before a session is marked as expired (to be purged) on the server-side. `-1` means the session will never expire. If successful, returns SOAP_OK, or SOAP_ERR otherwise.
long soap_get_session_max_inactive_secs(struct soap * soap, const char *domain, const char *path);\n Get the `max_inactive_secs` member for the current session. Returns `0` if the session is not found or is expired.
struct soap_session* soap_get_sessions_head(); \n Get `soap_sessions`, the first node in the sessions linked list. Not necessary unless low-level list management is needed (see section: @ref internals).
@section settings Settings In `plugins/session.h`, the following constants are defined: | Name | Default | Description | ------------------------------------- | --------------------- | -------------------------------------------------- | `SESSION_COOKIE_NAME` | `"GSOAP_SESSIONID"` | The name to use for session ID cookies. | `SESSION_MAX` | `8192` | The max number of sessions allowed in the database. Any attempt to create more sessions than this will fail. | `SESSIONS_DEFAULT_MAX_INACTIVE_SECS` | `300` | The default time in seconds for sessions' `max_inactive_secs` member. `-1` means the session will never expire server-side. | `SESSION_PURGE_INTERVAL` | `300` | On every session database access, the time since the last database purge is checked. If it's been longer than this interval (in seconds), any expired sessions will be purged with `soap_purge_sessions()`. `0` means a purge will occcur on every database access. `-1` means a purge will never occur. Set these values to whatever makes sense for your application. In each session node, the following settings are declared: | Name | Default | Description | ----------------------- | ------------------------------------ | ----------------------- | `rolling` | `0` | Non-rolling (`0`) means the session ID cookie is only set when the session is first created. Rolling (non-`0`) means the session ID cookie will be re-set on every response after any part of the session has been touched. | `cookie_maxage` | `-1` | This will be used to set the session ID cookie's `Max-Age` and `Expires` attributes. `-1` means the cookie will have no `Max-Age` or `Expires` attributes. | `max_inactive_secs` | `SESSIONS_DEFAULT_MAX_INACTIVE_SECS` | This is the maximum amount of stale time in seconds before a session is marked as expired (to be purged) on the server-side. `-1` means the session will never expire server-side. This should probably be set equal to `cookie_maxage` unless that value is `-1`. @note These settings should not be changed directly. See the @ref api section for functions to manage these settings. @section internals The Internals The session database is stored as a linked list pointed to by `soap_sessions` (accessible by calling `soap_get_sessions_head()`). Session nodes are of type `struct soap_session` and are declared as: @code struct soap_session { struct soap_session *next; const char *id; struct soap_session_var *session_vars; struct soap_session_var *session_vars_tail; int num_session_vars; // the total number of session_vars we have in this session long cookie_maxage; // client time in seconds before the cookie expires (-1 for session cookie) int rolling; // whether or not we should set the cookie's maxage on every response time_t last_touched; // time that we last touched this session or its vars long max_inactive_secs; // server time in seconds before inactive session expires (-1 to never expire) // this should usually be at least as high as cookie_maxage }; @endcode The `session_vars` member is a pointer to the session's linked list of session variables. Session variable nodes are of type `struct soap_session_var` and are declared as: @code struct soap_session_var { struct soap_session_var *next; const char *name; const char *value; }; @endcode */ #include "sessions.h" #ifdef __cplusplus extern "C" { #endif const char sessions_id[] = SESSIONS_ID; /** Head node of session database (linked list) */ static struct soap_session *soap_sessions = NULL; /** Tail node of session database */ static struct soap_session *soap_sessions_tail = NULL; /** Number of sessions in database */ static int num_sessions = 0; /** Time that we last purged expired sessions */ static time_t last_purged_sessions; /** Session database lock */ static MUTEX_TYPE sessions_lock = MUTEX_INITIALIZER; /******************************************************************************\ * * Forward decls * \******************************************************************************/ static int sessions_init(struct soap *soap); static void sessions_delete(struct soap *soap, struct soap_plugin *p); /******************************************************************************\ * * Plugin registry * \******************************************************************************/ /** @brief Plugin registry function, invoked by soap_register_plugin. */ SOAP_FMAC1 int SOAP_FMAC2 sessions(struct soap *soap, struct soap_plugin *p, void *arg) { (void)arg; p->id = sessions_id; /* register the destructor */ p->fdelete = sessions_delete; if (sessions_init(soap)) return SOAP_EOM; /* return error */ return SOAP_OK; } /******************************************************************************/ /** @brief Used by plugin registry function. */ static int sessions_init(struct soap *soap) { (void)soap; last_purged_sessions = time(NULL); return SOAP_OK; } /******************************************************************************\ * * Callbacks * \******************************************************************************/ /** @brief Deletes all sessions. Called when plugin is ended by first caller. */ static void sessions_delete(struct soap *soap, struct soap_plugin *p) { (void)p; soap_end_sessions(soap, NULL, NULL); } /******************************************************************************\ * * For Internal Plugin Use (NOT THREAD-SAFE ALONE) * \******************************************************************************/ /** @brief Intended for internal plugin use. Creates a cookie that will be sent to the client, named SESSION_COOKIE_NAME with the given value, domain, and path. */ SOAP_FMAC1 void SOAP_FMAC2 soap_session_cookie_create(struct soap *soap, const char *value, const char *domain, const char *path, long maxage) { /* create session ID cookie */ soap_set_cookie(soap, SESSION_COOKIE_NAME, value, domain, path); soap_set_cookie_session(soap, SESSION_COOKIE_NAME, domain, path); soap_set_cookie_expire(soap, SESSION_COOKIE_NAME, maxage, domain, path); } /******************************************************************************/ /** @brief Intended for internal plugin use. Creates a cookie that will be sent to the client, named SESSION_COOKIE_NAME with the given domain and path. The value will be empty and it will be set to expire when the client recieves it. This ensures the session ID is no longer in the client's cookie. */ SOAP_FMAC1 void SOAP_FMAC2 soap_session_cookie_delete(struct soap *soap, const char *domain, const char *path) { /* delete client-side cookie */ soap_set_cookie(soap, SESSION_COOKIE_NAME, "", domain, path); soap_set_cookie_session(soap, SESSION_COOKIE_NAME, domain, path); soap_set_cookie_expire(soap, SESSION_COOKIE_NAME, 0, domain, path); } /******************************************************************************/ /** @brief Intended for internal plugin use. Find the current session with domain and path. Returns NULL if not found or if the session is expired (for normal use, set `when` to `time(NULL)`). Will also clear the cookie if the session is expired. */ SOAP_FMAC1 struct soap_session* SOAP_FMAC2 soap_session_internal_find(struct soap *soap, const char *domain, const char *path, time_t when) { struct soap_session *s = NULL; const char *id; id = soap_cookie_value(soap, SESSION_COOKIE_NAME, domain, path); if (id) { for (s = soap_sessions; s; s = s->next) if (s && !strcmp(s->id, id)) break; if (!s) return NULL; if (s->max_inactive_secs >= 0 && (s->last_touched + s->max_inactive_secs <= when)) { /* session has expired and has not been purged, or NULL */ soap_session_cookie_delete(soap, domain, path); return NULL; } } return s; } /******************************************************************************\ * * Sessions Public API * \******************************************************************************/ /** @brief Gets soap_sessions (first node in sessions linked list). */ SOAP_FMAC1 struct soap_session* SOAP_FMAC2 soap_get_sessions_head() { return soap_sessions; } /******************************************************************************/ /** @brief Removes all sessions that have been stale longer than their max_inactive_secs allows. -1 max_inactive_secs means the session will never expire. The when parameter is usually the current time, i.e. time(NULL) to purge sessions that expire after the when time. This is called internally every time a session or a session_var is accessed, so it shouldn't ever be necessary to manually call this function. Only will purge when the last time we purged was more than SESSION_PURGE_INTERVAL seconds ago (defined in sessions.h). SESSION_PURGE_INTERVAL == -1 means never purge. SESSION_PURGE_INTERVAL == 0 means always purge. */ SOAP_FMAC1 void SOAP_FMAC2 soap_purge_sessions(struct soap *soap, const char *domain, const char *path, time_t when) { struct soap_session *s, *t; struct soap_session_var *v; const char *id; if (SESSION_PURGE_INTERVAL == -1) return; MUTEX_LOCK(sessions_lock); /* If we purge the current session, we'll want to clear the cookie, too. So grab the current ID to check against. */ id = soap_cookie_value(soap, SESSION_COOKIE_NAME, domain, path); /* not time to purge yet */ if (SESSION_PURGE_INTERVAL && (last_purged_sessions + SESSION_PURGE_INTERVAL >= when)) { MUTEX_UNLOCK(sessions_lock); return; } last_purged_sessions = when; s = soap_sessions; while (s) { t = s->next; /* delete session if its older than allowed */ if (s->max_inactive_secs >= 0 && (s->last_touched + s->max_inactive_secs <= when)) { /* clear the cookie if this was the current session */ if (id && !strcmp(id, s->id)) soap_session_cookie_delete(soap, domain, path); /* delete session vars */ for (v = s->session_vars; v;) { struct soap_session_var *u; if (v->name) SOAP_FREE(soap, v->name); if (v->value) SOAP_FREE(soap, v->value); u = v->next; SOAP_FREE(soap, v); v = u; } /* delete session data */ if (s->id) SOAP_FREE(soap, s->id); /* deleting not last node? */ if (t) { /* adjust tail if this was second to last */ if (t == soap_sessions_tail) soap_sessions_tail = s; s->id = t->id; s->session_vars = t->session_vars; s->session_vars_tail = t->session_vars_tail; s->num_session_vars = t->num_session_vars; s->cookie_maxage = t->cookie_maxage; s->rolling = t->rolling; s->last_touched = t->last_touched; s->max_inactive_secs = t->max_inactive_secs; s->next = t->next; SOAP_FREE(soap, t); } else /* deleting the last node */ { /* set tail to previous node */ struct soap_session *u = soap_sessions; while (u && u->next != s) u = u->next; soap_sessions_tail = u; if (soap_sessions_tail == NULL) soap_sessions = NULL; else soap_sessions_tail->next = NULL; SOAP_FREE(soap, s); /* to end the loop */ s = NULL; } num_sessions--; } else { /* only move s to the next node if we didn't do a deletion (because we moved the next node onto s if we did do a deletion) */ s = t; } } MUTEX_UNLOCK(sessions_lock); } /******************************************************************************/ /** @brief Gets the number of sessions in the database. */ SOAP_FMAC1 int SOAP_FMAC2 soap_get_num_sessions(struct soap *soap, const char *domain, const char *path) { /* purge first, so num_sessions will not include expired ones */ soap_purge_sessions(soap, domain, path, time(NULL)); return num_sessions; } /******************************************************************************/ /** @brief Finds the session identified by the cookie with name SESSION_COOKIE_NAME and the given domain and path. Updates session's last_touched to now. Returns NULL if not found. */ SOAP_FMAC1 struct soap_session* SOAP_FMAC2 soap_get_session(struct soap *soap, const char *domain, const char *path) { struct soap_session *s; time_t now = time(NULL); soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); s = soap_session_internal_find(soap, domain, path, now); if (!s) { MUTEX_UNLOCK(sessions_lock); return NULL; } s->last_touched = now; if (s->rolling) soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); MUTEX_UNLOCK(sessions_lock); return s; } /******************************************************************************/ /** @brief Creates a new session with a random 128-bit ID. A cookie will be created with name SESSION_COOKIE_NAME and the given domain and path. If there already exists a session ID cookie, the only action will be updating the session's last_touched to now. Returns a pointer to the session. */ SOAP_FMAC1 struct soap_session* SOAP_FMAC2 soap_start_session(struct soap *soap, const char *domain, const char *path) { struct soap_session *s = NULL; time_t now = time(NULL); const char *id; soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); id = soap_cookie_value(soap, SESSION_COOKIE_NAME, domain, path); if (id) { for (s = soap_sessions; s; s = s->next) if (!strcmp(s->id, id)) break; } if (!s) { /* check if we have too many sessions */ if (num_sessions >= SESSION_MAX) { MUTEX_UNLOCK(sessions_lock); return NULL; } /* create new session */ s = (struct soap_session*)SOAP_MALLOC(soap, sizeof(struct soap_session)); if (s) { /* generate random 128-bit session ID */ s->id = (const char*)soap_strdup(NULL, soap_rand_uuid(soap, NULL)); s->session_vars = NULL; s->session_vars_tail = NULL; s->num_session_vars = 0; s->cookie_maxage = -1; s->rolling = 0; s->last_touched = now; s->max_inactive_secs = SESSIONS_DEFAULT_MAX_INACTIVE_SECS; /* add to end of list */ if (num_sessions == 0) soap_sessions = s; else soap_sessions_tail->next = s; soap_sessions_tail = s; soap_sessions_tail->next = NULL; num_sessions++; soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); } } else { /* session already exists */ if (s->max_inactive_secs >= 0 && (s->last_touched + s->max_inactive_secs <= now)) { /* session has expired and has not been purged, or NULL */ soap_session_cookie_delete(soap, domain, path); } else { /* update last_touched */ s->last_touched = now; if (s->rolling) soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); } } MUTEX_UNLOCK(sessions_lock); return s; } /******************************************************************************/ /** @brief The session ID for the current session (identified by the cookie with name SESSION_COOKIE_NAME and the given domain and path) will be set to a new random 128-bit ID. Also updates the cookie. Returns a pointer to the session. */ SOAP_FMAC1 struct soap_session* SOAP_FMAC2 soap_regenerate_session_id(struct soap *soap, const char *domain, const char *path) { struct soap_session *s; time_t now = time(NULL); soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); s = soap_session_internal_find(soap, domain, path, now); if (!s) { MUTEX_UNLOCK(sessions_lock); return NULL; } s->last_touched = now; if (s->id) SOAP_FREE(soap, s->id); /* generate random 128-bit session ID if one wasn't provided */ s->id = (const char*)soap_strdup(NULL, soap_rand_uuid(soap, NULL)); /* reset session ID cookie */ soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); MUTEX_UNLOCK(sessions_lock); return s; } /******************************************************************************/ /** @brief Frees all sessions in database. */ SOAP_FMAC1 void SOAP_FMAC2 soap_end_sessions(struct soap *soap, const char *domain, const char *path) { struct soap_session *s; MUTEX_LOCK(sessions_lock); last_purged_sessions = time(NULL); s = soap_sessions; while (s) { /* delete session_vars */ struct soap_session *t = s->next; struct soap_session_var *v = s->session_vars; while (v) { struct soap_session_var *u; if (v->name) SOAP_FREE(soap, v->name); if (v->value) SOAP_FREE(soap, v->value); u = v->next; SOAP_FREE(soap, v); v = u; } /* delete session */ if (s->id) SOAP_FREE(soap, s->id); SOAP_FREE(soap, s); s = t; } soap_sessions = NULL; soap_sessions_tail = NULL; num_sessions = 0; /* clear session ID cookie */ soap_session_cookie_delete(soap, domain, path); MUTEX_UNLOCK(sessions_lock); } /******************************************************************************/ /** @brief Frees the current session (identified by the cookie with name SESSION_COOKIE_NAME and the given domain and path). Also clears the cookie. */ SOAP_FMAC1 void SOAP_FMAC2 soap_end_session(struct soap *soap, const char *domain, const char *path) { struct soap_session *s; struct soap_session_var *v; time_t now = time(NULL); soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); s = soap_session_internal_find(soap, domain, path, now); if (!s) { MUTEX_UNLOCK(sessions_lock); return; } /* deleting whether expired or not */ /* delete session vars */ for (v = s->session_vars; v;) { struct soap_session_var *u; if (v->name) SOAP_FREE(soap, v->name); if (v->value) SOAP_FREE(soap, v->value); u = v->next; SOAP_FREE(soap, v); v = u; } /* delete session data */ if (s->id) SOAP_FREE(soap, s->id); struct soap_session *temp = s->next; /* deleting not last node */ if (temp != NULL) { /* adjust tail if this was second to last */ if (temp == soap_sessions_tail) soap_sessions_tail = s; s->id = temp->id; s->session_vars = temp->session_vars; s->session_vars_tail = temp->session_vars_tail; s->num_session_vars = temp->num_session_vars; s->cookie_maxage = temp->cookie_maxage; s->last_touched = temp->last_touched; s->max_inactive_secs = temp->max_inactive_secs; s->next = temp->next; SOAP_FREE(soap, temp); } /* deleting the last node */ else { /* set tail to previous node */ struct soap_session *t = soap_sessions; while (t && t->next != s) t = t->next; soap_sessions_tail = t; if (soap_sessions_tail == NULL) soap_sessions = NULL; else soap_sessions_tail->next = NULL; SOAP_FREE(soap, s); } num_sessions--; /* delete client-side cookie */ soap_session_cookie_delete(soap, domain, path); MUTEX_UNLOCK(sessions_lock); } /******************************************************************************/ /** @brief Gets the cookie_maxage for the current session (identified by the cookie with name SESSION_COOKIE_NAME and the given domain and path). -1 means the cookie is a session cookie. 0 means a session was not found or is expired. */ SOAP_FMAC1 long SOAP_FMAC2 soap_get_session_cookie_maxage(struct soap *soap, const char *domain, const char *path) { struct soap_session *s; time_t now = time(NULL); long result; soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); s = soap_session_internal_find(soap, domain, path, now); if (!s) { MUTEX_UNLOCK(sessions_lock); return 0; } result = s->cookie_maxage; if (s->rolling) soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); MUTEX_UNLOCK(sessions_lock); return result; } /******************************************************************************/ /** @brief Sets the cookie_maxage for the current session (identified by the cookie with name SESSION_COOKIE_NAME and the given domain and path). -1 means the cookie will be a session cookie. If successful, returns SOAP_OK, or SOAP_ERR otherwise. */ SOAP_FMAC1 int SOAP_FMAC2 soap_set_session_cookie_maxage(struct soap *soap, long maxage, const char *domain, const char *path) { struct soap_session *s; time_t now = time(NULL); soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); s = soap_session_internal_find(soap, domain, path, now); if (!s) { MUTEX_UNLOCK(sessions_lock); return SOAP_ERR; } s->cookie_maxage = maxage; soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); MUTEX_UNLOCK(sessions_lock); return SOAP_OK; } /******************************************************************************/ /** @brief Gets the max_inactive_secs for the current session (identified by the cookie with name SESSION_COOKIE_NAME and the given domain and path). -1 means the session will never expire. 0 means the session wasn't found or is expired. */ SOAP_FMAC1 long SOAP_FMAC2 soap_get_session_max_inactive_secs(struct soap *soap, const char *domain, const char *path) { struct soap_session *s; time_t now = time(NULL); long result; soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); s = soap_session_internal_find(soap, domain, path, now); if (!s) { MUTEX_UNLOCK(sessions_lock); return 0; } result = s->max_inactive_secs; if (s->rolling) soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); MUTEX_UNLOCK(sessions_lock); return result; } /******************************************************************************/ /** @brief Sets the max_inactive_secs for the current session (identified by the cookie with name SESSION_COOKIE_NAME and the given domain and path). -1 means the session will never expire. If successful, returns SOAP_OK, or SOAP_ERR otherwise. */ SOAP_FMAC1 int SOAP_FMAC2 soap_set_session_max_inactive_secs(struct soap *soap, long max, const char *domain, const char *path) { struct soap_session *s; time_t now = time(NULL); soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); s = soap_session_internal_find(soap, domain, path, now); if (!s) { MUTEX_UNLOCK(sessions_lock); return SOAP_ERR; } s->max_inactive_secs = max; if (s->rolling) soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); MUTEX_UNLOCK(sessions_lock); return SOAP_OK; } /******************************************************************************/ /** @brief Gets the `rolling` flag for the current session (identified by the cookie with name SESSION_COOKIE_NAME and the given domain and path). -1 means the session was not found or is expired. */ SOAP_FMAC1 int SOAP_FMAC2 soap_get_session_rolling(struct soap *soap, const char *domain, const char *path) { struct soap_session *s; time_t now = time(NULL); int result; soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); s = soap_session_internal_find(soap, domain, path, now); if (!s) { MUTEX_UNLOCK(sessions_lock); return -1; } result = s->rolling; if (s->rolling) soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); MUTEX_UNLOCK(sessions_lock); return result; } /******************************************************************************/ /** @brief Sets the `rolling` flag for the current session (identified by the cookie with name SESSION_COOKIE_NAME and the given domain and path). If successful, returns SOAP_OK, or SOAP_ERR otherwise. */ SOAP_FMAC1 int SOAP_FMAC2 soap_set_session_rolling(struct soap *soap, int rolling, const char *domain, const char *path) { struct soap_session *s; time_t now = time(NULL); soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); s = soap_session_internal_find(soap, domain, path, now); if (!s) { MUTEX_UNLOCK(sessions_lock); return SOAP_ERR; } s->rolling = rolling; if (s->rolling) soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); MUTEX_UNLOCK(sessions_lock); return SOAP_OK; } /******************************************************************************\ * * Session Variables Public API * \******************************************************************************/ /** @brief Gets the number of sessions variables in the current session (identified by the cookie with name SESSION_COOKIE_NAME and the given domain and path). Returns -1 if the session does not exist. */ SOAP_FMAC1 int SOAP_FMAC2 soap_get_num_session_vars(struct soap *soap, const char *domain, const char *path) { struct soap_session *s; time_t now = time(NULL); int result; soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); s = soap_session_internal_find(soap, domain, path, now); if (!s) { MUTEX_UNLOCK(sessions_lock); return -1; } result = s->num_session_vars; if (s->rolling) soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); MUTEX_UNLOCK(sessions_lock); return result; } /******************************************************************************/ /** @brief Returns session_var value by name in the current session (identified by the cookie with name SESSION_COOKIE_NAME and the given domain and path). Returns NULL if not found. */ SOAP_FMAC1 const char* SOAP_FMAC2 soap_get_session_var(struct soap *soap, const char *name, const char *domain, const char *path) { struct soap_session *s; struct soap_session_var *v; time_t now = time(NULL); const char *value; soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); s = soap_session_internal_find(soap, domain, path, now); if (!s) { MUTEX_UNLOCK(sessions_lock); return NULL; } s->last_touched = now; if (s->rolling) soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); for (v = s->session_vars; v; v = v->next) if (!strcmp(v->name, name)) break; value = v ? v->value : NULL; MUTEX_UNLOCK(sessions_lock); return value; } /******************************************************************************/ /** @brief Add new session_var with the given name and value to the current session (identified by the cookie with name SESSION_COOKIE_NAME and the given domain and path). If the name is already used, the value is re-written. If successful, returns pointer to a session_var node in the session's linked list, or NULL otherwise. */ SOAP_FMAC1 struct soap_session_var* SOAP_FMAC2 soap_set_session_var(struct soap *soap, const char *name, const char *value, const char *domain, const char *path) { struct soap_session *s; struct soap_session_var *v; time_t now = time(NULL); soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); s = soap_session_internal_find(soap, domain, path, now); if (!s) { MUTEX_UNLOCK(sessions_lock); return NULL; } s->last_touched = now; if (s->rolling) soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); for (v = s->session_vars; v; v = v->next) if (!strcmp(v->name, name)) break; if (!v) { v = (struct soap_session_var*)SOAP_MALLOC(soap, sizeof(struct soap_session_var)); if (v) { size_t l = strlen(name); char *x = (char*)SOAP_MALLOC(soap, l + 1); v->name = x; if (v->name) soap_strcpy(x, l + 1, name); l = strlen(value); x = (char*)SOAP_MALLOC(soap, l + 1); v->value = x; if (v->value) soap_strcpy(x, l + 1, value); /* add to end of list */ if (s->num_session_vars == 0) s->session_vars = v; else s->session_vars_tail->next = v; s->session_vars_tail = v; s->session_vars_tail->next = NULL; s->num_session_vars++; } } else { size_t l = strlen(value); char *x; if (v->value) SOAP_FREE(soap, v->value); x = (char*)SOAP_MALLOC(soap, l + 1); v->value = x; if (v->value) soap_strcpy(x, l + 1, value); } MUTEX_UNLOCK(sessions_lock); return v; } /******************************************************************************/ /** @brief Frees all session vars in the current session (identified by the cookie with name SESSION_COOKIE_NAME and the given domain and path). */ SOAP_FMAC1 void SOAP_FMAC2 soap_clr_session_vars(struct soap *soap, const char *domain, const char *path) { struct soap_session *s; struct soap_session_var *v; time_t now = time(NULL); soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); s = soap_session_internal_find(soap, domain, path, now); if (!s) { MUTEX_UNLOCK(sessions_lock); return; } s->last_touched = now; if (s->rolling) soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); v = s->session_vars; while (v) { struct soap_session_var *u; if (v->name) SOAP_FREE(soap, v->name); if (v->value) SOAP_FREE(soap, v->value); u = v->next; SOAP_FREE(soap, v); v = u; } s->session_vars = NULL; s->num_session_vars = 0; MUTEX_UNLOCK(sessions_lock); } /******************************************************************************/ /** @brief Frees session var with the given name in the current session (identified by the cookie with name SESSION_COOKIE_NAME and the given domain and path). */ SOAP_FMAC1 void SOAP_FMAC2 soap_clr_session_var(struct soap *soap, const char *name, const char *domain, const char *path) { struct soap_session *s; struct soap_session_var *v; time_t now = time(NULL); soap_purge_sessions(soap, domain, path, now); MUTEX_LOCK(sessions_lock); s = soap_session_internal_find(soap, domain, path, now); if (!s) { MUTEX_UNLOCK(sessions_lock); return; } s->last_touched = now; if (s->rolling) soap_session_cookie_create(soap, s->id, domain, path, s->cookie_maxage); for (v = s->session_vars; v; v = v->next) if (!strcmp(v->name, name)) break; if (!v) { MUTEX_UNLOCK(sessions_lock); return; } /* delete session var data */ if (v->name) SOAP_FREE(soap, v->name); if (v->value) SOAP_FREE(soap, v->value); struct soap_session_var *temp = v->next; /* deleting not last node */ if (temp != NULL) { /* adjust tail if this was second to last */ if (temp == s->session_vars_tail) s->session_vars_tail = v; v->name = temp->name; v->value = temp->value; v->next = temp->next; SOAP_FREE(soap, temp); } /* deleting the last node */ else { /* set tail to previous node */ struct soap_session_var *t = s->session_vars; while (t && t->next != v) t = t->next; s->session_vars_tail = t; if (s->session_vars_tail == NULL) s->session_vars = NULL; else s->session_vars_tail->next = NULL; SOAP_FREE(soap, v); } s->num_session_vars--; MUTEX_UNLOCK(sessions_lock); } /******************************************************************************/ #ifdef __cplusplus } #endif