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 "serf.h"
22#include "serf_private.h"
23#include "auth_spnego.h"
24
25#ifdef SERF_USE_GSSAPI
26#include <apr_strings.h>
27#include <gssapi/gssapi.h>
28
29
30/* This module can support all authentication mechanisms as provided by
31   the GSS-API implementation, but for now it only supports SPNEGO for
32   Negotiate.
33   SPNEGO can delegate authentication to Kerberos if supported by the
34   host. */
35
36#ifndef GSS_SPNEGO_MECHANISM
37static gss_OID_desc spnego_mech_oid = { 6, "\x2b\x06\x01\x05\x05\x02" };
38#define GSS_SPNEGO_MECHANISM &spnego_mech_oid
39#endif
40
41struct serf__spnego_context_t
42{
43    /* GSSAPI context */
44    gss_ctx_id_t gss_ctx;
45
46    /* Mechanism used to authenticate. */
47    gss_OID gss_mech;
48};
49
50static void
51log_error(int verbose_flag, apr_socket_t *skt,
52          serf__spnego_context_t *ctx,
53          OM_uint32 err_maj_stat,
54          OM_uint32 err_min_stat,
55          const char *msg)
56{
57    OM_uint32 maj_stat, min_stat;
58    gss_buffer_desc stat_buff;
59    OM_uint32 msg_ctx = 0;
60
61    if (verbose_flag) {
62        maj_stat = gss_display_status(&min_stat,
63                                      err_maj_stat,
64                                      GSS_C_GSS_CODE,
65                                      ctx->gss_mech,
66                                      &msg_ctx,
67                                      &stat_buff);
68        if (maj_stat == GSS_S_COMPLETE ||
69            maj_stat == GSS_S_FAILURE) {
70            maj_stat = gss_display_status(&min_stat,
71                                          err_min_stat,
72                                          GSS_C_MECH_CODE,
73                                          ctx->gss_mech,
74                                          &msg_ctx,
75                                          &stat_buff);
76        }
77
78        serf__log_skt(verbose_flag, __FILE__, skt,
79                  "%s (%x,%d): %s\n", msg,
80                  err_maj_stat, err_min_stat, stat_buff.value);
81    }
82}
83
84/* Cleans the GSS context object, when the pool used to create it gets
85   cleared or destroyed. */
86static apr_status_t
87cleanup_ctx(void *data)
88{
89    serf__spnego_context_t *ctx = data;
90
91    if (ctx->gss_ctx != GSS_C_NO_CONTEXT) {
92        OM_uint32 gss_min_stat, gss_maj_stat;
93
94        gss_maj_stat = gss_delete_sec_context(&gss_min_stat, &ctx->gss_ctx,
95                                              GSS_C_NO_BUFFER);
96        if(GSS_ERROR(gss_maj_stat)) {
97            log_error(AUTH_VERBOSE, NULL, ctx,
98                      gss_maj_stat, gss_min_stat,
99                      "Error cleaning up GSS security context");
100            return SERF_ERROR_AUTHN_FAILED;
101        }
102    }
103
104    return APR_SUCCESS;
105}
106
107static apr_status_t
108cleanup_sec_buffer(void *data)
109{
110    OM_uint32 min_stat;
111    gss_buffer_desc *gss_buf = data;
112
113    gss_release_buffer(&min_stat, gss_buf);
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    serf__spnego_context_t *ctx;
125
126    ctx = apr_pcalloc(result_pool, sizeof(*ctx));
127
128    ctx->gss_ctx = GSS_C_NO_CONTEXT;
129    ctx->gss_mech = GSS_SPNEGO_MECHANISM;
130
131    apr_pool_cleanup_register(result_pool, ctx,
132                              cleanup_ctx,
133                              apr_pool_cleanup_null);
134
135    *ctx_p = ctx;
136
137    return APR_SUCCESS;
138}
139
140apr_status_t
141serf__spnego_reset_sec_context(serf__spnego_context_t *ctx)
142{
143    OM_uint32 dummy_stat;
144
145    if (ctx->gss_ctx)
146        (void)gss_delete_sec_context(&dummy_stat, &ctx->gss_ctx,
147                                     GSS_C_NO_BUFFER);
148    ctx->gss_ctx = GSS_C_NO_CONTEXT;
149
150    return APR_SUCCESS;
151}
152
153apr_status_t
154serf__spnego_init_sec_context(serf_connection_t *conn,
155                              serf__spnego_context_t *ctx,
156                              const char *service,
157                              const char *hostname,
158                              serf__spnego_buffer_t *input_buf,
159                              serf__spnego_buffer_t *output_buf,
160                              apr_pool_t *result_pool,
161                              apr_pool_t *scratch_pool
162                              )
163{
164    gss_buffer_desc gss_input_buf = GSS_C_EMPTY_BUFFER;
165    gss_buffer_desc *gss_output_buf_p;
166    OM_uint32 gss_min_stat, gss_maj_stat;
167    gss_name_t host_gss_name;
168    gss_buffer_desc bufdesc;
169    gss_OID dummy; /* unused */
170
171    /* Get the name for the HTTP service at the target host. */
172    /* TODO: should be shared between multiple requests. */
173    bufdesc.value = apr_pstrcat(scratch_pool, service, "@", hostname, NULL);
174    bufdesc.length = strlen(bufdesc.value);
175    serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
176                  "Get principal for %s\n", bufdesc.value);
177    gss_maj_stat = gss_import_name (&gss_min_stat, &bufdesc,
178                                    GSS_C_NT_HOSTBASED_SERVICE,
179                                    &host_gss_name);
180    if(GSS_ERROR(gss_maj_stat)) {
181        log_error(AUTH_VERBOSE, conn->skt, ctx,
182                  gss_maj_stat, gss_min_stat,
183                  "Error converting principal name to GSS internal format ");
184        return SERF_ERROR_AUTHN_FAILED;
185    }
186
187    /* If the server sent us a token, pass it to gss_init_sec_token for
188       validation. */
189    gss_input_buf.value = input_buf->value;
190    gss_input_buf.length = input_buf->length;
191
192    gss_output_buf_p = apr_pcalloc(result_pool, sizeof(*gss_output_buf_p));
193
194    /* Establish a security context to the server. */
195    gss_maj_stat = gss_init_sec_context
196        (&gss_min_stat,             /* minor_status */
197         GSS_C_NO_CREDENTIAL,       /* XXXXX claimant_cred_handle */
198         &ctx->gss_ctx,              /* gssapi context handle */
199         host_gss_name,             /* HTTP@server name */
200         ctx->gss_mech,             /* mech_type (SPNEGO) */
201         GSS_C_MUTUAL_FLAG,         /* ensure the peer authenticates itself */
202         0,                         /* default validity period */
203         GSS_C_NO_CHANNEL_BINDINGS, /* do not use channel bindings */
204         &gss_input_buf,            /* server token, initially empty */
205         &dummy,                    /* actual mech type */
206         gss_output_buf_p,           /* output_token */
207         NULL,                      /* ret_flags */
208         NULL                       /* not interested in remaining validity */
209         );
210
211    apr_pool_cleanup_register(result_pool, gss_output_buf_p,
212                              cleanup_sec_buffer,
213                              apr_pool_cleanup_null);
214
215    output_buf->value = gss_output_buf_p->value;
216    output_buf->length = gss_output_buf_p->length;
217
218    switch(gss_maj_stat) {
219    case GSS_S_COMPLETE:
220        return APR_SUCCESS;
221    case GSS_S_CONTINUE_NEEDED:
222        return APR_EAGAIN;
223    default:
224        log_error(AUTH_VERBOSE, conn->skt, ctx,
225                  gss_maj_stat, gss_min_stat,
226                  "Error during Kerberos handshake");
227        return SERF_ERROR_AUTHN_FAILED;
228    }
229}
230
231#endif /* SERF_USE_GSSAPI */
232