1 /* zxidpep.c  -  Handwritten functions for XACML Policy Enforcement Point
2  * Copyright (c) 2010-2011 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.
3  * Copyright (c) 2009 Symlabs (symlabs@symlabs.com), All Rights Reserved.
4  * Author: Sampo Kellomaki (sampo@iki.fi)
5  * This is confidential unpublished proprietary source code of the author.
6  * NO WARRANTY, not even implied warranties. Contains trade secrets.
7  * Distribution prohibited unless authorized in writing.
8  * Licensed under Apache License 2.0, see file COPYING.
9  * $Id: zxidpep.c,v 1.10 2010-01-08 02:10:09 sampo Exp $
10  *
11  * 24.8.2009, created --Sampo
12  * 10.10.2009, added zxid_az() family --Sampo
13  * 12.2.2010,  added locking to lazy loading --Sampo
14  * 31.5.2010,  generalized to several PEPs model --Sampo
15  * 7.9.2010,   merged patches from Stijn Lievens --Sampo
16  * 10.1.2011,  added TrustPDP support --Sampo
17  *
18  * See also: zxid_simple_ab_pep() in zxidpdp.c, zxidwsc.c, zxidwsp.c
19  */
20 
21 #include "platform.h"
22 #include "errmac.h"
23 #include "zxid.h"
24 #include "zxidpriv.h"
25 #include "zxidconf.h"
26 #include "saml2.h"
27 #include "tas3.h"
28 #include "c/zx-const.h"
29 #include "c/zx-ns.h"
30 #include "c/zx-data.h"
31 #include "c/zx-e-data.h"
32 
33 #if 0
34 #define XS_STRING "xs:string"
35 #else
36 #define XS_STRING "http://www.w3.org/2001/XMLSchema#string"
37 #endif
38 
39 /*() Check for duplicate attribute in XACML attribute chain
40  * for nth occurance of named attribute.
41  *
42  * - NULL or zero length name will match any
43  * - minus one (-1) as either length field will cause strlen() to be done
44  * - the index n is one based
45  *
46  * *Arguments*
47  *
48  * xac_at_list:: List of (already existing) xac attributes to scan, potentially NULL.
49  * name_len:: Length of the attribute name, or 0 if no matching by attribute name is desired
50  * name:: attribute name to match (or 0)
51  * n:: Howmanieth instance of the matching attribute is desired. 1 means first.
52  * return:: Data structure representing the matching attribute.
53  */
54 
55 /* Called by:  zxid_pepmap_extract x4 */
zxid_find_xac_attribute(struct zx_xac_Attribute_s * xac_at_list,int name_len,char * name,int n)56 static struct zx_xac_Attribute_s* zxid_find_xac_attribute(struct zx_xac_Attribute_s* xac_at_list, int name_len, char* name, int n)
57 {
58   struct zx_xac_Attribute_s* at;
59   if (!name) { name_len = 0; name = ""; }
60   if (name_len == -1 && name) name_len = strlen(name);
61   if (!xac_at_list) {
62     DD("No xac attribute list when looking for attribute name(%.*s) n=%d", name_len, name, n);
63     return 0;
64   }
65   for (at = xac_at_list;
66        at;
67        at = (struct zx_xac_Attribute_s*)at->gg.g.n) {
68     if (at->gg.g.tok != zx_xac_Attribute_ELEM)
69       continue;
70     if ((name_len ? (at->AttributeId
71 		     && at->AttributeId->g.len == name_len
72 		     && !memcmp(at->AttributeId->g.s, name, name_len)) : 1)
73 	) {
74       --n;
75       if (!n)
76 	return at;
77     }
78   }
79   return 0;
80 }
81 
82 /*() Extract attributes from session pool according to pepmap, usually the PEPMAP
83  * configuration variable.
84  *
85  * cf:: the configuration will need to have ~PEPMAP~ and ~PDP_URL~ options
86  *     set according to your situation.
87  * cgi:: if non-null, will receive error and status codes
88  * ses:: all attributes are obtained from the session. You may wish
89  *     to add additional attributes that are not known by SSO.
90  * pepmap:: The map used to extract the attributes from the pool to the XACML request
91  * subj:: Value-result parameter. Linked list of subject attributes.
92  * rsrc:: Value-result parameter. Linked list of resource attributes.
93  * act::  Value-result parameter. Linked list of action attributes (usually just one attribute).
94  * env::  Value-result parameter. Linked list of environment attributes.
95  */
96 
97 /* Called by:  zxid_call_trustpdp, zxid_pep_az_base_soap_pepmap, zxid_pep_az_soap_pepmap */
zxid_pepmap_extract(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,struct zxid_map * pepmap,struct zx_xac_Attribute_s ** subj,struct zx_xac_Attribute_s ** rsrc,struct zx_xac_Attribute_s ** act,struct zx_xac_Attribute_s ** env)98 static void zxid_pepmap_extract(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, struct zxid_map* pepmap, struct zx_xac_Attribute_s** subj, struct zx_xac_Attribute_s** rsrc, struct zx_xac_Attribute_s** act, struct zx_xac_Attribute_s** env)
99 {
100   struct zxid_map* map;
101   struct zxid_attr* at;
102   struct zxid_attr* av;
103   struct zx_xac_Attribute_s* xac_at;
104   char* name;
105 
106   for (at = ses?ses->at:0; at; at = at->n) {
107     map = zxid_find_map(pepmap, at->name);
108     if (!map) {
109       D("ATTR(%s)=VAL(%s): Ignored due to no matching PEPMAP", at->name, at->val);
110       continue;
111     }
112 
113     if (map->rule == ZXID_MAP_RULE_DEL) {
114       D("attribute(%s) filtered out by del rule in PEPMAP", at->name);
115       continue;
116     }
117 
118     at->map_val = zxid_map_val(cf, ses, 0, map, at->name, at->val);
119 
120     if (!at->map_val || !at->map_val->len || !at->map_val->s) {
121       if (cf->az_opt & 0x02) {
122 	INFO("attribute(%s) with empty value passed due to AZ_OPT=0x02", at->name);
123 	continue;
124       } else {
125 	INFO("attribute(%s) filtered out due to empty value", at->name);
126 	continue;
127       }
128     }
129 
130     if (map->dst && *map->dst && map->src && map->src[0] != '*') {
131       D("renaming(%s) to(%s) orig_val(%s) map_val(%.*s)", at->name, map->dst, at->val, at->map_val->len, at->map_val->s);
132       name = map->dst;
133     } else {
134       name = at->name;
135     }
136     xac_at = zxid_mk_xacml_simple_at(cf, 0,
137 				     zx_dup_str(cf->ctx, name),
138 				     zx_dup_str(cf->ctx, XS_STRING),
139 				     at->issuer, at->map_val);
140     if (map->ns && *map->ns) {
141       if (!strcmp(map->ns, "subj")) {
142 	if (cf->az_opt & 0x04) {
143 	  DD("Dup allowed by AZ_OPT=0x04");
144 	} else if (zxid_find_xac_attribute(*subj, -1, at->name, 1)) {
145 	  INFO("attribute(%s) filtered out as duplicate", at->name);
146 	  continue;
147 	}
148 	ZX_NEXT(xac_at) = (void*)*subj;
149 	*subj = xac_at;
150       } else if (!strcmp(map->ns, "rsrc")) {
151 	if (cf->az_opt & 0x04) {
152 	  DD("Dup allowed by AZ_OPT=0x04");
153 	} else if (zxid_find_xac_attribute(*rsrc, -1, at->name, 1)) {
154 	  INFO("attribute(%s) filtered out as duplicate", at->name);
155 	  continue;
156 	}
157 	ZX_NEXT(xac_at) = (void*)*rsrc;
158 	*rsrc = xac_at;
159       } else if (!strcmp(map->ns, "act")) {
160 	if (cf->az_opt & 0x04) {
161 	  DD("Dup allowed by AZ_OPT=0x04");
162 	} else if (zxid_find_xac_attribute(*act, -1, at->name, 1)) {
163 	  INFO("attribute(%s) filtered out as duplicate", at->name);
164 	  continue;
165 	}
166 #if 0
167 	*act = xac_at;  /* there can be only one */
168 #else
169         ZX_NEXT(xac_at) = (void*)*act;
170         *act = xac_at;  /* We can have multiple attributes in the action section */
171 #endif
172       } else if (!strcmp(map->ns, "env")) {
173 	if (cf->az_opt & 0x04) {
174 	  DD("Dup allowed by AZ_OPT=0x04");
175 	} else if (zxid_find_xac_attribute(*env, -1, at->name, 1)) {
176 	  INFO("attribute(%s) filtered out as duplicate", at->name);
177 	  continue;
178 	}
179 	ZX_NEXT(xac_at) = (void*)*env;
180 	*env = xac_at;
181       } else {
182 	ERR("PEPMAP unknown ns(%s). Valid values are subj, rsrc, act, and env.", map->ns);
183       }
184     } else {
185       ERR("PEPMAP entry lacks ns %p", map->ns);
186     }
187 
188     for (av = at->nv; av; av = av->n) {
189       av->map_val = zxid_map_val(cf, ses, 0, map, at->name, av->val);
190       xac_at = zxid_mk_xacml_simple_at(cf, 0,
191 				       zx_dup_str(cf->ctx, name),
192 				       zx_dup_str(cf->ctx, XS_STRING),
193 				       at->issuer, at->map_val);
194       if (map->ns && *map->ns) {
195 	if (!strcmp(map->ns, "subj")) {
196 	  ZX_NEXT(xac_at) = (void*)*subj;
197 	  *subj = xac_at;
198 	} else if (!strcmp(map->ns, "rsrc")) {
199 	  ZX_NEXT(xac_at) = (void*)*rsrc;
200 	  *rsrc = xac_at;
201 	} else if (!strcmp(map->ns, "act")) {
202 	  ERR("PEPMAP: Only one XACML action attribute allowed %d", 0);
203 	} else if (!strcmp(map->ns, "env")) {
204 	  ZX_NEXT(xac_at) = (void*)*env;
205 	  *env = xac_at;
206 	} else {
207 	  ERR("PEPMAP unknown ns(%s). Valid values are subj, rsrc, act, and env.", map->ns);
208 	}
209       } else {
210 	ERR("PEPMAP entry lacks ns %p", map->ns);
211       }
212     }
213   }
214 
215   if (!*act) {
216     *act = zxid_mk_xacml_simple_at(cf, 0,
217 	     zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:1.0:action:action-id"),
218 	     zx_dup_str(cf->ctx, XS_STRING),
219 	     0,
220 	     zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:1.0:action:implied-action"));
221   }
222 }
223 
224 /* ============== Policy Enforcement Point, Authorization Decision Query ============== */
225 
226 /*() Call Policy Decision Point (PDP) to obtain an authorization decision
227  * about a contemplated action on a resource. The attributes from the session
228  * pool, as filtered by PEPMAP are fed to the PDP as inputs
229  * for the decision. The call is using XACML SAML profile over SOAP.
230  *
231  * cf:: the configuration will need to have ~PEPMAP~ and ~PDP_URL~ options
232  *     set according to your situation.
233  * cgi:: if non-null, will receive error and status codes
234  * ses:: all attributes are obtained from the session. You may wish
235  *     to add additional attributes that are not known by SSO.
236  * pdp_url:: URL of the PDP to contact
237  * subj:: Linked list of subject attributes.
238  * rsrc:: Linked list of resource attributes.
239  * act::  Linked list of action attributes (usually just one attribute).
240  * envi::  Linked list of environment attributes.
241  * returns:: SAML Response as data structure or null upon error.
242  */
243 
244 /* Called by:  zxid_call_trustpdp, zxid_pep_az_base_soap_pepmap, zxid_pep_az_soap_pepmap */
zxid_az_soap(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,const char * pdp_url,struct zx_xac_Attribute_s * subj,struct zx_xac_Attribute_s * rsrc,struct zx_xac_Attribute_s * act,struct zx_xac_Attribute_s * envi)245 static struct zx_sp_Response_s* zxid_az_soap(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* pdp_url, struct zx_xac_Attribute_s* subj, struct zx_xac_Attribute_s* rsrc, struct zx_xac_Attribute_s* act, struct zx_xac_Attribute_s* envi)
246 {
247   X509* sign_cert;
248   EVP_PKEY* sign_pkey;
249   struct zxsig_ref refs;
250   struct zx_root_s* r;
251   struct zx_e_Header_s* hdr;
252   struct zx_e_Body_s* body;
253   struct zx_wsse_Security_s* sec;
254   struct zx_str* ss;
255   struct zx_sp_Response_s* resp;
256 
257   hdr = zx_NEW_e_Header(cf->ctx,0);
258 #if 0
259   hdr->Action = zx_NEW_a_Action(cf->ctx, &hdr->gg);
260   //zx_add_content(cf->ctx, &hdr->Action->gg, zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:2.0:profile:saml2.0:v2:schema:protocol:cd-01"));
261   zx_add_content(cf->ctx, &hdr->Action->gg, zx_dup_str(cf->ctx, "urn:oasis:xacml:2.0:saml:protocol:schema:os"));
262 
263   //zx_add_content(cf->ctx, &hdr->Action->gg, zx_dup_str(cf->ctx, "SAML2XACMLAuthzRequest"));
264   //zx_add_content(cf->ctx, &hdr->Action->gg, zx_dup_str(cf->ctx, "http://ws.apache.org/axis2/TestPolicyPortType/authRequestRequest"));
265   hdr->Action->mustUnderstand = zx_ref_str(cf->ctx, XML_TRUE);
266   hdr->Action->actor = zx_ref_str(cf->ctx, SOAP_ACTOR_NEXT);
267 #endif
268 
269   /* Add our own token so PDP can do whatever PEP can (they are considered to be
270    * part of the same entity). This is TAS3 specific hack. */
271 
272   if (!(cf->az_opt & 0x01)) {
273     sec = hdr->Security = zx_NEW_wsse_Security(cf->ctx, &hdr->gg);
274     sec->actor = zx_ref_attr(cf->ctx, &sec->gg, zx_e_actor_ATTR, SOAP_ACTOR_NEXT);
275     sec->mustUnderstand = zx_ref_attr(cf->ctx, &sec->gg, zx_e_mustUnderstand_ATTR, XML_TRUE);
276     sec->Timestamp = zx_NEW_wsu_Timestamp(cf->ctx, &sec->gg);
277     sec->Timestamp->Created = zx_NEW_wsu_Created(cf->ctx, &sec->Timestamp->gg);
278     zx_add_content(cf->ctx, &sec->Timestamp->Created->gg, zxid_date_time(cf, time(0)));
279     sec->Assertion = ses->tgta7n;
280     zx_reverse_elem_lists(&sec->gg);
281     D("tgta7n=%p", ses->tgta7n);
282   }
283   /* Prepare request according to the version */
284 
285   body = zx_NEW_e_Body(cf->ctx,0);
286   if (!strcmp(cf->xasp_vers, "xac-soap")) {
287     ZX_ADD_KID(body, xac_Request, zxid_mk_xac_az(cf, &body->gg, subj, rsrc, act, envi));
288 #if 0
289     /* *** xac:Response does not have signature field */
290     if (cf->sso_soap_sign) {
291       ZERO(&refs, sizeof(refs));
292       refs.id = body->xac_Request->ID;
293       refs.canon = zx_EASY_ENC_SO_xac_Request(cf->ctx, body->xac_Request);
294       if (zxid_lazy_load_sign_cert_and_pkey(cf, &sign_cert, &sign_pkey, "use sign cert az xac-soap")) {
295 	body->xac_Request->Signature = zxsig_sign(cf->ctx, 1, &refs, sign_cert, sign_pkey, cf->xmldsig_sig_meth, cf->xmldsig_digest_algo);
296 	zx_add_kid(&body->xac_Request->gg, &body->xac_Request->Signature->gg);
297       }
298       zx_str_free(cf->ctx, refs.canon);
299     }
300 #endif
301   } else if (!strcmp(cf->xasp_vers, "2.0-cd1")) {
302     ZX_ADD_KID(body, xaspcd1_XACMLAuthzDecisionQuery, zxid_mk_az_cd1(cf, subj, rsrc, act, envi));
303     if (cf->sso_soap_sign) {
304       ZERO(&refs, sizeof(refs));
305       refs.id = &body->xaspcd1_XACMLAuthzDecisionQuery->ID->g;
306       refs.canon = zx_easy_enc_elem_sig(cf, &body->xaspcd1_XACMLAuthzDecisionQuery->gg);
307       if (zxid_lazy_load_sign_cert_and_pkey(cf, &sign_cert, &sign_pkey, "use sign cert az cd1")) {
308 	body->xaspcd1_XACMLAuthzDecisionQuery->Signature
309 	  = zxsig_sign(cf->ctx, 1, &refs, sign_cert, sign_pkey, cf->xmldsig_sig_meth, cf->xmldsig_digest_algo);
310 	zx_add_kid_after_sa_Issuer(&body->xaspcd1_XACMLAuthzDecisionQuery->gg, &body->xaspcd1_XACMLAuthzDecisionQuery->Signature->gg);
311       }
312       zx_str_free(cf->ctx, refs.canon);
313     }
314   } else {
315     ZX_ADD_KID(body, XACMLAuthzDecisionQuery, zxid_mk_az(cf, subj, rsrc, act, envi));
316     if (cf->sso_soap_sign) {
317       ZERO(&refs, sizeof(refs));
318       refs.id = &body->XACMLAuthzDecisionQuery->ID->g;
319       refs.canon = zx_easy_enc_elem_sig(cf, &body->XACMLAuthzDecisionQuery->gg);
320       if (zxid_lazy_load_sign_cert_and_pkey(cf, &sign_cert, &sign_pkey, "use sign cert az")) {
321 	body->XACMLAuthzDecisionQuery->Signature
322 	  = zxsig_sign(cf->ctx, 1, &refs, sign_cert, sign_pkey, cf->xmldsig_sig_meth, cf->xmldsig_digest_algo);
323 	zx_add_kid_after_sa_Issuer(&body->XACMLAuthzDecisionQuery->gg, &body->XACMLAuthzDecisionQuery->Signature->gg);
324       }
325       zx_str_free(cf->ctx, refs.canon);
326     }
327   }
328 
329   /* Perform the network I/O for the call (connect to PDP) */
330 
331 #if 0
332   //ss = zx_ref_str(cf->ctx, "https://idpdemo.tas3.eu:8443/zxididp?o=S");
333   // http://192.168.136.42:1104/axis2/services/TestPolicy.PERMISAuthzServerHttpSoap12Endpoint/
334   // http://192.168.1.27:1104/axis2/services/TestPolicy?wsdl
335   // http://192.168.1.66:1104/axis2/services/TestPolicy.PERMISAuthzServerHttpEndpoint/
336   ss = zx_ref_str(cf->ctx, "http://192.168.1.27:1104/axis2/services/TestPolicy.PERMISAuthzServerHttpEndpoint/");
337   //ss = zx_ref_str(cf->ctx, "");
338   //ss = zx_ref_str(cf->ctx, "http://192.168.1.66:1104/axis2/services/TestPolicy.PERMISAuthzServerHttpEndpoint/");
339   //ss = zx_ref_str(cf->ctx, "http://192.168.1.27:1104/axis2/services/TestPolicy.PERMISAuthzServerHttpSoap12Endpoint/");
340 #else
341   ss = zx_ref_str(cf->ctx, pdp_url);
342 #endif
343   r = zxid_soap_call_hdr_body(cf, ss, hdr, body);
344   //r = zxid_idp_soap(cf, cgi, ses, idp_meta, ZXID_MNI_SVC, body);
345   if (!r || !r->Envelope || !r->Envelope->Body || !r->Envelope->Body->Response) {
346     ERR("Missing Response or other essential element %p %p %p %p", r, r?r->Envelope:0, r && r->Envelope?r->Envelope->Body:0, r && r->Envelope && r->Envelope->Body ? r->Envelope->Body->Response:0);
347     return 0;
348   }
349 
350   resp = r->Envelope->Body->Response;
351 
352   /* Parse response from the PDP. */
353 
354   if (!zxid_saml_ok(cf, cgi, resp->Status, "AzResp")) {
355     ERR("Response->Status no OK (%p)", resp->Status);
356     return 0;
357   }
358   if (!resp->Assertion) {
359     ERR("No Assertion in the Response (%p)", resp);
360     return 0;
361   }
362 
363   return resp;
364 }
365 
366 /*(i) Call Policy Decision Point (PDP) to obtain an authorization decision
367  * about a contemplated action on a resource. The attributes from the session
368  * pool, as filtered by PEPMAP are fed to the PDP as inputs
369  * for the decision. The call is using XACML SAML profile over SOAP.
370  *
371  * cf:: the configuration will need to have ~PEPMAP~
372  *     set according to your situation.
373  * cgi:: if non-null, will receive error and status codes
374  * ses:: all attributes are obtained from the session. You may wish
375  *     to add additional attributes that are not known by SSO.
376  * pdp_url:: URL of the PDP to contact
377  * pepmap:: The map used to extract the attributes from the pool to the XACML request
378  * returns:: 0 on error or deny (for any reason, e.g. indeterminate); in case of
379  *     permit returns <xac:Response> as string, allowing the obligations to be extracted.
380  *
381  * For simpler API, see zxid_az() family of functions.
382  */
383 
384 /* Called by:  zxid_pep_az_soap, zxid_query_ctlpt_pdp, zxid_simple_ab_pep, zxid_simple_ses_active_cf */
zxid_pep_az_soap_pepmap(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,const char * pdp_url,struct zxid_map * pepmap,const char * lk)385 char* zxid_pep_az_soap_pepmap(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* pdp_url, struct zxid_map* pepmap, const char* lk)
386 {
387   struct zx_xac_Attribute_s* subj = 0;
388   struct zx_xac_Attribute_s* rsrc = 0;
389   struct zx_xac_Attribute_s* act = 0;
390   struct zx_xac_Attribute_s* env = 0;
391   struct zx_str* ss;
392   struct zx_sp_Response_s* resp;
393   struct zx_sa_Statement_s* stmt;
394   struct zx_xasa_XACMLAuthzDecisionStatement_s* az_stmt;
395   struct zx_xasacd1_XACMLAuthzDecisionStatement_s* az_stmt_cd1;
396   struct zx_elem_s* decision = 0;
397   char* p;
398 
399   if (cf->log_level>0)
400     zxlog(cf, 0, 0, 0, 0, 0, 0, ses?ZX_GET_CONTENT(ses->nameid):0, "N", "W", "AZSOAP", ses?ses->sid:0, " ");
401 
402   if (!pdp_url || !*pdp_url) {
403     if (cf->az_fail_mode & (ZXID_AZ_FAIL_MODE1_MISSING_URL | ZXID_AZ_FAIL_MODE4_PERMIT_ALWAYS)) {
404       ERR("%s: No PDP_URL or PDP_CALL_URL set. Permit due to AZ_FAIL_MODE=%d",lk,cf->az_fail_mode);
405       goto fail_mode_permit;
406     } else {
407       ERR("%s: No PDP_URL or PDP_CALL_URL set. Deny. %p", lk, pdp_url);
408       return 0;
409     }
410   }
411 
412   zxid_pepmap_extract(cf, cgi, ses, pepmap, &subj, &rsrc, &act, &env);
413   resp = zxid_az_soap(cf, cgi, ses, pdp_url, subj, rsrc, act, env);
414   if (!resp || !resp->Assertion) {
415     if (cf->az_fail_mode & (ZXID_AZ_FAIL_MODE2_NET_FAIL | ZXID_AZ_FAIL_MODE4_PERMIT_ALWAYS)) {
416       ERR("%s: Malformed or nonexistent authorization response from PDP(%s) (%p).\nWARNING: Permit due to AZ_FAIL_MODE=%d", lk, pdp_url, resp, cf->az_fail_mode);
417       goto fail_mode_permit;
418     } else {
419       ERR("%s: DENY due to malformed authorization response from PDP. Either no response or response lacking assertion. %p", lk, resp);
420       return 0;
421     }
422   }
423 
424   az_stmt = resp->Assertion->XACMLAuthzDecisionStatement;
425   if (az_stmt && az_stmt->Response && az_stmt->Response->Result) {
426     decision = az_stmt->Response->Result->Decision;
427     if (ZX_CONTENT_EQ_CONST(decision, "Permit")) {
428       ss = zx_easy_enc_elem_opt(cf, &az_stmt->Response->gg);
429       if (!ss || !ss->len) goto nomem;
430       p = ss->s;
431       INFO("%s: PERMIT found in azstmt len=%d", lk, ss->len);
432       ZX_FREE(cf->ctx, ss);
433       return p;
434     }
435   }
436   az_stmt_cd1 = resp->Assertion->xasacd1_XACMLAuthzDecisionStatement;
437   if (az_stmt_cd1 && az_stmt_cd1->Response && az_stmt_cd1->Response->Result) {
438     decision = az_stmt_cd1->Response->Result->Decision;
439     if (ZX_CONTENT_EQ_CONST(decision, "Permit")) {
440       ss = zx_easy_enc_elem_opt(cf, &az_stmt_cd1->Response->gg);
441       if (!ss || !ss->len) goto nomem;
442       p = ss->s;
443       ZX_FREE(cf->ctx, ss);
444       INFO("%s: PERMIT found in azstmt_cd1 len=%d", lk, ss->len);
445       ZX_FREE(cf->ctx, ss);
446       return p;
447     }
448   }
449   stmt = resp->Assertion->Statement;
450   if (stmt && stmt->Response && stmt->Response->Result) {  /* Response here is xac:Response */
451     decision = stmt->Response->Result->Decision;
452     if (ZX_CONTENT_EQ_CONST(decision, "Permit")) {
453       ss = zx_easy_enc_elem_opt(cf, &stmt->Response->gg);
454       if (!ss || !ss->len) goto nomem;
455       p = ss->s;
456       INFO("%s: PERMIT found in stmt len=%d", lk, ss->len);
457       ZX_FREE(cf->ctx, ss);
458       return p;
459     }
460   }
461   /*if (resp->Assertion->AuthzDecisionStatement) {  }*/
462   if (decision) {
463     if (ZX_CONTENT_EQ_CONST(decision, "Deny")) {
464       if (cf->az_fail_mode & ZXID_AZ_FAIL_MODE4_PERMIT_ALWAYS) {
465 	ERR("%s: DENY found in stmt.\nWARNING: Permit due to AZ_FAIL_MODE=%d",lk,cf->az_fail_mode);
466 	goto fail_mode_permit;
467       } else {
468 	INFO("%s: DENY found in stmt", lk);
469 	return 0;
470       }
471     } else {
472       if (cf->az_fail_mode & ZXID_AZ_FAIL_MODE4_PERMIT_ALWAYS) {
473 	ERR("%s: DENY due to Other(%.*s).\nWARNING: Permit due to AZ_FAIL_MODE=%d", lk, ZX_GET_CONTENT_LEN(decision), ZX_GET_CONTENT_S(decision), cf->az_fail_mode);
474 	goto fail_mode_permit;
475       } else {
476 	INFO("%s: DENY due to Other(%.*s) (treated as Deny) found in stmt", lk, ZX_GET_CONTENT_LEN(decision), ZX_GET_CONTENT_S(decision));
477 	return 0;
478       }
479     }
480   }
481   if (cf->az_fail_mode & ZXID_AZ_FAIL_MODE4_PERMIT_ALWAYS) {
482     ERR("%s: DENY due to error or no xac:Response from PPD.\nWARNING: Permit due to AZ_FAIL_MODE=%d", lk, cf->az_fail_mode);
483     goto fail_mode_permit;
484   } else {
485     ERR("%s: DENY due to error or no xac:Response from PDP %p %p %p", lk,az_stmt,az_stmt_cd1,stmt);
486     return 0;
487   }
488  nomem:
489   ERR("%s: DENY due memory allocation error. %p", lk, ss);
490   return 0;
491  fail_mode_permit:
492   return zx_dup_cstr(cf->ctx,"<xac:Response xmlns:xac=\"urn:oasis:names:tc:xacml:2.0:context:schema:os\"><xac:Result><xac:Decision>Permit</xac:Decision><xac:Status><xac:StatusCode Value=\"urn:oasis:names:tc:xacml:1.0:status:ok\"/></xac:Status></xac:Result></xac:Response>");
493 }
494 
495 /*(i) Call Policy Decision Point (PDP) to obtain an authorization decision
496  * about a contemplated action on a resource. The attributes from the session
497  * pool, as filtered by PEPMAP are fed to the PDP as inputs
498  * for the decision. The call is using XACML SAML profile over SOAP.
499  *
500  * This is similar to zxid_pep_az_soap_pepmap() with the difference that the <xac:Response>
501  * element is returned even in the deny and indeterminate cases (null
502  * is still returned if there was an error). Effectively this +base+
503  * form does not make judgement about whether <xac:Response> means
504  * permit, deny, or something else.
505  *
506  * You should use this function if the Deny message contains interesting
507  * obligations (normally it does not).
508  *
509  * cf:: the configuration will need to have ~PEPMAP~ and ~PDP_URL~ options
510  *     set according to your situation.
511  * cgi:: if non-null, will receive error and status codes
512  * ses:: all attributes are obtained from the session. You may wish
513  *     to add additional attributes that are not known by SSO.
514  * pdp_url:: URL of the PDP to contact
515  * pepmap:: The map used to extract the attributes from the pool to the XACML request
516  * returns:: 0 on error; in case of deny (or indeterminate, etc.) as well as
517  *     permit returns <xac:Response> as string, allowing the obligations to be extracted.
518  *
519  * For simpler API, see zxid_az_base() family of functions.
520  */
521 
522 /* Called by:  zxid_pep_az_base_soap */
zxid_pep_az_base_soap_pepmap(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,const char * pdp_url,struct zxid_map * pepmap)523 char* zxid_pep_az_base_soap_pepmap(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* pdp_url, struct zxid_map* pepmap)
524 {
525   struct zx_xac_Attribute_s* subj = 0;
526   struct zx_xac_Attribute_s* rsrc = 0;
527   struct zx_xac_Attribute_s* act = 0;
528   struct zx_xac_Attribute_s* env = 0;
529   char* res;
530   struct zx_str* ss;
531   struct zx_sp_Response_s* resp;
532   struct zx_sa_Statement_s* stmt;
533   struct zx_xasa_XACMLAuthzDecisionStatement_s* az_stmt;
534   struct zx_xasacd1_XACMLAuthzDecisionStatement_s* az_stmt_cd1;
535 
536   if (cf->log_level>0)
537     zxlog(cf, 0, 0, 0, 0, 0, 0, ses?ZX_GET_CONTENT(ses->nameid):0, "N", "W", "AZSOAP", ses?ses->sid:0, " ");
538 
539   if (!pdp_url || !*pdp_url) {
540     ERR("No PDP_URL or PDP_CALL_URL set. Deny. %p", pdp_url);
541     return 0;
542   }
543 
544   zxid_pepmap_extract(cf, cgi, ses, pepmap, &subj, &rsrc, &act, &env);
545   resp = zxid_az_soap(cf, cgi, ses, pdp_url, subj, rsrc, act, env);
546   if (!resp || !resp->Assertion) {
547     ERR("DENY due to malformed authorization response from PDP. Either no response or response lacking assertion. %p", resp);
548     return 0;
549   }
550 
551   az_stmt = resp->Assertion->XACMLAuthzDecisionStatement;
552   if (az_stmt && az_stmt->Response) {
553     ss = zx_easy_enc_elem_opt(cf, &az_stmt->Response->gg);
554     if (!ss || !ss->len)
555       return 0;
556     res = ss->s;
557     ZX_FREE(cf->ctx, ss);
558     D("azstmt(%s)", res);
559     return res;
560   }
561   az_stmt_cd1 = resp->Assertion->xasacd1_XACMLAuthzDecisionStatement;
562   if (az_stmt_cd1 && az_stmt_cd1->Response) {
563     ss = zx_easy_enc_elem_opt(cf, &az_stmt_cd1->Response->gg);
564     if (!ss || !ss->len)
565       return 0;
566     res = ss->s;
567     ZX_FREE(cf->ctx, ss);
568     D("cd1(%s)", res);
569     return res;
570   }
571   stmt = resp->Assertion->Statement;
572   if (stmt && stmt->Response) {  /* Response here is xac:Response */
573     ss = zx_easy_enc_elem_opt(cf, &stmt->Response->gg);
574     if (!ss || !ss->len)
575       return 0;
576     res = ss->s;
577     ZX_FREE(cf->ctx, ss);
578     D("stmt(%s)", res);
579     return res;
580   }
581 
582   D("Missing az related Response element %d",0);
583   return 0;
584 }
585 
586 /*() Call Policy Decision Point (PDP) to obtain an authorization decision.
587  * Uses default PEPMAP to call zxid_pep_az_soap_pepmap(). */
588 
589 /* Called by:  zxid_az_cf_ses */
zxid_pep_az_soap(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,const char * pdp_url)590 char* zxid_pep_az_soap(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* pdp_url) {
591   return zxid_pep_az_soap_pepmap(cf, cgi, ses, pdp_url, cf->pepmap, "az_soap");
592 }
593 
594 /* Called by:  zxid_az_base_cf_ses */
zxid_pep_az_base_soap(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,const char * pdp_url)595 char* zxid_pep_az_base_soap(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* pdp_url) {
596   return zxid_pep_az_base_soap_pepmap(cf, cgi, ses, pdp_url, cf->pepmap);
597 }
598 
599 /*int zxid_az_cf_cgi_ses(zxid_conf* cf,  zxid_cgi* cgi, zxid_ses* ses);*/
600 
601 /*(i) Call Policy Decision Point (PDP) to obtain an authorization decision
602  * about a contemplated action on a resource. The attributes from the session
603  * pool, as filtered by ~PEPMAP~ are fed to the PDP as inputs
604  * for the decision. Session object is passed in as an argument.
605  *
606  * cf:: the configuration will need to have ~PEPMAP~ and ~PDP_URL~
607  *     or ~PDP_CALL_URL~ options set according to your situation.
608  * qs:: if non-null, will resceive error and status codes
609  * ses:: all attributes are obtained from the session. You may wish
610  *     to add additional attributes that are not known by SSO.
611  *     The session object, e.g. from zxid_get_ses()
612  * returns:: 0 on deny (for any reason, e.g. indeterminate),  or string
613  *     containing the obligations on permit.
614  *
615  * For simpler API, see zxid_az() family of functions.
616  */
617 
618 /* Called by:  zxcall_main, zxid_az_cf */
zxid_az_cf_ses(zxid_conf * cf,const char * qs,zxid_ses * ses)619 char* zxid_az_cf_ses(zxid_conf* cf, const char* qs, zxid_ses* ses)
620 {
621   zxid_cgi cgi;
622   char* ret;
623   char* url = (cf->pdp_call_url&&*cf->pdp_call_url) ? cf->pdp_call_url : cf->pdp_url;
624   D_INDENT("az: ");
625   ZERO(&cgi, sizeof(cgi));
626   /*zxid_parse_cgi(cf, &cgi, "");  DD("qs(%s) ses=%p", STRNULLCHKD(qs), ses);*/
627   if (qs && ses)
628     zxid_add_qs2ses(cf, ses, zx_dup_cstr(cf->ctx, qs), 1);
629   ret = zxid_pep_az_soap(cf, &cgi, ses, url);
630   D_DEDENT("az: ");
631   return ret;
632 }
633 
634 /* Called by:  zxid_az_base_cf */
zxid_az_base_cf_ses(zxid_conf * cf,const char * qs,zxid_ses * ses)635 char* zxid_az_base_cf_ses(zxid_conf* cf, const char* qs, zxid_ses* ses)
636 {
637   zxid_cgi cgi;
638   char* ret;
639   char* url = (cf->pdp_call_url&&*cf->pdp_call_url) ? cf->pdp_call_url : cf->pdp_url;
640   D_INDENT("azb: ");
641   ZERO(&cgi, sizeof(cgi));
642   /*zxid_parse_cgi(cf, &cgi, "");  DD("qs(%s) ses=%p", STRNULLCHKD(qs), ses);*/
643   if (qs && ses)
644     zxid_add_qs2ses(cf, ses, zx_dup_cstr(cf->ctx, qs), 1);
645   ret = zxid_pep_az_base_soap(cf, &cgi, ses, url);
646   D_DEDENT("azb: ");
647   return ret;
648 }
649 
650 /*(i) Call Policy Decision Point (PDP) to obtain an authorization decision
651  * about a contemplated action on a resource. The attributes from the session
652  * pool, as filtered by ~PEPMAP~ are fed to the PDP as inputs
653  * for the decision. Session is identified by a session id.
654  *
655  * cf:: the configuration will need to have ~PEPMAP~ and ~PDP_URL~ options
656  *     set according to your situation.
657  * qs:: if non-null, will resceive error and status codes
658  * sid:: all attributes are obtained from the session. You may wish
659  *     to add additional attributes that are not known by SSO. The
660  *     session id, such as returned from SSO.
661  * returns:: 0 on deny (for any reason, e.g. indeterminate),  or string
662  *     containing the obligations on permit.
663  *
664  * For simpler API, see zxid_az() family of functions.
665  */
666 
667 /* Called by:  zxid_az */
zxid_az_cf(zxid_conf * cf,const char * qs,const char * sid)668 char* zxid_az_cf(zxid_conf* cf, const char* qs, const char* sid)
669 {
670   zxid_ses ses;
671   ZERO(&ses, sizeof(ses));
672   if (sid && sid[0]) {
673     zxid_get_ses(cf, &ses, sid);
674     zxid_ses_to_pool(cf, &ses);
675   }
676   return zxid_az_cf_ses(cf, qs, &ses);
677 }
678 
679 /* Called by:  zxid_az_base */
zxid_az_base_cf(zxid_conf * cf,const char * qs,const char * sid)680 char* zxid_az_base_cf(zxid_conf* cf, const char* qs, const char* sid)
681 {
682   zxid_ses ses;
683   ZERO(&ses, sizeof(ses));
684   if (sid && sid[0]) {
685     zxid_get_ses(cf, &ses, sid);
686     zxid_ses_to_pool(cf, &ses);
687   }
688   return zxid_az_base_cf_ses(cf, qs, &ses);
689 }
690 
691 /*() See zxid_az_cf() for description. Only difference is that the configuration
692  * is accepted as a string instead of an object. */
693 
694 /* Called by: */
zxid_az(const char * conf,const char * qs,const char * sid)695 char* zxid_az(const char* conf, const char* qs, const char* sid)
696 {
697   struct zx_ctx ctx;
698   zxid_conf cf;
699   zx_reset_ctx(&ctx);
700   ZERO(&cf, sizeof(cf));
701   cf.ctx = &ctx;
702   zxid_conf_to_cf_len(&cf, -1, conf);
703   return zxid_az_cf(&cf, qs, sid);
704 }
705 
706 /* Called by: */
zxid_az_base(const char * conf,const char * qs,const char * sid)707 char* zxid_az_base(const char* conf, const char* qs, const char* sid)
708 {
709   struct zx_ctx ctx;
710   zxid_conf cf;
711   zx_reset_ctx(&ctx);
712   ZERO(&cf, sizeof(cf));
713   cf.ctx = &ctx;
714   zxid_conf_to_cf_len(&cf, -1, conf);
715   return zxid_az_base_cf(&cf, qs, sid);
716 }
717 
718 /*() Call Trust Policy Decision Point (PDP) to obtain a trust evaluation and scoring
719  *
720  * You should use this function if the Deny message contains interesting
721  * obligations (normally it does not).
722  *
723  * cf:: the configuration will need to have ~PEPMAP~ and ~PDP_URL~ options
724  *     set according to your situation.
725  * cgi:: if non-null, will receive error and status codes
726  * ses:: all attributes are obtained from the session. You may wish
727  *     to add additional attributes that are not known by SSO.
728  * pepmap:: The map used to extract the attributes from the pool to the XACML request
729  * start:: Start of query string format buffer of trust options
730  * lim:: One past end of trust option buffer
731  * returns:: 0 on error; in case of deny (or indeterminate, etc.) as well as
732  *     permit returns <xac:Response> as string, allowing the obligations to be extracted.
733  */
734 
735 /* Called by:  zxid_di_query */
zxid_call_trustpdp(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,struct zxid_map * pepmap,const char * start,const char * lim,zxid_epr * epr)736 int zxid_call_trustpdp(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, struct zxid_map* pepmap, const char* start, const char* lim, zxid_epr* epr)
737 {
738   struct zx_xac_Attribute_s* xac_at;
739   struct zx_xac_Attribute_s* subj = 0;
740   struct zx_xac_Attribute_s* rsrc = 0;
741   struct zx_xac_Attribute_s* act = 0;
742   struct zx_xac_Attribute_s* env = 0;
743   const char* val;
744   const char* sep;
745   struct zx_str* ss;
746   struct zx_sp_Response_s* resp;
747   struct zx_sp_StatusCode_s* sc;
748   struct zx_sa_Statement_s* stmt;
749   struct zx_xasa_XACMLAuthzDecisionStatement_s* az_stmt;
750   struct zx_xasacd1_XACMLAuthzDecisionStatement_s* az_stmt_cd1;
751   struct zx_elem_s* decision;
752   struct zx_tas3_TrustRanking_s* tr;
753 
754   D("option(%.*s)", ((int)(lim-start)), start);
755 
756   if (cf->log_level>0)
757     zxlog(cf, 0, 0, 0, 0, 0, 0, ses?ZX_GET_CONTENT(ses->nameid):0, "N", "W", "TRUSTPDP", ses?ses->sid:0, " ");
758 
759   if (!cf->trustpdp_url || !*cf->trustpdp_url) {
760     ERR("No TRUSTPDP_URL. Deny. %p", cf->trustpdp_url);
761     return 0;
762   }
763 
764 #if 1
765   if (epr->Metadata && epr->Metadata->ProviderID) {
766     ss = ZX_GET_CONTENT(epr->Metadata->ProviderID);
767     D("Using ProviderID(%.*s) as resource-id", ss->len, ss->s);
768     rsrc = zxid_mk_xacml_simple_at(cf, 0,
769 				     zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:1.0:resource:resource-id"),
770 				     zx_dup_str(cf->ctx, XS_STRING),
771 				     0, zx_dup_zx_str(cf->ctx, ss));
772     ss = ZX_GET_CONTENT(epr->Metadata->ServiceType);
773     xac_at = zxid_mk_xacml_simple_at(cf, 0,
774 				     zx_dup_str(cf->ctx, "urn:tas3:servicetype"),
775 				     zx_dup_str(cf->ctx, XS_STRING),
776 				     0, zx_dup_zx_str(cf->ctx, ss));
777     ZX_NEXT(xac_at) = &rsrc->gg.g;
778     rsrc = xac_at;
779   } else {
780     ERR("EPR does not have Metadata or Metadata/ProviderID. resource-id not set %p",epr->Metadata);
781   }
782 
783   if (ses->nid && *ses->nid) {
784 #if 0
785     /* Pass user as subject. This is TAS3 correct behaviour. */
786     subj = zxid_mk_xacml_simple_at(cf, 0,
787 		zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:1.0:subject:subject-id"),
788 		zx_dup_str(cf->ctx, XS_STRING),
789 		0,
790 		zx_dup_str(cf->ctx, ses->nid));
791 #else
792     /* *** Pass service to rank as subject. This is a kludge to work around Jerry's bug. */
793     ss = ZX_GET_CONTENT(epr->Metadata->ProviderID);
794     D("*** Using ProviderID(%.*s) as subject-id", ss->len, ss->s);
795     subj = zxid_mk_xacml_simple_at(cf, 0,
796 		zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:1.0:subject:subject-id"),
797 		zx_dup_str(cf->ctx, XS_STRING),
798 		0, zx_dup_zx_str(cf->ctx, ss));
799 #endif
800   } else {
801     D("No ses->nid %p", ses->nid);
802   }
803 
804   act = zxid_mk_xacml_simple_at(cf, 0,
805 	     zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:1.0:action:action-id"),
806 	     zx_dup_str(cf->ctx, XS_STRING),
807 	     0,
808 	     zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:1.0:action:implied-action"));
809 #else
810   zxid_pepmap_extract(cf, cgi, ses, pepmap, &subj, &rsrc, &act, &env);
811 #endif
812 
813   while (start < lim && (start = zx_memmem(start, (lim - start), TAS3_TRUST_INPUT_CTL1, sizeof(TAS3_TRUST_INPUT_CTL1)-1))) {
814 
815     val = memchr(start+sizeof(TAS3_TRUST_INPUT_CTL1)-1, '=', lim - (start+sizeof(TAS3_TRUST_INPUT_CTL1)-1));
816     if (!val) {
817       ERR("Malformed trust option(%.*s)", ((int)(lim-start)), start);
818       break;
819     }
820     ++val;
821     sep = memchr(val, '&', lim-val);
822     if (!sep) {
823       sep = lim;
824     }
825 
826     D("add trust attr(%.*s) val(%.*s)", ((int)(val-1-start)), start, ((int)(sep-val)), val);
827     xac_at = zxid_mk_xacml_simple_at(cf, 0,
828 				     zx_dup_len_str(cf->ctx, val-1-start, start),
829 				     zx_dup_str(cf->ctx, XS_STRING),
830 				     0, zx_dup_len_str(cf->ctx, sep-val, val));
831     ZX_NEXT(xac_at) = &env->gg.g;
832     env = xac_at;
833     start = sep;
834   }
835 
836   resp = zxid_az_soap(cf, cgi, ses, cf->trustpdp_url, subj, rsrc, act, env);
837   if (!resp)
838     return 0;
839 
840   az_stmt = resp->Assertion->XACMLAuthzDecisionStatement;
841   if (az_stmt && az_stmt->Response) {
842     decision = az_stmt->Response->Result->Decision;
843   } else {
844     az_stmt_cd1 = resp->Assertion->xasacd1_XACMLAuthzDecisionStatement;
845     if (az_stmt_cd1 && az_stmt_cd1->Response) {
846       decision = az_stmt_cd1->Response->Result->Decision;
847     } else {
848       stmt = resp->Assertion->Statement;
849       if (stmt && stmt->Response) {  /* Response here is xac:Response */
850 	decision = stmt->Response->Result->Decision;
851       } else {
852 	D("Missing az related Response element %d",0);
853 	return 0;
854       }
855     }
856   }
857   if (ZX_CONTENT_EQ_CONST(decision, "Permit")) {
858     D("Permit %d",0);
859     if (resp->Status && resp->Status->StatusCode && resp->Status->StatusCode->StatusCode) {
860       /* Second and further layer status codes may contain Trust Rankings */
861       for (sc = resp->Status->StatusCode->StatusCode; sc; sc = sc->StatusCode) {
862 	if (!sc->Value || !sc->Value->g.len < sizeof(TAS3_TRUST_RANKING_CTL1)-1 ||!sc->Value->g.s
863 	    || memcmp(sc->Value->g.s, TAS3_TRUST_RANKING_CTL1,sizeof(TAS3_TRUST_RANKING_CTL1)-1))
864 	  continue;
865 	val = memchr(sc->Value->g.s + sizeof(TAS3_TRUST_RANKING_CTL1)-1, '=', sc->Value->g.len - (sizeof(TAS3_TRUST_RANKING_CTL1)-1));
866 	if (!val) {
867 	  ERR("Malformed trust ranking(%.*s)", sc->Value->g.len, sc->Value->g.s);
868 	  continue;
869 	}
870 
871 	if (!epr->Metadata)
872 	  epr->Metadata = zx_NEW_a_Metadata(cf->ctx, &epr->gg);
873 	if (!epr->Metadata->Trust)
874 	  epr->Metadata->Trust = zx_NEW_tas3_Trust(cf->ctx, &epr->Metadata->gg);
875 	tr = zx_NEW_tas3_TrustRanking(cf->ctx, &epr->Metadata->Trust->gg);
876 	tr->metric = zx_dup_len_attr(cf->ctx, &tr->gg, zx_metric_ATTR,
877 				     val - sc->Value->g.s, sc->Value->g.s);
878 	++val;
879 	tr->val = zx_dup_len_attr(cf->ctx, &tr->gg, zx_val_ATTR,
880 				  sc->Value->g.len - (val - sc->Value->g.s), val);
881       }
882     }
883 
884     INFO("PERMIT found in azstmt len=%d", ss->len);
885     ZX_FREE(cf->ctx, ss);
886     return 1;
887   } else {
888     D("Deny %d",0);
889     return 0;
890   }
891 }
892 
893 /* EOF  --  zxidpep.c */
894