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