Ejemplo n.º 1
0
/* Called by:  zxid_soap_call_hdr_body, zxid_wsc_call */
struct zx_root_s* zxid_soap_call_raw(zxid_conf* cf, struct zx_str* url, struct zx_e_Envelope_s* env, char** ret_enve)
{
#ifdef USE_CURL
  struct zx_root_s* r;
  struct zx_str* ret;
  struct zx_str* ss;
  char soap_action_buf[1024];
  char* soap_act;
  const char* env_start;

  ss = zx_easy_enc_elem_opt(cf, &env->gg);
  DD("ss(%.*s) len=%d", ss->len, ss->s, ss->len);

  if (cf->soap_action_hdr && strcmp(cf->soap_action_hdr,"#inhibit")) {
    if (!strcmp(cf->soap_action_hdr,"#same")) {
      if (env->Header && env->Header->Action && ZX_GET_CONTENT_S(env->Header->Action)) {
	snprintf(soap_action_buf,sizeof(soap_action_buf), "SOAPAction: \"%.*s\"", ZX_GET_CONTENT_LEN(env->Header->Action), ZX_GET_CONTENT_S(env->Header->Action));
	soap_action_buf[sizeof(soap_action_buf)-1] = 0;
	soap_act = soap_action_buf;
	D("SOAPaction(%s)", soap_action_buf);
      } else {
	ERR("e:Envelope/e:Headers/a:Action SOAP header is malformed %p", env->Header);
      }
    } else {
      snprintf(soap_action_buf,sizeof(soap_action_buf), "SOAPAction: \"%s\"", cf->soap_action_hdr);
      soap_action_buf[sizeof(soap_action_buf)-1] = 0;
      soap_act = soap_action_buf;
    }
  } else
    soap_act = 0;
  
  ret = zxid_http_cli(cf, url->len, url->s, ss->len, ss->s, cf->wsc_soap_content_type, soap_act, 0);
  zx_str_free(cf->ctx, ss);
  if (ret_enve)
    *ret_enve = ret?ret->s:0;
  if (!ret)
    return 0;
  
  env_start = zxid_locate_soap_Envelope(ret->s);
  if (!env_start) {
    ERR("SOAP response does not have Envelope element url(%.*s)", url->len, url->s);
    D_XML_BLOB(cf, "NO ENVELOPE SOAP RESPONSE", ret->len, ret->s);
    ZX_FREE(cf->ctx, ret);
    return 0;
  }

