1/* ====================================================================
2 *    Licensed to the Apache Software Foundation (ASF) under one
3 *    or more contributor license agreements.  See the NOTICE file
4 *    distributed with this work for additional information
5 *    regarding copyright ownership.  The ASF licenses this file
6 *    to you under the Apache License, Version 2.0 (the
7 *    "License"); you may not use this file except in compliance
8 *    with the License.  You may obtain a copy of the License at
9 *
10 *      http://www.apache.org/licenses/LICENSE-2.0
11 *
12 *    Unless required by applicable law or agreed to in writing,
13 *    software distributed under the License is distributed on an
14 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 *    KIND, either express or implied.  See the License for the
16 *    specific language governing permissions and limitations
17 *    under the License.
18 * ====================================================================
19 */
20
21#include "auth_spnego.h"
22#include "serf.h"
23#include "serf_private.h"
24
25#ifdef SERF_USE_SSPI
26#include <apr.h>
27#include <apr_strings.h>
28
29#define SECURITY_WIN32
30#include <sspi.h>
31
32/* SEC_E_MUTUAL_AUTH_FAILED is not defined in Windows Platform SDK 5.0. */
33#ifndef SEC_E_MUTUAL_AUTH_FAILED
34#define SEC_E_MUTUAL_AUTH_FAILED _HRESULT_TYPEDEF_(0x80090363L)
35#endif
36
37struct serf__spnego_context_t
38{
39    CredHandle sspi_credentials;
40    CtxtHandle sspi_context;
41    BOOL initalized;
42    apr_pool_t *pool;
43
44    /* Service Principal Name (SPN) used for authentication. */
45    const char *target_name;
46
47    /* One of SERF_AUTHN_* authentication types.*/
48    int authn_type;
49};
50
51/* Map SECURITY_STATUS from SSPI to APR error code. Some error codes mapped
52 * to our own codes and some to Win32 error codes:
53 * http://support.microsoft.com/kb/113996
54 */
55static apr_status_t
56map_sspi_status(SECURITY_STATUS sspi_status)
57{
58    switch(sspi_status)
59    {
60    case SEC_E_INSUFFICIENT_MEMORY:
61        return APR_FROM_OS_ERROR(ERROR_NO_SYSTEM_RESOURCES);
62    case SEC_E_INVALID_HANDLE:
63        return APR_FROM_OS_ERROR(ERROR_INVALID_HANDLE);
64    case SEC_E_UNSUPPORTED_FUNCTION:
65        return APR_FROM_OS_ERROR(ERROR_INVALID_FUNCTION);
66    case SEC_E_TARGET_UNKNOWN:
67        return APR_FROM_OS_ERROR(ERROR_BAD_NETPATH);
68    case SEC_E_INTERNAL_ERROR:
69        return APR_FROM_OS_ERROR(ERROR_INTERNAL_ERROR);
70    case SEC_E_SECPKG_NOT_FOUND:
71    case SEC_E_BAD_PKGID:
72        return APR_FROM_OS_ERROR(ERROR_NO_SUCH_PACKAGE);
73    case SEC_E_NO_IMPERSONATION:
74        return APR_FROM_OS_ERROR(ERROR_CANNOT_IMPERSONATE);
75    case SEC_E_NO_AUTHENTICATING_AUTHORITY:
76        return APR_FROM_OS_ERROR(ERROR_NO_LOGON_SERVERS);
77    case SEC_E_UNTRUSTED_ROOT:
78        return APR_FROM_OS_ERROR(ERROR_TRUST_FAILURE);
79    case SEC_E_WRONG_PRINCIPAL:
80        return APR_FROM_OS_ERROR(ERROR_WRONG_TARGET_NAME);
81    case SEC_E_MUTUAL_AUTH_FAILED:
82        return APR_FROM_OS_ERROR(ERROR_MUTUAL_AUTH_FAILED);
83    case SEC_E_TIME_SKEW:
84        return APR_FROM_OS_ERROR(ERROR_TIME_SKEW);
85    default:
86        return SERF_ERROR_AUTHN_FAILED;
87    }
88}
89
90/* Cleans the SSPI context object, when the pool used to create it gets
91   cleared or destroyed. */
92static apr_status_t
93cleanup_ctx(void *data)
94{
95    serf__spnego_context_t *ctx = data;
96
97    if (SecIsValidHandle(&ctx->sspi_context)) {
98        DeleteSecurityContext(&ctx->sspi_context);
99        SecInvalidateHandle(&ctx->sspi_context);
100    }
101
102    if (SecIsValidHandle(&ctx->sspi_credentials)) {
103        FreeCredentialsHandle(&ctx->sspi_credentials);
104        SecInvalidateHandle(&ctx->sspi_credentials);
105    }
106
107    return APR_SUCCESS;
108}
109
110static apr_status_t
111cleanup_sec_buffer(void *data)
112{
113    FreeContextBuffer(data);
114
115    return APR_SUCCESS;
116}
117
118apr_status_t
119serf__spnego_create_sec_context(serf__spnego_context_t **ctx_p,
120                                const serf__authn_scheme_t *scheme,
121                                apr_pool_t *result_pool,
122                                apr_pool_t *scratch_pool)
123{
124    SECURITY_STATUS sspi_status;
125    serf__spnego_context_t *ctx;
126    const char *sspi_package;
127
128    ctx = apr_pcalloc(result_pool, sizeof(*ctx));
129
130    SecInvalidateHandle(&ctx->sspi_context);
131    SecInvalidateHandle(&ctx->sspi_credentials);
132    ctx->initalized = FALSE;
133    ctx->pool = result_pool;
134    ctx->target_name = NULL;
135    ctx->authn_type = scheme->type;
136
137    apr_pool_cleanup_register(result_pool, ctx,
138                              cleanup_ctx,
139                              apr_pool_cleanup_null);
140
141    if (ctx->authn_type == SERF_AUTHN_NEGOTIATE)
142        sspi_package = "Negotiate";
143    else
144        sspi_package = "NTLM";
145
146    sspi_status = AcquireCredentialsHandleA(
147        NULL, sspi_package, SECPKG_CRED_OUTBOUND,
148        NULL, NULL, NULL, NULL,
149        &ctx->sspi_credentials, NULL);
150
151    if (FAILED(sspi_status)) {
152        return map_sspi_status(sspi_status);
153    }
154
155    *ctx_p = ctx;
156
157    return APR_SUCCESS;
158}
159
160static apr_status_t
161get_canonical_hostname(const char **canonname,
162                       const char *hostname,
163                       apr_pool_t *pool)
164{
165    struct addrinfo hints;
166    struct addrinfo *addrinfo;
167
168    ZeroMemory(&hints, sizeof(hints));
169    hints.ai_flags = AI_CANONNAME;
170
171    if (getaddrinfo(hostname, NULL, &hints, &addrinfo)) {
172        return apr_get_netos_error();
173    }
174
175    if (addrinfo) {
176        *canonname = apr_pstrdup(pool, addrinfo->ai_canonname);
177    }
178    else {
179        *canonname = apr_pstrdup(pool, hostname);
180    }
181
182    freeaddrinfo(addrinfo);
183    return APR_SUCCESS;
184}
185
186apr_status_t
187serf__spnego_reset_sec_context(serf__spnego_context_t *ctx)
188{
189    if (SecIsValidHandle(&ctx->sspi_context)) {
190        DeleteSecurityContext(&ctx->sspi_context);
191        SecInvalidateHandle(&ctx->sspi_context);
192    }
193
194    ctx->initalized = FALSE;
195
196    return APR_SUCCESS;
197}
198
199apr_status_t
200serf__spnego_init_sec_context(serf_connection_t *conn,
201                              serf__spnego_context_t *ctx,
202                              const char *service,
203                              const char *hostname,
204                              serf__spnego_buffer_t *input_buf,
205                              serf__spnego_buffer_t *output_buf,
206                              apr_pool_t *result_pool,
207                              apr_pool_t *scratch_pool
208                              )
209{
210    SECURITY_STATUS status;
211    ULONG actual_attr;
212    SecBuffer sspi_in_buffer;
213    SecBufferDesc sspi_in_buffer_desc;
214    SecBuffer sspi_out_buffer;
215    SecBufferDesc sspi_out_buffer_desc;
216    apr_status_t apr_status;
217    const char *canonname;
218
219    if (!ctx->initalized && ctx->authn_type == SERF_AUTHN_NEGOTIATE) {
220        apr_status = get_canonical_hostname(&canonname, hostname, scratch_pool);
221        if (apr_status) {
222            return apr_status;
223        }
224
225        ctx->target_name = apr_pstrcat(scratch_pool, service, "/", canonname,
226                                       NULL);
227
228        serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
229                      "Using SPN '%s' for '%s'\n", ctx->target_name, hostname);
230    }
231    else if (ctx->authn_type == SERF_AUTHN_NTLM)
232    {
233        /* Target name is not used for NTLM authentication. */
234        ctx->target_name = NULL;
235    }
236
237    /* Prepare input buffer description. */
238    sspi_in_buffer.BufferType = SECBUFFER_TOKEN;
239    sspi_in_buffer.pvBuffer = input_buf->value;
240    sspi_in_buffer.cbBuffer = input_buf->length;
241
242    sspi_in_buffer_desc.cBuffers = 1;
243    sspi_in_buffer_desc.pBuffers = &sspi_in_buffer;
244    sspi_in_buffer_desc.ulVersion = SECBUFFER_VERSION;
245
246    /* Output buffers. Output buffer will be allocated by system. */
247    sspi_out_buffer.BufferType = SECBUFFER_TOKEN;
248    sspi_out_buffer.pvBuffer = NULL;
249    sspi_out_buffer.cbBuffer = 0;
250
251    sspi_out_buffer_desc.cBuffers = 1;
252    sspi_out_buffer_desc.pBuffers = &sspi_out_buffer;
253    sspi_out_buffer_desc.ulVersion = SECBUFFER_VERSION;
254
255    status = InitializeSecurityContextA(
256        &ctx->sspi_credentials,
257        ctx->initalized ? &ctx->sspi_context : NULL,
258        ctx->target_name,
259        ISC_REQ_ALLOCATE_MEMORY
260        | ISC_REQ_MUTUAL_AUTH
261        | ISC_REQ_CONFIDENTIALITY,
262        0,                          /* Reserved1 */
263        SECURITY_NETWORK_DREP,
264        &sspi_in_buffer_desc,
265        0,                          /* Reserved2 */
266        &ctx->sspi_context,
267        &sspi_out_buffer_desc,
268        &actual_attr,
269        NULL);
270
271    if (sspi_out_buffer.cbBuffer > 0) {
272        apr_pool_cleanup_register(result_pool, sspi_out_buffer.pvBuffer,
273                                  cleanup_sec_buffer,
274                                  apr_pool_cleanup_null);
275    }
276
277    ctx->initalized = TRUE;
278
279    /* Finish authentication if SSPI requires so. */
280    if (status == SEC_I_COMPLETE_NEEDED
281        || status == SEC_I_COMPLETE_AND_CONTINUE)
282    {
283        CompleteAuthToken(&ctx->sspi_context, &sspi_out_buffer_desc);
284    }
285
286    output_buf->value = sspi_out_buffer.pvBuffer;
287    output_buf->length = sspi_out_buffer.cbBuffer;
288
289    switch(status) {
290    case SEC_I_COMPLETE_AND_CONTINUE:
291    case SEC_I_CONTINUE_NEEDED:
292        return APR_EAGAIN;
293
294    case SEC_I_COMPLETE_NEEDED:
295    case SEC_E_OK:
296        return APR_SUCCESS;
297
298    default:
299        return map_sspi_status(status);
300    }
301}
302
303#endif /* SERF_USE_SSPI */
304