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