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