1 /******************************************************************************
2 * qDecoder - http://www.qdecoder.org
3 *
4 * Copyright (c) 2000-2012 Seungyoung Kim.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 ******************************************************************************
28 * $Id: qcgisess.c 639 2012-05-07 23:44:19Z seungyoung.kim $
29 ******************************************************************************/
30
31 /**
32 * @file qcgisess.c CGI Session API
33 */
34
35 #ifdef ENABLE_FASTCGI
36 #include "fcgi_stdio.h"
37 #else
38 #include <stdio.h>
39 #endif
40 #include <stdlib.h>
41 #include <stdbool.h>
42 #include <string.h>
43 #include <stdarg.h>
44 #include <unistd.h>
45 #include <time.h>
46 #include <sys/time.h>
47 #include <limits.h>
48 #ifndef _WIN32
49 #include <dirent.h>
50 #endif
51 #include "qdecoder.h"
52 #include "internal.h"
53
54 #if defined(__MINGW32__) && defined(_WIN32) && !defined(__CYGWIN__)
55 #include "msw_missing.h"
56 #endif
57
58 #ifdef _WIN32
59 #define SESSION_DEFAULT_REPOSITORY "C:\\Windows\\Temp"
60 #else
61 #define SESSION_DEFAULT_REPOSITORY "/tmp"
62 #endif
63
64 #define SESSION_ID "QSESSIONID"
65 #define SESSION_PREFIX "qsession-"
66 #define SESSION_STORAGE_EXTENSION ".properties"
67 #define SESSION_TIMEOUT_EXTENSION ".expire"
68 #define SESSION_TIMETOCLEAR_FILENAME "qsession-timetoclear"
69 #define SESSION_DEFAULT_TIMEOUT_INTERVAL (30 * 60)
70
71 #ifndef _DOXYGEN_SKIP
72
73 #define INTER_PREFIX "_Q_"
74 #define INTER_SESSIONID INTER_PREFIX "SESSIONID"
75 #define INTER_SESSION_REPO INTER_PREFIX "REPOSITORY"
76 #define INTER_CREATED_SEC INTER_PREFIX "CREATED"
77 #define INTER_INTERVAL_SEC INTER_PREFIX "INTERVAL"
78 #define INTER_CONNECTIONS INTER_PREFIX "CONNECTIONS"
79
80 static bool _clear_repo(const char *session_repository_path);
81 static int _is_valid_session(const char *filepath);
82 static bool _update_timeout(const char *filepath, time_t timeout_interval);
83 static char *_genuniqid(void);
84
85 #endif
86
87 /**
88 * Initialize session
89 *
90 * @param request a pointer of request structure returned by qcgireq_parse()
91 * @param dirpath directory path where session data will be kept
92 *
93 * @return a pointer of malloced session data list (qentry_t type)
94 *
95 * @note
96 * The returned qentry_t list must be de-allocated by calling qentry_t->free().
97 * And if you want to append or remove some user session data, use qentry_t->*()
98 * functions then finally call qcgisess_save() to store updated session data.
99 */
qcgisess_init(qentry_t * request,const char * dirpath)100 qentry_t *qcgisess_init(qentry_t *request, const char *dirpath)
101 {
102 // check content flag
103 if (qcgires_getcontenttype(request) != NULL) {
104 DEBUG("Should be called before qRequestSetContentType().");
105 return NULL;
106 }
107
108 qentry_t *session = qEntry();
109 if (session == NULL) return NULL;
110
111 // check session status & get session id
112 bool new_session;
113 char *sessionkey;
114 if (request->getstr(request, SESSION_ID, false) != NULL) {
115 sessionkey = request->getstr(request, SESSION_ID, true);
116 new_session = false;
117 } else { // new session
118 sessionkey = _genuniqid();
119 new_session = true;
120 }
121
122 // make storage path for session
123 char session_repository_path[PATH_MAX];
124 char session_storage_path[PATH_MAX];
125 char session_timeout_path[PATH_MAX];
126 time_t session_timeout_interval = (time_t)SESSION_DEFAULT_TIMEOUT_INTERVAL; // seconds
127
128 if (dirpath != NULL) strncpy(session_repository_path, dirpath,
129 sizeof(session_repository_path));
130 else strncpy(session_repository_path, SESSION_DEFAULT_REPOSITORY,
131 sizeof(session_repository_path));
132 snprintf(session_storage_path, sizeof(session_storage_path),
133 "%s/%s%s%s",
134 session_repository_path,
135 SESSION_PREFIX, sessionkey, SESSION_STORAGE_EXTENSION);
136 snprintf(session_timeout_path, sizeof(session_timeout_path),
137 "%s/%s%s%s",
138 session_repository_path,
139 SESSION_PREFIX, sessionkey, SESSION_TIMEOUT_EXTENSION);
140
141 // validate exist session
142 if (new_session == false) {
143 int valid = _is_valid_session(session_timeout_path);
144 if (valid <= 0) { // expired or not found
145 if (valid < 0) {
146 _q_unlink(session_storage_path);
147 _q_unlink(session_timeout_path);
148 }
149
150 // remake storage path
151 free(sessionkey);
152 sessionkey = _genuniqid();
153 snprintf(session_storage_path, sizeof(session_storage_path),
154 "%s/%s%s%s",
155 session_repository_path,
156 SESSION_PREFIX, sessionkey, SESSION_STORAGE_EXTENSION);
157 snprintf(session_timeout_path, sizeof(session_timeout_path),
158 "%s/%s%s%s",
159 session_repository_path,
160 SESSION_PREFIX, sessionkey, SESSION_TIMEOUT_EXTENSION);
161
162 // set flag
163 new_session = true;
164 }
165 }
166
167 // if new session, set session id
168 if (new_session == true) {
169 qcgires_setcookie(request, SESSION_ID, sessionkey, 0, "/", NULL, NULL);
170 // force to add session_in to query list
171 request->putstr(request, SESSION_ID, sessionkey, true);
172
173 // save session informations
174 char created_sec[10+1];
175 snprintf(created_sec, sizeof(created_sec), "%ld", (long int)time(NULL));
176 session->putstr(session, INTER_SESSIONID, sessionkey, false);
177 session->putstr(session, INTER_SESSION_REPO, session_repository_path, false);
178 session->putstr(session, INTER_CREATED_SEC, created_sec, false);
179 session->putint(session, INTER_CONNECTIONS, 1, false);
180
181 // set timeout interval
182 qcgisess_settimeout(session, session_timeout_interval);
183 } else { // read session properties
184
185 // read exist session informations
186 session->load(session, session_storage_path);
187
188 // update session informations
189 int conns = session->getint(session, INTER_CONNECTIONS);
190 session->putint(session, INTER_CONNECTIONS, ++conns, true);
191
192 // set timeout interval
193 qcgisess_settimeout(session, session->getint(session, INTER_INTERVAL_SEC));
194 }
195
196 free(sessionkey);
197
198 // set globals
199 return session;
200 }
201
202 /**
203 * Set the auto-expiration seconds about user session
204 *
205 * @param session a pointer of session structure
206 * @param seconds expiration seconds
207 *
208 * @return true if successful, otherwise returns false
209 *
210 * @note Default timeout is defined as SESSION_DEFAULT_TIMEOUT_INTERVAL. 1800 seconds
211 */
qcgisess_settimeout(qentry_t * session,time_t seconds)212 bool qcgisess_settimeout(qentry_t *session, time_t seconds)
213 {
214 if (seconds <= 0) return false;
215 session->putint(session, INTER_INTERVAL_SEC, (int)seconds, true);
216 return true;
217 }
218
219 /**
220 * Get user session id
221 *
222 * @param session a pointer of session structure
223 *
224 * @return a pointer of session identifier
225 *
226 * @note Do not free manually
227 */
qcgisess_getid(qentry_t * session)228 const char *qcgisess_getid(qentry_t *session)
229 {
230 return session->getstr(session, INTER_SESSIONID, false);
231 }
232
233 /**
234 * Get user session created time
235 *
236 * @param session a pointer of session structure
237 *
238 * @return user session created time in UTC time seconds
239 */
qcgisess_getcreated(qentry_t * session)240 time_t qcgisess_getcreated(qentry_t *session)
241 {
242 const char *created = session->getstr(session, INTER_CREATED_SEC, false);
243 return (time_t)atol(created);
244 }
245
246 /**
247 * Update session data
248 *
249 * @param session a pointer of session structure
250 *
251 * @return true if successful, otherwise returns false
252 */
qcgisess_save(qentry_t * session)253 bool qcgisess_save(qentry_t *session)
254 {
255 const char *sessionkey = session->getstr(session, INTER_SESSIONID, false);
256 const char *session_repository_path = session->getstr(session, INTER_SESSION_REPO, false);
257 int session_timeout_interval = session->getint(session, INTER_INTERVAL_SEC);
258 if (sessionkey == NULL || session_repository_path == NULL) return false;
259
260 char session_storage_path[PATH_MAX];
261 char session_timeout_path[PATH_MAX];
262 snprintf(session_storage_path, sizeof(session_storage_path),
263 "%s/%s%s%s",
264 session_repository_path,
265 SESSION_PREFIX, sessionkey, SESSION_STORAGE_EXTENSION);
266 snprintf(session_timeout_path, sizeof(session_timeout_path),
267 "%s/%s%s%s",
268 session_repository_path,
269 SESSION_PREFIX, sessionkey, SESSION_TIMEOUT_EXTENSION);
270
271 if (session->save(session, session_storage_path) == false) {
272 DEBUG("Can't save session file %s", session_storage_path);
273 return false;
274 }
275 if (_update_timeout(session_timeout_path, session_timeout_interval) == false) {
276 DEBUG("Can't update file %s", session_timeout_path);
277 return false;
278 }
279
280 _clear_repo(session_repository_path);
281 return true;
282 }
283
284 /**
285 * Destroy user session
286 *
287 * @param session a pointer of session structure
288 *
289 * @return true if successful, otherwise returns false
290 *
291 * @note
292 * If you only want to de-allocate session structure, just call qentry_t->free().
293 * This will remove all user session data permanantely and also free the session structure.
294 */
qcgisess_destroy(qentry_t * session)295 bool qcgisess_destroy(qentry_t *session)
296 {
297 const char *sessionkey = session->getstr(session, INTER_SESSIONID, false);
298 const char *session_repository_path = session->getstr(session, INTER_SESSION_REPO, false);
299 if (sessionkey == NULL || session_repository_path == NULL) {
300 if (session != NULL) session->free(session);
301 return false;
302 }
303
304 char session_storage_path[PATH_MAX];
305 char session_timeout_path[PATH_MAX];
306 snprintf(session_storage_path, sizeof(session_storage_path),
307 "%s/%s%s%s",
308 session_repository_path,
309 SESSION_PREFIX, sessionkey, SESSION_STORAGE_EXTENSION);
310 snprintf(session_timeout_path, sizeof(session_timeout_path),
311 "%s/%s%s%s",
312 session_repository_path,
313 SESSION_PREFIX, sessionkey, SESSION_TIMEOUT_EXTENSION);
314
315 _q_unlink(session_storage_path);
316 _q_unlink(session_timeout_path);
317
318 if (session != NULL) session->free(session);
319 return true;
320 }
321
322 #ifndef _DOXYGEN_SKIP
323
_clear_repo(const char * session_repository_path)324 static bool _clear_repo(const char *session_repository_path)
325 {
326 #ifdef _WIN32
327 return false;
328 #else
329 // clear old session data
330 DIR *dp;
331 if ((dp = opendir(session_repository_path)) == NULL) {
332 DEBUG("Can't open session repository %s", session_repository_path);
333 return false;
334 }
335
336 struct dirent *dirp;
337 while ((dirp = readdir(dp)) != NULL) {
338 if (strstr(dirp->d_name, SESSION_PREFIX) &&
339 strstr(dirp->d_name, SESSION_TIMEOUT_EXTENSION)) {
340 char timeoutpath[PATH_MAX];
341 snprintf(timeoutpath, sizeof(timeoutpath),
342 "%s/%s", session_repository_path, dirp->d_name);
343 if (_is_valid_session(timeoutpath) <= 0) { // expired
344 // remove timeout
345 _q_unlink(timeoutpath);
346
347 // remove properties
348 timeoutpath[strlen(timeoutpath) - strlen(SESSION_TIMEOUT_EXTENSION)] = '\0';
349 strcat(timeoutpath, SESSION_STORAGE_EXTENSION);
350 _q_unlink(timeoutpath);
351 }
352 }
353 }
354 closedir(dp);
355
356 return true;
357 #endif
358 }
359
360 // session not found 0, session expired -1, session valid 1
_is_valid_session(const char * filepath)361 static int _is_valid_session(const char *filepath)
362 {
363 time_t timeout, timenow;
364 double timediff;
365
366 if ((timeout = (time_t)_q_countread(filepath)) == 0) return 0;
367
368 timenow = time(NULL);
369 timediff = difftime(timeout, timenow); // return timeout - timenow
370
371 if (timediff >= 0) return 1; // valid
372 return -1; // expired
373 }
374
375 // success > 0, write fail 0
_update_timeout(const char * filepath,time_t timeout_interval)376 static bool _update_timeout(const char *filepath, time_t timeout_interval)
377 {
378 if (timeout_interval <= 0) return false;
379
380 if (_q_countsave(filepath, (time(NULL) + timeout_interval)) == false) {
381 return false;
382 }
383 return true;
384 }
385
_genuniqid(void)386 static char *_genuniqid(void)
387 {
388 #ifdef _WIN32
389 unsigned int sec = time(NULL);
390 FILETIME ft;
391 GetSystemTimeAsFileTime(&ft);
392 unsigned int usec = ft.dwLowDateTime % 1000000;
393 #else
394 struct timeval tv;
395 gettimeofday(&tv, NULL);
396 unsigned int sec = tv.tv_sec;
397 unsigned int usec = tv.tv_usec;
398 #endif
399 unsigned int port = 0;
400 const char *remote_port = getenv("REMOTE_PORT");
401 if (remote_port != NULL) {
402 port = atoi(remote_port);
403 }
404
405 char *uniqid = (char *)malloc(5+5+4+4+1);
406 if (snprintf(uniqid, 5+5+4+4+1, "%05x%05x%04x%04x",
407 usec%0x100000, sec%0x100000, getpid()%0x10000, port%0x10000)
408 >= 5+5+4+4+1) uniqid[5+5+4+4] = '\0';;
409
410 return uniqid;
411 }
412
413 #endif /* _DOXYGEN_SKIP */
414