  cf->ctx->top1 = 1;  /* Stop parsing after first toplevel <e:Envelope> */
  r = zx_dec_zx_root(cf->ctx, ret->len - (env_start - ret->s), env_start, "soap_call");
  if (!r || !r->Envelope || !r->Envelope->Body) {
    ERR("Failed to parse SOAP response url(%.*s)", url->len, url->s);
    D_XML_BLOB(cf, "BAD SOAP RESPONSE", ret->len, ret->s);
    ZX_FREE(cf->ctx, ret);
    return 0;
  }
  return r;
#else
  ERR("This copy of zxid was compiled to NOT use libcurl. SOAP calls (such as Artifact profile and WSC) are not supported. Add -DUSE_CURL (make ENA_CURL=1) and recompile. %d", 0);
  return 0;
#endif
}
Ejemplo n.º 2
0
Archivo: zxidmk.c Proyecto: kiwiroy/zx
/* Called by:  zxid_call_trustpdp x6, zxid_pepmap_extract x3 */
struct zx_xac_Attribute_s* zxid_mk_xacml_simple_at(zxid_conf* cf, struct zx_elem_s* father, struct zx_str* atid, struct zx_str* attype, struct zx_str* atissuer, struct zx_str* atvalue)
{
    struct zx_root_s* r;
    struct zx_xac_Attribute_s* at = zx_NEW_xac_Attribute(cf->ctx, father);
    at->AttributeId = zx_ref_len_attr(cf->ctx, &at->gg, zx_AttributeId_ATTR, atid->len, atid->s);
    at->DataType = zx_ref_len_attr(cf->ctx, &at->gg, zx_DataType_ATTR, attype->len, attype->s);
    if (atissuer)
        at->Issuer = zx_ref_len_attr(cf->ctx, &at->gg, zx_Issuer_ATTR, atissuer->len, atissuer->s);
    if (atvalue->s[0] == '<') {
        /* Looks like the value may be XML data. We need to pass it as XML data structure for
         * canonicalization to work right (e.g. value is an A7N that is rendered one
         * way when canonicalized independently, but in different way when canonicalized
         * as part of a bigger structure - for example sa namespace may be omitted as it
         * is already supplied by the parent element). */
        r = zx_dec_zx_root(cf->ctx, atvalue->len, atvalue->s, "xac at parse");
        if (r && r->gg.kids) {
            at->AttributeValue = zx_new_elem(cf->ctx, &at->gg, zx_xac_AttributeValue_ELEM);
            at->AttributeValue->kids = r->gg.kids;
            ZX_FREE(cf->ctx, r);
        } else {
            /* XML did not parse, may be its just string data, after all. */
            at->AttributeValue = zx_new_str_elem(cf->ctx, &at->gg, zx_xac_AttributeValue_ELEM, atvalue);
        }
    } else {
        at->AttributeValue = zx_new_str_elem(cf->ctx, &at->gg, zx_xac_AttributeValue_ELEM, atvalue);
    }
    zx_reverse_elem_lists(&at->gg);
    return at;
}
Ejemplo n.º 3
0
Archivo: zxidmk.c Proyecto: kiwiroy/zx
/* Called by:  zxid_add_mapped_attr, zxid_map_val_ss, zxid_mk_sa_attribute */
struct zx_sa_Attribute_s* zxid_mk_sa_attribute_ss(zxid_conf* cf, struct zx_elem_s* father, const char* name, const char* namfmt, struct zx_str* val)
{
    struct zx_root_s* r;
    struct zx_sa_Attribute_s* at = zx_NEW_sa_Attribute(cf->ctx, father);
    if (namfmt)
        at->NameFormat = zx_ref_attr(cf->ctx, &at->gg, zx_NameFormat_ATTR, namfmt);
    at->Name = zx_dup_attr(cf->ctx, &at->gg, zx_Name_ATTR, name);
    at->AttributeValue = zx_NEW_sa_AttributeValue(cf->ctx, &at->gg);
    if (!val)
        return at;

