static void set_cookie_hdl(void *userdata, const char *value) { char **pairs = pair_string(value, ';', '=', "\"'", " \r\n\t"); ne_cookie *cook; ne_cookie_cache *cache = userdata; int n; /* Check sanity */ if (pairs[0] == NULL || pairs[1] == NULL) { /* yagaboo */ return; } NE_DEBUG(NE_DBG_HTTP, "Got cookie name=%s\n", pairs[0]); /* Search for a cookie of this name */ NE_DEBUG(NE_DBG_HTTP, "Searching for existing cookie...\n"); for (cook = cache->cookies; cook != NULL; cook = cook->next) { if (strcasecmp(cook->name, pairs[0]) == 0) { break; } } if (cook == NULL) { NE_DEBUG(NE_DBG_HTTP, "New cookie.\n"); cook = ne_malloc(sizeof *cook); memset(cook, 0, sizeof *cook); cook->name = ne_strdup(pairs[0]); cook->next = cache->cookies; cache->cookies = cook; } else { /* Free the old value */ ne_free(cook->value); } cook->value = ne_strdup(pairs[1]); for (n = 2; pairs[n] != NULL; n+=2) { NE_DEBUG(NE_DBG_HTTP, "Cookie parm %s=%s\n", pairs[n], pairs[n+1]); if (strcasecmp(pairs[n], "path") == 0) { cook->path = ne_strdup(pairs[n+1]); } else if (strcasecmp(pairs[n], "max-age") == 0) { int t = atoi(pairs[n+1]); cook->expiry = time(NULL) + (time_t)t; } else if (strcasecmp(pairs[n], "domain") == 0) { cook->domain = ne_strdup(pairs[n+1]); } } NE_DEBUG(NE_DBG_HTTP, "End of parms.\n"); pair_string_free(pairs); }
/* A new challenge presented by the server */ int http_auth_challenge(http_auth_session *sess, const char *value) { char **pairs, *pnt, *unquoted, *key; struct http_auth_chall *chall = NULL, *challenges = NULL; int n, success; DEBUG(DEBUG_HTTPAUTH, "Got new auth challenge: %s\n", value); /* The header value may be made up of one or more challenges. * We split it down into attribute-value pairs, then search for * schemes in the pair keys. */ pairs = pair_string(value, ',', '=', HTTP_QUOTES, HTTP_WHITESPACE); for (n = 0; pairs[n]!=NULL; n+=2) { /* Look for an auth-scheme in the key */ pnt = strchr(pairs[n], ' '); if (pnt != NULL) { /* We have a new challenge */ DEBUG(DEBUG_HTTPAUTH, "New challenge.\n"); chall = ne_calloc(sizeof *chall); chall->next = challenges; challenges = chall; /* Initialize the challenge parameters */ /* Which auth-scheme is it (case-insensitive matching) */ if (strncasecmp(pairs[n], "basic ", 6) == 0) { DEBUG(DEBUG_HTTPAUTH, "Basic scheme.\n"); chall->scheme = http_auth_scheme_basic; } else if (strncasecmp(pairs[n], "digest ", 7) == 0) { DEBUG(DEBUG_HTTPAUTH, "Digest scheme.\n"); chall->scheme = http_auth_scheme_digest; } else { DEBUG(DEBUG_HTTPAUTH, "Unknown scheme.\n"); free(chall); challenges = NULL; break; } /* Now, the real key for this pair starts after the * auth-scheme... skipping whitespace */ while (strchr(HTTP_WHITESPACE, *(++pnt)) != NULL) /* nullop */; key = pnt; } else if (chall == NULL) { /* If we haven't got an auth-scheme, and we're * haven't yet found a challenge, skip this pair. */ continue; } else { key = pairs[n]; } DEBUG(DEBUG_HTTPAUTH, "Got pair: [%s] = [%s]\n", key, pairs[n+1]); /* Most values are quoted, so unquote them here */ unquoted = shave_string(pairs[n+1], '"'); /* Now parse the attribute */ DEBUG(DEBUG_HTTPAUTH, "Unquoted pair is: [%s]\n", unquoted); if (strcasecmp(key, "realm") == 0) { chall->realm = pairs[n+1]; } else if (strcasecmp(key, "nonce") == 0) { chall->nonce = pairs[n+1]; } else if (strcasecmp(key, "opaque") == 0) { chall->opaque = pairs[n+1]; } else if (strcasecmp(key, "domain") == 0) { chall->domain = pairs[n+1]; } else if (strcasecmp(key, "stale") == 0) { /* Truth value */ chall->stale = (strcasecmp(unquoted, "true") == 0); } else if (strcasecmp(key, "algorithm") == 0) { if (strcasecmp(unquoted, "md5") == 0) { chall->alg = http_auth_alg_md5; } else if (strcasecmp(unquoted, "md5-sess") == 0) { chall->alg = http_auth_alg_md5_sess; } else { chall->alg = http_auth_alg_unknown; } } else if (strcasecmp(key, "qop") == 0) { char **qops; int qop; qops = split_string(unquoted, ',', NULL, HTTP_WHITESPACE); chall->got_qop = 1; for (qop = 0; qops[qop] != NULL; qop++) { if (strcasecmp(qops[qop], "auth") == 0) { chall->qop_auth = 1; } else if (strcasecmp(qops[qop], "auth-int") == 0) { chall->qop_auth_int = 1; } } split_string_free(qops); } free(unquoted); } DEBUG(DEBUG_HTTPAUTH, "Finished parsing parameters.\n"); /* Did we find any challenges */ if (challenges == NULL) { pair_string_free(pairs); return -1; } success = 0; DEBUG(DEBUG_HTTPAUTH, "Looking for Digest challenges.\n"); /* Try a digest challenge */ for (chall = challenges; chall != NULL; chall = chall->next) { if (chall->scheme == http_auth_scheme_digest) { if (!digest_challenge(sess, chall)) { success = 1; break; } } } if (!success) { DEBUG(DEBUG_HTTPAUTH, "No good Digest challenges, looking for Basic.\n"); for (chall = challenges; chall != NULL; chall = chall->next) { if (chall->scheme == http_auth_scheme_basic) { if (!basic_challenge(sess, chall)) { success = 1; break; } } } if (!success) { /* No good challenges - record this in the session state */ DEBUG(DEBUG_HTTPAUTH, "Did not understand any challenges.\n"); } } /* Remember whether we can now supply the auth details */ sess->can_handle = success; while (challenges != NULL) { chall = challenges->next; free(challenges); challenges = chall; } /* Free up the parsed header values */ pair_string_free(pairs); return !success; }
/* Pass this the value of the 'Authentication-Info:' header field, if * one is received. * Returns: * 0 if it gives a valid authentication for the server * non-zero otherwise (don't believe the response in this case!). */ int http_auth_verify_response(http_auth_session *sess, const char *value) { char **pairs; http_auth_qop qop = http_auth_qop_none; char *nextnonce = NULL, /* for the nextnonce= value */ *rspauth = NULL, /* for the rspauth= value */ *cnonce = NULL, /* for the cnonce= value */ *nc = NULL, /* for the nc= value */ *unquoted, *qop_value = NULL; int n, nonce_count, okay; if (!sess->will_handle) { /* Ignore it */ return 0; } if (sess->scheme != http_auth_scheme_digest) { DEBUG(DEBUG_HTTPAUTH, "Found Auth-Info header not in response to Digest credentials - dodgy.\n"); return -1; } DEBUG (DEBUG_HTTPAUTH, "Auth-Info header: %s\n", value); pairs = pair_string(value, ',', '=', HTTP_QUOTES, HTTP_WHITESPACE); for (n = 0; pairs[n]!=NULL; n+=2) { unquoted = shave_string(pairs[n+1], '"'); if (strcasecmp(pairs[n], "qop") == 0) { qop_value = ne_strdup(pairs[n+1]); if (strcasecmp(pairs[n+1], "auth-int") == 0) { qop = http_auth_qop_auth_int; } else if (strcasecmp(pairs[n+1], "auth") == 0) { qop = http_auth_qop_auth; } else { qop = http_auth_qop_none; } } else if (strcasecmp(pairs[n], "nextnonce") == 0) { nextnonce = ne_strdup(unquoted); } else if (strcasecmp(pairs[n], "rspauth") == 0) { rspauth = ne_strdup(unquoted); } else if (strcasecmp(pairs[n], "cnonce") == 0) { cnonce = ne_strdup(unquoted); } else if (strcasecmp(pairs[n], "nc") == 0) { nc = ne_strdup(pairs[n]); if (sscanf(pairs[n+1], "%x", &nonce_count) != 1) { DEBUG(DEBUG_HTTPAUTH, "Couldn't scan [%s] for nonce count.\n", pairs[n+1]); } else { DEBUG(DEBUG_HTTPAUTH, "Got nonce_count: %d\n", nonce_count); } } free(unquoted); } pair_string_free(pairs); /* Presume the worst */ okay = -1; if ((qop != http_auth_qop_none) && (qop_value != NULL)) { if ((rspauth == NULL) || (cnonce == NULL) || (nc == NULL)) { DEBUG(DEBUG_HTTPAUTH, "Missing rspauth, cnonce or nc with qop.\n"); } else { /* Have got rspauth, cnonce and nc */ if (strcmp(cnonce, sess->unq_cnonce) != 0) { DEBUG(DEBUG_HTTPAUTH, "Response cnonce doesn't match.\n"); } else if (nonce_count != sess->nonce_count) { DEBUG(DEBUG_HTTPAUTH, "Response nonce count doesn't match.\n"); } else { /* Calculate and check the response-digest value. * joe: IMO the spec is slightly ambiguous as to whether * we use the qop which WE sent, or the qop which THEY * sent... */ struct md5_ctx a2; unsigned char a2_md5[16], rdig_md5[16]; char a2_md5_ascii[33], rdig_md5_ascii[33]; DEBUG(DEBUG_HTTPAUTH, "Calculating response-digest.\n"); /* First off, H(A2) again. */ md5_init_ctx(&a2); md5_process_bytes(":", 1, &a2); md5_process_bytes(sess->uri, strlen(sess->uri), &a2); if (qop == http_auth_qop_auth_int) { unsigned char heb_md5[16]; char heb_md5_ascii[33]; /* Add on ":" H(entity-body) */ md5_finish_ctx(&sess->response_body, heb_md5); md5_to_ascii(heb_md5, heb_md5_ascii); md5_process_bytes(":", 1, &a2); md5_process_bytes(heb_md5_ascii, 32, &a2); DEBUG(DEBUG_HTTPAUTH, "Digested [:%s]\n", heb_md5_ascii); } md5_finish_ctx(&a2, a2_md5); md5_to_ascii(a2_md5, a2_md5_ascii); /* We have the stored digest-so-far of * H(A1) ":" unq(nonce-value) * [ ":" nc-value ":" unq(cnonce-value) ] for qop * in sess->stored_rdig, to save digesting them again. * */ if (qop != http_auth_qop_none) { /* Add in qop-value */ DEBUG(DEBUG_HTTPAUTH, "Digesting qop-value [%s:].\n", qop_value); md5_process_bytes(qop_value, strlen(qop_value), &sess->stored_rdig); md5_process_bytes(":", 1, &sess->stored_rdig); } /* Digest ":" H(A2) */ md5_process_bytes(a2_md5_ascii, 32, &sess->stored_rdig); /* All done */ md5_finish_ctx(&sess->stored_rdig, rdig_md5); md5_to_ascii(rdig_md5, rdig_md5_ascii); DEBUG(DEBUG_HTTPAUTH, "Calculated response-digest of: [%s]\n", rdig_md5_ascii); DEBUG(DEBUG_HTTPAUTH, "Given response-digest of: [%s]\n", rspauth); /* And... do they match? */ okay = (strcasecmp(rdig_md5_ascii, rspauth) == 0)?0:-1; DEBUG(DEBUG_HTTPAUTH, "Matched: %s\n", okay?"nope":"YES!"); } free(rspauth); free(cnonce); free(nc); } free(qop_value); } else { DEBUG(DEBUG_HTTPAUTH, "No qop directive, auth okay.\n"); okay = 0; } /* Check for a nextnonce */ if (nextnonce != NULL) { DEBUG(DEBUG_HTTPAUTH, "Found nextnonce of [%s].\n", nextnonce); if (sess->unq_nonce != NULL) free(sess->unq_nonce); sess->unq_nonce = nextnonce; } return okay; }