1 /* zxiddi.c  -  Discovery Server
2  * Copyright (c) 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) 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: zxiddi.c,v 1.2 2009-11-24 23:53:40 sampo Exp $
11  *
12  * 15.11.2009, created --Sampo
13  * 10.1.2011,  added TrustPDP and CPN support --Sampo
14  * 7.12.2013,  added EPR ranking --Sampo
15  *
16  * See also zxidepr.c for discovery client code.
17  *
18  *   zxcot -e http://idp.tas3.pt:8081/zxididp?o=S 'Discovery Svc' \
19  *     http://idp.tas3.pt:8081/zxididp?o=B urn:liberty:disco:2006-08 \
20  *   | zxcot -bs /var/zxid/idpdimd
21  */
22 
23 #include "platform.h"  /* for dirent.h */
24 #include "errmac.h"
25 #include "zxid.h"
26 #include "zxidpriv.h"
27 #include "zxidutil.h"
28 #include "zxidconf.h"
29 #include "saml2.h"
30 #include "tas3.h"
31 #include "wsf.h"
32 #include "c/zx-const.h"
33 #include "c/zx-ns.h"
34 #include "c/zx-data.h"
35 
36 /*() Recover end user's identity: uid at IdP. This is actually done via "self federation"
37  * that was created when token for accessing discovery was issued.
38  * Returns 1 on success, 0 on failure. */
39 
40 /* Called by:  zxid_di_query, zxid_imreq, zxid_ps_addent_invite, zxid_ps_resolv_id, zxid_ssos_anreq */
zxid_idp_map_nid2uid(zxid_conf * cf,int len,char * uid,zxid_nid * nameid,struct zx_lu_Status_s ** stp)41 int zxid_idp_map_nid2uid(zxid_conf* cf, int len, char* uid, zxid_nid* nameid, struct zx_lu_Status_s** stp)
42 {
43   struct zx_str* affil;
44   char sp_name_buf[1024];
45   if (!nameid) {
46     ERR("Missing nameid %d",0);
47     return 0;
48   }
49 
50   affil = nameid->SPNameQualifier ? &nameid->SPNameQualifier->g : zxid_my_ent_id(cf);
51   zxid_nice_sha1(cf, sp_name_buf, sizeof(sp_name_buf), affil, affil, 7);
52   len = read_all(len-1, uid, "idp_map_nid2uid", 1, "%s" ZXID_NID_DIR "%s/%.*s", cf->cpath, sp_name_buf, ZX_GET_CONTENT_LEN(nameid), ZX_GET_CONTENT_S(nameid));
53   if (!len) {
54     ERR("Can not find reverse mapping for SP,SHA1(%s) nid(%.*s)", sp_name_buf, ZX_GET_CONTENT_LEN(nameid), ZX_GET_CONTENT_S(nameid));
55     if (stp)
56       *stp = zxid_mk_lu_Status(cf, 0, "Fail", 0, 0, 0);
57     return 0;
58   }
59   return 1;
60 }
61 
62 /*(-) Return 1 if any requested servicetype prefix matches filename, i.e. EPR file
63  * is a viable candidate. Return 0 if no match. */
64 
zxid_di_match_prefix(int nth,struct zx_di_RequestedService_s * rs,struct dirent * de)65 static int zxid_di_match_prefix(int nth, struct zx_di_RequestedService_s* rs, struct dirent* de)
66 {
67   struct zx_elem_s* el;
68   struct zx_str* ss;
69   int len;
70   char prefix[ZXID_MAX_BUF];
71 
72   if (!rs->ServiceType)
73     return 1;  /* No svctype specified in query: all match */
74   for (el = rs->ServiceType; el; el = (struct zx_elem_s*)el->g.n) {
75     if (el->g.tok != zx_di_ServiceType_ELEM)
76       continue;
77     ss = ZX_GET_CONTENT(el);
78     if (!ss || !ss->len)
79       continue;
80     len = MIN(ss->len, sizeof(prefix)-1);
81     memcpy(prefix, ss->s, len);
82     prefix[len] = 0;
83     zxid_fold_svc(prefix, len);
84     if (memcmp(de->d_name, prefix, len) || de->d_name[len] != ',') {
85       D("%d:     no match prefix(%s) file(%s)", nth, prefix, de->d_name);
86     } else {
87       D("%d:     candidate due to prefix(%s) file(%s)", nth, prefix, de->d_name);
88       return 1;
89     }
90   }
91   return 0;
92 }
93 
94 /*(-) Return 1 if any requested svctype matches svctype parsed from file. Return 0 if no match. */
95 
zxid_di_match_svctype(int nth,struct zx_di_RequestedService_s * rs,struct zx_str * svctyp,struct dirent * de)96 static int zxid_di_match_svctype(int nth, struct zx_di_RequestedService_s* rs, struct zx_str* svctyp, struct dirent* de)
97 {
98   struct zx_elem_s* el;
99   struct zx_str* ss;
100 
101   if (!svctyp || !svctyp->len) {
102     INFO("EPR missing ServiceType. Rejected file(%s).", de->d_name);
103     return 0;
104   }
105   if (!rs->ServiceType)
106     return 1;  /* No svctype specified in query: all match */
107   for (el = rs->ServiceType; el; el = (struct zx_elem_s*)el->g.n) {
108     if (el->g.tok != zx_di_ServiceType_ELEM)
109       continue;
110     ss = ZX_GET_CONTENT(el);
111     if (!ss || !ss->len)
112       continue;
113     if (ss->len != svctyp->len || memcmp(ss->s, svctyp->s, ss->len)) {
114       D("%d: Requested svctype(%.*s) does not match file prefix(%.*s)", nth, ss->len, ss->s, svctyp->len, svctyp->s);
115       continue;
116     }
117     D("%d: ServiceType matches. file(%s)", nth, de->d_name);
118     return 1;
119   }
120   D("%d: Rejected due to ServiceType. file(%s)", nth, de->d_name);
121   return 0;
122 }
123 
124 /*(-) Return 1 if any requested svctype matches svctype parsed from file. Return 0 if no match. */
125 
zxid_di_match_entid(int nth,struct zx_di_RequestedService_s * rs,struct zx_str * prvid,struct zx_str * addr,struct dirent * de)126 static int zxid_di_match_entid(int nth, struct zx_di_RequestedService_s* rs, struct zx_str* prvid, struct zx_str* addr, struct dirent* de)
127 {
128   struct zx_elem_s* el;
129   struct zx_str* ss;
130 
131   if (!prvid || !prvid->len) {
132     INFO("EPR missing ProviderID. Rejected file(%s).", de->d_name);
133     return 0;
134   }
135   if (!rs->ProviderID)
136     return 1;  /* No ProviderID specified in query: all match */
137   for (el = rs->ProviderID; el; el = (struct zx_elem_s*)el->g.n) {
138     if (el->g.tok != zx_di_ProviderID_ELEM)
139       continue;
140     ss = ZX_GET_CONTENT(el);
141     if (!ss || !ss->len)
142       continue;
143     if (ss->len != prvid->len || memcmp(ss->s, prvid->s, ss->len)) {
144       D("%d: ProviderID(%.*s) does not match desired(%.*s)", nth, prvid->len, prvid->s, ss->len, ss->s);
145       continue;
146     }
147     D("%d: ProviderID matches. file(%s)", nth, de->d_name);
148     return 1;
149   }
150 
151   /* TAS3 extension: allow matching ProviderID by the Address (URL) as well */
152   for (el = rs->ProviderID; el; el = (struct zx_elem_s*)el->g.n) {
153     if (el->g.tok != zx_di_ProviderID_ELEM)
154       continue;
155     ss = ZX_GET_CONTENT(el);
156     if (!ss || !ss->len)
157       continue;
158     if (ss->len != addr->len || memcmp(ss->s, addr->s, ss->len)) {
159       D("%d: Address(%.*s) does not match desired(%.*s)", nth, addr->len, addr->s, ss->len, ss->s);
160       continue;
161     }
162     D("%d: Address matches. file(%s)", nth, de->d_name);
163     return 1;
164   }
165   return 0;
166 }
167 
168 /*(-) Return 1 if Discovery Options match. Return 0 if no match. */
169 
zxid_di_match_options(zxid_conf * cf,zxid_ses * ses,int nth,struct zx_di_RequestedService_s * rs,zxid_epr * epr,struct dirent * de)170 static int zxid_di_match_options(zxid_conf* cf, zxid_ses* ses, int nth, struct zx_di_RequestedService_s* rs, zxid_epr* epr, struct dirent* de)
171 {
172   struct zx_elem_s* el;
173   struct zx_str* ss;
174   char* p;
175   char* start;
176   char* lim;
177 
178   if (!rs->Options)
179     return 1;  /* none specified, automatic match */
180   for (el = rs->Options->Option; el; el = (struct zx_elem_s*)el->g.n) {
181     if (el->g.tok != zx_di_Option_ELEM)
182       continue;
183     ss = ZX_GET_CONTENT(el);
184     if (!ss || !ss->len) {
185       D("Option element does not have content %p", ss);
186       continue;
187     }
188     p = zx_memmem(ss->s, ss->len, TAS3_TRUST_INPUT_CTL1, sizeof(TAS3_TRUST_INPUT_CTL1)-1);
189     if (!p) {
190       D("Option(%.*s) is not trust related", ss->len, ss->s);
191       /* *** what about all other types of options?!? */
192       continue;
193     }
194     start = p;
195     lim = memchr(p+sizeof(TAS3_TRUST_INPUT_CTL1)-1, '&', ss->len - (p - ss->s));
196     if (!lim) {
197       lim = ss->s + ss->len;
198     } else {
199       while (p = zx_memmem(lim, ss->len - (lim - ss->s), TAS3_TRUST_INPUT_CTL1, sizeof(TAS3_TRUST_INPUT_CTL1)-1)) {
200 	lim = memchr(p+sizeof(TAS3_TRUST_INPUT_CTL1)-1, '&', ss->len - (p - ss->s));
201 	if (!lim) {
202 	  lim = ss->s + ss->len;
203 	  break;
204 	}
205       }
206     }
207 
208     if (cf->trustpdp_url && *cf->trustpdp_url) {
209       D("Trust related discovery options(%.*s), TRUSTPDP_URL(%s)", ((int)(lim-start)), start, cf->trustpdp_url);
210       if (zxid_call_trustpdp(cf, 0, ses, cf->pepmap_rsin, start, lim, epr)) {
211 	D("%d: Trust PERMIT. file(%s)", nth, de->d_name);
212 	/* *** return trust scorings as part of the EPR */
213 	continue;
214       } else {
215 	D("%d: Rejected due to Trust DENY. file(%s)", nth, de->d_name);
216 	return 0;
217       }
218     } else {
219       INFO("Trust related discovery options(%.*s), but no TRUSTPDP_URL configured", ((int)(lim-start)), start);
220       continue;
221     }
222   }
223   return 1;
224 }
225 
226 /*(-) Return 1 if credentials and Privacy Negotation matches. Return 0 if no match.
227  * This is a TAS3 extension. */
228 
zxid_di_match_cpn(zxid_conf * cf,zxid_ses * ses,int nth,struct zx_str * svctyp,struct zx_str * prvid,struct dirent * de)229 static int zxid_di_match_cpn(zxid_conf* cf, zxid_ses* ses, int nth, struct zx_str* svctyp, struct zx_str* prvid, struct dirent* de)
230 {
231   struct zx_str* ss;
232   if (!cf->cpn_ena)
233     return 1;
234 #if 0
235   /* Call Trust and Privacy Negotiation (TrustBuilder), Andreas. */
236   systemf("./tpn-client.sh %s %s %s", idpnid, "urn:idhrxml:cv:update", host);
237 #else
238   if (svctyp && svctyp->len && prvid && prvid->len) {
239     ss = zxid_callf(cf, ses, "urn:tas3:cpn-agent",0,0,0,
240 		 "<tas3cpn:CPNRequest xmlns:tas3cpn=\"urn:tas3:cpn-agent\">"
241 		   "<di:RequestedService xmlns:di=\"urn:liberty:disco:2006-08\">"
242 		     "<di:ServiceType>%.*s</di:ServiceType>"
243 		     "<di:ProviderID>%.*s</di:ProviderID>"
244 		     "<di:Framework version=\"2.0\"/>"
245 		     /*"<di:Action>urn:x-foobar:Create</di:Action>"*/
246 		   "</di:RequestedService>"
247 		 "</tas3cpn:CPNRequest>",
248 			  svctyp->len, svctyp->s,
249 			  prvid->len, prvid->s);
250     if (!ss || !ss->s) {
251       D("CPN returned nothing or emptiness (no CPN agent discoverable?) %p", ss);
252     } else {
253       D("CPN returned(%.*s)", ss->len, ss->s);
254     }
255   }
256 #endif
257   return 1;
258 }
259 
260 /*() Add rankKey field based on filename, if no rank key was specified
261  * in the EPR XML parsed from the file.
262  * Typically a service is represented by cached EPR file in session directory
263  * or in /var/zxid/dimd/ directory in the IdP case. This file name
264  * will have comma separated structure:
265  *
266  *   FOLDEDSVCTYP,RANKKEY,NICE_SHA1_OF_CONTENTS
267  *
268  * The goals of this arrangement are that discovery results would be predictable
269  * in ordering so that index numbers can be used to select one of the many
270  * discovered EPRS, and that the normally (LANG=C) sorted ls(1) listing matches
271  * discovery order.
272  *
273  * This function extracts everything after the first comma as rankKey.  */
274 
zxid_di_set_rankKey_if_needed(zxid_conf * cf,struct zx_a_Metadata_s * md,int nth,struct dirent * de)275 void zxid_di_set_rankKey_if_needed(zxid_conf* cf, struct zx_a_Metadata_s* md, int nth, struct dirent* de)
276 {
277   char buf[48];
278   char* p;
279   if (!md) {
280     ERR("%d: EPR lacks Metadata element", nth);
281     return;
282   }
283   if (md->rankKey)
284     return;  /* Already set in the XML parsed from file */
285 
286   p = strchr(de->d_name, ',');
287   if (!p) {
288     snprintf(buf, sizeof(buf), "Z%04d", nth);
289     buf[sizeof(buf)-1] = 0;
290     p = buf;
291   }
292   md->rankKey = zx_dup_attr(cf->ctx, &md->gg, zx_rankKey_ATTR, p);  /* strdup as de buf is temp */
293 }
294 
295 /*(-) We do not want to leak IdP internal ranking infor so clean these out. This
296  * also means better standards compliant output. The WSC can always recreate
297  * its own rankKey from the order in which the EPRs were received.
298  *
299  * See:: zxid_snarf_eprs() and zxid_get_epr() */
300 
zxid_di_sanitize_rankKey_out(zxid_epr * epr)301 static void zxid_di_sanitize_rankKey_out(zxid_epr* epr) {
302   for (; epr; epr = (zxid_epr*)epr->gg.g.n)
303     if (epr->Metadata)
304       epr->Metadata->rankKey = 0;  /* *** should we also free them? */
305 }
306 
307 /*(-) Compare two EPRs by rankKey (string comparison) to help sorting discovery results.
308  * Return -1 if a<b; 0 if a==b; 1 if a>b. */
309 
zxid_id_epr_cmp(zxid_epr * a,zxid_epr * b)310 static int zxid_id_epr_cmp(zxid_epr* a, zxid_epr* b) {
311   if (!a || !a->Metadata || !a->Metadata->rankKey)
312     return 1;  /* missing parts: sort to end of list */
313   if (!b || !b->Metadata || !b->Metadata->rankKey)
314     return -1;
315   return zx_str_cmp(&a->Metadata->rankKey->g, &b->Metadata->rankKey->g);
316 }
317 
318 /*() Sort discovery results (epr list) according to rankKey. */
319 
zxid_di_sort_eprs(zxid_conf * cf,zxid_epr * epr)320 zxid_epr* zxid_di_sort_eprs(zxid_conf* cf, zxid_epr* epr)
321 {
322   zxid_epr* out;
323   zxid_epr* ep;
324   zxid_epr* nxt;
325   zxid_epr* prv;
326 
327   if (!epr)
328     return 0;
329 
330   /* The number of EPRs is expected to be from one to several tens, but not hundreds.
331    * Thus a simple list insertion sort should be good enough, even optimal. */
332 
333   out = epr;
334   epr = (zxid_epr*)epr->gg.g.n;
335   out->gg.g.n = 0;
336 
337   for (; epr; epr = nxt) {
338     nxt = (zxid_epr*)epr->gg.g.n;
339     /* scan already sorted list out for right place for new insertion */
340     if (zxid_id_epr_cmp(out, epr) >= 0) {
341       out = epr;
342       epr = (zxid_epr*)epr->gg.g.n;
343       out->gg.g.n = 0;
344       continue;
345     }
346     for (prv = out, ep = (zxid_epr*)out->gg.g.n;
347 	 ep && zxid_id_epr_cmp(ep, epr) < 0;
348 	 prv = ep, ep = (zxid_epr*)ep->gg.g.n);
349     epr->gg.g.n = prv->gg.g.n;
350     prv->gg.g.n = &epr->gg.g;
351   }
352   return out;
353 }
354 
355 /*() Server side Discovery Service Query processing.
356  * See also:: zxid_gen_bootstraps(), zxid_find_epr() */
357 
358 /* Called by:  zxid_sp_soap_dispatch */
zxid_di_query(zxid_conf * cf,zxid_ses * ses,struct zx_di_Query_s * req)359 struct zx_di_QueryResponse_s* zxid_di_query(zxid_conf* cf, zxid_ses* ses,struct zx_di_Query_s* req)
360 {
361   struct zx_di_RequestedService_s* rs;
362   struct zx_di_QueryResponse_s* resp = zx_NEW_di_QueryResponse(cf->ctx,0);
363   struct zx_root_s* r;
364   int epr_len, n_discovered = 0;
365   char logop[8];
366   char uid[ZXID_MAX_USER];
367   char mdpath[ZXID_MAX_BUF];
368   char* epr_buf;
369   DIR* dir;
370   struct dirent* de;
371   struct zx_a_Metadata_s* md = 0;
372   struct zx_str* ss;
373   struct zx_str* svctyp;
374   struct zx_str* prvid;
375   struct zx_str* addr = 0;
376   zxid_epr* epr = 0;
377   strcpy(logop, "xxxDIyy");
378   D_INDENT("di_query: ");
379   ses->uid = uid;
380 
381   if (!zxid_idp_map_nid2uid(cf, sizeof(uid), uid, ses->tgtnameid, &resp->Status)) {
382     D_DEDENT("di_query: ");
383     return resp;
384   }
385   name_from_path(mdpath, sizeof(mdpath), "%sdimd", cf->cpath);
386 
387   /* Work through all requests */
388 
389   for (rs = req->RequestedService; rs; rs = (struct zx_di_RequestedService_s*)ZX_NEXT(rs)) {
390     if (rs->gg.g.tok != zx_di_RequestedService_ELEM)
391       continue;
392 
393     /* Look for all entities providing service */
394 
395     D("%d: Looking for service metadata in dir(%s)", n_discovered, mdpath);
396     dir = opendir(mdpath);
397     if (!dir) {
398       perror("opendir to find service metadata");
399       ERR("Opening service metadata directory failed path(%s)", mdpath);
400       resp->Status = zxid_mk_lu_Status(cf, &resp->gg, "Fail", 0, 0, 0);
401       D_DEDENT("di_query: ");
402       return resp;
403     }
404 
405     /* Work through all available providers, filtering out insuitable ones. */
406 
407     while (de = readdir(dir)) {
408       D("%d: Considering file(%s)", n_discovered, de->d_name);
409 
410       if (de->d_name[strlen(de->d_name)-1] == '~')  /* Ignore backups from hand edited EPRs. */
411 	continue;
412 
413       /* Filter file name by service type... */
414       if (!zxid_di_match_prefix(n_discovered, rs, de))
415 	continue;
416 
417       /* ...Probable enough, read and parse EPR so we can continue examination. */
418       epr_buf = read_all_alloc(cf->ctx, "find_svcmd", 1, &epr_len, "%s/%s", mdpath, de->d_name);
419       if (!epr_buf)
420 	continue;
421 
422       r = zx_dec_zx_root(cf->ctx, epr_len, epr_buf, "diq epr");
423       if (!r) {
424 	ERR("Failed to XML parse epr_buf(%.*s) file(%s)", epr_len, epr_buf, de->d_name);
425 	ZX_FREE(cf->ctx, epr_buf);
426 	continue;
427       }
428       /* *** add ID-WSF 1.1 handling */
429       epr = r->EndpointReference;
430       ZX_FREE(cf->ctx, r);
431       if (!epr || !epr->Metadata) {
432 	ERR("No EPR or missing <Metadata>. epr_buf(%.*s) file(%s)", epr_len, epr_buf, de->d_name);
433         goto next_file;
434       }
435       if (!ZX_SIMPLE_ELEM_CHK(epr->Address)) {
436 	ERR("EPR missing <Address>. epr_buf(%.*s) file(%s)", epr_len, epr_buf, de->d_name);
437         goto next_file;
438       }
439       addr = ZX_GET_CONTENT(epr->Address);
440       md = epr->Metadata;
441       svctyp = ZX_GET_CONTENT(md->ServiceType);
442       prvid = ZX_GET_CONTENT(md->ProviderID);
443 
444       if (!zxid_di_match_svctype(n_discovered, rs, svctyp, de))    /* Filter by service type */
445         goto next_file;
446 
447       if (!zxid_di_match_entid(n_discovered, rs, prvid, addr, de)) /* Filter by provider id */
448         goto next_file;
449 
450       /* Check Options, in particular whether Trust parameters are there. */
451       if (!zxid_di_match_options(cf, ses, n_discovered, rs, epr, de))
452         goto next_file;
453 
454       /* *** Check Framework */
455 
456       /* *** Check Action */
457 
458       /* TAS3 Trust Credentials and Privacy Negotiation (if configured) */
459       if (!zxid_di_match_cpn(cf, ses, n_discovered, svctyp, prvid, de))
460         goto next_file;
461 
462       ++n_discovered;
463       D("%d: DISCOVERED EPR epurl(%.*s)", n_discovered, addr->len, addr->s);
464       if (!zxid_add_fed_tok2epr(cf, ses, epr, 1, logop))
465 	goto next_file;
466       zxid_di_set_rankKey_if_needed(cf, md, n_discovered, de);
467 
468       zx_add_kid(&resp->gg, &epr->gg);
469       if (!resp->EndpointReference)
470 	resp->EndpointReference = epr;
471 
472       zxlogwsp(cf, ses, "K", logop, uid, 0);
473 
474       if (rs->resultsType && rs->resultsType->g.s
475 	  && (!memcmp(rs->resultsType->g.s, "only-one", rs->resultsType->g.len)
476 	      || !memcmp(rs->resultsType->g.s, "best", rs->resultsType->g.len))) {
477 	D("only-one or best requested (%.*s)", rs->resultsType->g.len, rs->resultsType->g.s);
478 	break;
479       }
480       /* All epr_bufs that are in the discovered set are leaked at this point because
481        * the XML structures used in preparing the response still reference the epr_buf. */
482       continue;
483 
484 next_file:
485       if (epr)
486 	zx_free_elem(cf->ctx, &epr->gg, 0);
487       ZX_FREE(cf->ctx, epr_buf);  /* free the reject EPR */
488       continue;
489     }
490 
491     closedir(dir);
492   }
493 
494   r->EndpointReference = zxid_di_sort_eprs(cf, (zxid_epr*)resp->gg.kids);
495   resp->gg.kids = &r->EndpointReference->gg;
496   zxid_di_sanitize_rankKey_out(r->EndpointReference);
497 
498   ss = ZX_GET_CONTENT(req->RequestedService->ServiceType);
499   D("TOTAL discovered %d svctype1(%.*s)", n_discovered, ss?ss->len:0, ss?ss->s:"");
500   zxlogwsp(cf, ses, "K", "DIOK", 0, "%.*s n=%d", ss?ss->len:1, ss?ss->s:"-", n_discovered);
501   resp->Status = zxid_mk_lu_Status(cf, &resp->gg, "OK", 0, 0, 0);  /* last is first */
502   D_DEDENT("di_query: ");
503   return resp;
504 }
505 
506 /* EOF  --  zxiddi.c */
507