"Fossies" - the Fresh Open Source Software Archive

Member "dacs-1.4.46/src/authlib.c" (23 Feb 2021, 162426 Bytes) of package /linux/www/dacs-1.4.46.txz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "authlib.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.4.45_vs_1.4.46.

    1 /*
    2  * Copyright (c) 2003-2021
    3  * Distributed Systems Software.  All rights reserved.
    4  * See the file LICENSE for redistribution information.
    5  */
    6 
    7 /*****************************************************************************
    8  * COPYRIGHT AND PERMISSION NOTICE
    9  * 
   10  * Copyright (c) 2001-2003 The Queen in Right of Canada
   11  * 
   12  * All rights reserved.
   13  * 
   14  * Permission is hereby granted, free of charge, to any person obtaining a copy
   15  * of this software and associated documentation files (the "Software"), to
   16  * deal in the Software without restriction, including without limitation 
   17  * the rights to use, copy, modify, merge, publish, distribute, and/or sell
   18  * copies of the Software, and to permit persons to whom the Software is 
   19  * furnished to do so, provided that the above copyright notice(s) and this
   20  * permission notice appear in all copies of the Software and that both the
   21  * above copyright notice(s) and this permission notice appear in supporting
   22  * documentation.
   23  * 
   24  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   25  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
   26  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
   27  * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE 
   28  * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
   29  * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
   30  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 
   31  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
   32  * SOFTWARE.
   33  * 
   34  * Except as contained in this notice, the name of a copyright holder shall not
   35  * be used in advertising or otherwise to promote the sale, use or other
   36  * dealings in this Software without prior written authorization of the
   37  * copyright holder.
   38  ***************************************************************************/
   39 
   40 /*
   41  * Support functions for DACS authentication.
   42  */
   43 
   44 #ifndef lint
   45 static const char copyright[] =
   46 "Copyright (c) 2003-2021\n\
   47 Distributed Systems Software.  All rights reserved.";
   48 static const char revid[] =
   49   "$Id: authlib.c 3171 2021-02-23 18:43:40Z brachman $";
   50 #endif
   51 
   52 #include "auth.h"
   53 #include "acs.h"
   54 #include "group.h"
   55 #include "frame.h"
   56 #include "crypto_aux.h"
   57 
   58 #ifdef NOTDEF
   59 #include <netinet/in.h>
   60 #include <openssl/pem.h>
   61 #include <openssl/bio.h>
   62 #endif
   63 
   64 static char *log_module_name = "authlib";
   65 
   66 static int cookie_output_syntax = COOKIE_NETSCAPE;
   67 
   68 static int debug_auth = 0;
   69 
   70 typedef struct Auth_style_map {
   71   Auth_style auth_style;
   72   char *string;
   73   int min_abbrev;
   74   int altnum;
   75 } Auth_style_map;
   76 
   77 static Auth_style_map auth_style_map[] = {
   78   { AUTH_STYLE_PASSWORD,  "passwd",              4, 0 },    /* First alt */
   79   { AUTH_STYLE_PASSWORD,  "password",            0, 1 },    /* Second alt */
   80   { AUTH_STYLE_CERT,      "certificate",         4, 0 },
   81   { AUTH_STYLE_PROMPTED,  "prompted",            6, 0 },
   82   { AUTH_STYLE_NATIVE,    "native",              3, 0 },
   83   { AUTH_STYLE_EXPR,      "expr",                0, 0 },
   84   { AUTH_STYLE_ADMIN,     "admin",               0, 0 },
   85   { AUTH_STYLE_IMPORTED,  "imported",            6, 0 },
   86   { AUTH_STYLE_ALIEN,     "alien",               0, 0 },
   87   { AUTH_STYLE_GENERATED, "generated",           3, 0 },
   88   { AUTH_STYLE_RLINK,     "rlink",               0, 0 },
   89   { AUTH_STYLE_DIGEST,    "digest",              0, 0 },
   90   { AUTH_STYLE_CAS,       "cas",                 0, 0 },
   91   { AUTH_STYLE_SIMPLE,    "simple",              0, 0 },
   92   { AUTH_STYLE_ACS,       "acs",                 0, 0 },
   93   { AUTH_STYLE_INFOCARD,  "infocard",            0, 0 },
   94   { AUTH_STYLE_MINFOCARD, "managed_infocard",    0, 0 },
   95   { AUTH_STYLE_SINFOCARD, "selfissued_infocard", 0, 0 },
   96   { AUTH_STYLE_TGMA,      "tgma",                0, 0 },
   97   { AUTH_STYLE_UNKNOWN,   NULL,                  0, 0 }
   98 };
   99 
  100 static Auth_style_map auth_style_modifier_map[] = {
  101   { AUTH_STYLE_SET_ROLES,  "set_roles",          0, 0 },
  102   { AUTH_STYLE_ADD_ROLES,  "add_roles",          0, 0 },
  103   { AUTH_STYLE_UNKNOWN,   NULL,                  0, 0 }
  104 };
  105 
  106 static int
  107 auth_single_cookie(void)
  108 {
  109 
  110   if (conf_val_eq(CONF_AUTH_SINGLE_COOKIE, "federation"))
  111     return(2);
  112 
  113   if (conf_val_eq(CONF_AUTH_SINGLE_COOKIE, "jurisdiction"))
  114     return(1);
  115 
  116   return(0);
  117 }
  118 
  119 /*
  120  * These are default cookie name component terminators.
  121  * The defaults were hardwired up to and including version 1.4.30.
  122  *
  123  * Unfortunately, the original cookie name syntax used colons as name
  124  * component separators, which is not compliant with RFC 2965.
  125  * The RFC disallows the space, tab, control characters 0 through 31, and:
  126  *     ( ) <  > @ , ; : \ "  /  [  ]  ? = {  }
  127  *
  128  * This leaves a few characters that could potentially replace the colon
  129  * in this context: ! # $ % & ' * + - . ^ _ ` | ~
  130  *
  131  * A suitable character must also be disallowed within federation names and
  132  * jurisdiction names (eliminating the hyphen and underscore), and within
  133  * usernames (eliminating: ! # $ % & ' . ^ `).
  134  * This leaves the four characters: * + | ~
  135  * The '|' is not allowed with the path component of a URI (which may not be
  136  * significant).
  137  * The '|' and '~' are the best candidates.
  138  * It is not yet known whether these alternatives to a colon will work with
  139  * most clients.
  140  * Note that a change would not affect the syntax of jurisdiction names,
  141  * user names, etc., only the HTTP cookie name format.
  142  */
  143 const Cookie_name_terminators default_cookie_name_terminators = {
  144   ":", "::", ":", ":"
  145 };
  146 
  147 Cookie_name_terminators current_cookie_name_terminators;
  148 
  149 /*
  150  * Create and return a new, initialized cookie name terminator set.
  151  * If TERM_STR is NULL, create a new set equivalent to the default;
  152  * if TERM_STR is a single item, populate a new set having the same format as
  153  * the default, but using TERM_STR for each terminator;
  154  * if TERM_STR consists of four comma-separated items, populate a new set
  155  * using the four string elements; otherwise, NULL is returned.
  156  * All terminator strings provided are assumed to be valid.
  157  * A comma cannot be escaped and may therefore not be used as, or within,
  158  * a terminator string.  Spaces are significant.
  159  * E.g., init_cookie_name_terminators(":,:,:,:");
  160  */
  161 Cookie_name_terminators *
  162 init_cookie_name_terminators(char *term_str)
  163 {
  164   char *p;
  165   Cookie_name_terminators *term;
  166 
  167   term = ALLOC(Cookie_name_terminators);
  168   if (term_str == NULL)
  169     *term = default_cookie_name_terminators;
  170   else if ((p = strchr(term_str, (int) ',')) == NULL) {
  171     term->app_end = strdup(term_str);
  172     /* XXX assumes this terminator must be doubled up. */
  173     term->federation_end = ds_xprintf("%s%s", term_str, term_str);
  174     term->jurisdiction_end = strdup(term_str);
  175     term->username_end = strdup(term_str);
  176   }
  177   else {
  178     Dsvec *dsv;
  179 
  180     if ((dsv =  strsplit(strdup(term_str), ",", 0)) == NULL
  181         || dsvec_len(dsv) != 4)
  182       return(NULL);
  183     term->app_end = dsvec_ptr_index(dsv, 0);
  184     term->federation_end = dsvec_ptr_index(dsv, 1);
  185     term->jurisdiction_end = dsvec_ptr_index(dsv, 2);
  186     term->username_end = dsvec_ptr_index(dsv, 3);
  187   }
  188 
  189   /* Disallow any null strings. */
  190   if (term->app_end[0] == '\0' || term->federation_end[0] == '\0'
  191       || term->jurisdiction_end[0] == '\0' || term->username_end[0] == '\0')
  192     return(NULL);
  193 
  194   return(term);
  195 }
  196 
  197 /*
  198  * Set the cookie name terminators in effect to TERM, or the default set
  199  * if TERM is NULL.
  200  * TERM is assumed to be a valid set of terminators.
  201  */
  202 int
  203 set_cookie_name_terminators(Cookie_name_terminators *term)
  204 {
  205 
  206   if (term == NULL)
  207     current_cookie_name_terminators = default_cookie_name_terminators;
  208   else
  209     current_cookie_name_terminators = *term;
  210 
  211   return(0);
  212 }
  213 
  214 /*
  215  * Generate an HTTP cookie name from APP, FEDERATION, JURISDICTION, USERNAME,
  216  * and SPECIAL, the first and last of which can be NULL to select a default.
  217  * Any of FEDERATION, JURISDICTION, and USERNAME may be the empty string.
  218  * If TERM is NULL, the default terminators are used.
  219  * It is assumed that the component and terminating strings are valid for
  220  * constructing an HTTP cookie name, but each component is checked to ensure
  221  * that it does not contain its terminator string (and can therefore be parsed
  222  * uniquely and correctly).
  223  *
  224  * Return the cookie name, or NULL if an error occurs.
  225  */
  226 char *
  227 make_cookie_name(Cookie_name_terminators *term, const char *app,
  228                  const char *federation, char *jurisdiction, char *username,
  229                  char *special)
  230 {
  231   char *cookie_name;
  232   const char *aname, *fname, *jname, *sname, *uname;
  233   const Cookie_name_terminators *t;
  234 
  235   if (term == NULL)
  236     t = &current_cookie_name_terminators;
  237   else
  238     t = term;
  239 
  240   aname = app;
  241   if (aname == NULL)
  242     aname = DACS_COOKIE_APP_NAME;
  243   if (strstr(aname, t->app_end) != NULL)
  244     return(NULL);
  245   
  246   fname = federation;
  247   if (fname == NULL)
  248     fname = "";
  249   if (strstr(fname, t->federation_end) != NULL)
  250     return(NULL);
  251 
  252   jname = jurisdiction;
  253   if (jname == NULL)
  254     jname = "";
  255   if (strstr(jname, t->jurisdiction_end) != NULL)
  256     return(NULL);
  257 
  258   uname = username;
  259   if (uname == NULL)
  260     uname = "";
  261 
  262   if (special == NULL)
  263     cookie_name = ds_xprintf("%s%s%s%s%s%s%s",
  264                              aname, t->app_end,
  265                              fname, t->federation_end,
  266                              jname, t->jurisdiction_end,
  267                              uname);
  268   else {
  269     if (strstr(uname, t->username_end) != NULL)
  270       return(NULL);
  271     cookie_name = ds_xprintf("%s%s%s%s%s%s%s%s%s",
  272                              aname, t->app_end,
  273                              fname, t->federation_end,
  274                              jname, t->jurisdiction_end,
  275                              uname, t->username_end,
  276                              special);
  277   }
  278 
  279   return(cookie_name);
  280 }
  281 
  282 /*
  283  * Return the cookie name prefix string used within the current federation.
  284  * This might be used when scanning through cookies submitted with an
  285  * HTTP request, for instance, to recognize those of interest to DACS.
  286  */
  287 char *
  288 make_cookie_name_federation_prefix(const Cookie_name_terminators *term)
  289 {
  290   char *cookie_name_prefix;
  291   const Cookie_name_terminators *t;
  292 
  293   if (term == NULL)
  294     t = &current_cookie_name_terminators;
  295   else
  296     t = term;
  297 
  298   if (conf_val(CONF_COMPAT_MODE) == NULL
  299       || conf_val_eq(CONF_COMPAT_MODE, "off"))
  300     cookie_name_prefix = ds_xprintf("%s%s%s%s",
  301                                     DACS_COOKIE_APP_NAME, t->app_end,
  302                                     conf_val(CONF_FEDERATION_NAME),
  303                                     t->federation_end);
  304   else {
  305     /* This is only for backward compatibility, supposedly temporarily. */
  306     cookie_name_prefix = ds_xprintf("DACS:%s:", conf_val(CONF_FEDERATION_NAME));
  307   }
  308 
  309   return(cookie_name_prefix);
  310 }
  311 
  312 /*
  313  * Return the cookie name prefix string used within the application.
  314  */
  315 char *
  316 make_cookie_name_app_prefix(const Cookie_name_terminators *term, char *app)
  317 {
  318   char *cookie_name_prefix;
  319   const Cookie_name_terminators *t;
  320 
  321   if (term == NULL)
  322     t = &current_cookie_name_terminators;
  323   else
  324     t = term;
  325 
  326   if (app != NULL && (conf_val(CONF_COMPAT_MODE) == NULL
  327                       || conf_val_eq(CONF_COMPAT_MODE, "off")))
  328     cookie_name_prefix = ds_xprintf("%s%s", app, t->app_end);
  329   else {
  330     /* Use the default prefix if none provided or for ancient compatibility. */
  331     cookie_name_prefix = ds_xprintf("DACS:");
  332   }
  333 
  334   return(cookie_name_prefix);
  335 }
  336 
  337 /*
  338  * Parse HTTP cookie name NAME into its components.
  339  * If TERM is NULL, the default terminators are used.
  340  * If a component is unspecified, it is set to the empty string, except for
  341  * the last component, which is set to NULL.
  342  * The individual name components are not checked for validity.
  343  *
  344  * Return the parsed components, or NULL if an error occurs.
  345  */
  346 Cookie_name *
  347 parse_cookie_name(Cookie_name_terminators *term, char *name)
  348 {
  349   char *aname, *e, *fname, *jname, *s, *sname, *uname;
  350   Cookie_name *cn;
  351   const Cookie_name_terminators *t;
  352 
  353   if (term == NULL)
  354     t = &current_cookie_name_terminators;
  355   else
  356     t = term;
  357 
  358   s = name;
  359   if ((e = strstr(s, t->app_end)) == NULL)
  360     return(NULL);
  361   aname = strndup(s, e - s);
  362   s = e + strlen(t->app_end);
  363 
  364   if ((e = strstr(s, t->federation_end)) == NULL)
  365     return(NULL);
  366   fname = strndup(s, e - s);
  367   s = e + strlen(t->federation_end);
  368 
  369   if ((e = strstr(s, t->jurisdiction_end)) == NULL)
  370     return(NULL);
  371   jname = strndup(s, e - s);
  372   s = e + strlen(t->jurisdiction_end);
  373 
  374   if ((e = strstr(s, t->username_end)) == NULL) {
  375     uname = strdup(s);
  376     sname = NULL;
  377   }
  378   else {
  379     uname = strndup(s, e - s);
  380     s = e + strlen(t->username_end);
  381     sname = strdup(s);
  382   }
  383 
  384   cn = ALLOC(Cookie_name);
  385   cn->app_name = aname;
  386   cn->federation = fname;
  387   cn->jurisdiction = jname;
  388   cn->username = uname;
  389   cn->special = sname;
  390 
  391   return(cn);
  392 }
  393 
  394 /*
  395  * XXX this is semi-problematic because a colon is not allowed in
  396  * a cookie name, unless quoted, if RFC 2109/2965 is honoured.
  397  * This seems to be an issue only with some releases of Tomcat.
  398  */
  399 char *
  400 make_auth_cookie_name(Credentials *credentials)
  401 {
  402   char *name;
  403 
  404   name = make_cookie_name(NULL, DACS_COOKIE_APP_NAME, credentials->federation,
  405                           (auth_single_cookie() == 2)
  406                           ? "" : credentials->home_jurisdiction,
  407                           auth_single_cookie() ? "" : credentials->username,
  408                           NULL);
  409   return(name);
  410 }
  411 
  412 time_t
  413 make_auth_expiry(time_t auth_time, time_t delta_secs)
  414 {
  415   time_t expires;
  416 
  417   expires = auth_time + delta_secs;
  418 
  419   return(expires);
  420 }
  421 
  422 /*
  423  * DELTA may be a negative value
  424  */
  425 time_t
  426 make_auth_expiry_delta(time_t auth_time, char *delta)
  427 {
  428   time_t lifetime;
  429 
  430   if (delta == NULL) {
  431     log_msg((LOG_ERROR_LEVEL, "Invalid delta value"));
  432     dacs_fatal("Bad configuration");
  433   }
  434 
  435   if (strnum(delta, STRNUM_TIME_T, &lifetime) == -1) {
  436     log_msg((LOG_ERROR_LEVEL, "Invalid lifetime for credentials: %s", delta));
  437     dacs_fatal("Bad configuration");
  438     /*NOTREACHED*/
  439   }
  440 
  441   return(make_auth_expiry(auth_time, lifetime));
  442 }
  443 
  444 Credentials *
  445 init_credentials(void)
  446 {
  447   Credentials *c;
  448 
  449   c = ALLOC(Credentials);
  450   c->roles = NULL;
  451   c->federation = NULL;
  452   c->username = NULL;
  453   c->home_jurisdiction = NULL;
  454   c->ip_address = NULL;
  455   c->role_str = NULL;
  456   c->expires_secs = 0;
  457   c->auth_style = AUTH_STYLE_UNKNOWN;
  458   c->unique = NULL;
  459   c->version = NULL;
  460   c->valid_for = NULL;
  461   c->imported_by = NULL;
  462   c->ua_hash = NULL;
  463   c->cookie_name = NULL;
  464   c->selected = 0;
  465   c->next = NULL;
  466 
  467   return(c);
  468 }
  469 
  470 char *
  471 auth_style_to_string(Auth_style auth_style)
  472 {
  473   int found, i;
  474   Ds ds;
  475 
  476   ds_init(&ds);
  477   found = 0;
  478 
  479   for (i = 0; auth_style_map[i].string != NULL; i++) {
  480     if ((auth_style & auth_style_map[i].auth_style) != 0) {
  481       ds_asprintf(&ds, "%s%s", found ? "," : "", auth_style_map[i].string);
  482       found++;
  483 
  484       /* Skip any synonyms for this name. */
  485       while (auth_style_map[i + 1].string != NULL
  486              && auth_style_map[i + 1].altnum)
  487         i++;
  488     }
  489   }
  490 
  491   if (!found)
  492     return(NULL);
  493 
  494   for (i = 0; auth_style_modifier_map[i].string != NULL; i++) {
  495     if ((auth_style & auth_style_modifier_map[i].auth_style) != 0)
  496       ds_asprintf(&ds, ",%s", auth_style_modifier_map[i].string);
  497   }
  498 
  499   return(ds_buf(&ds));
  500 }
  501 
  502 Auth_style
  503 auth_style_from_string(const char *str)
  504 {
  505   int found, found_style, i;
  506   Auth_style auth_style;
  507   Auth_style_map *sm;
  508   Dsvec *dsv;
  509 
  510   if ((dsv = strsplit(str, ",", 0)) == NULL)
  511     return(AUTH_STYLE_UNKNOWN);
  512 
  513   auth_style = 0;
  514   found_style = 0;
  515 
  516   for (i = 0; i < dsvec_len(dsv); i++) {
  517     char *as;
  518 
  519     as = (char *) dsvec_ptr_index(dsv, i);
  520     found = 0;
  521     for (sm = &auth_style_map[0]; sm->string != NULL; sm++) {
  522       if ((sm->min_abbrev == 0 && strcaseeq(sm->string, as))
  523           || strncaseprefix(as, sm->string, sm->min_abbrev)) {
  524         auth_style |= sm->auth_style;
  525         found_style++;
  526         found++;
  527         break;
  528       }
  529     }
  530 
  531     if (!found) {
  532       for (sm = &auth_style_modifier_map[0]; sm->string != NULL; sm++) {
  533         if ((sm->min_abbrev == 0 && strcaseeq(sm->string, as))
  534             || strncaseprefix(as, sm->string, sm->min_abbrev)) {
  535           auth_style |= sm->auth_style;
  536           found++;
  537           break;
  538         }
  539       }
  540 
  541       if (!found) {
  542         log_msg((LOG_ERROR_LEVEL,
  543                  "Invalid style keyword or modifier: \"%s\"", as));
  544         return(AUTH_STYLE_UNKNOWN);
  545       }
  546     }
  547   }
  548 
  549   if (!found_style) {
  550     log_msg((LOG_ERROR_LEVEL, "No style keyword found"));
  551     return(AUTH_STYLE_UNKNOWN);
  552   }
  553 
  554   if ((auth_style & AUTH_STYLE_EXPR)) {
  555     if ((auth_style & AUTH_STYLE_PASSWORD)
  556         || (auth_style & AUTH_STYLE_SIMPLE)
  557         || (auth_style & AUTH_STYLE_INFOCARD)
  558         || (auth_style & AUTH_STYLE_MINFOCARD)
  559         || (auth_style & AUTH_STYLE_SINFOCARD)
  560         || (auth_style & AUTH_STYLE_CAS)
  561         || (auth_style & AUTH_STYLE_CERT)
  562         || (auth_style & AUTH_STYLE_NATIVE)
  563         || (auth_style & AUTH_STYLE_TGMA)
  564         || (auth_style & AUTH_STYLE_PROMPTED)) {
  565       log_msg((LOG_ERROR_LEVEL, "STYLE expr cannot be combined"));
  566       return(AUTH_STYLE_UNKNOWN);
  567     }
  568   }
  569 
  570   return(auth_style);
  571 }
  572 
  573 static Parse_attr_tab credentials_attr_tab[] = {
  574   { "federation",   NULL, ATTR_REQUIRED, NULL, 0 },
  575   { "username",     NULL, ATTR_REQUIRED, NULL, 0 },
  576   { "jurisdiction", NULL, ATTR_REQUIRED, NULL, 0 },
  577   { "ip",           NULL, ATTR_REQUIRED, NULL, 0 },
  578   { "roles",        NULL, ATTR_REQUIRED, NULL, 0 },
  579   { "auth_time",    NULL, ATTR_REQUIRED, NULL, 0 },
  580   { "expires",      NULL, ATTR_REQUIRED, NULL, 0 },
  581   { "auth_style",   NULL, ATTR_REQUIRED, NULL, 0 },
  582   { "unique",       NULL, ATTR_REQUIRED, NULL, 0 },
  583   { "version",      NULL, ATTR_REQUIRED, NULL, 0 },
  584   { "valid_for",    NULL, ATTR_REQUIRED, NULL, 0 },
  585   { "imported_by",  NULL, ATTR_IMPLIED,  NULL, 0 },
  586   { "ua_hash",      NULL, ATTR_IMPLIED,  NULL, 0 },
  587   { NULL,           NULL, ATTR_END,      NULL, 0 }
  588 };
  589 
  590 static void
  591 parse_xml_credentials_element_start(void *data, const char *element,
  592                                     const char **attr)
  593 {
  594   char *el, *errmsg, *p;
  595   Credentials **credentials;
  596 
  597   if (parse_xmlns_name(element, NULL, &el) == -1) {
  598     parse_xml_set_error(ds_xprintf("Invalid namespace: %s", element));
  599     return;
  600   }
  601 
  602   /* XXX There should only be one element */
  603   credentials = (Credentials **) data;
  604 
  605   if (streq(el, "credentials")) {
  606     char *auth_time, *auth_style, *expires, *valid_for;
  607     Credentials *c;
  608 
  609     if (parse_xml_is_not_empty()) {
  610       parse_xml_set_error("Stack not empty at start of parse");
  611       return;
  612     }
  613 
  614     c = init_credentials();
  615 
  616     /* Just put something on the parse stack so it's not empty. */
  617     parse_xml_push(NULL);
  618 
  619     credentials_attr_tab[0].value  = &c->federation;
  620     credentials_attr_tab[1].value  = &c->username;
  621     credentials_attr_tab[2].value  = &c->home_jurisdiction;
  622     credentials_attr_tab[3].value  = &c->ip_address;
  623     credentials_attr_tab[4].value  = &c->role_str;
  624     credentials_attr_tab[5].value  = &auth_time;
  625     credentials_attr_tab[6].value  = &expires;
  626     credentials_attr_tab[7].value  = &auth_style;
  627     credentials_attr_tab[8].value  = &c->unique;
  628     credentials_attr_tab[9].value  = &c->version;
  629     credentials_attr_tab[10].value = &valid_for;
  630     credentials_attr_tab[11].value = &c->imported_by;
  631     credentials_attr_tab[12].value = &c->ua_hash;
  632     if (parse_xml_attr(credentials_attr_tab, attr, &errmsg) == -1) {
  633       parse_xml_set_error(errmsg);
  634       return;
  635     }
  636 
  637     if (!isdigit((int) auth_time[0]))
  638       return;
  639     if (strnum(auth_time, STRNUM_TIME_T, &c->auth_time) == -1)
  640       return;
  641 
  642     if (!isdigit((int) expires[0]))
  643       return;
  644     if (strnum(expires, STRNUM_TIME_T, &c->expires_secs) == -1)
  645       return;
  646 
  647     if ((c->auth_style = auth_style_from_string(auth_style))
  648         == AUTH_STYLE_UNKNOWN)
  649       return;
  650 
  651     if ((p = is_valid_valid_for(valid_for)) == NULL)
  652       return;
  653     c->valid_for = strdup(p);
  654 
  655     *credentials = c;
  656     parse_xml_pop((void **) NULL);
  657   }
  658   else {
  659     parse_xml_set_error(ds_xprintf("Unknown element: %s", el));
  660     return;
  661   }
  662 }
  663 
  664 static void
  665 parse_xml_credentials_element_end(void *data, const char *el)
  666 {
  667 
  668 }
  669 
  670 int
  671 parse_xml_credentials(char *xml_credentials, Credentials **credentials)
  672 {
  673   int st;
  674   Parse_xml_error err;
  675   XML_Parser p = XML_ParserCreateNS(NULL, XMLNS_SEP_CHAR);
  676 
  677   if (p == NULL)
  678     return(-1);
  679 
  680   parse_xml_init("Credentials", p);
  681 
  682   XML_SetElementHandler(p, parse_xml_credentials_element_start,
  683                         parse_xml_credentials_element_end);
  684   XML_SetUserData(p, (void *) credentials);
  685 
  686   st = XML_Parse(p, xml_credentials, strlen(xml_credentials), 1);
  687 
  688   if (parse_xml_is_error(&err) || st == 0) {
  689     if (err.mesg == NULL) {
  690       parse_xml_set_error(NULL);
  691       parse_xml_is_error(&err);
  692     }
  693     log_msg((LOG_ERROR_LEVEL, "parse_xml_credentials: line %d, pos %d",
  694              err.line, err.pos));
  695     if (err.mesg != NULL)
  696       log_msg((LOG_ERROR_LEVEL, "parse_xml_credentials: %s", err.mesg));
  697     parse_xml_end();
  698     return(-1);
  699   }
  700 
  701   parse_xml_end();
  702 
  703   if (parse_xml_is_not_empty())
  704     return(-1);
  705 
  706   return(0);
  707 }
  708 
  709 typedef enum Parse_xml_scredentials_state_code {
  710   SCREDENTIALS_PARSE_SCREDENTIALS = 0,
  711   SCREDENTIALS_PARSE_UNAUTH       = 1,
  712   SCREDENTIALS_PARSE_SELECTED     = 2
  713 } Parse_xml_scredentials_state_code;
  714 
  715 typedef struct Parse_xml_scredentials_state {
  716   Parse_xml_scredentials_state_code code;
  717   union {
  718     Scredentials *scredentials;
  719     Scredentials_unauth *unauth;
  720     Scredentials_selected *selected;
  721   } object;
  722 } Parse_xml_scredentials_state;
  723 
  724 static Parse_xml_scredentials_state *
  725 parse_xml_make_scredentials_state(Parse_xml_scredentials_state_code code,
  726                                           void *object)
  727 {
  728   Parse_xml_scredentials_state *s;
  729 
  730   s = ALLOC(Parse_xml_scredentials_state);
  731   s->code = code;
  732 
  733   switch (code) {
  734   case SCREDENTIALS_PARSE_SCREDENTIALS:
  735     s->object.scredentials = (Scredentials *) object;
  736     break;
  737 
  738   case SCREDENTIALS_PARSE_UNAUTH:
  739     s->object.unauth = (Scredentials_unauth *) object;
  740     break;
  741 
  742   case SCREDENTIALS_PARSE_SELECTED:
  743     s->object.selected = (Scredentials_selected *) object;
  744     break;
  745 
  746   default:
  747     /* XXX ??? */
  748     return(NULL);
  749   }
  750 
  751   return(s);
  752 }
  753 
  754 static Parse_attr_tab scredentials_selected_attr_tab[] = {
  755   { "federation",   NULL, ATTR_REQUIRED, NULL, 0 },
  756   { "jurisdiction", NULL, ATTR_REQUIRED, NULL, 0 },
  757   { "username",     NULL, ATTR_REQUIRED, NULL, 0 },
  758   { "unique",       NULL, ATTR_REQUIRED, NULL, 0 },
  759   { "version",      NULL, ATTR_REQUIRED, NULL, 0 },
  760   { NULL,           NULL, ATTR_END,      NULL, 0 }
  761 };
  762 
  763 static Parse_attr_tab scredentials_unauth_attr_tab[] = {
  764   { "federation",   NULL, ATTR_REQUIRED, NULL, 0 },
  765   { NULL,           NULL, ATTR_END,      NULL, 0 }
  766 };
  767 
  768 static void
  769 parse_xml_scredentials_element_start(void *data, const char *element,
  770                                      const char **attr)
  771 {
  772   char *el, *errmsg;
  773   Scredentials **scp;
  774   Parse_xml_scredentials_state *state;
  775 
  776   if (parse_xmlns_name(element, NULL, &el) == -1) {
  777     parse_xml_set_error(ds_xprintf("Invalid namespace: %s", element));
  778     return;
  779   }
  780 
  781   /* XXX There should only be one element */
  782   scp = (Scredentials **) data;
  783 
  784   if (streq(el, "selected_credentials")) {
  785     Scredentials *sc;
  786 
  787     if (parse_xml_is_not_empty()) {
  788       parse_xml_set_error("Unexpected \"selected_credentials\" element");
  789       return;
  790     }
  791 
  792     if (parse_xml_attr(NULL, attr, &errmsg) == -1) {
  793       parse_xml_set_error(errmsg);
  794       return;
  795     }
  796 
  797     sc = ALLOC(Scredentials);
  798     sc->selected = NULL;
  799     sc->unauth = NULL;
  800     *scp = sc;
  801 
  802     parse_xml_push(parse_xml_make_scredentials_state(SCREDENTIALS_PARSE_SCREDENTIALS, (void *) sc));
  803   }
  804   else if (streq(el, "selected")) {
  805     Scredentials *sc;
  806     Scredentials_selected *s, **sp;
  807 
  808     if (parse_xml_top((void **) &state) != PARSE_XML_OK
  809         || state->code != SCREDENTIALS_PARSE_SCREDENTIALS) {
  810       parse_xml_set_error("Unexpected \"selected\" element");
  811       return;
  812     }
  813     sc = state->object.scredentials;
  814 
  815     if (sc->unauth != NULL) {
  816       parse_xml_set_error("Unexpected \"selected\" element -- unauth present");
  817       return;
  818     }
  819 
  820     s = ALLOC(Scredentials_selected);
  821     s->federation = NULL;
  822     s->jurisdiction = NULL;
  823     s->username = NULL;
  824     s->unique = NULL;
  825     s->version = NULL;
  826     s->next = NULL;
  827 
  828     scredentials_selected_attr_tab[0].value = &s->federation;
  829     scredentials_selected_attr_tab[1].value = &s->jurisdiction;
  830     scredentials_selected_attr_tab[2].value = &s->username;
  831     scredentials_selected_attr_tab[3].value = &s->unique;
  832     scredentials_selected_attr_tab[4].value = &s->version;
  833     if (parse_xml_attr(scredentials_selected_attr_tab, attr, &errmsg) == -1) {
  834       parse_xml_set_error(errmsg);
  835       return;
  836     }
  837 
  838     for (sp = &sc->selected; *sp != NULL; sp = &(*sp)->next)
  839       ;
  840     *sp = s;
  841   }
  842   else if (streq(el, "unauth")) {
  843     Scredentials *sc;
  844     Scredentials_unauth *scu;
  845 
  846     if (parse_xml_top((void **) &state) != PARSE_XML_OK
  847         || state->code != SCREDENTIALS_PARSE_SCREDENTIALS) {
  848       parse_xml_set_error("Unexpected \"unauth\" element");
  849       return;
  850     }
  851     sc = state->object.scredentials;
  852 
  853     if (sc->unauth != NULL) {
  854       parse_xml_set_error("Duplicate \"unauth\" element");
  855       return;
  856     }
  857     if (sc->selected != NULL) {
  858       parse_xml_set_error("Unexpected \"unauth\" element - selected present");
  859       return;
  860     }
  861 
  862     scu = ALLOC(Scredentials_unauth);
  863     scu->federation = NULL;
  864 
  865     scredentials_unauth_attr_tab[0].value = &scu->federation;
  866     if (parse_xml_attr(scredentials_unauth_attr_tab, attr, &errmsg)
  867         == -1) {
  868       parse_xml_set_error(errmsg);
  869       return;
  870     }
  871     sc->unauth = scu;
  872   }
  873   else {
  874     parse_xml_set_error(ds_xprintf("Unknown element: %s", el));
  875     return;
  876   }
  877 }
  878 
  879 static void
  880 parse_xml_scredentials_element_end(void *data, const char *element)
  881 {
  882   char *el, *err;
  883   Scredentials *sc;
  884   Parse_xml_scredentials_state *state;
  885 
  886   if (parse_xmlns_name(element, NULL, &el) == -1) {
  887     parse_xml_set_error(ds_xprintf("Invalid namespace: %s", element));
  888     return;
  889   }
  890 
  891   sc = *(Scredentials **) data;
  892 
  893   if (parse_xml_is_error(NULL))
  894     return;
  895 
  896   err = "";
  897   if (streq(el, "selected_credentials")) {
  898     if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR)
  899       goto err;
  900     if (state->code != SCREDENTIALS_PARSE_SCREDENTIALS)
  901       goto err;
  902     sc = state->object.scredentials;
  903     if (sc->unauth == NULL && sc->selected == NULL)
  904       goto err;
  905     if (sc->unauth != NULL && sc->selected != NULL)
  906       goto err;
  907   }
  908   else if (streq(el, "unauth")) {
  909     /* Do nothing */
  910   }
  911   else if (streq(el, "selected")) {
  912     /* Do nothing */
  913   }
  914   else {
  915     err = ds_xprintf("Unknown element: %s", el);
  916     goto err;
  917   }
  918 
  919   return;
  920 
  921  err:
  922   parse_xml_set_error(err);
  923 }
  924 
  925 int
  926 parse_xml_scredentials(char *xml_scredentials, Scredentials **scredentials)
  927 {
  928   int st;
  929   Parse_xml_error err;
  930   XML_Parser p = XML_ParserCreateNS(NULL, XMLNS_SEP_CHAR);
  931 
  932   if (p == NULL)
  933     return(-1);
  934 
  935   parse_xml_init("Scredentials", p);
  936 
  937   XML_SetElementHandler(p, parse_xml_scredentials_element_start,
  938                         parse_xml_scredentials_element_end);
  939   XML_SetUserData(p, (void *) scredentials);
  940 
  941   st = XML_Parse(p, xml_scredentials, strlen(xml_scredentials), 1);
  942 
  943   if (parse_xml_is_error(&err) || st == 0) {
  944     if (err.mesg == NULL) {
  945       parse_xml_set_error(NULL);
  946       parse_xml_is_error(&err);
  947     }
  948     log_msg((LOG_ERROR_LEVEL, "parse_xml_scredentials: line %d, pos %d",
  949              err.line, err.pos));
  950     if (err.mesg != NULL)
  951       log_msg((LOG_ERROR_LEVEL, "parse_xml_scredentials: %s", err.mesg));
  952     parse_xml_end();
  953     return(-1);
  954   }
  955 
  956   parse_xml_end();
  957 
  958   if (parse_xml_is_not_empty())
  959     return(-1);
  960 
  961   return(0);
  962 }
  963 
  964 int
  965 set_cookie_output_syntax(Cookie_syntax syntax)
  966 {
  967 
  968   if (syntax == COOKIE_NETSCAPE || syntax == COOKIE_EXT_NETSCAPE) {
  969     cookie_output_syntax = syntax;
  970     return(0);
  971   }
  972 
  973   log_msg((LOG_ERROR_LEVEL,
  974            "Attempt to select unsupported cookie output syntax"));
  975   return(-1);
  976 }
  977 
  978 int
  979 configure_cookie_syntax(char *cookie_syntax, char **errmsg)
  980 {
  981   int st;
  982 
  983   if (strcaseeq(cookie_syntax, "COOKIE_NETSCAPE"))
  984     st = set_cookie_output_syntax(COOKIE_NETSCAPE);
  985   else if (strcaseeq(cookie_syntax, "COOKIE_EXT_NETSCAPE"))
  986     st = set_cookie_output_syntax(COOKIE_EXT_NETSCAPE);
  987   else if (strcaseeq(cookie_syntax, "COOKIE_RFC2109"))
  988     st = set_cookie_output_syntax(COOKIE_RFC2109);
  989   else if (strcaseeq(cookie_syntax, "COOKIE_RFC2965"))
  990     st = set_cookie_output_syntax(COOKIE_RFC2965);
  991   else if (strcaseeq(cookie_syntax, "COOKIE_RFC6265"))
  992     st = set_cookie_output_syntax(COOKIE_RFC6265);
  993   else {
  994     *errmsg = "Unrecognized COOKIE_SYNTAX";
  995     return(-1);
  996   }
  997 
  998   if (st == -1) {
  999     *errmsg = "Unsupported COOKIE_SYNTAX";
 1000     return(-1);
 1001   }
 1002 
 1003   return(0);
 1004 }
 1005 
 1006 /*
 1007  * DACS authentication-related cookie names look like:
 1008  *   DACS:<federation-name>::<jurisdiction-name>:<username>
 1009  * And, if SELECTED_CREDENTIALS are enabled:
 1010  *   DACS:<federation-name>::::SELECTED
 1011  * For the latter format, the username is NULL.
 1012  * Return a new Cookie_name structure or NULL on error.
 1013  */
 1014 static Cookie_name *
 1015 parse_dacs_auth_cookie_name(char *name)
 1016 {
 1017   Cookie_name *cn;
 1018 
 1019   if ((cn = parse_cookie_name(NULL, name)) == NULL)
 1020     return(NULL);
 1021 
 1022   if (cn->special != NULL) {
 1023     if (streq(cn->special, "SELECTED"))
 1024       ;
 1025     else if (strneq(cn->special, "TOKEN-", 6))
 1026       ;
 1027     else {
 1028       log_msg((LOG_INFO_LEVEL,
 1029                "Unrecognized auth cookie name, ignored: %s", name));
 1030       return(NULL);
 1031     }
 1032   }
 1033 
 1034   return(cn);
 1035 }
 1036 
 1037 static Cookie *
 1038 init_cookie(void)
 1039 {
 1040   Cookie *cookie;
 1041 
 1042   cookie = ALLOC(Cookie);
 1043   cookie->syntax = COOKIE_NETSCAPE;
 1044   cookie->str = NULL;
 1045   cookie->name = cookie->value = cookie->domain = cookie->path = NULL;
 1046   cookie->parsed_name = NULL;
 1047   cookie->expires = NULL;
 1048   cookie->max_age = NULL;
 1049   cookie->comment = cookie->version = NULL;
 1050   cookie->discard = cookie->port = cookie->comment_url = NULL;
 1051   cookie->secure = 0;
 1052   cookie->httponly = 0;
 1053   cookie->set_void = 0;
 1054   cookie->next = NULL;
 1055 
 1056   return(cookie);
 1057 }
 1058 
 1059 /*
 1060  * Given COOKIE_STR, break up its fields and store them in a Cookie structure.
 1061  * The caller must check the fields for validity.
 1062  * See: developer.netscape.com/docs/manuals/js/client/jsref/cookies.htm
 1063  * For the de facto standard Netscape syntax, We have:
 1064  * <NAME=VALUE>[; expires=DATE][; domain=DOMAIN_NAME][; path=PATH][; secure]
 1065  * The VALUE is allowed to be empty.
 1066  * The optional fields are accepted in any order.
 1067  * The spec is unclear about how to handle an '=' within the name or value;
 1068  * we'll take the first one as the separator and require the cookie creator
 1069  * to URL encode any other '=' characters.
 1070  *
 1071  * This is not an implementation of RFC 2109 or RFC 2965
 1072  * (www.rfc-editor.org/rfc/rfc2965.txt)
 1073  *
 1074  * Return NULL if there is an error, otherwise the parsed cookie.
 1075  */
 1076 Cookie *
 1077 cookie_parse(char *cookie_str, int *is_dacs_cookie)
 1078 {
 1079   char *p, *s, *stop_chars, *str;
 1080   Cookie *cookie;
 1081 
 1082   /* Make a copy, which we will carve up below. */
 1083   s = str = strdup(cookie_str);
 1084   if ((p = strchr(s, '=')) == NULL) {
 1085     cookie = NULL;
 1086     goto err;
 1087   }
 1088   *p++ = '\0';
 1089 
 1090   cookie = init_cookie();
 1091 
 1092   if ((cookie->name = url_decode(s, NULL, NULL)) == NULL)
 1093     goto err;
 1094 
 1095   s = p;
 1096 
 1097   if ((cookie->parsed_name = parse_dacs_auth_cookie_name(cookie->name))
 1098       == NULL)
 1099     *is_dacs_cookie = 0;
 1100   else
 1101     *is_dacs_cookie = 1;
 1102 
 1103   if ((p = strpbrk(s, "; ")) == NULL) {
 1104     /* There are no optional fields. */
 1105     if ((cookie->value = url_decode(s, NULL, NULL)) == NULL)
 1106       return(NULL);
 1107 
 1108     return(cookie);
 1109   }
 1110   *p++ = '\0';
 1111 
 1112   if ((cookie->value = url_decode(s, NULL, NULL)) == NULL)
 1113     return(NULL);
 1114 
 1115   for (s = p; *s == ' ' || *s == ';'; s++)
 1116     ;
 1117 
 1118   while (*s != '\0') {
 1119     if (strncaseeq(s, "domain=", 7)) {
 1120       s += 7;
 1121       cookie->domain = s;
 1122       stop_chars = "; ";
 1123     }
 1124     else if (strncaseeq(s, "path=", 5)) {
 1125       s += 5;
 1126       cookie->path = s;
 1127       stop_chars = "; ";
 1128     }
 1129     else if (strncaseeq(s, "secure", 6)) {
 1130       s += 6;
 1131       if (*s != ' ' && *s != ';' && *s != '\0')
 1132         goto err;
 1133       cookie->secure = 1;
 1134       stop_chars = "; ";
 1135     }
 1136     else if (strncaseeq(s, "httponly", 8)) {
 1137       s += 8;
 1138       if (*s != ' ' && *s != ';' && *s != '\0')
 1139         goto err;
 1140       cookie->httponly = 1;
 1141       stop_chars = "; ";
 1142     }
 1143     else if (strncaseeq(s, "expires=", 8)) {
 1144       s += 8;
 1145       cookie->expires = s;
 1146       stop_chars = ";";
 1147     }
 1148     else if (strncaseeq(s, "max-age=", 8)) {
 1149       s += 8;
 1150       cookie->max_age = s;
 1151       cookie->syntax = COOKIE_EXT_NETSCAPE;
 1152       stop_chars = ";";
 1153     }
 1154     else {
 1155       /*
 1156        * Ignore this field.
 1157        * In particular, this ought to skip RFC 2109/2965 style fields, all of
 1158        * which begin with a '$'.
 1159        */
 1160       stop_chars = "; ";
 1161     }
 1162 
 1163     if ((p = strpbrk(s, stop_chars)) == NULL)
 1164       break;
 1165     *p++ = '\0';
 1166     for (s = p; *s == ' ' || *s == ';'; s++)
 1167       ;
 1168   }
 1169 
 1170   return(cookie);
 1171 
 1172  err:
 1173   if (cookie != NULL) {
 1174     if (cookie->name != NULL)
 1175       free(cookie->name);
 1176     if (cookie->value != NULL)
 1177       free(cookie->value);
 1178     free(str);
 1179   }
 1180 
 1181   return(NULL);
 1182 }
 1183 
 1184 /*
 1185  * Retain non-authentication cookies, which may be used by DACS for
 1186  * other purposes (such as NATs) or which are not intended for DACS at all.
 1187  */
 1188 char *non_auth_cookie_header = NULL;
 1189 Cookie *non_auth_cookies = NULL;
 1190 
 1191 /*
 1192  * Find all of the DACS-specific cookies for this application.
 1193  * Cookies can be passed in any one of several ways.
 1194  * If KWV is non-NULL, dacs_acs is the caller and KWV, which was obtained from
 1195  * mod_auth_dacs, must define either SERVICE_COOKIE or HTTP_COOKIE.
 1196  * If KWV is NULL, an Authorization header that specifies the "DACS"
 1197  * auth-scheme (RFC 2617) is used if present, otherwise the environment
 1198  * variable DACS_COOKIE or HTTP_COOKIE is used.
 1199  * Whichever source is used, the string is parsed into its components as
 1200  * a Cookie structure.
 1201  * Only the first AUTH_MAX_CREDENTIALS are handled; the rest are dropped.
 1202  * Return -1 upon an error, 0 otherwise and set NCOOKIES to the count.
 1203  *
 1204  * We only parse Netscape spec cookies, not RFC 2109 etc. cookies.
 1205  */
 1206 int
 1207 get_cookies(Kwv *kwv, Cookie **cookies, unsigned *ncookies)
 1208 {
 1209   int is_dacs_cookie, n, saw_http_cookie_envar;
 1210   char *auth_header, *c, *http_cookie, *name_prefix, *p, *r, *s;
 1211   char *app_cookie_name_prefix;
 1212   size_t name_prefix_len;
 1213   Cookie *cookie, *first_cookie, *last_cookie;
 1214   Kwv_pair *v;
 1215 
 1216   *ncookies = 0;
 1217   *cookies = NULL;
 1218 
 1219   /*
 1220    * All authentication-related DACS cookies for this federation begin with
 1221    * this prefix.
 1222    * Any others may be used by DACS for other purposes or by the web service
 1223    * being called.
 1224    */
 1225   name_prefix = make_cookie_name_federation_prefix(NULL);
 1226   name_prefix_len = strlen(name_prefix);
 1227 
 1228   saw_http_cookie_envar = 0;
 1229 
 1230   s = NULL;
 1231   if ((auth_header = kwv_lookup_value(kwv, "SERVICE_AUTHORIZATION")) != NULL) {
 1232     char **argv;
 1233     unsigned char *decoded;
 1234     Mkargv conf = { 1, 0, NULL, NULL, NULL };
 1235 
 1236     log_msg((LOG_TRACE_LEVEL, "Authorization header found"));
 1237     if ((n = mkargv(auth_header, &conf, &argv)) == 2
 1238         && streq(argv[0], "DACS")) {
 1239       if (mime_decode_base64(argv[1], &decoded) == -1)
 1240         log_msg((LOG_ERROR_LEVEL, "Authorization header failed to decode"));
 1241       else
 1242         s = (char *) decoded;
 1243     }
 1244     else {
 1245       log_msg((LOG_TRACE_LEVEL, "Authorization header is non-DACS, ignoring"));
 1246       auth_header = NULL;
 1247     }
 1248   }
 1249 
 1250   if (kwv == NULL) {
 1251     if ((c = getenv("DACS_COOKIE")) != NULL && *c != '\0') {
 1252       log_msg((LOG_TRACE_LEVEL, "get_cookies: env DACS_COOKIE=%s", c));
 1253       if (s != NULL)
 1254         s = ds_xprintf("%s%s%s", s, COOKIE_SEP_STR, c);
 1255       else
 1256         s = strdup(c);
 1257       envzap("DACS_COOKIE");
 1258     }
 1259     else {
 1260       http_cookie = getenv("HTTP_COOKIE");
 1261       if (http_cookie == NULL || *http_cookie == '\0')
 1262         return(0);
 1263       log_msg((LOG_TRACE_LEVEL,
 1264                "get_cookies: env HTTP_COOKIE=%s", http_cookie));
 1265       if (s != NULL)
 1266         s = ds_xprintf("%s%s%s", s, COOKIE_SEP_STR, http_cookie);
 1267       else
 1268         s = strdup(http_cookie);
 1269       envzap("HTTP_COOKIE");
 1270       saw_http_cookie_envar = 1;
 1271     }
 1272   }
 1273   else if (auth_header == NULL) {
 1274     if ((v = kwv_lookup(kwv, "SERVICE_COOKIE")) != NULL) {
 1275       if (s != NULL)
 1276         s = ds_xprintf("%s%s%s", s, COOKIE_SEP_STR, v->val);
 1277       else
 1278         s = strdup(v->val);
 1279       log_msg((LOG_TRACE_LEVEL, "get_cookies: SERVICE_COOKIE=%s", s));
 1280     }
 1281     else if ((v = kwv_lookup(kwv, "HTTP_COOKIE")) != NULL) {
 1282       if (s != NULL)
 1283         s = ds_xprintf("%s%s%s", s, COOKIE_SEP_STR, v->val);
 1284       else
 1285         s = strdup(v->val);
 1286       log_msg((LOG_TRACE_LEVEL, "get_cookies: HTTP_COOKIE=%s", s));
 1287     }
 1288     else
 1289       return(0);
 1290   }
 1291 
 1292   log_msg((LOG_TRACE_LEVEL, "get_cookies: COOKIE=\"%s\"", non_null(s)));
 1293 
 1294   app_cookie_name_prefix = make_cookie_name_app_prefix(NULL, NULL);
 1295 
 1296   first_cookie = last_cookie = NULL;
 1297   n = 0;
 1298   p = s;
 1299   while (p != NULL) {
 1300     if (n == AUTH_MAX_CREDENTIALS) {
 1301       log_msg((LOG_NOTICE_LEVEL, "Too many credentials"));
 1302       break;
 1303     }
 1304 
 1305     /*
 1306      * The Netscape spec says that a semicolon is used to separate
 1307      * multiple name/value pairs in a Cookie header.
 1308      * RFC 2109 uses a comma for this purpose; such cookies ought
 1309      * to be identified by an initial field of '$Version="1";' in the
 1310      * Cookie header, but that doesn't seem be the case for all clients.
 1311      * DACS doesn't use a comma in Cookie headers, so treating a comma
 1312      * as a semicolon ought to be ok.
 1313      */
 1314     if ((r = strchr(p, ';')) != NULL) {
 1315       *r++ = '\0';
 1316       while (*r == ' ')
 1317         r++;
 1318     }
 1319     else if ((r = strchr(p, ',')) != NULL) {
 1320       *r++ = '\0';
 1321       while (*r == ' ')
 1322         r++;
 1323     }
 1324 
 1325     s = p;
 1326     p = r;
 1327 
 1328     /* Break the cookie up into its fields. */
 1329     if ((cookie = cookie_parse(s, &is_dacs_cookie)) == NULL) {
 1330       strzap(s);
 1331       continue;
 1332     }
 1333 
 1334     if (!is_dacs_cookie) {
 1335       if (non_auth_cookie_header == NULL) {
 1336         non_auth_cookie_header = strdup(s);
 1337         non_auth_cookies = cookie;
 1338       }
 1339       else {
 1340         Cookie **cp;
 1341 
 1342         non_auth_cookie_header = ds_xprintf("%s%s%s", non_auth_cookie_header,
 1343                                             COOKIE_SEP_STR, s);
 1344         for (cp = &non_auth_cookies; *cp != NULL; cp = &(*cp)->next)
 1345           ;
 1346         *cp = cookie;
 1347       }
 1348       strzap(s);
 1349       continue;
 1350     }
 1351 
 1352     if (!conf_val_eq(CONF_ACCEPT_ALIEN_CREDENTIALS, "yes")) {
 1353       /*
 1354        * Ignore cookies unrelated to DACS authentication and this federation.
 1355        */
 1356       if (!strneq(cookie->name, name_prefix, name_prefix_len)) {
 1357         log_msg((LOG_DEBUG_LEVEL, "Ignoring cookie (ACCEPT_ALIEN_CREDENTIAL disabled): \"%s\" does not match required prefix \"%s\"", cookie->name, name_prefix));
 1358         strzap(s);
 1359         continue;
 1360       }
 1361     }
 1362     else {
 1363       if (!strneq(cookie->name, app_cookie_name_prefix,
 1364                   strlen(app_cookie_name_prefix))) {
 1365         log_msg((LOG_DEBUG_LEVEL, "Ignoring cookie: \"%s\"", cookie->name));
 1366         strzap(s);
 1367         continue;
 1368       }
 1369     }
 1370 
 1371     if (first_cookie == NULL)
 1372       first_cookie = last_cookie = cookie;
 1373     else {
 1374       last_cookie->next = cookie;
 1375       last_cookie = cookie;
 1376     }
 1377 
 1378     n++;
 1379   }
 1380 
 1381   *cookies = first_cookie;
 1382   *ncookies = n;
 1383 
 1384   if (n && saw_http_cookie_envar) {
 1385     /*
 1386      * If there's a DACS cookie in the environment (via HTTP_COOKIE), we may
 1387      * have a problem...
 1388      * But only DACS cookies are a problem, not simply HTTP_COOKIE.
 1389      */
 1390     if (!conf_val_eq(CONF_ALLOW_HTTP_COOKIE, "yes")) {
 1391       log_msg((LOG_ALERT_LEVEL, "HTTP_COOKIE present in the environment!"));
 1392       return(-1);
 1393     }
 1394   }
 1395 
 1396   if (non_auth_cookie_header != NULL)
 1397     log_msg((LOG_DEBUG_LEVEL, "Saw non-auth cookies: \"%s\"",
 1398              non_auth_cookie_header));
 1399 
 1400   return(0);
 1401 }
 1402 
 1403 static char *
 1404 make_ua_hash(char *ua_str)
 1405 {
 1406   unsigned int outlen;
 1407   char *ua_hash;
 1408   unsigned char *outp;
 1409 
 1410   if (*ua_str != '\0') {
 1411     outp = crypto_digest(AUTH_UA_HASH_DIGEST_NAME, ua_str, strlen(ua_str),
 1412                          NULL, &outlen);
 1413     mime_encode_base64(outp + 10, outlen - 10, &ua_hash);
 1414     log_msg((LOG_TRACE_LEVEL, "UA=\"%s\", outlen=%u, uahash=\"%s\"",
 1415              ua_str, outlen, ua_hash));
 1416   }
 1417   else
 1418     ua_hash = "";
 1419 
 1420   return(ua_hash);
 1421 }
 1422 
 1423 /*
 1424  * This is called by auth_unique() to heuristically derive an identifier that
 1425  * will ideally be the same for a series of requests from the same user but
 1426  * distinct among all users.
 1427  */
 1428 char *
 1429 make_hash_from_credentials(Credentials *cr)
 1430 {
 1431   Digest_ctx *ctx;
 1432   unsigned int mdbuf_len;
 1433   unsigned char *mdbuf;
 1434   char *buf, *f, *j;
 1435 
 1436   ctx = crypto_digest_open(CRYPTO_DIGEST_ALG_NAME);
 1437   if ((f = cr->federation) == NULL) {
 1438     if ((f = conf_val(CONF_FEDERATION_NAME)) == NULL) {
 1439       /* In case there's no configuration... */
 1440       f = "";
 1441     }
 1442   }
 1443   crypto_digest_hash(ctx, (const void *) f, strlen(f));
 1444   crypto_digest_hash(ctx, (const void *) cr->username, strlen(cr->username));
 1445   if ((j = cr->home_jurisdiction) == NULL)
 1446     j = "";
 1447   crypto_digest_hash(ctx, (const void *) j, strlen(j));
 1448   crypto_digest_hash(ctx, (const void *) cr->ip_address,
 1449                      strlen(cr->ip_address));
 1450   crypto_digest_hash(ctx, (const void *) cr->version,
 1451                      strlen(cr->version));
 1452   if (cr->imported_by != NULL)
 1453     crypto_digest_hash(ctx, (const void *) cr->imported_by,
 1454                        strlen(cr->imported_by));
 1455   if (cr->ua_hash != NULL)
 1456     crypto_digest_hash(ctx, (const void *) cr->ua_hash,
 1457                        strlen(cr->ua_hash));
 1458 
 1459   mdbuf = crypto_digest_close(ctx, NULL, &mdbuf_len);
 1460 
 1461   strba64(mdbuf, mdbuf_len, &buf);
 1462   free(mdbuf);
 1463 
 1464   return(ds_xprintf("%.8s", buf));
 1465 }
 1466 
 1467 /*
 1468  * Return a heuristically-derived identifier string for the context of the
 1469  * current request if no credentials are available, otherwise an almost
 1470  * certainly unique identifier string.  The former is not guaranteed to be
 1471  * unique but may be in some environments and usages.
 1472  * The latter includes the former as a prefix.
 1473  * This identifier is used for audit purposes, to associate a sequence of
 1474  * requests with a particular source.
 1475  */
 1476 char *
 1477 auth_tracker(Credentials *cr)
 1478 {
 1479   static char *tracker = NULL;
 1480 
 1481   if (tracker == NULL) {
 1482     char *ua_str;
 1483     Credentials *temp;
 1484 
 1485     temp = init_credentials();
 1486     if ((temp->username = getenv("SSL_CLIENT_S_DN")) == NULL)
 1487       temp->username = "";
 1488     temp->home_jurisdiction = NULL;
 1489     temp->federation = NULL;
 1490     if ((temp->ip_address = getenv("REMOTE_ADDR")) == NULL)
 1491       temp->ip_address = DEFAULT_IP_ADDR;
 1492     if ((ua_str = getenv("HTTP_USER_AGENT")) == NULL)
 1493       ua_str = "";
 1494     temp->ua_hash = make_ua_hash(ua_str);
 1495     temp->version = DACS_VERSION_NUMBER;
 1496     tracker = make_hash_from_credentials(temp);
 1497   }
 1498 
 1499   if (cr == NULL)
 1500     return(tracker);
 1501 
 1502   return(ds_xprintf("%s,%s", tracker, cr->unique));
 1503 }
 1504 
 1505 char *
 1506 auth_identity(char *federation, char *jurisdiction, char *username,
 1507               char *tracker)
 1508 {
 1509   Ds ds;
 1510 
 1511   ds_init(&ds);
 1512   if (jurisdiction == NULL || username == NULL)
 1513     ds_asprintf(&ds, "unauth");
 1514   else {
 1515     if (jurisdiction == NULL)
 1516       jurisdiction = "???";
 1517     if (username == NULL)
 1518       username = "???";
 1519     if (federation == NULL)
 1520       ds_asprintf(&ds, "%s%c%s",
 1521                   jurisdiction, JURISDICTION_NAME_SEP_CHAR, username);
 1522     else
 1523       ds_asprintf(&ds, "%s%c%c%s%c%s",
 1524                   federation,
 1525                   JURISDICTION_NAME_SEP_CHAR, JURISDICTION_NAME_SEP_CHAR,
 1526                   jurisdiction, JURISDICTION_NAME_SEP_CHAR,
 1527                   username);
 1528     if (tracker != NULL)
 1529       ds_asprintf(&ds, " (%s)", tracker);
 1530   }
 1531 
 1532   return(ds_buf(&ds));
 1533 }
 1534 
 1535 char *
 1536 auth_identity_mine(char *username)
 1537 {
 1538 
 1539   return(auth_identity(conf_val(CONF_FEDERATION_NAME),
 1540                        conf_val(CONF_JURISDICTION_NAME),
 1541                        username, NULL));
 1542 }
 1543 
 1544 char *
 1545 auth_identity_from_credentials(Credentials *cr)
 1546 {
 1547 
 1548   if (cr == NULL)
 1549     return("unauth");
 1550 
 1551   return(auth_identity(cr->federation, cr->home_jurisdiction, cr->username,
 1552                        NULL));
 1553 }
 1554 
 1555 char *
 1556 auth_identity_from_credentials_track(Credentials *cr)
 1557 {
 1558   char *a, *tracker;
 1559 
 1560   tracker = auth_tracker(cr);
 1561   if (cr == NULL)
 1562     a = ds_xprintf("unauthenticated user (%s)", tracker);
 1563   else
 1564     a = auth_identity(cr->federation, cr->home_jurisdiction, cr->username,
 1565                       tracker);
 1566   return(a);
 1567 }
 1568 
 1569 char *
 1570 auth_identities_from_credentials_track(Credentials *credentials)
 1571 {
 1572   char *tracker;
 1573   Credentials *cr;
 1574   Ds ds;
 1575 
 1576   if (credentials == NULL)
 1577     return(auth_identity_from_credentials_track(NULL));
 1578 
 1579   ds_init(&ds);
 1580   for (cr = credentials; cr != NULL; cr = cr->next) {
 1581     tracker = auth_tracker(cr);
 1582     ds_asprintf(&ds, "%s%s", cr == credentials ? "" : ", ",
 1583                 auth_identity(cr->federation, cr->home_jurisdiction,
 1584                               cr->username, tracker));
 1585   }
 1586 
 1587   return(ds_buf(&ds));
 1588 }
 1589 
 1590 char *
 1591 auth_identity_from_cookie(Cookie *c)
 1592 {
 1593 
 1594   if (c == NULL || c->parsed_name == NULL)
 1595     return(NULL);
 1596   return(auth_identity(c->parsed_name->federation,
 1597                        c->parsed_name->jurisdiction,
 1598                        c->parsed_name->username, NULL));
 1599 }
 1600 
 1601 char *
 1602 user_info_service_uri(char *jurisdiction_name)
 1603 {
 1604   char *jname;
 1605   static Jurisdiction *j_info = NULL;
 1606 
 1607   if ((jname = jurisdiction_name) == NULL)
 1608     jname = conf_val(CONF_JURISDICTION_NAME);
 1609 
 1610   if (j_info == NULL || !streq(j_info->jname, jname)) {
 1611     if (get_jurisdiction_meta(jname, &j_info) == -1)
 1612       return(NULL);
 1613   }
 1614 
 1615   return(ds_xprintf("[user_info]dacs-vfs:%s/dacs_vfs", j_info->dacs_url));
 1616 }
 1617 
 1618 static Parse_attr_tab user_info_auth_attr_tab[] = {
 1619   { "ident",        NULL, ATTR_REQUIRED,  NULL, 0 },
 1620   { "ip",           NULL, ATTR_REQUIRED,  NULL, 0 },
 1621   { "auth_time",    NULL, ATTR_REQUIRED,  NULL, 0 },
 1622   { "expires",      NULL, ATTR_REQUIRED,  NULL, 0 },
 1623   { "jurisdiction", NULL, ATTR_REQUIRED,  NULL, 0 },
 1624   { "auth_style",   NULL, ATTR_REQUIRED,  NULL, 0 },
 1625   { "valid_for" ,   NULL, ATTR_REQUIRED,  NULL, 0 },
 1626   { "unique",       NULL, ATTR_REQUIRED,  NULL, 0 },
 1627   { "imported_by",  NULL, ATTR_IMPLIED,   NULL, 0 },
 1628   { NULL,           NULL, ATTR_END,       NULL, 0 }
 1629 };
 1630 
 1631 static Parse_attr_tab user_info_signout_attr_tab[] = {
 1632   { "ident",        NULL, ATTR_REQUIRED,  NULL, 0 },
 1633   { "ip",           NULL, ATTR_REQUIRED,  NULL, 0 },
 1634   { "date",         NULL, ATTR_REQUIRED,  NULL, 0 },
 1635   { "jurisdiction", NULL, ATTR_REQUIRED,  NULL, 0 },
 1636   { "unique",       NULL, ATTR_REQUIRED,  NULL, 0 },
 1637   { NULL,           NULL, ATTR_END,       NULL, 0 }
 1638 };
 1639 
 1640 static Parse_attr_tab user_info_access_attr_tab[] = {
 1641   { "ident",        NULL, ATTR_REQUIRED,  NULL, 0 },
 1642   { "ip",           NULL, ATTR_REQUIRED,  NULL, 0 },
 1643   { "date",         NULL, ATTR_REQUIRED,  NULL, 0 },
 1644   { "jurisdiction", NULL, ATTR_REQUIRED,  NULL, 0 },
 1645   { "host",         NULL, ATTR_IMPLIED,   NULL, 0 },
 1646   { "addr",         NULL, ATTR_IMPLIED,   NULL, 0 },
 1647   { "unique",       NULL, ATTR_REQUIRED,  NULL, 0 },
 1648   { "uri",          NULL, ATTR_REQUIRED,  NULL, 0 },
 1649   { NULL,           NULL, ATTR_END,       NULL, 0 }
 1650 };
 1651 
 1652 static void
 1653 parse_xml_user_info_element_start(void *data, const char *element,
 1654                                   const char **attr)
 1655 {
 1656   char *el, *errmsg;
 1657   User_info *ui, **uip;
 1658 
 1659   if (parse_xmlns_name(element, NULL, &el) == -1) {
 1660     parse_xml_set_error(ds_xprintf("Invalid namespace: %s", element));
 1661     return;
 1662   }
 1663 
 1664   uip = (User_info **) data;
 1665 
 1666   ui = *uip = ALLOC(User_info);
 1667   ui->which = USERINFO_ERROR;
 1668 
 1669   if (parse_xml_is_error(NULL))
 1670     return;
 1671 
 1672   /* Just put something on the parse stack so it's not empty. */
 1673   parse_xml_push(NULL);
 1674 
 1675   if (streq(el, "auth")) {
 1676     User_auth *ua;
 1677 
 1678     ua = &ui->info.auth;
 1679     user_info_auth_attr_tab[0].value = &ua->ident;
 1680     user_info_auth_attr_tab[1].value = &ua->ip;
 1681     user_info_auth_attr_tab[2].value = &ua->auth_time;
 1682     user_info_auth_attr_tab[3].value = &ua->expires;
 1683     user_info_auth_attr_tab[4].value = &ua->jurisdiction;
 1684     user_info_auth_attr_tab[5].value = &ua->auth_style;
 1685     user_info_auth_attr_tab[6].value = &ua->valid_for;
 1686     user_info_auth_attr_tab[7].value = &ua->unique;
 1687     user_info_auth_attr_tab[8].value = &ua->imported_by;
 1688     ua->imported_by = NULL;
 1689     if (parse_xml_attr(user_info_auth_attr_tab, attr, &errmsg) == -1) {
 1690       parse_xml_set_error(errmsg);
 1691       return;
 1692     }
 1693     ui->which = USERINFO_AUTH;
 1694   }
 1695   else if (streq(el, "signout")) {
 1696     User_signout *us;
 1697 
 1698     us = &ui->info.signout;
 1699     user_info_signout_attr_tab[0].value = &us->ident;
 1700     user_info_signout_attr_tab[1].value = &us->ip;
 1701     user_info_signout_attr_tab[2].value = &us->date;
 1702     user_info_signout_attr_tab[3].value = &us->jurisdiction;
 1703     user_info_signout_attr_tab[4].value = &us->unique;
 1704     if (parse_xml_attr(user_info_signout_attr_tab, attr, &errmsg) == -1) {
 1705       parse_xml_set_error(errmsg);
 1706       return;
 1707     }
 1708     ui->which = USERINFO_SIGNOUT;
 1709   }
 1710   else if (streq(el, "acs")) {
 1711     User_access *uacs;
 1712 
 1713     uacs = &ui->info.acs;
 1714     user_info_access_attr_tab[0].value = &uacs->ident;
 1715     user_info_access_attr_tab[1].value = &uacs->ip;
 1716     user_info_access_attr_tab[2].value = &uacs->date;
 1717     user_info_access_attr_tab[3].value = &uacs->jurisdiction;
 1718     user_info_access_attr_tab[4].value = &uacs->host;
 1719     user_info_access_attr_tab[5].value = &uacs->addr;
 1720     user_info_access_attr_tab[6].value = &uacs->unique;
 1721     user_info_access_attr_tab[7].value = &uacs->uri;
 1722     uacs->host = uacs->addr = NULL;
 1723     if (parse_xml_attr(user_info_access_attr_tab, attr, &errmsg) == -1) {
 1724       parse_xml_set_error(errmsg);
 1725       return;
 1726     }
 1727     ui->which = USERINFO_ACCESS;
 1728   }
 1729   else
 1730     return;
 1731 
 1732   parse_xml_pop((void **) NULL);
 1733 }
 1734 
 1735 static void
 1736 parse_xml_user_info_element_end(void *data, const char *el)
 1737 {
 1738 
 1739 }
 1740 
 1741 /*
 1742  * Parse one user information tracking record.
 1743  */
 1744 int
 1745 parse_xml_user_info(char *user_info_rec, User_info **uip)
 1746 {
 1747   int st;
 1748   Parse_xml_error err;
 1749   XML_Parser p = XML_ParserCreateNS(NULL, XMLNS_SEP_CHAR);
 1750 
 1751   if (p == NULL)
 1752     return(-1);
 1753 
 1754   parse_xml_init("Userinfo", p);
 1755 
 1756   XML_SetElementHandler(p, parse_xml_user_info_element_start,
 1757                         parse_xml_user_info_element_end);
 1758   XML_SetUserData(p, (void *) uip);
 1759 
 1760   st = XML_Parse(p, user_info_rec, strlen(user_info_rec), 1);
 1761 
 1762   if (parse_xml_is_error(&err) || st == 0) {
 1763     if (err.mesg == NULL) {
 1764       parse_xml_set_error(NULL);
 1765       parse_xml_is_error(&err);
 1766     }
 1767     log_msg((LOG_ERROR_LEVEL, "parse_xml_user_info: line %d, pos %d",
 1768              err.line, err.pos));
 1769     if (err.mesg != NULL)
 1770       log_msg((LOG_ERROR_LEVEL, "parse_xml_user_info: %s", err.mesg));
 1771     parse_xml_end();
 1772     return(-1);
 1773   }
 1774 
 1775   parse_xml_end();
 1776 
 1777   if (parse_xml_is_not_empty())
 1778     return(-1);
 1779 
 1780   return(0);
 1781 }
 1782 
 1783 /*
 1784  * Return a vector of user information tracking records at JURISDICTION.
 1785  */
 1786 Dsvec *
 1787 user_info_load(char *jurisdiction)
 1788 {
 1789   char *buf, *e, *s, *uri;
 1790   Dsvec *dsv;
 1791   User_info *ui;
 1792   Vfs_handle *h;
 1793 
 1794   if ((uri = user_info_service_uri(jurisdiction)) == NULL)
 1795     return(NULL);
 1796 
 1797   if ((h = vfs_open_item_type(ITEM_TYPE_USER_INFO)) == NULL)
 1798     return(NULL);
 1799 
 1800   if (vfs_get(h, NULL, (void **) &buf, NULL) == -1) {
 1801     vfs_close(h);
 1802     return(NULL);
 1803   }
 1804 
 1805   vfs_close(h);
 1806 
 1807   dsv = dsvec_init(NULL, sizeof(User_info *));
 1808 
 1809   s = buf;
 1810   while (*s != '\0') {
 1811     if ((e = strchr(s, (int) '\n')) != NULL)
 1812       *e = '\0';
 1813     if (parse_xml_user_info(s, &ui) == -1)
 1814       return(NULL);
 1815     dsvec_add_ptr(dsv, ui);
 1816     s = e + 1;
 1817   }
 1818 
 1819   return(dsv);
 1820 }
 1821 
 1822 /*
 1823  * Return a list of user information tracking records by scanning the
 1824  * records in DSV looking for authentication instances that are apparently
 1825  * still active (they have not expired and there is no corresponding signout
 1826  * record) for IDENT, or all identities if IDENT is NULL.
 1827  * Return NULL is no such records exist.
 1828  * If IDENT and LAST_AUTH are non-NULL, set LAST_AUTH to the last
 1829  * authentication record for IDENT.
 1830  */
 1831 Dsvec *
 1832 user_info_active(Dsvec *dsv, char *ident)
 1833 {
 1834   int i, j;
 1835   time_t expires_secs, now;
 1836   Dsvec *dsv_active;
 1837   User_info *ui, *ui_later;
 1838 
 1839   time(&now);
 1840   dsv_active = NULL;
 1841 
 1842   ui = NULL;
 1843   for (i = 0; i < dsvec_len(dsv); i++) {
 1844     ui = (User_info *) dsvec_ptr_index(dsv, i);
 1845     if (ui->which != USERINFO_AUTH
 1846         || (ident != NULL && !streq(ui->info.auth.ident, ident)))
 1847       continue;
 1848 
 1849     if (strnum(ui->info.auth.expires, STRNUM_TIME_T, &expires_secs) == -1)
 1850       continue;
 1851 
 1852     if (expires_secs <= now)
 1853       continue;
 1854 
 1855     for (j = i + 1; j < dsvec_len(dsv); j++) {
 1856       ui_later = (User_info *) dsvec_ptr_index(dsv, j);
 1857       if (ui_later->which == USERINFO_SIGNOUT
 1858           && streq(ui_later->info.signout.unique, ui->info.auth.unique))
 1859         break;
 1860     }
 1861     if (j != dsvec_len(dsv))
 1862       continue;
 1863 
 1864     if (dsv_active == NULL)
 1865       dsv_active = dsvec_init(NULL, sizeof(User_info *));
 1866     dsvec_add_ptr(dsv_active, ui);
 1867     log_msg((LOG_DEBUG_LEVEL, "Active user: %s (expires in %lu secs)\n",
 1868              ui->info.auth.ident,
 1869              (unsigned long) expires_secs - (unsigned long) now));
 1870   }
 1871 
 1872   return(dsv_active);
 1873 }
 1874 
 1875 #ifdef NOTDEF
 1876 /*
 1877  * Return a list of user information tracking records by scanning the
 1878  * records in DSV to remove access records and inactive sessions
 1879  * (expired authentication records or pairs of authentication/sign-off
 1880  * records, except for the last one).
 1881  * This can be used to reduce the number of records without losing
 1882  * the one for the last sign on and any active sessions.
 1883  */
 1884 Dsvec *
 1885 user_info_trim(Dsvec *dsv)
 1886 {
 1887   int i;
 1888   Dsvec *dsv_new;
 1889   User_info *ui;
 1890 
 1891   dsv_new = dsvec_init(NULL, sizeof(User_info *));
 1892   for (i = 0; i < dsvec_len(dsv); i++) {
 1893     ui = (User_info *) dsvec_ptr_index(dsv, i);
 1894     if (ui->which == USERINFO_ACCESS)
 1895       continue;
 1896 dsvec_add_ptr(dsv_new, ui);
 1897   }
 1898 
 1899   return(dsv_new);
 1900 }
 1901 #endif
 1902 
 1903 Dsvec *
 1904 user_info_last_auth(Dsvec *dsv, char *ident)
 1905 {
 1906   int i;
 1907   Dsvec *dsv_auth;
 1908   User_info *ui;
 1909 
 1910   ui = NULL;
 1911   dsv_auth = NULL;
 1912 
 1913   for (i = dsvec_len(dsv) - 1; i >= 0; i--) {
 1914     ui = (User_info *) dsvec_ptr_index(dsv, i);
 1915     if (ui->which == USERINFO_AUTH && streq(ui->info.auth.ident, ident)) {
 1916       if (dsv_auth == NULL)
 1917         dsv_auth = dsvec_init(NULL, sizeof(User_info *));
 1918       dsvec_add_ptr(dsv_auth, ui);
 1919     }
 1920   }
 1921 
 1922   return(dsv_auth);
 1923 }
 1924 
 1925 #ifdef ENABLE_USER_INFO
 1926 void
 1927 user_info_authenticate(Credentials *cr)
 1928 {
 1929   Ds ds;
 1930   Vfs_conf *conf;
 1931   Vfs_handle *h;
 1932 
 1933   if (cr->auth_style & AUTH_STYLE_ADMIN)
 1934     return;
 1935 
 1936   conf = vfs_conf(NULL);
 1937   conf->append_flag = 1;
 1938   vfs_conf(conf);
 1939   if ((h = vfs_open_item_type(ITEM_TYPE_USER_INFO)) == NULL)
 1940     return;
 1941 
 1942   ds_init(&ds);
 1943   ds_asprintf(&ds, "<auth");
 1944   ds_asprintf(&ds, " ident=\"%s\"", auth_identity_from_credentials(cr));
 1945   ds_asprintf(&ds, " ip=\"%s\"", cr->ip_address);
 1946   ds_asprintf(&ds, " auth_time=\"%lu\"", cr->auth_time);
 1947   ds_asprintf(&ds, " expires=\"%lu\"", cr->expires_secs);
 1948   ds_asprintf(&ds, " jurisdiction=\"%s\"", conf_val(CONF_JURISDICTION_NAME));
 1949   ds_asprintf(&ds, " auth_style=\"%u\"", cr->auth_style);
 1950   ds_asprintf(&ds, " valid_for=\"%s\"", cr->valid_for);
 1951   ds_asprintf(&ds, " unique=\"%s\"", cr->unique);
 1952   if (cr->imported_by != NULL)
 1953     ds_asprintf(&ds, " imported_by=\"%s\"", cr->imported_by);
 1954   ds_asprintf(&ds, "/>\n");
 1955 
 1956   vfs_put(h, NULL, ds_buf(&ds), ds_len(&ds) - 1);
 1957 
 1958   vfs_close(h);
 1959 }
 1960 
 1961 void
 1962 user_info_signout(Credentials *cr)
 1963 {
 1964   Ds ds;
 1965   Vfs_conf *conf;
 1966   Vfs_handle *h;
 1967 
 1968   ds_init(&ds);
 1969   ds_asprintf(&ds, "<signout");
 1970   ds_asprintf(&ds, " ident=\"%s\"", auth_identity_from_credentials(cr));
 1971   ds_asprintf(&ds, " ip=\"%s\"", cr->ip_address);
 1972   ds_asprintf(&ds, " date=\"%lu\"", time(NULL));
 1973   ds_asprintf(&ds, " jurisdiction=\"%s\"", conf_val(CONF_JURISDICTION_NAME));
 1974   ds_asprintf(&ds, " unique=\"%s\"", cr->unique);
 1975   ds_asprintf(&ds, "/>\n");
 1976 
 1977   conf = vfs_conf(NULL);
 1978   conf->append_flag = 1;
 1979 
 1980   if (streq(cr->home_jurisdiction, conf_val(CONF_JURISDICTION_NAME))) {
 1981     vfs_conf(conf);
 1982     if ((h = vfs_open_item_type(ITEM_TYPE_USER_INFO)) == NULL)
 1983       return;
 1984   }
 1985   else {
 1986     char *vfs_uri;
 1987 
 1988     /* Must write the record to cr->home_jurisdiction */
 1989     if ((vfs_uri = user_info_service_uri(cr->home_jurisdiction)) == NULL)
 1990       return;
 1991     vfs_conf(conf);
 1992     if ((h = vfs_open_uri(vfs_uri)) == NULL)
 1993       return;
 1994 
 1995     if (streq(h->sd->uri->scheme, "https")) {
 1996       char *cookies[2];
 1997       Credentials *admin_cr;
 1998 
 1999       admin_cr = make_admin_credentials();
 2000       credentials_to_auth_cookies(admin_cr, &cookies[0]);
 2001       cookies[1] = NULL;
 2002       vfs_control(h, VFS_SET_COOKIES, cookies);
 2003     }
 2004   }
 2005 
 2006   vfs_put(h, NULL, ds_buf(&ds), ds_len(&ds) - 1);
 2007 
 2008   vfs_close(h);
 2009 }
 2010 
 2011 void
 2012 user_info_access(Credentials *cr, char *uri, char *remote_addr,
 2013                  char *remote_host)
 2014 {
 2015   char *p;
 2016   Ds ds;
 2017   Vfs_conf *conf;
 2018   Vfs_handle *h;
 2019 
 2020   if (cr == NULL || (cr->auth_style & AUTH_STYLE_ADMIN))
 2021     return;
 2022 
 2023   ds_init(&ds);
 2024   ds_asprintf(&ds, "<acs");
 2025   ds_asprintf(&ds, " ident=\"%s\"", auth_identity_from_credentials(cr));
 2026   ds_asprintf(&ds, " ip=\"%s\"", cr->ip_address);
 2027   ds_asprintf(&ds, " date=\"%lu\"", time(NULL));
 2028   ds_asprintf(&ds, " jurisdiction=\"%s\"", conf_val(CONF_JURISDICTION_NAME));
 2029   if (remote_host != NULL)
 2030     ds_asprintf(&ds, " host=\"%s\"", remote_host);
 2031   else if (remote_addr != NULL)
 2032     ds_asprintf(&ds, " addr=\"%s\"", remote_addr);
 2033   else if ((p = getenv("REMOTE_HOST")) != NULL)
 2034     ds_asprintf(&ds, " host=\"%s\"", p);
 2035   else if ((p = getenv("REMOTE_ADDR")) != NULL)
 2036     ds_asprintf(&ds, " addr=\"%s\"", p);
 2037   ds_asprintf(&ds, " unique=\"%s\"", cr->unique);
 2038   ds_asprintf(&ds, " uri=\"%s\"", url_encode(uri, 0));
 2039   ds_asprintf(&ds, "/>\n");
 2040 
 2041   conf = vfs_conf(NULL);
 2042   conf->append_flag = 1;
 2043 
 2044   if (streq(cr->home_jurisdiction, conf_val(CONF_JURISDICTION_NAME))) {
 2045     vfs_conf(conf);
 2046     if ((h = vfs_open_item_type(ITEM_TYPE_USER_INFO)) == NULL)
 2047       return;
 2048   }
 2049   else {
 2050     /* Must write the record to cr->home_jurisdiction */
 2051     char *vfs_uri;
 2052 
 2053     /* Must write the record to cr->home_jurisdiction */
 2054     if ((vfs_uri = user_info_service_uri(cr->home_jurisdiction)) == NULL)
 2055       return;
 2056     vfs_conf(conf);
 2057     if ((h = vfs_open_uri(vfs_uri)) == NULL)
 2058       return;
 2059 
 2060     if (streq(h->sd->uri->scheme, "https")) {
 2061       char *cookies[2];
 2062       Credentials *admin_cr;
 2063 
 2064       admin_cr = make_admin_credentials();
 2065       credentials_to_auth_cookies(admin_cr, &cookies[0]);
 2066       cookies[1] = NULL;
 2067       vfs_control(h, VFS_SET_COOKIES, cookies);
 2068     }
 2069   }
 2070 
 2071   vfs_put(h, NULL, ds_buf(&ds), ds_len(&ds) - 1);
 2072 
 2073   vfs_close(h);
 2074 }
 2075 #endif
 2076 
 2077 /*
 2078  * As a structure, credentials are only used in memory.  They are converted
 2079  * to XML before they leave DACS, so we don't need to worry about byte
 2080  * order issues here.
 2081  * We assume that USERNAME is already known to be valid.
 2082  */
 2083 Credentials *
 2084 make_credentials(char *fname, char *jname, char *username, char *ip,
 2085                  char *role_str, char *expires, Auth_style style,
 2086                  char *validity, char *imported_by, char *ua_str)
 2087 {
 2088   Credentials *cr;
 2089   time_t et;
 2090 
 2091   cr = init_credentials();
 2092   if (fname != NULL)
 2093     cr->federation = strdup(fname);
 2094   else
 2095     cr->federation = strdup(conf_val(CONF_FEDERATION_NAME));
 2096   if (jname != NULL)
 2097     cr->home_jurisdiction = strdup(jname);
 2098   else
 2099     cr->home_jurisdiction = strdup(conf_val(CONF_JURISDICTION_NAME));
 2100   cr->username = strdup(username);
 2101   cr->ip_address = strdup(non_null(ip));
 2102   cr->role_str = strdup(non_null(role_str));
 2103   cr->auth_time = time(NULL);
 2104   if (expires == NULL)
 2105     et = make_auth_expiry_delta(cr->auth_time,
 2106                                 conf_val(CONF_AUTH_CREDENTIALS_DEFAULT_LIFETIME_SECS));
 2107   else
 2108     et = make_auth_expiry_delta(cr->auth_time, expires);
 2109   cr->expires_secs = et;
 2110   cr->auth_style = style;
 2111   cr->version = strdup(DACS_VERSION_NUMBER);
 2112   cr->valid_for = strdup(validity);
 2113   if (imported_by != NULL)
 2114     cr->imported_by = strdup(imported_by);
 2115 
 2116   /*
 2117    * As an additional measure against misappropriated credentials, optionally
 2118    * put a hash of the user agent identification string (the USER-AGENT
 2119    * request header) in the credentials for (optional) comparison with the
 2120    * string supplied with subsequent requests.  This is similar in concept to
 2121    * IP address checking.
 2122    * A 20-byte hash seems a bit much (excuse the pun); let's only use
 2123    * 10 bytes.
 2124    *
 2125    * If ua_str is NULL, no check should be made; if it is the
 2126    * empty string, it means the client did not provide a UA identifier;
 2127    * otherwise, ua_str is the UA identifier.
 2128    */
 2129   if (ua_str != NULL && !conf_val_eq(CONF_VERIFY_UA, "disable"))
 2130     cr->ua_hash = make_ua_hash(ua_str);
 2131 
 2132   cr->unique = crypto_make_random_a64(NULL, AUTH_CREDENTIALS_UNIQUE_BYTES);
 2133 
 2134   cr->next = NULL;
 2135 
 2136 #ifdef ENABLE_USER_INFO
 2137   user_info_authenticate(cr);
 2138 #endif
 2139 
 2140   return(cr);
 2141 }
 2142 
 2143 Credentials *
 2144 make_admin_credentials(void)
 2145 {
 2146   char *expires, *role_str, *ua_str, *validity;
 2147 
 2148   if ((expires = conf_val(CONF_AUTH_CREDENTIALS_ADMIN_LIFETIME_SECS)) == NULL)
 2149     expires = AUTH_CREDENTIALS_ADMIN_DEFAULT_LIFETIME_SECS;
 2150 
 2151   role_str = "";
 2152   validity = AUTH_VALID_FOR_ACS;
 2153   ua_str = NULL;
 2154 
 2155   return(make_credentials(NULL, NULL, make_dacs_admin_name("DACS-ADMIN"),
 2156                           "", role_str, expires,
 2157                           AUTH_STYLE_ADMIN, validity, NULL, ua_str));
 2158 }
 2159 
 2160 int
 2161 credentials_externalize_with_keys(Credentials *credentials, Crypt_keys *ck,
 2162                                   char **buf)
 2163 {
 2164   unsigned int cookie_len, len;
 2165   unsigned char *encrypted_cookie;
 2166   char *cr_str;
 2167 
 2168   if (credentials == NULL) {
 2169     *buf = "";
 2170     return(0);
 2171   }
 2172 
 2173   cookie_len = make_xml_credentials(credentials, &cr_str);
 2174   log_msg((LOG_TRACE_LEVEL, "XML credentials='%s'", cr_str));
 2175 
 2176   len = crypto_encrypt_string(NULL, ck, (unsigned char *) cr_str,
 2177                               cookie_len + 1, &encrypted_cookie);
 2178   strba64(encrypted_cookie, len, buf);
 2179 
 2180   return(strlen(*buf));
 2181 }
 2182 
 2183 int
 2184 credentials_externalize(Credentials *credentials, char **buf)
 2185 {
 2186   int length;
 2187   Crypt_keys *ck;
 2188 
 2189   if (credentials == NULL) {
 2190     *buf = "";
 2191     return(0);
 2192   }
 2193 
 2194   ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
 2195   length = credentials_externalize_with_keys(credentials, ck, buf);
 2196                                         
 2197   return(length);
 2198 }
 2199 
 2200 /*
 2201  * Convert the list of CREDENTIALS to XML, encrypt, convert that to text,
 2202  * and then create one or more "name=value" strings that will be the payload
 2203  * of a Set-Cookie.
 2204  * Return the length of the cookie (-1 implies an error occurred)
 2205  * XXX Might consider XML Encryption
 2206  * http://www.w3.org/TR/2002/REC-xmlenc-core-20021210/
 2207  */
 2208 int
 2209 credentials_to_auth_cookies(Credentials *credentials, char **cookie_buf)
 2210 {
 2211   int i, length, st;
 2212   char *encrypted_cookie_text;
 2213   Credentials *cr;
 2214   Crypt_keys *ck;
 2215   Ds ds;
 2216 
 2217   if (credentials == NULL) {
 2218     *cookie_buf = "";
 2219     return(0);
 2220   }
 2221 
 2222   ds_init(&ds);
 2223   ds.exact_flag = 1;
 2224   ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
 2225 
 2226   st = 0;
 2227   for (i = 0, cr = credentials; cr != NULL; cr = cr->next, i++) {
 2228     credentials_externalize_with_keys(cr, ck, &encrypted_cookie_text);
 2229 
 2230     if (cr->cookie_name == NULL)
 2231       cr->cookie_name = make_auth_cookie_name(cr);
 2232 
 2233     st = ds_asprintf(&ds, "%s%s=%s", i == 0 ? "" : COOKIE_SEP_STR,
 2234                      cr->cookie_name, encrypted_cookie_text);
 2235     if (st == -1)
 2236       break;
 2237   }
 2238 
 2239   crypt_keys_free(ck);
 2240   if (st == -1)
 2241     return(-1);
 2242 
 2243   *cookie_buf = ds_buf(&ds);
 2244   length = ds_len(&ds);
 2245 
 2246   return(length);
 2247 }
 2248 
 2249 /*
 2250  * Create a Set-Cookie response header string (setting
 2251  * SET_COOKIE_BUF to point to it) and return its length.
 2252  * COOKIE is the 'name=value' part of the cookie.
 2253  * A return value of zero implies an error occurred.
 2254  *
 2255  * XXX The values obtained from configuration aren't checked for validity.
 2256  */
 2257 int
 2258 make_set_cookie_header(char *cookie, char *lifetime, int for_acs,
 2259                        int cookie_only, char **set_cookie_buf)
 2260 {
 2261   char *path, *persistent_cookie;
 2262   Ds ds;
 2263 
 2264   if (for_acs && cookie_only) {
 2265     /* An invalid combination. */
 2266     return(0);
 2267   }
 2268 
 2269   if (DACS_USE_PERSISTENT_COOKIE && lifetime != NULL) {
 2270     if (cookie_output_syntax == COOKIE_NETSCAPE)
 2271       persistent_cookie = ds_xprintf("expires=Fri, 31-Dec-2012 17:17:17 GMT");
 2272     else
 2273       persistent_cookie = ds_xprintf("Max-Age=%s", lifetime);
 2274   }
 2275   else
 2276     persistent_cookie = NULL;
 2277 
 2278   if ((path = conf_val(CONF_COOKIE_PATH)) == NULL)
 2279     path = "/";
 2280 
 2281   ds_init(&ds);
 2282   if (cookie_only)
 2283     ds_asprintf(&ds, "%s", cookie);
 2284   else {
 2285     /* XXX All of these things should use the new acs_emit_*header() API */
 2286     ds_asprintf(&ds, "%sSet-Cookie%s%s",
 2287                 for_acs ? "=" : "",
 2288                 for_acs ? "=" : ": ",
 2289                 cookie);
 2290   }
 2291 
 2292   ds_asprintf(&ds, "; path=%s", path);
 2293 
 2294   if (!conf_val_eq(CONF_COOKIE_NO_DOMAIN, "yes")) {
 2295     /* Note the "." preceding the value of the domain attribute. */
 2296     ds_asprintf(&ds, "; domain=.%s",
 2297                 conf_val(CONF_FEDERATION_DOMAIN));
 2298   }
 2299 
 2300   if (persistent_cookie != NULL)
 2301     ds_asprintf(&ds, "; %s", persistent_cookie);
 2302 
 2303   if (dacs_secure_mode() || dacs_is_https_request())
 2304     ds_asprintf(&ds, "; secure");
 2305 
 2306   /* http://msdn.microsoft.com/workshop/author/dhtml/httponly_cookies.asp */
 2307   if (conf_val_eq(CONF_COOKIE_HTTPONLY, "yes"))
 2308     ds_asprintf(&ds, "; HttpOnly");
 2309 
 2310   ds_asprintf(&ds, "\n");
 2311 
 2312   if (!cookie_only)
 2313     ds_asprintf(&ds, "%sCache-Control%sno-cache=\"set-cookie\"\n",
 2314                 for_acs ? "=" : "",
 2315                 for_acs ? "=" : ": ");
 2316 
 2317   *set_cookie_buf = ds_buf(&ds);
 2318   return(ds_len(&ds));
 2319 }
 2320 
 2321 int
 2322 make_set_void_cookie_header(char *cookie_name, int for_acs,
 2323                             char **set_cookie_buf)
 2324 {
 2325   char *path, *ps;
 2326   Ds ds;
 2327 
 2328   /*
 2329    * A time in the past.  We use the earliest possible Unix clock time
 2330    * (time(NULL) == 0).
 2331    * XXX The Netscape cookie spec isn't specific about what the earliest
 2332    * possible date is.  This may be important if we want to forcibly remove
 2333    * cookies (i.e., revoke authentication) and the user has turned back his
 2334    * clock.
 2335    */
 2336   if (cookie_output_syntax == COOKIE_NETSCAPE)
 2337     ps = "expires=Thu, 01-Jan-1970 00:00:01 GMT";
 2338   else
 2339     ps = ds_xprintf("Max-Age=0");
 2340 
 2341   if ((path = conf_val(CONF_COOKIE_PATH)) == NULL)
 2342     path = "/";
 2343 
 2344   ds_init(&ds);
 2345   if (conf_val_eq(CONF_COOKIE_NO_DOMAIN, "yes"))
 2346     ds_asprintf(&ds, "%sSet-Cookie%s%s=; path=%s; %s\n",
 2347                 for_acs ? "=" : "",
 2348                 for_acs ? "=" : ": ",
 2349                 cookie_name, path, ps);
 2350   else {
 2351     /* Note the "." preceding the value of the domain attribute. */
 2352     ds_asprintf(&ds, "%sSet-Cookie%s%s=; path=%s; domain=.%s;%s\n",
 2353                 for_acs ? "=" : "",
 2354                 for_acs ? "=" : ": ",
 2355                 cookie_name, path, conf_val(CONF_FEDERATION_DOMAIN), ps);
 2356   }
 2357 
 2358   *set_cookie_buf = ds_buf(&ds);
 2359   return(0);
 2360 }
 2361 
 2362 /*
 2363  * Construct a Set-Cookie response header using CREDENTIALS.
 2364  * Return the length of the string or -1 on error.
 2365  */
 2366 int
 2367 make_set_auth_cookie_header(Credentials *credentials, char *lifetime,
 2368                             int cookie_only, char **cookie_buf)
 2369 {
 2370   int len;
 2371   char *cookie;
 2372 
 2373   if (credentials_to_auth_cookies(credentials, &cookie) == -1)
 2374     return(-1);
 2375 
 2376   len = make_set_cookie_header(cookie, lifetime, 0, cookie_only, cookie_buf);
 2377 
 2378   strzap(cookie);
 2379   free(cookie);
 2380 
 2381   return(len);
 2382 }
 2383 
 2384 /*
 2385  * Check if the syntax of COOKIE_NAME is valid for the name of an
 2386  * authentication cookie for this federation.
 2387  * Return 1 if it is, 0 otherwise.
 2388  */
 2389 int
 2390 is_auth_cookie_name(char *cookie_name)
 2391 {
 2392   Cookie_name *cn;
 2393 
 2394   if ((cn = parse_dacs_auth_cookie_name(cookie_name)) == NULL)
 2395     return(0);
 2396 
 2397   if (cn->app_name == NULL || cn->federation == NULL
 2398       || cn->jurisdiction == NULL || cn->username == NULL)
 2399     return(0);
 2400 
 2401   if (!streq(cn->app_name, "DACS"))
 2402     return(0);
 2403 
 2404   if (!conf_val_eq(CONF_ACCEPT_ALIEN_CREDENTIALS, "yes")) {
 2405     if (!name_eq(cn->federation, conf_val(CONF_FEDERATION_NAME),
 2406                  DACS_NAME_CMP_CONFIG)) {
 2407       log_msg((LOG_INFO_LEVEL, "Ignoring credentials from federation \"%s\"",
 2408                cn->federation));
 2409       return(0);
 2410     }
 2411   }
 2412   else if (!is_valid_federation_name(cn->federation))
 2413     return(0);
 2414 
 2415   if (auth_single_cookie() == 2) {
 2416     /*
 2417      * The jurisdiction name is suppressed at this jurisdiction; we will reject
 2418      * a cookie name that includes it.
 2419      * But if it is from a different jurisdiction we don't care.
 2420      */
 2421     if (name_eq(cn->jurisdiction, conf_val(CONF_JURISDICTION_NAME),
 2422                 DACS_NAME_CMP_CONFIG)) {
 2423       log_msg((LOG_INFO_LEVEL,
 2424                "Ignoring credentials because of suppressed jurisdiction"));
 2425       return(0);
 2426     }
 2427   }
 2428   else if (auth_single_cookie() == 1) {
 2429     /*
 2430      * The username is suppressed at this jurisdiction; we will reject
 2431      * a cookie name that includes it.
 2432      * But if it is from a different jurisdiction, we don't care.
 2433      */
 2434     if (name_eq(cn->jurisdiction, conf_val(CONF_JURISDICTION_NAME),
 2435                 DACS_NAME_CMP_CONFIG) && *cn->username != '\0') {
 2436       log_msg((LOG_INFO_LEVEL,
 2437                "Ignoring credentials because of suppressed username"));
 2438       return(0);
 2439     }
 2440   }
 2441   else {
 2442     /*
 2443      * AUTH_SINGLE_COOKIE is disabled, so we don't care if the jurisdiction
 2444      * name or username are absent.  This cookie may have come from a different
 2445      * jurisdiction or the directive might have been changed.
 2446      */
 2447   }
 2448  
 2449   if (*cn->jurisdiction != '\0'
 2450       && !is_valid_jurisdiction_name(cn->jurisdiction))
 2451     return(0);
 2452 
 2453   if (*cn->username != '\0' && !is_valid_username(cn->username))
 2454     return(0);
 2455 
 2456   if (*cn->jurisdiction == '\0' && *cn->username != '\0')
 2457     return(0);
 2458 
 2459   return(1);
 2460 }
 2461 
 2462 #ifdef ANONYMOUS_USERS
 2463 /*
 2464  * Anonymous credentials are intended to represent an unauthenticated user
 2465  * for tracking purposes and to allow such users to have roles.
 2466  */
 2467 static char *
 2468 make_anonymous_cookie_name(void)
 2469 {
 2470   char *name;
 2471 
 2472   name = make_cookie_name(NULL, DACS_COOKIE_APP_NAME,
 2473                           conf_val(CONF_FEDERATION_NAME),
 2474                           conf_val(CONF_JURISDICTION_NAME),
 2475                           make_dacs_admin_name("ANONYMOUS"), NULL);
 2476   return(name);
 2477 }
 2478 
 2479 int
 2480 is_anonymous_cookie_name(char *cookie_name)
 2481 {
 2482   char *cn;
 2483 
 2484   cn = make_anonymous_cookie_name();
 2485   if (streq(cookie_name, cn))
 2486     return(1);
 2487 
 2488   return(0);
 2489 }
 2490 
 2491 Credentials *
 2492 make_anonymous_credentials(char *ip, char *role_str, char *ua_str)
 2493 {
 2494 
 2495   return(make_credentials(NULL, NULL, make_dacs_admin_name("ANONYMOUS"),
 2496                           ip, role_str, NULL, AUTH_STYLE_ADMIN,
 2497                           AUTH_VALID_FOR_ACS, ua_str));
 2498 }
 2499 #endif
 2500 
 2501 static char *
 2502 make_selected_cookie_name(void)
 2503 {
 2504   char *name;
 2505 
 2506   name = make_cookie_name(NULL, DACS_COOKIE_APP_NAME,
 2507                           conf_val(CONF_FEDERATION_NAME), NULL, NULL,
 2508                           "SELECTED");
 2509   return(name);
 2510 }
 2511 
 2512 int
 2513 make_set_void_selected_cookie_header(char **buf)
 2514 {
 2515   char *name;
 2516 
 2517   name = make_selected_cookie_name();
 2518 
 2519   return(make_set_void_cookie_header(name, 0, buf));
 2520 }
 2521 
 2522 int
 2523 is_selected_cookie_name(char *cookie_name)
 2524 {
 2525   char *cn;
 2526 
 2527   cn = make_selected_cookie_name();
 2528   if (streq(cookie_name, cn))
 2529     return(1);
 2530 
 2531   return(0);
 2532 }
 2533 
 2534 int
 2535 reset_scredentials(Credentials *credentials)
 2536 {
 2537   int n;
 2538   Credentials *cr;
 2539 
 2540   n = 0;
 2541   for (cr = credentials; cr != NULL; cr = cr->next) {
 2542     if (cr->selected) {
 2543       cr->selected = 0;
 2544       n++;
 2545     }
 2546   }
 2547 
 2548   return(n);
 2549 }
 2550 
 2551 /*
 2552  * Scan through the CREDENTIALS list, looking for credentials that
 2553  * match any of S.
 2554  * If a match is found, those are selected credentials, and create a list
 2555  * at which SELECTED will point.
 2556  * Set each credentials' 'selected' field appropriately.
 2557  * Return -1 on error, otherwise a count of the number of selected
 2558  * credentials;
 2559  */
 2560 static int
 2561 find_matching_scredentials(Credentials *credentials,
 2562                            Scredentials *s, Credentials **selected)
 2563 {
 2564   int n;
 2565   Credentials *cr, *new_cr, **sp;
 2566   Scredentials_selected *sc;
 2567 
 2568   *selected = NULL;
 2569 
 2570   reset_scredentials(credentials);
 2571 
 2572   if (s->unauth != NULL)
 2573     return(0);
 2574 
 2575   n = 0;
 2576   sp = selected;
 2577   for (cr = credentials; cr != NULL; cr = cr->next) {
 2578     for (sc = s->selected; sc != NULL; sc = sc->next) {
 2579       if (streq(cr->unique, sc->unique)) {
 2580         cr->selected = 1;
 2581         new_cr = ALLOC(Credentials);
 2582         *new_cr = *cr;
 2583         new_cr->next = NULL;
 2584         *sp = new_cr;
 2585         sp = &new_cr->next;
 2586         n++;
 2587         break;
 2588       }
 2589     }
 2590   }
 2591 
 2592   return(n);
 2593 }
 2594 
 2595 /*
 2596  * Search the list of cookies for a SELECTED cookie.  If found, parse
 2597  * its contents and make SCP point to it.
 2598  * Return 0 if there is no SELECTED cookie, -1 on error, and 1 otherwise.
 2599  */
 2600 int
 2601 get_scredentials(Cookie *cookies, Scredentials **sp)
 2602 {
 2603   unsigned int decrypted_cookie_len, encrypted_cookie_len;
 2604   unsigned char *decrypted_cookie, *encrypted_cookie;
 2605   char *name;
 2606   Cookie *c;
 2607   Crypt_keys *ck;
 2608 
 2609   *sp = NULL;
 2610   name = make_selected_cookie_name();
 2611   for (c = cookies; c != NULL; c = c->next) {
 2612     if (streq(c->name, name))
 2613       break;
 2614   }
 2615 
 2616   if (c == NULL) {
 2617     log_msg((LOG_TRACE_LEVEL, "There are no selected credentials"));
 2618     return(0);
 2619   }
 2620 
 2621   if (stra64b(c->value, &encrypted_cookie, &encrypted_cookie_len) == NULL) {
 2622     log_msg((LOG_ERROR_LEVEL, "Cookie unpacking failed"));
 2623     return(-1);
 2624   }
 2625 
 2626   ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
 2627   if (crypto_decrypt_string(NULL, ck, encrypted_cookie, encrypted_cookie_len,
 2628                             &decrypted_cookie, &decrypted_cookie_len) == -1) {
 2629     crypt_keys_free(ck);
 2630     log_msg((LOG_ERROR_LEVEL, "Cookie decryption failed"));
 2631     return(-1);
 2632   }
 2633   crypt_keys_free(ck);
 2634 
 2635   if (parse_xml_scredentials((char *) decrypted_cookie, sp) == -1) {
 2636     log_msg((LOG_ERROR_LEVEL, "XML scredentials parse failed"));
 2637     return(-1);
 2638   }
 2639 
 2640   log_msg((LOG_TRACE_LEVEL, "Selection cookie:\n%s", decrypted_cookie));
 2641   if ((*sp)->unauth != NULL)
 2642     log_msg((LOG_TRACE_LEVEL, "User has selected to be unauthenticated"));
 2643   else
 2644     log_msg((LOG_TRACE_LEVEL, "There are selected credentials"));
 2645 
 2646   return(1);
 2647 }
 2648 
 2649 /*
 2650  * Scan through the list of cookies for one that identifies the selected set of
 2651  * credentials.  If such a cookie is found, decrypt it and try to find
 2652  * matching credentials in the list of valid credentials.
 2653  * If matching credentials are found, they are the selected credentials and
 2654  * set SELECTED to point to them.
 2655  * Return -1 on error, otherwise a count of the number of effectively
 2656  * selected credentials;
 2657  *
 2658  * XXX We assume that there can only be one selected credentials.
 2659  */
 2660 static int
 2661 select_scredentials(Credentials *credentials, Cookie *cookies,
 2662                     Credentials **selected, Scredentials **sp)
 2663 {
 2664   int st;
 2665   Scredentials *s;
 2666 
 2667   if ((st = get_scredentials(cookies, &s)) == -1)
 2668     return(-1);
 2669 
 2670   *selected = NULL;
 2671   if (sp != NULL)
 2672     *sp = s;
 2673 
 2674   if (st == 0) {
 2675     int n;
 2676     Credentials *cr;
 2677 
 2678     /* No SELECTED cookie was found so all credentials are used. */
 2679     *selected = credentials;
 2680     n = 0;
 2681     for (cr = credentials; cr != NULL; cr = cr->next)
 2682       n++;
 2683     return(n);
 2684   }
 2685 
 2686   st = find_matching_scredentials(credentials, s, selected);
 2687 
 2688   return(st);
 2689 }
 2690 
 2691 /*
 2692  * Traverse a list of cookies, extract the valid credentials from them and
 2693  * put those credentials into a list, setting SELECTED to point to it.
 2694  * If there are selected credentials, either only those credentials or no
 2695  * credentials will be returned.
 2696  * Return the number of credentials, selected or not, or -1 on error.
 2697  */ 
 2698 int
 2699 get_valid_scredentials(Cookie *cookies, char *remote_addr, int valid_for_acs,
 2700                        Credentials **credentials,
 2701                        Credentials **selected, Scredentials **sp)
 2702 {
 2703   int n;
 2704   Credentials *cr;
 2705 
 2706   *selected = NULL;
 2707   if (sp != NULL)
 2708     *sp = NULL;
 2709 
 2710   /* First, weed out only the valid, potentially selectable credentials. */
 2711   n = get_valid_credentials(cookies, remote_addr, valid_for_acs, credentials);
 2712 
 2713   if (n > 0) {
 2714     n = select_scredentials(*credentials, cookies, selected, sp);
 2715     if (n == -1)
 2716       return(-1);
 2717 
 2718     for (cr = *selected; cr != NULL; cr = cr->next)
 2719       log_msg((LOG_TRACE_LEVEL,
 2720                "Credentials: identity=%s, style=%s, valid_for=%s",
 2721                auth_identity_from_credentials(cr),
 2722                auth_style_to_string(cr->auth_style),
 2723                cr->valid_for));
 2724   }
 2725 
 2726   return(n);
 2727 }
 2728 
 2729 /*
 2730  * Create a cookie value to set selected credentials.
 2731  * The cookie value will be XML that describes the selected credentials,
 2732  * which are already in the possession of the user.  The most important part
 2733  * of the XML is the reference to the unique id.  Encrypt the XML and convert
 2734  * that to text, and then create a "name=value" string that will be the
 2735  * payload of a cookie.
 2736  * As a special case, if CREDENTIALS is NULL, it means that a special
 2737  * cookie value should be created that will indicate that the user should
 2738  * be treated as unauthenticated.
 2739  * Return the length of the cookie; 0 indicates that there are no selected
 2740  * credentials and -1 implies an error occurred.
 2741  */
 2742 int
 2743 make_scredentials_cookie(Credentials *credentials, char **buf)
 2744 {
 2745   int nselected;
 2746   unsigned int len;
 2747   unsigned char *encrypted_cookie;
 2748   char *encrypted_cookie_text;
 2749   Credentials *cr;
 2750   Crypt_keys *ck;
 2751   Ds ds;
 2752 
 2753   ds_init(&ds);
 2754   ds.exact_flag = 1;
 2755 
 2756   if (credentials == NULL) {
 2757     ds_asprintf(&ds, "<selected_credentials>");
 2758     ds_asprintf(&ds, "<unauth federation=\"%s\"/>",
 2759                 conf_val(CONF_FEDERATION_NAME));
 2760     ds_asprintf(&ds, "</selected_credentials>");
 2761     log_msg((LOG_TRACE_LEVEL, "make_scredentials_cookie: unauth"));
 2762   }
 2763   else {
 2764     nselected = 0;
 2765     for (cr = credentials; cr != NULL; cr = cr->next) {
 2766       if (cr->selected) {
 2767         if (nselected == 0)
 2768           ds_asprintf(&ds, "<selected_credentials>");
 2769         ds_asprintf(&ds, "<selected");
 2770         ds_asprintf(&ds, " federation=\"%s\"", cr->federation);
 2771         ds_asprintf(&ds, " jurisdiction=\"%s\"", cr->home_jurisdiction);
 2772         ds_asprintf(&ds, " username=\"%s\"", cr->username);
 2773         ds_asprintf(&ds, " unique=\"%s\"", cr->unique);
 2774         ds_asprintf(&ds, " version=\"%s\"", cr->version);
 2775         ds_asprintf(&ds, "/>");
 2776         nselected++;
 2777         log_msg((LOG_TRACE_LEVEL, "make_scredentials_cookie: %s",
 2778                  auth_identity_from_credentials(cr)));
 2779       }
 2780     }
 2781     
 2782     if (nselected)
 2783       ds_asprintf(&ds, "</selected_credentials>");
 2784     else {
 2785       *buf = NULL;
 2786       return(0);
 2787     }
 2788   }
 2789 
 2790   log_msg((LOG_TRACE_LEVEL, "make_scredentials_cookie:\n%s", ds_buf(&ds)));
 2791 
 2792   ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
 2793   len = crypto_encrypt_string(NULL, ck, (unsigned char *) ds_buf(&ds),
 2794                               ds_len(&ds) + 1, &encrypted_cookie);
 2795   crypt_keys_free(ck);
 2796   ds_free(&ds);
 2797   strba64(encrypted_cookie, len, &encrypted_cookie_text);
 2798   free(encrypted_cookie);
 2799 
 2800   ds_init(&ds);
 2801   ds.exact_flag = 1;
 2802   if (ds_asprintf(&ds, "%s=%s",
 2803                   make_selected_cookie_name(), encrypted_cookie_text) == -1) {
 2804     free(encrypted_cookie_text);
 2805     return(-1);
 2806   }
 2807 
 2808   free(encrypted_cookie_text);
 2809 
 2810   *buf = ds_buf(&ds);
 2811   return(strlen(*buf));
 2812 }
 2813 
 2814 int
 2815 make_set_scredentials_cookie_header(Credentials *credentials,
 2816                                     char **cookie_buf)
 2817 {
 2818   char *buf;
 2819 
 2820   if (make_scredentials_cookie(credentials, &buf) == -1)
 2821     return(-1);
 2822   if (buf == NULL)
 2823     return(0);
 2824 
 2825   make_set_cookie_header(buf, NULL, 0, 0, cookie_buf);
 2826 
 2827   return(1);
 2828 }
 2829 
 2830 void
 2831 cookies_html(FILE *fp, Cookie *cookies)
 2832 {
 2833   int i;
 2834   Cookie *c;
 2835 
 2836   i = 0;
 2837   for (c = cookies; c != NULL; c = c->next) {
 2838     fprintf(fp, "<p>");
 2839     fprintf(fp, "Cookie %d:", i + 1);
 2840     if (c->syntax == COOKIE_NETSCAPE)
 2841       fprintf(fp, "<br>Netscape format\n");
 2842     else if (c->syntax == COOKIE_EXT_NETSCAPE)
 2843       fprintf(fp, "<br>Extended Netscape format\n");
 2844     else if (c->syntax == COOKIE_RFC2109)
 2845       fprintf(fp, "<br>RFC 2109 format\n");
 2846     else if (c->syntax == COOKIE_RFC2965)
 2847       fprintf(fp, "<br>RFC 2965 format\n");
 2848     else if (c->syntax == COOKIE_RFC6265)
 2849       fprintf(fp, "<br>RFC 6265 format\n");
 2850 
 2851     if (c->name != NULL)
 2852       fprintf(fp, "<br>name='%s'\n", c->name);
 2853     if (c->value != NULL)
 2854       fprintf(fp, "<br>value='%s'\n", c->value);
 2855     if (c->domain != NULL)
 2856       fprintf(fp, "<br>domain='%s'\n", c->domain);
 2857     if (c->path != NULL)
 2858       fprintf(fp, "<br>path='%s'\n", c->path);
 2859     if (c->expires != NULL)
 2860       fprintf(fp, "<br>expires='%s'\n", c->expires);
 2861     if (c->max_age != NULL)
 2862       fprintf(fp, "<br>max-age='%s'\n", c->max_age);
 2863     if (c->comment != NULL)
 2864       fprintf(fp, "<br>comment='%s'\n", c->comment);
 2865     if (c->version != NULL)
 2866       fprintf(fp, "<br>version='%s'\n", c->version);
 2867     if (c->comment_url != NULL)
 2868       fprintf(fp, "<br>comment_url='%s'\n", c->comment_url);
 2869     if (c->discard != NULL)
 2870       fprintf(fp, "<br>discard='%s'\n", c->discard);
 2871     if (c->port != NULL)
 2872       fprintf(fp, "<br>port='%s'\n", c->port);
 2873     fprintf(fp, "<br>secure=%d\n", c->secure);
 2874     fprintf(fp, "<br>HttpOnly=%d\n", c->httponly);
 2875     i++;
 2876     fflush(fp);
 2877   }
 2878   fprintf(fp, "<p>");
 2879 }
 2880 
 2881 /**********************************************/
 2882 
 2883 typedef enum Parse_xml_auth_state_code {
 2884   AUTH_PARSE_ROLES_REPLY            = 10,
 2885   AUTH_PARSE_ROLES_REPLY_OK         = 11,
 2886   AUTH_PARSE_ROLES_REPLY_FAILED     = 12,
 2887   AUTH_PARSE_ROLES_REPLY_ERROR      = 13,
 2888   AUTH_PARSE_AUTH_REPLY             = 20,
 2889   AUTH_PARSE_AUTH_REPLY_OK          = 21,
 2890   AUTH_PARSE_AUTH_REPLY_FAILED      = 22,
 2891   AUTH_PARSE_AUTH_REPLY_ERROR       = 23,
 2892   AUTH_PARSE_AUTH_REPLY_PROMPTS     = 24,
 2893   AUTH_PARSE_AUTH_REPLY_PROMPT      = 25,
 2894   AUTH_PARSE_AUTH_REPLY_ROLES_REPLY = 26
 2895 } Parse_xml_auth_state_code;
 2896 
 2897 typedef struct Parse_xml_roles_reply_state {
 2898   Parse_xml_auth_state_code code;
 2899   union {
 2900     Roles_reply *roles_reply;
 2901   } object;
 2902 } Parse_xml_roles_reply_state;
 2903 
 2904 static Parse_xml_roles_reply_state *
 2905 parse_xml_make_roles_reply_state(Parse_xml_auth_state_code code,
 2906                                  void *object)
 2907 {
 2908   Parse_xml_roles_reply_state *s;
 2909 
 2910   s = ALLOC(Parse_xml_roles_reply_state);
 2911   s->code = code;
 2912 
 2913   switch (code) {
 2914   case AUTH_PARSE_ROLES_REPLY:
 2915   case AUTH_PARSE_ROLES_REPLY_OK:
 2916   case AUTH_PARSE_ROLES_REPLY_FAILED:
 2917   case AUTH_PARSE_ROLES_REPLY_ERROR:
 2918     s->object.roles_reply = (Roles_reply *) object;
 2919     break;
 2920   default:
 2921     /* XXX ??? */
 2922     return(NULL);
 2923   }
 2924 
 2925   return(s);
 2926 }
 2927 
 2928 static Parse_attr_tab roles_reply_attr_tab[] = {
 2929   { XMLNS_XSI,    NULL, ATTR_IGNORE_NS, NULL, 0 },
 2930   { XMLNS_PREFIX, NULL, ATTR_IGNORE_NS, NULL, 0 },
 2931   { NULL,         NULL, ATTR_END,       NULL, 0 }
 2932 };
 2933 
 2934 static Parse_attr_tab roles_reply_ok_attr_tab[] = {
 2935   { "roles",      NULL, ATTR_REQUIRED,  NULL, 0 },
 2936   { XMLNS_XSI,    NULL, ATTR_IGNORE_NS, NULL, 0 },
 2937   { XMLNS_PREFIX, NULL, ATTR_IGNORE_NS, NULL, 0 },
 2938   { NULL,         NULL, ATTR_END,       NULL, 0 }
 2939 };
 2940 
 2941 static Parse_attr_tab roles_reply_failed_attr_tab[] = {
 2942   { "reason",     NULL, ATTR_REQUIRED,  NULL, 0 },
 2943   { XMLNS_XSI,    NULL, ATTR_IGNORE_NS, NULL, 0 },
 2944   { XMLNS_PREFIX, NULL, ATTR_IGNORE_NS, NULL, 0 },
 2945   { NULL,         NULL, ATTR_END,       NULL, 0 }
 2946 };
 2947 
 2948 static void
 2949 parse_xml_roles_reply_element_start(void *data, const char *element,
 2950                                     const char **attr)
 2951 {
 2952   char *el, *errmsg;
 2953   Roles_reply *rr;
 2954   Parse_xml_roles_reply_state *state;
 2955 
 2956   if (parse_xmlns_name(element, NULL, &el) == -1) {
 2957     parse_xml_set_error(ds_xprintf("Invalid namespace: %s", element));
 2958     return;
 2959   }
 2960 
 2961   if (parse_xml_is_error(NULL))
 2962     return;
 2963 
 2964   rr = *(Roles_reply **) data;
 2965 
 2966   if (streq(el, "roles_reply")) {
 2967     if (parse_xml_attr(roles_reply_attr_tab, attr, &errmsg) == -1) {
 2968       parse_xml_set_error(errmsg);
 2969       return;
 2970     }
 2971     parse_xml_push(parse_xml_make_roles_reply_state(AUTH_PARSE_ROLES_REPLY,
 2972                                                     (void *) rr));
 2973   }
 2974   else if (streq(el, "ok")) {
 2975     Roles_reply_ok *ok;
 2976 
 2977     if (parse_xml_top((void **) &state) != PARSE_XML_OK
 2978         || state->code != AUTH_PARSE_ROLES_REPLY) {
 2979       parse_xml_set_error("Unexpected \"failed\" element");
 2980       return;
 2981     }
 2982 
 2983     ok = ALLOC(Roles_reply_ok);
 2984     rr->ok = ok;
 2985 
 2986     roles_reply_ok_attr_tab[0].value = &rr->ok->roles;
 2987     if (parse_xml_attr(roles_reply_ok_attr_tab, attr, &errmsg) == -1) {
 2988       parse_xml_set_error(errmsg);
 2989       return;
 2990     }
 2991 
 2992     parse_xml_push(parse_xml_make_roles_reply_state(AUTH_PARSE_ROLES_REPLY_OK,
 2993                                                     (void *) rr));
 2994   }
 2995   else if (streq(el, "failed")) {
 2996     Roles_reply_failed *failed;
 2997 
 2998     if (parse_xml_top((void **) &state) != PARSE_XML_OK
 2999         || state->code != AUTH_PARSE_ROLES_REPLY) {
 3000       parse_xml_set_error("Unexpected \"failed\" element");
 3001       return;
 3002     }
 3003 
 3004     failed = ALLOC(Roles_reply_failed);
 3005     rr->failed = failed;
 3006 
 3007     roles_reply_failed_attr_tab[0].value = &rr->failed->reason;
 3008     if (parse_xml_attr(roles_reply_failed_attr_tab, attr, &errmsg) == -1) {
 3009       parse_xml_set_error(errmsg);
 3010       return;
 3011     }
 3012 
 3013     parse_xml_push(parse_xml_make_roles_reply_state(AUTH_PARSE_ROLES_REPLY_FAILED,
 3014                                                     (void *) rr));
 3015   }
 3016   else if (streq(el, "common_status")) {
 3017     Common_status *status;
 3018 
 3019     if (parse_xml_top((void **) &state) != PARSE_XML_OK
 3020         || state->code != AUTH_PARSE_ROLES_REPLY) {
 3021       parse_xml_set_error("Unexpected \"common_status\" element");
 3022       return;
 3023     }
 3024 
 3025     status = ALLOC(Common_status);
 3026     status->context = status->code = status->message = NULL;
 3027 
 3028     common_status_attr_tab[0].value = &status->context;
 3029     common_status_attr_tab[1].value = &status->code;
 3030     common_status_attr_tab[2].value = &status->message;
 3031     if (parse_xml_attr(common_status_attr_tab, attr, &errmsg) == -1) {
 3032       parse_xml_set_error(errmsg);
 3033       free(status);
 3034       return;
 3035     }
 3036 
 3037     rr->status = status;
 3038     parse_xml_push(parse_xml_make_roles_reply_state(AUTH_PARSE_ROLES_REPLY_ERROR,
 3039                                                    (void *) rr));
 3040   }
 3041 }
 3042 
 3043 static void
 3044 parse_xml_roles_reply_element_end(void *data, const char *element)
 3045 {
 3046   char *el;
 3047   Roles_reply **rr;
 3048   Parse_xml_roles_reply_state *state;
 3049 
 3050   if (parse_xmlns_name(element, NULL, &el) == -1) {
 3051     parse_xml_set_error(ds_xprintf("Invalid namespace: %s", element));
 3052     return;
 3053   }
 3054 
 3055   rr = (Roles_reply **) data;
 3056 
 3057   if (parse_xml_is_error(NULL))
 3058     return;
 3059 
 3060   if (streq(el, "roles_reply")) {
 3061     if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR
 3062         || state->code != AUTH_PARSE_ROLES_REPLY) {
 3063       parse_xml_set_error("Unexpected \"roles_reply\" terminating element");
 3064       return;
 3065     }
 3066   }
 3067   else if (streq(el, "ok")) {
 3068     if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR
 3069         || state->code != AUTH_PARSE_ROLES_REPLY_OK) {
 3070       parse_xml_set_error("Unexpected \"ok\" terminating element");
 3071       return;
 3072     }
 3073   }
 3074   else if (streq(el, "failed")) {
 3075     if (parse_xml_pop((void **) &state) != PARSE_XML_OK
 3076         || state->code != AUTH_PARSE_ROLES_REPLY_FAILED) {
 3077       parse_xml_set_error("Unexpected \"failed\" terminating element");
 3078       return;
 3079     }
 3080   }
 3081   else if (streq(el, "common_status")) {
 3082     if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR
 3083         || state->code != AUTH_PARSE_ROLES_REPLY_ERROR) {
 3084       parse_xml_set_error("Unexpected \"common_status\" terminating element");
 3085       return;
 3086     }
 3087   }
 3088   else {
 3089     parse_xml_set_error(ds_xprintf("Unknown terminating element: '%s'", el));
 3090     return;
 3091   }
 3092 
 3093 }
 3094 
 3095 int
 3096 parse_xml_roles_reply(char *reply_string, Roles_reply **roles_reply)
 3097 {
 3098   int st;
 3099   Parse_xml_error err;
 3100   XML_Parser p = XML_ParserCreateNS(NULL, XMLNS_SEP_CHAR);
 3101 
 3102   if (p == NULL)
 3103     return(-1);
 3104 
 3105   parse_xml_init("Roles_reply", p);
 3106 
 3107   XML_SetElementHandler(p, parse_xml_roles_reply_element_start,
 3108                         parse_xml_roles_reply_element_end);
 3109 
 3110   *roles_reply = ALLOC(Roles_reply);
 3111   (*roles_reply)->ok = NULL;
 3112   (*roles_reply)->failed = NULL;
 3113   (*roles_reply)->status = NULL;
 3114 
 3115   XML_SetUserData(p, (void *) roles_reply);
 3116 
 3117   st = XML_Parse(p, reply_string, strlen(reply_string), 1);
 3118 
 3119   if (parse_xml_is_not_empty())
 3120     parse_xml_set_error("Unexpected \"roles_reply\" terminating element");
 3121 
 3122   if (parse_xml_is_error(&err) || st == 0) {
 3123     if (err.mesg == NULL) {
 3124       parse_xml_set_error(NULL);
 3125       parse_xml_is_error(&err);
 3126     }
 3127     log_msg((LOG_ERROR_LEVEL, "parse_xml_roles_reply: line %d, pos %d",
 3128              err.line, err.pos));
 3129     if (err.mesg != NULL)
 3130       log_msg((LOG_ERROR_LEVEL, "parse_xml_roles_reply: %s", err.mesg));
 3131     parse_xml_end();
 3132     return(-1);
 3133   }
 3134 
 3135   parse_xml_end();
 3136 
 3137   if (parse_xml_is_not_empty())
 3138     return(-1);
 3139 
 3140   return(0);
 3141 }
 3142 
 3143 void
 3144 emit_roles_reply_ok(FILE *fp, char *role_str)
 3145 {
 3146   Roles_reply rr;
 3147   Roles_reply_ok ok;
 3148 
 3149   rr.ok = &ok;
 3150   rr.failed = NULL;
 3151   rr.status = NULL;
 3152   ok.roles = role_str;
 3153 
 3154   emit_xml_header(fp, "roles_reply");
 3155   fprintf(fp, "%s", make_xml_roles_reply(&rr));
 3156   emit_xml_trailer(fp);
 3157 }
 3158 
 3159 void
 3160 emit_roles_reply_failed(FILE *fp, char *reason)
 3161 {
 3162   Roles_reply rr;
 3163   Roles_reply_failed failed;
 3164 
 3165   rr.ok = NULL;
 3166   rr.failed = &failed;
 3167   rr.status = NULL;
 3168   if (reason == NULL)
 3169     failed.reason = "unknown";
 3170   else
 3171     failed.reason = escape_xml_attribute(reason, '\"');
 3172 
 3173   emit_xml_header(fp, "roles_reply");
 3174   fprintf(fp, "%s", make_xml_roles_reply(&rr));
 3175   emit_xml_trailer(fp);
 3176 }
 3177 
 3178 /**********************************************/
 3179 static Parse_attr_tab auth_reply_prompt_attr_tab[] = {
 3180   { "type",    NULL, ATTR_REQUIRED, NULL, 0 },
 3181   { "label",   NULL, ATTR_IMPLIED,  NULL, 0 },
 3182   { "varname", NULL, ATTR_IMPLIED,  NULL, 0 },
 3183   { NULL,      NULL, ATTR_END,      NULL, 0 }
 3184 };
 3185 
 3186 static Auth_prompt *
 3187 parse_xml_auth_prompt(const char **attr)
 3188 {
 3189   char *errmsg, *type;
 3190   Auth_prompt *prompt;
 3191 
 3192   prompt = ALLOC(Auth_prompt);
 3193   prompt->type = NULL;
 3194   prompt->label = NULL;
 3195   prompt->varname = NULL;
 3196   prompt->next = NULL;
 3197 
 3198   auth_reply_prompt_attr_tab[0].value = &type;
 3199   auth_reply_prompt_attr_tab[1].value = &prompt->label;
 3200   auth_reply_prompt_attr_tab[2].value = &prompt->varname;
 3201   if (parse_xml_attr(auth_reply_prompt_attr_tab, attr, &errmsg) == -1) {
 3202     parse_xml_set_error(errmsg);
 3203     free(prompt);
 3204     return(NULL);
 3205   }
 3206 
 3207   if (!streq(type, "password") && !streq(type, "text")
 3208       && !streq(type, "error") && !streq(type, "label")) {
 3209     parse_xml_set_error("Invalid \"prompt\" attribute");
 3210     free(prompt);
 3211     return(NULL);
 3212   }
 3213   prompt->type = type;
 3214 
 3215   if ((streq(prompt->type, "password") || streq(prompt->type, "text"))
 3216       && prompt->varname == NULL) {
 3217     parse_xml_set_error("Missing \"varname\" attribute");
 3218     free(prompt);
 3219     return(NULL);
 3220   }
 3221 
 3222   if ((streq(prompt->type, "error") || streq(prompt->type, "label"))
 3223       && prompt->label == NULL) {
 3224     parse_xml_set_error("Missing \"label\" attribute");
 3225     free(prompt);
 3226     return(NULL);
 3227   }
 3228 
 3229   return(prompt);
 3230 }
 3231 
 3232 static Parse_attr_tab auth_reply_prompts_attr_tab[] = {
 3233   { "transid", NULL, ATTR_REQUIRED, NULL, 0 },
 3234   { NULL,      NULL, ATTR_END,      NULL, 0 }
 3235 };
 3236 
 3237 static Auth_prompts *
 3238 parse_xml_auth_prompts(const char **attr)
 3239 {
 3240   char *errmsg;
 3241   Auth_prompts *prompts;
 3242 
 3243   prompts = ALLOC(Auth_prompts);
 3244   prompts->transid = NULL;
 3245   prompts->head = NULL;
 3246 
 3247   auth_reply_prompts_attr_tab[0].value = &prompts->transid;
 3248   if (parse_xml_attr(auth_reply_prompts_attr_tab, attr, &errmsg) == -1) {
 3249     parse_xml_set_error(errmsg);
 3250     free(prompts);
 3251     return(NULL);
 3252   }
 3253 
 3254   return(prompts);
 3255 }
 3256 
 3257 typedef struct Parse_xml_auth_reply_state {
 3258   Parse_xml_auth_state_code code;
 3259   union {
 3260     Auth_reply *auth_reply;
 3261     Auth_prompts *prompts;
 3262   } object;
 3263 } Parse_xml_auth_reply_state;
 3264 
 3265 static Parse_xml_auth_reply_state *
 3266 parse_xml_make_auth_reply_state(Parse_xml_auth_state_code code, void *object)
 3267 {
 3268   Parse_xml_auth_reply_state *s;
 3269 
 3270   s = ALLOC(Parse_xml_auth_reply_state);
 3271   s->code = code;
 3272 
 3273   switch (code) {
 3274   case AUTH_PARSE_AUTH_REPLY:
 3275   case AUTH_PARSE_AUTH_REPLY_OK:
 3276   case AUTH_PARSE_AUTH_REPLY_FAILED:
 3277   case AUTH_PARSE_AUTH_REPLY_ERROR:
 3278   case AUTH_PARSE_AUTH_REPLY_ROLES_REPLY:
 3279     s->object.auth_reply = (Auth_reply *) object;
 3280     break;
 3281   case AUTH_PARSE_AUTH_REPLY_PROMPTS:
 3282   case AUTH_PARSE_AUTH_REPLY_PROMPT:
 3283     s->object.prompts = (Auth_prompts *) object;
 3284     break;
 3285   default:
 3286     /* XXX ??? */
 3287     return(NULL);
 3288   }
 3289 
 3290   return(s);
 3291 }
 3292 
 3293 static Parse_attr_tab auth_reply_ok_attr_tab[] = {
 3294   { "username",   NULL, ATTR_REQUIRED,  NULL, 0 },
 3295   { "lifetime",   NULL, ATTR_IMPLIED,   NULL, 0 },
 3296   { XMLNS_XSI,    NULL, ATTR_IGNORE_NS, NULL, 0 },
 3297   { XMLNS_PREFIX, NULL, ATTR_IGNORE_NS, NULL, 0 },
 3298   { NULL,         NULL, ATTR_END,       NULL, 0 }
 3299 };
 3300 
 3301 static Parse_attr_tab auth_reply_failed_attr_tab[] = {
 3302   { "username",     NULL, ATTR_IMPLIED,   NULL, 0 },
 3303   { "redirect_url", NULL, ATTR_IMPLIED,   NULL, 0 },
 3304   { XMLNS_XSI,      NULL, ATTR_IGNORE_NS, NULL, 0 },
 3305   { XMLNS_PREFIX,   NULL, ATTR_IGNORE_NS, NULL, 0 },
 3306   { NULL,           NULL, ATTR_END,       NULL, 0 }
 3307 };
 3308 
 3309 static void
 3310 parse_xml_auth_reply_element_start(void *data, const char *element,
 3311                                    const char **attr)
 3312 {
 3313   char *el, *errmsg;
 3314   Auth_reply *ar, **auth_reply;
 3315   Parse_xml_auth_reply_state *state;
 3316 
 3317   auth_reply = (Auth_reply **) data;
 3318 
 3319   if (parse_xml_top((void **) &state) == PARSE_XML_OK
 3320       && (state->code == AUTH_PARSE_ROLES_REPLY
 3321           || state->code == AUTH_PARSE_ROLES_REPLY_OK
 3322           || state->code == AUTH_PARSE_ROLES_REPLY_FAILED
 3323           || state->code == AUTH_PARSE_ROLES_REPLY_ERROR)) {
 3324     Roles_reply **rr;
 3325 
 3326     rr = &(*auth_reply)->ok->roles_reply;
 3327     parse_xml_roles_reply_element_start((void *) rr, element, attr);
 3328     return;
 3329   }
 3330 
 3331   if (parse_xmlns_name(element, NULL, &el) == -1) {
 3332     parse_xml_set_error(ds_xprintf("Invalid namespace: %s", element));
 3333     return;
 3334   }
 3335 
 3336   if (parse_xml_is_error(NULL))
 3337     return;
 3338 
 3339   if (streq(el, "auth_reply")) {
 3340     if (parse_xml_is_not_empty()) {
 3341       parse_xml_set_error("Unexpected \"auth_reply\" element");
 3342       return;
 3343     }
 3344     if (parse_xml_attr(NULL, attr, &errmsg) == -1) {
 3345       parse_xml_set_error(errmsg);
 3346       return;
 3347     }
 3348 
 3349     ar = ALLOC(Auth_reply);
 3350     ar->ok = NULL;
 3351     ar->failed = NULL;
 3352     ar->status = NULL;
 3353     ar->prompts = NULL;
 3354     *auth_reply = ar;
 3355 
 3356     parse_xml_push(parse_xml_make_auth_reply_state(AUTH_PARSE_AUTH_REPLY,
 3357                                                    (void *) ar));
 3358   }
 3359   else if (streq(el, "ok")) {
 3360     Auth_reply_ok *ok;
 3361 
 3362     if (parse_xml_top((void **) &state) != PARSE_XML_OK
 3363         || state->code != AUTH_PARSE_AUTH_REPLY) {
 3364       parse_xml_set_error("Unexpected \"ok\" element");
 3365       return;
 3366     }
 3367     ar = state->object.auth_reply;
 3368 
 3369     ok = ALLOC(Auth_reply_ok);
 3370     ok->username = NULL;
 3371     ok->lifetime = NULL;
 3372     ok->roles_reply = NULL;
 3373 
 3374     auth_reply_ok_attr_tab[0].value = &ok->username;
 3375     auth_reply_ok_attr_tab[1].value = &ok->lifetime;
 3376     if (parse_xml_attr(auth_reply_ok_attr_tab, attr, &errmsg) == -1) {
 3377       parse_xml_set_error(errmsg);
 3378       free(ok);
 3379       return;
 3380     }
 3381 
 3382     ar->ok = ok;
 3383     parse_xml_push(parse_xml_make_auth_reply_state(AUTH_PARSE_AUTH_REPLY_OK,
 3384                                                    (void *) ar));
 3385   }
 3386   else if (streq(el, "roles_reply")) {
 3387     Roles_reply *rr;
 3388 
 3389     if (parse_xml_top((void **) &state) != PARSE_XML_OK
 3390         || state->code != AUTH_PARSE_AUTH_REPLY_OK) {
 3391       parse_xml_set_error("Unexpected \"roles_reply\" element");
 3392       return;
 3393     }
 3394     ar = state->object.auth_reply;
 3395 
 3396     rr = ALLOC(Roles_reply);
 3397     rr->ok = NULL;
 3398     rr->failed = NULL;
 3399     rr->status = NULL;
 3400     ar->ok->roles_reply = rr;
 3401     parse_xml_roles_reply_element_start((void *) &ar->ok->roles_reply,
 3402                                         element, attr);
 3403   }
 3404   else if (streq(el, "failed")) {
 3405     Auth_reply_failed *failed;
 3406 
 3407     if (parse_xml_top((void **) &state) != PARSE_XML_OK
 3408         || state->code != AUTH_PARSE_AUTH_REPLY) {
 3409       parse_xml_set_error("Unexpected \"failed\" element");
 3410       return;
 3411     }
 3412     ar = state->object.auth_reply;
 3413 
 3414     failed = ALLOC(Auth_reply_failed);
 3415     failed->username = NULL;
 3416     failed->redirect_url = NULL;
 3417 
 3418     auth_reply_failed_attr_tab[0].value = &failed->username;
 3419     auth_reply_failed_attr_tab[1].value = &failed->redirect_url;
 3420     if (parse_xml_attr(auth_reply_failed_attr_tab, attr, &errmsg) == -1) {
 3421       parse_xml_set_error(errmsg);
 3422       free(failed);
 3423       return;
 3424     }
 3425 
 3426     ar->failed = failed;
 3427     parse_xml_push(parse_xml_make_auth_reply_state(AUTH_PARSE_AUTH_REPLY_FAILED,
 3428                                                    (void *) ar));
 3429   }
 3430   else if (streq(el, "common_status")) {
 3431     Common_status *status;
 3432 
 3433     if (parse_xml_top((void **) &state) != PARSE_XML_OK
 3434         || state->code != AUTH_PARSE_AUTH_REPLY) {
 3435       parse_xml_set_error("Unexpected \"common_status\" element");
 3436       return;
 3437     }
 3438     ar = state->object.auth_reply;
 3439 
 3440     status = ALLOC(Common_status);
 3441     status->context = status->code = status->message = NULL;
 3442 
 3443     common_status_attr_tab[0].value = &status->context;
 3444     common_status_attr_tab[1].value = &status->code;
 3445     common_status_attr_tab[2].value = &status->message;
 3446     if (parse_xml_attr(common_status_attr_tab, attr, &errmsg) == -1) {
 3447       parse_xml_set_error(errmsg);
 3448       free(status);
 3449       return;
 3450     }
 3451 
 3452     ar->status = status;
 3453     parse_xml_push(parse_xml_make_auth_reply_state(AUTH_PARSE_AUTH_REPLY_ERROR,
 3454                                                    (void *) ar));
 3455   }
 3456   else if (streq(el, "prompts")) {
 3457     Auth_prompts *prompts;
 3458 
 3459     if (parse_xml_top((void **) &state) != PARSE_XML_OK
 3460         || state->code != AUTH_PARSE_AUTH_REPLY) {
 3461       parse_xml_set_error("Unexpected \"prompts\" element");
 3462       return;
 3463     }
 3464     ar = state->object.auth_reply;
 3465 
 3466     if ((prompts = parse_xml_auth_prompts(attr)) == NULL)
 3467       return;
 3468     ar->prompts = prompts;
 3469 
 3470     parse_xml_push(parse_xml_make_auth_reply_state(AUTH_PARSE_AUTH_REPLY_PROMPTS,
 3471                                                    (void *) prompts));
 3472   }
 3473   else if (streq(el, "prompt")) {
 3474     Auth_prompt **ppr, *prompt;
 3475     Auth_prompts *prs;
 3476 
 3477     if (parse_xml_top((void **) &state) != PARSE_XML_OK
 3478         || state->code != AUTH_PARSE_AUTH_REPLY_PROMPTS) {
 3479       parse_xml_set_error("Unexpected \"prompt\" element");
 3480       return;
 3481     }
 3482     prs = state->object.prompts;
 3483     if ((prompt = parse_xml_auth_prompt(attr)) != NULL) {
 3484       for (ppr = &prs->head; *ppr != NULL; ppr = &(*ppr)->next)
 3485         ;
 3486       *ppr = prompt;
 3487     }
 3488     parse_xml_push(parse_xml_make_auth_reply_state(AUTH_PARSE_AUTH_REPLY_PROMPT,
 3489                                                    (void *) prs));
 3490   }
 3491   else {
 3492     parse_xml_set_error(ds_xprintf("Unknown starting element: '%s'", el));
 3493     return;
 3494   }
 3495 }
 3496 
 3497 static void
 3498 parse_xml_auth_reply_element_end(void *data, const char *element)
 3499 {
 3500   char *el;
 3501   Auth_reply **ar;
 3502   Parse_xml_auth_reply_state *state;
 3503 
 3504   if (parse_xml_top((void **) &state) == PARSE_XML_OK
 3505       && (state->code == AUTH_PARSE_ROLES_REPLY
 3506           || state->code == AUTH_PARSE_ROLES_REPLY_OK
 3507           || state->code == AUTH_PARSE_ROLES_REPLY_FAILED
 3508           || state->code == AUTH_PARSE_ROLES_REPLY_ERROR)) {
 3509     parse_xml_roles_reply_element_end(data, element);
 3510     return;
 3511   }
 3512 
 3513   if (parse_xmlns_name(element, NULL, &el) == -1) {
 3514     parse_xml_set_error(ds_xprintf("Invalid namespace: %s", element));
 3515     return;
 3516   }
 3517 
 3518   ar = (Auth_reply **) data;
 3519 
 3520   if (parse_xml_is_error(NULL))
 3521     return;
 3522 
 3523   if (streq(el, "auth_reply")) {
 3524     if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR
 3525         || state->code != AUTH_PARSE_AUTH_REPLY || parse_xml_is_not_empty()) {
 3526       parse_xml_set_error("Unexpected \"auth_reply\" terminating element");
 3527       return;
 3528     }
 3529     *ar = state->object.auth_reply;
 3530   }
 3531   else if (streq(el, "ok")) {
 3532     if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR
 3533         || state->code != AUTH_PARSE_AUTH_REPLY_OK) {
 3534       parse_xml_set_error("Unexpected \"ok\" terminating element");
 3535       return;
 3536     }
 3537   }
 3538   else if (streq(el, "roles_reply")) {
 3539     if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR
 3540         || state->code != AUTH_PARSE_AUTH_REPLY_ROLES_REPLY) {
 3541       parse_xml_set_error("Unexpected \"roles_reply\" terminating element");
 3542       return;
 3543     }
 3544     parse_xml_roles_reply_element_end(data, element);
 3545   }
 3546   else if (streq(el, "failed")) {
 3547     if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR
 3548         || state->code != AUTH_PARSE_AUTH_REPLY_FAILED) {
 3549       parse_xml_set_error("Unexpected \"failed\" terminating element");
 3550       return;
 3551     }
 3552   }
 3553   else if (streq(el, "error")) {
 3554     if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR
 3555         || state->code != AUTH_PARSE_AUTH_REPLY_ERROR) {
 3556       parse_xml_set_error("Unexpected \"error\" terminating element");
 3557       return;
 3558     }
 3559   }
 3560   else if (streq(el, "prompt")) {
 3561     if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR
 3562         || state->code != AUTH_PARSE_AUTH_REPLY_PROMPT) {
 3563       parse_xml_set_error("Unexpected \"prompt\" terminating element");
 3564       return;
 3565     }
 3566   }
 3567   else if (streq(el, "prompts")) {
 3568     Auth_prompts *prompts;
 3569 
 3570     if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR
 3571         || state->code != AUTH_PARSE_AUTH_REPLY_PROMPTS) {
 3572       parse_xml_set_error("Unexpected \"prompts\" terminating element");
 3573       return;
 3574     }
 3575     if ((prompts = state->object.prompts) == NULL
 3576         || prompts->head == NULL) {
 3577       parse_xml_set_error("No \"prompt\" elements");
 3578       return;
 3579     }
 3580   }
 3581   else {
 3582     parse_xml_set_error(ds_xprintf("Unknown terminating element: '%s'", el));
 3583     return;
 3584   }
 3585 }
 3586 
 3587 int
 3588 parse_xml_auth_reply(char *reply_string, Auth_reply **auth_reply)
 3589 {
 3590   int st;
 3591   Parse_xml_error err;
 3592   XML_Parser p = XML_ParserCreateNS(NULL, XMLNS_SEP_CHAR);
 3593 
 3594   if (p == NULL)
 3595     return(-1);
 3596 
 3597   parse_xml_init("Auth_reply", p);
 3598 
 3599   XML_SetElementHandler(p, parse_xml_auth_reply_element_start,
 3600                         parse_xml_auth_reply_element_end);
 3601   XML_SetUserData(p, (void *) auth_reply);
 3602 
 3603   st = XML_Parse(p, reply_string, strlen(reply_string), 1);
 3604 
 3605   if (parse_xml_is_error(&err) || st == 0) {
 3606     if (err.mesg == NULL) {
 3607       parse_xml_set_error(NULL);
 3608       parse_xml_is_error(&err);
 3609     }
 3610     log_msg((LOG_ERROR_LEVEL, "parse_xml_auth_reply: line %d, pos %d",
 3611              err.line, err.pos));
 3612     if (err.mesg != NULL)
 3613       log_msg((LOG_ERROR_LEVEL, "parse_xml_auth_reply: %s", err.mesg));
 3614     log_msg((LOG_ERROR_LEVEL,
 3615              "parse_xml_auth_reply: Input: %s", reply_string));
 3616     parse_xml_end();
 3617     return(-1);
 3618   }
 3619 
 3620   parse_xml_end();
 3621 
 3622   if (parse_xml_is_not_empty())
 3623     return(-1);
 3624 
 3625   return(0);
 3626 }
 3627 
 3628 /**********************************************/
 3629 
 3630 /*
 3631  * Return 1 if the expiry time is ok (and if REMAINING is non-NULL, set it
 3632  * to the number of seconds until the expiry date), 0 if it has passed, and -1
 3633  * if the date is invalid or an error occurs.
 3634  */
 3635 int
 3636 verify_expiration(time_t expires_secs, int *remaining)
 3637 {
 3638   time_t now;
 3639 
 3640   time(&now);
 3641 
 3642   if (expires_secs == (time_t) -1)
 3643     return(-1);
 3644 
 3645   if (remaining != NULL)
 3646     *remaining = (int) (expires_secs - now);
 3647 
 3648   if (expires_secs > now)
 3649     return(1);
 3650 
 3651   return(0);
 3652 }
 3653 
 3654 static const char *invalid_username_chars = ",:+()~<>=|\\/\"";
 3655 static const char *reserved_username_chars = "*";
 3656 
 3657 /*
 3658  * At present, there are few restrictions on the syntax of a username.
 3659  * It must be at least one character long and consist of ASCII
 3660  * alphabetics, digits, and certain punctuation characters.
 3661  * Note that "auth", "unauth", etc. are perfectly valid usernames
 3662  * because they really mean "FOO:auth", which is different than "auth".
 3663  *
 3664  * NOTE: this function accepts usernames that are valid for authentication
 3665  * purposes AND for internal purposes.  For example, the "*" is valid
 3666  * in the general case, but invalid wrt user authentication.
 3667  * See is_valid_auth_username().
 3668  *
 3669  * We want to be quite liberal, so that typical native login names will be
 3670  * acceptable.  FreeBSD, which may or may not be a typical *nix, disallows
 3671  * the following characters in a username, at least wrt pw(8):
 3672  *   o any char in the set  " ,\t:+&#%$^()!@~*?<>=|\\/\""
 3673  *   o any char less than 040
 3674  *   o any character >= 0177
 3675  *   o an initial '-'
 3676  *
 3677  * The adduser(8) command has different rules: usernames consist of characters
 3678  * in the set "a-z0-9_-" and may not begin with a '-'.
 3679  *
 3680  * Linux distributions seem to use useradd(8), at least some versions of which
 3681  * require usernames to start with a letter, and not contain colons, commas,
 3682  * newlines, or any non-printable characters.
 3683  *
 3684  * MS documentation says that a Windows NT/2000 username
 3685  * ("Logon Name") "can contain any uppercase or lowercase characters except the
 3686  * following":
 3687  *     " / \ [ ] : ; | = , + * ? < >
 3688  * [Note the less-than-mprecise description...]
 3689  * A user name cannot consist solely of periods and spaces.
 3690  * Maximum length is 104 characters, however the maximum sAMAccountName
 3691  * is apparently 20 characters.
 3692  * https://msdn.microsoft.com/en-us/library/bb726984.aspx
 3693  * https://www.microsoft.com/resources/documentation/windowsnt/4/server/proddocs/en-us/concept/xcp02.mspx
 3694  *
 3695  * RFC 822 (Appendix D) defines quite a rich syntax that is commonly used for
 3696  * email addresses.  Ignoring various "quoted" variations, it disallows:
 3697  *   o any character less than 040
 3698  *   o any character >= 0177
 3699  *   o any character in the set " ()<>,;:\\\"[]"   
 3700  * http://www.rfc-editor.org/rfc/rfc822.txt
 3701  *
 3702  * The JURISDICTION_NAME_SEP_CHAR character (a colon) may not appear.
 3703  * Because the username appears in contexts where weird characters might
 3704  * cause problems, such as in a cookie name, it's probably
 3705  * wise to avoid potentially problematic characters.
 3706  */
 3707 int
 3708 is_valid_username(const char *username)
 3709 {
 3710   const char *p;
 3711 
 3712   /* There must be at least one character */
 3713   if (username == NULL || username[0] == '\0')
 3714     return(0);
 3715 
 3716   /* A single reserved character (e.g., "*") is always invalid. */
 3717   if (username[1] == '\0' && strchr(reserved_username_chars, (int) *username))
 3718     return(0);
 3719 
 3720   for (p = username; *p != '\0'; p++) {
 3721     if (strchr(invalid_username_chars, (int) *p) != NULL)
 3722       return(0);
 3723     if (*p <= 040 || *p >= 0177)
 3724       return(0);
 3725   }
 3726 
 3727   return(1);
 3728 }
 3729 
 3730 /*
 3731  * The dacs_authenticate service must only allow usernames that are
 3732  * acceptable to this predicate.  Some usernames are reserved for internal
 3733  * use and we don't want them to be confused with the names of "real" users,
 3734  * so they are constructed from characters not allowed by this function.
 3735  */
 3736 int
 3737 is_valid_auth_username(const char *username)
 3738 {
 3739   const char *p;
 3740 
 3741   if (!is_valid_username(username))
 3742     return(0);
 3743 
 3744   for (p = username; *p != '\0'; p++) {
 3745     if (strchr(reserved_username_chars, (int) *p) != NULL)
 3746       return(0);
 3747   }
 3748 
 3749   return(1);
 3750 }
 3751 
 3752 int
 3753 is_valid_role_str(char *role_str)
 3754 {
 3755   unsigned int max_len;
 3756   char *p;
 3757 
 3758   if (role_str == NULL || role_str[0] == '\0'
 3759       || role_str[0] == '/' || role_str[0] == ',') {
 3760     log_msg((LOG_ERROR_LEVEL, "Empty or initially invalid role string"));
 3761     return(0);
 3762   }
 3763 
 3764   /* Could use strspn(), but that would be slower. */
 3765   for (p = role_str; *p != '\0'; p++) {
 3766     if (!isalpha((int) *p) && !isdigit((int) *p) && *p != '-' && *p != '_'
 3767         && *p != '/' && *p != ',') {
 3768       log_msg((LOG_ERROR_LEVEL, "Invalid character in role string"));
 3769       return(0);
 3770     }
 3771   }
 3772 
 3773   if (*(p - 1) == '/' || *(p - 1) == ',') {
 3774     log_msg((LOG_ERROR_LEVEL, "Invalid final character in role string"));
 3775     return(0);
 3776   }
 3777 
 3778   if (conf_val_uint(CONF_ROLE_STRING_MAX_LENGTH, &max_len) != 1)
 3779     max_len = AUTH_MAX_ROLE_STR_LENGTH;
 3780   if (strlen(role_str) > max_len) {
 3781     /* XXX We might instead truncate the list at the last valid element. */
 3782     log_msg((LOG_ERROR_LEVEL,
 3783              "Invalid role string: exceeds maximum length of %d bytes",
 3784              max_len));
 3785     return(0);
 3786   }
 3787 
 3788   return(1);
 3789 }
 3790 
 3791 /*
 3792  * Lookup and return the role string for USERNAME.
 3793  * If an error occurs, return NULL.
 3794  * If no error occurs but USERNAME has no roles, return the empty string.
 3795  */
 3796 char *
 3797 get_role_string(char *vfs_uri, char *item_type, char *username, char *fs)
 3798 {
 3799   char *role_str;
 3800   Vfs_handle *h;
 3801 
 3802   if (!is_valid_username(username))
 3803     return(NULL);
 3804 
 3805   if (vfs_uri != NULL) {
 3806     if ((h = vfs_open_uri(vfs_uri)) == NULL) {
 3807       log_msg((LOG_DEBUG_LEVEL, "Can't open vfs_uri \"%s\"", vfs_uri));
 3808       return(NULL);
 3809     }
 3810   }
 3811   else if (item_type != NULL) {
 3812     if ((h = vfs_open_item_type(item_type)) == NULL) {
 3813       log_msg((LOG_DEBUG_LEVEL, "Can't open item type \"%s\"", item_type));
 3814       return(NULL);
 3815     }
 3816   }
 3817   else
 3818     return(NULL);
 3819 
 3820   if (fs != NULL && vfs_control(h, VFS_SET_FIELD_SEP, fs) == -1)
 3821     return(NULL);
 3822 
 3823   role_str = NULL;
 3824   if (vfs_get(h, username, (void **) &role_str, NULL) == -1) {
 3825     log_msg((LOG_DEBUG_LEVEL,
 3826              "Lookup of roles for username \"%s\" in \"%s\" failed",
 3827              username, item_type != NULL ? item_type : vfs_uri));
 3828     if (h->error_num != 0)
 3829       role_str = NULL;
 3830     else
 3831       role_str = "";
 3832   }
 3833   else {
 3834     if (role_str == NULL || role_str[0] == '\0')
 3835       role_str = "";
 3836   }
 3837 
 3838   vfs_close(h);
 3839 
 3840   return(role_str);
 3841 }
 3842 
 3843 Dsvec *
 3844 get_roles(char *item_type, char *username, char *fs)
 3845 {
 3846   char *role_str;
 3847   Dsvec *dsv;
 3848 
 3849   if ((role_str = get_role_string(NULL, item_type, username, fs)) == NULL)
 3850     return(NULL);
 3851 
 3852   dsv = strsplit(role_str, ",", 0);
 3853   return(dsv);
 3854 }
 3855 
 3856 void
 3857 set_valid_for(Credentials *credentials, char *valid_for)
 3858 {
 3859   Credentials *cr;
 3860 
 3861   for (cr = credentials; cr != NULL; cr = cr->next)
 3862     cr->valid_for = strdup(valid_for);
 3863     
 3864 }
 3865 
 3866 char *
 3867 is_valid_valid_for(const char *valid_for)
 3868 {
 3869 
 3870   if (strcaseeq(valid_for, AUTH_VALID_FOR_ACS))
 3871     return(AUTH_VALID_FOR_ACS);
 3872   if (strcaseeq(valid_for, AUTH_VALID_FOR_CHAINING))
 3873     return(AUTH_VALID_FOR_CHAINING);
 3874   if (strcaseeq(valid_for, AUTH_VALID_FOR_IDENT))
 3875     return(AUTH_VALID_FOR_IDENT);
 3876   if (strcaseeq(valid_for, AUTH_VALID_FOR_TRANSFER))
 3877     return(AUTH_VALID_FOR_TRANSFER);
 3878 
 3879   return(NULL);
 3880 }
 3881 
 3882 /*
 3883  * Depending on the VERIFY_IP directive, check if the IP address in the
 3884  * credentials (the apparent IP address of the host from which the user's
 3885  * authentication request came) matches ACTUAL_IP_ADDRESS (the apparent
 3886  * IP address of a subsequent user request).
 3887  * Return 1 if the two addresses match, 0 if no matching was required,
 3888  * and -1 if they do not match or an error occurs.
 3889  */
 3890 int
 3891 validate_credentials_ip(Credentials *cr, char *actual_ip_address,
 3892                         char **errmsg)
 3893 {
 3894   int st;
 3895   char *msg, *verify_ip;
 3896 
 3897   st = 0;
 3898   if (actual_ip_address != NULL && cr->auth_style != AUTH_STYLE_ADMIN) {
 3899     verify_ip = conf_val(CONF_VERIFY_IP);
 3900 
 3901     if (strcaseeq(verify_ip, "yes")) {
 3902       if (!streq(cr->ip_address, actual_ip_address)) {
 3903         if (errmsg != NULL)
 3904           *errmsg = "Invalid credentials: source IP address invalid";
 3905         st = -1;
 3906       }
 3907       else {
 3908         st = 1;
 3909         log_msg((LOG_TRACE_LEVEL,
 3910                  "IP address of credentials ok: exact match"));
 3911       }
 3912     }
 3913     else if (!strcaseeq(verify_ip, "no")) {
 3914       msg = NULL;
 3915       if ((st = is_from_address(verify_ip, actual_ip_address, NULL, &msg))
 3916           != 1) {
 3917         if (errmsg != NULL && msg != NULL)
 3918           *errmsg = ds_xprintf("Invalid credentials: %s", msg);
 3919         st = -1;
 3920       }
 3921       else {
 3922         st = 1;
 3923         log_msg((LOG_TRACE_LEVEL,
 3924                  "IP address of credentials ok: matched \"%s\"", verify_ip));
 3925       }
 3926     }
 3927   }
 3928 
 3929   if (st == 0)
 3930     log_msg((LOG_TRACE_LEVEL,
 3931              "IP address of credentials was not validated"));
 3932 
 3933   return(st);
 3934 }
 3935 
 3936 /*
 3937  * Validate credentials C, returning 0 if they're ok, -1 otherwise.
 3938  * This involves optionally comparing the IP address against
 3939  * ACTUAL_IP_ADDRESS, and checking the version and expiration date.
 3940  */
 3941 int
 3942 validate_credentials(Credentials *cr, char *actual_ip_address, char **errmsg)
 3943 {
 3944   int st;
 3945   int remaining_secs;
 3946 
 3947   if (!conf_val_eq(CONF_ACCEPT_ALIEN_CREDENTIALS, "yes")) {
 3948     if (!name_eq(cr->federation, conf_val(CONF_FEDERATION_NAME),
 3949                  DACS_NAME_CMP_CONFIG)) {
 3950       log_msg((LOG_INFO_LEVEL, "Ignoring credentials from federation \"%s\"",
 3951                cr->federation));
 3952       if (errmsg != NULL)
 3953         *errmsg = "Invalid credentials: cannot accept alien credentials";
 3954       return(-1);
 3955     }
 3956   }
 3957   else if (!is_valid_federation_name(cr->federation)) {
 3958     if (errmsg != NULL)
 3959       *errmsg = "Invalid credentials: invalid federation name syntax";
 3960     return(-1);
 3961   }
 3962 
 3963   if (validate_credentials_ip(cr, actual_ip_address, errmsg) == -1)
 3964     return(-1);
 3965 
 3966   if (cr->version == NULL || !is_compatible_dacs_version(cr->version)) {
 3967     if (errmsg != NULL)
 3968       *errmsg = "Invalid credentials: version incompatibility";
 3969     return(-1);
 3970   }
 3971 
 3972   if ((st = verify_expiration(cr->expires_secs, &remaining_secs)) == -1) {
 3973     if (errmsg != NULL)
 3974       *errmsg = "Invalid credentials: invalid expiry date format";
 3975     return(-1);
 3976   }
 3977   else if (st == 0) {
 3978     if (errmsg != NULL)
 3979       *errmsg = ds_xprintf("Invalid credentials: credentials expired %d secs ago",
 3980                            -remaining_secs);
 3981     return(-1);
 3982   }
 3983   log_msg((LOG_TRACE_LEVEL, "Valid credentials expire in %d secs",
 3984            remaining_secs));
 3985 
 3986   if ((conf_val(CONF_VERIFY_UA) == NULL
 3987        || conf_val_eq(CONF_VERIFY_UA, "yes")) && cr->ua_hash != NULL) {
 3988     char *ua_hash, *ua_str;
 3989 
 3990     if ((ua_str = getenv("HTTP_USER_AGENT")) != NULL) {
 3991       ua_hash = make_ua_hash(ua_str);
 3992       if (!streq(ua_hash, cr->ua_hash)) {
 3993         if (errmsg != NULL)
 3994           *errmsg = "Invalid credentials: user agent mismatch";
 3995         return(-1);
 3996       }
 3997     }
 3998     else if (cr->ua_hash[0] != '\0') {
 3999       if (errmsg != NULL)
 4000         *errmsg = "Invalid credentials: user agent mismatch";
 4001       return(-1);
 4002     }
 4003     log_msg((LOG_TRACE_LEVEL, "ua_hashes match"));
 4004   }
 4005 
 4006   return(0);
 4007 }
 4008 
 4009 /*
 4010  * Convert from the external "cookie value" form to internal credentials.
 4011  * This involves decoding the cookie value, decrypting, XML parsing, and
 4012  * validating the credentials.
 4013  * If -1 is returned, the caller should not use these credentials.
 4014  */
 4015 int
 4016 cookie_value_to_credentials(char *cookie_value, char *remote_addr,
 4017                             Credentials **credentials)
 4018 {
 4019   char *errmsg;
 4020   unsigned int decrypted_cookie_len, encrypted_cookie_len;
 4021   unsigned char *decrypted_cookie, *encrypted_cookie;
 4022   Credentials *cr;
 4023   Crypt_keys *ck;
 4024 
 4025   if (stra64b(cookie_value, &encrypted_cookie, &encrypted_cookie_len)
 4026       == NULL) {
 4027     log_msg((LOG_ERROR_LEVEL, "Cookie unpacking failed"));
 4028     return(-1);
 4029   }
 4030 
 4031   ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
 4032   if (crypto_decrypt_string(NULL, ck, encrypted_cookie, encrypted_cookie_len,
 4033                             &decrypted_cookie, &decrypted_cookie_len) == -1) {
 4034     crypt_keys_free(ck);
 4035     log_msg((LOG_ERROR_LEVEL, "Cookie decryption failed"));
 4036     return(-1);
 4037   }
 4038   crypt_keys_free(ck);
 4039 
 4040   log_msg((LOG_TRACE_LEVEL, "decrypted_cookie=\"%s\"", decrypted_cookie));
 4041 
 4042   if (parse_xml_credentials((char *) decrypted_cookie, credentials) == -1) {
 4043     log_msg((LOG_ERROR_LEVEL, "XML credentials parse failed"));
 4044     return(-1);
 4045   }
 4046 
 4047   cr = *credentials;
 4048 
 4049   errmsg = NULL;
 4050   if (validate_credentials(cr, remote_addr, &errmsg) == -1) {
 4051     if (trace_level) {
 4052       int remaining;
 4053 
 4054       if (verify_expiration(cr->expires_secs, &remaining) == 0)
 4055         log_msg((LOG_TRACE_LEVEL, "expires_date=%lu, remaining=%d",
 4056                  cr->expires_secs, remaining));
 4057     }
 4058     if (errmsg != NULL)
 4059       log_msg((LOG_NOTICE_LEVEL, "Validation failed: %s", errmsg));
 4060     log_msg((LOG_NOTICE_LEVEL, "Invalid or expired credentials found"));
 4061     if (cr->home_jurisdiction != NULL && cr->username != NULL)
 4062       log_msg((LOG_NOTICE_LEVEL, "Discarding credentials: %s",
 4063                auth_identity_from_credentials(cr)));
 4064     return(-1);
 4065   }
 4066 
 4067   log_msg((LOG_DEBUG_LEVEL, "Valid credentials for %s:%s",
 4068            cr->home_jurisdiction, cr->username));
 4069 
 4070   return(0);
 4071 }
 4072 
 4073 /*
 4074  * Convert a parsed external cookie to internal credentials and validate them.
 4075  * If the cookie is syntactically ok, then CREDENTIALS will be made to
 4076  * point to the parsed credentials, otherwise it will be set to NULL.
 4077  * If they are syntactically ok, proceed to check if they are valid.
 4078  * Return -1 if there's a problem, 0 otherwise.
 4079  */
 4080 int
 4081 cookie_to_credentials(Cookie *cookie, char *remote_addr,
 4082                       Credentials **credentials)
 4083 {
 4084   Credentials *cr;
 4085 
 4086   *credentials = NULL;
 4087   if (cookie_value_to_credentials(cookie->value, remote_addr, credentials)
 4088       == -1)
 4089     return(-1);
 4090 
 4091   cr = *credentials;
 4092 
 4093   /* Check that the cookie name matches the enclosed credentials. */
 4094   if (!name_eq(cookie->parsed_name->federation, cr->federation,
 4095                DACS_NAME_CMP_CONFIG)
 4096       || (*cookie->parsed_name->jurisdiction != '\0'
 4097           && !name_eq(cookie->parsed_name->jurisdiction, cr->home_jurisdiction,
 4098                       DACS_NAME_CMP_CONFIG))
 4099       || (*cookie->parsed_name->username != '\0'
 4100           && !name_eq(cookie->parsed_name->username, cr->username,
 4101                       DACS_NAME_CMP_CONFIG))) {
 4102     log_msg((LOG_ALERT_LEVEL, "Cookie name doesn't match credentials!"));
 4103     log_msg((LOG_ALERT_LEVEL,
 4104              "Cookie name is: %s, credentials say %s:%s:%s",
 4105              cookie->name, cr->federation, cr->home_jurisdiction,
 4106              cr->username));
 4107     return(-1);
 4108   }
 4109 
 4110   cr->cookie_name = strdup(cookie->name);
 4111 
 4112   log_msg((LOG_DEBUG_LEVEL, "Valid cookie for %s:%s",
 4113            cr->home_jurisdiction, cr->username));
 4114 
 4115   return(0);
 4116 }
 4117 
 4118 /*
 4119  * Traverse a list of cookies, extract the valid credentials from them and
 4120  * put those credentials into a list, setting CREDENTIALS to point to it.
 4121  * Return the number of valid credentials.
 4122  */
 4123 int
 4124 get_valid_credentials(Cookie *cookies, char *remote_addr,
 4125                       int valid_for_acs, Credentials **credentials)
 4126 {
 4127   int n, valid;
 4128   Cookie *c;
 4129   Credentials *cr, *cred, *prev;
 4130 
 4131   n = 0;
 4132   prev = NULL;
 4133   log_msg((LOG_TRACE_LEVEL, "valid_for_acs=%d", valid_for_acs));
 4134 
 4135   *credentials = NULL;
 4136   for (c = cookies; c != NULL; c = c->next) {
 4137     /* Weed out non-auth DACS cookies. */
 4138     if (!is_auth_cookie_name(c->name))
 4139       continue;
 4140 
 4141     if (cookie_to_credentials(c, remote_addr, &cr) == -1)
 4142       continue;
 4143 
 4144     /*
 4145      * Reject all credentials if this identity has more than one set
 4146      * of credentials.
 4147      * Also reject all credentials if two cookies with the same name
 4148      * are found.
 4149      * This may be harsh but better safe than sorry.  An alternative might
 4150      * be to only ignore an identity that is duplicated.
 4151      */
 4152     for (cred = *credentials; cred != NULL; cred = cred->next) {
 4153       if (streq(cred->federation, cr->federation)
 4154           && streq(cred->home_jurisdiction, cr->home_jurisdiction)
 4155           && streq(cred->username, cr->username)) {
 4156         log_msg((LOG_ALERT_LEVEL, "Duplicate identity found: %s",
 4157                  auth_identity_from_credentials(cr)));
 4158         *credentials = NULL;
 4159         return(-1);
 4160       }
 4161 
 4162       if (streq(cred->cookie_name, cr->cookie_name)) {
 4163         log_msg((LOG_ALERT_LEVEL, "Duplicate cookie name found: %s",
 4164                  cr->cookie_name));
 4165         *credentials = NULL;
 4166         return(-1);
 4167       }
 4168     }
 4169 
 4170     if (valid_for_acs) {
 4171       /*
 4172        * The caller is only interested in credentials that are valid for
 4173        * access control purposes.
 4174        * They must be so marked, or DACS must be configured to treat as
 4175        * valid credentials marked as AUTH_VALID_FOR_CHAINING.
 4176        */
 4177       valid = 0;
 4178 
 4179       if (cr->valid_for != NULL) {
 4180         if (strcaseeq(cr->valid_for, AUTH_VALID_FOR_ACS)) {
 4181           valid = 1;
 4182           log_msg((LOG_TRACE_LEVEL, "Received credentials are valid for ACS"));
 4183         }
 4184         else if (strcaseeq(cr->valid_for, AUTH_VALID_FOR_CHAINING)) {
 4185           if (conf_val(CONF_PERMIT_CHAINING) == NULL)
 4186             log_msg((LOG_TRACE_LEVEL, "PERMIT_CHAINING is not configured"));
 4187           else
 4188             log_msg((LOG_TRACE_LEVEL, "PERMIT_CHAINING=\"%s\"",
 4189                      conf_val(CONF_PERMIT_CHAINING)));
 4190 
 4191           if (conf_val_eq(CONF_PERMIT_CHAINING, "yes")) {
 4192             valid = 1;
 4193             log_msg((LOG_NOTICE_LEVEL,
 4194                      "Received chaining credentials are valid for ACS"));
 4195           }
 4196           else
 4197             log_msg((LOG_TRACE_LEVEL, "Credentials not configured for ACS"));
 4198         }
 4199       }
 4200       else
 4201         valid = 1;
 4202 
 4203       if (!valid) {
 4204         log_msg((LOG_ALERT_LEVEL,
 4205                  "Received credentials are invalid for ACS use!"));
 4206         if (cr->valid_for != NULL)
 4207           log_msg((LOG_TRACE_LEVEL, "valid_for=\"%s\"", cr->valid_for));
 4208         *credentials = NULL;
 4209         return(-1);
 4210       }
 4211     }
 4212 
 4213     if (n == AUTH_MAX_CREDENTIALS) {
 4214       log_msg((LOG_NOTICE_LEVEL,
 4215                "Maximum number of credentials exceeded for \"%s\"",
 4216                cr->username));
 4217       return(n);
 4218     }
 4219 
 4220     cr->cookie_name = strdup(c->name);
 4221 
 4222     if (prev == NULL)
 4223       *credentials = cr;
 4224     else
 4225       prev->next = cr;
 4226     prev = cr;
 4227     n++;
 4228   }
 4229 
 4230   if (n == 0)
 4231     *credentials = NULL;
 4232 
 4233   return(n);
 4234 }
 4235 
 4236 int
 4237 count_valid_credentials(Credentials *credentials)
 4238 {
 4239   int n;
 4240   Credentials *cr;
 4241 
 4242   for (n = 0, cr = credentials; cr != NULL; cr = cr->next)
 4243     n++;
 4244 
 4245   return(n);
 4246 }
 4247 
 4248 /*
 4249  * Return 1 if the given identity is a DACS administrator as defined by
 4250  * one or more ADMIN_IDENTITY configuration directives, 0 otherwise.
 4251  * This does not recognize AUTH_STYLE_ADMIN identities - we do not
 4252  * rely on the name syntax, just to be on the safe side.
 4253  */
 4254 int
 4255 is_dacs_admin_identity(char *f, char *j, char *u)
 4256 {
 4257   char *federation, *jurisdiction, *username;
 4258   DACS_name name;
 4259   DACS_name_type nt;
 4260   Kwv_pair *v;
 4261 
 4262   if (f == NULL)
 4263     federation = conf_val(CONF_FEDERATION_NAME);
 4264   else
 4265     federation = f;
 4266 
 4267   if (j == NULL)
 4268     jurisdiction = conf_val(CONF_JURISDICTION_NAME);
 4269   else
 4270     jurisdiction = j;
 4271 
 4272   if (u == NULL)
 4273     return(0);
 4274   username = u;
 4275 
 4276   if (strcaseeq(username, "unauth") || strcaseeq(username, "unauthenticated"))
 4277     return(0);
 4278 
 4279   for (v = conf_var(CONF_ADMIN_IDENTITY); v != NULL; v = v->next) {
 4280     nt = parse_dacs_name(v->val, &name);
 4281     if (nt == DACS_USER_NAME) {
 4282       if (name.federation == NULL)
 4283         name.federation = conf_val(CONF_FEDERATION_NAME);
 4284       if (name.jurisdiction == NULL)
 4285         name.jurisdiction = conf_val(CONF_JURISDICTION_NAME);
 4286 
 4287       if (name_eq(name.federation, federation, DACS_NAME_CMP_CONFIG)
 4288           && name_eq(name.jurisdiction, jurisdiction, DACS_NAME_CMP_CONFIG)
 4289           && name_eq(name.username, username, DACS_NAME_CMP_CONFIG))
 4290         return(1);
 4291     }
 4292     else if (nt == DACS_GROUP_NAME) {
 4293       /*
 4294        * It might be reasonable to test group membership here.
 4295        * That is currently expensive.
 4296        */
 4297       log_msg((LOG_WARN_LEVEL, "Unrecognized name type in ADMIN_IDENTITY"));
 4298     }
 4299     else if (nt == DACS_FEDERATION_NAME || nt == DACS_UNKNOWN_NAME) {
 4300       log_msg((LOG_WARN_LEVEL, "Invalid name type in ADMIN_IDENTITY"));
 4301     }
 4302     else
 4303       log_msg((LOG_WARN_LEVEL, "Unrecognized name type in ADMIN_IDENTITY"));
 4304   }
 4305 
 4306   return(0);
 4307 }
 4308 
 4309 int
 4310 is_dacs_admin(Credentials *credentials)
 4311 {
 4312   Credentials *cr;
 4313 
 4314   for (cr = credentials; cr != NULL; cr = cr->next) {
 4315     if (cr->auth_style == AUTH_STYLE_ADMIN)
 4316       return(1);
 4317 
 4318     if (is_dacs_admin_identity(cr->federation, cr->home_jurisdiction,
 4319                                cr->username) == 1) {
 4320       log_msg((LOG_TRACE_LEVEL, "Matched admin identity: %s",
 4321                auth_identity_from_credentials(cr)));
 4322       return(1);
 4323     }
 4324     log_msg((LOG_TRACE_LEVEL, "No admin identity match: %s",
 4325              auth_identity_from_credentials(cr)));
 4326   }
 4327 
 4328   return(0);
 4329 }
 4330 
 4331 /*
 4332  * Test if USERNAME is an internal admin name.
 4333  * N.B. code should also verify that credentials are AUTH_STYLE_ADMIN
 4334  * before conferring any admin privileges (see is_dacs_admin()).
 4335  */
 4336 int
 4337 is_dacs_admin_name(char *username)
 4338 {
 4339 
 4340   if (strprefix(username, AUTH_ADMIN_USERNAME_PREFIX_STR) == NULL
 4341       || strsuffix(username, strlen(username),
 4342                    AUTH_ADMIN_USERNAME_SUFFIX_STR) == NULL)
 4343     return(0);
 4344 
 4345   return(1);
 4346 }
 4347 
 4348 /*
 4349  * Create a username that cannot correspond to any name assigned through
 4350  * authentication.
 4351  */
 4352 char *
 4353 make_dacs_admin_name(char *basename)
 4354 {
 4355   char *username;
 4356 
 4357   username = ds_xprintf("%s%s%s",
 4358                         AUTH_ADMIN_USERNAME_PREFIX_STR,
 4359                         basename,
 4360                         AUTH_ADMIN_USERNAME_SUFFIX_STR);
 4361   return(username);
 4362 }
 4363 
 4364 /*
 4365  * Return 1 if these credentials were issued by this jurisdiction,
 4366  * 0 otherwise.
 4367  */
 4368 int
 4369 is_local_user_identity(Credentials *cr)
 4370 {
 4371   DACS_name_cmp cmp_mode;
 4372 
 4373   cmp_mode = DACS_NAME_CMP_CONFIG;
 4374   if (name_eq(cr->home_jurisdiction, conf_val(CONF_JURISDICTION_NAME), cmp_mode)
 4375       && name_eq(cr->federation, conf_val(CONF_FEDERATION_NAME), cmp_mode))
 4376     return(1);
 4377 
 4378   return(0);
 4379 }
 4380 
 4381 /*
 4382  * The USER argument can be:
 4383  *   o a user (e.g., "DSS:brachman")
 4384  *     (with wildcard capability: "*:brachman", "*::*:brachman", "foo::*:bob")
 4385  *   o an argument to the from() predicate
 4386  *     (e.g., "10.0.0.123", "10.0.0.0/24")
 4387  *   o a group or role to which the user belongs (e.g., "%IRMS:sysadmin")
 4388  *   o the federation or jurisdiction that authenticated the user
 4389  *     (e.g., "DSS:", "FEDROOT::")
 4390  *   o an IP address (e.g., "10.0.0.123")
 4391  *   o a wildcard that matches any locally authenticated user ("mine")
 4392  *   o a wildcard that matches any authenticated user ("auth")
 4393  *   o a wildcard that matches any unauthenticated user ("unauth")
 4394  *   o a wildcard that matches any user ("any")
 4395  *   o a wildcard that matches no user ("none")
 4396  */
 4397 int
 4398 is_matching_user_identity(char *user, Credentials *credentials,
 4399                           DACS_name_cmp cmp_mode, char **errmsg)
 4400 {
 4401   int st, wildcard;
 4402   Credentials *cr;
 4403   DACS_name_type nt;
 4404   DACS_name dacs_name;
 4405 
 4406   if (strcaseeq(user, "any")) {
 4407     log_msg((LOG_TRACE_LEVEL, "Matched any user"));
 4408     return(1);
 4409   }
 4410 
 4411   if (strcaseeq(user, "none")) {
 4412     log_msg((LOG_TRACE_LEVEL, "Matched user \"none\""));
 4413     return(0);
 4414   }
 4415 
 4416   if (strcaseeq(user, "mine")) {
 4417     log_msg((LOG_TRACE_LEVEL, "Matched user \"mine\""));
 4418     if (credentials != NULL && is_local_user_identity(credentials)) {
 4419       log_msg((LOG_TRACE_LEVEL, "Matched local user"));
 4420       return(1);
 4421     }
 4422     return(0);
 4423   }
 4424 
 4425   if (strcaseeq(user, "unauth") || strcaseeq(user, "unauthenticated")) {
 4426     if (credentials == NULL) {
 4427       log_msg((LOG_TRACE_LEVEL, "Matched unauthenticated user"));
 4428       return(1);
 4429     }
 4430     return(0);
 4431   }
 4432 
 4433   if (strcaseeq(user, "auth") || strcaseeq(user, "authenticated")) {
 4434     if (credentials != NULL) {
 4435       log_msg((LOG_TRACE_LEVEL, "Matched authenticated user"));
 4436       return(1);
 4437     }
 4438     return(0);
 4439   }
 4440 
 4441   nt = parse_dacs_name(user, &dacs_name);
 4442   if (dacs_name.federation == NULL)
 4443     dacs_name.federation = conf_val(CONF_FEDERATION_NAME);
 4444   if (dacs_name.jurisdiction == NULL)
 4445     dacs_name.jurisdiction = conf_val(CONF_JURISDICTION_NAME);
 4446 
 4447   if (nt == DACS_UNKNOWN_NAME) {
 4448     /*
 4449      * Check for the wildcard syntax for a username or a jurisdiction.
 4450      * o "*:bob" means any bob from any jurisdiction of this federation
 4451      * o "FOO::*:bob" means any bob from federation FOO
 4452      * o "*::*:bob" means any bob from any federation
 4453      */
 4454     if (dacs_name.type != DACS_USER_NAME
 4455         || dacs_name.username == NULL
 4456         || !streq(dacs_name.jurisdiction, "*")) {
 4457       st = is_from_address(user,
 4458                            (credentials == NULL) ? NULL
 4459                            : credentials->ip_address, NULL, NULL);
 4460       if (st == -1) {
 4461         if (errmsg != NULL)
 4462           *errmsg = ds_xprintf("Invalid argument: \"%s\" ", user);
 4463         return(-1);
 4464       }
 4465       return(st);
 4466     }
 4467     log_msg((LOG_TRACE_LEVEL, "Looking for a wildcard match"));
 4468     wildcard = 1;
 4469   }
 4470   else
 4471     wildcard = 0;
 4472 
 4473   if ((cr = credentials) == NULL) {
 4474     char *unauth_roles;
 4475 
 4476     unauth_roles = conf_val(CONF_UNAUTH_ROLES);
 4477     if (nt == DACS_GROUP_NAME && unauth_roles != NULL) {
 4478       if (has_unauth_role(unauth_roles, dacs_name.jurisdiction,
 4479                           dacs_name.username) == 1) {
 4480         log_msg((LOG_TRACE_LEVEL, "Matched unauth role"));
 4481         return(1);
 4482       }
 4483     }
 4484     return(0);
 4485   }
 4486 
 4487   log_msg((LOG_TRACE_LEVEL,
 4488            "Matching credentials \"%s\" against \"%s\"",
 4489            auth_identity_from_credentials(cr), user));
 4490 
 4491   if (wildcard) {
 4492     if (!name_eq(cr->username, dacs_name.username, cmp_mode))
 4493       return(0);
 4494 
 4495     if (streq(dacs_name.federation, "*")
 4496         || name_eq(cr->federation, dacs_name.federation, cmp_mode)) {
 4497       log_msg((LOG_TRACE_LEVEL, "Matched DACS username with wildcard"));
 4498       return(1);
 4499     }
 4500 
 4501     return(0);
 4502   }
 4503 
 4504   switch (nt) {
 4505   case DACS_USER_NAME:
 4506     if (name_eq(cr->username, dacs_name.username, cmp_mode)
 4507         && name_eq(cr->home_jurisdiction, dacs_name.jurisdiction, cmp_mode)
 4508         && name_eq(cr->federation, dacs_name.federation, cmp_mode)) {
 4509       log_msg((LOG_TRACE_LEVEL, "Matched DACS username"));
 4510       return(1);
 4511     }
 4512     break;
 4513 
 4514   case DACS_JURISDICTION_NAME:
 4515     if (name_eq(cr->home_jurisdiction, dacs_name.jurisdiction, cmp_mode)
 4516         && name_eq(cr->federation, dacs_name.federation, cmp_mode)) {
 4517       log_msg((LOG_TRACE_LEVEL,
 4518                "Matched user's authenticating DACS jurisdiction"));
 4519       return(1);
 4520     }
 4521     break;
 4522 
 4523   case DACS_FEDERATION_NAME:
 4524     if (name_eq(cr->federation, dacs_name.federation, cmp_mode)) {
 4525       log_msg((LOG_TRACE_LEVEL,
 4526                "Matched user's authenticating DACS federation"));
 4527       return(1);
 4528     }
 4529     break;
 4530 
 4531   case DACS_GROUP_NAME:
 4532     if (cr->role_str != NULL)
 4533       cr->roles = make_group_names_from_role_str(cr->home_jurisdiction,
 4534                                                  cr->role_str);
 4535 
 4536     st = is_group_member(cr->home_jurisdiction, cr->username, cr->roles,
 4537                          dacs_name.jurisdiction, dacs_name.username);
 4538     if (st == 1) {
 4539       log_msg((LOG_TRACE_LEVEL, "Matched group or role"));
 4540       return(1);
 4541     }
 4542     else if (st == -1) {
 4543       if (errmsg != NULL)
 4544         *errmsg = "is_matching_user_identity: group resolution error";
 4545       return(-1);
 4546     }
 4547     break;
 4548 
 4549   case DACS_IP_NAME:
 4550     if (streq(cr->ip_address, dacs_name.username)) {
 4551       log_msg((LOG_TRACE_LEVEL,
 4552                "Matched IP address of authenticating jurisdiction"));
 4553       return(1);
 4554     }
 4555     break;
 4556 
 4557   default:
 4558     if (errmsg != NULL)
 4559       *errmsg = "is_matching_user_identity: internal error";
 4560     return(-1);
 4561   }
 4562 
 4563   return(0);
 4564 }
 4565 
 4566 /*
 4567  * Test if the USER filter specification matches any CREDENTIALS.
 4568  * If so, return 1 and set MATCHED to point to the first matching credentials
 4569  * if any.  If not, return 0.
 4570  * If an error occurs, return -1 and set ERRMSG to an explanatory message.
 4571  * This is called during revocation checks, the ACS "user" predicate, and
 4572  * while processing ACL user_list elements.
 4573  *
 4574  * XXX It is not possible to match admin credentials, except that they
 4575  * count as "authenticated".
 4576  */
 4577 int
 4578 is_matching_user(char *user, Credentials *credentials, DACS_name_cmp cmp_mode,
 4579                  Credentials **matched, char **errmsg)
 4580 {
 4581   int st;
 4582   Credentials *cr;
 4583 
 4584   cr = credentials;
 4585   do {
 4586     if ((st = is_matching_user_identity(user, cr, cmp_mode, errmsg)) == -1)
 4587       return(-1);
 4588     if (st == 1) {
 4589       if (matched != NULL)
 4590         *matched = cr;
 4591       return(1);
 4592     }
 4593     if (cr != NULL)
 4594       cr = cr->next;
 4595   } while (cr != NULL);
 4596 
 4597   return(0);
 4598 }
 4599 
 4600 char *
 4601 get_revocations(char *item_type)
 4602 {
 4603   int st;
 4604   char *buf;
 4605   Vfs_handle *h;
 4606 
 4607   if ((h = vfs_open_item_type(item_type)) == NULL) {
 4608     log_msg((LOG_ERROR_LEVEL, "Could not open \"%s\"", item_type));
 4609     return(NULL);
 4610   }
 4611 
 4612   st = vfs_get(h, NULL, (void **) &buf, NULL);
 4613 
 4614   if (vfs_close(h) == -1) {
 4615     log_msg((LOG_ERROR_LEVEL, "vfs_close() failed"));
 4616     st = -1;
 4617   }
 4618   if (st == -1)
 4619     return(NULL);
 4620 
 4621   return(buf);
 4622 }
 4623 
 4624 /*
 4625  * The revocation list BUF is destructively carved up into lines and typed.
 4626  * Each line in "revocations" is either:
 4627  *   o a comment line (first non-whitespace character, if any, is a '#')
 4628  *   o the keyword "deny", followed by an expression
 4629  *     If the expression is TRUE for any credentials, all access is denied
 4630  *   o the keyword "revoke", followed by an expression
 4631  *     If the expression is TRUE for any credentials, the credentials
 4632  *     are considered invalid; the caller may delete them
 4633  */
 4634 Dsvec *
 4635 parse_revocations(char *buf, int check_exprs)
 4636 {
 4637   char *line;
 4638   Ds ds;
 4639   Dsio *dsio;
 4640   Dsvec *dsv;
 4641   Revocation *r;
 4642 
 4643   dsv = dsvec_init(NULL, sizeof(Revocation *));
 4644 
 4645   /* Parse the revocation file, line by line. */
 4646   ds_init(&ds);
 4647   ds.escnl_flag = 1;
 4648   ds.delnl_flag = 1;
 4649   dsio = dsio_set(&ds, NULL, buf, 0, 0);
 4650 
 4651   while ((line = dsio_gets(&ds)) != NULL) {
 4652     char *p, *q;
 4653 
 4654     r = ALLOC(Revocation);
 4655     line = p = ds_buf(&ds);
 4656     while (*p == ' ' || *p == '\t')
 4657       p++;
 4658 
 4659     if (*p == '\0') {
 4660       r->type = AUTH_REVOKE_COMMENT;
 4661       r->item = "";
 4662     }
 4663     else if (*p == '#') {
 4664       r->type = AUTH_REVOKE_COMMENT;
 4665       r->item = line;
 4666     }
 4667     else if ((q = strcaseprefix(p, "deny")) && (*q == ' ' || *q == '\t')) {
 4668       r->type = AUTH_REVOKE_DENY;
 4669       for (p = q + 1; *p == ' ' || *p == '\t'; p++)
 4670         ;
 4671       r->item = p;
 4672     }
 4673     else if ((q = strcaseprefix(p, "revoke")) && (*q == ' ' || *q == '\t')) {
 4674       r->type = AUTH_REVOKE_REVOKE;
 4675       for (p = q + 1; *p == ' ' || *p == '\t'; p++)
 4676         ;
 4677       r->item = p;
 4678     }
 4679     else if ((q = strcaseprefix(p, "disable")) && (*q == ' ' || *q == '\t')) {
 4680       r->type = AUTH_REVOKE_DISABLE;
 4681       for (p = q + 1; *p == ' ' || *p == '\t'; p++)
 4682         ;
 4683       r->item = p;
 4684     }
 4685     else if (( q= strcaseprefix(p, "block")) && (*q == ' ' || *q == '\t')) {
 4686       r->type = AUTH_REVOKE_BLOCK;
 4687       for (p = q + 1; *p == ' ' || *p == '\t'; p++)
 4688         ;
 4689       r->item = p;
 4690     }
 4691     else {
 4692       /* Unrecognized */
 4693       log_msg((LOG_ERROR_LEVEL, "Unrecognized revocation line: %s", line));
 4694       return(NULL);
 4695     }
 4696 
 4697     dsvec_add_ptr(dsv, r);
 4698     ds_reset_buf(&ds);
 4699   }
 4700 
 4701   if (check_exprs) {
 4702     int i;
 4703     Acs_environment env;
 4704     Acs_expr_result st;
 4705     Expr_result result;
 4706 
 4707     for (i = 0; i < dsvec_len(dsv); i++) {
 4708       r = (Revocation *) dsvec_ptr(dsv, i, Revocation *);
 4709       if (r->type == AUTH_REVOKE_COMMENT)
 4710         continue;
 4711       acs_new_env(&env);
 4712       acs_init_env(NULL, NULL, "", NULL, &env);
 4713       env.do_eval = 0;
 4714       st = acs_expr(r->item, &env, &result);
 4715       if (st == ACS_EXPR_SYNTAX_ERROR) {
 4716         log_msg((LOG_ERROR_LEVEL, "Syntax error in revocation item: %s",
 4717                  r->item));
 4718         return(NULL);
 4719       }
 4720       if (st == ACS_EXPR_EVAL_ERROR) {
 4721         log_msg((LOG_ERROR_LEVEL, "Evaluation error in revocation item: %s",
 4722                  r->item));
 4723         return(NULL);
 4724       }
 4725       if (st == ACS_EXPR_LEXICAL_ERROR) {
 4726         log_msg((LOG_ERROR_LEVEL, "Lexical error in revocation item: %s",
 4727                  r->item));
 4728         return(NULL);
 4729       }
 4730     }
 4731   }
 4732 
 4733   return(dsv);
 4734 }
 4735 
 4736 /*
 4737  * Go through CREDENTIALS, checking to see if access should be denied
 4738  * or any credentials revoked (FOR_ACS non-zero), or authentication should
 4739  * be disallowed (FOR_ACS zero).
 4740  *
 4741  * Return 1 if access is denied (for any identity) or authentication disabled,
 4742  * 0 if access is not denied (but some credentials may have been revoked),
 4743  * and -1 if there's an error.
 4744  * If the revocation list has been configured, it must be accessible.
 4745  */
 4746 int
 4747 check_revocation(Credentials *credentials, Kwv *kwv, char *item_type,
 4748                  int for_acs)
 4749 {
 4750   int i, st;
 4751   char *buf;
 4752   Acs_environment env;
 4753   Credentials *cr;
 4754   Dsvec *revocations;
 4755   Revocation *r;
 4756 
 4757   log_msg((LOG_TRACE_LEVEL, "Checking \"%s\" for revocations...", item_type));
 4758   if ((buf = get_revocations(item_type)) == NULL) {
 4759     log_msg((LOG_TRACE_LEVEL, "Could not load revocations"));
 4760     return(-1);
 4761   }
 4762 
 4763   if ((revocations = parse_revocations(buf, 0)) == NULL) {
 4764     log_msg((LOG_ERROR_LEVEL, "Could not parse revocations"));
 4765     return(-1);
 4766   }
 4767 
 4768   for (i = 0; i < dsvec_len(revocations); i++) {
 4769     r = (Revocation *) dsvec_ptr(revocations, i, Revocation *);
 4770     if (r->type == AUTH_REVOKE_COMMENT)
 4771       continue;
 4772 
 4773     if (r->type == AUTH_REVOKE_BLOCK
 4774         || (for_acs && (credentials == NULL || r->type == AUTH_REVOKE_DENY))) {
 4775       acs_new_env(&env);
 4776       acs_init_env(kwv, NULL, NULL, credentials, &env);
 4777       if ((st = acs_expr(r->item, &env, NULL)) == ACS_EXPR_TRUE) {
 4778         log_msg((LOG_NOTICE_LEVEL,
 4779                  "Revocation: deny%s is true: \"%s\"",
 4780                  (r->type == AUTH_REVOKE_BLOCK) ? "/block " : "", r->item));
 4781         return(1);
 4782       }
 4783           else if (st == ACS_EXPR_FALSE)
 4784             log_msg((LOG_TRACE_LEVEL,
 4785                      "Revocation: deny%s is false: \"%s\"",
 4786                      (r->type == AUTH_REVOKE_BLOCK) ? "/block " : "",
 4787                      r->item));
 4788       else {
 4789         log_msg((LOG_ERROR_LEVEL,
 4790                  "Revocation: deny%s processing error: \"%s\"",
 4791                  (r->type == AUTH_REVOKE_BLOCK) ? "/block " : "", r->item));
 4792         return(-1);
 4793       }
 4794     }
 4795     else if (for_acs && r->type == AUTH_REVOKE_REVOKE) {
 4796       Credentials *cr_next;
 4797 
 4798       for (cr = credentials; cr != NULL; cr = cr->next) {
 4799         cr_next = cr->next;
 4800         cr->next = NULL;
 4801 
 4802         acs_new_env(&env);
 4803         acs_init_env(kwv, NULL, NULL, cr, &env);
 4804         st = acs_expr(r->item, &env, NULL);
 4805         cr->next = cr_next;
 4806 
 4807         if (st == ACS_EXPR_TRUE) {
 4808           log_msg((LOG_NOTICE_LEVEL,
 4809                    "Revocation: revoke is true: \"%s\"", r->item));
 4810           cr->valid_for = AUTH_VALID_FOR_NOTHING;
 4811         }
 4812         else if (st == ACS_EXPR_FALSE)
 4813           log_msg((LOG_TRACE_LEVEL,
 4814                    "Revocation: revoke is false: \"%s\"", r->item));
 4815         else {
 4816           log_msg((LOG_ERROR_LEVEL,
 4817                    "Revocation: revoke processing error: \"%s\"", r->item));
 4818           return(-1);
 4819         }
 4820       }
 4821     }
 4822     else if (!for_acs) {
 4823       if (r->type == AUTH_REVOKE_DISABLE) {
 4824         acs_new_env(&env);
 4825         acs_init_env(kwv, NULL, NULL, credentials, &env);
 4826         if ((st = acs_expr(r->item, &env, NULL)) == ACS_EXPR_TRUE) {
 4827           log_msg((LOG_NOTICE_LEVEL,
 4828                    "Revocation: disable is true: \"%s\"", r->item));
 4829           return(1);
 4830         }
 4831         else if (st == ACS_EXPR_FALSE)
 4832           log_msg((LOG_TRACE_LEVEL,
 4833                    "Revocation: disable is false: \"%s\"", r->item));
 4834         else {
 4835           log_msg((LOG_ERROR_LEVEL,
 4836                    "Revocation: disable processing error: \"%s\"", r->item));
 4837           return(-1);
 4838         }
 4839       }
 4840     }
 4841   }
 4842 
 4843   return(0);
 4844 }
 4845 
 4846 /*
 4847  * Parse an HTTP digest WWW-Authenticate response header
 4848  * RFC 2617, S2 and S3.2.1
 4849  */
 4850 Http_auth_www_authenticate *
 4851 http_auth_www_authenticate_parse(char *challenge, char **errmsg)
 4852 {
 4853   char *p, *q, *start;
 4854   Http_auth_www_authenticate *wwwa;
 4855   Kwv *kwv;
 4856   static Kwv_conf conf = {
 4857     "=", NULL, NULL, KWV_CONF_DEFAULT, ",", 10, NULL, NULL
 4858   };
 4859 
 4860   wwwa = ALLOC(Http_auth_www_authenticate);
 4861   wwwa->www_authenticate = strdup(challenge);
 4862 
 4863   p = challenge;
 4864   while (*p == ' ')
 4865     p++;
 4866   if ((q = strcaseprefix(p, "Basic ")) != NULL) {
 4867     wwwa->scheme = HTTP_AUTH_BASIC;
 4868     wwwa->scheme_name = "Basic";
 4869   }
 4870   else if ((q = strcaseprefix(p, "Digest ")) != NULL) {
 4871     wwwa->scheme = HTTP_AUTH_DIGEST;
 4872     wwwa->scheme_name = "Digest";
 4873   }
 4874   else {
 4875     /* XXX Extend for arbitrary schemes */
 4876     return(NULL);
 4877   }
 4878 
 4879   while (*q == ' ')
 4880     q++;
 4881   start = q;
 4882 
 4883   wwwa->domain = wwwa->nonce = wwwa->opaque = wwwa->stale = NULL;
 4884   wwwa->algorithm = wwwa->qop_options = wwwa->auth_param = NULL;
 4885 
 4886   kwv = kwv_init(10);
 4887   kwv->icase = 1;
 4888   kwv->dup_mode = KWV_NO_DUPS;
 4889   if ((kwv = kwv_make_sep(kwv, start, &conf)) == NULL) {
 4890     *errmsg = "Error parsing WWW-Authenticate header";
 4891     return(NULL);
 4892   }
 4893 
 4894   if ((wwwa->realm = kwv_lookup_value(kwv, "realm")) == NULL) {
 4895     *errmsg = "No realm in WWW-Authenticate?";
 4896     return(NULL);
 4897   }
 4898 
 4899   if (wwwa->scheme == HTTP_AUTH_BASIC) {
 4900     if (kwv_count(kwv, NULL) != 1) {
 4901       *errmsg = "Too many directives in Basic WWW-Authenticate";
 4902       return(NULL);
 4903     }
 4904     return(wwwa);
 4905   }
 4906 
 4907   if ((wwwa->domain = kwv_lookup_value(kwv, "domain")) == NULL) {
 4908     *errmsg = "No domain in Digest WWW-Authenticate?";
 4909     return(NULL);
 4910   }
 4911   if ((wwwa->nonce = kwv_lookup_value(kwv, "nonce")) == NULL) {
 4912     *errmsg = "No nonce in Digest WWW-Authenticate?";
 4913     return(NULL);
 4914   }
 4915   wwwa->opaque = kwv_lookup_value(kwv, "opaque");
 4916   wwwa->stale = kwv_lookup_value(kwv, "stale");
 4917   wwwa->algorithm = kwv_lookup_value(kwv, "algorithm");
 4918   wwwa->qop_options = kwv_lookup_value(kwv, "qop");
 4919 
 4920   /* XXX Ignore any unrecognized directives */
 4921   wwwa->auth_param = NULL;
 4922 
 4923   return(wwwa);
 4924 }
 4925 
 4926 Http_auth_authorization *
 4927 http_auth_authorization_init(char *username, char *scheme_name, char *realm)
 4928 {
 4929   Http_auth_authorization *aa;
 4930 
 4931   aa = ALLOC(Http_auth_authorization);
 4932   if (scheme_name != NULL) {
 4933     if (strcaseeq(scheme_name, "Basic"))
 4934       aa->scheme = HTTP_AUTH_BASIC;
 4935     else if (strcaseeq(scheme_name, "Digest"))
 4936       aa->scheme = HTTP_AUTH_DIGEST;
 4937     else
 4938       aa->scheme = HTTP_AUTH_UNKNOWN;
 4939     aa->scheme_name = strdup(scheme_name);
 4940   }
 4941   else {
 4942     aa->scheme = HTTP_AUTH_UNKNOWN;
 4943     aa->scheme_name = NULL;
 4944   }
 4945 
 4946   if (username != NULL)
 4947     aa->username = strdup(username);
 4948   else
 4949     aa->username = NULL;
 4950 
 4951   if (realm != NULL)
 4952     aa->realm = strdup(realm);
 4953   else
 4954     aa->realm = NULL;
 4955 
 4956   aa->authorization = NULL;
 4957   aa->password = aa->nonce = aa->digest_uri = NULL;
 4958   aa->response = aa->algorithm = aa->cnonce = aa->opaque = NULL;
 4959   aa->message_qop = aa->nonce_count = aa->auth_param = NULL;
 4960   aa->http_method = NULL;
 4961   aa->www_auth = NULL;
 4962 
 4963   return(aa);
 4964 }
 4965 
 4966 /*
 4967  * Parse an HTTP Authorization request header
 4968  * RFC 2617, S3.2.2
 4969  */
 4970 Http_auth_authorization *
 4971 http_auth_authorization_parse(char *response, char **errmsg)
 4972 {
 4973   char *p, *q, *start;
 4974   Http_auth_authorization *aa;
 4975   Kwv *kwv;
 4976   static Kwv_conf conf = {
 4977     "=", NULL, NULL, KWV_CONF_DEFAULT, ",", 10, NULL, NULL
 4978   };
 4979 
 4980   aa = http_auth_authorization_init(NULL, NULL, NULL);
 4981   aa->authorization = strdup(response);
 4982 
 4983   p = response;
 4984   while (*p == ' ')
 4985     p++;
 4986 
 4987   if ((q = strcaseprefix(p, "Basic ")) != NULL) {
 4988     char *basic_credentials;
 4989 
 4990     aa->scheme = HTTP_AUTH_BASIC;
 4991     aa->scheme_name = "Basic";
 4992 
 4993     while (*q == ' ')
 4994       q++;
 4995     if (mime_decode_base64(q, (unsigned char **) &basic_credentials) == -1) {
 4996       *errmsg = "Base64 decoding error in Basic Authorization header";
 4997       return(NULL);
 4998     }
 4999 
 5000     log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
 5001              "Basic auth info: \"%s\"", basic_credentials));
 5002 
 5003     if ((p = strchr(basic_credentials, (int) ':')) == NULL) {
 5004       *errmsg = "Invalid Basic Authorization header format, no colon";
 5005       return(NULL);
 5006     }
 5007 
 5008     aa->username = basic_credentials;
 5009     *p++ = '\0';
 5010     aa->password = p;
 5011 
 5012     return(aa);
 5013   }
 5014   else if ((q = strcaseprefix(p, "Digest ")) != NULL) {
 5015     aa->scheme = HTTP_AUTH_DIGEST;
 5016     aa->scheme_name = "Digest";
 5017   }
 5018   else {
 5019     /* XXX Extend for arbitrary schemes */
 5020     log_msg((LOG_ERROR_LEVEL, "Unrecognized scheme: \"%s\"", response));
 5021     return(NULL);
 5022   }
 5023 
 5024   while (*q == ' ')
 5025     q++;
 5026   start = q;
 5027 
 5028   kwv = kwv_init(10);
 5029   kwv->icase = 1;
 5030   kwv->dup_mode = KWV_NO_DUPS;
 5031   if ((kwv = kwv_make_sep(kwv, start, &conf)) == NULL) {
 5032     *errmsg = "Error parsing Digest Authorization header";
 5033     return(NULL);
 5034   }
 5035 
 5036   if ((aa->username = kwv_lookup_value(kwv, "username")) == NULL) {
 5037     *errmsg = "No username in Digest Authorization?";
 5038     return(NULL);
 5039   }
 5040   if ((aa->realm = kwv_lookup_value(kwv, "realm")) == NULL) {
 5041     *errmsg = "No realm in Digest Authorization?";
 5042     return(NULL);
 5043   }
 5044   if ((aa->nonce = kwv_lookup_value(kwv, "nonce")) == NULL) {
 5045     *errmsg = "No nonce in Digest Authorization?";
 5046     return(NULL);
 5047   }
 5048   if ((aa->digest_uri = kwv_lookup_value(kwv, "uri")) == NULL) {
 5049     *errmsg = "No uri in Digest Authorization?";
 5050     return(NULL);
 5051   }
 5052   if ((aa->response = kwv_lookup_value(kwv, "response")) == NULL) {
 5053     *errmsg = "No response in Digest Authorization?";
 5054     return(NULL);
 5055   }
 5056   aa->algorithm = kwv_lookup_value(kwv, "algorithm");
 5057   aa->cnonce = kwv_lookup_value(kwv, "cnonce");
 5058   aa->opaque = kwv_lookup_value(kwv, "opaque");
 5059   aa->message_qop = kwv_lookup_value(kwv, "qop");
 5060   aa->nonce_count = kwv_lookup_value(kwv, "nc");
 5061   /* XXX Ignore any unrecognized directives */
 5062   aa->auth_param = NULL;
 5063 
 5064   return(aa);
 5065 }
 5066 
 5067 /*
 5068  * Create a nonce; return it or NULL if an error occurs.
 5069  * If TIME_STAMP is given, use it (presumably for validation), otherwise
 5070  * create a new one using the current time.
 5071  * A nonce consists of a time stamp, followed by a colon, followed by
 5072  * a hash value (we'll use an HMAC) of the time stamp plus (optionally)
 5073  * other select data.
 5074  */
 5075 static char *
 5076 make_digest_nonce(char *time_stamp)
 5077 {
 5078   unsigned int hmac_len;
 5079   char *hashval, *nonce, *nonce_hash_str, *ts;
 5080   unsigned char *outp;
 5081   Crypt_keys *ck;
 5082   Hmac_handle *hmac;
 5083 
 5084   if ((ck = crypt_keys_from_vfs(ITEM_TYPE_JURISDICTION_KEYS)) == NULL)
 5085     return(NULL);
 5086 
 5087   if (time_stamp == NULL) {
 5088     time_t now;
 5089 
 5090     time(&now);
 5091     ts = ds_xprintf("%lu", (unsigned long) now);
 5092   }
 5093   else
 5094     ts = time_stamp;
 5095 
 5096   /* We'll just hash the time stamp for now. */
 5097   nonce_hash_str = ts;
 5098 
 5099   hmac = crypto_hmac_open(AUTH_DIGEST_NONCE_DIGEST_NAME, ck->hmac_key,
 5100                           CRYPTO_HMAC_KEY_LENGTH);
 5101   crypto_hmac_hash(hmac, (unsigned char *) nonce_hash_str,
 5102                    strlen(nonce_hash_str));
 5103   outp = crypto_hmac_close(hmac, NULL, &hmac_len);
 5104   crypt_keys_free(ck);
 5105 
 5106   strba64(outp, hmac_len, &hashval);
 5107   nonce = ds_xprintf("%s:%s", ts, hashval);
 5108 
 5109   return(nonce);
 5110 }
 5111 
 5112 enum {
 5113   /*
 5114    * Time, in seconds, from when WWW-Authenticate is issued to when an
 5115    * Authorization request header becomes invalid.
 5116    * The minimal value should allow for some think time while a user
 5117    * enters his/her username and password.
 5118    * Note that this is unrelated to the lifetime of DACS credentials.
 5119    */
 5120   NONCE_TIMEOUT_SECS = 20
 5121 };
 5122 
 5123 /*
 5124  * Given NONCE, as received from an Authorization request header, check if
 5125  * it is valid.
 5126  * Return 0 if so, -1 otherwise.
 5127  */
 5128 static int
 5129 validate_nonce(char *nonce)
 5130 {
 5131   char *check_nonce, *n, *nonce_timeout_secs, *p, *time_stamp;
 5132   time_t creation_time, diff, now, timeout;
 5133   Kwv *akwv;
 5134 
 5135   log_msg((LOG_TRACE_LEVEL, "nonce=\"%s\"", nonce));
 5136 
 5137   n = strdup(nonce);
 5138   if ((p = strchr(n, (int) ':')) == NULL)
 5139     return(-1);
 5140   time_stamp = n;
 5141   *p++ = '\0';
 5142   
 5143   if (strnum(time_stamp, STRNUM_TIME_T, &creation_time) == -1) {
 5144     log_msg((LOG_ERROR_LEVEL, "Invalid nonce creation time"));
 5145     return(-1);
 5146   }
 5147 
 5148   if (dacs_conf != NULL
 5149       && (akwv = var_ns_lookup_kwv(dacs_conf->conf_var_ns, "Conf")) != NULL
 5150       && (nonce_timeout_secs
 5151           = kwv_lookup_value(akwv, "http_auth_timeout_secs")) != NULL) {
 5152     if (strnum(nonce_timeout_secs, STRNUM_TIME_T, &timeout) == -1) {
 5153       log_msg((LOG_ERROR_LEVEL, "Invalid http_auth_timeout_secs: \"%s\"",
 5154                nonce_timeout_secs));
 5155       return(-1);
 5156     }
 5157   }
 5158   else
 5159     timeout = NONCE_TIMEOUT_SECS;
 5160   log_msg((LOG_TRACE_LEVEL, "Using nonce timeout: %lu secs", timeout));
 5161 
 5162   time(&now);
 5163   if (((diff = now - creation_time)) > timeout) {
 5164     log_msg((LOG_DEBUG_LEVEL, "Nonce \"%s\" has expired", nonce));
 5165     return(-1);
 5166   }
 5167   log_msg((LOG_TRACE_LEVEL, "Nonce \"%s\" remaining secs: %lu", nonce, diff));
 5168 
 5169   check_nonce = make_digest_nonce(time_stamp);
 5170   if (!streq(check_nonce, nonce)) {
 5171     log_msg((LOG_ERROR_LEVEL, "Recomputed nonce != Authorization nonce"));
 5172     return(-1);
 5173   }
 5174   log_msg((LOG_TRACE_LEVEL, "Nonce recomputes ok"));
 5175 
 5176   return(0);
 5177 }
 5178 
 5179 /*
 5180  * Create a digest-challenge
 5181  * RFC 2617, S3.2.1
 5182  */
 5183 char *
 5184 http_auth_digest_auth(Http_auth *auth, char *domain)
 5185 {
 5186   unsigned int len;
 5187   char *nonce, *opaque, *qop, *www_authenticate;
 5188   unsigned char *encrypted_opaque;
 5189   Crypt_keys *ck;
 5190   Ds ds;
 5191 
 5192   nonce = make_digest_nonce(NULL);
 5193   qop = "auth";
 5194 
 5195   ds_init(&ds);
 5196   ds_asprintf(&ds, "%s realm=\"%s\"", auth->scheme_name, auth->realm);
 5197   ds_asprintf(&ds, ", nonce=\"%s\"", nonce);
 5198   ds_asprintf(&ds, ", algorithm=\"MD5\"");
 5199   ds_asprintf(&ds, ", domain=\"%s\"", domain);
 5200   ds_asprintf(&ds, ", qop=\"%s