1 /************************************************************************
2 qDecoder - Web Application Interface for C/C++ http://www.qDecoder.org
3
4 Copyright (C) 2001 The qDecoder Project.
5 Copyright (C) 1999,2000 Hongik Internet, Inc.
6 Copyright (C) 1998 Nobreak Technologies, Inc.
7 Copyright (C) 1996,1997 Seung-young Kim.
8
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Lesser General Public
11 License as published by the Free Software Foundation; either
12 version 2.1 of the License, or (at your option) any later version.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public
20 License along with this library; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
23 Copyright Disclaimer:
24 Hongik Internet, Inc., hereby disclaims all copyright interest.
25 President, Christopher Roh, 6 April 2000
26
27 Nobreak Technologies, Inc., hereby disclaims all copyright interest.
28 President, Yoon Cho, 6 April 2000
29
30 Seung-young Kim, hereby disclaims all copyright interest.
31 Author, Seung-young Kim, 6 April 2000
32 ************************************************************************/
33
34 #include "qDecoder.h"
35 #include "qInternal.h"
36
37
38 /**********************************************
39 ** Define Paragraph
40 **********************************************/
41
42 #ifdef _WIN32
43 #define SESSION_DEFAULT_REPOSITORY "C:\\Windows\\Temp"
44 #else
45 #define SESSION_DEFAULT_REPOSITORY "/tmp"
46 #endif
47
48 #define SESSION_ID "QSESSIONID"
49 #define SESSION_PREFIX "qsession-"
50 #define SESSION_STORAGE_EXTENSION ".properties"
51 #define SESSION_TIMEOUT_EXTENSION ".timeout"
52 #define SESSION_TIMETOCLEAR_FILENAME "qsession-timetoclear"
53
54 #define INTER_PREFIX "_Q_"
55 #define INTER_SESSIONID INTER_PREFIX "SESSIONID"
56 #define INTER_CREATED_GMT INTER_PREFIX "CREATED-GMT"
57 #define INTER_CREATED_SEC INTER_PREFIX "CREATED"
58 #define INTER_INTERVAL_SEC INTER_PREFIX "INTERVAL"
59 #define INTER_CONNECTIONS INTER_PREFIX "CONNECTIONS"
60
61 #define SESSION_DEFAULT_TIMEOUT_INTERVAL (30 * 60)
62
63
64 /**********************************************
65 ** Internal Functions Definition
66 **********************************************/
67
68 static int _clearRepository(void);
69 static int _isValidSession(char *filename);
70 static time_t _updateTimeout(char *filename, time_t timeout_interval);
71
72
73 /**********************************************
74 ** Static Values Definition used only internal
75 **********************************************/
76
77 static int _session_started = 0;
78 static int _session_new = 0;
79 static int _session_modified = 0;
80 static Q_Entry *_session_first_entry = NULL;
81
82 static char _session_repository_path[1024];
83 static char _session_storage_path[1024];
84 static char _session_timeout_path[1024];
85 static time_t _session_timeout_interval = (time_t)SESSION_DEFAULT_TIMEOUT_INTERVAL; /* seconds */
86
87
88 /**********************************************
89 ** Usage : qSession(Repository Path);
90 ** Return: New session 1 else 0.
91 ** Do : Start Session.
92 **
93 ** ex) qSession(NULL); // use default storage
94 ** qSession("/tmp"); // use /tmp for session storage
95 **********************************************/
96 /* Initialize session data */
97 int qSession(char *repository) {
98 int new_session;
99 char *sessionkey;
100
101 /* check if session already started */
102 if(_session_started) return _session_new;
103 _session_first_entry = NULL;
104 _session_started = 1;
105 _session_modified = 0;
106
107 /* check content flag */
108 if(qGetContentFlag() == 1) qError("qSession(): must be called before qContentType() and any stream out.");
109
110 /* check session status & get session id */
111 sessionkey = qValue(SESSION_ID);
112 if(sessionkey == NULL) { /* new session */
113 sessionkey = qUniqueID();
114 new_session = 1;
115 }
116 else {
117 new_session = 0;
118 }
119
120 /* make storage path for session */
121 if (repository != NULL) strcpy(_session_repository_path, repository);
122 else strcpy(_session_repository_path, SESSION_DEFAULT_REPOSITORY);
123 sprintf(_session_storage_path, "%s/%s%s%s", _session_repository_path, SESSION_PREFIX, sessionkey, SESSION_STORAGE_EXTENSION);
124 sprintf(_session_timeout_path, "%s/%s%s%s", _session_repository_path, SESSION_PREFIX, sessionkey, SESSION_TIMEOUT_EXTENSION);
125
126 /* validate exist session */
127 if(new_session == 0) {
128 if(_isValidSession(_session_timeout_path) <= 0) { /* expired or not found */
129 unlink(_session_storage_path);
130 unlink(_session_timeout_path);
131
132 /* remake storage path */
133 sessionkey = qUniqueID();
134 sprintf(_session_storage_path, "%s/%s%s%s", _session_repository_path, SESSION_PREFIX, sessionkey, SESSION_STORAGE_EXTENSION);
135 sprintf(_session_timeout_path, "%s/%s%s%s", _session_repository_path, SESSION_PREFIX, sessionkey, SESSION_TIMEOUT_EXTENSION);
136
137 /* set flag */
138 new_session = 1;
139 }
140 }
141
142 /* if new session, set session id */
143 if(new_session == 1) {
144 char created_gmt[32], created_sec[32];
145 time_t nowtime;
146
147 qCookieSet(SESSION_ID, sessionkey, 0, "/", NULL, NULL);
148 qValueAdd(SESSION_ID, sessionkey); /* force to add session_in to query list */
149
150 /* save session informations */
151 nowtime = qGetGMTime(created_gmt, (time_t)0);
152 sprintf(created_sec, "%ld", (long)nowtime);
153
154 _session_first_entry = _EntryAdd(_session_first_entry, INTER_SESSIONID, sessionkey, 1);
155 _EntryAdd(_session_first_entry, INTER_CREATED_GMT, created_gmt, 1);
156 _EntryAdd(_session_first_entry, INTER_CREATED_SEC, created_sec, 1);
157 _EntryAdd(_session_first_entry, INTER_CONNECTIONS, "1", 1);
158
159 /* set timeout interval */
160 qSessionSetTimeout(_session_timeout_interval);
161 }
162 /* else read session properties */
163 else {
164 int conns;
165 char connstr[16];
166
167 /* read exist session informations */
168 _session_first_entry = _EntryLoad(_session_storage_path);
169
170 /* update session informations */
171 conns = qSessionValueInteger(INTER_CONNECTIONS);
172 sprintf(connstr, "%d", ++conns);
173 _EntryAdd(_session_first_entry, INTER_CONNECTIONS, connstr, 1);
174
175 /* set timeout interval */
176 qSessionSetTimeout((time_t)atol(qSessionValue(INTER_INTERVAL_SEC)));
177 }
178
179 /* set globals */
180 _session_new = new_session;
181 return _session_new;
182 }
183
184 /**********************************************
185 ** Usage : qSessionAdd(name, value);
186 ** Return: Stored String pointer of value.
187 ** Do : Add session value.
188 **
189 ** ex) qSessionAdd("name", "qDecoder");
190 ** qSessionAdd("cginame", "%s", qCGIname());
191 **********************************************/
192 char *qSessionAdd(char *name, char *format, ...) {
193 Q_Entry *new_entry;
194 char value[1024];
195 int status;
196 va_list arglist;
197
198 if(_session_started == 0) qError("qSessionAdd(): qSession() must be called before.");
199 if(!strcmp(name, "")) qError("qSessionAdd(): can not add empty name.");
200 if(!strncmp(name, INTER_PREFIX, strlen(INTER_PREFIX))) qError("qSessionAdd(): Name can not start with %s. It's reserved for internal uses.", INTER_PREFIX);
201
202 va_start(arglist, format);
203 status = vsprintf(value, format, arglist);
204 if(strlen(value) + 1 > sizeof(value) || status == EOF) qError("qSessionAdd(): Message is too long or invalid.");
205 va_end(arglist);
206
207 new_entry = _EntryAdd(_session_first_entry, name, value, 1);
208 if(!_session_first_entry) _session_first_entry = new_entry;
209
210 /* set modified flag */
211 _session_modified = 1;
212
213 return qSessionValue(name);
214 }
215
216 /**********************************************
217 ** Usage : qSessionAddInteger(name, integer);
218 ** Return: Stored integer value.
219 ** Do : Add session value of integer type.
220 **
221 ** ex) qSessionAddInteger("count", 32);
222 **********************************************/
223 int qSessionAddInteger(char *name, int valueint) {
224 char value[32];
225
226 sprintf(value, "%d", valueint);
227 qSessionAdd(name, value);
228
229 return qSessionValueInteger(name);
230 }
231
232 /**********************************************
233 ** Usage : qSessionUpdateInteger(name, plus integer);
234 ** Return: Updated integer value.
235 ** Do : Update session value of integer type.
236 **
237 ** ex) qSessionUpdateInteger("count", -4);
238 **********************************************/
239 int qSessionUpdateInteger(char *name, int plusint) {
240 qSessionAddInteger(name, qSessionValueInteger(name) + plusint);
241
242 return qSessionValueInteger(name);
243 }
244
245 /**********************************************
246 ** Usage : qSessionRemove(name);
247 ** Do : Remove session variable.
248 **
249 ** ex) qSessionRemove("name");
250 ** qSessionRemove("%d.name", i);
251 **********************************************/
252 void qSessionRemove(char *format, ...) {
253 char name[1024];
254 int status;
255 va_list arglist;
256
257 va_start(arglist, format);
258 status = vsprintf(name, format, arglist);
259 if(strlen(name) + 1 > sizeof(name) || status == EOF) qError("qSessionRemove(): Message is too long or invalid.");
260 va_end(arglist);
261
262 if(!strcmp(name, "")) qError("qAddRemove(): can not remove empty name.");
263 if(_session_started == 0) qError("qSessionRemove(): qSession() must be called before.");
264 if(!strncmp(name, INTER_PREFIX, strlen(INTER_PREFIX))) qError("qSessionRemove(): can not remove reserved words.");
265
266 _session_first_entry = _EntryRemove(_session_first_entry, name);
267
268 /* set modified flag */
269 _session_modified = 1;
270 }
271
272 /**********************************************
273 ** Usage : qSessionValue(name);
274 ** Return: Success pointer of value string, Fail NULL.
275 ** Do : Return session value.
276 **
277 ** ex) char *value;
278 ** value = qSessionValue("name");
279 ** value = qSessionValue("%d.name", i);
280 **********************************************/
281 char *qSessionValue(char *format, ...) {
282 char name[1024], *value;
283 int status;
284 va_list arglist;
285
286 if(_session_started == 0) qError("qSessionValue(): qSession() must be called before.");
287
288 va_start(arglist, format);
289 status = vsprintf(name, format, arglist);
290 if(strlen(name) + 1 > sizeof(name) || status == EOF) qError("qSessionValue(): Message is too long or invalid.");
291 va_end(arglist);
292
293 value = _EntryValue(_session_first_entry, name);
294
295 return value;
296 }
297
298 /**********************************************
299 ** Usage : qSessionValueInteger(name);
300 ** Return: Success integer of value, Fail 0.
301 ** Do : Return session value.
302 **
303 ** ex) int value;
304 ** value = qSessionValueInteger("count");
305 **********************************************/
306 int qSessionValueInteger(char *format, ...) {
307 char name[1024];
308 int value;
309 int status;
310 va_list arglist;
311
312 if(_session_started == 0) qError("qSessionValue(): qSession() must be called before.");
313
314 va_start(arglist, format);
315 status = vsprintf(name, format, arglist);
316 if(strlen(name) + 1 > sizeof(name) || status == EOF) qError("qSessionValue(): Message is too long or invalid.");
317 va_end(arglist);
318
319 value = _EntryiValue(_session_first_entry, name);
320
321 return value;
322 }
323
324 /**********************************************
325 ** Usage : qSessionPrint();
326 ** Do : Print all session variables for debugging
327 **********************************************/
328 int qSessionPrint(void) {
329 if(_session_started == 0) qError("qSessionPrint(): qSession() must be called before.");
330 return _EntryPrint(_session_first_entry);
331 }
332
333 /**********************************************
334 ** Usage : qSessionSave();
335 ** Do : Save session data immediately.
336 **********************************************/
337 void qSessionSave(void) {
338 if(_session_started == 0 || _session_first_entry == NULL) return;
339 if(_session_new == 1 && _session_modified == 0) return;
340
341 if(_EntrySave(_session_first_entry, _session_storage_path) == 0) {
342 qError("qSessionSave(): Can not access session repository(%s).", _session_storage_path);
343 }
344 if(_updateTimeout(_session_timeout_path, _session_timeout_interval) == 0) {
345 qError("qSessionSave(): Can not access session repository(%s).", _session_timeout_path);
346 }
347
348 /* clear modified flag */
349 _session_modified = 0;
350 }
351
352 /**********************************************
353 ** Usage : qSessionFree();
354 ** Do : Save session data and deallocate memories.
355 **********************************************/
356 /* Free & Save */
357 void qSessionFree(void) {
358 if(_session_started == 0) return;
359
360 qSessionSave();
361 _clearRepository();
362
363 if(_session_first_entry) _EntryFree(_session_first_entry);
364 _session_first_entry = NULL;
365 _session_started = 0;
366 _session_new = 0;
367 _session_modified = 0;
368 _session_timeout_interval = (time_t)SESSION_DEFAULT_TIMEOUT_INTERVAL;
369 strcpy(_session_repository_path, "");
370 strcpy(_session_storage_path, "");
371 strcpy(_session_timeout_path, "");
372 }
373
374 /**********************************************
375 ** Usage : qSessionDestroy();
376 ** Do : Destroy current session and stored all session data
377 ** will be removed.
378 **********************************************/
379 void qSessionDestroy(void) {
380 if(_session_started == 0) qError("qSessionDestroy(): qSession() must be called before.");
381
382 unlink(_session_storage_path);
383 unlink(_session_timeout_path);
384 if(_session_first_entry) _EntryFree(_session_first_entry);
385 _session_first_entry = NULL;
386
387 qSessionFree();
388
389 if(qGetContentFlag() == 0) {
390 qCookieRemove(SESSION_ID, "/", NULL, NULL);
391 }
392 }
393
394 /**********************************************
395 ** Usage : qSessionSetTimeout(interval seconds);
396 ** Return: New expiration period.
397 ** Do : Change session expiration period.
398 **
399 ** ex) qSessionSetTimeout((time_t)3600);
400 **********************************************/
401 time_t qSessionSetTimeout(time_t seconds) {
402 char interval_sec[32];
403
404 if(_session_started == 0) qError("qSessionSetTimeout(): qSession() must be called before.");
405 if(seconds <= (time_t)0) qError("qSessionSetTimeout(): can not set negative interval. Use qSessionDestory() instead.");
406
407 _session_timeout_interval = seconds;
408
409 /* save session informations */
410 sprintf(interval_sec, "%ld", (long)_session_timeout_interval);
411 _EntryAdd(_session_first_entry, INTER_INTERVAL_SEC, interval_sec, 1);
412
413 return _session_timeout_interval;
414 }
415
416 /**********************************************
417 ** Usage : qSessionGetID();
418 ** Return: String pointer of session id.
419 ** Do : Return current session id.
420 **
421 ** ex) char *sessionid;
422 ** sessionid = qSessionGetID();
423 **********************************************/
424 char *qSessionGetID(void) {
425 if(_session_started == 0) qError("qSessionGetID(): qSession() must be called before.");
426 return qSessionValue(INTER_SESSIONID);
427 }
428
429 /**********************************************
430 ** Usage : qSessionGetCreated();
431 ** Return: Value of time in seconds since 0 hours,
432 ** 0 minutes, 0 seconds, January 1, 1970.
433 ** Do : Return session created time in seconds.
434 **
435 ** ex) time_t created;
436 ** struct tm *gmtime;
437 ** created = qSessionGetCreated();
438 ** gmtime = gmtime(&created);
439 **********************************************/
440 time_t qSessionGetCreated(void) {
441 time_t created;
442 char *tmp;
443
444 if(_session_started == 0) qError("qSessionGetCreated(): qSession() must be called before.");
445 tmp = qSessionValue(INTER_CREATED_SEC);
446 created = (time_t)atol(tmp);
447
448 return created;
449 }
450
451 /**********************************************
452 ** Internal Functions
453 **********************************************/
454
455 static int _clearRepository(void) {
456 #ifdef _WIN32
457 return 0;
458 #else
459 DIR *dp;
460 struct dirent *dirp;
461 char timeoutpath[1024];
462 int clearcnt;
463
464 if(_session_started == 0) qError("_clearRepository(): qSession() must be called before.");
465 sprintf(timeoutpath, "%s/%s", _session_repository_path, SESSION_TIMETOCLEAR_FILENAME);
466
467 if(_isValidSession(timeoutpath) > 0) return 0; /* Valid */
468
469 /* expired or not found, main routine start here */
470 /* to prevent race condition, update time stamp first */
471 if(_updateTimeout(timeoutpath, _session_timeout_interval) == 0) {
472 qError("_clearRepository(): Can not access session repository(%s).", timeoutpath);
473 }
474
475 /* clear old session data */
476 if((dp = opendir(_session_repository_path)) == NULL) qError("_clearRepository(): Can not access session repository(%s).", _session_repository_path);
477
478 for (clearcnt = 0; (dirp = readdir(dp)) != NULL; ) {
479 if(strstr(dirp->d_name, SESSION_PREFIX) && strstr(dirp->d_name, SESSION_TIMEOUT_EXTENSION)) {
480 sprintf(timeoutpath, "%s/%s", _session_repository_path, dirp->d_name);
481 if(_isValidSession(timeoutpath) <= 0) { /* expired */
482 /* remove timeout */
483 unlink(timeoutpath);
484
485 /* remove properties */
486 timeoutpath[strlen(timeoutpath) - strlen(SESSION_TIMEOUT_EXTENSION)] = '\0';
487 strcat(timeoutpath, SESSION_STORAGE_EXTENSION);
488 unlink(timeoutpath);
489
490 clearcnt++;
491 }
492 }
493 }
494 closedir(dp);
495
496 return clearcnt;
497 #endif
498 }
499
500 /* session not found 0, session expired -1, session valid 1 */
501 static int _isValidSession(char *filename) {
502 FILE *fp;
503 time_t timeout, timenow;
504 double timediff;
505
506 if((fp = qfopen(filename, "r")) == NULL) return 0;
507 fscanf(fp, "%ld", &timeout);
508 qfclose(fp);
509
510 timenow = time(NULL);
511 timediff = difftime(timeout, timenow); /* return timeout - timenow */
512
513 if(timediff >= (double)0) return 1; /* valid */
514 return -1; /* expired */
515 }
516
517 /* success > 0, write fail 0 */
518 static time_t _updateTimeout(char *filename, time_t timeout_interval) {
519 FILE *fp;
520 time_t timeout;
521
522 timeout = time(NULL);
523 timeout += timeout_interval;
524
525 if((fp = qfopen(filename, "w")) == NULL) return 0;
526 fprintf(fp, "%ld\n", (long)timeout);
527 qfclose(fp);
528
529 return timeout;
530 }
531