1 /* zxidsimp.c - Handwritten zxid_simple() API
2 * Copyright (c) 2012-2016 Synergetics NV (sampo@synergetics.be), All Rights Reserved.
3 * Copyright (c) 2009-2011 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.
4 * Copyright (c) 2007-2009 Symlabs (symlabs@symlabs.com), All Rights Reserved.
5 * Author: Sampo Kellomaki (sampo@iki.fi)
6 * This is confidential unpublished proprietary source code of the author.
7 * NO WARRANTY, not even implied warranties. Contains trade secrets.
8 * Distribution prohibited unless authorized in writing.
9 * Licensed under Apache License 2.0, see file COPYING.
10 * $Id: zxidsimp.c,v 1.64 2010-01-08 02:10:09 sampo Exp $
11 *
12 * 17.1.2007, created --Sampo
13 * 2.2.2007, improved the LDIF return --Sampo
14 * 9.3.2008, refactored the logged in and need login cases to subroutines --Sampo
15 * 7.10.2008, added documentation --Sampo
16 * 4.9.2009, added attribute broker and PEP functionality --Sampo
17 * 31.5.2010, moved local PEP and attribute broker functionality to zxidpep.c --Sampo
18 * 7.9.2010, tweaked the az requests to separate ses az from resource az --Sampo
19 * 22.9.2010, added People Service invitation resolution --Sampo
20 * 10.12.2011, added OAuth2, OpenID Connect, and UMA support --Sampo
21 * 30.9.2012, added PTM support --Sampo
22 * 13.2.2013, added WD option --Sampo
23 * 14.3.2013 added language/skin dependent templates --Sampo
24 * 15.4.2013, added fflush(3) here and there to accommodate broken atexit() --Sampo
25 * 17.11.2013, move redir_to_content feature to zxid_simple() --Sampo
26 * 20.11.2013, move defaultqs feature feature to zxid_simple() --Sampo
27 * 14.2.2014, added redirafter feature for local IdP logins (e.g. zxidatsel.pl) --Sampo
28 * 1.4.2015, fixed skin based template path in case it does not have directory --Sampo
29 *
30 * Login button abbreviations
31 * A2 = SAML 2.0 Artifact Profile
32 * P2 = SAML 2.0 POST Profile
33 * S2 = SAML 2.0 POST Simple Sign
34 * A12 = Liberty ID-FF 1.2 Artifact Profile
35 * P12 = Liberty ID-FF 1.2 POST Profile
36 * A1 = Bare SAML 1.x Artifact Profile
37 * P1 = Base SAML 1.x POST Profile
38 * A0 = WS-Federation Artifact Profile
39 * P0 = WS-Federation POST Profile
40 */
41
42 #include "platform.h" /* needed on Win32 for pthread_mutex_lock() et al. */
43
44 #include <memory.h>
45 #include <string.h>
46 #include <stdlib.h>
47
48 #include "errmac.h"
49 #include "zx.h"
50 #include "zxid.h"
51 #include "zxidutil.h"
52 #include "zxidconf.h"
53 #include "zxidpriv.h"
54 #include "wsf.h"
55 #include "c/zxidvers.h"
56 #include "c/zx-md-data.h"
57
58 /*#include "dietstdio.h"*/
59
60 /*() Convert configuration string ~conf~ to configuration object ~cf~.
61 * cf:: Configuration object, already allocated
62 * conf_len:: length of conf string, or -1 to use strlen(conf)
63 * conf:: Configuration string in query string format
64 * See also: zxid_conf_to_cf() */
65
66 /* Called by: dirconf, main x2, zxid_az, zxid_az_base, zxid_fed_mgmt_len, zxid_idp_list_len, zxid_idp_select_len, zxid_new_conf_to_cf, zxid_simple_len */
zxid_conf_to_cf_len(zxid_conf * cf,int conf_len,const char * conf)67 int zxid_conf_to_cf_len(zxid_conf* cf, int conf_len, const char* conf)
68 {
69 #if 1
70 if (!cf->ctx) {
71 cf->ctx = zx_init_ctx();
72 if (!cf->ctx) {
73 ERR("Failed to alloc zx_ctx %d",0);
74 exit(2);
75 }
76 }
77 zxid_init_conf(cf, ZXID_PATH); /* Hardwired conf from zxidconf.h, and /var/zxid/zxid.conf */
78 #ifdef USE_CURL
79 //INFO("%lx == %lx? eq=%d sizeof(cf->curl_mx.thr)=%ld a=%lx sizeof(a)=%ld sizeof(pthread_t)=%ld sizeof(int)=%ld sizeof(long)=%ld sizeof(long long)=%ld sizeof(char*)=%ld", cf->curl_mx.thr, pthread_self(), (long)cf->curl_mx.thr == (long)pthread_self(), sizeof(cf->curl_mx.thr), a, sizeof(a), sizeof(pthread_t), sizeof(int), sizeof(long), sizeof(long long), sizeof(char*));
80 LOCK(cf->curl_mx, "curl init");
81 cf->curl = curl_easy_init();
82 if (!cf->curl) {
83 ERR("Failed to initialize libcurl %d",0);
84 UNLOCK(cf->curl_mx, "curl init");
85 exit(2);
86 }
87 UNLOCK(cf->curl_mx, "curl init");
88 #endif
89 #else
90 zxid_init_conf_ctx(cf, ZXID_PATH /* N.B. Often this is overridden. */);
91 #endif
92 #if defined(ZXID_CONF_FILE_ENA) || defined(ZXID_CONF_FLAG)
93 /* The usual case is that config file processing is compiled in, so this code happens. */
94 {
95 char* buf;
96 char* cc;
97 int len;
98 if (conf_len == -1) {
99 if (conf)
100 conf_len = strlen(conf);
101 else
102 conf_len = 0;
103 }
104
105 if (!conf || conf_len < 5 || memcmp(conf, "PATH=", 5)) {
106 /* No conf, or conf does not start by PATH: read from file default values */
107 buf = read_all_alloc(cf->ctx, "-conf_to_cf", 1, &len, "%s" ZXID_CONF_FILE, cf->cpath);
108 if (!buf || !len)
109 buf = read_all_alloc(cf->ctx, "-conf_to_cf", 1, &len, "%szxid.conf", cf->cpath);
110 if (buf && len)
111 zxid_parse_conf_raw(cf, len, buf);
112 }
113
114 buf = getenv(ZXID_ENV_PREFIX "PRE_CONF");
115 D("Check " ZXID_ENV_PREFIX "PRE_CONF(%s)", STRNULLCHKD(buf));
116 if (buf) {
117 /* Copy the conf string because we are going to modify it in place. */
118 D("Applying " ZXID_ENV_PREFIX "PRE_CONF(%s)", buf);
119 len = strlen(buf);
120 cc = ZX_ALLOC(cf->ctx, len+1);
121 memcpy(cc, buf, len);
122 cc[len] = 0;
123 zxid_parse_conf_raw(cf, len, cc);
124 }
125
126 if (conf && conf_len) {
127 /* Copy the conf string because we are going to modify it in place. */
128 cc = ZX_ALLOC(cf->ctx, conf_len+1);
129 memcpy(cc, conf, conf_len);
130 cc[conf_len] = 0;
131 zxid_parse_conf_raw(cf, conf_len, cc);
132 }
133
134 buf = getenv(ZXID_ENV_PREFIX "CONF");
135 if (buf) {
136 /* Copy the conf string because we are going to modify it in place. */
137 D("Applying " ZXID_ENV_PREFIX "CONF(%s)", buf);
138 len = strlen(buf);
139 cc = ZX_ALLOC(cf->ctx, len+1);
140 memcpy(cc, buf, len);
141 cc[len] = 0;
142 zxid_parse_conf_raw(cf, len, cc);
143 }
144 }
145 #endif
146 return 0;
147 }
148
149 /*() Create new ZXID configuration object given configuration string and
150 * possibly configuration file.
151 *
152 * zxid_new_conf_to_cf() parses first the default config file, then the string (i.e. string
153 * can override config file). However, if the string contains PATH specification,
154 * then the config file is reread from (presumably new) location and overrides
155 * eariler config.
156 *
157 * conf:: Configuration string
158 * return:: Configuration object */
159
160 /* Called by: a7n_test, handle_request, main x6, opt x2, test_receipt, ws_validations, zxbusd_main, zxbuslist_main, zxbustailf_main, zxcall_main, zxcot_main, zxidwspcgi_main x2 */
zxid_new_conf_to_cf(const char * conf)161 zxid_conf* zxid_new_conf_to_cf(const char* conf)
162 {
163 zxid_conf* cf = malloc(sizeof(zxid_conf)); /* *** direct use of malloc */
164 D("malloc %p size=%d", cf, (int)sizeof(zxid_conf));
165 if (!cf) {
166 ERR("out-of-memory %d", (int)sizeof(zxid_conf));
167 exit(1); /* *** perhaps too severe! */
168 }
169 cf = ZERO(cf, sizeof(zxid_conf));
170 zxid_conf_to_cf_len(cf, -1, conf);
171 return cf;
172 }
173
174 /* ------------ zxid_fed_mgmt() ------------ */
175
176 /*(i) Generate Single Logout button and possibly other federation management
177 * buttons for use in logged in state of the app HTML GUI.
178 *
179 * Either outputs the management screen to stdout or returns string of HTML (at specified
180 * automation level). If res_len is supplied, the string length is returned in res_len.
181 * Otherwise you can just run strlen() on return value.
182 *
183 * N.B. More complete documentation is available in <<link: zxid-simple.pd>> (*** fixme) */
184
185 /* Called by: zxid_fed_mgmt_len, zxid_simple_ses_active_cf */
zxid_fed_mgmt_cf(zxid_conf * cf,int * res_len,int sid_len,char * sid,int auto_flags)186 char* zxid_fed_mgmt_cf(zxid_conf* cf, int* res_len, int sid_len, char* sid, int auto_flags)
187 {
188 char* res;
189 struct zx_str* ss;
190 struct zx_str* ss2;
191 int slen = sid_len == -1 && sid ? strlen(sid) : sid_len;
192 if (auto_flags & ZXID_AUTO_DEBUG) zxid_set_opt(cf, 1, 3);
193
194 if (cf->log_level>1)
195 zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "W", "MGMT", 0, "sid(%.*s)", sid_len, STRNULLCHK(sid));
196
197 if ((auto_flags & ZXID_AUTO_FORMT) && (auto_flags & ZXID_AUTO_FORMF))
198 ss = zx_strf(cf->ctx,
199 "%s"
200 #ifdef ZXID_USE_POST
201 "<form method=post action=\"%s?o=P\">\n"
202 #else
203 "<form method=get action=\"%s\">\n"
204 #endif
205 "<input type=hidden name=s value=\"%.*s\">\n"
206 "%s%s\n"
207 "</form>%s%s%s%s",
208 cf->mgmt_start,
209 cf->burl,
210 slen, STRNULLCHK(sid),
211 cf->mgmt_logout, cf->mgmt_defed,
212 cf->mgmt_footer, zxid_version_str(), STRNULLCHK(cf->dbg), cf->mgmt_end);
213 else if (auto_flags & ZXID_AUTO_FORMT)
214 ss = zx_strf(cf->ctx,
215 #ifdef ZXID_USE_POST
216 "<form method=post action=\"%s?o=P\">\n"
217 #else
218 "<form method=get action=\"%s\">\n"
219 #endif
220 "<input type=hidden name=s value=\"%.*s\">"
221 "%s%s\n"
222 "</form>",
223 cf->burl,
224 slen, STRNULLCHK(sid),
225 cf->mgmt_logout, cf->mgmt_defed);
226 else if (auto_flags & ZXID_AUTO_FORMF)
227 ss = zx_strf(cf->ctx,
228 "<input type=hidden name=s value=\"%.*s\">"
229 "%s%s\n",
230 slen, STRNULLCHK(sid),
231 cf->mgmt_logout, cf->mgmt_defed);
232 else
233 ss = zx_dup_str(cf->ctx, "");
234
235 #if 0
236 printf("COOKIE: foo\r\n");
237 if (qs) printf("QS(%s)\n", qs);
238 if (got>0) printf("GOT(%.*s)\n", got, buf);
239 if (cgi->err) printf("<p><font color=red><i>%s</i></font></p>\n", cgi->err);
240 if (cgi->msg) printf("<p><i>%s</i></p>\n", cgi->msg);
241 printf("User:<input name=user> PW:<input name=pw type=password>");
242 printf("<input name=login value=\" Login \" type=submit>");
243 printf("<h3>Technical options (typically hidden fields on production site)</h3>\n");
244 printf("sid(%s) nid(%s) <a href=\"zxid?s=%s\">Reload</a>", ses->sid, ses->nid, ses->sid);
245 if (cgi->dbg) printf("<p><form><textarea cols=100 row=10>%s</textarea></form>\n", cgi->dbg);
246 #endif
247
248 if (auto_flags & ZXID_AUTO_MGMTC && auto_flags & ZXID_AUTO_MGMTH) { /* Both H&C: CGI */
249 fprintf(stdout, "Content-Type: text/html" CRLF "Content-Length: %d" CRLF2 "%.*s",
250 ss->len, ss->len, ss->s);
251 fflush(stdout);
252 zx_str_free(cf->ctx, ss);
253 return 0;
254 }
255
256 if (auto_flags & (ZXID_AUTO_MGMTC | ZXID_AUTO_MGMTH)) {
257 if (auto_flags & ZXID_AUTO_MGMTH) { /* H only: return both H and C */
258 D("With headers 0x%x", auto_flags);
259 ss2 = zx_strf(cf->ctx, "Content-Type: text/html" CRLF "Content-Length: %d" CRLF2 "%.*s",
260 ss->len, ss->len, ss->s);
261 zx_str_free(cf->ctx, ss);
262 } else {
263 D("No headers 0x%x", auto_flags);
264 ss2 = ss; /* C only */
265 }
266 res = ss2->s;
267 DD("res(%s)", res);
268 if (res_len)
269 *res_len = ss2->len;
270 ZX_FREE(cf->ctx, ss2);
271 return res;
272 }
273 D("m(%.*s)", ss->len, ss->s);
274 zx_str_free(cf->ctx, ss);
275 if (res_len)
276 *res_len = 1;
277 return zx_dup_cstr(cf->ctx, "m"); /* Neither H nor C */
278 }
279
280 /* Called by: zxid_fed_mgmt */
zxid_fed_mgmt_len(int conf_len,char * conf,int * res_len,char * sid,int auto_flags)281 char* zxid_fed_mgmt_len(int conf_len, char* conf, int* res_len, char* sid, int auto_flags) {
282 zxid_conf cf;
283 zxid_conf_to_cf_len(&cf, conf_len, conf);
284 return zxid_fed_mgmt_cf(&cf, 0, -1, sid, auto_flags);
285 }
286
287 /* Called by: */
zxid_fed_mgmt(char * conf,char * sid,int auto_flags)288 char* zxid_fed_mgmt(char* conf, char* sid, int auto_flags) {
289 return zxid_fed_mgmt_len(-1, conf, 0, sid, auto_flags);
290 }
291
292 /* ------------ zxid_an_page() ------------ */
293
294 #define BBMATCH(k, key, lim) (sizeof(k)-1 == (lim)-(key) && !memcmp((k), (key), sizeof(k)-1))
295
296 /*() Bang-bang expansions (!!VAR) understood in the templates. */
297
298 /* Called by: zxid_template_page_cf */
zxid_map_bangbang(zxid_conf * cf,zxid_cgi * cgi,const char * key,const char * lim,int auto_flags)299 static const char* zxid_map_bangbang(zxid_conf* cf, zxid_cgi* cgi, const char* key, const char* lim, int auto_flags)
300 {
301 switch (*key) {
302 case 'A':
303 if (BBMATCH("ACTION_URL", key, lim)) return cgi->action_url;
304 break;
305 case 'B':
306 if (BBMATCH("BURL", key, lim)) return cf->burl;
307 break;
308 case 'D':
309 if (BBMATCH("DBG", key, lim)) return cgi->dbg;
310 break;
311 case 'E':
312 if (BBMATCH("EID", key, lim)) return zxid_my_ent_id_cstr(cf);
313 if (BBMATCH("ERR", key, lim)) return cgi->err;
314 break;
315 case 'F':
316 if (BBMATCH("FR", key, lim)) return zxid_unbase64_inflate(cf->ctx, -2, cgi->rs, 0);
317 break;
318 case 'I':
319 if (BBMATCH("IDP_LIST", key, lim)) return zxid_idp_list_cf_cgi(cf, cgi, 0, auto_flags);
320 if (BBMATCH("IDP_POPUP", key, lim)) {
321 cf->idp_list_meth = ZXID_IDP_LIST_POPUP;
322 return zxid_idp_list_cf_cgi(cf, cgi, 0, auto_flags);
323 }
324 if (BBMATCH("IDP_BUTTON", key, lim)) {
325 cf->idp_list_meth = ZXID_IDP_LIST_BUTTON;
326 return zxid_idp_list_cf_cgi(cf, cgi, 0, auto_flags);
327 }
328 if (BBMATCH("IDP_BRAND", key, lim)) {
329 cf->idp_list_meth = ZXID_IDP_LIST_BRAND;
330 return zxid_idp_list_cf_cgi(cf, cgi, 0, auto_flags);
331 }
332 break;
333 case 'M':
334 if (BBMATCH("MSG", key, lim)) return cgi->msg;
335 break;
336 case 'U':
337 if (BBMATCH("URL", key, lim)) return cf->burl;
338 break;
339 case 'R':
340 if (BBMATCH("RS", key, lim)) return cgi->rs;
341 break;
342 case 'S':
343 if (BBMATCH("SKIN", key, lim)) return cgi->skin;
344 if (BBMATCH("SIG", key, lim)) return cgi->sig;
345 if (BBMATCH("SP_EID", key, lim)) return cgi->sp_eid;
346 if (BBMATCH("SP_DPY_NAME", key, lim)) return cgi->sp_dpy_name;
347 if (BBMATCH("SP_BUTTON_URL", key, lim)) return cgi->sp_button_url;
348 if (BBMATCH("SSOREQ", key, lim)) return cgi->ssoreq;
349 if (BBMATCH("SAML_ART", key, lim)) return cgi->saml_art;
350 if (BBMATCH("SAML_RESP", key, lim)) return cgi->saml_resp;
351 break;
352 case 'V':
353 if (BBMATCH("VERSION", key, lim)) return zxid_version_str();
354 break;
355 case 'Z':
356 if (BBMATCH("ZXAPP", key, lim)) return cgi->zxapp;
357 break;
358 }
359 D("Unmatched bangbang key(%.*s), taken as empty.", ((int)(lim-key)), key);
360 return 0;
361 }
362
363 /*() Expand a template. Only selected !!VAR expansions supported. No IFs or loops. */
364
365 /* Called by: zxid_idp_select_zxstr_cf_cgi, zxid_saml2_post_enc, zxid_simple_idp_show_an, zxid_simple_show_err */
zxid_template_page_cf(zxid_conf * cf,zxid_cgi * cgi,const char * templ_path,const char * default_templ,int size_hint,int auto_flags)366 struct zx_str* zxid_template_page_cf(zxid_conf* cf, zxid_cgi* cgi, const char* templ_path, const char* default_templ, int size_hint, int auto_flags)
367 {
368 const char* templ = 0;
369 const char* tp;
370 const char* tq;
371 const char* p;
372 char* pp;
373 struct zx_str* ss;
374 int len;
375
376 if (cgi->skin && *cgi->skin) {
377 for (pp = cgi->skin; *pp; ++pp)
378 if (*pp == '/') { /* Squash to avoid accessing files beyond webroot */
379 ERR("Illegal character 0x%x (%c) in skin CGI variable (possible attack or misconfiguration)", *pp, *pp);
380 *pp = '_';
381 }
382
383 /* scan for end of path component, if any. */
384 for (p = templ_path + strlen(templ_path)-1;
385 p >= templ_path && !ONE_OF_2(*p, '/', '\\');
386 --p);
387 if (p < templ_path) /* there was no directory component */
388 templ = read_all_alloc(cf->ctx, "templ", 1, 0, "%s/%s", cgi->skin, templ_path);
389 else
390 templ = read_all_alloc(cf->ctx, "templ", 1, 0, "%.*s/%s%s",
391 p-templ_path, templ_path, cgi->skin, p);
392 D("Tried to read from skin(%s) templ_path(%s) %p", cgi->skin, templ_path, templ);
393 }
394
395 if (!templ)
396 templ = read_all_alloc(cf->ctx, "templ", 1, 0, "%s", templ_path);
397 if (!templ) {
398 D("Template at path(%s) not found. Using default template.", templ_path);
399 templ = default_templ;
400 }
401 while (1) { /* Try rendering, iterate if expansion is needed. */
402 tp = templ;
403 ss = zx_new_len_str(cf->ctx, strlen(tp) + size_hint);
404 for (pp = ss->s; *tp && pp < ss->s + ss->len; ) {
405 if (tp[0] == '!' && tp[1] == '!' && AZaz_(tp[2])) {
406 for (tq = tp+=2; AZaz_(*tp); ++tp) ;
407 tq = zxid_map_bangbang(cf, cgi, tq, tp, auto_flags);
408 if (!tq || !*tq)
409 continue;
410 len = strlen(tq);
411 if (pp + len >= ss->s + ss->len) {
412 pp += len;
413 break;
414 }
415 memcpy(pp, tq, len);
416 pp += len;
417 continue;
418 }
419 *pp++ = *tp++;
420 }
421 if (pp >= ss->s + ss->len) {
422 INFO("Expansion of template does not fit in %d. Enlarging buffer.", ss->len);
423 size_hint += size_hint; /* Double it */
424 continue;
425 }
426 break;
427 }
428 if (templ && templ != default_templ)
429 ZX_FREE(cf->ctx, (void*)templ);
430 *pp = 0;
431 ss->len = pp - ss->s;
432 return ss;
433 }
434
435 /* ------------ zxid_idp_list() ------------ */
436
437 /*(i) Generate IdP selection buttons (Login buttons) for the IdPs that are
438 * members of our Circle of Trust (CoT). This can be used as component for
439 * developing your application specific (HTML) login screen.
440 *
441 * N.B. More complete documentation is available in <<link: zxid-simple.pd>> (*** fixme) */
442
443 /* Called by: zxid_idp_list_cf, zxid_idp_select_zxstr_cf_cgi, zxid_map_bangbang x4 */
zxid_idp_list_cf_cgi(zxid_conf * cf,zxid_cgi * cgi,int * res_len,int auto_flags)444 char* zxid_idp_list_cf_cgi(zxid_conf* cf, zxid_cgi* cgi, int* res_len, int auto_flags)
445 {
446 int i;
447 char* s;
448 char mark[32];
449 struct zx_str* ss;
450 struct zx_str* dd;
451 zxid_entity* idp;
452 zxid_entity* idp_cdc;
453 if (auto_flags & ZXID_AUTO_DEBUG) zxid_set_opt(cf, 1, 3);
454 idp = zxid_load_cot_cache(cf);
455 if (!idp) {
456 D("No IdP's found %p", res_len);
457 if (res_len)
458 *res_len = 0;
459 return "";
460 }
461
462 #if 0
463 if ((auto_flags & ZXID_AUTO_FORMT) && (auto_flags & ZXID_AUTO_FORMF))
464 ss = zx_dup_str(cf->ctx, "<h3>Login Using Known IdP</h3>\n");
465 else
466 #endif
467 ss = zx_dup_str(cf->ctx, "");
468
469 if (cf->idp_list_meth == ZXID_IDP_LIST_POPUP) {
470 dd = zx_strf(cf->ctx, "%.*s<select name=d>\n", ss->len, ss->s);
471 zx_str_free(cf->ctx, ss);
472 ss = dd;
473 }
474
475 D("Starting IdP list processing... %p", idp);
476 for (; idp; idp = idp->n) {
477 if (!idp->ed->IDPSSODescriptor)
478 continue;
479
480 mark[0] = 0;
481 if (cgi) { /* Was IdP recommended in IdP list supplied via CDC? See zxid_cdc_check() */
482 for (idp_cdc = cgi->idp_list, i=1;
483 idp_cdc && idp_cdc != idp;
484 idp_cdc = idp_cdc->n_cdc, ++i);
485 if (cf->cdc_choice == ZXID_CDC_CHOICE_UI_ONLY_CDC && cgi->idp_list && !idp_cdc)
486 continue;
487 if (idp_cdc) {
488 snprintf(mark, sizeof(mark), " CDC %d", i);
489 mark[sizeof(mark)-1] = 0;
490 }
491 }
492
493 switch (cf->idp_list_meth) {
494 default:
495 ERR("Unsupported IDP_LIST_METH=%d, reverting to popup.", cf->idp_list_meth);
496 cf->idp_list_meth = ZXID_IDP_LIST_POPUP;
497 /* fall thru */
498 case ZXID_IDP_LIST_POPUP:
499 dd = zx_strf(cf->ctx, "%.*s"
500 "<option class=zxidplistopt value=\"%s\"> %s (%s) %s\n",
501 ss->len, ss->s, idp->eid, STRNULLCHK(idp->dpy_name), idp->eid, mark);
502 break;
503 case ZXID_IDP_LIST_BUTTON:
504 if (cf->show_tech) {
505 dd = zx_strf(cf->ctx, "%.*s"
506 "<input type=submit class=zxidplistbut name=\"l0%s\" value=\" Login with %s (%s)\">\n"
507 "<input type=submit class=zxidplistbut name=\"l1%s\" value=\" Login with %s (%s) (A2) \">\n"
508 "<input type=submit class=zxidplistbut name=\"l2%s\" value=\" Login with %s (%s) (P2) \">\n"
509 "<input type=submit class=zxidplistbut name=\"l5%s\" value=\" Login with %s (%s) (S2) \">\n"
510 "<input type=submit class=zxidplistbut name=\"l8%s\" value=\" Login with %s (%s) (O2C) \">"
511 "<input type=submit class=zxidplistbut name=\"l9%s\" value=\" Login with %s (%s) (O2I) \">"
512 "%s<br>\n",
513 ss->len, ss->s,
514 idp->eid, STRNULLCHK(idp->dpy_name), idp->eid,
515 idp->eid, STRNULLCHK(idp->dpy_name), idp->eid,
516 idp->eid, STRNULLCHK(idp->dpy_name), idp->eid,
517 idp->eid, STRNULLCHK(idp->dpy_name), idp->eid,
518 idp->eid, STRNULLCHK(idp->dpy_name), idp->eid,
519 idp->eid, STRNULLCHK(idp->dpy_name), idp->eid,
520 mark);
521 } else {
522 dd = zx_strf(cf->ctx, "%.*s"
523 "<input type=submit name=\"l0%s\" value=\" Login with %s (%s) \">%s<br>\n",
524 ss->len, ss->s, idp->eid, STRNULLCHK(idp->dpy_name), idp->eid, mark);
525 }
526 break;
527 case ZXID_IDP_LIST_BRAND:
528 if (idp->button_url) { /* see symlabs-saml-displayname-2008.pdf */
529 dd = zx_strf(cf->ctx, "%.*s"
530 "<input type=image name=\"l0%s\" src=\"%s\" title=\"%s (%s)\">%s<br>\n",
531 ss->len, ss->s, idp->eid, idp->button_url, STRNULLCHK(idp->dpy_name), idp->eid, mark);
532 } else {
533 dd = zx_strf(cf->ctx, "%.*s"
534 "<input type=submit name=\"l0%s\" value=\" %s (%s) \">%s<br>\n",
535 ss->len, ss->s, idp->eid, STRNULLCHK(idp->dpy_name), idp->eid, mark);
536 }
537 break;
538 }
539 zx_str_free(cf->ctx, ss);
540 ss = dd;
541 }
542 if (cf->idp_list_meth == ZXID_IDP_LIST_POPUP) {
543 if (cf->show_tech) {
544 dd = zx_strf(cf->ctx, "%.*s</select>"
545 "<input type=submit class=zxidplistbut name=\"l0\" value=\" Login \">\n"
546 "<input type=submit class=zxidplistbut name=\"l1\" value=\" Login (A2) \">\n"
547 "<input type=submit class=zxidplistbut name=\"l2\" value=\" Login (P2) \">\n"
548 "<input type=submit class=zxidplistbut name=\"l5\" value=\" Login (S2) \">\n"
549 "<input type=submit class=zxidplistbut name=\"l8\" value=\" Login (O2C) \">\n"
550 "<input type=submit class=zxidplistbut name=\"l9\" value=\" Login (O2I) \"><br>\n",
551 ss->len, ss->s);
552 } else {
553 dd = zx_strf(cf->ctx, "%.*s</select>"
554 "<input type=submit id=zxidplistlogin class=zxidplistbut name=\"l0\" value=\" Login \"><br>\n",
555 ss->len, ss->s);
556 }
557 zx_str_free(cf->ctx, ss);
558 ss = dd;
559 }
560
561 s = ss->s;
562 D("IdP list(%s)", s);
563 if (res_len)
564 *res_len = ss->len;
565 ZX_FREE(cf->ctx, ss);
566 return s;
567 }
568
569 /* Called by: zxid_idp_list_len */
zxid_idp_list_cf(zxid_conf * cf,int * res_len,int auto_flags)570 char* zxid_idp_list_cf(zxid_conf* cf, int* res_len, int auto_flags) {
571 return zxid_idp_list_cf_cgi(cf, 0, res_len, auto_flags);
572 }
573
574 /* Called by: zxid_idp_list */
zxid_idp_list_len(int conf_len,char * conf,int * res_len,int auto_flags)575 char* zxid_idp_list_len(int conf_len, char* conf, int* res_len, int auto_flags) {
576 zxid_conf cf;
577 zxid_conf_to_cf_len(&cf, conf_len, conf);
578 return zxid_idp_list_cf(&cf, 0, auto_flags);
579 }
580
581 /* Called by: */
zxid_idp_list(char * conf,int auto_flags)582 char* zxid_idp_list(char* conf, int auto_flags) {
583 return zxid_idp_list_len(-1, conf, 0, auto_flags);
584 }
585
586 #define FLDCHK(x,y) (x && x->y ? x->y : "")
587
588 /*(i) Render entire IdP selection screen. You may use this code, possibly adjusted
589 * by some configuration options (see zxidconf.h), or you may choose to develop
590 * your own IdP selection screen from scratch.
591 *
592 * N.B. More complete documentation is available in <<link: zxid-simple.pd>> (*** fixme) */
593
594 /* Called by: zxid_idp_select_zxstr_cf, zxid_simple_show_idp_sel */
zxid_idp_select_zxstr_cf_cgi(zxid_conf * cf,zxid_cgi * cgi,int auto_flags)595 struct zx_str* zxid_idp_select_zxstr_cf_cgi(zxid_conf* cf, zxid_cgi* cgi, int auto_flags)
596 {
597 int please_free_tf = 0;
598 struct zx_str* ss;
599 char* tf;
600 char* p;
601
602 DD("HERE e(%s) m(%s) d(%s)", FLDCHK(cgi, err), FLDCHK(cgi, msg), FLDCHK(cgi, dbg));
603 if (cf->log_level>1)
604 zxlog(cf, 0,0,0,0,0,0,0, "N", "W", "IDPSEL", 0, 0);
605
606 #if 1
607 if (cgi->templ && *cgi->templ) {
608 /* Template supplied by cgi Query String. This is often used
609 * to implement tabbed user interface. See also cgi->skin in zxid_template_page_cf()
610 * Two problems:
611 * 1. This could be an attack so we need to squash dangerous characters
612 * 2. It is not very portable to give absolute paths in QS as filesystem
613 * layout and location should not be web developer's concern. Thus
614 * we make requirement that alternate template is in the same subdirectory
615 * as the original and we use the path prefix of the original. */
616 D("HERE t(%s)", cgi->templ);
617 for (p = cgi->templ; *p; ++p)
618 if (*p == '/') { /* Squash to avoid accessing files beyond webroot */
619 ERR("Illegal character 0x%x (%c) in templ CGI variable (possible attack or misconfiguration)", *p, *p);
620 *p = '_';
621 }
622 tf = cgi->templ;
623 if (cf->idp_sel_templ_file && *cf->idp_sel_templ_file) {
624 /* scan for end of path component, if any. */
625 for (p = cf->idp_sel_templ_file + strlen(cf->idp_sel_templ_file)-1;
626 p >= cf->idp_sel_templ_file && !ONE_OF_2(*p, '/', '\\');
627 --p);
628 if (p > cf->idp_sel_templ_file) {
629 ++p;
630 D("making tf from old(%.*s) (%s) templ(%s)", (int)(p-cf->idp_sel_templ_file), cf->idp_sel_templ_file, p, cgi->templ);
631 tf = ZX_ALLOC(cf->ctx, p-cf->idp_sel_templ_file+strlen(cgi->templ)+1);
632 memcpy(tf, cf->idp_sel_templ_file, p-cf->idp_sel_templ_file);
633 strcpy(tf + (p-cf->idp_sel_templ_file), cgi->templ);
634 please_free_tf = 1;
635 }
636 }
637 } else
638 tf = cf->idp_sel_templ_file;
639 D("HERE tf(%s) k(%s) t(%s) cgi=%p", STRNULLCHKNULL(tf), STRNULLCHKNULL(cgi->skin), STRNULLCHKNULL(cf->idp_sel_templ), cgi);
640 ss = zxid_template_page_cf(cf, cgi, tf, cf->idp_sel_templ, 4096, auto_flags);
641 if (please_free_tf)
642 ZX_FREE(cf->ctx, tf);
643 #else
644 char* eid=0;
645 if (cf->idp_sel_our_eid && cf->idp_sel_our_eid[0])
646 eid = zxid_my_ent_id_cstr(cf);
647 char* idp_list = zxid_idp_list_cf_cgi(cf, cgi, 0, auto_flags);
648 if ((auto_flags & ZXID_AUTO_FORMT) && (auto_flags & ZXID_AUTO_FORMF)) {
649 DD("HERE %p", cgi->idp_list);
650 ss = zx_strf(cf->ctx,
651 "%s"
652 #ifdef ZXID_USE_POST
653 "<form method=post action=\"%s?o=P\">\n"
654 #else
655 "<form method=get action=\"%s\">\n"
656 #endif
657 "<font color=red>%s</font><font color=green>%s</font><font color=white>%s</font>"
658 "%s"
659 "%s<a href=\"%s\">%s</a><br>"
660 "%s" /* IdP List */
661 "%s%s"
662 "<input type=hidden name=fr value=\"%s\">\n"
663 "</form>%s%s%s",
664 cf->idp_sel_start,
665 cf->burl,
666 FLDCHK(cgi, err), FLDCHK(cgi, msg), FLDCHK(cgi, dbg),
667 cf->idp_sel_new_idp,
668 cf->idp_sel_our_eid, STRNULLCHK(eid), STRNULLCHK(eid),
669 idp_list,
670 cf->idp_sel_tech_user, cf->idp_sel_tech_site,
671 FLDCHK(cgi, rs),
672 cf->idp_sel_footer, zxid_version_str(), cf->idp_sel_end);
673 DD("HERE(%d) ss(%.*s)", ss->len, ss->len, ss->s);
674 } else if (auto_flags & ZXID_AUTO_FORMT) {
675 ss = zx_strf(cf->ctx,
676 #ifdef ZXID_USE_POST
677 "<form method=post action=\"%s?o=P\">\n"
678 #else
679 "<form method=get action=\"%s\">\n"
680 #endif
681 "<font color=red>%s</font><font color=green>%s</font><font color=white>%s</font>"
682 "%s"
683 "%s<a href=\"%s\">%s</a><br>"
684 "%s" /* IdP List */
685 "%s%s"
686 "<input type=hidden name=fr value=\"%s\">\n"
687 "</form>",
688 cf->burl,
689 FLDCHK(cgi, err), FLDCHK(cgi, msg), FLDCHK(cgi, dbg),
690 cf->idp_sel_new_idp,
691 cf->idp_sel_our_eid, STRNULLCHK(eid), STRNULLCHK(eid),
692 idp_list,
693 cf->idp_sel_tech_user, cf->idp_sel_tech_site,
694 FLDCHK(cgi, rs));
695 } else if (auto_flags & ZXID_AUTO_FORMF) {
696 ss = zx_strf(cf->ctx,
697 "<font color=red>%s</font><font color=green>%s</font><font color=white>%s</font>"
698 "%s"
699 "%s<a href=\"%s\">%s</a><br>"
700 "%s" /* IdP List */
701 "%s%s"
702 "<input type=hidden name=fr value=\"%s\">\n",
703 FLDCHK(cgi, err), FLDCHK(cgi, msg), FLDCHK(cgi, dbg),
704 cf->idp_sel_new_idp,
705 cf->idp_sel_our_eid, STRNULLCHK(eid), STRNULLCHK(eid),
706 idp_list,
707 cf->idp_sel_tech_user, cf->idp_sel_tech_site,
708 FLDCHK(cgi, rs));
709 } else
710 ss = zx_dup_str(cf->ctx, "");
711 #endif
712 #if 0
713 if (cgi.err) printf("<p><font color=red><i>%s</i></font></p>\n", cgi.err);
714 if (cgi.msg) printf("<p><i>%s</i></p>\n", cgi.msg);
715 printf("User:<input name=user> PW:<input name=pw type=password>");
716 printf("<input name=login value=\" Login \" type=submit>");
717 printf("<h3>Login Using IdP Discovered from Common Domain Cookie (CDC)</h3>\n");
718 printf("RelayState: <input name=fr value=\"rs123\"><br>\n");
719 if (cgi.dbg) printf("<p><form><textarea cols=100 row=10>%s</textarea></form>\n", cgi.dbg);
720 #endif
721 return ss;
722 }
723
724 /* Called by: zxid_idp_select_cf */
zxid_idp_select_zxstr_cf(zxid_conf * cf,int auto_flags)725 struct zx_str* zxid_idp_select_zxstr_cf(zxid_conf* cf, int auto_flags) {
726 return zxid_idp_select_zxstr_cf_cgi(cf, 0, auto_flags);
727 }
728
729 /* Called by: zxid_idp_select_len */
zxid_idp_select_cf(zxid_conf * cf,int * res_len,int auto_flags)730 char* zxid_idp_select_cf(zxid_conf* cf, int* res_len, int auto_flags) {
731 char* s;
732 struct zx_str* ss = zxid_idp_select_zxstr_cf(cf, auto_flags);
733 s = ss->s;
734 if (res_len)
735 *res_len = ss->len;
736 ZX_FREE(cf->ctx, ss);
737 return s;
738 }
739
740 /* Called by: zxid_idp_select */
zxid_idp_select_len(int conf_len,char * conf,int * res_len,int auto_flags)741 char* zxid_idp_select_len(int conf_len, char* conf, int* res_len, int auto_flags) {
742 zxid_conf cf;
743 zxid_conf_to_cf_len(&cf, conf_len, conf);
744 return zxid_idp_select_cf(&cf, 0, auto_flags);
745 }
746
747 /* Called by: */
zxid_idp_select(char * conf,int auto_flags)748 char* zxid_idp_select(char* conf, int auto_flags) {
749 return zxid_idp_select_len(-1, conf, 0, auto_flags);
750 }
751
752 /* ------------ zxid_simple() ------------ */
753
754 /*() Deal with the various methods of shipping the page, including CGI stdout, or
755 * as string with or without headers, as indicated by the auto_flag. The
756 * page is in ss.
757 *
758 * cf:: ZXID configuration object
759 * ss:: The page
760 * c_mask:: auto_flags content mask
761 * h_mask:: auto_flags headers mask
762 * rets:: Return value in case content is output (not returned)
763 * cont_type:: content-type header
764 * res_len:: Response length, pass 0 if not needed
765 * auto_flags:: flags to control if content is output or returned
766 * status:: Additional CGI headers, such as Status: 201 Created
767 * return:: Depends on autoflags and masks. Can be headers+data, data only, or rets (data
768 * was output to stdout, cgi style)
769 */
770
771 /* Called by: zxid_idp_oauth2_check_id, zxid_simple_idp_show_an, zxid_simple_show_carml, zxid_simple_show_conf, zxid_simple_show_err, zxid_simple_show_idp_sel, zxid_simple_show_meta */
zxid_simple_show_page(zxid_conf * cf,struct zx_str * ss,int c_mask,int h_mask,char * rets,char * cont_type,int * res_len,int auto_flags,const char * status)772 char* zxid_simple_show_page(zxid_conf* cf, struct zx_str* ss, int c_mask, int h_mask, char* rets, char* cont_type, int* res_len, int auto_flags, const char* status)
773 {
774 char* res;
775 struct zx_str* ss2;
776 if (auto_flags & c_mask && auto_flags & h_mask) { /* Both H&C: CGI */
777 int extralen = 0;
778 D("CGI %x ss->len=%d ss->s=%p ss->s[0]=%x", auto_flags, ss->len, ss->s, ss->s[0]);
779 /*hexdmp("ss->s: ", ss->s, ss->len, 40);*/
780 #ifdef MINGW
781 /* It seems that Apache strips off the \n in this output when running as a CGI Script.
782 * This means the content length does not reflect reality, and we end up losing the
783 * last N bytes, where N is the number of newlines in the output
784 */
785 char *p = ss->s;
786 while(*p != '\0') {
787 if(*p == '\n')
788 ++extralen;
789 p++;
790 }
791 #endif
792 fprintf(stdout, "%sContent-Type: %s" CRLF "Content-Length: %d" CRLF2 "%.*s",
793 STRNULLCHK(status), cont_type, ss->len+extralen, ss->len+extralen, ss->s);
794 fflush(stdout);
795 DD("__stdio_file fd=%d flags=%x bs=%d bm=%d buflen=%d buf=%p buf(%.4s) next=%p pok=%d unget=%x ungotten=%x", stdout->fd, stdout->flags, stdout->bs, stdout->bm, stdout->buflen, stdout->buf, stdout->buf, stdout->next, stdout->popen_kludge, stdout->ungetbuf, stdout->ungotten);
796 if (auto_flags & ZXID_AUTO_EXIT)
797 exit(0);
798 zx_str_free(cf->ctx, ss);
799 if (res_len)
800 *res_len = 1;
801 return zx_dup_cstr(cf->ctx, "n");
802 }
803
804 if (auto_flags & (c_mask | h_mask)) {
805 if (auto_flags & h_mask) { /* H only: return both H and C */
806 if (errmac_debug & MOD_AUTH_SAML_INOUT) D("With headers %x (%s)", auto_flags, ss->s);
807 ss2 = zx_strf(cf->ctx, "%sContent-Type: %s" CRLF "Content-Length: %d" CRLF2 "%.*s",
808 STRNULLCHK(status), cont_type, ss->len, ss->len, ss->s);
809 zx_str_free(cf->ctx, ss);
810 } else {
811 D("No headers %x (%s)", auto_flags, ss->s);
812 ss2 = ss; /* C only */
813 }
814 res = ss2->s;
815 DD("res(%s)", res);
816 if (res_len)
817 *res_len = ss2->len;
818 ZX_FREE(cf->ctx, ss2);
819 return res;
820 }
821 /* Do not output anything (both c and h 0). Effectively the generated page is thrown away. */
822 D("e(%.*s) cm=%x hm=%x af=%x rets(%s)", ss?ss->len:-1, ss?ss->s:"", c_mask, h_mask, auto_flags, rets);
823 if (ss)
824 zx_str_free(cf->ctx, ss);
825 if (res_len)
826 *res_len = 1;
827 return zx_dup_cstr(cf->ctx, rets); /* Neither H nor C */
828 }
829
830 /*() Show JSON page, as often needed in OAUTH2 */
831
zxid_simple_show_json(zxid_conf * cf,const char * json,int * res_len,int auto_flags,const char * status)832 char* zxid_simple_show_json(zxid_conf* cf, const char* json, int* res_len, int auto_flags, const char* status)
833 {
834 struct zx_str* ss = zx_ref_str(cf->ctx, json);
835 return zxid_simple_show_page(cf, ss, ZXID_AUTO_METAC, ZXID_AUTO_METAH, "J", "application/json", res_len, auto_flags, status);
836 }
837
838 /*() Helper function to redirect according to auto flags. */
839
840 /* Called by: zxid_show_protected_content_setcookie, zxid_simple_idp_an_ok_do_rest, zxid_simple_idp_new_user, zxid_simple_idp_recover_password, zxid_simple_idp_show_an, zxid_simple_show_err, zxid_simple_show_idp_sel */
zxid_simple_redir_page(zxid_conf * cf,char * redir,char * rs,int * res_len,int auto_flags)841 static char* zxid_simple_redir_page(zxid_conf* cf, char* redir, char* rs, int* res_len, int auto_flags)
842 {
843 char* res;
844 struct zx_str* ss;
845 D("cf=%p redir(%s)", cf, redir);
846 if (auto_flags & ZXID_AUTO_REDIR) {
847 fprintf(stdout, "Location: %s%c%s" CRLF2, redir, rs?'?':0, STRNULLCHK(rs));
848 fflush(stdout);
849 if (auto_flags & ZXID_AUTO_EXIT)
850 exit(0);
851 if (res_len)
852 *res_len = 1;
853 return zx_dup_cstr(cf->ctx, "n");
854 }
855 ss = zx_strf(cf->ctx, "Location: %s%c%s" CRLF2, redir, rs?'?':0, STRNULLCHK(rs));
856 if (res_len)
857 *res_len = ss->len;
858 res = ss->s;
859 ZX_FREE(cf->ctx, ss);
860 return res;
861 }
862
863 /*() Show IdP selection or login screen.
864 *
865 * N.B. More complete documentation is available in <<link: zxid-simple.pd>> (*** fixme) */
866
867 /* Called by: zxid_ps_accept_invite, zxid_ps_finalize_invite, zxid_simple_no_ses_cf, zxid_simple_ses_active_cf x5 */
zxid_simple_show_idp_sel(zxid_conf * cf,zxid_cgi * cgi,int * res_len,int auto_flags)868 char* zxid_simple_show_idp_sel(zxid_conf* cf, zxid_cgi* cgi, int* res_len, int auto_flags)
869 {
870 struct zx_str* ss;
871 zxid_sso_set_relay_state_to_return_to_this_url(cf, cgi);
872
873 D("cf=%p cgi=%p templ(%s)", cf, cgi, STRNULLCHKQ(cgi->templ));
874 if (cf->idp_sel_page && cf->idp_sel_page[0]) {
875 D("idp_sel_page(%s) rs(%s)", cf->idp_sel_page, STRNULLCHK(cgi->rs));
876 return zxid_simple_redir_page(cf, cf->idp_sel_page, cgi->rs, res_len, auto_flags);
877 }
878 ss = auto_flags & (ZXID_AUTO_LOGINC | ZXID_AUTO_LOGINH)
879 ? zxid_idp_select_zxstr_cf_cgi(cf, cgi, auto_flags)
880 : 0;
881 DD("idp_select: ret(%s)", ss?ss->len:1, ss?ss->s:"?");
882 return zxid_simple_show_page(cf, ss, ZXID_AUTO_LOGINC, ZXID_AUTO_LOGINH,
883 "e", "text/html", res_len, auto_flags, 0);
884 }
885
886
887 /*() Emit metadata. Corresponds to "o=B" query string.
888 *
889 * N.B. More complete documentation is available in <<link: zxid-simple.pd>> (*** fixme) */
890
891 /* Called by: zxid_simple_no_ses_cf x2, zxid_simple_ses_active_cf x2 */
zxid_simple_show_meta(zxid_conf * cf,zxid_cgi * cgi,int * res_len,int auto_flags)892 static char* zxid_simple_show_meta(zxid_conf* cf, zxid_cgi* cgi, int* res_len, int auto_flags)
893 {
894 struct zx_str* meta = zxid_sp_meta(cf, cgi);
895 return zxid_simple_show_page(cf, meta, ZXID_AUTO_METAC, ZXID_AUTO_METAH,
896 "b", "text/xml", res_len, auto_flags, 0);
897 }
898
899 /*() Emit CARML declaration for SP. Corresponds to "o=c" query string. */
900
901 /* Called by: zxid_simple_no_ses_cf, zxid_simple_ses_active_cf */
zxid_simple_show_carml(zxid_conf * cf,zxid_cgi * cgi,int * res_len,int auto_flags)902 static char* zxid_simple_show_carml(zxid_conf* cf, zxid_cgi* cgi, int* res_len, int auto_flags)
903 {
904 struct zx_str* carml = zxid_sp_carml(cf);
905 return zxid_simple_show_page(cf, carml, ZXID_AUTO_METAC, ZXID_AUTO_METAH,
906 "c", "text/xml", res_len, auto_flags, 0);
907 }
908
909 /*() Dump internal info and configuration. Corresponds to "o=d" query string. */
910
911 /* Called by: zxid_simple_no_ses_cf, zxid_simple_ses_active_cf */
zxid_simple_show_conf(zxid_conf * cf,zxid_cgi * cgi,int * res_len,int auto_flags)912 static char* zxid_simple_show_conf(zxid_conf* cf, zxid_cgi* cgi, int* res_len, int auto_flags)
913 {
914 struct zx_str* ss = zxid_show_conf(cf);
915 return zxid_simple_show_page(cf, ss, ZXID_AUTO_METAC, ZXID_AUTO_METAH,
916 "d", "text/html", res_len, auto_flags, 0);
917 }
918
919 /*() Emit Java Web Key Set. Corresponds to "o=j" query string. */
920
921 /* Called by: zxid_simple_no_ses_cf, zxid_simple_ses_active_cf */
zxid_simple_show_jwks(zxid_conf * cf,zxid_cgi * cgi,int * res_len,int auto_flags)922 static char* zxid_simple_show_jwks(zxid_conf* cf, zxid_cgi* cgi, int* res_len, int auto_flags)
923 {
924 struct zx_str* ss = zx_ref_str(cf->ctx, zxid_mk_jwks(cf));
925 return zxid_simple_show_page(cf, ss, ZXID_AUTO_METAC, ZXID_AUTO_METAH,
926 "j",
927 //"text/json",
928 "application/jwk-set+json",
929 res_len, auto_flags, 0);
930 }
931
932 /*() Perform and reply to OAUTH2 Dynamic Client Registration. Corresponds to "o=J" query string. */
933
934 /* Called by: zxid_simple_no_ses_cf, zxid_simple_ses_active_cf */
zxid_simple_show_dynclireg(zxid_conf * cf,zxid_cgi * cgi,int * res_len,int auto_flags)935 static char* zxid_simple_show_dynclireg(zxid_conf* cf, zxid_cgi* cgi, int* res_len, int auto_flags)
936 {
937 return zxid_simple_show_json(cf, zxid_mk_oauth2_dyn_cli_reg_res(cf, cgi),
938 res_len, auto_flags, "Status: 201 Created" CRLF);
939 }
940
941 /*() Perform and reply to OAUTH2 Resource Registration. Corresponds to "o=H" query string. */
942
943 /* Called by: zxid_simple_no_ses_cf, zxid_simple_ses_active_cf */
zxid_simple_show_rsrcreg(zxid_conf * cf,zxid_cgi * cgi,int * res_len,int auto_flags)944 static char* zxid_simple_show_rsrcreg(zxid_conf* cf, zxid_cgi* cgi, int* res_len, int auto_flags)
945 {
946 char rev[256];
947 char status_etag[1024];
948 char* json = zxid_mk_oauth2_rsrc_reg_res(cf, cgi, rev);
949 snprintf(status_etag, sizeof(status_etag), "Status: 201 Created" CRLF "Etag: %s" CRLF, rev);
950 return zxid_simple_show_json(cf, json, res_len, auto_flags, status_etag);
951 }
952
953 /*() Show Error screen. */
954
955 /* Called by: zxid_ps_accept_invite x4, zxid_ps_finalize_invite x4 */
zxid_simple_show_err(zxid_conf * cf,zxid_cgi * cgi,int * res_len,int auto_flags)956 char* zxid_simple_show_err(zxid_conf* cf, zxid_cgi* cgi, int* res_len, int auto_flags)
957 {
958 char* p;
959 struct zx_str* ss;
960
961 if (cf->log_level>1)
962 zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "W", "ERR", 0, "");
963
964 if (cf->err_page && cf->err_page[0]) {
965 p = zx_alloc_sprintf(cf->ctx, 0, "zxrfr=F%s%s%s%s&zxidpurl=%s",
966 cgi->zxapp && cgi->zxapp[0] ? "&zxapp=" : "", cgi->zxapp ? cgi->zxapp : "",
967 cgi->err && cgi->err[0] ? "&err=" : "", cgi->err ? cgi->err : "",
968 cf->burl);
969 D("err_page(%s) p(%s)", cf->err_page, p);
970 return zxid_simple_redir_page(cf, cf->err_page, p, res_len, auto_flags);
971 }
972
973 ss = zxid_template_page_cf(cf, cgi, cf->err_templ_file, cf->err_templ, 4096, auto_flags);
974 return zxid_simple_show_page(cf, ss, ZXID_AUTO_LOGINC, ZXID_AUTO_LOGINH,
975 "g", "text/html", res_len, auto_flags, 0);
976 }
977
978 /* ----------- IdP Screens ----------- */
979
980 /*() Decode ssoreq (ar=), i.e. the preserved original AuthnReq */
981
982 /* Called by: zxid_simple_idp_pw_authn, zxid_simple_idp_show_an, zxid_sp_sso_finalize */
zxid_decode_ssoreq(zxid_conf * cf,zxid_cgi * cgi)983 int zxid_decode_ssoreq(zxid_conf* cf, zxid_cgi* cgi)
984 {
985 int len;
986 char* p;
987 if (!cgi->ssoreq || !cgi->ssoreq[0])
988 return 1;
989 p = zxid_unbase64_inflate(cf->ctx, -2, cgi->ssoreq, &len);
990 if (!p)
991 return 0;
992 cgi->op = 0;
993 D("ar/ssoreq decoded(%s) len=%d", p, len);
994 zxid_parse_cgi(cf, cgi, p); /* cgi->op will be Q due to SAMLRequest inside ssoreq */
995 cgi->op = 'F';
996 return 1;
997 }
998
999 /*() Process IdP side after successful authentication. If IdP was
1000 * invoked with AuthnReq (in SAMLRequest) then op=='F' as set
1001 * in zxid_simple_idp_pw_authn() which will trigger the rest of the
1002 * SSO protocol in zxid_simple_ses_active_cf(). Otherwise just
1003 * show the IdP management screen. */
1004
1005 /* Called by: zxid_simple_idp_pw_authn, zxid_simple_idp_show_an */
zxid_simple_idp_an_ok_do_rest(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,int * res_len,int auto_flags)1006 static char* zxid_simple_idp_an_ok_do_rest(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, int* res_len, int auto_flags)
1007 {
1008 int len;
1009 char* p;
1010 DD("idp do_rest %p", ses);
1011 if (cf->atsel_page && cgi->atselafter) { /* *** More sophisticated criteria needed. */
1012 p = zx_alloc_sprintf(cf->ctx, 0, "ar=%s&s=%s&zxrfr=F%s%s%s%s&zxidpurl=%s",
1013 cgi->ssoreq, cgi->sid,
1014 cgi->zxapp && cgi->zxapp[0] ? "&zxapp=" : "", cgi->zxapp ? cgi->zxapp : "",
1015 cgi->err && cgi->err[0] ? "&err=" : "", cgi->err ? cgi->err : "",
1016 cf->burl);
1017 D("atsel_page(%s) redir(%s)", cf->atsel_page, p);
1018 return zxid_simple_redir_page(cf, cf->atsel_page, p, res_len, auto_flags);
1019 }
1020 if (cgi->redirafter && *cgi->redirafter) {
1021 len = strlen(cgi->redirafter);
1022 if (!strcmp(cgi->redirafter + len - sizeof("s=X") + 1, "s=X")) {
1023 p = zx_alloc_sprintf(cf->ctx, 0, "%.*s%s", len-1, cgi->redirafter, cgi->sid);
1024 D("redirafter(%s)", p);
1025 return zxid_simple_redir_page(cf, p, 0, res_len, auto_flags);
1026 } else {
1027 return zxid_simple_redir_page(cf, cgi->redirafter, 0, res_len, auto_flags);
1028 }
1029 }
1030 return zxid_simple_ses_active_cf(cf, cgi, ses, res_len, auto_flags); /* o=F variant */
1031 }
1032
1033 /*() Show Authentication screen. Generally this will be in response to
1034 * the SP having sent user via redirect to o=F carrying AuthnRequest encoded
1035 * in SAMLRequest query string parameter, per SAML redirect binding
1036 * [SAML2bind]. We must preserve SAMLRequest as hidden field, ar, in the
1037 * page for later processing once the authentication step has been
1038 * taken care of. It will also be passed on the query string to
1039 * external authentication page if any was configured with AN_PAGE
1040 * directive.
1041 *
1042 * N.B. More complete documentation is available in <<link: zxid-simple.pd>> (*** fixme) */
1043
1044 /* Called by: zxid_simple_idp_new_user, zxid_simple_idp_pw_authn, zxid_simple_idp_recover_password, zxid_simple_no_ses_cf */
zxid_simple_idp_show_an(zxid_conf * cf,zxid_cgi * cgi,int * res_len,int auto_flags)1045 static char* zxid_simple_idp_show_an(zxid_conf* cf, zxid_cgi* cgi, int* res_len, int auto_flags)
1046 {
1047 char* p;
1048 char* ar;
1049 struct zx_sa_Issuer_s* issuer;
1050 zxid_entity* meta;
1051 struct zx_root_s* root;
1052 struct zx_str* ss;
1053 zxid_ses sess;
1054 ZERO(&sess, sizeof(sess));
1055 D("cf=%p cgi=%p", cf, cgi);
1056 DD("z saml_req(%s) rs(%s) sigalg(%s) sig(%s)", cgi->saml_req, cgi->rs, cgi->sigalg, cgi->sig);
1057 if ((cgi->uid || cgi->pcode) && zxid_pw_authn(cf, cgi, &sess)) { /* Try login, just in case. */
1058 return zxid_simple_idp_an_ok_do_rest(cf, cgi, &sess, res_len, auto_flags);
1059 }
1060 if (cgi->redirafter) { /* Save next screen for local login (e.g. zxidatsel.pl */
1061 D("zz redirafter(%s) rs(%s)", cgi->redirafter, cgi->rs);
1062 cgi->ssoreq = zxid_deflate_safe_b64(cf->ctx,
1063 zx_strf(cf->ctx,
1064 "redirafter=%s",
1065 cgi->redirafter));
1066 }
1067 if (cgi->response_type) { /* Save incoming OAUTH2 / OpenID-Connect Az request as hidden field */
1068 DD("zz response_type(%s) rs(%s)", cgi->response_type, cgi->rs);
1069 cgi->ssoreq = zxid_deflate_safe_b64(cf->ctx,
1070 zx_strf(cf->ctx,
1071 "response_type=%s"
1072 "&client_id=%s"
1073 "&scope=%s"
1074 "&redirect_uri=%s"
1075 "&nonce=%s"
1076 "%s%s" /* &state= */
1077 "%s%s" /* &display= */
1078 "%s%s", /* &prompt= */
1079 cgi->response_type,
1080 cgi->client_id,
1081 cgi->scope,
1082 cgi->redirect_uri,
1083 cgi->nonce,
1084 cgi->state?"&state=":"", STRNULLCHK(cgi->state),
1085 cgi->display?"&display=":"", STRNULLCHK(cgi->display),
1086 cgi->prompt?"&prompt=":"", STRNULLCHK(cgi->prompt)
1087 ));
1088 }
1089 if (cgi->saml_req) { /* Save incoming SAMLRequest as hidden form field ar */
1090 DD("zz saml_req(%s) rs(%s) sigalg(%s) sig(%s)", cgi->saml_req, cgi->rs, cgi->sigalg, cgi->sig);
1091 cgi->ssoreq = zxid_deflate_safe_b64(cf->ctx,
1092 zx_strf(cf->ctx, "SAMLRequest=%s%s%s&SigAlg=%s&Signature=%s",
1093 STRNULLCHK(cgi->saml_req),
1094 cgi->rs && cgi->rs[0] ? "&RelayState=" : "", cgi->rs ? cgi->rs : "",
1095 STRNULLCHK(cgi->sigalg),
1096 STRNULLCHK(cgi->sig)));
1097 }
1098
1099 if (cf->an_page && cf->an_page[0]) { /* Redirect to sysadmin configured page */
1100 ar = zx_alloc_sprintf(cf->ctx, 0, "ar=%s&zxrfr=F%s%s%s%s&zxidpurl=%s",
1101 cgi->ssoreq,
1102 cgi->zxapp && cgi->zxapp[0] ? "&zxapp=" : "", cgi->zxapp ? cgi->zxapp : "",
1103 cgi->err && cgi->err[0] ? "&err=" : "", cgi->err ? cgi->err : "",
1104 cf->burl);
1105 if (cgi->ssoreq)
1106 ZX_FREE(cf->ctx, cgi->ssoreq);
1107 D("an_page(%s) ar(%s)", cf->an_page, ar);
1108 return zxid_simple_redir_page(cf, cf->an_page, ar, res_len, auto_flags);
1109 }
1110
1111 if (cf->log_level>1)
1112 zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "W", "AUTHN", 0, "");
1113
1114 /* Attempt to provisorily decode the request and fetch metadata of the SP so we
1115 * can detect trouble early on and provide some assuring knowledge to the user. */
1116
1117 if (!cgi->saml_req && !cgi->response_type && cgi->ssoreq) {
1118 zxid_decode_ssoreq(cf, cgi);
1119 }
1120
1121 if (cgi->response_type) { /* OAUTH2 AzReq redir (OpenID-Connect) */
1122 if (cgi->client_id) {
1123 meta = zxid_get_ent(cf, cgi->client_id);
1124 if (meta) {
1125 cgi->sp_eid = meta->eid;
1126 cgi->sp_dpy_name = meta->dpy_name;
1127 cgi->sp_button_url = meta->button_url;
1128 } else {
1129 ERR("Unable to find metadata for client_id(%s) in OAUTH2 AzReq Redir", cgi->client_id);
1130 cgi->err = "OAUTH2 client_id unknown - metadata exchange may be needed (AnReq).";
1131 cgi->sp_dpy_name = "--SP description unavailable--";
1132 cgi->sp_eid = zx_dup_cstr(cf->ctx, cgi->client_id);
1133 }
1134 } else {
1135 cgi->err = "OAUTH2 client_id missing.";
1136 cgi->sp_eid = "";
1137 cgi->sp_dpy_name = "--No SP could be determined--";
1138 }
1139 } else { /* Assume SAML2 */
1140 root = zxid_decode_redir_or_post(cf, cgi, &sess, 0x2);
1141 if (root) {
1142 issuer = zxid_extract_issuer(cf, cgi, &sess, root);
1143 if (ZX_SIMPLE_ELEM_CHK(issuer)) {
1144 meta = zxid_get_ent_ss(cf, ZX_GET_CONTENT(issuer));
1145 if (meta) {
1146 cgi->sp_eid = meta->eid;
1147 cgi->sp_dpy_name = meta->dpy_name;
1148 cgi->sp_button_url = meta->button_url;
1149 } else {
1150 ERR("Unable to find metadata for Issuer(%.*s) in AnReq Redir", ZX_GET_CONTENT_LEN(issuer), ZX_GET_CONTENT_S(issuer));
1151 cgi->err = "Issuer unknown - metadata exchange may be needed (AnReq).";
1152 cgi->sp_dpy_name = "--SP description unavailable--";
1153 cgi->sp_eid = zx_str_to_c(cf->ctx, ZX_GET_CONTENT(issuer));
1154 }
1155 } else {
1156 cgi->err = "Issuer could not be determined from Authentication Request.";
1157 cgi->sp_eid = "";
1158 cgi->sp_dpy_name = "--No SP could be determined--";
1159 }
1160 } else {
1161 cgi->err = "Malformed or nonexistant Authentication Request";
1162 cgi->sp_eid = "";
1163 cgi->sp_dpy_name = "--No SP could be determined--";
1164 }
1165 }
1166
1167 /* Render the authentication page */
1168 if (cgi->templ) {
1169 cf->an_templ_file = cgi->templ;
1170 for (p = cf->an_templ_file; *p; ++p)
1171 if (*p == '/') { /* Squash to avoid accessing files beyond webroot */
1172 ERR("Illegal character 0x%x (%c) in templ CGI variable (possible attack or misconfiguration)", *p, *p);
1173 *p = '_';
1174 }
1175 }
1176 #if 1
1177 /* Hack: Different page for mobile */
1178 if (cgi->mob) {
1179 D("Mobile detected TF(%s)", cf->an_templ_file);
1180 /* Replace final .html with -mob.html */
1181 cf->an_templ_file = zx_alloc_sprintf(cf->ctx, 0, "%.*s-mob.html",
1182 strlen(cf->an_templ_file)-sizeof(".html")+1,
1183 cf->an_templ_file);
1184 D("New TF(%s)", cf->an_templ_file);
1185 }
1186 #endif
1187 ss = zxid_template_page_cf(cf, cgi, cf->an_templ_file, cf->an_templ, 4096, auto_flags);
1188 /* if (cgi->ssoreq) ZX_FREE(cf->ctx, cgi->ssoreq); might not be malloc'd if tabs have CGI */
1189 DD("an_page: ret(%s)", ss?ss->len:1, ss?ss->s:"?");
1190 return zxid_simple_show_page(cf, ss, ZXID_AUTO_LOGINC, ZXID_AUTO_LOGINH,
1191 "a", "text/html", res_len, auto_flags, 0);
1192 }
1193
1194 /*() Process password authentication form and, if ssoreq (ar=) is present
1195 * (see zxid_simple_idp_show_an() for how it is embedded to hidden
1196 * form field), proceed to federated SSO. If login fails, redisplay
1197 * the authentication page.
1198 *
1199 * N.B. More complete documentation is available in <<link: zxid-simple.pd>> (*** fixme) */
1200
1201 /* Called by: zxid_simple_no_ses_cf */
zxid_simple_idp_pw_authn(zxid_conf * cf,zxid_cgi * cgi,int * res_len,int auto_flags)1202 static char* zxid_simple_idp_pw_authn(zxid_conf* cf, zxid_cgi* cgi, int* res_len, int auto_flags)
1203 {
1204 zxid_ses sess;
1205 D("cf=%p cgi=%p", cf, cgi);
1206
1207 if (!zxid_decode_ssoreq(cf, cgi))
1208 goto err;
1209
1210 ZERO(&sess, sizeof(sess));
1211 if (zxid_pw_authn(cf, cgi, &sess))
1212 return zxid_simple_idp_an_ok_do_rest(cf, cgi, &sess, res_len, auto_flags);
1213
1214 D("PW Login failed uid(%s) pw(%s) err(%s)", STRNULLCHK(cgi->uid), STRNULLCHK(cgi->pw), STRNULLCHK(cgi->err));
1215 err:
1216 return zxid_simple_idp_show_an(cf, cgi, res_len, auto_flags);
1217 }
1218
1219 /*() Redirect user to new user creation page. */
1220
1221 /* Called by: zxid_simple_no_ses_cf */
zxid_simple_idp_new_user(zxid_conf * cf,zxid_cgi * cgi,int * res_len,int auto_flags)1222 static char* zxid_simple_idp_new_user(zxid_conf* cf, zxid_cgi* cgi, int* res_len, int auto_flags)
1223 {
1224 char* p;
1225 D("cf=%p cgi=%p", cf, cgi);
1226
1227 // ***
1228
1229 if (cf->new_user_page && cf->new_user_page[0]) {
1230 p = zx_alloc_sprintf(cf->ctx, 0, "ar=%s&zxrfr=F%s%s%s%s&zxidpurl=%s",
1231 STRNULLCHK(cgi->ssoreq),
1232 cgi->zxapp && cgi->zxapp[0] ? "&zxapp=" : "", cgi->zxapp ? cgi->zxapp : "",
1233 cgi->err && cgi->err[0] ? "&err=" : "", cgi->err ? cgi->err : "",
1234 cf->burl);
1235 D("new_user_page(%s) redir(%s)", cf->new_user_page, p);
1236 return zxid_simple_redir_page(cf, cf->new_user_page, p, res_len, auto_flags);
1237 }
1238
1239 ERR("No new user page URL defined. (IdP config problem, or IdP intentionally does not support online new user creation. See NEW_USER_PAGE config option.) %d", 0);
1240 cgi->err = "No new user page URL defined. (IdP config problem, or IdP intentionally does not support online new user creation.)";
1241
1242 return zxid_simple_idp_show_an(cf, cgi, res_len, auto_flags);
1243 }
1244
1245 /*() Redirect user to recover password page. */
1246
1247 /* Called by: zxid_simple_no_ses_cf */
zxid_simple_idp_recover_password(zxid_conf * cf,zxid_cgi * cgi,int * res_len,int auto_flags)1248 static char* zxid_simple_idp_recover_password(zxid_conf* cf, zxid_cgi* cgi, int* res_len, int auto_flags)
1249 {
1250 char* p;
1251 D("cf=%p cgi=%p", cf, cgi);
1252
1253 // ***
1254
1255 if (cf->recover_passwd && cf->recover_passwd[0]) {
1256 p = zx_alloc_sprintf(cf->ctx, 0, "ar=%s&zxrfr=F%s%s%s%s&zxidpurl=%s",
1257 STRNULLCHK(cgi->ssoreq),
1258 cgi->zxapp && cgi->zxapp[0] ? "&zxapp=" : "", cgi->zxapp ? cgi->zxapp : "",
1259 cgi->err && cgi->err[0] ? "&err=" : "", cgi->err ? cgi->err : "",
1260 cf->burl);
1261 D("recover_passwd(%s) redir(%s)", cf->recover_passwd, p);
1262 return zxid_simple_redir_page(cf, cf->recover_passwd, p, res_len, auto_flags);
1263 }
1264
1265 ERR("No password recover page URL defined. (IdP config problem, or IdP intentionally does not support online password recovery. See RECOVER_PASSWD config option.) %d", 0);
1266 cgi->err = "No password recover page URL defined. (IdP config problem, or IdP intentionally does not support online password recovery.)";
1267
1268 return zxid_simple_idp_show_an(cf, cgi, res_len, auto_flags);
1269 }
1270
1271 /*() Final steps of SSO: set the cookies and check authorization
1272 * before returning the LDIF. */
1273
1274 /* Called by: zxid_simple_no_ses_cf x2 */
zxid_show_protected_content_setcookie(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,int * res_len,int auto_flags)1275 static char* zxid_show_protected_content_setcookie(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, int* res_len, int auto_flags)
1276 {
1277 struct zx_str* issuer;
1278 struct zx_str* url;
1279 zxid_epr* epr;
1280 char* rs;
1281 char* rs_qs;
1282
1283 if (cf->ses_cookie_name && *cf->ses_cookie_name) {
1284 ses->setcookie = zx_alloc_sprintf(cf->ctx, 0, "%s=%s; path=/%s%s",
1285 cf->ses_cookie_name, ses->sid,
1286 cgi->mob?"; Max-Age=15481800":"",
1287 ONE_OF_2(cf->burl[4], 's', 'S')?"; secure; HttpOnly":"; HttpOnly");
1288 ses->cookie = zx_alloc_sprintf(cf->ctx, 0, "$Version=1; %s=%s",
1289 cf->ses_cookie_name, ses->sid);
1290 D("setcookie(%s)=(%s) ses=%p", cf->ses_cookie_name, ses->setcookie, ses);
1291 }
1292 if (cf->ptm_cookie_name && *cf->ptm_cookie_name) {
1293 D("ptm_cookie_name(%s) ses->a7n=%p", cf->ptm_cookie_name, ses->a7n);
1294 issuer = ses->a7n?ZX_GET_CONTENT(ses->a7n->Issuer):0;
1295 if (!issuer)
1296 ERR("Assertion does not have Issuer. %p", ses->a7n);
1297
1298 if (epr = zxid_get_epr(cf, ses, TAS3_PTM, 0, 0, 0, 1)) {
1299 url = zxid_get_epr_address(cf, epr);
1300 if (!url)
1301 ERR("EPR does not have Address. %p", epr);
1302 ses->setptmcookie = zx_alloc_sprintf(cf->ctx, 0, "%s=%.*s?l0%.*s=1; path=/%s",
1303 cf->ptm_cookie_name,
1304 url?url->len:0, url?url->s:"",
1305 issuer?issuer->len:0, issuer?issuer->s:"",
1306 ONE_OF_2(cf->burl[4], 's', 'S')?"; secure":"");
1307 //ses->ptmcookie = zx_alloc_sprintf(cf->ctx,0,"$Version=1; %s=%s",cf->ptm_cookie_name,?);
1308 D("setptmcookie(%s)", ses->setptmcookie);
1309 } else {
1310 D("The PTM epr could not be discovered. Has it been registered at discovery service? Is there a discovery service? %p", epr);
1311 }
1312 }
1313 // *** check cf->redir_to_content here
1314 ses->rs = cgi->rs;
1315 if (cgi->rs && cgi->rs[0] && cgi->rs[0] != '-') {
1316 /* N.B. RelayState was set by chkuid() "some other page" section by setting cgi.rs
1317 * to deflated and safe base64 encoded value which was then sent to IdP as RelayState.
1318 * It then came back from IdP and was decoded as one of the SSO attributes.
1319 * The decoding is controlled by <<tt: rsrc$rs$unsb64-inf$$ >> rule in OUTMAP. */
1320 cgi->redirect_uri = rs = zxid_unbase64_inflate(cf->ctx, -2, cgi->rs, 0);
1321 if (!rs) {
1322 ERR("Bad relaystate. Error in inflate. %d", 0);
1323 goto erro;
1324 }
1325 if (!*rs) {
1326 D("Empty rs %p", rs);
1327 goto erro;
1328 }
1329 if (cgi->uri_path) {
1330 rs_qs = strchr(rs, '?');
1331 if (rs_qs /* if there is query string, compare differently */
1332 ?(memcmp(cgi->uri_path, rs, rs_qs-rs)||strcmp(cgi->qs?cgi->qs:"",rs_qs+1))
1333 :strcmp(cgi->uri_path, rs)) { /* Different, need external or internal redirect */
1334 D("redirect(%s) redir_to_content=%d", rs, cf->redir_to_content);
1335 if (cf->redir_to_content) {
1336 return zxid_simple_redir_page(cf, rs, 0, res_len, auto_flags);
1337 } else {
1338 D("*** internal redirect(%s)", rs);
1339 }
1340 }
1341 }
1342 }
1343 erro:
1344 return zxid_simple_ab_pep(cf, ses, res_len, auto_flags);
1345 }
1346
1347 /* ===== Main Control Logic for Session Active and Session Inactive Cases ===== */
1348
1349 /*() Subroutine of zxid_simple_cf() for the session active case.
1350 * cgi->uri_path should have been set by the caller.
1351 *
1352 * NULL return means the "not logged in" processing is needed, see zxid_simple_no_ses_cf()
1353 *
1354 * N.B. More complete documentation is available in <<link: zxid-simple.pd>> (*** fixme) */
1355
1356 /* Called by: chkuid x2, zxid_mini_httpd_sso x2, zxid_simple_cf_ses, zxid_simple_idp_an_ok_do_rest, zxid_sp_dispatch, zxid_sp_oauth2_dispatch */
zxid_simple_ses_active_cf(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,int * res_len,int auto_flags)1357 char* zxid_simple_ses_active_cf(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, int* res_len, int auto_flags)
1358 {
1359 struct zx_str* accr;
1360 char* p;
1361 char* res = 0;
1362 struct zx_str* ss;
1363
1364 if (!cf || !cgi || !ses) {
1365 ERR("FATAL: NULL pointer. You MUST supply configuration(%p), cgi(%p), and session(%p) objects (programming error)", cf, cgi, ses);
1366 NEVERNEVER("Bad args %p", cf);
1367 }
1368 if (cf->wd)
1369 chdir(cf->wd);
1370
1371 /* OPs (the o= CGI field. Not to be confused with first letter of zxid_simple() return value)
1372 * l = local logout (form gl)
1373 * r = SLO redir (form gr)
1374 * s = SLO soap (form gs)
1375 * t = nireg redir (form gt, gn=newnym)
1376 * u = nireg soap (form gu, gn=newnym)
1377 * v = Az soap (form gv)
1378 * c = CARML for the SP
1379 * d = Dump internal data, including config; debug screen
1380 * m = Show management screen
1381 * n = Just check session (used for checking session for protected content pages)
1382 * p = Password Login (IdP form submit alp= with au= and ap=)
1383 * P = POST response. HTTP POST in general
1384 * Q = POST request
1385 * R = POST request to IdP
1386 * S = SOAP (POST) request
1387 * Y = SOAP (POST) request for PDP and misc support services
1388 * Z = SOAP (POST) request for discovery
1389 * B = Metadata
1390 * b = Metadata Authority
1391 * j = jwks
1392 * J = OAUTH2 Dynamic client registration endpoint
1393 * H = OAUTH2 Resource Registration endpoint
1394 *
1395 * M = CDC redirect and LECP detect
1396 * C = CDC reader
1397 * E = Normal "Entry" page (e.g. after CDC read, idpsel)
1398 * L = Start SSO (submit of E)
1399 * A = Artifact processing
1400 * N = New User, during IdP Login (form an)
1401 * W = Recover password, during IdP Login (form aw)
1402 * D = Delegation / Invitation acceptance user interface, the idp selection
1403 * G = Delegation / Invitation finalization after SSO (via RelayState)
1404 * O = OAuth2 redirect destination
1405 * T = OAuth2 Check ID / Token Endpoint
1406 *
1407 * I = used for IdP ???
1408 * K = used?
1409 * F = IdP: Return SSO A7N after successful An; no ses case, generate IdP ui
1410 * V = Proxy IdP return
1411 *
1412 * Still available: UWXacefghikqwxyz
1413 */
1414
1415 if (cgi->enc_hint)
1416 cf->nameid_enc = cgi->enc_hint != '0';
1417 D("op(%c) sesid(%s) active", cgi->op?cgi->op:'-', STRNULLCHK(cgi->sid));
1418 DD("ses(%s) active op(%c) saml_req(%s)",cgi->sid,cgi->op?cgi->op:'-', STRNULLCHK(cgi->saml_req));
1419 switch (cgi->op) {
1420 case 'l':
1421 if (cf->log_level>0)
1422 zxlog(cf, 0,0,0,0,0,0, ZX_GET_CONTENT(ses->nameid), "N", "W", "LOCLO", ses->sid,0);
1423 zxid_del_ses(cf, ses);
1424 cgi->msg = "Local logout Ok. Session terminated.";
1425 return zxid_simple_show_idp_sel(cf, cgi, res_len, auto_flags);
1426 case 'r':
1427 ss = zxid_sp_slo_redir(cf, cgi, ses);
1428 zxid_del_ses(cf, ses);
1429 goto redir_ok;
1430 case 's':
1431 zxid_sp_slo_soap(cf, cgi, ses);
1432 zxid_del_ses(cf, ses);
1433 cgi->msg = "SP Initiated logout (SOAP). Session terminated.";
1434 return zxid_simple_show_idp_sel(cf, cgi, res_len, auto_flags);
1435 case 't':
1436 ss = zxid_sp_mni_redir(cf, cgi, ses, zx_ref_str(cf->ctx, cgi->newnym));
1437 goto redir_ok;
1438 case 'u':
1439 zxid_sp_mni_soap(cf, cgi, ses, zx_ref_str(cf->ctx, cgi->newnym));
1440 cgi->msg = "SP Initiated defederation (SOAP).";
1441 break; /* Defederation does not have to mean SLO */
1442 case 'v': /* N.B. This is just testing facility. The result is ignored. */
1443 zxid_pep_az_soap_pepmap(cf, cgi, ses, cf->pdp_call_url?cf->pdp_call_url:cf->pdp_url, cf->pepmap, "test (o=v)");
1444 cgi->msg = "PEP-to-PDP Authorization call (SOAP).";
1445 break; /* Defederation does not have to mean SLO */
1446 case 'm':
1447 res = zxid_fed_mgmt_cf(cf, res_len, -1, cgi->sid, auto_flags);
1448 if (auto_flags & ZXID_AUTO_EXIT)
1449 exit(0);
1450 return res;
1451 case 'P': /* POST Profile Responses */
1452 case 'I':
1453 case 'K':
1454 case 'Q': /* POST Profile Requests */
1455 D("saml_req(%s) rs(%s) sigalg(%s) sig(%s)", STRNULLCHK(cgi->saml_req), STRNULLCHK(cgi->rs), STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
1456 ss = zxid_sp_dispatch(cf, cgi, ses);
1457 switch (ss->s[0]) {
1458 case 'K': return zxid_simple_show_idp_sel(cf, cgi, res_len, auto_flags);
1459 case 'L': goto redir_ok;
1460 case 'I': goto idp;
1461 }
1462 D("Q ss(%.*s) (fall thru)", ss->len, ss->s);
1463 break;
1464
1465 /* Delegation / Invitation URL clicked. */
1466 case 'D': return zxid_ps_accept_invite(cf, cgi, ses, res_len, auto_flags);
1467 case 'G': return zxid_ps_finalize_invite(cf, cgi, ses, res_len, auto_flags);
1468
1469 case 'V': /* (PXY) Middle IdP of Proxy IdP flow */
1470 ss = zxid_idp_dispatch(cf, cgi, ses, 0); /* N.B. The original request is in cgi->saml_req */
1471 goto ret_idp_dispatch;
1472 case 'R':
1473 cgi->op = 'F';
1474 /* Fall thru */
1475 case 'F': /* IdP: Return SSO A7N after successful An; no ses case, generate IdP ui */
1476 idp:
1477 ss = zxid_idp_dispatch(cf, cgi, ses, 1); /* N.B. The original request is in cgi->saml_req */
1478 ret_idp_dispatch:
1479 switch (ss->s[0]) {
1480 case 'K': return zxid_simple_show_idp_sel(cf, cgi, res_len, auto_flags); /* proxy IdP */
1481 case 'C': /* Content-type: -- i.e. ship page or XML out */
1482 case 'L':
1483 redir_ok:
1484 if (auto_flags & ZXID_AUTO_REDIR) {
1485 fprintf(stdout, "%.*s", ss->len, ss->s);
1486 fflush(stdout);
1487 zx_str_free(cf->ctx, ss);
1488 goto cgi_exit;
1489 } else
1490 goto res_zx_str;
1491 }
1492 D("idp err(%.*s) (fall thru)", ss->len, ss->s);
1493 /* *** */
1494 break;
1495 case 'H': return zxid_simple_show_rsrcreg(cf, cgi, res_len, auto_flags);
1496 case 'J': return zxid_simple_show_dynclireg(cf, cgi, res_len, auto_flags);
1497 case 'j': return zxid_simple_show_jwks(cf, cgi, res_len, auto_flags);
1498 case 'c': return zxid_simple_show_carml(cf, cgi, res_len, auto_flags);
1499 case 'd': return zxid_simple_show_conf(cf, cgi, res_len, auto_flags);
1500 case 'B': return zxid_simple_show_meta(cf, cgi, res_len, auto_flags);
1501 case 'b': return zxid_simple_md_authority(cf, cgi, res_len, auto_flags);
1502 case 'n': break;
1503 case 'p': break;
1504 default:
1505 if (cf->bare_url_entityid)
1506 return zxid_simple_show_meta(cf, cgi, res_len, auto_flags);
1507 }
1508 if (cf->required_authnctx) {
1509 zxid_get_ses_sso_a7n(cf, ses);
1510 accr = ses->a7n&&ses->a7n->AuthnStatement&&ses->a7n->AuthnStatement->AuthnContext
1511 ?ZX_GET_CONTENT(ses->a7n->AuthnStatement->AuthnContext->AuthnContextClassRef):0;
1512
1513 if (accr)
1514 for (p = cf->required_authnctx[0]; p; ++p)
1515 if (!memcmp(accr->s, p, accr->len) && !p[accr->len])
1516 goto ok;
1517
1518 /* *** arrange same session to be used after step-up authentication. */
1519
1520 D("Required AuthnCtx not satisfied by (%.*s). Step-up authentication needed.", accr&&accr->len?accr->len:1, accr&&accr->len?accr->s:"-");
1521 cgi->msg = "Step-up authentication requested.";
1522 return zxid_simple_show_idp_sel(cf, cgi, res_len, auto_flags);
1523 ok:
1524 D("Required AuthnCtx satisfied(%s)", p);
1525 }
1526
1527 /* Already successful Single Sign-On case starts here */
1528 ses->rs = cgi->rs;
1529 return zxid_simple_ab_pep(cf, ses, res_len, auto_flags);
1530
1531 cgi_exit:
1532 if (auto_flags & ZXID_AUTO_EXIT)
1533 exit(0);
1534 res = zx_dup_cstr(cf->ctx, "n");
1535 if (res_len)
1536 *res_len = 1;
1537 return res;
1538
1539 res_zx_str:
1540 res = ss->s;
1541 if (res_len)
1542 *res_len = ss->len;
1543 ZX_FREE(cf->ctx, ss);
1544 return res;
1545 }
1546
1547 /*() Subroutine of zxid_simple_cf() for the no session detected/active case.
1548 * cgi->uri_path should have been set by the caller.
1549 *
1550 * N.B. More complete documentation is available in <<link: zxid-simple.pd>> (*** fixme) */
1551
1552 /* Called by: chkuid, zxid_mini_httpd_sso, zxid_simple_cf_ses */
zxid_simple_no_ses_cf(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,int * res_len,int auto_flags)1553 char* zxid_simple_no_ses_cf(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, int* res_len, int auto_flags)
1554 {
1555 char* res = 0;
1556 struct zx_str* ss;
1557
1558 if (!cf || !cgi || !ses) {
1559 ERR("FATAL: NULL pointer. You MUST supply configuration(%p), cgi(%p), and session(%p) objects (programming error)", cf, cgi, ses);
1560 exit(1);
1561 }
1562 if (cf->wd && *cf->wd)
1563 chdir(cf->wd);
1564
1565 D("op(%c) cf=%p cgi=%p ses=%p auto=%x wd(%s)", cgi->op?cgi->op:'-', cf, cgi, ses, auto_flags, STRNULLCHKD(cf->wd));
1566 if (!cgi->op && cf->defaultqs && cf->defaultqs[0]) {
1567 zxid_parse_cgi(cf, cgi, cf->defaultqs);
1568 INFO("DEFAULTQS(%s) op(%c)", cf->defaultqs, cgi->op?cgi->op:'-');
1569 }
1570
1571 switch (cgi->op) {
1572 case 'M': /* Invoke LECP or redirect to CDC reader. */
1573 ss = zxid_lecp_check(cf, cgi);
1574 D("LECP check: ss(%.*s)", ss?ss->len:1, ss?ss->s:"?");
1575 if (ss) {
1576 if (auto_flags & ZXID_AUTO_REDIR) {
1577 fprintf(stdout, "%.*s", ss->len, ss->s);
1578 fflush(stdout);
1579 zx_str_free(cf->ctx, ss);
1580 goto cgi_exit;
1581 } else
1582 goto res_zx_str;
1583 } else {
1584 if (auto_flags & ZXID_AUTO_REDIR) {
1585 fprintf(stdout, "Location: %s?o=C" CRLF2, cf->cdc_url);
1586 fflush(stdout);
1587 goto cgi_exit;
1588 } else {
1589 ss = zx_strf(cf->ctx, "Location: %s?o=C" CRLF2, cf->cdc_url);
1590 goto res_zx_str;
1591 }
1592 }
1593 case 'C': /* CDC Read: Common Domain Cookie Reader */
1594 ss = zxid_cdc_read(cf, cgi);
1595 if (auto_flags & ZXID_AUTO_REDIR) {
1596 fprintf(stdout, "%.*s", ss->len, ss->s);
1597 fflush(stdout);
1598 zx_str_free(cf->ctx, ss);
1599 goto cgi_exit;
1600 } else
1601 goto res_zx_str;
1602 case 'E': /* Return from CDC read, or start here to by-pass CDC read. */
1603 ss = zxid_lecp_check(cf, cgi); /* use o=E&fc=1&fn=p to set allow create true */
1604 D("LECP check: ss(%.*s)", ss?ss->len:1, ss?ss->s:"?");
1605 if (ss) {
1606 if (auto_flags & ZXID_AUTO_REDIR) {
1607 fprintf(stdout, "%.*s", ss->len, ss->s);
1608 fflush(stdout);
1609 zx_str_free(cf->ctx, ss);
1610 goto cgi_exit;
1611 } else
1612 goto res_zx_str;
1613 }
1614 if (zxid_cdc_check(cf, cgi))
1615 return 0;
1616 D("NOT CDC %d", 0);
1617 break;
1618 case 'L':
1619 if (ss = zxid_start_sso_location(cf, cgi)) {
1620 if (auto_flags & ZXID_AUTO_REDIR) {
1621 printf("%.*s", ss->len, ss->s);
1622 goto cgi_exit;
1623 } else {
1624 goto res_zx_str;
1625 }
1626 }
1627 break;
1628 case 'A':
1629 D("Process artifact(%s) pid=%d", cgi->saml_art, getpid());
1630 switch (zxid_sp_deref_art(cf, cgi, ses)) {
1631 case ZXID_REDIR_OK: ERR("*** Odd, redirect on artifact deref. %d", 0); break;
1632 case ZXID_SSO_OK:
1633 return zxid_show_protected_content_setcookie(cf, cgi, ses, res_len, auto_flags);
1634 }
1635 break;
1636 case 'O':
1637 D("Process OAUTH2 / OpenID-Connect1 pid=%d", getpid());
1638 ss = zxid_sp_oauth2_dispatch(cf, cgi, ses);
1639 goto post_dispatch;
1640 case 'T':
1641 D("Process OAUTH2 / OpenID-Connect1 check id pid=%d", getpid());
1642 return zxid_idp_oauth2_token_and_check_id(cf, cgi, ses, res_len, auto_flags);
1643 case 'P': /* POST Profile Responses */
1644 case 'I':
1645 case 'K':
1646 case 'Q': /* POST Profile Requests */
1647 DD("PRE saml_req(%s) saml_resp(%s) rs(%s) sigalg(%s) sig(%s)", STRNULLCHK(cgi->saml_req), STRNULLCHK(cgi->saml_resp), cgi->rs, cgi->sigalg, cgi->sig);
1648 ss = zxid_sp_dispatch(cf, cgi, ses);
1649 post_dispatch:
1650 D("POST dispatch_loc(%s)", ss->s);
1651 switch (ss->s[0]) {
1652 case 'O': return zxid_show_protected_content_setcookie(cf, cgi, ses, res_len, auto_flags);
1653 case 'M': return zxid_simple_ab_pep(cf, ses, res_len, auto_flags); /* Mgmt screen case */
1654 case 'L': /* Location */
1655 if (auto_flags & ZXID_AUTO_REDIR) {
1656 fprintf(stdout, "%.*s", ss->len, ss->s);
1657 fflush(stdout);
1658 zx_str_free(cf->ctx, ss);
1659 goto cgi_exit;
1660 } else
1661 goto res_zx_str;
1662 case 'I': goto idp;
1663 }
1664 D("Q err (fall thru) %d", 0);
1665 break;
1666 case 'H': return zxid_simple_show_rsrcreg(cf, cgi, res_len, auto_flags);
1667 case 'J': return zxid_simple_show_dynclireg(cf, cgi, res_len, auto_flags);
1668 case 'j': return zxid_simple_show_jwks(cf, cgi, res_len, auto_flags);
1669 case 'c': return zxid_simple_show_carml(cf, cgi, res_len, auto_flags);
1670 case 'd': return zxid_simple_show_conf(cf, cgi, res_len, auto_flags);
1671 case 'B': return zxid_simple_show_meta(cf, cgi, res_len, auto_flags);
1672 case 'b': return zxid_simple_md_authority(cf, cgi, res_len, auto_flags);
1673 case 'D': /* Delegation / Invitation URL clicked. */
1674 return zxid_ps_accept_invite(cf, cgi, ses, res_len, auto_flags);
1675 case 'R':
1676 cgi->op = 'F';
1677 /* Fall thru */
1678 case 'F':
1679 idp: return zxid_simple_idp_show_an(cf, cgi, res_len, auto_flags);
1680 case 'p': return zxid_simple_idp_pw_authn(cf, cgi, res_len, auto_flags);
1681 case 'N': return zxid_simple_idp_new_user(cf, cgi, res_len, auto_flags);
1682 case 'W': return zxid_simple_idp_recover_password(cf, cgi, res_len, auto_flags);
1683 default:
1684 if (cf->bare_url_entityid)
1685 return zxid_simple_show_meta(cf, cgi, res_len, auto_flags);
1686 D("unknown op(%c)", cgi->op);
1687 }
1688 return zxid_simple_show_idp_sel(cf, cgi, res_len, auto_flags);
1689
1690 cgi_exit:
1691 if (auto_flags & ZXID_AUTO_EXIT)
1692 exit(0);
1693 res = zx_dup_cstr(cf->ctx, "n");
1694 if (res_len)
1695 *res_len = 1;
1696 return res;
1697
1698 res_zx_str:
1699 res = ss->s;
1700 if (res_len)
1701 *res_len = ss->len;
1702 ZX_FREE(cf->ctx, ss);
1703 return res;
1704 }
1705
1706 /*(i) Simple handler that assumes the configuration has already been read in.
1707 * The memory for result is grabbed from ZX_ALLOC(), usually malloc(3)
1708 * and is "given" away to the caller, i.e. caller must free it. The
1709 * return value is LDIF (or JSON or query string, if configured)
1710 * of attributes in success case.
1711 * res_len, if non-null, will receive the length of the response.
1712 *
1713 * The major advantage of zxid_simple_cf_ses() is that the session stays
1714 * as binary object and does not need to be recreated / reparsed from
1715 * filesystem representation. The object can be directly used for PEP
1716 * calls (but see inline PEP call enabled by PDPURL) and WSC.
1717 *
1718 * cf:: Configuration object
1719 * qs_len:: Length of the query string. -1 = use strlen()
1720 * qs:: Query string (or POST content)
1721 * ses:: Session object
1722 * res_len:: Result parameter. If non-null, will be set to the length of the returned string
1723 * auto_flags:: Automation flags, see zxid-simple.pd for documentation
1724 * return:: String representing protocol action or SSO attributes
1725 *
1726 * N.B. More complete documentation is available in <<link: zxid-simple.pd>> (*** fixme) */
1727
1728 /* Called by: zxid_simple_cf */
zxid_simple_cf_ses(zxid_conf * cf,int qs_len,char * qs,zxid_ses * ses,int * res_len,int auto_flags)1729 char* zxid_simple_cf_ses(zxid_conf* cf, int qs_len, char* qs, zxid_ses* ses, int* res_len, int auto_flags)
1730 {
1731 int got, ret;
1732 char* remote_addr;
1733 char* cont_len;
1734 char* buf = 0;
1735 char* res = 0;
1736 zxid_cgi cgi;
1737 ZERO(&cgi, sizeof(cgi));
1738
1739 if (!cf || !ses) {
1740 ERR("NULL pointer. You MUST supply configuration object %p and session object %p (programming error). auto_flags=%x", cf, ses, auto_flags);
1741 exit(1);
1742 }
1743
1744 /*fprintf(stderr, "qs(%s) arg, autoflags=%x\n", qs, auto_flags);*/
1745 if (auto_flags & ZXID_AUTO_DEBUG) zxid_set_opt(cf, 1, 3);
1746 LOCK(cf->mx, "simple ipport");
1747 if (!cf->ipport) {
1748 remote_addr = getenv("REMOTE_ADDR");
1749 if (remote_addr) {
1750 ses->ipport = ZX_ALLOC(cf->ctx, strlen(remote_addr) + 6 + 1); /* :12345 */
1751 sprintf(ses->ipport, "%s:-", remote_addr);
1752 cf->ipport = ses->ipport;
1753 }
1754 }
1755 UNLOCK(cf->mx, "simple ipport");
1756
1757 cgi.uri_path = getenv("SCRIPT_NAME");
1758 cgi.qs = qs; /* save orig for use in zxid_sso_set_relay_state_to_return_to_this_url() */
1759
1760 if (!qs) {
1761 cgi.qs = qs = getenv("QUERY_STRING");
1762 if (qs) {
1763 qs = zx_dup_cstr(cf->ctx, qs);
1764 D("QUERY_STRING(%s) %s %d", STRNULLCHK(qs), ZXID_REL, errmac_debug);
1765 zxid_parse_cgi(cf, &cgi, qs);
1766 if (ONE_OF_8(cgi.op, 'H', 'J', 'P', 'R', 'S', 'T', 'Y', 'Z')) {
1767 cont_len = getenv("CONTENT_LENGTH");
1768 if (cont_len) {
1769 sscanf(cont_len, "%d", &got);
1770 D("o=%c cont_len=%s got=%d rel=%s", cgi.op, cont_len, got, ZXID_REL);
1771 cgi.post = buf = ZX_ALLOC(cf->ctx, got + 1 /* nul term */);
1772 if (!buf) {
1773 ERR("out of memory len=%d", got);
1774 exit(1);
1775 }
1776 if (read_all_fd(fdstdin, buf, got, &got) == -1) {
1777 perror("Trouble reading post content.");
1778 } else {
1779 buf[got] = 0;
1780 DD("POST(%s) got=%d cont_len(%s)", buf, got, cont_len);
1781 D_XML_BLOB(cf, "POST", got, buf);
1782 if (buf[0] == '<') goto sp_soap; /* No BOM and looks like XML */
1783 if (buf[0] == '{') goto json; /* No BOM and looks like JSON */
1784 if (buf[2] == '<') { /* UTF-16 BOM and looks like XML */
1785 got-=2; buf+=2;
1786 ERR("UTF-16 NOT SUPPORTED %x%x", buf[0], buf[1]);
1787 goto sp_soap;
1788 }
1789 if (buf[2] == '{') { /* UTF-16 BOM and looks like JSON */
1790 got-=2; buf+=2;
1791 ERR("UTF-16 NOT SUPPORTED %x%x", buf[0], buf[1]);
1792 goto json;
1793 }
1794 if (buf[3] == '<') { /* UTF-8 BOM and looks XML */
1795 got-=3; buf+=3;
1796 sp_soap:
1797 /* *** TODO: SOAP response should not be sent internally unless there is auto */
1798 ret = zxid_sp_soap_parse(cf, &cgi, ses, got, buf);
1799 D("POST soap parse returned %d (0=fail, 1=ok, 2=redir, 3=sso ok)", ret);
1800 if (ret == ZXID_SSO_OK)
1801 return zxid_simple_ab_pep(cf, ses, res_len, auto_flags);
1802 if (auto_flags & ZXID_AUTO_SOAPC || auto_flags & ZXID_AUTO_SOAPH) {
1803 if (auto_flags & ZXID_AUTO_EXIT)
1804 exit(0);
1805 res = zx_dup_cstr(cf->ctx, "n");
1806 if (res_len)
1807 *res_len = 1;
1808 goto done;
1809 }
1810 res = zx_dup_cstr(cf->ctx, ret ? "n" : "*** SOAP error (enable debug if you want to see why)");
1811 if (res_len)
1812 *res_len = strlen(res);
1813 goto done;
1814 }
1815 if (buf[3] == '{') { /* UTF-8 BOM and looks JSON */
1816 got-=3; buf+=3;
1817 json:
1818 D("JSON detected %s", buf);
1819 /* Do not parse yet, this will be handled later in zxid_simple code. */
1820 } else
1821 zxid_parse_cgi(cf, &cgi, buf);
1822 }
1823 } else {
1824 D("o=%c post, but no CONTENT_LENGTH rel=%s", cgi.op, ZXID_REL);
1825 }
1826 } else {
1827 D("o=%c other rel=%s", cgi.op, ZXID_REL);
1828 }
1829 }
1830 } else {
1831 if (qs_len == -1)
1832 qs_len = strlen(qs);
1833 if (qs[qs_len]) { /* *** may read one past end of buffer in non-nulterm case */
1834 ERR("IMPLEMENTATION LIMIT: Query String MUST be nul terminated len=%d", qs_len);
1835 exit(1);
1836 }
1837 D("QUERY_STRING(%s) %s", STRNULLCHK(qs), ZXID_REL);
1838 if (qs)
1839 qs = zx_dup_cstr(cf->ctx, qs);
1840 zxid_parse_cgi(cf, &cgi, qs);
1841 }
1842
1843 if (!cgi.op && !cf->bare_url_entityid)
1844 cgi.op = 'M'; /* By default, if no ses, check CDC and offer SSO */
1845
1846 if (!cgi.sid && cf->ses_cookie_name && *cf->ses_cookie_name)
1847 zxid_get_sid_from_cookie(cf, &cgi, getenv("HTTP_COOKIE"));
1848
1849 if (cgi.sid) {
1850 if (!zxid_get_ses(cf, ses, cgi.sid)) {
1851 D("No session(%s) active op(%c)", cgi.sid, cgi.op);
1852 } else
1853 if (res = zxid_simple_ses_active_cf(cf, &cgi, ses, res_len, auto_flags))
1854 goto done;
1855 }
1856
1857 ZERO(ses, sizeof(zxid_ses)); /* No session yet! Process login form */
1858 res = zxid_simple_no_ses_cf(cf, &cgi, ses, res_len, auto_flags);
1859
1860 done:
1861 if (qs)
1862 ZX_FREE(cf->ctx, qs);
1863 if (buf)
1864 ZX_FREE(cf->ctx, buf);
1865 return res;
1866 }
1867
1868 /*() Allocate simple session and then call simple handler. Strings
1869 * are length + pointer (no C string nul termination needed).
1870 * A wrapper for zxid_simple_cf().
1871 *
1872 * cf:: Configuration object
1873 * qs_len:: Length of the query string. -1 = use strlen()
1874 * qs:: Query string (or POST content)
1875 * res_len:: Result parameter. If non-null, will be set to the length of the returned string
1876 * auto_flags:: Automation flags, see zxid-simple.pd for documentation
1877 * return:: String representing protocol action or SSO attributes
1878 *
1879 * N.B. More complete documentation is available in <<link: zxid-simple.pd>> (*** fixme) */
1880
1881 /* Called by: main x3, zxid_simple_len, zxidwspcgi_main */
zxid_simple_cf(zxid_conf * cf,int qs_len,char * qs,int * res_len,int auto_flags)1882 char* zxid_simple_cf(zxid_conf* cf, int qs_len, char* qs, int* res_len, int auto_flags)
1883 {
1884 zxid_ses ses;
1885 ZERO(&ses, sizeof(ses));
1886 return zxid_simple_cf_ses(cf, qs_len, qs, &ses, res_len, auto_flags);
1887 }
1888
1889 /*() Process simple configuration and then call simple handler. Strings
1890 * are length + pointer (no C string nul termination needed).
1891 * a wrapper for zxid_simple_cf().
1892 *
1893 * N.B. More complete documentation is available in <<link: zxid-simple.pd>> (*** fixme) */
1894
1895 /* Called by: zxid_simple */
zxid_simple_len(int conf_len,char * conf,int qs_len,char * qs,int * res_len,int auto_flags)1896 char* zxid_simple_len(int conf_len, char* conf, int qs_len, char* qs, int* res_len, int auto_flags)
1897 {
1898 struct zx_ctx ctx;
1899 zxid_conf cf;
1900 zx_reset_ctx(&ctx);
1901 ZERO(&cf, sizeof(cf));
1902 cf.ctx = &ctx;
1903 zxid_conf_to_cf_len(&cf, conf_len, conf);
1904 return zxid_simple_cf(&cf, qs_len, qs, res_len, auto_flags);
1905 }
1906
1907 /*() Main simple interface. C string nul termination is assumed. Really just
1908 * a wrapper for zxid_simple_cf().
1909 *
1910 * N.B. More complete documentation is available in <<link: zxid-simple.pd>> (*** fixme) */
1911
1912 /* Called by: main x4 */
zxid_simple(char * conf,char * qs,int auto_flags)1913 char* zxid_simple(char* conf, char* qs, int auto_flags)
1914 {
1915 return zxid_simple_len(-1, conf, -1, qs, 0, auto_flags);
1916 }
1917
1918 /* EOF -- zxidsimp.c */
1919