1 /* zxidses.c - Handwritten functions for SP session handling
2 * Copyright (c) 2010 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.
3 * Copyright (c) 2006-2009 Symlabs (symlabs@symlabs.com), All Rights Reserved.
4 * Author: Sampo Kellomaki (sampo@iki.fi)
5 * This is confidential unpublished proprietary source code of the author.
6 * NO WARRANTY, not even implied warranties. Contains trade secrets.
7 * Distribution prohibited unless authorized in writing.
8 * Licensed under Apache License 2.0, see file COPYING.
9 * $Id: zxidses.c,v 1.30 2010-01-08 02:10:09 sampo Exp $
10 *
11 * 12.8.2006, created --Sampo
12 * 16.1.2007, split from zxidlib.c --Sampo
13 * 5.2.2007, added EPR handling --Sampo
14 * 7.8.2008, added session lookup by NameID --Sampo
15 * 7.10.2008, added documentation --Sampo
16 * 12.2.2010, added pthread locking --Sampo
17 *
18 * See also: http://hoohoo.ncsa.uiuc.edu/cgi/interface.html (CGI specification)
19 */
20
21 #include "platform.h" /* for dirent.h */
22
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <string.h>
27 #include <stdio.h>
28 #include <errno.h>
29
30 #include "errmac.h"
31 #include "zxid.h"
32 #include "zxidutil.h"
33 #include "zxidconf.h"
34 #include "saml2.h"
35 #include "c/zx-ns.h"
36 #include "c/zx-e-data.h"
37
38 /* ============== Sessions ============== */
39
40 #define ZXID_MAX_SES (256) /* Just the session nid and path to assertion */
41
42 /*() When session is loaded, we only get the reference to assertion. This
43 * is to avoid parsing overhead when the assertion really is not needed.
44 * But when the assertion is needed, you have to call this function to load
45 * it from file (under /var/zxid/log/rely/EID/a7n/AID) and parse it. */
46
47 /* Called by: zxid_get_ses_idp, zxid_idp_loc, zxid_ses_to_pool, zxid_simple_ses_active_cf, zxid_snarf_eprs_from_ses, zxid_sp_loc, zxid_sp_mni_redir, zxid_sp_mni_soap, zxid_sp_slo_redir, zxid_sp_slo_soap */
zxid_get_ses_sso_a7n(zxid_conf * cf,zxid_ses * ses)48 int zxid_get_ses_sso_a7n(zxid_conf* cf, zxid_ses* ses)
49 {
50 struct zx_sa_EncryptedID_s* encid;
51 struct zx_str* ss;
52 struct zx_root_s* r;
53 struct zx_str* subj;
54 int gotall;
55 if (ses->a7n || ses->a7n11 || ses->a7n12) /* already in cache */
56 return 1;
57 if (!ses->sso_a7n_path) {
58 D("Session object does not have any SSO assertion sid(%s)", STRNULLCHK(ses->sid));
59 return 0;
60 }
61 ses->sso_a7n_buf = read_all_alloc(cf->ctx, "get_ses_sso_a7n", 1, &gotall, "%s", ses->sso_a7n_path);
62 if (!ses->sso_a7n_buf)
63 return 0;
64
65 DD("a7n(%s)", ses->sso_a7n_buf);
66 r = zx_dec_zx_root(cf->ctx, gotall, ses->sso_a7n_buf, "sso a7n");
67 if (!r) {
68 ERR("Failed to decode the sso assertion of session sid(%s) from path(%s), a7n data(%.*s)",
69 STRNULLCHK(ses->sid), ses->sso_a7n_path, gotall, ses->sso_a7n_buf);
70 return 0;
71 }
72
73 ses->a7n = r->Assertion;
74 ses->a7n11 = r->sa11_Assertion;
75 ses->a7n12 = r->ff12_Assertion;
76 if (ses->a7n && ses->a7n->Subject) {
77 ses->nameid = ses->a7n->Subject->NameID;
78 encid = ses->a7n->Subject->EncryptedID;
79 if (!ses->nameid && encid) {
80 ss = zxenc_privkey_dec(cf, encid->EncryptedData, encid->EncryptedKey);
81 if (!ss) {
82 ERR("Failed to decrypt EncryptedID. Most probably certificate-private key mismatch or metadata problem. Could also be corrupt message. %d", 0);
83 return 0;
84 }
85 r = zx_dec_zx_root(cf->ctx, ss->len, ss->s, "ses nid");
86 if (!r) {
87 ERR("Failed to parse EncryptedID buf(%.*s)", ss->len, ss->s);
88 return 0;
89 }
90 ses->nameid = r->NameID;
91 }
92 if (ses->nameid)
93 subj = ZX_GET_CONTENT(ses->nameid);
94 } else if (ses->a7n11)
95 subj = ZX_GET_CONTENT(ses->a7n11->AuthenticationStatement->Subject->NameIdentifier);
96 else if (ses->a7n12)
97 subj = ZX_GET_CONTENT(ses->a7n12->AuthenticationStatement->Subject->NameIdentifier);
98
99 if (subj) {
100 if (ses->nid) {
101 if (memcmp(ses->nid, subj->s, subj->len)) {
102 ERR("Session sid(%s), nid(%s), SSO assertion in path(%s) had different nid(%.*s). a7n data(%.*s)",
103 STRNULLCHK(ses->sid), ses->nid, ses->sso_a7n_path, subj->len, subj->s, gotall, ses->sso_a7n_buf);
104 }
105 } else
106 ses->nid = zx_str_to_c(cf->ctx, subj);
107 ses->tgt = ses->nid;
108 } else
109 ERR("Session sid(%s) SSO assertion in path(%s) did not have Name ID. a7n data(%.*s)",
110 STRNULLCHK(ses->sid), ses->sso_a7n_path, gotall, ses->sso_a7n_buf);
111 return 1;
112 }
113
114 /*() Get the IdP entity associated with the session. Generally this is figured out from
115 * the Issuer field of the SSO assertion that started the session. */
116
117 /* Called by: zxid_sp_mni_redir, zxid_sp_mni_soap, zxid_sp_slo_redir, zxid_sp_slo_soap */
zxid_get_ses_idp(zxid_conf * cf,zxid_ses * ses)118 zxid_entity* zxid_get_ses_idp(zxid_conf* cf, zxid_ses* ses)
119 {
120 if (!zxid_get_ses_sso_a7n(cf, ses))
121 return 0;
122 if (!ses->a7n || ! ses->a7n->Issuer) {
123 ERR("Session assertion is missing Issuer (the IdP) %p", ses->a7n);
124 return 0;
125 }
126 return zxid_get_ent_ss(cf, ZX_GET_CONTENT(ses->a7n->Issuer));
127 }
128
129 /*() Allocate memory for session object. Used with zxid_simple_cf_ses(). */
130
131 /* Called by: zxid_as_call, zxid_fetch_ses, zxid_mini_httpd_sso, zxid_mini_httpd_wsp */
zxid_alloc_ses(zxid_conf * cf)132 zxid_ses* zxid_alloc_ses(zxid_conf* cf)
133 {
134 zxid_ses* ses = ZX_ZALLOC(cf->ctx, zxid_ses);
135 LOCK_INIT(ses->mx);
136 return ses;
137 }
138
139 /*(i) Allocate memory and get session object from the filesystem, populating
140 * attributes to pool so they are available for use. You mus obtain session id
141 * from some source. */
142
143 /* Called by: zxcall_main */
zxid_fetch_ses(zxid_conf * cf,const char * sid)144 zxid_ses* zxid_fetch_ses(zxid_conf* cf, const char* sid)
145 {
146 zxid_ses* ses = zxid_alloc_ses(cf);
147 if (sid && sid[0])
148 if (!zxid_get_ses(cf, ses, sid)) {
149 ZX_FREE(cf->ctx, ses);
150 return 0;
151 }
152 zxid_ses_to_pool(cf, ses);
153 return ses;
154 }
155
156 /*() Get simple session object from the filesystem. This just gets the nameid
157 * and reference to the assertion. Use zxid_get_ses_sso_a7n() to actually
158 * load the assertion, if needed. Or zxid_ses_to_pool() if you need attributes
159 * as well. Returns 1 if session gotten, 0 if fail. */
160
161 /* Called by: chkuid x2, main x5, zxid_az_base_cf, zxid_az_cf, zxid_fetch_ses, zxid_find_ses, zxid_mini_httpd_sso x2, zxid_simple_cf_ses */
zxid_get_ses(zxid_conf * cf,zxid_ses * ses,const char * sid)162 int zxid_get_ses(zxid_conf* cf, zxid_ses* ses, const char* sid)
163 {
164 char* p;
165 int gotall;
166 #if 0
167 /* *** why would this set-cookie preservation code ever be needed? */
168 if (cf->ses_cookie_name && ses->setcookie
169 && !memcmp(cf->ses_cookie_name, ses->setcookie, strlen(cf->ses_cookie_name)))
170 p = ses->setcookie;
171 else
172 p = 0;
173 ZERO(ses, sizeof(zxid_ses));
174 ses->magic = ZXID_SES_MAGIC;
175 ses->setcookie = p;
176 #else
177 ZERO(ses, sizeof(zxid_ses));
178 ses->magic = ZXID_SES_MAGIC;
179 #endif
180
181 gotall = strlen(sid);
182 if (gotall != strspn(sid, safe_basis_64)) {
183 ERR("EVIL Session ID(%s)", sid);
184 return 0;
185 }
186
187 ses->sesbuf = ZX_ALLOC(cf->ctx, ZXID_MAX_SES);
188 gotall = read_all(ZXID_MAX_SES-1, ses->sesbuf, "get_ses", 1,
189 "%s" ZXID_SES_DIR "%s/.ses", cf->cpath, sid);
190 if (!gotall)
191 return 0;
192 D("ses(%.*s) len=%d sid(%s) sesptr=%p", gotall, ses->sesbuf, gotall, sid, ses);
193 ses->sesbuf[gotall] = 0;
194 DD("ses(%s)", ses->sesbuf);
195 ses->sid = zx_dup_cstr(cf->ctx, sid);
196
197 ses->nid = ses->sesbuf;
198 p = strchr(ses->sesbuf, '|');
199 if (!p) goto out;
200 *p++ = 0;
201
202 ses->sso_a7n_path = p;
203 p = strchr(p, '|');
204 if (!p) goto out;
205 *p++ = 0;
206
207 ses->sesix = p;
208 p = strchr(p, '|');
209 if (!p) goto out;
210 *p++ = 0;
211
212 ses->an_ctx = p;
213 p = strchr(p, '|');
214 if (!p) goto out;
215 *p++ = 0;
216
217 ses->uid = p;
218 p = strchr(p, '|');
219 if (!p) goto out;
220 *p++ = 0;
221
222 ses->an_instant = atol(p);
223
224 out:
225 D("GOT sesdir(%s" ZXID_SES_DIR "%s) uid(%s) nid(%s) sso_a7n_path(%s) sesix(%s) an_ctx(%s)", cf->cpath, ses->sid, STRNULLCHK(ses->uid), STRNULLCHK(ses->nid), STRNULLCHK(ses->sso_a7n_path), STRNULLCHK(ses->sesix), STRNULLCHK(ses->an_ctx));
226 return 1;
227 }
228
229 /*() Create new session object in file system. The assertion must have
230 * been created separately.
231 *
232 * cf:: Configuration object
233 * ses:: Pointer to previously allocated and populated session object
234 * return:: 1 upon success, 0 on failure. */
235
236 /* Called by: zxid_as_call_ses, zxid_pw_authn, zxid_sp_anon_finalize, zxid_sp_sso_finalize, zxid_wsp_validate_env */
zxid_put_ses(zxid_conf * cf,zxid_ses * ses)237 int zxid_put_ses(zxid_conf* cf, zxid_ses* ses)
238 {
239 char dir[ZXID_MAX_BUF];
240 char* buf;
241 struct zx_str* ss;
242
243 if (ses->sid) {
244 if (strlen(ses->sid) != strspn(ses->sid, safe_basis_64)) {
245 ERR("EVIL Session ID(%s)", ses->sid);
246 return 0;
247 }
248 } else { /* New session */
249 ss = zxid_mk_id(cf, "S", ZXID_ID_BITS);
250 ses->sid = ss->s;
251 ZX_FREE(cf->ctx, ss);
252 }
253
254 name_from_path(dir, sizeof(dir), "%s" ZXID_SES_DIR "%s", cf->cpath, ses->sid);
255 if (MKDIR(dir, 0777) && errno != EEXIST) {
256 ERR("Creating session directory(%s) failed: %d %s; euid=%d egid=%d", dir, errno, STRERROR(errno), geteuid(), getegid());
257 zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "S", "EFILE", dir, "mkdir fail, permissions?");
258 return 0;
259 }
260
261 buf = ZX_ALLOC(cf->ctx, ZXID_MAX_SES);
262 if (!write_all_path_fmt("put_ses", ZXID_MAX_SES, buf,
263 "%s" ZXID_SES_DIR "%s/.ses", cf->cpath, ses->sid,
264 "%s|%s|%s|%s|%s|%d|",
265 STRNULLCHK(ses->nid),
266 STRNULLCHK(ses->sso_a7n_path),
267 STRNULLCHK(ses->sesix),
268 STRNULLCHK(ses->an_ctx),
269 STRNULLCHK(ses->uid),
270 ses->an_instant)) {
271 zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "S", "EFILE", ses->sid, "writing ses fail, permissions?");
272 ZX_FREE(cf->ctx, buf);
273 return 0;
274 }
275 ZX_FREE(cf->ctx, buf);
276 D("SESSION CREATED sid(%s)", STRNULLCHK(ses->sid));
277 return 1;
278 }
279
280 /*() Delete, or archive, session object from file system. Assertion, if any,
281 * is not deleted. This is called upon explicit logout events. However, in reality
282 * many sessions are simply abandoned, thus a deploying site should implement
283 * some mechanism, such as a cron(8) job to remove or archive expired sessions. */
284
285 /* Called by: zxid_idp_dispatch, zxid_idp_slo_do, zxid_mgmt x3, zxid_simple_ses_active_cf x3, zxid_sp_dispatch, zxid_sp_slo_do */
zxid_del_ses(zxid_conf * cf,zxid_ses * ses)286 int zxid_del_ses(zxid_conf* cf, zxid_ses* ses)
287 {
288 char old[ZXID_MAX_BUF];
289 char new[ZXID_MAX_BUF];
290 int len;
291 if (!ses || !ses->sid) {
292 D("No session in place. %p", ses);
293 return 0;
294 }
295
296 if (ses->sid) {
297 len = strlen(ses->sid);
298 if (len != strspn(ses->sid, safe_basis_64)) {
299 ERR("EVIL Session ID(%s)", ses->sid);
300 return 0;
301 }
302 }
303
304 if (!name_from_path(old, sizeof(old), "%s" ZXID_SES_DIR "%s", cf->cpath, ses->sid))
305 return 0;
306
307 if (cf->ses_arch_dir) {
308 if (!name_from_path(new, sizeof(new), "%s%s", cf->ses_arch_dir, ses->sid))
309 return 0;
310 if (rename(old,new) == -1) {
311 perror("rename to archieve session");
312 ERR("Deleting session by renaming failed old(%s) new(%s), euid=%d egid=%d", old, new, geteuid(), getegid());
313 zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "S", "EFILE", old, "ses arch rename, permissions?");
314 return 0;
315 }
316 } else {
317 DIR* dir;
318 struct dirent * de;
319
320 dir = opendir(old);
321 if (!dir) {
322 perror("opendir to delete session");
323 ERR("Deleting session by opendir failed old(%s), euid=%d egid=%d", old, geteuid(), getegid());
324 zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "S", "EFILE", old, "ses del opendir, permissions?");
325 return 0;
326 }
327 while (de = readdir(dir)) {
328 if (de->d_name[0] == '.' && ONE_OF_2(de->d_name[1], '.', 0)) /* skip . and .. */
329 continue;
330 if (!name_from_path(new, sizeof(new), "%s" ZXID_SES_DIR "%s/%s", cf->cpath, ses->sid, de->d_name))
331 return 0;
332 if (unlink(new) == -1) {
333 perror("unlink to delete files in session");
334 ERR("Deleting session file(%s) by unlink failed, euid=%d egid=%d", new, geteuid(), getegid());
335 zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "S", "EFILE", new, "ses unlink, permissions?");
336 return 0;
337 }
338 }
339 closedir(dir);
340 if (rmdir(old) == -1) {
341 perror("rmdir to delete session");
342 ERR("Deleting session by rmdir failed old(%s), euid=%d egid=%d", old, geteuid(), getegid());
343 zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "S", "EFILE", old, "ses rmdir, permissions?");
344 return 0;
345 }
346 }
347 return 1;
348 }
349
350 /*() Find a session object by a number of criteria.
351 *
352 * cf:: ZXID configuration object
353 * ses:: Result parameter. Must have been previously allocated. This will be modified
354 * to match the found session.
355 * ses_ix:: Session Index, usually from SSO asserion or from SLO request. If not
356 * supplied (i.e. 0), the ~nid~ MUST be supplied and will be used as sole basis for
357 * deleting the session.
358 * nid:: The idp assigned Name ID associated with the session. If supplied as 0, then
359 * ~ses_ix~ MUST be supplied and will be used to determine which session is deleted.
360 * return:: 0 unknown session or error, 1 session found successfully */
361
362 /* Called by: zxid_idp_slo_do, zxid_sp_slo_do */
zxid_find_ses(zxid_conf * cf,zxid_ses * ses,struct zx_str * ses_ix,struct zx_str * nid)363 int zxid_find_ses(zxid_conf* cf, zxid_ses* ses, struct zx_str* ses_ix, struct zx_str* nid)
364 {
365 char buf[ZXID_MAX_BUF];
366 DIR* dir;
367 struct dirent * de;
368
369 D("ses_ix(%.*s) nid(%.*s)", ses_ix?ses_ix->len:0, ses_ix?ses_ix->s:"", nid?nid->len:0, nid?nid->s:"");
370
371 if (!name_from_path(buf, sizeof(buf), "%s" ZXID_SES_DIR, cf->cpath))
372 return 0;
373
374 dir = opendir(buf);
375 if (!dir) {
376 perror("opendir to find session");
377 ERR("Finding session by opendir failed buf(%s), euid=%d egid=%d", buf, geteuid(), getegid());
378 return 0;
379 }
380 while (de = readdir(dir)) {
381 if (de->d_name[0] == '.' && ONE_OF_2(de->d_name[1], '.', 0)) /* skip . and .. */
382 continue;
383 if (zxid_get_ses(cf, ses, de->d_name)) {
384 if (nid && (!ses->nid || memcmp(ses->nid, nid->s, nid->len) || ses->nid[nid->len]))
385 continue;
386 if (ses_ix && (!ses->sesix || memcmp(ses->sesix, ses_ix->s, ses_ix->len) || ses->sesix[ses_ix->len]))
387 continue;
388 return 1;
389 }
390 }
391 closedir(dir);
392 ZERO(ses, sizeof(zxid_ses));
393 return 0;
394 }
395
396 /* EOF -- zxidses.c */
397