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