aboutsummaryrefslogtreecommitdiffstats
path: root/auth/auth_spnego_sspi.c
blob: 32b719ee556a2d4245df550d71cf433c24057e1f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
/* 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_spnego.h"
#include "serf.h"
#include "serf_private.h"

#ifdef SERF_USE_SSPI
#include <apr.h>
#include <apr_strings.h>

#define SECURITY_WIN32
#include <sspi.h>

/* SEC_E_MUTUAL_AUTH_FAILED is not defined in Windows Platform SDK 5.0. */
#ifndef SEC_E_MUTUAL_AUTH_FAILED
#define SEC_E_MUTUAL_AUTH_FAILED _HRESULT_TYPEDEF_(0x80090363L)
#endif

struct serf__spnego_context_t
{
    CredHandle sspi_credentials;
    CtxtHandle sspi_context;
    BOOL initalized;
    apr_pool_t *pool;

    /* Service Principal Name (SPN) used for authentication. */
    const char *target_name;

    /* One of SERF_AUTHN_* authentication types.*/
    int authn_type;
};

/* Map SECURITY_STATUS from SSPI to APR error code. Some error codes mapped
 * to our own codes and some to Win32 error codes:
 * http://support.microsoft.com/kb/113996
 */
static apr_status_t
map_sspi_status(SECURITY_STATUS sspi_status)
{
    switch(sspi_status)
    {
    case SEC_E_INSUFFICIENT_MEMORY:
        return APR_FROM_OS_ERROR(ERROR_NO_SYSTEM_RESOURCES);
    case SEC_E_INVALID_HANDLE:
        return APR_FROM_OS_ERROR(ERROR_INVALID_HANDLE);
    case SEC_E_UNSUPPORTED_FUNCTION:
        return APR_FROM_OS_ERROR(ERROR_INVALID_FUNCTION);
    case SEC_E_TARGET_UNKNOWN:
        return APR_FROM_OS_ERROR(ERROR_BAD_NETPATH);
    case SEC_E_INTERNAL_ERROR:
        return APR_FROM_OS_ERROR(ERROR_INTERNAL_ERROR);
    case SEC_E_SECPKG_NOT_FOUND:
    case SEC_E_BAD_PKGID:
        return APR_FROM_OS_ERROR(ERROR_NO_SUCH_PACKAGE);
    case SEC_E_NO_IMPERSONATION:
        return APR_FROM_OS_ERROR(ERROR_CANNOT_IMPERSONATE);
    case SEC_E_NO_AUTHENTICATING_AUTHORITY:
        return APR_FROM_OS_ERROR(ERROR_NO_LOGON_SERVERS);
    case SEC_E_UNTRUSTED_ROOT:
        return APR_FROM_OS_ERROR(ERROR_TRUST_FAILURE);
    case SEC_E_WRONG_PRINCIPAL:
        return APR_FROM_OS_ERROR(ERROR_WRONG_TARGET_NAME);
    case SEC_E_MUTUAL_AUTH_FAILED:
        return APR_FROM_OS_ERROR(ERROR_MUTUAL_AUTH_FAILED);
    case SEC_E_TIME_SKEW:
        return APR_FROM_OS_ERROR(ERROR_TIME_SKEW);
    default:
        return SERF_ERROR_AUTHN_FAILED;
    }
}

/* 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__spnego_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_credentials);
        SecInvalidateHandle(&ctx->sspi_credentials);
    }

    return APR_SUCCESS;
}

static apr_status_t
cleanup_sec_buffer(void *data)
{
    FreeContextBuffer(data);

    return APR_SUCCESS;
}

apr_status_t
serf__spnego_create_sec_context(serf__spnego_context_t **ctx_p,
                                const serf__authn_scheme_t *scheme,
                                apr_pool_t *result_pool,
                                apr_pool_t *scratch_pool)
{
    SECURITY_STATUS sspi_status;
    serf__spnego_context_t *ctx;
    const char *sspi_package;

    ctx = apr_pcalloc(result_pool, sizeof(*ctx));

    SecInvalidateHandle(&ctx->sspi_context);
    SecInvalidateHandle(&ctx->sspi_credentials);
    ctx->initalized = FALSE;
    ctx->pool = result_pool;
    ctx->target_name = NULL;
    ctx->authn_type = scheme->type;

    apr_pool_cleanup_register(result_pool, ctx,
                              cleanup_ctx,
                              apr_pool_cleanup_null);

    if (ctx->authn_type == SERF_AUTHN_NEGOTIATE)
        sspi_package = "Negotiate";
    else
        sspi_package = "NTLM";

    sspi_status = AcquireCredentialsHandle(
        NULL, sspi_package, SECPKG_CRED_OUTBOUND,
        NULL, NULL, NULL, NULL,
        &ctx->sspi_credentials, NULL);

    if (FAILED(sspi_status)) {
        return map_sspi_status(sspi_status);
    }

    *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__spnego_reset_sec_context(serf__spnego_context_t *ctx)
{
    if (SecIsValidHandle(&ctx->sspi_context)) {
        DeleteSecurityContext(&ctx->sspi_context);
        SecInvalidateHandle(&ctx->sspi_context);
    }

    ctx->initalized = FALSE;

    return APR_SUCCESS;
}

apr_status_t
serf__spnego_init_sec_context(serf_connection_t *conn,
                              serf__spnego_context_t *ctx,
                              const char *service,
                              const char *hostname,
                              serf__spnego_buffer_t *input_buf,
                              serf__spnego_buffer_t *output_buf,
                              apr_pool_t *result_pool,
                              apr_pool_t *scratch_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;
    apr_status_t apr_status;
    const char *canonname;

    if (!ctx->initalized && ctx->authn_type == SERF_AUTHN_NEGOTIATE) {
        apr_status = get_canonical_hostname(&canonname, hostname, scratch_pool);
        if (apr_status) {
            return apr_status;
        }

        ctx->target_name = apr_pstrcat(scratch_pool, service, "/", canonname,
                                       NULL);

        serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
                      "Using SPN '%s' for '%s'\n", ctx->target_name, hostname);
    }
    else if (ctx->authn_type == SERF_AUTHN_NTLM)
    {
        /* Target name is not used for NTLM authentication. */
        ctx->target_name = 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,
        ctx->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 map_sspi_status(status);
    }
}

#endif /* SERF_USE_SSPI */