    if (val->s[0] == '<') {
        /* Looks like the value may be XML data. We need to pass it as XML data structure for
         * canonicalization to work right (e.g. value is an A7N that is rendered one
         * way when canonicalized independently, but in different way when canonicalized
         * as part of a bigger structure - for example sa namespace may be omitted as it
         * is already supplied by the parent element). */
        r = zx_dec_zx_root(cf->ctx, val->len, val->s, "sa at parse");
        if (r && r->gg.kids) {
            at->AttributeValue->gg.kids = r->gg.kids;
            switch (r->gg.kids->g.tok) {
            case zx_sa_Assertion_ELEM:
                at->AttributeValue->Assertion = (void*)r->gg.kids;
                break;
            case zx_sa_EncryptedAssertion_ELEM:
                at->AttributeValue->EncryptedAssertion = (void*)r->gg.kids;
                break;
            case zx_di12_ResourceOffering_ELEM:
                at->AttributeValue->ResourceOffering = (void*)r->gg.kids;
                break;
            case zx_a_EndpointReference_ELEM:
                at->AttributeValue->EndpointReference = (void*)r->gg.kids;
                break;
            }
            ZX_FREE(cf->ctx, r);
        } else {
            /* XML did not parse, may be its just string data, after all. */
            zx_add_content(cf->ctx, &at->AttributeValue->gg, val);
        }
    } else {
        zx_add_content(cf->ctx, &at->AttributeValue->gg, val);
    }
    return at;
}
Ejemplo n.º 4
0
Archivo: zxcot.c Proyecto: gitpan/zxid
/* Called by:  zxcot_main */
static int zxid_reg_svc(zxid_conf* cf, int bs_reg, int dry_run, const char* ddimd, const char* duid)
{
  char sha1_name[28];
  char path[ZXID_MAX_BUF];
  int got;
  fdtype fd;
  struct zx_root_s* r;
  zxid_epr* epr;
  struct zx_str* ss;
  struct zx_str* tt;
  
  read_all_fd(fdstdin, buf, sizeof(buf)-1, &got);  /* Read EPR */
  buf[got] = 0;
  
  r = zx_dec_zx_root(cf->ctx, got, buf, "cot reg_svc");
  if (!r || !r->EndpointReference) {
    ERR("Failed to parse <EndpointReference> buf(%.*s)", got, buf);
    return 1;
  }
  epr = r->EndpointReference;
  if (!ZX_SIMPLE_ELEM_CHK(epr->Address)) {
    ERR("<EndpointReference> MUST have <Address> element buf(%.*s)", got, buf);
    return 1;
  }
  if (!epr->Metadata) {
    ERR("<EndpointReference> MUST have <Metadata> element buf(%.*s)", got, buf);
    return 1;
  }
  if (!ZX_SIMPLE_ELEM_CHK(epr->Metadata->ProviderID)) {
    ERR("<EndpointReference> MUST have <Metadata> with <ProviderID> element buf(%.*s)", got, buf);
    return 1;
  }
  if (!epr->Metadata->ServiceType) {
    ERR("<EndpointReference> MUST have <ServiceType> element buf(%.*s)", got, buf);
    return 1;
  }

  /* *** possibly add something here and double check the required fields are available. */

  ss = zx_easy_enc_elem_opt(cf, &epr->gg);
  if (!ss)
    return 2;
  
#if 0
  // *** wrong
  tt = ZX_GET_CONTENT(epr->Metadata->ProviderID);
#else
  tt = ZX_GET_CONTENT(epr->Metadata->ServiceType);
#endif
  got = MIN(tt->len, sizeof(path)-1);
  memcpy(path, tt?tt->s:"", got);
  path[got] = 0;
  zxid_fold_svc(path, got);

  sha1_safe_base64(sha1_name, ss->len, ss->s);
  sha1_name[27] = 0;

  if (verbose)
    fprintf(stderr, "Registering metadata in %s%s,%s\n", ddimd, path, sha1_name);
  
  if (dry_run) {
    if (verbose)
      fprintf(stderr, "Register EPR dry run. Would have written to path(%s%s,%s). "
	      "You may also want to\n"
	      "  touch %s.all/.bs/%s,%s\n\n", ddimd, path, sha1_name, uiddir, path, sha1_name);
    fflush(stdin);
    write_all_fd(fdstdout, ss->s, ss->len);
    zx_str_free(cf->ctx, ss);
    return 0;
  }
  
  D("Register EPR path(%s%s,%s) in discovery metadata.", ddimd, path, sha1_name);
  fd = open_fd_from_path(O_CREAT | O_WRONLY | O_TRUNC, 0666, "zxcot -b", 1,
			 "%s%s,%s", ddimd, path, sha1_name);
  if (fd == BADFD) {
    perror("open epr for registering");
    ERR("Failed to open file for writing: sha1_name(%s,%s) to service registration", path, sha1_name);
    zx_str_free(cf->ctx, ss);
    return 1;
  }
  
  write_all_fd(fd, ss->s, ss->len);
  zx_str_free(cf->ctx, ss);
  close_file(fd, (const char*)__FUNCTION__);

  if (bs_reg) {
    if (verbose)
      fprintf(stderr, "Activating bootstrap %s.all/.bs/%s,%s", duid, path, sha1_name);

    if (!dryrun) {
      fd = open_fd_from_path(O_CREAT | O_WRONLY | O_TRUNC, 0666, "zxcot -bs", 1,
			     "%s.all/.bs/%s,%s", duid, path, sha1_name);
      if (fd == BADFD) {
	perror("open epr for bootstrap activation");
	ERR("Failed to open file for writing: sha1_name(%s,%s) to bootstrap activation", path, sha1_name);
	return 1;
      }
    
      write_all_fd(fd, "", 0);
      close_file(fd, (const char*)__FUNCTION__);
    }
  } else {
    D("You may also want to activate bootstrap by\n  touch %s.all/.bs/%s,%s", duid, path, sha1_name);
  }
  return 0;
}
Ejemplo n.º 5
0
Archivo: zxidwsc.c Proyecto: kiwiroy/zx
/* Called by:  zxid_call_epr, zxid_wsc_prepare_call, zxid_wsc_valid_resp, zxid_wsp_decorate */
struct zx_e_Envelope_s* zxid_add_env_if_needed(zxid_conf* cf, const char* enve)
{
  struct zx_e_Envelope_s* env;
  struct zx_root_s* r;
  struct zx_str* ret;
  r = zx_dec_zx_root(cf->ctx, strlen(enve), enve, "add_env");
  if (!r) {
    ERR("Malformed XML enve(%s)", enve);
    return 0;
  }
  /* N.B. The lists are in reverse order after the parse. */
  env = r->Envelope;
  if (env) {
    /* N.B. Maintain the forward order, Header is 1st element of Envelope->kids. */
    if (!env->Header) {
      D("ENV EXISTS, no Header %p %p", env, env->Body);
      if (!env->Body)
	env->Body = zx_NEW_e_Body(cf->ctx, &env->gg);
      env->Header = zx_NEW_e_Header(cf->ctx, &env->gg);
    } else {
      D("ENV EXISTS w/Header %p %p", env, env->Body);
      if (!env->Body)
	env->Body = zx_NEW_e_Body(cf->ctx, &env->gg);
    }
  } else if (r->Body) {
    D("HERE2 BODY EXISTS %p %p", env, r->Header);
    env = zx_NEW_e_Envelope(cf->ctx,0);
    ZX_ADD_KID(env, Body, r->Body);
    if (r->Header)
      ZX_ADD_KID(env, Header, r->Header);
    else
      env->Header = zx_NEW_e_Header(cf->ctx, &env->gg);
    /* N.B. Maintain the Forward order: Header is now first element of Envelope->kids. */
  } else { /* Resort to stringwise attempt to add envelope. */
    ZX_FREE(cf->ctx, r);
    if (!memcmp(enve, "<?xml ", sizeof("<?xml ")-1)) {  /* Ignore common, but unnecessary decl. */
      for (enve += sizeof("<?xml "); *enve && !(enve[0] == '?' && enve[1] == '>'); ++enve) ;
      if (*enve)
	enve += 2;
    }
    /* Must be just payload */
    enve = zx_alloc_sprintf(cf->ctx, 0, "%s%s%s", zx_env_body_open, enve, zx_env_body_close);
    D("HERE3 ADD ENV(%s)", enve);
    r = zx_dec_zx_root(cf->ctx, strlen(enve), enve, "add_env2");
    if (!r) {
      ERR("Malformed XML enve(%s)", enve);
      return 0;
    }
    env = r->Envelope;
#if 0
    ret=zx_easy_enc_elem_opt(cf,&env->gg); INFO("ser(%.*s) enve(%s)",ret->len,ret->s,enve); // ***
    /* The lists are in reverse order after the parse. But since this is a text parse,
     * wireorder is maintained, thus giving forward order, afterall. */
    zx_reverse_elem_lists(&env->gg);
#endif
  }
  ZX_FREE(cf->ctx, r);
  if (env->gg.kids != &env->Header->gg) {
    D("ENV Fixing Header-Body ordering %p", env);
    env->gg.kids = &env->Header->gg;
    env->Header->gg.g.n = &env->Body->gg.g;
    env->Body->gg.g.n = 0;
  }
  ret = zx_easy_enc_elem_opt(cf,&env->gg); INFO("ser(%.*s) enve(%s)",ret->len,ret->s,enve); // ***
  if (!env)
    ERR("No <e:Envelope> found in input argument. enve(%s)", enve);
  /* DO NOT: zx_reverse_elem_lists(&env->gg);  * ensure forward order for external use */
  return env;
}
Ejemplo n.º 6
0
/* Called by:  zxid_idp_dispatch, zxid_simple_idp_show_an, zxid_sp_dispatch */
struct zx_root_s* zxid_decode_redir_or_post(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, int chk_dup)
{
  struct zx_sa_Issuer_s* issuer = 0;
  zxid_entity* meta;
  struct zx_str* ss;
  struct zx_str* logpath;
  struct zx_root_s* r = 0;
  struct zx_str id_ss;
  char id_buf[28];
  char sigbuf[512];  /* 192 should be large enough for 1024bit RSA keys */
  int simplesig = 0;
  int msglen, len;
  char* p;
  char* m2;
  char* p2;
  char* msg;
  char* b64msg;
  char* field;
  
