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 */ 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 */ 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 */ 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