summaryrefslogtreecommitdiff
path: root/auth/auth.c
diff options
context:
space:
mode:
Diffstat (limited to 'auth/auth.c')
-rw-r--r--auth/auth.c374
1 files changed, 374 insertions, 0 deletions
diff --git a/auth/auth.c b/auth/auth.c
new file mode 100644
index 0000000..13f822b
--- /dev/null
+++ b/auth/auth.c
@@ -0,0 +1,374 @@
+/* Copyright 2009 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "serf.h"
+#include "serf_private.h"
+#include "auth.h"
+
+#include <apr.h>
+#include <apr_base64.h>
+#include <apr_strings.h>
+
+static apr_status_t
+default_auth_response_handler(int code,
+ serf_connection_t *conn,
+ serf_request_t *request,
+ serf_bucket_t *response,
+ apr_pool_t *pool)
+{
+ return APR_SUCCESS;
+}
+
+static const serf__authn_scheme_t serf_authn_schemes[] = {
+ {
+ 401,
+ "Basic",
+ SERF_AUTHN_BASIC,
+ serf__init_basic,
+ serf__init_basic_connection,
+ serf__handle_basic_auth,
+ serf__setup_request_basic_auth,
+ default_auth_response_handler,
+ },
+ {
+ 407,
+ "Basic",
+ SERF_AUTHN_BASIC,
+ serf__init_basic,
+ serf__init_basic_connection,
+ serf__handle_basic_auth,
+ serf__setup_request_basic_auth,
+ default_auth_response_handler,
+ },
+ {
+ 401,
+ "Digest",
+ SERF_AUTHN_DIGEST,
+ serf__init_digest,
+ serf__init_digest_connection,
+ serf__handle_digest_auth,
+ serf__setup_request_digest_auth,
+ serf__validate_response_digest_auth,
+ },
+ {
+ 407,
+ "Digest",
+ SERF_AUTHN_DIGEST,
+ serf__init_digest,
+ serf__init_digest_connection,
+ serf__handle_digest_auth,
+ serf__setup_request_digest_auth,
+ serf__validate_response_digest_auth,
+ },
+#ifdef SERF_HAVE_KERB
+ {
+ 401,
+ "Negotiate",
+ SERF_AUTHN_NEGOTIATE,
+ serf__init_kerb,
+ serf__init_kerb_connection,
+ serf__handle_kerb_auth,
+ serf__setup_request_kerb_auth,
+ serf__validate_response_kerb_auth,
+ },
+#endif
+ /* ADD NEW AUTHENTICATION IMPLEMENTATIONS HERE (as they're written) */
+
+ /* sentinel */
+ { 0 }
+};
+
+
+/**
+ * Baton passed to the response header callback function
+ */
+typedef struct {
+ int code;
+ apr_status_t status;
+ const char *header;
+ serf_request_t *request;
+ serf_bucket_t *response;
+ void *baton;
+ apr_pool_t *pool;
+ const serf__authn_scheme_t *scheme;
+ const char *last_scheme_name;
+} auth_baton_t;
+
+/* Reads and discards all bytes in the response body. */
+static apr_status_t discard_body(serf_bucket_t *response)
+{
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ while (1) {
+ status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len);
+
+ if (status) {
+ return status;
+ }
+
+ /* feed me */
+ }
+}
+
+/**
+ * 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_header(void *baton,
+ const char *key,
+ const char *header)
+{
+ auth_baton_t *ab = baton;
+ int scheme_found = FALSE;
+ const char *auth_name;
+ const char *auth_attr;
+ const serf__authn_scheme_t *scheme = NULL;
+ serf_connection_t *conn = ab->request->conn;
+ serf_context_t *ctx = conn->ctx;
+
+ /* We're only interested in xxxx-Authenticate headers. */
+ if (strcmp(key, ab->header) != 0)
+ return 0;
+
+ /* Extract the authentication scheme name, and prepare for reading
+ the attributes. */
+ auth_attr = strchr(header, ' ');
+ if (auth_attr) {
+ auth_name = apr_pstrmemdup(ab->pool, header, auth_attr - header);
+ ++auth_attr;
+ }
+ else
+ auth_name = header;
+
+ ab->last_scheme_name = auth_name;
+
+ /* 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->code != 0; ++scheme) {
+ if (ab->code == scheme->code &&
+ strcmp(auth_name, scheme->name) == 0 &&
+ ctx->authn_types & scheme->type) {
+ serf__auth_handler_func_t handler = scheme->handle_func;
+ apr_status_t status = 0;
+
+ /* If this is the first time we use this scheme on this connection,
+ make sure to initialize the authentication handler first. */
+ if (ab->code == 401 && ctx->authn_info.scheme != scheme) {
+ status = scheme->init_ctx_func(ab->code, ctx, ctx->pool);
+ if (!status) {
+ status = scheme->init_conn_func(ab->code, conn, conn->pool);
+
+ if (!status)
+ ctx->authn_info.scheme = scheme;
+ else
+ ctx->authn_info.scheme = NULL;
+ }
+ }
+ else if (ab->code == 407 && ctx->proxy_authn_info.scheme != scheme) {
+ status = scheme->init_ctx_func(ab->code, ctx, ctx->pool);
+ if (!status) {
+ status = scheme->init_conn_func(ab->code, conn, conn->pool);
+
+ if (!status)
+ ctx->proxy_authn_info.scheme = scheme;
+ else
+ ctx->proxy_authn_info.scheme = NULL;
+ }
+ }
+
+ if (!status) {
+ scheme_found = TRUE;
+ ab->scheme = scheme;
+ status = handler(ab->code, ab->request, ab->response,
+ header, auth_attr, ab->baton, ctx->pool);
+ }
+
+ /* If the authentication fails, cache the error for now. Try the
+ next available scheme. If there's none raise the error. */
+ if (status) {
+ scheme_found = FALSE;
+ scheme = NULL;
+ }
+ /* Let the caller now if the authentication setup was succesful
+ or not. */
+ ab->status = status;
+
+ break;
+ }
+ }
+
+ /* If a matching scheme handler was found, we can stop iterating
+ over the response headers - so return a non-0 value. */
+ return scheme_found;
+}
+
+/* Dispatch authentication handling. This function matches the possible
+ authentication mechanisms with those available. Server and proxy
+ authentication are evaluated separately. */
+static apr_status_t dispatch_auth(int code,
+ serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *hdrs;
+
+ if (code == 401 || code == 407) {
+ auth_baton_t ab = { 0 };
+ const char *auth_hdr;
+
+ ab.code = code;
+ ab.status = APR_SUCCESS;
+ ab.request = request;
+ ab.response = response;
+ ab.baton = baton;
+ ab.pool = pool;
+
+ /* Before iterating over all authn headers, check if there are any. */
+ if (code == 401)
+ ab.header = "WWW-Authenticate";
+ else
+ ab.header = "Proxy-Authenticate";
+
+ hdrs = serf_bucket_response_get_headers(response);
+ auth_hdr = serf_bucket_headers_get(hdrs, ab.header);
+
+ if (!auth_hdr) {
+ return SERF_ERROR_AUTHN_FAILED;
+ }
+
+ /* Iterate over all headers. Try to find a matching authentication scheme
+ handler.
+
+ Note: it is possible to have multiple Authentication: headers. We do
+ not want to combine them (per normal header combination rules) as that
+ would make it hard to parse. Instead, we want to individually parse
+ and handle each header in the response, looking for one that we can
+ work with.
+ */
+ serf_bucket_headers_do(hdrs,
+ handle_auth_header,
+ &ab);
+ if (ab.status != APR_SUCCESS)
+ return ab.status;
+
+ if (!ab.scheme || ab.scheme->name == NULL) {
+ /* No matching authentication found. */
+ return SERF_ERROR_AUTHN_NOT_SUPPORTED;
+ }
+ } else {
+ /* Validate the response authn headers if needed. */
+
+ }
+
+ 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;
+
+ 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! */
+ serf_connection_priority_request_create(request->conn,
+ request->setup,
+ request->setup_baton);
+
+ return APR_EOF;
+ }
+
+ return APR_SUCCESS;
+}
+
+/**
+ * base64 encode the authentication data and build an authentication
+ * header in this format:
+ * [SCHEME] [BASE64 of auth DATA]
+ */
+void serf__encode_auth_header(const char **header,
+ const char *scheme,
+ const char *data, apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ apr_size_t encoded_len, scheme_len;
+ char *ptr;
+
+ encoded_len = apr_base64_encode_len(data_len);
+ scheme_len = strlen(scheme);
+
+ ptr = apr_palloc(pool, encoded_len + scheme_len + 1);
+ *header = ptr;
+
+ apr_cpystrn(ptr, scheme, scheme_len + 1);
+ ptr += scheme_len;
+ *ptr++ = ' ';
+
+ apr_base64_encode(ptr, data, data_len);
+}