/* Implements serf__setup_request_func_t callback. */ static apr_status_t serf__setup_request_basic_auth(const serf__authn_scheme_t *scheme, peer_t peer, int code, serf_connection_t *conn, serf_request_t *request, const char *method, const char *uri, serf_bucket_t *hdrs_bkt) { serf_context_t *ctx = conn->ctx; serf__authn_info_t *authn_info; basic_authn_info_t *basic_info; if (peer == HOST) { authn_info = serf__get_authn_info_for_server(conn); } else { authn_info = &ctx->proxy_authn_info; } basic_info = authn_info->baton; if (basic_info && basic_info->header && basic_info->value) { serf_bucket_headers_setn(hdrs_bkt, basic_info->header, basic_info->value); return APR_SUCCESS; } return SERF_ERROR_AUTHN_FAILED; }
/* For Basic authentication we expect all authn info to be the same for all connections in the context to the same server (same realm, username, password). Therefore we can keep the header value in the per-server store context instead of per connection. Implements serf__init_conn_func_t callback. TODO: we currently don't cache this info per realm, so each time a request 'switches realms', we have to ask the application for new credentials. */ static apr_status_t serf__init_basic_connection(const serf__authn_scheme_t *scheme, int code, serf_connection_t *conn, apr_pool_t *pool) { serf_context_t *ctx = conn->ctx; serf__authn_info_t *authn_info; if (code == 401) { authn_info = serf__get_authn_info_for_server(conn); } else { authn_info = &ctx->proxy_authn_info; } if (!authn_info->baton) { authn_info->baton = apr_pcalloc(pool, sizeof(basic_authn_info_t)); } return APR_SUCCESS; }
/* Implements serf__init_conn_func_t callback. */ static apr_status_t serf__init_digest_connection(const serf__authn_scheme_t *scheme, int code, serf_connection_t *conn, apr_pool_t *pool) { serf_context_t *ctx = conn->ctx; serf__authn_info_t *authn_info; if (code == 401) { authn_info = serf__get_authn_info_for_server(conn); } else { authn_info = &ctx->proxy_authn_info; } if (!authn_info->baton) { authn_info->baton = apr_pcalloc(pool, sizeof(digest_authn_info_t)); } /* Make serf send the initial requests one by one */ serf__connection_set_pipelining(conn, 0); return APR_SUCCESS; }
/* do_auth is invoked in two situations: - when a response from a server is received that contains an authn header (either from a 40x or 2xx response) - when a request is prepared on a connection with stateless authentication. Read the header sent by the server (if any), invoke the gssapi authn code and use the resulting Server Ticket on the next request to the server. */ static apr_status_t do_auth(peer_t peer, int code, gss_authn_info_t *gss_info, serf_connection_t *conn, serf_request_t *request, const char *auth_hdr, apr_pool_t *pool) { serf_context_t *ctx = conn->ctx; serf__authn_info_t *authn_info; const char *tmp = NULL; char *token = NULL; apr_size_t tmp_len = 0, token_len = 0; apr_status_t status; if (peer == HOST) { authn_info = serf__get_authn_info_for_server(conn); } else { authn_info = &ctx->proxy_authn_info; } /* Is this a response from a host/proxy? auth_hdr should always be set. */ if (code && auth_hdr) { const char *space = NULL; /* The server will return a token as attribute to the Negotiate key. Negotiate YGwGCSqGSIb3EgECAgIAb10wW6ADAgEFoQMCAQ+iTzBNoAMCARCiRgREa6 mouMBAMFqKVdTGtfpZNXKzyw4Yo1paphJdIA3VOgncaoIlXxZLnkHiIHS2v65pVvrp bRIyjF8xve9HxpnNIucCY9c= Read this base64 value, decode it and validate it so we're sure the server is who we expect it to be. */ space = strchr(auth_hdr, ' '); if (space) { token = apr_palloc(pool, apr_base64_decode_len(space + 1)); token_len = apr_base64_decode(token, space + 1); } } else { /* This is a new request, not a retry in response to a 40x of the host/proxy. Only add the Authorization header if we know the server requires per-request authentication (stateless). */ if (gss_info->pstate != pstate_stateless) return APR_SUCCESS; } switch(gss_info->pstate) { case pstate_init: /* Nothing to do here */ break; case pstate_undecided: /* Fall through */ case pstate_stateful: { /* Switch to stateless mode, from now on handle authentication of each request with a new gss context. This is easiest to manage when sending requests one by one. */ serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt, "Server requires per-request SPNEGO authn, " "switching to stateless mode.\n"); gss_info->pstate = pstate_stateless; serf_connection_set_max_outstanding_requests(conn, 1); break; } case pstate_stateless: /* Nothing to do here */ break; } if (request->auth_baton && !token) { /* We provided token with this request, but server responded with empty authentication header. This means server rejected our credentials. XXX: Probably we need separate error code for this case like SERF_ERROR_AUTHN_CREDS_REJECTED? */ return SERF_ERROR_AUTHN_FAILED; } /* If the server didn't provide us with a token, start with a new initial step in the SPNEGO authentication. */ if (!token) { serf__spnego_reset_sec_context(gss_info->gss_ctx); gss_info->state = gss_api_auth_not_started; } if (peer == HOST) { status = gss_api_get_credentials(conn, token, token_len, conn->host_info.hostname, &tmp, &tmp_len, gss_info); } else { char *proxy_host = conn->ctx->proxy_address->hostname; status = gss_api_get_credentials(conn, token, token_len, proxy_host, &tmp, &tmp_len, gss_info); } if (status) return status; /* On the next request, add an Authorization header. */ if (tmp_len) { serf__encode_auth_header(&gss_info->value, authn_info->scheme->name, tmp, tmp_len, pool); gss_info->header = (peer == HOST) ? "Authorization" : "Proxy-Authorization"; } return APR_SUCCESS; }
/* Read the headers of the response and try the available handlers if authentication or validation is needed. */ apr_status_t serf__handle_auth_response(int *consumed_response, serf_request_t *request, serf_bucket_t *response, void *baton, apr_pool_t *pool) { apr_status_t status; serf_status_line sl; *consumed_response = 0; /* TODO: the response bucket was created by the application, not at all guaranteed that this is of type response_bucket!! */ status = serf_bucket_response_status(response, &sl); if (SERF_BUCKET_READ_ERROR(status)) { return status; } if (!sl.version && (APR_STATUS_IS_EOF(status) || APR_STATUS_IS_EAGAIN(status))) { return status; } status = serf_bucket_response_wait_for_headers(response); if (status) { if (!APR_STATUS_IS_EOF(status)) { return status; } /* If status is APR_EOF, there were no headers to read. This can be ok in some situations, and it definitely means there's no authentication requested now. */ return APR_SUCCESS; } if (sl.code == 401 || sl.code == 407) { /* Authentication requested. */ /* Don't bother handling the authentication request if the response wasn't received completely yet. Serf will call serf__handle_auth_response again when more data is received. */ status = discard_body(response); *consumed_response = 1; /* Discard all response body before processing authentication. */ if (!APR_STATUS_IS_EOF(status)) { return status; } status = dispatch_auth(sl.code, request, response, baton, pool); if (status != APR_SUCCESS) { return status; } /* Requeue the request with the necessary auth headers. */ /* ### Application doesn't know about this request! */ if (request->ssltunnel) { serf__ssltunnel_request_create(request->conn, request->setup, request->setup_baton); } else { serf_connection_priority_request_create(request->conn, request->setup, request->setup_baton); } return APR_EOF; } else { serf__validate_response_func_t validate_resp; serf_connection_t *conn = request->conn; serf_context_t *ctx = conn->ctx; serf__authn_info_t *authn_info; apr_status_t resp_status = APR_SUCCESS; /* Validate the response server authn headers. */ authn_info = serf__get_authn_info_for_server(conn); if (authn_info->scheme) { validate_resp = authn_info->scheme->validate_response_func; resp_status = validate_resp(authn_info->scheme, HOST, sl.code, conn, request, response, pool); } /* Validate the response proxy authn headers. */ authn_info = &ctx->proxy_authn_info; if (!resp_status && authn_info->scheme) { validate_resp = authn_info->scheme->validate_response_func; resp_status = validate_resp(authn_info->scheme, PROXY, sl.code, conn, request, response, pool); } if (resp_status) { /* If there was an error in the final step of the authentication, consider the reponse body as invalid and discard it. */ status = discard_body(response); *consumed_response = 1; if (!APR_STATUS_IS_EOF(status)) { return status; } /* The whole body was discarded, now return our error. */ return resp_status; } } return APR_SUCCESS; }
/** * handle_auth_header is called for each header in the response. It filters * out the Authenticate headers (WWW or Proxy depending on what's needed) and * tries to find a matching scheme handler. * * Returns a non-0 value of a matching handler was found. */ static int handle_auth_headers(int code, void *baton, apr_hash_t *hdrs, serf_request_t *request, serf_bucket_t *response, apr_pool_t *pool) { const serf__authn_scheme_t *scheme; serf_connection_t *conn = request->conn; serf_context_t *ctx = conn->ctx; apr_status_t status; status = SERF_ERROR_AUTHN_NOT_SUPPORTED; /* Find the matching authentication handler. Note that we don't reuse the auth scheme stored in the context, as that may have changed. (ex. fallback from ntlm to basic.) */ for (scheme = serf_authn_schemes; scheme->name != 0; ++scheme) { const char *auth_hdr; serf__auth_handler_func_t handler; serf__authn_info_t *authn_info; if (! (ctx->authn_types & scheme->type)) continue; serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt, "Client supports: %s\n", scheme->name); auth_hdr = apr_hash_get(hdrs, scheme->key, APR_HASH_KEY_STRING); if (!auth_hdr) continue; if (code == 401) { authn_info = serf__get_authn_info_for_server(conn); } else { authn_info = &ctx->proxy_authn_info; } if (authn_info->failed_authn_types & scheme->type) { /* Skip this authn type since we already tried it before. */ continue; } /* Found a matching scheme */ status = APR_SUCCESS; handler = scheme->handle_func; serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt, "... matched: %s\n", scheme->name); /* If this is the first time we use this scheme on this context and/or this connection, make sure to initialize the authentication handler first. */ if (authn_info->scheme != scheme) { status = scheme->init_ctx_func(code, ctx, ctx->pool); if (!status) { status = scheme->init_conn_func(scheme, code, conn, conn->pool); if (!status) authn_info->scheme = scheme; else authn_info->scheme = NULL; } } if (!status) { const char *auth_attr = strchr(auth_hdr, ' '); if (auth_attr) { auth_attr++; } status = handler(code, request, response, auth_hdr, auth_attr, baton, ctx->pool); } if (status == APR_SUCCESS) break; /* No success authenticating with this scheme, try the next. If no more authn schemes are found the status of this scheme will be returned. */ serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt, "%s authentication failed.\n", scheme->name); /* Clear per-request auth_baton when switching to next auth scheme. */ request->auth_baton = NULL; /* Remember failed auth types to skip in future. */ authn_info->failed_authn_types |= scheme->type; } return status; }
/* Implements serf__validate_response_func_t callback. */ static apr_status_t serf__validate_response_digest_auth(const serf__authn_scheme_t *scheme, peer_t peer, int code, serf_connection_t *conn, serf_request_t *request, serf_bucket_t *response, apr_pool_t *pool) { const char *key; char *auth_attr; char *nextkv; const char *rspauth = NULL; const char *qop = NULL; const char *nc_str = NULL; serf_bucket_t *hdrs; serf_context_t *ctx = conn->ctx; apr_status_t status; hdrs = serf_bucket_response_get_headers(response); /* Need a copy cuz we're going to write NUL characters into the string. */ if (peer == HOST) auth_attr = apr_pstrdup(pool, serf_bucket_headers_get(hdrs, "Authentication-Info")); else auth_attr = apr_pstrdup(pool, serf_bucket_headers_get(hdrs, "Proxy-Authentication-Info")); /* If there's no Authentication-Info header there's nothing to validate. */ if (! auth_attr) return APR_SUCCESS; /* We're expecting a list of key=value pairs, separated by a comma. Ex. rspauth="8a4b8451084b082be6b105e2b7975087", cnonce="346531653132652d303033392d3435", nc=00000007, qop=auth */ for ( ; (key = apr_strtok(auth_attr, ",", &nextkv)) != NULL; auth_attr = NULL) { char *val; val = strchr(key, '='); if (val == NULL) continue; *val++ = '\0'; /* skip leading spaces */ while (*key && *key == ' ') key++; /* If the value is quoted, then remove the quotes. */ if (*val == '"') { apr_size_t last = strlen(val) - 1; if (val[last] == '"') { val[last] = '\0'; val++; } } if (strcmp(key, "rspauth") == 0) rspauth = val; else if (strcmp(key, "qop") == 0) qop = val; else if (strcmp(key, "nc") == 0) nc_str = val; } if (rspauth) { const char *ha2, *tmp, *resp_hdr_hex; unsigned char resp_hdr[APR_MD5_DIGESTSIZE]; const char *req_uri = request->auth_baton; serf__authn_info_t *authn_info; digest_authn_info_t *digest_info; if (peer == HOST) { authn_info = serf__get_authn_info_for_server(conn); } else { authn_info = &ctx->proxy_authn_info; } digest_info = authn_info->baton; status = build_digest_ha2(&ha2, req_uri, "", qop, pool); if (status) return status; tmp = apr_psprintf(pool, "%s:%s:%s:%s:%s:%s", digest_info->ha1, digest_info->nonce, nc_str, digest_info->cnonce, digest_info->qop, ha2); apr_md5(resp_hdr, tmp, strlen(tmp)); resp_hdr_hex = hex_encode(resp_hdr, pool); /* Incorrect response-digest in Authentication-Info header. */ if (strcmp(rspauth, resp_hdr_hex) != 0) { return SERF_ERROR_AUTHN_FAILED; } } return APR_SUCCESS; }
/* Implements serf__setup_request_func_t callback. */ static apr_status_t serf__setup_request_digest_auth(peer_t peer, int code, serf_connection_t *conn, serf_request_t *request, const char *method, const char *uri, serf_bucket_t *hdrs_bkt) { serf_context_t *ctx = conn->ctx; serf__authn_info_t *authn_info; digest_authn_info_t *digest_info; apr_status_t status; if (peer == HOST) { authn_info = serf__get_authn_info_for_server(conn); } else { authn_info = &ctx->proxy_authn_info; } digest_info = authn_info->baton; if (digest_info && digest_info->realm) { const char *value; const char *path; /* TODO: per request pool? */ /* for request 'CONNECT serf.googlecode.com:443', the uri also should be serf.googlecode.com:443. apr_uri_parse can't handle this, so special case. */ if (strcmp(method, "CONNECT") == 0) path = uri; else { apr_uri_t parsed_uri; /* Extract path from uri. */ status = apr_uri_parse(conn->pool, uri, &parsed_uri); if (status) return status; path = parsed_uri.path; } /* Build a new Authorization header. */ digest_info->header = (peer == HOST) ? "Authorization" : "Proxy-Authorization"; status = build_auth_header(&value, digest_info, path, method, conn->pool); if (status) return status; serf_bucket_headers_setn(hdrs_bkt, digest_info->header, value); digest_info->digest_nc++; /* Store the uri of this request on the serf_request_t object, to make it available when validating the Authentication-Info header of the matching response. */ request->auth_baton = (void *)path; } return APR_SUCCESS; }
/* Implements serf__auth_handler_func_t callback. */ static apr_status_t serf__handle_digest_auth(int code, serf_request_t *request, serf_bucket_t *response, const char *auth_hdr, const char *auth_attr, apr_pool_t *pool) { char *attrs; char *nextkv; const char *realm, *realm_name = NULL; const char *nonce = NULL; const char *algorithm = NULL; const char *qop = NULL; const char *opaque = NULL; const char *key; serf_connection_t *conn = request->conn; serf_context_t *ctx = conn->ctx; serf__authn_info_t *authn_info; digest_authn_info_t *digest_info; apr_status_t status; apr_pool_t *cred_pool; char *username, *password; /* Can't do Digest authentication if there's no callback to get username & password. */ if (!ctx->cred_cb) { return SERF_ERROR_AUTHN_FAILED; } if (code == 401) { authn_info = serf__get_authn_info_for_server(conn); } else { authn_info = &ctx->proxy_authn_info; } digest_info = authn_info->baton; /* Need a copy cuz we're going to write NUL characters into the string. */ attrs = apr_pstrdup(pool, auth_attr); /* We're expecting a list of key=value pairs, separated by a comma. Ex. realm="SVN Digest", nonce="f+zTl/leBAA=e371bd3070adfb47b21f5fc64ad8cc21adc371a5", algorithm=MD5, qop="auth" */ for ( ; (key = apr_strtok(attrs, ",", &nextkv)) != NULL; attrs = NULL) { char *val; val = strchr(key, '='); if (val == NULL) continue; *val++ = '\0'; /* skip leading spaces */ while (*key && *key == ' ') key++; /* If the value is quoted, then remove the quotes. */ if (*val == '"') { apr_size_t last = strlen(val) - 1; if (val[last] == '"') { val[last] = '\0'; val++; } } if (strcmp(key, "realm") == 0) realm_name = val; else if (strcmp(key, "nonce") == 0) nonce = val; else if (strcmp(key, "algorithm") == 0) algorithm = val; else if (strcmp(key, "qop") == 0) qop = val; else if (strcmp(key, "opaque") == 0) opaque = val; /* Ignore all unsupported attributes. */ } if (!realm_name) { return SERF_ERROR_AUTHN_MISSING_ATTRIBUTE; } realm = serf__construct_realm(code == 401 ? HOST : PROXY, conn, realm_name, pool); /* Ask the application for credentials */ apr_pool_create(&cred_pool, pool); status = serf__provide_credentials(ctx, &username, &password, request, code, authn_info->scheme->name, realm, cred_pool); if (status) { apr_pool_destroy(cred_pool); return status; } digest_info->header = (code == 401) ? "Authorization" : "Proxy-Authorization"; /* Store the digest authentication parameters in the context cached for this server in the serf context, so we can use it to create the Authorization header when setting up requests on the same or different connections (e.g. in case of KeepAlive off on the server). TODO: we currently don't cache this info per realm, so each time a request 'switches realms', we have to ask the application for new credentials. */ digest_info->pool = conn->pool; digest_info->qop = apr_pstrdup(digest_info->pool, qop); digest_info->nonce = apr_pstrdup(digest_info->pool, nonce); digest_info->cnonce = NULL; digest_info->opaque = apr_pstrdup(digest_info->pool, opaque); digest_info->algorithm = apr_pstrdup(digest_info->pool, algorithm); digest_info->realm = apr_pstrdup(digest_info->pool, realm_name); digest_info->username = apr_pstrdup(digest_info->pool, username); digest_info->digest_nc++; status = build_digest_ha1(&digest_info->ha1, username, password, digest_info->realm, digest_info->pool); apr_pool_destroy(cred_pool); /* If the handshake is finished tell serf it can send as much requests as it likes. */ serf__connection_set_pipelining(conn, 1); return status; }
/* Implements serf__auth_handler_func_t callback. */ static apr_status_t serf__handle_basic_auth(const serf__authn_scheme_t *scheme, int code, serf_request_t *request, serf_bucket_t *response, const char *auth_hdr, const char *auth_attr, apr_pool_t *pool) { const char *tmp; apr_size_t tmp_len; serf_connection_t *conn = request->conn; serf_context_t *ctx = conn->ctx; serf__authn_info_t *authn_info; basic_authn_info_t *basic_info; apr_status_t status; apr_pool_t *cred_pool; char *username, *password, *realm_name; const char *eq, *realm = NULL; /* Can't do Basic authentication if there's no callback to get username & password. */ if (!ctx->cred_cb) { return SERF_ERROR_AUTHN_FAILED; } if (code == 401) { authn_info = serf__get_authn_info_for_server(conn); } else { authn_info = &ctx->proxy_authn_info; } basic_info = authn_info->baton; realm_name = NULL; eq = strchr(auth_attr, '='); if (eq && strncasecmp(auth_attr, "realm", 5) == 0) { realm_name = apr_pstrdup(pool, eq + 1); if (realm_name[0] == '\"') { apr_size_t realm_len; realm_len = strlen(realm_name); if (realm_name[realm_len - 1] == '\"') { realm_name[realm_len - 1] = '\0'; realm_name++; } } if (!realm_name) { return SERF_ERROR_AUTHN_MISSING_ATTRIBUTE; } realm = serf__construct_realm(code == 401 ? HOST : PROXY, conn, realm_name, pool); } /* Ask the application for credentials */ apr_pool_create(&cred_pool, pool); status = serf__provide_credentials(ctx, &username, &password, request, code, scheme->name, realm, cred_pool); if (status) { apr_pool_destroy(cred_pool); return status; } tmp = apr_pstrcat(conn->pool, username, ":", password, NULL); tmp_len = strlen(tmp); apr_pool_destroy(cred_pool); serf__encode_auth_header(&basic_info->value, scheme->name, tmp, tmp_len, pool); basic_info->header = (code == 401) ? "Authorization" : "Proxy-Authorization"; return APR_SUCCESS; }