  if (cgi->saml_resp && *cgi->saml_resp) {
    field = "SAMLResponse";
    b64msg = cgi->saml_resp;
  } else if (cgi->saml_req && *cgi->saml_req) {
    field = "SAMLRequest";
    b64msg = cgi->saml_req;
  } else {
    ERR("No SAMLRequest or SAMLResponse field?! %p", cgi);
    return 0;
  }
  
  msglen = strlen(b64msg);
  msg = ZX_ALLOC(cf->ctx, SIMPLE_BASE64_PESSIMISTIC_DECODE_LEN(msglen));
  p = unbase64_raw(b64msg, b64msg + msglen, msg, zx_std_index_64);
  *p = 0;
  DD("Msg(%s) x=%x", msg, *msg);

  /* Skip whitespace in the beginning and end of the payload to help correct POST detection. */
  for (m2 = msg; m2 < p; ++m2)
    if (!ONE_OF_4(*m2, ' ', '\t', '\015', '\012'))
      break;
  for (p2 = p-1; m2 < p2; --p2)
    if (!ONE_OF_4(*p2, ' ', '\t', '\015', '\012'))
      break;
  DD("Msg_sans_ws(%.*s) start=%x end=%x", p2-m2+1, m2, *m2, *p2);
  
  if (!(chk_dup & 0x02) && cf->log_level > 1)
    zxlog(cf, 0, 0, 0, 0, 0, 0, ZX_GET_CONTENT(ses->nameid), "N", "W", "REDIRDEC", 0, "sid(%s) len=%d", STRNULLCHK(ses->sid), msglen);

