1 /* mod_auth_saml.c  -  Handwritten functions for Apache mod_auth_saml module
2  * Copyright (c) 2012-2015 Synergetics NV (sampo@synergetics.be), All Rights Reserved.
3  * Copyright (c) 2009-2011 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.
4  * Copyright (c) 2008-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 or as licensed below.
9  * Licensed under Apache License 2.0, see file COPYING.
10  * $Id: mod_auth_saml.c,v 1.17 2010-01-08 02:10:09 sampo Exp $
11  *
12  * 1.2.2008,  created --Sampo
13  * 22.2.2008, distilled to much more compact version --Sampo
14  * 25.8.2009, add attribute passing and pep call --Sampo
15  * 11.1.2010, refactoring and review --Sampo
16  * 15.7.2010, consider passing to simple layer more data about the request --Sampo
17  * 28.9.2012, changed zx_instance string to "mas", fixed parsing CGI for other page --Sampo
18  * 13.2.2013, added WD option --Sampo
19  * 21.6.2013, added SOAP WSP capability --Sampo
20  * 17.11.2013, move redir_to_content feature to zxid_simple() --Sampo
21  * 8.2.2014,  added OPTIONAL_LOGIN_PAT feature --Sampo
22  * 5.3.2015,  improved Apache httpd-2.4 compatibility --Sampo
23  * 9.3.2015,  refactored to isolate httpd version dependencies to httpdglue.c --Sampo
24  * 20151218,  added special placeholder user "-anon-" for the 2.4 optional_login_pat case --Sampo
25  *
26  * To configure this module add to httpd.conf something like
27  *
28  *   LoadModule auth_saml_module modules/mod_auth_saml.so
29  *   <Location /secret>
30  *     Require valid-user
31  *     AuthType "saml"
32  *     ZXIDConf "URL=https://sp1.zxidsp.org:8443/secret/saml"
33  *   </Location>
34  *
35  * http://httpd.apache.org/docs/2.2/developer/
36  * http://modules.apache.org/doc/API.html
37  *
38  * Apache 2.4 Quirks
39  *
40  * [Mon Apr 13 23:04:07.291360 2015] [core:error] [pid 4841:tid 139900761208576] [client 127.0.0.1:60629] AH00027: No authentication done but request not allowed without authentication for /protected/index.html. Authentication not configured?
41  *
42  * See: httpd-2.4.12/server/request.c lines 250 and 287 (basically r->user needs to be set)
43  */
44 
45 #define _LARGEFILE64_SOURCE   /* So off64_t is found, see: man 3 lseek64 */
46 
47 #include <zx/platform.h>
48 #include <zx/errmac.h>
49 #include <zx/zxid.h>
50 #include <zx/zxidpriv.h>
51 #include <zx/zxidconf.h>
52 #include <zx/zxidutil.h>
53 #include <zx/c/zxidvers.h>
54 
55 #ifdef MINGW
56 /* apr.h defines these */
57 #undef uid_t
58 #undef gid_t
59 #endif
60 
61 #include "ap_config.h"
62 #include "ap_compat.h"
63 #include "apr_strings.h"
64 #include "httpd.h"         /* request_rec et al. */
65 #include "http_config.h"
66 #include "http_core.h"
67 #include "http_log.h"
68 #include "http_protocol.h"
69 #include "http_request.h"  /* accessor methods for request_rec */
70 
71 #include "HRR.h"  /* httpd glue */
72 
73 /*#define srv_cf(s) (struct zxid_srv_cf*)ap_get_module_config((s)->module_config, &auth_saml_module)*/
74 #define dir_cf(r) (zxid_conf*)ap_get_module_config(HRR_per_dir_config(r), &auth_saml_module)
75 
76 /* This extern variable is used as first argument to LoadModule in httpd.conf,
77  * E.g: LoadModule auth_saml_module modules/mod_auth_saml.so */
78 
79 extern module AP_MODULE_DECLARE_DATA auth_saml_module;
80 
81 #if 0
82 /*(-) This function is run when each child process of apache starts. It does
83  * initializations that do not survive fork(2). */
84 /* Called by: */
85 static void chldinit(apr_pool_t* p, server_rec* s)
86 {
87   CURLcode res;
88   D("server_rec=%p", m, s);
89   res = curl_global_init(CURL_GLOBAL_ALL); /* vs. _SSL. Also OpenSSL vs. gnuTLS. */
90   if(res != CURLE_OK) {
91     ERR("Failed to initialize curl library: %u", res);
92   }
93 }
94 #endif
95 
96 /*(-) Set cookies apache style. Internal. */
97 
set_cookies(zxid_conf * cf,request_rec * r,const char * setcookie,const char * setptmcookie)98 static void set_cookies(zxid_conf* cf, request_rec* r, const char* setcookie, const char* setptmcookie)
99 {
100   if (setcookie && setcookie[0] && setcookie[0] != '-') {
101     /* http://dev.ariel-networks.com/apr/apr-tutorial/html/apr-tutorial-19.html */
102     D("Set-Cookie(%s)", setcookie);
103     apr_table_addn(HRR_headers_out(r), "Set-Cookie", setcookie);
104     apr_table_addn(HRR_err_headers_out(r), "Set-Cookie", setcookie);  /* Only way to get redir to set header */
105     apr_table_addn(HRR_headers_in(r),  "Set-Cookie", setcookie);  /* So subrequest can pick them up! */
106   }
107   if (setptmcookie && setptmcookie[0] && setptmcookie[0] != '-') {
108     /* http://dev.ariel-networks.com/apr/apr-tutorial/html/apr-tutorial-19.html */
109     D("PTM Set-Cookie(%s)", setptmcookie);
110     apr_table_addn(HRR_headers_out(r), "Set-Cookie", setptmcookie);
111     apr_table_addn(HRR_err_headers_out(r), "Set-Cookie", setptmcookie);  /* Only way to get redir to set header */
112     apr_table_addn(HRR_headers_in(r),  "Set-Cookie", setptmcookie);  /* So subrequest can pick them up! */
113   }
114 }
115 
116 /* ------------------------ Run time action -------------------------- */
117 
118 /*() Convert session attribute pool into Apache execution environment
119  * that will be passed to CGI, mod_php, mod_perl, and other Apache modules.
120  *
121  * OUTMAP will be applied to decide which attributes to pass to the environment
122  * and to rename them.
123  *
124  * This is considered internal function to mod_auth_saml, called by chkuid().
125  * You should not call this directly, unless you know what you are doing.
126  *
127  * return:: Apache error code, typically OK, which allows Apache continue
128  *     processing the request. */
129 
130 /* Called by:  chkuid x2 */
pool2apache(zxid_conf * cf,request_rec * r,struct zxid_attr * pool)131 static int pool2apache(zxid_conf* cf, request_rec* r, struct zxid_attr* pool)
132 {
133   int ret = OK;
134   char* name;
135   //char* rs = 0;
136   //char* rs_qs;
137   char* setcookie = 0;
138   char* setptmcookie = 0;
139   char* cookie = 0;
140   char* idpnid = 0;
141   struct zxid_map* map;
142   struct zxid_attr* at;
143   struct zxid_attr* av;
144   void* r_pool = HRR_pool(r);
145   void* sbe = HRR_subprocess_env(r);
146 
147   /* Length computation pass */
148 
149   for (at = pool; at; at = at->n) {
150     DD("HERE name(%s)", at->name);
151     map = zxid_find_map(cf->outmap, at->name);
152     if (map) {
153       if (map->rule == ZXID_MAP_RULE_DEL) {
154 	D("attribute(%s) filtered out by del rule in OUTMAP", at->name);
155 	continue;
156       }
157       at->map_val = zxid_map_val(cf, 0, 0, map, at->name, at->val);
158       if (map->dst && *map->dst && map->src && map->src[0] != '*') {
159 	name = map->dst;
160       } else {
161 	name = at->name;
162       }
163 
164       name = apr_psprintf(r_pool, "%s%s", cf->mod_saml_attr_prefix, name);
165       apr_table_set(sbe, name, at->val);
166       for (av = at->nv; av; av = av->n) {
167 	av->map_val = zxid_map_val(cf, 0, 0, map, at->name, av->val);
168 	apr_table_set(sbe, name, av->map_val->s);
169       }
170     } else {
171       if ((errmac_debug & ERRMAC_DEBUG_MASK)>1)
172 	D("ATTR(%s)=VAL(%s)", at->name, STRNULLCHKNULL(at->val));
173       else
174 	D("ATTR(%s)=VAL(%.*s)", at->name, at->val?(int)MIN(35,strlen(at->val)):6, at->val?at->val:"(null)");
175       /* *** handling of multivalued attributes (right now only last is preserved) */
176       name = apr_psprintf(r_pool, "%s%s", cf->mod_saml_attr_prefix, at->name);
177       apr_table_set(sbe, name, at->val);
178       for (av = at->nv; av; av = av->n)
179 	apr_table_set(sbe, name, av->val);
180     }
181     if      (!strcmp(at->name, "idpnid"))       idpnid = at->val;      /* Capture special */
182     else if (!strcmp(at->name, "setcookie"))    setcookie = at->val;
183     else if (!strcmp(at->name, "setptmcookie")) setptmcookie = at->val;
184     else if (!strcmp(at->name, "cookie"))       cookie = at->val;
185     //else if (!strcmp(at->name, "rs"))         rs = at->val;
186   }
187 #if 0
188   /* This code moved to zxidsimp.c: zxid_show_protected_content_setcookie() */
189   if (rs && rs[0] && rs[0] != '-') {
190     /* N.B. RelayState was set by chkuid() "some other page" section by setting cgi.rs
191      * to deflated and safe base64 encoded value which was then sent to IdP as RelayState.
192      * It then came back from IdP and was decoded as one of the SSO attributes.
193      * The decoding is controlled by <<tt: rsrc$rs$unsb64-inf$$ >>  rule in OUTMAP. */
194     rs = zxid_unbase64_inflate(cf->ctx, -2, rs, 0);
195     if (!rs) {
196       ERR("Bad relaystate. Error in inflate. %d", 0);
197       return HTTP_BAD_REQUEST;
198     }
199     rs_qs = strchr(rs, '?');
200     if (rs_qs
201 	?(memcmp(HRR_uri(r), rs, rs_qs-rs)||strcmp(HRR_args(r)?HRR_args(r):"",rs_qs+1))
202 	:strcmp(HRR_uri(r), rs)) {  /* Different, need external or internal redirect */
203       D("redirect(%s) redir_to_content=%d", rs, cf->redir_to_content);
204       //r->uri = apr_pstrdup(r->pool, val);
205       if (cf->redir_to_content) {
206 	apr_table_setn(HRR_headers_out(r), "Location", rs);
207 	ret = HTTP_SEE_OTHER;
208       } else {
209 	/* *** any attributes after this point may not appear in subrequest */
210 	ap_internal_redirect_handler(rs, r);
211       }
212     }
213   }
214 #endif
215 
216   set_cookies(cf, r, setcookie, setptmcookie);
217   if (cookie && cookie[0] != '-') {
218     D("Cookie(%s) 2", cookie);
219     apr_table_addn(HRR_headers_in(r), "Cookie", cookie);  /* so internal redirect sees it */
220   }
221   if (idpnid && idpnid[0] != '-') {
222     D("REMOTE_USER(%s)", idpnid);
223     apr_table_set(sbe, "REMOTE_USER", idpnid);
224     HRR_set_user(r, idpnid);  /* httpd-2.4 anz framework requires this, 2.2 does not care */
225   }
226 
227   //apr_table_setn(r->subprocess_env,
228   //		 apr_psprintf(r->pool, "%sLDIF", cf->mod_saml_attr_prefix), ldif);
229   D("SSO OK ret(%d) uri(%s) filename(%s) path_info(%s) user(%s)=%p", ret, (char*)HRR_uri(r), (char*)HRR_filename(r), (char*)HRR_path_info(r), STRNULLCHKD((char*)HRR_user(r)), HRR_user(r));
230   return ret;
231 }
232 
233 /*() Send Apache response.
234  *
235  * This is considered internal function to mod_auth_saml, called by chkuid().
236  * You should not call this directly, unless you know what you are doing. */
237 
238 /* Called by:  chkuid */
send_res(zxid_conf * cf,request_rec * r,char * res)239 static int send_res(zxid_conf* cf, request_rec* r, char* res)
240 {
241   int len;
242   char* p;
243 #if 0
244   apr_table_setn(HRR_headers_out(r),     "Cache-Control", "no-cache");
245   apr_table_setn(HRR_err_headers_out(r), "Cache-Control", "no-cache");
246   apr_table_setn(HRR_headers_out(r),     "Pragma", "no-cache");
247   apr_table_setn(HRR_err_headers_out(r), "Pragma", "no-cache");
248 #endif
249   res += 14;  /* skip "Content-Type:" (14 chars) */
250   DD("RES(%s)", res);
251   p = strchr(res, '\r');
252   *p = 0;
253   //apr_table_setn(HRR_headers_out(r), "Content-Type", res);
254   DD("CONTENT-TYPE(%s)", res);
255   ap_set_content_type(r, res);
256   res = p+2 + 16;  /* skip "Content-Length:" (16 chars) */
257   sscanf(res, "%d", &len);
258   res = strchr(res, '\r') + 4; /* skip CRFL pair before body */
259   DD("CONTENT-LENGTH(%d)", len);
260   ap_set_content_length(r, len);
261 
262   if (errmac_debug & MOD_AUTH_SAML_INOUT) INFO("LEN(%d) strlen(%d) RES(%s)", len, (int)strlen(res), res);
263 
264   //register_timeout("send", r);
265   ap_send_http_header(r);
266   if (!HRR_header_only(r))
267     ap_rprintf(r, "%s", res);  //send_fd(f, r);  rprintf(); ap_rwrite()
268   return DONE;   /* Prevent further hooks from processing the request. */
269 }
270 
271 /*(-) Read POST input, Apache style
272  *
273  * This is considered internal function to mod_auth_saml, called by chkuid().
274  * You should not call this directly, unless you know what you are doing. */
275 
276 /* Called by:  chkuid x2 */
read_post(zxid_conf * cf,request_rec * r)277 static char* read_post(zxid_conf* cf, request_rec* r)
278 {
279   int len, ret;
280   char* res;
281   char* p;
282   /*len = apr_table_get(r->headers_in, "Content-Length");*/
283 
284   /* Ask Apache to dechunk data if it is chunked. */
285   ret = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK);
286   if (ret != OK) {
287     ERR("ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK): %d", ret);
288     return 0;
289   }
290 
291   /* This function will send a 100 Continue response if the client is
292    * waiting for that. If the client isn't going to send data, then this
293    * function will return 0. */
294   if (!ap_should_client_block(r)) {
295     len = 0;
296   } else {
297     len = HRR_remaining(r);
298   }
299   res = p = apr_palloc(HRR_pool(r), len + 1);
300   res[len] = 0;
301   D("remaining=%d", len);
302 
303   while (len > 0) {
304     /* Read data from the client. Returns 0 on EOF or error, the
305      * number of bytes otherwise.   */
306     ret = ap_get_client_block(r, p, len);
307     if (!ret) {
308       ERR("Failed to read POST data from client. len=%d",len);
309       return 0;  /* HTTP_INTERNAL_SERVER_ERROR */
310     }
311 
312     p += ret;
313     len -= ret;
314   }
315   if (errmac_debug & MOD_AUTH_SAML_INOUT) INFO("POST(%s)", res);
316   return res;
317 }
318 
319 /* 0x6000 outf QS + JSON = no output on successful sso, the attrubutes are in session
320  * 0x1000 debug
321  * 0x0e00 11 + 10 = Generate all HTML + Mgmt w/headers as string
322  * 0x00a0 10 + 10 = Login w/headers as string + Meta w/headers as string
323  * 0x0008 10 + 00 = SOAP w/headers as string + no auto redir, no exit(2) */
324 #define AUTO_FLAGS 0x6ea8
325 
326 /*() Apache hook. Internal function of mod_auth_saml. Do not try to call.
327  *
328  * Called from httpd-2.2.8/server/request.c: ap_process_request_internal()
329  * ap_run_check_user_id(). Return value is processed in modules/http/http_request.c
330  * and redirect is in ap_die(), http_protocol.c: ap_send_error_response()
331  *
332  * It seems this function will in effect be called twice by Apache internals: once
333  * to see if it would succeed and second time to actually do the work. This is rather
334  * wasteful, but we do not know any easy way to avoid it.
335  *
336  * Originally this was just for SSO, but nowdays we also support WSP mode.  */
337 
338 /* Called by: */
chkuid(request_rec * r)339 static int chkuid(request_rec* r)
340 {
341   int ret, len, uri_len, url_len, args_len;
342   char* cp;
343   char* res;
344   char buf[256];
345   const char* cookie_hdr=0;
346   const char* set_cookie_hdr;
347   const char* cur_auth;
348   request_rec* main_req;
349   char* uri = r?HRR_uri(r):0;
350   zxid_conf* cf = dir_cf(r);
351   zxid_cgi cgi;
352   zxid_ses ses;
353   ZERO(&cgi, sizeof(zxid_cgi));
354   ZERO(&ses, sizeof(zxid_ses));
355   cgi.uri_path = uri;
356   cgi.qs = r?HRR_args(r):0;
357 
358   //D("request_rec sizeof=%d offset(r->uri)=%d offset(r->user)=%d", sizeof(request_rec), (void*)(uri)-(void*)r, (void*)(&(r->user))-(void*)r);
359   D("===== START %s req=%p uri(%s) args(%s) pid=%d cwd(%s)", ZXID_REL, r, r?STRNULLCHKNULL(uri):"(r null)", r?STRNULLCHKNULL(HRR_args(r)):"(r null)", getpid(), getcwd(buf,sizeof(buf)));
360   if (cf->wd && *cf->wd)
361     chdir(cf->wd);  /* Ensure the working dir is not / (sometimes Apache httpd changes dir) */
362   D_INDENT("chkuid: ");
363 
364   if (main_req = HRR_main(r)) {  /* subreq can't come from net: always auth OK. */
365     D("sub ok user(%s)=%p", STRNULLCHKD((char*)HRR_user(r)), HRR_user(r));
366     HRR_set_user(r, HRR_user(main_req));
367     D("sub from main user(%s)=%p", STRNULLCHKD((char*)HRR_user(r)), HRR_user(r));
368     D_DEDENT("chkuid: ");
369     return OK;
370   }
371 
372   cur_auth = ap_auth_type(r);   /* From directive: AuthType "saml" */
373   if (!cur_auth || strcasecmp(cur_auth, "saml")) {
374     D("not saml auth (%s) %d", STRNULLCHKD(cur_auth), DECLINED);
375     D_DEDENT("chkuid: ");
376     return DECLINED;
377   }
378   //r->ap_auth_type = "saml";  *** This is already verified to be the case?!?
379 
380   /* Probe for Session ID in cookie. Also propagate the cookie to subrequests. */
381 
382   if (cf->ses_cookie_name && *cf->ses_cookie_name) {
383     cookie_hdr = apr_table_get(HRR_headers_in(r), "Cookie");
384     if (cookie_hdr) {
385       D("found cookie(%s) 3", STRNULLCHK(cookie_hdr));
386       zxid_get_sid_from_cookie(cf, &cgi, cookie_hdr);
387       apr_table_addn(HRR_headers_out(r), "Cookie", cookie_hdr);       /* Pass cookies to subreq */
388       DD("found cookie(%s) 5", STRNULLCHK(cookie_hdr));
389       /* Kludge to get subrequest to set-cookie, i.e. on return path */
390       set_cookie_hdr = apr_table_get(HRR_headers_in(r), "Set-Cookie");
391       if (set_cookie_hdr) {
392 	D("subrequest set-cookie(%s) 2", set_cookie_hdr);
393 	apr_table_addn(HRR_headers_out(r), "Set-Cookie", set_cookie_hdr);
394       }
395     }
396   }
397 
398   /* Redirect hack: deal with externally imposed ACS url that does not follow zxid convention. */
399 
400   args_len = HRR_args(r)?strlen(HRR_args(r)):0;
401   if (cf->redirect_hack_imposed_url && !strcmp(uri, cf->redirect_hack_imposed_url)) {
402     D("Redirect hack: mapping(%s) imposed to zxid(%s)", uri, cf->redirect_hack_zxid_url);
403     HRR_set_uri(r, cf->redirect_hack_zxid_url);
404     uri = cf->redirect_hack_zxid_url;
405     if (cf->redirect_hack_zxid_qs && *cf->redirect_hack_zxid_qs) {
406       if (args_len) {
407 	/* concatenate redirect_hack_zxid_qs with existing qs */
408 	len = strlen(cf->redirect_hack_zxid_qs);
409 	cp = apr_palloc(HRR_pool(r), len+1+args_len+1);
410 	strcpy(cp, cf->redirect_hack_zxid_qs);
411 	cp[len] = '&';
412 	strcpy(cp+len+1, HRR_args(r));
413 	cgi.qs = cp;
414 	HRR_set_args(r, cp);
415       } else {
416 	cgi.qs = cf->redirect_hack_zxid_qs;
417 	HRR_set_args(r, cgi.qs);
418       }
419       args_len = strlen(HRR_args(r));
420     }
421     D("After hack uri(%s) args(%s)", STRNULLCHK(uri), STRNULLCHK(HRR_args(r)));
422   }
423 
424   DD("HERE1 args_len=%d cgi=%p k(%s) args(%s)", args_len, &cgi, STRNULLCHKNULL(cgi.skin), STRNULLCHKNULL(HRR_args(r)));
425   if (args_len) {
426     /* leak the dup str: the cgi structure will take references to this and change &s to nuls */
427     cp = apr_palloc(HRR_pool(r), args_len + 1);
428     strcpy(cp, HRR_args(r));
429     zxid_parse_cgi(cf, &cgi, cp);
430     DD("HERE2 args_len=%d cgi=%p k(%s) args(%s)", args_len, &cgi, STRNULLCHKNULL(cgi.skin), STRNULLCHKNULL(HRR_args(r)));
431   }
432   /* Check if we are supposed to enter zxid due to URL suffix - to
433    * process protocol messages rather than ordinary pages. To do this
434    * correctly we need to ignore the query string part. We are looking
435    * here at exact match, like /protected/saml, rather than any of
436    * the other documents under /protected/ (which are handled in the
437    * else clause). Both then and else -clause URLs are defined as requiring
438    * SSO by virtue of the web server configuration. */
439 
440   uri_len = strlen(uri);
441   url_len = strlen(cf->burl);
442   for (cp = cf->burl + url_len - 1; cp > cf->burl; --cp)
443     if (*cp == '?')
444       break;
445   if (cp == cf->burl)
446     cp = cf->burl + url_len;
447 
448   if (url_len >= uri_len && !memcmp(cp - uri_len, uri, uri_len)) {  /* Suffix match */
449     if (errmac_debug & MOD_AUTH_SAML_INOUT) INFO("matched uri(%s) cf->burl(%s) qs(%s) rs(%s) op(%c)", uri, cf->burl, STRNULLCHKNULL(HRR_args(r)), STRNULLCHKNULL(cgi.rs), cgi.op);
450     if (HRR_method_number(r) == M_POST) {
451       res = read_post(cf, r);   /* Will print some debug output */
452       if (res) {
453 	if (cgi.op == 'S') {
454 	  ret = zxid_sp_soap_parse(cf, &cgi, &ses, strlen(res), res);
455 	  D("POST soap parse returned %d", ret);
456 #if 0
457 	  /* *** TODO: SOAP response should not be sent internally unless there is auto */
458 	  if (ret == ZXID_SSO_OK) {
459 	    ret = zxid_simple_ab_pep(cf, &ses, res_len, auto_flags);
460 	    D_DEDENT("chkuid: ");
461 	    return ret;
462 	  }
463 	  if (auto_flags & ZXID_AUTO_SOAPC || auto_flags & ZXID_AUTO_SOAPH) {
464 	    res = zx_dup_cstr(cf->ctx, "n");
465 	    if (res_len)
466 	      *res_len = 1;
467 	    goto done;
468 	  }
469 	  res = zx_dup_cstr(cf->ctx, ret ? "n" : "*** SOAP error (enable debug to see why)");
470 	  if (res_len)
471 	    *res_len = strlen(res);
472 	  goto done;
473 #endif
474 	} else {
475 	  zxid_parse_cgi(cf, &cgi, res);
476 	  D("POST CGI parsed. rs(%s)", STRNULLCHKQ(cgi.rs));
477 	}
478       }
479     }
480     if (ONE_OF_2(cgi.op, 'L', 'A')) /* SSO (Login, Artifact) activity overrides current session. */
481       goto step_up;
482     if (!cgi.sid || !zxid_get_ses(cf, &ses, cgi.sid)) {
483       D("No session(%s) active op(%c)", STRNULLCHK(cgi.sid), cgi.op);
484     } else {
485       res = zxid_simple_ses_active_cf(cf, &cgi, &ses, 0, AUTO_FLAGS);
486       if (res)
487 	goto process_zxid_simple_outcome;
488     }
489     /* not logged in, fall thru */
490   } else if (zx_match(cf->wsp_pat, uri)) {
491     /* WSP case */
492     if (HRR_method_number(r) == M_POST) {
493       res = read_post(cf, r);   /* Will print some debug output */
494       if (zxid_wsp_validate(cf, &ses, 0, res)) {
495 	D("WSP(%s) request valid", uri);
496 	D("WSP CALL uri(%s) filename(%s) path_info(%s)", uri, (char*)HRR_filename(r), (char*)HRR_path_info(r));
497 	ret = pool2apache(cf, r, ses.at);
498 	D_DEDENT("chkuid: ");
499 	return ret;
500 	/* Essentially we fall through and let CGI processing happen.
501 	 * *** how to decorate the CGI return value?!? New hook needed? --Sampo */
502       } else {
503 	ERR("WSP(%s) request validation failed", uri);
504 	D_DEDENT("chkuid: ");
505 	return HTTP_FORBIDDEN;
506       }
507     } else {
508       ERR("WSP(%s) must be called with POST method %d", uri, HRR_method_number(r));
509       D_DEDENT("chkuid: ");
510       return HTTP_METHOD_NOT_ALLOWED;
511     }
512   } else if (zx_match(cf->uma_pat, uri)) {
513     /* UMA case */
514     if (HRR_method_number(r) == M_POST) {
515       res = read_post(cf, r);   /* Will print some debug output */
516 #if 0
517       // *** add UMA Resource Server stuff here
518       if (zxid_wsp_validate(cf, &ses, 0, res)) {
519 	D("WSP(%s) request valid", uri);
520 	D("WSP CALL uri(%s) filename(%s) path_info(%s)", uri, HRR_filename(r), HRR_path_info(r));
521 	ret = pool2apache(cf, r, ses.at);
522 	D_DEDENT("chkuid: ");
523 	return ret;
524 	/* Essentially we fall through and let CGI processing happen.
525 	 * *** how to decorate the CGI return value?!? New hook needed? --Sampo */
526       } else {
527 	ERR("WSP(%s) request validation failed", uri);
528 	D_DEDENT("chkuid: ");
529 	return HTTP_FORBIDDEN;
530       }
531 #endif
532     } else {
533       ERR("WSP(%s) must be called with POST method %d", uri, HRR_method_number(r));
534       D_DEDENT("chkuid: ");
535       return HTTP_METHOD_NOT_ALLOWED;
536     }
537   } else {
538     /* Some other page. Just check for session. */
539     if (errmac_debug & MOD_AUTH_SAML_INOUT) INFO("other page uri(%s) qs(%s) cf->burl(%s) uri_len=%d url_len=%d", uri, STRNULLCHKNULL(HRR_args(r)), cf->burl, uri_len, url_len);
540     if (cgi.sid && cgi.sid[0] && zxid_get_ses(cf, &ses, cgi.sid)) {
541       res = zxid_simple_ses_active_cf(cf, &cgi, &ses, 0, AUTO_FLAGS);
542       if (res)
543 	goto process_zxid_simple_outcome;
544     } else {
545       D("No active session(%s) op(%c)", STRNULLCHK(cgi.sid), cgi.op?cgi.op:'-');
546       if (cf->optional_login_pat && zx_match(cf->optional_login_pat, uri)) {
547 	D("optional_login_pat matches %d", OK);
548 	HRR_set_user(r, "-anon-");  /* httpd-2.4 anz framework requires this, 2.2 does not care */
549 	D_DEDENT("chkuid: ");
550 	return OK;
551       }
552     }
553     if (HRR_args(r) && ((char*)HRR_args(r))[0] == 'l') {
554       D("Detect login(%s)", (char*)HRR_args(r));
555     } else
556       cgi.op = 'E';   /* Trigger IdP selection screen */
557     D("other page: no_ses uri(%s) templ(%s) tf(%s) k(%s)", uri, STRNULLCHKNULL(cgi.templ), STRNULLCHKNULL(cf->idp_sel_templ_file), STRNULLCHKNULL(cgi.skin));
558   }
559 step_up:
560   res = zxid_simple_no_ses_cf(cf, &cgi, &ses, 0, AUTO_FLAGS);
561 
562 process_zxid_simple_outcome:
563   if (cookie_hdr && cookie_hdr[0]) {
564     D("Passing previous cookie(%s) to environment", cookie_hdr);
565     zxid_add_attr_to_ses(cf, &ses, "cookie", zx_dup_str(cf->ctx, cookie_hdr));
566   }
567 
568   switch (res[0]) {
569   case 'L':
570     if (errmac_debug & MOD_AUTH_SAML_INOUT) INFO("REDIR(%s)", res);
571     apr_table_setn(HRR_headers_out(r), "Location", res+10);
572     set_cookies(cf, r, ses.setcookie, ses.setptmcookie);
573     D_DEDENT("chkuid: ");
574     return HTTP_SEE_OTHER;
575   case 'C':
576     if (errmac_debug & MOD_AUTH_SAML_INOUT) INFO("CONTENT(%s)", res);
577     set_cookies(cf, r, ses.setcookie, ses.setptmcookie);
578     ret = send_res(cf, r, res);
579     D_DEDENT("chkuid: ");
580     return ret;
581   case 'z':
582     INFO("User not authorized %d", 0);
583     D_DEDENT("chkuid: ");
584     return HTTP_FORBIDDEN;
585   case 0: /* Logged in case */
586     D("SSO OK pre uri(%s) filename(%s) path_info(%s)", uri, (char*)HRR_filename(r), (char*)HRR_path_info(r));
587     ret = pool2apache(cf, r, ses.at);
588     D_DEDENT("chkuid: ");
589     return ret;
590 #if 0
591   case 'd': /* Logged in case */
592     if (errmac_debug & MOD_AUTH_SAML_INOUT) INFO("SSO OK LDIF(%s)", res);
593     D("SSO OK pre uri(%s) filename(%s) path_info(%s)", uri, (char*)HRR_filename(r), (char*)HRR_path_info(r));
594     ret = ldif2apache(cf, r, res);
595     D_DEDENT("chkuid: ");
596     return ret;
597 #endif
598   default:
599     ERR("Unknown zxid_simple response(%s)", res);
600     D_DEDENT("chkuid: ");
601     return HTTP_INTERNAL_SERVER_ERROR;
602   }
603 
604   D("final ok %d", OK);
605   D_DEDENT("chkuid: ");
606   return OK;
607 }
608 
609 /* ------------------------ CONF -------------------------- */
610 
611 /*(-) Process ZXIDDebug directive in Apache configuration file.
612  *
613  * This is considered internal function to mod_auth_saml. Do not call directly. */
614 
615 /* Called by: */
set_debug(cmd_parms * cmd,void * st,const char * arg)616 static const char* set_debug(cmd_parms* cmd, void* st, const char* arg) {
617   char buf[256];
618   D("old debug=%x, new debug(%s)", errmac_debug, arg);
619   sscanf(arg, "%i", &errmac_debug);
620   INFO("debug=0x%x now arg(%s) cwd(%s)", errmac_debug, arg, getcwd(buf, sizeof(buf)));
621   {
622     struct rlimit rlim;
623     getrlimit(RLIMIT_CORE, &rlim);
624     D("MALLOC_CHECK_(%s) core_rlimit=%d,%d", getenv("MALLOC_CHECK_"), (int)rlim.rlim_cur, (int)rlim.rlim_max);
625   }
626   return 0;
627 }
628 
629 /*(-) Process ZXIDConf directive in Apache configuration file.
630  * Can be called any number of times to set additional parameters.
631  *
632  * This is considered internal function to mod_auth_saml. Do not call directly. */
633 
634 /* Called by: */
set_zxid_conf(cmd_parms * cmd,void * st,const char * arg)635 static const char* set_zxid_conf(cmd_parms* cmd, void* st, const char* arg) {
636   int len;
637   char* buf;
638   zxid_conf* cf = (zxid_conf*)st;
639   D("arg(%s) cf=%p", arg, cf);
640   len = strlen(arg);
641   buf = ZX_ALLOC(cf->ctx, len+1);
642   memcpy(buf, arg, len+1);
643   zxid_parse_conf(cf, buf);
644   return 0;
645 }
646 
647 const command_rec zxid_apache_commands[] = {
648   AP_INIT_TAKE1("ZXIDDebug", set_debug, 0, OR_AUTHCFG,
649 		"Enable debugging output to stderr. 0 to disable."),
650   AP_INIT_TAKE1("ZXIDConf", set_zxid_conf, 0, OR_AUTHCFG,
651 		"Supply ZXID CONF string. May be supplied multiple times."),
652   {0}
653 };
654 
655 
656 #define ZXID_APACHE_DEFAULT_CONF ""  /* defaults will reign, including cpath /var/zxid */
657 
658 /*(-) Create default configuration in response for Apache <Location> or <Directory>
659  * directives. This is then augmented by ZXIDConf directives.
660  * This code may run twice: once for syntax check, and then again for
661  * production use. Currently we just redo the work. Apache stores the
662  * return value of this function and it can be read in chkuid() hook using
663  *    ap_get_module_config((r)->per_dir_config, &auth_saml_module)
664  *
665  * This is considered internal function to mod_auth_saml. Do not call directly. */
666 
667 /* Called by: */
dirconf(apr_pool_t * p,char * d)668 static void* dirconf(apr_pool_t* p, char* d)
669 {
670   zxid_conf* cf;
671   strncpy(errmac_instance, "\tmas", sizeof(errmac_instance));
672   cf = apr_palloc(p, sizeof(zxid_conf));
673   ZERO(cf, sizeof(zxid_conf));
674   cf->ctx = apr_palloc(p, sizeof(struct zx_ctx));
675   zx_reset_ctx(cf->ctx);
676   D("cf=%p ctx=%p d(%s)", cf, cf->ctx, STRNULLCHKD(d));
677   /* *** set malloc func ptr in ctx to use apr_palloc() */
678   zxid_conf_to_cf_len(cf, -1, ZXID_APACHE_DEFAULT_CONF);
679   cf->cpath_supplied = 0;
680   return cf;
681 }
682 
683 /* ------------------------ Hooks -------------------------- */
684 
685 /*(-) Register Apache hook for mod_auth_saml
686  *
687  * This is considered internal function to mod_auth_saml. Do not call directly. */
688 
689 /* Called by: */
reghk(apr_pool_t * p)690 static void reghk(apr_pool_t* p) {
691   D("pool=%p", p);
692   //ap_hook_access_checker(authusr,  0, 0, APR_HOOK_MIDDLE);
693   ap_hook_check_user_id( chkuid,   0, 0, APR_HOOK_MIDDLE);
694   //ap_hook_post_config(   postconf, 0, 0, APR_HOOK_MIDDLE);
695   /*ap_hook_child_init(    chldinit, 0, 0, APR_HOOK_MIDDLE);*/
696 }
697 
698 /* This extern variable is used as first argument to LoadModule in httpd.conf,
699  * E.g: LoadModule auth_saml_module modules/mod_auth_saml.so
700  * See httpd-2.2/include/http_config.h for module_struct.
701  * Lucky for 2.2 vs. 2.4 compat, m->module_index and other fields up to
702  * m->magic are on sample places on both. */
703 
704 module AP_MODULE_DECLARE_DATA auth_saml_module = {
705   STANDARD20_MODULE_STUFF,
706   dirconf,
707   0, //dirmerge,
708   0, //srvconf,
709   0, //srvmerge,
710   zxid_apache_commands,
711   reghk
712 };
713 
714 /* EOF - mod_auth_saml.c */
715