1 /* zxidcgi.c  -  Handwritten functions for parsing SP specific CGI options
2  * Copyright (c) 2012-2013 Synergetics NV (sampo@synergetics.be), All Rights Reserved.
3  * Copyright (c) 2010-2011 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.
4  * Copyright (c) 2006-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: zxidcgi.c,v 1.33 2010-01-08 02:10:09 sampo Exp $
11  *
12  * 12.8.2006,  created --Sampo
13  * 16.1.2007,  split from zxidlib.c --Sampo
14  * 12.10.2007, added cookie scanning --Sampo
15  * 7.10.2008,  added documentation --Sampo
16  * 10.12.2011, added OAuth2, OpenID Connect, and UMA support --Sampo
17  * 20.10.2012, made the fr to rs copy cause deflate safe base64 encode --Sampo
18  * 14.3.2013   added language/skin dependent templates --Sampo
19  * See also: http://hoohoo.ncsa.uiuc.edu/cgi/interface.html (CGI specification)
20  */
21 
22 #include <memory.h>
23 #include <string.h>
24 #include "platform.h"
25 #include "errmac.h"
26 #include "zxid.h"
27 #include "zxidconf.h"
28 #include "zxidutil.h"
29 
30 /* ============== CGI Parsing ============== */
31 
32 /*(i) Parse query string or form POST and detect parameters relevant for ZXID.
33  * N.B. This CGI parsing is very specific for needs of ZXID. It is not generic.
34  *
35  * cf:: ZXID configuration object, for occasional memory allocation.
36  * cgi:: Already allocated CGI structure where results of this function
37  *     are deposited. Note that this structure is not cleared. Thus it is
38  *     possible to call zxid_parse_cgi() multiple times to accumulate
39  *     results from multiple sources, e.g. foirst for query string, and then
40  *     for form POST.
41  * qs:: CGI formatted input. Usually query string or form POST content.
42  *     qs is modified in-place  e.g. to perform url decoding and nul termination.
43  *     References to the qs will be taken, e.g. pointers in cgi struct point
44  *     to it, thus qs MUST NOT be freed before cgi struct is freed, which
45  *     may be long after the call to zxid_parse_cgi().
46  * return:: 0 on success. Other values reserved. Usually return value is
47  *     ignored as there really is no way for this function to fail. Unrecognized
48  *     CGI arguments are simply ignored with assumption that some other processing
49  *     layer will pick them up - hence no need to flag error. */
50 
51 /* Called by:  chkuid x2, main x4, zxid_decode_ssoreq, zxid_mini_httpd_sso x2, zxid_new_cgi, zxid_simple_cf_ses x3, zxid_simple_no_ses_cf */
zxid_parse_cgi(zxid_conf * cf,zxid_cgi * cgi,char * qs)52 int zxid_parse_cgi(zxid_conf* cf, zxid_cgi* cgi, char* qs)
53 {
54   char *p, *n, *v;
55   DD("qs(%s)=%p len=%d", STRNULLCHK(qs), qs, qs?strlen(qs):-1);
56   if (!qs)
57     return 0;
58 #if 1
59   p = getenv("HTTP_USER_AGENT");
60   if (p && !memcmp(p, "Synmobile", sizeof("Synmobile")-1)) {
61     D("Mobile detected UA(%s)", p);
62     cgi->mob = 1;
63   }
64 #endif
65 
66   while (qs && *qs) {
67     qs = zxid_qs_nv_scan(qs, &n, &v, 2);
68     if (!n)
69       n = "NULL_NAME_ERROR";
70     DD("n(%s)=v(%s) qs(%s)=%p len=%d", n,v, STRNULLCHKNULL(qs), qs, qs?strlen(qs):-1);
71     switch (n[0]) {
72     case 'n':
73       if (!strcmp(n, "nonce")) { cgi->nonce = v; break; }
74       goto unknown;
75     case 'o':
76       if (!n[1]) { cgi->op = v[0];    break; }
77       if (n[1] = 'k' && !n[2]) { cgi->ok = v;  break; }  /* ok button */
78       goto unknown;
79     case 'p':
80       if (!strcmp(n, "prompt")) { cgi->prompt = v; break; }  /* OAUTH2 */
81       if (!strcmp(n, "pcode"))  { cgi->pcode = v; break; }   /* Mobile pairing code */
82       goto unknown;
83     case 'r':
84       if (!strcmp(n, "response_type")) { cgi->response_type = v; break; }  /* OAUTH2/OIDC1 */
85       if (!strcmp(n, "redirect_uri"))  { cgi->redirect_uri = v;	 break; }  /* OAUTH2 */
86       if (!strcmp(n, "redirafter"))    { cgi->redirafter = v;	 break; }
87       if (!strcmp(n, "rs"))            { cgi->rs = v; break; }
88       if (!strcmp(n, "rest"))          { cgi->rest = v; break; }
89       if (!strcmp(n, "refresh_token")) { cgi->refresh_token = v; break; }  /* OAUTH2 */
90       goto unknown;
91     case 's':
92       if (!n[1]) { cgi->sid = v; break; }
93       if (!strcmp(n, "scope"))  { cgi->scope = v; break; }   /* OAUTH2 */
94       if (!strcmp(n, "state"))  { cgi->state = v; break; }   /* OAUTH2 */
95       if (!strcmp(n, "schema")) { cgi->schema = v; break; }  /* OAUTH2 */
96       goto unknown;
97     case 't':
98       if (!strcmp(n, "token_type")) { cgi->token_type = v; break; }  /* OAUTH2 */
99       if (!strcmp(n, "templ"))   { DD("Detect templ(%s) cgi=%p",v,cgi); cgi->templ = v; break; }
100       goto unknown;
101     case 'u':
102       if (!strcmp(n, "user_id")) { cgi->user_id = v; break; }   /* OAUTH2 */
103       goto unknown;
104     case 'c':
105       if (!n[1]) { cgi->cdc = v; break; }
106       if (!strcmp(n, "client_id")) { cgi->client_id = v; break; }    /* OAUTH2 */
107       if (!strcmp(n, "code"))      { cgi->code = v;      break; }    /* OAUTH2 */
108       goto unknown;
109       /* The following two entity IDs, combined with various login buttons
110        * aim at supporting may different user interface layouts. You need to
111        * understand how they interact to avoid undesired conflicts. */
112     case 'e':  /* EntityID field (manual entry). Overrides 'd'. */
113       if (!strcmp(n, "expires_in")) { cgi->expires_in = atoi(v); break; }  /* OAUTH2 */
114       if (!strcmp(n, "exp"))        { cgi->exp = v; break; }    /* OAUTH2 */
115       if (!n[1]) {
116 set_eid:
117 	if (v[0]) cgi->eid = v;
118 	DD("v(%s) v0=0x%02x v=%p cgi->eid=%p cgi=%p", v, v[0], v, cgi->eid, cgi);
119 	break;
120       }
121       goto unknown;
122     case 'd':  /* EntityID popup or radio box */
123       if (!n[1]) {
124 	if (cgi->eid && cgi->eid[0]) {
125 	  D("EID already set v(%s) v0=0x%02x v=%p cgi->eid=%p cgi=%p", v, v[0], v, cgi->eid, cgi);
126 	  break;
127 	}
128 	goto set_eid;
129       }
130       if (!strcmp(n, "display")) { cgi->display = v; break; }
131       goto unknown;
132     case 'l':
133       /* Login button names are like lP<eid>, where "l" is literal ell, P is
134        * protocol profile designator, and <eid> is Entity ID of the IdP.
135        * N.B. If eid is omitted from button name, it may be provided using
136        * d or e fields (see above). */
137       cgi->pr_ix = n[1]-'0';
138       if (n[2]) {
139 	cgi->eid = n+2;
140 	/*if (cf->idp_list_meth == ZXID_IDP_LIST_BRAND)*/
141 	/* We need to remove the .x and/or .y from the end (image map coordinates) */
142 	p = strchr(cgi->eid, 0);  /* Pointer to end of string */
143 	DD("p[-2]=%x (%c%c) n=%p p=%p name(%s) val(%s)", p[-2], p[-2], p[-1], n, p, n, v);
144 	if (p[-2] == '.' && ONE_OF_2(p[-1], 'x', 'y')) {
145 	  p[-2] = 0;
146 	  DD("eid=%p eid(%s) p[-2]=%x n=%p p=%p v=%p (%s)=(%s)", cgi->eid, cgi->eid, p[-2], n, p, v, n, v);
147 	}
148       }
149       cgi->op = 'L';
150       D("cgi: login eid=%p eid(%s)", cgi->eid, cgi->eid);
151       break;
152     case 'k':
153       DD("k CGI field(%s) val(%s) cgi=%p", n, v, cgi);
154       if (!n[1]) { cgi->skin = v;    break; }  /* often used for lang as well */
155       goto unknown;
156     case 'i':
157       if (!strcmp(n, "id_token")) { cgi->id_token = v; break; }  /* OAUTH2 */
158       if (!strcmp(n, "iss")) { cgi->iss = v; break; }            /* OAUTH2 */
159       if (!strcmp(n, "iso29115")) { cgi->iso29115 = v; break; }  /* OAUTH2 */
160       if (!strcmp(n, "id"))  { cgi->id = v; break; }             /* OAUTH2 */
161       if (!strcmp(n, "inv")) { cgi->inv = v; break; }            /* OAUTH2 */
162       /* IdP and protocol index selection popup values are like P<eid>
163        * N.B. If eid is omitted from button name, it may be provided using
164        * d or e fields (see above). This effectively allows i to be just
165        * a protocol selection popup. */
166       cgi->pr_ix = v[0]-'0';
167       if (v[1])
168 	cgi->eid = v+1;
169       break;
170     case 'f':  /* flags and (hidden) fields found in typical SP login form */
171       if (!n[1] || n[2]) goto unknown;
172       switch (n[1]) {  /* Typical fields of SP side idp selection form (or query string) */
173       case 'a': cgi->authn_ctx = v;       D("authn_ctx=%s", cgi->authn_ctx); break; /* fa */
174       case 'c': cgi->allow_create = v[0]; D("allow_create=%c", cgi->allow_create); break; /* fc */
175       case 'f': cgi->force_authn = v[0];  D("force_authn=%c", cgi->force_authn); break; /* ff */
176       case 'g': cgi->get_complete = v;    break;   /* fg */
177       case 'h': cgi->pxy_count = v;       break;   /* fh */
178 	/*case 'i': cgi->idp_list = v;    break;*/ /* fi */
179       case 'm': cgi->matching_rule = v;   break;   /* fm */
180       case 'n': cgi->nid_fmt = v;         D("nid_fmt=%s", cgi->nid_fmt); break;     /* fn */
181       case 'p': cgi->ispassive = v[0];    D("ispassive=%c", cgi->ispassive); break; /* fp */
182       case 'q': cgi->affil = v;           break;   /* fq */
183       case 'r': cgi->rs = zxid_deflate_safe_b64_raw(cf->ctx, -2, v); break;         /* fr */
184       case 'y': cgi->consent = v;         break;   /* fy */
185       }
186       break;
187     case 'g':  /* management (gestion) form fields or query string arguments */
188       switch (n[1]) {
189       case 'r': /* gr - single logout redirect */
190 	if (!strcmp(n, "grant_type")) {    /* OAUTH2 */
191 	  cgi->grant_type = v;
192 	  break;
193 	}
194       case 'l': /* gl - local logout */
195       case 's': /* gs - single logout SOAP */
196       case 't': /* gt - defederate redir */
197       case 'u': cgi->op = n[1];           break; /* gu - defederate SOAP */
198       case 'n': cgi->newnym = v;          break; /* gn */
199       case 'e': cgi->enc_hint = v[0];     break; /* ge */
200       case 0: goto unknown; /* N.B. single letter g=GrantToken in zxidgrant.pl */
201       }
202       break;
203     case 'a':
204       if (!n[1]) goto unknown;
205       if (!strcmp(n, "access_token")) { cgi->access_token = v; break; }    /* OAUTH2 */
206       if (!strcmp(n, "aud"))          { cgi->aud = v; break; }             /* OAUTH2 */
207       switch (n[1]) {
208       case 'l': if (n[3]) goto unknown;  cgi->op = n[2];           break;  /* al = login */
209       case 'u': if (n[2]) goto unknown;  if (v[0] || !cgi->uid) cgi->uid = v; break; /* au =user */
210       case 'p': if (n[2]) goto unknown;  cgi->pw = v;              break;  /* ap = password */
211       case 'q': if (n[2]) goto unknown;  cgi->pin = v;             break;  /* aq = pin */
212       case 'r': if (n[2]) goto unknown;  cgi->ssoreq = v;          break;  /* ar = AnRq */
213       case 'n': if (n[2]) goto unknown;  cgi->op = 'N';            break;  /* an = new user */
214       case 'w': if (n[2]) goto unknown;  cgi->op = 'W';            break;  /* aw = recover pw */
215       case 't': if (n[2]) goto unknown;  cgi->atselafter = 1;      break;  /* at */
216       }
217       break;
218     case 'z':
219       switch (n[1]) {
220       case 'x':
221 	switch (n[2]) {
222 	case 'a': cgi->zxapp = v;         break;
223 	case 'r': cgi->zxrfr = v;         break;
224 	}
225 	break;
226       }
227       break;
228     case 'R':
229       if (!strcmp(n, "RelayState")) { cgi->rs = v; break; }
230       goto unknown;
231     case 'S':
232       if (!strcmp(n, "SAMLart")) {
233 	cgi->saml_art = v;
234 	cgi->op = 'A';
235 	break;
236       }
237       if (!strcmp(n, "SAMLResponse")) {
238 	cgi->saml_resp = v;
239 	cgi->op = 'P';
240 	break;
241       }
242       if (!strcmp(n, "SAMLRequest")) {
243 	cgi->saml_req = v;
244 	if (!ONE_OF_3(cgi->op, 'p', 'F', 'R'))  /* Avoid redundant sigvfy and processing for IdP */
245 	  cgi->op = 'Q';
246 	break;
247       }
248       if (!strcmp(n, "SigAlg")) {
249 	cgi->sigalg = v;
250 	break;
251       }
252       if (!strcmp(n, "Signature")) {
253 	cgi->sig = v;
254 	break;
255       }
256       goto unknown;
257     case '_':
258       if (!strcmp(n, "_uma_authn")) {
259 	/* Decode in place: it should always fit */
260 	p = unbase64_raw(v, v+strlen(v), v, zx_std_index_64);
261 	*p = 0;
262 	cgi->uid = v;
263 	p = strchr(v, ':');
264 	if (p) {
265 	  *p = 0;
266 	  cgi->pw = p+1;
267 	} else {
268 	  ERR("Malformed _uma_authn token(%s): no colon found", v);
269 	}
270 	break;
271       }
272       /* fall thru */
273     unknown:
274     default:  D("Unknown CGI field(%s) val(%s) cgi=%p", n, v, cgi);
275     }
276   }
277   DD("END cgi=%p cgi->eid=%p (%s) op(%c) magic=%x", cgi, cgi->eid, cgi->eid, cgi->op, cgi->magic);
278   return 0;
279 }
280 
281 /* Called by:  covimp_test */
zxid_new_cgi(zxid_conf * cf,char * qs)282 zxid_cgi* zxid_new_cgi(zxid_conf* cf, char* qs)
283 {
284   zxid_cgi* cgi = ZX_ZALLOC(cf->ctx, zxid_cgi);
285   cgi->magic = ZXID_CGI_MAGIC;
286   if (qs) {
287     char* qqs;
288     int len = strlen(qs);
289     qqs = ZX_ALLOC(cf->ctx, len+1);
290     memcpy(qqs, qs, len);
291     qqs[len] = 0;
292     zxid_parse_cgi(cf, cgi, qqs);
293   }
294   return cgi;
295 }
296 
297 /*() Try to extract session ID from a cookie. The extracted value, if any,
298  * will be deposited in cgi->sid. If no session ID is found, then cgi->sid
299  * is not modified. The name of the cookie is determined by configuration
300  * option ~SES_COOKIE_NAME~ (see zxidconf.h).
301  * N.B. The session itself is not loaded, just the cgi->sid is populated.
302  *
303  * return:: none, but cgi->sid is modified
304  *
305  * For original Netscape cookie spec see: http://curl.haxx.se/rfc/cookie_spec.html (Oct2007)
306  *
307  * *Example*
308  *
309  *    ONE_COOKIE=aaa; ZXIDSES=S12cvd324; SOME_OTHER_COOKIE=...
310  */
311 
312 /* Called by:  chkuid, zxid_mini_httpd_sso, zxid_simple_cf_ses */
zxid_get_sid_from_cookie(zxid_conf * cf,zxid_cgi * cgi,const char * cookie)313 void zxid_get_sid_from_cookie(zxid_conf* cf, zxid_cgi* cgi, const char* cookie)
314 {
315   char* q;
316   int len;
317   if (!cookie)
318     return;
319   len = strlen(cf->ses_cookie_name);
320   for (cookie = strstr(cookie, cf->ses_cookie_name);
321        cookie;
322        cookie = strstr(cookie + 1, cf->ses_cookie_name))
323     if (cookie[len] == '=') {
324       cookie += len + 1;
325       if (*cookie == '"')
326 	++cookie;
327       q = strchr(cookie, ';');
328       len = q ? (q-cookie) : strlen(cookie);
329       if (cookie[len-1] == '"')
330 	--len;
331       cgi->sid = ZX_ALLOC(cf->ctx, len + 1);
332       memcpy(cgi->sid, cookie, len);
333       cgi->sid[len] = 0;
334       return;
335     }
336 }
337 
338 /* EOF  --  zxidcgi.c */
339