  if (*m2 == '<' && *p2 == '>') {  /* POST profiles do not compress the payload */
    len = p2 - m2 + 1;
    p = m2;
    simplesig = 1;
  } else {
    D("Detected compressed payload. [[m2(%c) %x p2(%c) %x]]", *m2, *m2, *p2, *p2);
    p = zx_zlib_raw_inflate(cf->ctx, p-msg, msg, &len);  /* Redir uses compressed payload. */
    ZX_FREE(cf->ctx, msg);
  }
  
  r = zx_dec_zx_root(cf->ctx, len, p, "decode redir or post");
  if (!r) {
    ERR("Failed to parse redir buf(%.*s)", len, p);
    zxlog(cf, 0, 0, 0, 0, 0, 0, ZX_GET_CONTENT(ses->nameid), "N", "C", "BADXML", 0, "sid(%s) bad redir", STRNULLCHK(ses->sid));
    return 0;
  }

  if (chk_dup & 0x02)
    return r;
  
  issuer = zxid_extract_issuer(cf, cgi, ses, r);
  if (!issuer)
    return 0;

  if (!cgi->sig || !*cgi->sig) {
    D("Redirect or POST was not signed at binding level %d", 0);
log_msg:
    if (cf->log_rely_msg) {
      DD("Logging... %d", 0);
      /* Path will be composed of sha1 hash of the data in p, i.e. the unbase64 data. */
      sha1_safe_base64(id_buf, len, p);
      id_buf[27] = 0;
      id_ss.len = 27;
      id_ss.s = id_buf;
      logpath = zxlog_path(cf, ZX_GET_CONTENT(issuer), &id_ss, ZXLOG_RELY_DIR, ZXLOG_WIR_KIND, 1);
      if (logpath) {
	if (chk_dup & 0x01) {
	  if (zxlog_dup_check(cf, logpath, "Redirect or POST assertion (unsigned)")) {
	    if (cf->dup_msg_fatal) {
	      cgi->err = "C Duplicate message";
	      r = 0;
	    }
	  }
	}
	id_ss.len = len;
	id_ss.s = p;
	zxlog_blob(cf, cf->log_rely_msg, logpath, &id_ss, "dec_redir_post nosig");
      }
    }
    return r;
  }

  meta = zxid_get_ent_ss(cf, ZX_GET_CONTENT(issuer));
  if (!meta) {
    ERR("Unable to find metadata for Issuer(%.*s) in Redir or SimpleSign POST binding", ZX_GET_CONTENT_LEN(issuer), ZX_GET_CONTENT_S(issuer));
    cgi->sigval = "I";
    cgi->sigmsg = "Issuer unknown - metadata exchange may be needed (SimpleSign, Redir, POST).";
    ses->sigres = ZXSIG_NO_SIG;
    goto log_msg;
  }

  /* ----- Signed at binding level ----- */
  
