1
|
// session.c -- Session management for HTTP/HTTPS connections
|
2
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
3
|
//
|
4
|
// This program is free software; you can redistribute it and/or modify
|
5
|
// it under the terms of the GNU General Public License version 2 as
|
6
|
// published by the Free Software Foundation.
|
7
|
//
|
8
|
// This program is distributed in the hope that it will be useful,
|
9
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
// GNU General Public License for more details.
|
12
|
//
|
13
|
// You should have received a copy of the GNU General Public License along
|
14
|
// with this program; if not, write to the Free Software Foundation, Inc.,
|
15
|
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
16
|
//
|
17
|
// In addition to these license terms, the author grants the following
|
18
|
// additional rights:
|
19
|
//
|
20
|
// If you modify this program, or any covered work, by linking or
|
21
|
// combining it with the OpenSSL project's OpenSSL library (or a
|
22
|
// modified version of that library), containing parts covered by the
|
23
|
// terms of the OpenSSL or SSLeay licenses, the author
|
24
|
// grants you additional permission to convey the resulting work.
|
25
|
// Corresponding Source for a non-source form of such a combination
|
26
|
// shall include the source code for the parts of OpenSSL used as well
|
27
|
// as that of the covered work.
|
28
|
//
|
29
|
// You may at your option choose to remove this additional permission from
|
30
|
// the work, or from any part of it.
|
31
|
//
|
32
|
// It is possible to build this program in a way that it loads OpenSSL
|
33
|
// libraries at run-time. If doing so, the following notices are required
|
34
|
// by the OpenSSL and SSLeay licenses:
|
35
|
//
|
36
|
// This product includes software developed by the OpenSSL Project
|
37
|
// for use in the OpenSSL Toolkit. (http://www.openssl.org/)
|
38
|
//
|
39
|
// This product includes cryptographic software written by Eric Young
|
40
|
// (eay@cryptsoft.com)
|
41
|
//
|
42
|
//
|
43
|
// The most up-to-date version of this program is always available from
|
44
|
// http://shellinabox.com
|
45
|
|
46
|
#include "config.h"
|
47
|
|
48
|
#include <stdlib.h>
|
49
|
#include <string.h>
|
50
|
#include <time.h>
|
51
|
#include <fcntl.h>
|
52
|
#include <unistd.h>
|
53
|
|
54
|
#include "shellinabox/session.h"
|
55
|
#include "logging/logging.h"
|
56
|
|
57
|
#ifdef HAVE_UNUSED
|
58
|
#defined ATTR_UNUSED __attribute__((unused))
|
59
|
#defined UNUSED(x) do { } while (0)
|
60
|
#else
|
61
|
#define ATTR_UNUSED
|
62
|
#define UNUSED(x) do { (void)(x); } while (0)
|
63
|
#endif
|
64
|
|
65
|
static HashMap *sessions;
|
66
|
|
67
|
|
68
|
static struct Graveyard {
|
69
|
struct Graveyard *next;
|
70
|
time_t timeout;
|
71
|
const char *sessionKey;
|
72
|
} *graveyard;
|
73
|
|
74
|
void addToGraveyard(struct Session *session) {
|
75
|
// It is possible for a child process to die, but for the Session to
|
76
|
// linger around, because the browser has also navigated away and thus
|
77
|
// nobody ever calls completePendingRequest(). We put these Sessions into
|
78
|
// the graveyard and reap them after a while.
|
79
|
struct Graveyard *g;
|
80
|
check(g = malloc(sizeof(struct Graveyard)));
|
81
|
g->next = graveyard;
|
82
|
g->timeout = time(NULL) + AJAX_TIMEOUT;
|
83
|
g->sessionKey = strdup(session->sessionKey);
|
84
|
graveyard = g;
|
85
|
}
|
86
|
|
87
|
static void checkGraveyardInternal(int expireAll) {
|
88
|
if (!graveyard) {
|
89
|
return;
|
90
|
}
|
91
|
time_t timeout = time(NULL) - (expireAll ? 2*AJAX_TIMEOUT : 0);
|
92
|
for (struct Graveyard **g = &graveyard, *old = *g;
|
93
|
old; ) {
|
94
|
if (old->timeout < timeout) {
|
95
|
*g = old->next;
|
96
|
deleteFromHashMap(sessions, old->sessionKey);
|
97
|
free((char *)old->sessionKey);
|
98
|
free(old);
|
99
|
} else {
|
100
|
g = &old->next;
|
101
|
}
|
102
|
old = *g;
|
103
|
}
|
104
|
}
|
105
|
|
106
|
void checkGraveyard(void) {
|
107
|
checkGraveyardInternal(0);
|
108
|
}
|
109
|
|
110
|
void initSession(struct Session *session, const char *sessionKey,
|
111
|
Server *server, URL *url, const char *peerName) {
|
112
|
session->sessionKey = sessionKey;
|
113
|
session->server = server;
|
114
|
check(session->peerName = strdup(peerName));
|
115
|
session->connection = NULL;
|
116
|
session->http = NULL;
|
117
|
session->url = url;
|
118
|
session->done = 0;
|
119
|
session->pty = -1;
|
120
|
session->width = 0;
|
121
|
session->height = 0;
|
122
|
session->buffered = NULL;
|
123
|
session->len = 0;
|
124
|
}
|
125
|
|
126
|
struct Session *newSession(const char *sessionKey, Server *server, URL *url,
|
127
|
const char *peerName) {
|
128
|
struct Session *session;
|
129
|
check(session = malloc(sizeof(struct Session)));
|
130
|
initSession(session, sessionKey, server, url, peerName);
|
131
|
return session;
|
132
|
}
|
133
|
|
134
|
void destroySession(struct Session *session) {
|
135
|
if (session) {
|
136
|
free((char *)session->peerName);
|
137
|
free((char *)session->sessionKey);
|
138
|
deleteURL(session->url);
|
139
|
if (session->pty >= 0) {
|
140
|
NOINTR(close(session->pty));
|
141
|
}
|
142
|
}
|
143
|
}
|
144
|
|
145
|
void deleteSession(struct Session *session) {
|
146
|
destroySession(session);
|
147
|
free(session);
|
148
|
}
|
149
|
|
150
|
void abandonSession(struct Session *session) {
|
151
|
deleteFromHashMap(sessions, session->sessionKey);
|
152
|
}
|
153
|
|
154
|
void finishSession(struct Session *session) {
|
155
|
deleteFromHashMap(sessions, session->sessionKey);
|
156
|
}
|
157
|
|
158
|
void finishAllSessions(void) {
|
159
|
checkGraveyardInternal(1);
|
160
|
deleteHashMap(sessions);
|
161
|
}
|
162
|
|
163
|
static void destroySessionHashEntry(void *arg ATTR_UNUSED,
|
164
|
char *key ATTR_UNUSED, char *value) {
|
165
|
UNUSED(arg);
|
166
|
UNUSED(key);
|
167
|
|
168
|
deleteSession((struct Session *)value);
|
169
|
}
|
170
|
|
171
|
char *newSessionKey(void) {
|
172
|
int fd;
|
173
|
check((fd = NOINTR(open("/dev/urandom", O_RDONLY))) >= 0);
|
174
|
unsigned char buf[16];
|
175
|
check(NOINTR(read(fd, buf, sizeof(buf))) == sizeof(buf));
|
176
|
NOINTR(close(fd));
|
177
|
char *sessionKey;
|
178
|
check(sessionKey = malloc((8*sizeof(buf) + 5)/6 + 1));
|
179
|
char *ptr = sessionKey;
|
180
|
int count = 0;
|
181
|
int bits = 0;
|
182
|
for (unsigned i = 0;;) {
|
183
|
bits = (bits << 8) | buf[i];
|
184
|
count += 8;
|
185
|
drain:
|
186
|
while (count >= 6) {
|
187
|
*ptr++ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
|
188
|
"ghijklmnopqrstuvwxyz0123456789-/"
|
189
|
[(bits >> (count -= 6)) & 0x3F];
|
190
|
}
|
191
|
if (++i >= sizeof(buf)) {
|
192
|
if (count && i == sizeof(buf)) {
|
193
|
bits <<= 8;
|
194
|
count += 8;
|
195
|
goto drain;
|
196
|
} else {
|
197
|
break;
|
198
|
}
|
199
|
}
|
200
|
}
|
201
|
*ptr = '\000';
|
202
|
check(!sessions || !getFromHashMap(sessions, sessionKey));
|
203
|
return sessionKey;
|
204
|
}
|
205
|
|
206
|
struct Session *findCGISession(int *isNew, HttpConnection *http, URL *url,
|
207
|
const char *cgiSessionKey) {
|
208
|
*isNew = 1;
|
209
|
if (!sessions) {
|
210
|
sessions = newHashMap(destroySessionHashEntry, NULL);
|
211
|
}
|
212
|
const HashMap *args = urlGetArgs(url);
|
213
|
const char *sessionKey = getFromHashMap(args, "session");
|
214
|
struct Session *session= NULL;
|
215
|
if (cgiSessionKey &&
|
216
|
(!sessionKey || strcmp(cgiSessionKey, sessionKey))) {
|
217
|
// In CGI mode, we only ever allow exactly one session with a
|
218
|
// pre-negotiated key.
|
219
|
deleteURL(url);
|
220
|
} else {
|
221
|
if (sessionKey && *sessionKey) {
|
222
|
session = (struct Session *)getFromHashMap(sessions,
|
223
|
sessionKey);
|
224
|
}
|
225
|
if (session) {
|
226
|
*isNew = 0;
|
227
|
deleteURL(session->url);
|
228
|
session->url = url;
|
229
|
} else if (!cgiSessionKey && sessionKey && *sessionKey) {
|
230
|
*isNew = 0;
|
231
|
debug("Failed to find session: %s", sessionKey);
|
232
|
deleteURL(url);
|
233
|
} else {
|
234
|
// First contact. Create session, now.
|
235
|
check(sessionKey = cgiSessionKey ? strdup(cgiSessionKey)
|
236
|
: newSessionKey());
|
237
|
session = newSession(sessionKey, httpGetServer(http), url,
|
238
|
httpGetPeerName(http));
|
239
|
addToHashMap(sessions, sessionKey, (const char *)session);
|
240
|
debug("Creating a new session: %s", sessionKey);
|
241
|
}
|
242
|
}
|
243
|
return session;
|
244
|
}
|
245
|
|
246
|
struct Session *findSession(int *isNew, HttpConnection *http, URL *url) {
|
247
|
return findCGISession(isNew, http, url, NULL);
|
248
|
}
|
249
|
|
250
|
void iterateOverSessions(int (*fnc)(void *, const char *, char **), void *arg){
|
251
|
iterateOverHashMap(sessions, fnc, arg);
|
252
|
}
|
253
|
|
254
|
int numSessions(void) {
|
255
|
return getHashmapSize(sessions);
|
256
|
}
|