From 74dd0183e4e56b07cedfa87eae7a8fb3166f01e8 Mon Sep 17 00:00:00 2001 From: Lorry Date: Tue, 28 Aug 2012 15:30:14 +0100 Subject: Tarball conversion --- auth/auth.c | 374 +++++++++++++++++++++++++++++++++++++++ auth/auth.h | 105 +++++++++++ auth/auth_basic.c | 155 ++++++++++++++++ auth/auth_digest.c | 475 ++++++++++++++++++++++++++++++++++++++++++++++++++ auth/auth_kerb.c | 350 +++++++++++++++++++++++++++++++++++++ auth/auth_kerb.h | 103 +++++++++++ auth/auth_kerb_gss.c | 146 ++++++++++++++++ auth/auth_kerb_sspi.c | 210 ++++++++++++++++++++++ 8 files changed, 1918 insertions(+) create mode 100644 auth/auth.c create mode 100644 auth/auth.h create mode 100644 auth/auth_basic.c create mode 100644 auth/auth_digest.c create mode 100644 auth/auth_kerb.c create mode 100644 auth/auth_kerb.h create mode 100644 auth/auth_kerb_gss.c create mode 100644 auth/auth_kerb_sspi.c (limited to 'auth') 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 +#include +#include + +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); +} diff --git a/auth/auth.h b/auth/auth.h new file mode 100644 index 0000000..1f2c751 --- /dev/null +++ b/auth/auth.h @@ -0,0 +1,105 @@ +/* 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. + */ + +#ifndef AUTH_H +#define AUTH_H + +#include "auth_kerb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void serf__encode_auth_header(const char **header, const char *protocol, + const char *data, apr_size_t data_len, + apr_pool_t *pool); + +/** Basic authentication **/ +apr_status_t serf__init_basic(int code, + serf_context_t *ctx, + apr_pool_t *pool); +apr_status_t serf__init_basic_connection(int code, + serf_connection_t *conn, + apr_pool_t *pool); +apr_status_t serf__handle_basic_auth(int code, + serf_request_t *request, + serf_bucket_t *response, + const char *auth_hdr, + const char *auth_attr, + void *baton, + apr_pool_t *pool); +apr_status_t serf__setup_request_basic_auth(int code, + serf_connection_t *conn, + const char *method, + const char *uri, + serf_bucket_t *hdrs_bkt); + +/** Digest authentication **/ +apr_status_t serf__init_digest(int code, + serf_context_t *ctx, + apr_pool_t *pool); +apr_status_t serf__init_digest_connection(int code, + serf_connection_t *conn, + apr_pool_t *pool); +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, + void *baton, + apr_pool_t *pool); +apr_status_t serf__setup_request_digest_auth(int code, + serf_connection_t *conn, + const char *method, + const char *uri, + serf_bucket_t *hdrs_bkt); +apr_status_t serf__validate_response_digest_auth(int code, + serf_connection_t *conn, + serf_request_t *request, + serf_bucket_t *response, + apr_pool_t *pool); + +#ifdef SERF_HAVE_KERB +/** Kerberos authentication **/ +apr_status_t serf__init_kerb(int code, + serf_context_t *ctx, + apr_pool_t *pool); +apr_status_t serf__init_kerb_connection(int code, + serf_connection_t *conn, + apr_pool_t *pool); +apr_status_t serf__handle_kerb_auth(int code, + serf_request_t *request, + serf_bucket_t *response, + const char *auth_hdr, + const char *auth_attr, + void *baton, + apr_pool_t *pool); +apr_status_t serf__setup_request_kerb_auth(int code, + serf_connection_t *conn, + const char *method, + const char *uri, + serf_bucket_t *hdrs_bkt); +apr_status_t serf__validate_response_kerb_auth(int code, + serf_connection_t *conn, + serf_request_t *request, + serf_bucket_t *response, + apr_pool_t *pool); +#endif /* SERF_HAVE_SPNEGO */ + +#ifdef __cplusplus +} +#endif + +#endif /* !AUTH_H */ diff --git a/auth/auth_basic.c b/auth/auth_basic.c new file mode 100644 index 0000000..b876cb8 --- /dev/null +++ b/auth/auth_basic.c @@ -0,0 +1,155 @@ +/* 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. + */ + +/*** Basic authentication ***/ + +#include +#include +#include + +#include +#include +#include + +typedef struct basic_authn_info_t { + const char *header; + const char *value; +} basic_authn_info_t; + +apr_status_t +serf__handle_basic_auth(int code, + serf_request_t *request, + serf_bucket_t *response, + const char *auth_hdr, + const char *auth_attr, + void *baton, + 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 = (code == 401) ? &ctx->authn_info : + &ctx->proxy_authn_info; + basic_authn_info_t *basic_info = authn_info->baton; + apr_status_t status; + apr_pool_t *cred_pool; + char *username, *password; + + /* Can't do Basic authentication if there's no callback to get + username & password. */ + if (!ctx->cred_cb) { + return SERF_ERROR_AUTHN_FAILED; + } + + if (!authn_info->realm) { + char *realm_name = NULL; + const char *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; + } + + authn_info->realm = apr_psprintf(conn->pool, "<%s://%s:%d> %s", + conn->host_info.scheme, + conn->host_info.hostname, + conn->host_info.port, + realm_name); + } + + /* Ask the application for credentials */ + apr_pool_create(&cred_pool, pool); + status = (*ctx->cred_cb)(&username, &password, request, baton, + code, authn_info->scheme->name, + authn_info->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, + authn_info->scheme->name, + tmp, tmp_len, pool); + basic_info->header = (code == 401) ? "Authorization" : "Proxy-Authorization"; + + return APR_SUCCESS; +} + +/* For Basic authentication we expect all authn info to be the same for all + connections in the context (same realm, username, password). Therefore we + can keep the header value in the context instead of per connection. */ +apr_status_t +serf__init_basic(int code, + serf_context_t *ctx, + apr_pool_t *pool) +{ + if (code == 401) { + ctx->authn_info.baton = apr_pcalloc(pool, sizeof(basic_authn_info_t)); + } else { + ctx->proxy_authn_info.baton = apr_pcalloc(pool, sizeof(basic_authn_info_t)); + } + + return APR_SUCCESS; +} + +apr_status_t +serf__init_basic_connection(int code, + serf_connection_t *conn, + apr_pool_t *pool) +{ + return APR_SUCCESS; +} + +apr_status_t +serf__setup_request_basic_auth(int code, + serf_connection_t *conn, + const char *method, + const char *uri, + serf_bucket_t *hdrs_bkt) +{ + serf_context_t *ctx = conn->ctx; + basic_authn_info_t *authn_info; + + if (code == 401) { + authn_info = ctx->authn_info.baton; + } else { + authn_info = ctx->proxy_authn_info.baton; + } + + if (authn_info && authn_info->header && authn_info->value) { + serf_bucket_headers_setn(hdrs_bkt, authn_info->header, + authn_info->value); + return APR_SUCCESS; + } + + return SERF_ERROR_AUTHN_FAILED; +} diff --git a/auth/auth_digest.c b/auth/auth_digest.c new file mode 100644 index 0000000..2e29180 --- /dev/null +++ b/auth/auth_digest.c @@ -0,0 +1,475 @@ +/* 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. + */ + +/*** Digest authentication ***/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +/** Digest authentication, implements RFC 2617. **/ + +/* Stores the context information related to Digest authentication. + The context is per connection. */ +typedef struct digest_authn_info_t { + /* nonce-count for digest authentication */ + unsigned int digest_nc; + + const char *header; + + const char *ha1; + + const char *realm; + const char *cnonce; + const char *nonce; + const char *opaque; + const char *algorithm; + const char *qop; + const char *username; + + apr_pool_t *pool; +} digest_authn_info_t; + +static char +int_to_hex(int v) +{ + return (v < 10) ? '0' + v : 'a' + (v - 10); +} + +/** + * Convert a string if ASCII characters HASHVAL to its hexadecimal + * representation. + * + * The returned string will be allocated in the POOL and be null-terminated. + */ +static const char * +hex_encode(const unsigned char *hashval, + apr_pool_t *pool) +{ + int i; + char *hexval = apr_palloc(pool, (APR_MD5_DIGESTSIZE * 2) + 1); + for (i = 0; i < APR_MD5_DIGESTSIZE; i++) { + hexval[2 * i] = int_to_hex((hashval[i] >> 4) & 0xf); + hexval[2 * i + 1] = int_to_hex(hashval[i] & 0xf); + } + hexval[APR_MD5_DIGESTSIZE * 2] = '\0'; + return hexval; +} + +/** + * Returns a 36-byte long string of random characters. + * UUIDs are formatted as: 00112233-4455-6677-8899-AABBCCDDEEFF. + * + * The returned string will be allocated in the POOL and be null-terminated. + */ +static const char * +random_cnonce(apr_pool_t *pool) +{ + apr_uuid_t uuid; + char *buf = apr_palloc(pool, APR_UUID_FORMATTED_LENGTH + 1); + + apr_uuid_get(&uuid); + apr_uuid_format(buf, &uuid); + + return hex_encode((unsigned char*)buf, pool); +} + +static const char * +build_digest_ha1(const char *username, + const char *password, + const char *realm_name, + apr_pool_t *pool) +{ + const char *tmp; + unsigned char ha1[APR_MD5_DIGESTSIZE]; + apr_status_t status; + + /* calculate ha1: + MD5 hash of the combined user name, authentication realm and password */ + tmp = apr_psprintf(pool, "%s:%s:%s", + username, + realm_name, + password); + status = apr_md5(ha1, tmp, strlen(tmp)); + + return hex_encode(ha1, pool); +} + +static const char * +build_digest_ha2(const char *uri, + const char *method, + const char *qop, + apr_pool_t *pool) +{ + if (!qop || strcmp(qop, "auth") == 0) { + const char *tmp; + unsigned char ha2[APR_MD5_DIGESTSIZE]; + apr_status_t status; + + /* calculate ha2: + MD5 hash of the combined method and URI */ + tmp = apr_psprintf(pool, "%s:%s", + method, + uri); + status = apr_md5(ha2, tmp, strlen(tmp)); + + return hex_encode(ha2, pool); + } else { + /* TODO: auth-int isn't supported! */ + } + + return NULL; +} + +static const char * +build_auth_header(digest_authn_info_t *digest_info, + const char *path, + const char *method, + apr_pool_t *pool) +{ + char *hdr; + const char *ha2; + const char *response; + unsigned char response_hdr[APR_MD5_DIGESTSIZE]; + const char *response_hdr_hex; + apr_status_t status; + + ha2 = build_digest_ha2(path, method, digest_info->qop, pool); + + hdr = apr_psprintf(pool, + "Digest realm=\"%s\"," + " username=\"%s\"," + " nonce=\"%s\"," + " uri=\"%s\"", + digest_info->realm, digest_info->username, + digest_info->nonce, + path); + + if (digest_info->qop) { + if (! digest_info->cnonce) + digest_info->cnonce = random_cnonce(digest_info->pool); + + hdr = apr_psprintf(pool, "%s, nc=%08x, cnonce=\"%s\", qop=\"%s\"", + hdr, + digest_info->digest_nc, + digest_info->cnonce, + digest_info->qop); + + /* Build the response header: + MD5 hash of the combined HA1 result, server nonce (nonce), + request counter (nc), client nonce (cnonce), + quality of protection code (qop) and HA2 result. */ + response = apr_psprintf(pool, "%s:%s:%08x:%s:%s:%s", + digest_info->ha1, digest_info->nonce, + digest_info->digest_nc, + digest_info->cnonce, digest_info->qop, ha2); + } else { + /* Build the response header: + MD5 hash of the combined HA1 result, server nonce (nonce) + and HA2 result. */ + response = apr_psprintf(pool, "%s:%s:%s", + digest_info->ha1, digest_info->nonce, ha2); + } + + status = apr_md5(response_hdr, response, strlen(response)); + response_hdr_hex = hex_encode(response_hdr, pool); + + hdr = apr_psprintf(pool, "%s, response=\"%s\"", hdr, response_hdr_hex); + + if (digest_info->opaque) { + hdr = apr_psprintf(pool, "%s, opaque=\"%s\"", hdr, + digest_info->opaque); + } + if (digest_info->algorithm) { + hdr = apr_psprintf(pool, "%s, algorithm=\"%s\"", hdr, + digest_info->algorithm); + } + + return hdr; +} + +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, + void *baton, + apr_pool_t *pool) +{ + char *attrs; + char *nextkv; + const char *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 = (code == 401) ? &ctx->authn_info : + &ctx->proxy_authn_info; + digest_authn_info_t *digest_info = (code == 401) ? conn->authn_baton : + conn->proxy_authn_baton; + 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; + } + + /* 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; + } + + authn_info->realm = apr_psprintf(conn->pool, "<%s://%s:%d> %s", + conn->host_info.scheme, + conn->host_info.hostname, + conn->host_info.port, + realm_name); + + /* Ask the application for credentials */ + apr_pool_create(&cred_pool, pool); + status = (*ctx->cred_cb)(&username, &password, request, baton, + code, authn_info->scheme->name, + authn_info->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 relative + to this connection, so we can use it to create the Authorization header + when setting up requests. */ + 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++; + + digest_info->ha1 = build_digest_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_max_outstanding_requests(conn, 0); + + return APR_SUCCESS; +} + +apr_status_t +serf__init_digest(int code, + serf_context_t *ctx, + apr_pool_t *pool) +{ + return APR_SUCCESS; +} + +apr_status_t +serf__init_digest_connection(int code, + serf_connection_t *conn, + apr_pool_t *pool) +{ + /* Digest authentication is done per connection, so keep all progress + information per connection. */ + if (code == 401) { + conn->authn_baton = apr_pcalloc(pool, sizeof(digest_authn_info_t)); + } else { + conn->proxy_authn_baton = apr_pcalloc(pool, sizeof(digest_authn_info_t)); + } + + /* Make serf send the initial requests one by one */ + serf_connection_set_max_outstanding_requests(conn, 1); + + return APR_SUCCESS; +} + +apr_status_t +serf__setup_request_digest_auth(int code, + serf_connection_t *conn, + const char *method, + const char *uri, + serf_bucket_t *hdrs_bkt) +{ + digest_authn_info_t *digest_info = (code == 401) ? conn->authn_baton : + conn->proxy_authn_baton; + apr_status_t status = APR_SUCCESS; + + if (digest_info && digest_info->realm) { + const char *value; + apr_uri_t parsed_uri; + + /* extract path from uri */ + status = apr_uri_parse(conn->pool, uri, &parsed_uri); + + /* Build a new Authorization header. */ + digest_info->header = (code == 401) ? "Authorization" : + "Proxy-Authorization"; + value = build_auth_header(digest_info, parsed_uri.path, method, + conn->pool); + + serf_bucket_headers_setn(hdrs_bkt, digest_info->header, + value); + digest_info->digest_nc++; + } + + return status; +} + +apr_status_t +serf__validate_response_digest_auth(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; + digest_authn_info_t *digest_info = (code == 401) ? conn->authn_baton : + conn->proxy_authn_baton; + + hdrs = serf_bucket_response_get_headers(response); + + /* Need a copy cuz we're going to write NUL characters into the string. */ + if (code == 401) + 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]; + + ha2 = build_digest_ha2(conn->host_info.path, "", qop, pool); + 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; +} diff --git a/auth/auth_kerb.c b/auth/auth_kerb.c new file mode 100644 index 0000000..f9be9a4 --- /dev/null +++ b/auth/auth_kerb.c @@ -0,0 +1,350 @@ +/* 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 "auth_kerb.h" + +#ifdef SERF_HAVE_KERB + +/*** Kerberos authentication ***/ + +#include +#include +#include + +#include +#include +#include + +/** These functions implements Kerberos authentication, using GSS-API + * (RFC 2743). The message-exchange is documented in RFC 4559. + * + * Note: this implementation uses gssapi and only works on *nix. + **/ + +/** TODO: + ** - send session key directly on new connections where we already know + ** the server requires Kerberos authn. + ** - fix authn status, as the COMPLETE/CONTINUE status values + ** are never used. + ** - Add a way for serf to give detailed error information back to the + ** application. + ** - proxy support + **/ + +/* Authentication over HTTP using Kerberos + * + * Kerberos involves three servers: + * - Authentication Server (AS): verifies users during login + * - Ticket-Granting Server (TGS): issues proof of identity tickets + * - HTTP server (S) + * + * Steps: + * 0. User logs in to the AS and receives a TGS ticket. On workstations + * where the login program doesn't support Kerberos, the user can use + * 'kinit'. + * + * 1. C --> S: GET + * + * C <-- S: 401 Authentication Required + * WWW-Authenticate: Negotiate + * + * -> app contacts the TGS to request a session key for the HTTP service + * @ target host. The returned session key is encrypted with the HTTP + * service's secret key, so we can safely send it to the server. + * + * 2. C --> S: GET + * Authorization: Negotiate + * gss_api_ctx->state = gss_api_auth_in_progress; + * + * C <-- S: 200 OK + * WWW-Authenticate: Negotiate + * + * -> The server returned a key to proof itself to us. We check this key + * with the TGS again. + * + * Note: It's possible that the server returns 401 again in step 3, if the + * Kerberos context isn't complete yet. Some (simple) tests with + * mod_auth_kerb and MIT Kerberos 5 show this never happens. + * + * This handshake is required for every new connection. If the handshake is + * completed successfully, all other requests on the same connection will + * be authenticated without needing to pass the WWW-Authenticate header. + * + * Note: Step 1 of the handshake will only happen on the first connection, once + * we know the server requires Kerberos authentication, the initial requests + * on the other connections will include a session key, so we start at + * step 2 in the handshake. + * ### TODO: Not implemented yet! + */ + +typedef enum { + gss_api_auth_not_started, + gss_api_auth_in_progress, + gss_api_auth_completed, +} gss_api_auth_state; + +/* HTTP Service name, used to get the session key. */ +#define KRB_HTTP_SERVICE "HTTP" + +/* Stores the context information related to Kerberos authentication. */ +typedef struct +{ + apr_pool_t *pool; + + /* GSSAPI context */ + serf__kerb_context_t *gss_ctx; + + /* Current state of the authentication cycle. */ + gss_api_auth_state state; + + const char *header; + const char *value; +} gss_authn_info_t; + +/* On the initial 401 response of the server, request a session key from + the Kerberos KDC to pass to the server, proving that we are who we + claim to be. The session key can only be used with the HTTP service + on the target host. */ +static apr_status_t +gss_api_get_credentials(char *token, apr_size_t token_len, + const char *hostname, + const char **buf, apr_size_t *buf_len, + gss_authn_info_t *gss_info) +{ + serf__kerb_buffer_t input_buf; + serf__kerb_buffer_t output_buf; + apr_status_t status = APR_SUCCESS; + + /* If the server sent us a token, pass it to gss_init_sec_token for + validation. */ + if (token) { + input_buf.value = token; + input_buf.length = token_len; + } else { + input_buf.value = 0; + input_buf.length = 0; + } + + /* Establish a security context to the server. */ + status = serf__kerb_init_sec_context + (gss_info->gss_ctx, + KRB_HTTP_SERVICE, hostname, + &input_buf, + &output_buf, + gss_info->pool, + gss_info->pool + ); + + switch(status) { + case APR_SUCCESS: + gss_info->state = gss_api_auth_completed; + break; + case APR_EAGAIN: + gss_info->state = gss_api_auth_in_progress; + status = APR_SUCCESS; + break; + default: + status = SERF_ERROR_AUTHN_FAILED; + break; + } + + /* Return the session key to our caller. */ + *buf = output_buf.value; + *buf_len = output_buf.length; + + return status; +} + +/* 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(int code, + gss_authn_info_t *gss_info, + serf_connection_t *conn, + const char *auth_hdr, + apr_pool_t *pool) +{ + serf_context_t *ctx = conn->ctx; + serf__authn_info_t *authn_info = (code == 401) ? &ctx->authn_info : + &ctx->proxy_authn_info; + const char *tmp = NULL; + char *token = NULL; + apr_size_t tmp_len = 0, token_len = 0; + const char *space = NULL; + apr_status_t status; + + /* The server will return a token as attribute to the Negotiate key. + Negotiate YGwGCSqGSIb3EgECAgIAb10wW6ADAgEFoQMCAQ+iTzBNoAMCARCiRgREa6mouM + BAMFqKVdTGtfpZNXKzyw4Yo1paphJdIA3VOgncaoIlXxZLnkHiIHS2v65pVvrp + bRIyjF8xve9HxpnNIucCY9c= + + Read this base64 value, decode it and validate it so we're sure the server + is who we expect it to be. */ + if (auth_hdr) + space = strchr(auth_hdr, ' '); + + if (space) { + token = apr_palloc(pool, apr_base64_decode_len(space + 1)); + token_len = apr_base64_decode(token, space + 1); + } + + /* We can get a whole batch of 401 responses from the server, but we should + only start the authentication phase once, so if we started authentication + already ignore all responses with initial Negotiate authentication header. + + Note: as we set the max. transfer rate to one message at a time until the + authentication cycle is finished, this check shouldn't be needed. */ + if (!token && gss_info->state != gss_api_auth_not_started) + return APR_SUCCESS; + + status = gss_api_get_credentials(token, token_len, conn->host_info.hostname, + &tmp, &tmp_len, + gss_info); + if (status) + return status; + + serf__encode_auth_header(&gss_info->value, authn_info->scheme->name, + tmp, + tmp_len, + pool); + gss_info->header = (code == 401) ? "Authorization" : "Proxy-Authorization"; + + /* If the handshake is finished tell serf it can send as much requests as it + likes. */ + if (gss_info->state == gss_api_auth_completed) + serf_connection_set_max_outstanding_requests(conn, 0); + + return APR_SUCCESS; +} + +apr_status_t +serf__init_kerb(int code, + serf_context_t *ctx, + apr_pool_t *pool) +{ + return APR_SUCCESS; +} + +/* A new connection is created to a server that's known to use + Kerberos. */ +apr_status_t +serf__init_kerb_connection(int code, + serf_connection_t *conn, + apr_pool_t *pool) +{ + gss_authn_info_t *gss_info; + apr_status_t status; + + gss_info = apr_pcalloc(pool, sizeof(*gss_info)); + gss_info->pool = conn->pool; + gss_info->state = gss_api_auth_not_started; + status = serf__kerb_create_sec_context(&gss_info->gss_ctx, pool, + gss_info->pool); + + if (status) { + return status; + } + + if (code == 401) { + conn->authn_baton = gss_info; + } else { + conn->proxy_authn_baton = gss_info; + } + + /* Make serf send the initial requests one by one */ + serf_connection_set_max_outstanding_requests(conn, 1); + + return APR_SUCCESS; +} + +/* A 401 response was received, handle the authentication. */ +apr_status_t +serf__handle_kerb_auth(int code, + serf_request_t *request, + serf_bucket_t *response, + const char *auth_hdr, + const char *auth_attr, + void *baton, + apr_pool_t *pool) +{ + serf_connection_t *conn = request->conn; + gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton : + conn->proxy_authn_baton; + + return do_auth(code, + gss_info, + request->conn, + auth_hdr, + pool); +} + +/* Setup the authn headers on this request message. */ +apr_status_t +serf__setup_request_kerb_auth(int code, + serf_connection_t *conn, + const char *method, + const char *uri, + serf_bucket_t *hdrs_bkt) +{ + gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton : + conn->proxy_authn_baton; + + if (gss_info && gss_info->header && gss_info->value) { + serf_bucket_headers_setn(hdrs_bkt, gss_info->header, + gss_info->value); + + /* We should send each token only once. */ + gss_info->header = NULL; + gss_info->value = NULL; + return APR_SUCCESS; + } + + return SERF_ERROR_AUTHN_FAILED; +} + +/* Function is called when 2xx responses are received. Normally we don't + * have to do anything, except for the first response after the + * authentication handshake. This specific response includes authentication + * data which should be validated by the client (mutual authentication). + */ +apr_status_t +serf__validate_response_kerb_auth(int code, + serf_connection_t *conn, + serf_request_t *request, + serf_bucket_t *response, + apr_pool_t *pool) +{ + gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton : + conn->proxy_authn_baton; + serf_bucket_t *hdrs; + const char *auth_hdr; + + hdrs = serf_bucket_response_get_headers(response); + auth_hdr = serf_bucket_headers_get(hdrs, "WWW-Authenticate"); + + if (gss_info->state != gss_api_auth_completed) { + return do_auth(code, + gss_info, + conn, + auth_hdr, + pool); + } + + return APR_SUCCESS; +} + +#endif /* SERF_HAVE_GSSAPI */ diff --git a/auth/auth_kerb.h b/auth/auth_kerb.h new file mode 100644 index 0000000..a0689c1 --- /dev/null +++ b/auth/auth_kerb.h @@ -0,0 +1,103 @@ +/* Copyright 2010 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. + */ + +#ifndef AUTH_KERB_H +#define AUTH_KERB_H + +#include +#include + +#if defined(SERF_HAVE_SSPI) +#define SERF_HAVE_KERB +#define SERF_USE_SSPI +#elif defined(SERF_HAVE_GSSAPI) +#define SERF_HAVE_KERB +#define SERF_USE_GSSAPI +#endif + +#ifdef SERF_HAVE_KERB + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct serf__kerb_context_t serf__kerb_context_t; + +typedef struct serf__kerb_buffer_t { + apr_size_t length; + void *value; +} serf__kerb_buffer_t; + +/* Create outbound security context. + * + * All temporary allocations will be performed in SCRATCH_POOL, while security + * context will be allocated in result_pool and will be destroyed automatically + * on RESULT_POOL cleanup. + * + */ +apr_status_t +serf__kerb_create_sec_context(serf__kerb_context_t **ctx_p, + apr_pool_t *scratch_pool, + apr_pool_t *result_pool); + +/* Initialize outbound security context. + * + * The function is used to build a security context between the client + * application and a remote peer. + * + * CTX is pointer to existing context created using + * serf__kerb_create_sec_context() function. + * + * SERVICE is name of Kerberos service name. Usually 'HTTP'. HOSTNAME is + * canonical name of destination server. Caller should resolve server's alias + * to canonical name. + * + * INPUT_BUF is pointer structure describing input token if any. Should be + * zero length on first call. + * + * OUTPUT_BUF will be populated with pointer to output data that should send + * to destination server. This buffer will be automatically freed on + * RESULT_POOL cleanup. + * + * All temporary allocations will be performed in SCRATCH_POOL. + * + * Return value: + * - APR_EAGAIN The client must send the output token to the server and wait + * for a return token. + * + * - APR_SUCCESS The security context was successfully initialized. There is no + * need for another serf__kerb_init_sec_context call. If the function returns + * an output token, that is, if the OUTPUT_BUF is of nonzero length, that + * token must be sent to the server. + * + * Other returns values indicates error. + */ +apr_status_t +serf__kerb_init_sec_context(serf__kerb_context_t *ctx, + const char *service, + const char *hostname, + serf__kerb_buffer_t *input_buf, + serf__kerb_buffer_t *output_buf, + apr_pool_t *scratch_pool, + apr_pool_t *result_pool + ); + +#ifdef __cplusplus +} +#endif + +#endif /* SERF_HAVE_KERB */ + +#endif /* !AUTH_KERB_H */ diff --git a/auth/auth_kerb_gss.c b/auth/auth_kerb_gss.c new file mode 100644 index 0000000..b7267d6 --- /dev/null +++ b/auth/auth_kerb_gss.c @@ -0,0 +1,146 @@ +/* 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 "auth_kerb.h" + +#ifdef SERF_USE_GSSAPI +#include +#include + +struct serf__kerb_context_t +{ + /* GSSAPI context */ + gss_ctx_id_t gss_ctx; + + /* Mechanism used to authenticate, should be Kerberos. */ + gss_OID gss_mech; +}; + +/* Cleans the GSS context object, when the pool used to create it gets + cleared or destroyed. */ +static apr_status_t +cleanup_ctx(void *data) +{ + OM_uint32 min_stat; + serf__kerb_context_t *ctx = data; + + if (ctx->gss_ctx != GSS_C_NO_CONTEXT) { + if (gss_delete_sec_context(&min_stat, &ctx->gss_ctx, + GSS_C_NO_BUFFER) == GSS_S_FAILURE) + return APR_EGENERAL; + } + + return APR_SUCCESS; +} + +static apr_status_t +cleanup_sec_buffer(void *data) +{ + OM_uint32 min_stat; + gss_buffer_desc *gss_buf = data; + + gss_release_buffer(&min_stat, gss_buf); + + return APR_SUCCESS; +} + +apr_status_t +serf__kerb_create_sec_context(serf__kerb_context_t **ctx_p, + apr_pool_t *scratch_pool, + apr_pool_t *result_pool) +{ + serf__kerb_context_t *ctx; + + ctx = apr_pcalloc(result_pool, sizeof(*ctx)); + + ctx->gss_ctx = GSS_C_NO_CONTEXT; + ctx->gss_mech = GSS_C_NO_OID; + + apr_pool_cleanup_register(result_pool, ctx, + cleanup_ctx, + apr_pool_cleanup_null); + + *ctx_p = ctx; + + return APR_SUCCESS; +} + +apr_status_t +serf__kerb_init_sec_context(serf__kerb_context_t *ctx, + const char *service, + const char *hostname, + serf__kerb_buffer_t *input_buf, + serf__kerb_buffer_t *output_buf, + apr_pool_t *scratch_pool, + apr_pool_t *result_pool + ) +{ + gss_buffer_desc gss_input_buf = GSS_C_EMPTY_BUFFER; + gss_buffer_desc *gss_output_buf_p; + OM_uint32 gss_min_stat, gss_maj_stat; + gss_name_t host_gss_name; + gss_buffer_desc bufdesc; + + /* Get the name for the HTTP service at the target host. */ + bufdesc.value = apr_pstrcat(scratch_pool, service, "@", hostname, NULL); + bufdesc.length = strlen(bufdesc.value); + gss_maj_stat = gss_import_name (&gss_min_stat, &bufdesc, GSS_C_NT_HOSTBASED_SERVICE, + &host_gss_name); + if(GSS_ERROR(gss_maj_stat)) { + return APR_EGENERAL; + } + + /* If the server sent us a token, pass it to gss_init_sec_token for + validation. */ + gss_input_buf.value = input_buf->value; + gss_input_buf.length = input_buf->length; + + gss_output_buf_p = apr_pcalloc(result_pool, sizeof(*gss_output_buf_p)); + + /* Establish a security context to the server. */ + gss_maj_stat = gss_init_sec_context + (&gss_min_stat, /* minor_status */ + GSS_C_NO_CREDENTIAL, /* XXXXX claimant_cred_handle */ + &ctx->gss_ctx, /* gssapi context handle */ + host_gss_name, /* HTTP@server name */ + ctx->gss_mech, /* mech_type (0 ininitially */ + GSS_C_MUTUAL_FLAG, /* ensure the peer authenticates itself */ + 0, /* default validity period */ + GSS_C_NO_CHANNEL_BINDINGS, /* do not use channel bindings */ + &gss_input_buf, /* server token, initially empty */ + &ctx->gss_mech, /* actual mech type */ + gss_output_buf_p, /* output_token */ + NULL, /* ret_flags */ + NULL /* not interested in remaining validity */ + ); + + apr_pool_cleanup_register(result_pool, gss_output_buf_p, + cleanup_sec_buffer, + apr_pool_cleanup_null); + + output_buf->value = gss_output_buf_p->value; + output_buf->length = gss_output_buf_p->length; + + switch(gss_maj_stat) { + case GSS_S_COMPLETE: + return APR_SUCCESS; + case GSS_S_CONTINUE_NEEDED: + return APR_EAGAIN; + default: + return APR_EGENERAL; + } +} + +#endif /* SERF_USE_GSSAPI */ diff --git a/auth/auth_kerb_sspi.c b/auth/auth_kerb_sspi.c new file mode 100644 index 0000000..db28ab1 --- /dev/null +++ b/auth/auth_kerb_sspi.c @@ -0,0 +1,210 @@ +/* Copyright 2010 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 "auth_kerb.h" + +#ifdef SERF_USE_SSPI +#include +#include + +#define SECURITY_WIN32 +#include + +struct serf__kerb_context_t +{ + CredHandle sspi_credentials; + CtxtHandle sspi_context; + BOOL initalized; +}; + +/* Cleans the SSPI context object, when the pool used to create it gets + cleared or destroyed. */ +static apr_status_t +cleanup_ctx(void *data) +{ + serf__kerb_context_t *ctx = data; + + if (SecIsValidHandle(&ctx->sspi_context)) { + DeleteSecurityContext(&ctx->sspi_context); + SecInvalidateHandle(&ctx->sspi_context); + } + + if (SecIsValidHandle(&ctx->sspi_credentials)) { + FreeCredentialsHandle(&ctx->sspi_context); + SecInvalidateHandle(&ctx->sspi_context); + } + + return APR_SUCCESS; +} + +static apr_status_t +cleanup_sec_buffer(void *data) +{ + FreeContextBuffer(data); + + return APR_SUCCESS; +} + +apr_status_t +serf__kerb_create_sec_context(serf__kerb_context_t **ctx_p, + apr_pool_t *scratch_pool, + apr_pool_t *result_pool) +{ + SECURITY_STATUS sspi_status; + serf__kerb_context_t *ctx; + + ctx = apr_pcalloc(result_pool, sizeof(*ctx)); + + SecInvalidateHandle(&ctx->sspi_context); + SecInvalidateHandle(&ctx->sspi_credentials); + ctx->initalized = FALSE; + + apr_pool_cleanup_register(result_pool, ctx, + cleanup_ctx, + apr_pool_cleanup_null); + + sspi_status = AcquireCredentialsHandle( + NULL, "Negotiate", SECPKG_CRED_OUTBOUND, + NULL, NULL, NULL, NULL, + &ctx->sspi_credentials, NULL); + + if (FAILED(sspi_status)) { + return APR_EGENERAL; + } + + *ctx_p = ctx; + + return APR_SUCCESS; +} + +static apr_status_t +get_canonical_hostname(const char **canonname, + const char *hostname, + apr_pool_t *pool) +{ + struct addrinfo hints; + struct addrinfo *addrinfo; + + ZeroMemory(&hints, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + + if (getaddrinfo(hostname, NULL, &hints, &addrinfo)) { + return apr_get_netos_error(); + } + + if (addrinfo) { + *canonname = apr_pstrdup(pool, addrinfo->ai_canonname); + } + else { + *canonname = apr_pstrdup(pool, hostname); + } + + freeaddrinfo(addrinfo); + return APR_SUCCESS; +} + +apr_status_t +serf__kerb_init_sec_context(serf__kerb_context_t *ctx, + const char *service, + const char *hostname, + serf__kerb_buffer_t *input_buf, + serf__kerb_buffer_t *output_buf, + apr_pool_t *scratch_pool, + apr_pool_t *result_pool + ) +{ + SECURITY_STATUS status; + ULONG actual_attr; + SecBuffer sspi_in_buffer; + SecBufferDesc sspi_in_buffer_desc; + SecBuffer sspi_out_buffer; + SecBufferDesc sspi_out_buffer_desc; + char *target_name; + apr_status_t apr_status; + const char *canonname; + + apr_status = get_canonical_hostname(&canonname, hostname, scratch_pool); + if (apr_status) { + return apr_status; + } + target_name = apr_pstrcat(scratch_pool, service, "/", canonname, NULL); + + /* Prepare input buffer description. */ + sspi_in_buffer.BufferType = SECBUFFER_TOKEN; + sspi_in_buffer.pvBuffer = input_buf->value; + sspi_in_buffer.cbBuffer = input_buf->length; + + sspi_in_buffer_desc.cBuffers = 1; + sspi_in_buffer_desc.pBuffers = &sspi_in_buffer; + sspi_in_buffer_desc.ulVersion = SECBUFFER_VERSION; + + /* Output buffers. Output buffer will be allocated by system. */ + sspi_out_buffer.BufferType = SECBUFFER_TOKEN; + sspi_out_buffer.pvBuffer = NULL; + sspi_out_buffer.cbBuffer = 0; + + sspi_out_buffer_desc.cBuffers = 1; + sspi_out_buffer_desc.pBuffers = &sspi_out_buffer; + sspi_out_buffer_desc.ulVersion = SECBUFFER_VERSION; + + status = InitializeSecurityContext( + &ctx->sspi_credentials, + ctx->initalized ? &ctx->sspi_context : NULL, + target_name, + ISC_REQ_ALLOCATE_MEMORY + | ISC_REQ_MUTUAL_AUTH + | ISC_REQ_CONFIDENTIALITY, + 0, /* Reserved1 */ + SECURITY_NETWORK_DREP, + &sspi_in_buffer_desc, + 0, /* Reserved2 */ + &ctx->sspi_context, + &sspi_out_buffer_desc, + &actual_attr, + NULL); + + if (sspi_out_buffer.cbBuffer > 0) { + apr_pool_cleanup_register(result_pool, sspi_out_buffer.pvBuffer, + cleanup_sec_buffer, + apr_pool_cleanup_null); + } + + ctx->initalized = TRUE; + + /* Finish authentication if SSPI requires so. */ + if (status == SEC_I_COMPLETE_NEEDED + || status == SEC_I_COMPLETE_AND_CONTINUE) + { + CompleteAuthToken(&ctx->sspi_context, &sspi_out_buffer_desc); + } + + output_buf->value = sspi_out_buffer.pvBuffer; + output_buf->length = sspi_out_buffer.cbBuffer; + + switch(status) { + case SEC_I_COMPLETE_AND_CONTINUE: + case SEC_I_CONTINUE_NEEDED: + return APR_EAGAIN; + + case SEC_I_COMPLETE_NEEDED: + case SEC_E_OK: + return APR_SUCCESS; + + default: + return APR_EGENERAL; + } +} + +#endif /* SERF_USE_SSPI */ \ No newline at end of file -- cgit v1.2.1