Conf file is now in source tree
[mod_auth_kerb.git] / gss.c
1 /*
2  * Copyright (c) 2010 CESNET
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  *    this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * 3. Neither the name of CESNET nor the names of its contributors may
16  *    be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include "mod_auth_gssapi.h"
33
34 static const char *
35 get_gss_error(request_rec *r, OM_uint32 err_maj, OM_uint32 err_min, char *prefix)
36 {
37    OM_uint32 maj_stat, min_stat; 
38    OM_uint32 msg_ctx = 0;
39    gss_buffer_desc status_string;
40    char *err_msg;
41    int first_pass;
42
43    gss_log(APLOG_MARK, APLOG_DEBUG, 0, r,
44            "GSS-API major_status:%8.8x, minor_status:%8.8x",
45            err_maj, err_min);
46
47    err_msg = apr_pstrdup(r->pool, prefix);
48    do {
49       maj_stat = gss_display_status (&min_stat,
50                                      err_maj,
51                                      GSS_C_GSS_CODE,
52                                      GSS_C_NO_OID,
53                                      &msg_ctx,
54                                      &status_string);
55       if (!GSS_ERROR(maj_stat)) {
56          err_msg = apr_pstrcat(r->pool, err_msg,
57                                ": ", (char*) status_string.value, NULL);
58          gss_release_buffer(&min_stat, &status_string);
59          first_pass = 0;
60       }
61    } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
62
63    msg_ctx = 0;
64    err_msg = apr_pstrcat(r->pool, err_msg, " (", NULL);
65    first_pass = 1;
66    do {
67       maj_stat = gss_display_status (&min_stat,
68                                      err_min,
69                                      GSS_C_MECH_CODE,
70                                      GSS_C_NULL_OID,
71                                      &msg_ctx,
72                                      &status_string);
73       if (!GSS_ERROR(maj_stat)) {
74          err_msg = apr_pstrcat(r->pool, err_msg,
75                                (first_pass) ? "" : ", ",
76                                (char *) status_string.value,
77                                NULL);
78          gss_release_buffer(&min_stat, &status_string);
79          first_pass = 0;
80       }
81    } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
82    err_msg = apr_pstrcat(r->pool, err_msg, ")", NULL);
83
84    return err_msg;
85 }
86
87 static int
88 get_gss_creds(request_rec *r,
89               gss_auth_config *conf,
90               gss_cred_id_t *server_creds)
91 {
92    gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
93    OM_uint32 major_status, minor_status, minor_status2;
94    gss_name_t server_name = GSS_C_NO_NAME;
95    char buf[1024];
96    int have_server_princ;
97
98    if (conf->service_name && strcmp(conf->service_name, "Any") == 0) {
99        *server_creds = GSS_C_NO_CREDENTIAL;
100        return 0;
101    }
102
103    have_server_princ = conf->service_name && strchr(conf->service_name, '/') != NULL;
104    if (have_server_princ)
105        strncpy(buf, conf->service_name, sizeof(buf));
106    else
107        snprintf(buf, sizeof(buf), "%s@%s",
108                (conf->service_name) ? conf->service_name : SERVICE_NAME,
109                ap_get_server_name(r));
110
111    token.value = buf;
112    token.length = strlen(buf) + 1;
113
114    major_status = gss_import_name(&minor_status, &token,
115                                   (have_server_princ) ? (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME : (gss_OID) GSS_C_NT_HOSTBASED_SERVICE,
116                                   &server_name);
117    memset(&token, 0, sizeof(token));
118    if (GSS_ERROR(major_status)) {
119       gss_log(APLOG_MARK, APLOG_ERR, 0, r,
120               "%s", get_gss_error(r, major_status, minor_status,
121               "gss_import_name() failed"));
122       return HTTP_INTERNAL_SERVER_ERROR;
123    }
124
125    major_status = gss_display_name(&minor_status, server_name, &token, NULL);
126    if (GSS_ERROR(major_status)) {
127       /* Perhaps we could just ignore this error but it's safer to give up now,
128          I think */
129       gss_log(APLOG_MARK, APLOG_ERR, 0, r,
130               "%s", get_gss_error(r, major_status, minor_status,
131                                   "gss_display_name() failed"));
132       return HTTP_INTERNAL_SERVER_ERROR;
133    }
134
135    gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "Acquiring creds for %s", token.value);
136    gss_release_buffer(&minor_status, &token);
137    
138    major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
139                                    GSS_C_NO_OID_SET, GSS_C_ACCEPT,
140                                    server_creds, NULL, NULL);
141    gss_release_name(&minor_status2, &server_name);
142    if (GSS_ERROR(major_status)) {
143       gss_log(APLOG_MARK, APLOG_ERR, 0, r,
144               "%s", get_gss_error(r, major_status, minor_status,
145                                   "Failed to load GSS-API credentials"));
146       return HTTP_INTERNAL_SERVER_ERROR;
147    }
148
149    return 0;
150 }
151
152 static int
153 cmp_gss_type(gss_buffer_t token, gss_OID oid)
154 {
155    unsigned char *p;
156    size_t len;
157
158    if (token->length == 0)
159       return GSS_S_DEFECTIVE_TOKEN;
160
161    p = token->value;
162    if (*p++ != 0x60)
163       return GSS_S_DEFECTIVE_TOKEN;
164    len = *p++;
165    if (len & 0x80) {
166       if ((len & 0x7f) > 4)
167          return GSS_S_DEFECTIVE_TOKEN;
168       p += len & 0x7f;
169    }
170    if (*p++ != 0x06)
171       return GSS_S_DEFECTIVE_TOKEN;
172
173    if (((OM_uint32) *p++) != oid->length)
174       return GSS_S_DEFECTIVE_TOKEN;
175
176    return memcmp(p, oid->elements, oid->length);
177 }
178
179 int
180 gss_authenticate(request_rec *r, gss_auth_config *conf, gss_conn_ctx ctx,
181                  const char *auth_line, char **negotiate_ret_value)
182 {
183   OM_uint32 major_status, minor_status, minor_status2;
184   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
185   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
186   const char *auth_param = NULL;
187   int ret;
188   gss_name_t client_name = GSS_C_NO_NAME;
189   gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
190   gss_cred_id_t server_creds = GSS_C_NO_CREDENTIAL;
191   OM_uint32 ret_flags = 0;
192   gss_OID_desc spnego_oid;
193   OM_uint32 (*accept_sec_context)
194                 (OM_uint32 *, gss_ctx_id_t *, const gss_cred_id_t,
195                  const gss_buffer_t, const gss_channel_bindings_t,
196                  gss_name_t *, gss_OID *, gss_buffer_t, OM_uint32 *,
197                  OM_uint32 *, gss_cred_id_t *);
198
199   *negotiate_ret_value = "\0";
200
201   spnego_oid.length = 6;
202   spnego_oid.elements = (void *)"\x2b\x06\x01\x05\x05\x02";
203
204   if (conf->krb5_keytab) {
205      char *ktname;
206      /* we don't use the ap_* calls here, since the string passed to putenv()
207       * will become part of the enviroment and shouldn't be free()ed by apache
208       */
209      ktname = malloc(strlen("KRB5_KTNAME=") + strlen(conf->krb5_keytab) + 1);
210      if (ktname == NULL) {
211         gss_log(APLOG_MARK, APLOG_ERR, 0, r, "malloc() failed: not enough memory");
212         ret = HTTP_INTERNAL_SERVER_ERROR;
213         goto end;
214      }
215      sprintf(ktname, "KRB5_KTNAME=%s", conf->krb5_keytab);
216      putenv(ktname);
217 #ifdef HEIMDAL
218      /* Seems to be also supported by latest MIT */
219      gsskrb5_register_acceptor_identity(conf->krb_5_keytab);
220 #endif
221   }
222
223   ret = get_gss_creds(r, conf, &server_creds);
224   if (ret)
225      goto end;
226
227   /* ap_getword() shifts parameter */
228   auth_param = ap_getword_white(r->pool, &auth_line);
229   if (auth_param == NULL) {
230      gss_log(APLOG_MARK, APLOG_ERR, 0, r,
231              "No Authorization parameter in request from client");
232      ret = HTTP_UNAUTHORIZED;
233      goto end;
234   }
235
236   if (ctx->state == GSS_CTX_ESTABLISHED) {
237       gss_delete_sec_context(&minor_status, &ctx->context, GSS_C_NO_BUFFER);
238       ctx->context = GSS_C_NO_CONTEXT;
239       ctx->state = GSS_CTX_EMPTY;
240   }
241
242   input_token.length = apr_base64_decode_len(auth_param) + 1;
243   input_token.value = apr_pcalloc(r->connection->pool, input_token.length);
244   if (input_token.value == NULL) {
245      gss_log(APLOG_MARK, APLOG_ERR, 0, r,
246              "ap_pcalloc() failed (not enough memory)");
247      ret = HTTP_INTERNAL_SERVER_ERROR;
248      goto end;
249   }
250   input_token.length = apr_base64_decode(input_token.value, auth_param);
251
252   /* LOG length, type */
253
254 #ifdef GSSAPI_SUPPORTS_SPNEGO
255   accept_sec_context = gss_accept_sec_context;
256 #else
257   accept_sec_context = (cmp_gss_type(&input_token, &spnego_oid) == 0) ?
258                       gss_accept_sec_context_spnego : gss_accept_sec_context;
259 #endif  
260
261   major_status = accept_sec_context(&minor_status,
262                                   &ctx->context,
263                                   server_creds,
264                                   &input_token,
265                                   GSS_C_NO_CHANNEL_BINDINGS,
266                                   NULL,
267                                   NULL,
268                                   &output_token,
269                                   &ret_flags,
270                                   NULL,
271                                   &delegated_cred);
272   gss_log(APLOG_MARK, APLOG_DEBUG, 0, r,
273           "Client %s us their credential",
274           (ret_flags & GSS_C_DELEG_FLAG) ? "delegated" : "didn't delegate");
275   if (output_token.length) {
276      char *token = NULL;
277      size_t len;
278      
279      len = apr_base64_encode_len(output_token.length) + 1;
280      token = apr_pcalloc(r->connection->pool, len + 1);
281      if (token == NULL) {
282         gss_log(APLOG_MARK, APLOG_ERR, 0, r,
283                 "ap_pcalloc() failed (not enough memory)");
284         ret = HTTP_INTERNAL_SERVER_ERROR;
285         gss_release_buffer(&minor_status2, &output_token);
286         goto end;
287      }
288      apr_base64_encode(token, output_token.value, output_token.length);
289      token[len] = '\0';
290      *negotiate_ret_value = token;
291      gss_log(APLOG_MARK, APLOG_DEBUG, 0, r,
292              "GSS-API token of length %d bytes will be sent back",
293              output_token.length);
294      gss_release_buffer(&minor_status2, &output_token);
295   }
296
297   if (GSS_ERROR(major_status)) {
298      gss_log(APLOG_MARK, APLOG_ERR, 0, r,
299              "%s", get_gss_error(r, major_status, minor_status,
300                                  "Failed to establish authentication"));
301 #if 0
302      /* Don't offer the Negotiate method again if call to GSS layer failed */
303      /* XXX ... which means we don't return the "error" output */
304      *negotiate_ret_value = NULL;
305 #endif
306      gss_delete_sec_context(&minor_status, &ctx->context, GSS_C_NO_BUFFER);
307      ctx->context = GSS_C_NO_CONTEXT;
308      ctx->state = GSS_CTX_EMPTY;
309      ret = HTTP_UNAUTHORIZED;
310      goto end;
311   }
312
313   if (major_status & GSS_S_CONTINUE_NEEDED) {
314      ctx->state = GSS_CTX_IN_PROGRESS;
315      ret = HTTP_UNAUTHORIZED;
316      goto end;
317   }
318
319   major_status = gss_inquire_context(&minor_status, ctx->context, &client_name,
320                                      NULL, NULL, NULL, NULL, NULL, NULL);
321   if (GSS_ERROR(major_status)) {
322       gss_log(APLOG_MARK, APLOG_ERR, 0, r,
323               "%s", get_gss_error(r, major_status, minor_status, "gss_inquire_context() failed"));
324       ret = HTTP_INTERNAL_SERVER_ERROR;
325       goto end;
326   }
327
328   major_status = gss_display_name(&minor_status, client_name, &output_token, NULL);
329   gss_release_name(&minor_status, &client_name); 
330   if (GSS_ERROR(major_status)) {
331     gss_log(APLOG_MARK, APLOG_ERR, 0, r,
332             "%s", get_gss_error(r, major_status, minor_status,
333                                 "gss_display_name() failed"));
334     ret = HTTP_INTERNAL_SERVER_ERROR;
335     goto end;
336   }
337
338   ctx->state = GSS_CTX_ESTABLISHED;
339   ctx->user = apr_pstrdup(r->pool, output_token.value);
340   gss_release_buffer(&minor_status, &output_token);
341
342   ret = OK;
343
344 end:
345   if (delegated_cred)
346      gss_release_cred(&minor_status, &delegated_cred);
347
348   if (output_token.length) 
349      gss_release_buffer(&minor_status, &output_token);
350
351   if (client_name != GSS_C_NO_NAME)
352      gss_release_name(&minor_status, &client_name);
353
354   if (server_creds != GSS_C_NO_CREDENTIAL)
355      gss_release_cred(&minor_status, &server_creds);
356
357   return ret;
358 }