  if (simplesig) {
    /* In SimpleSign the signature is over data inside base64. */
    p2 = p = cgi->sigalg;
    URL_DECODE(p, p2, cgi->sigalg + strlen(cgi->sigalg));
    *p = 0;
#if 1
    /* Original SimpleSign specification was ambiguous about handling of missing
     * relay state. Literal reading of the spec seemed to say that empty relay state
     * should be part of the signature computation. This was reported by yours
     * truly to SSTC, which has since issued errata clarifying that if the relay
     * state is empty, then the RelayState label is omitted from signature
     * computation. This is also consistent with how the redirect binding works. */
    if (cgi->rs && *cgi->rs)
      ss = zx_strf(cf->ctx, "%s=%s&RelayState=%s&SigAlg=%s&Signature=%s",
		   field, msg, cgi->rs, STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
    else
      ss = zx_strf(cf->ctx, "%s=%s&SigAlg=%s&Signature=%s",
		   field, msg, STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
#else
    cgi->rs = "Fake";
    ss = zx_strf(cf->ctx, "%s=%s&RelayState=%s&SigAlg=%s&Signature=%s",
		 field, msg, STRNULLCHK(cgi->rs), STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
#endif
  } else {
    /* In Redir binding, the signature is over base64 and url encoded data. This complicates
     * life as we need to know what the URL looked like prior to CGI processing
     * such as URL decoding. As such processing is done by default to all
     * query string fields, this requires special processing. zxid_parse_cgi()
     * has special case code to prevent URL decoding of SAMLRequest and SAMLResponse
     * fields so the b64msg valiable actually has the URL encoding as well. The
     * unbase64_raw() function is smart enough to unravel the URL decoding on
     * the fly, so it all ends up working fine. */
    if (cgi->rs && *cgi->rs)
      ss = zx_strf(cf->ctx, "%s=%s&RelayState=%s&SigAlg=%s&Signature=%s",
		   field, b64msg, cgi->rs /* *** should be URL encoded or preserved? */,
		   STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
    else
      ss = zx_strf(cf->ctx, "%s=%s&SigAlg=%s&Signature=%s",
		   field, b64msg, STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
  }
  
  DD("Signed data(%.*s) len=%d sig(%s)", ss->len, ss->s, ss->len, cgi->sig);
  p2 = unbase64_raw(cgi->sig, cgi->sig + strlen(cgi->sig), sigbuf, zx_std_index_64);
  ASSERTOPI(p2-sigbuf, <, sizeof(sigbuf));
  
  /* strcmp(cgi->sigalg, SIG_ALGO_RSA_SHA1) would be the right test, but as
   * SigAlg can be arbitrarily URL encoded, we make the match fuzzier. */
  if (cgi->sigalg && strstr(cgi->sigalg, "rsa-sha1")) {
    ses->sigres = zxsig_verify_data(ss->len  /* Adjust for Signature= which we log */
				    - (sizeof("&Signature=")-1 + strlen(cgi->sig)),
				    ss->s, p2-sigbuf, sigbuf,
				    meta->sign_cert, "Simple or Redir SigVfy");
    zxid_sigres_map(ses->sigres, &cgi->sigval, &cgi->sigmsg);
  } else {
    ERR("Unsupported or bad signature algorithm(%s).", STRNULLCHK(cgi->sigalg));
    cgi->sigval = "I";
    cgi->sigmsg = "Unsupported or bad signature algorithm (in SimpleSign, Redir, or POST).";
    ses->sigres = ZXSIG_NO_SIG;
  }
  
  DD("Signed data(%.*s) len=%d", ss->len, ss->s, ss->len);
  if (cf->log_rely_msg) {
    DD("Logging... %d", 0);
    sha1_safe_base64(id_buf, ss->len, ss->s);
    id_buf[27] = 0;
    id_ss.len = 27;
    id_ss.s = id_buf;
    logpath = zxlog_path(cf, ZX_GET_CONTENT(issuer), &id_ss, ZXLOG_RELY_DIR, ZXLOG_WIR_KIND, 1);
    if (logpath) {
      if (zxlog_dup_check(cf, logpath, "Redirect or POST assertion")) {
	if (cf->dup_msg_fatal) {
	  cgi->err = "C Duplicate message";
	  r = 0;
	}
      }
      zxlog_blob(cf, cf->log_rely_msg, logpath, ss, "dec_redir_post sig");
    }
  }
  zx_str_free(cf->ctx, ss);
  return r;
}