Implemented support for exporting GSS NAME attributes as environment variables.
[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 void
35 gss_log(const char *file,
36         int line,
37 #if AP_SERVER_MAJORVERSION_NUMBER == 2 && AP_SERVER_MINORVERSION_NUMBER == 4
38         int module_index,
39 #endif
40         int level,
41         int status,
42         const request_rec *r,
43         const char *fmt, ...)
44 {
45     char errstr[1024];
46     va_list ap;
47
48     va_start(ap, fmt);
49     vsnprintf(errstr, sizeof(errstr), fmt, ap);
50     va_end(ap);
51
52     ap_log_rerror(file,
53                   line,
54 #if AP_SERVER_MAJORVERSION_NUMBER == 2 && AP_SERVER_MINORVERSION_NUMBER == 4
55                   module_index,
56 #endif
57                   level | APLOG_NOERRNO,
58                   status,
59                   r,
60                   "%s",
61                   errstr);
62 }
63
64 apr_status_t
65 gss_cleanup_conn_ctx(void *data)
66 {
67     gss_conn_ctx ctx = (gss_conn_ctx) data;
68     OM_uint32 minor_status;
69
70     if (ctx && ctx->context != GSS_C_NO_CONTEXT)
71         gss_delete_sec_context(&minor_status, &ctx->context, GSS_C_NO_BUFFER);
72
73     if (ctx && ctx->server_creds != GSS_C_NO_CREDENTIAL)
74       gss_release_cred(&minor_status, &ctx->server_creds);
75
76     return APR_SUCCESS;
77 }
78
79 gss_conn_ctx
80 gss_create_conn_ctx(request_rec *r, gss_auth_config *conf)
81 {
82   char key[1024];
83   gss_conn_ctx ctx = NULL;
84
85   snprintf(key, sizeof(key), "mod_auth_gssweb:conn_ctx");
86
87   if (NULL == (ctx = (gss_conn_ctx) apr_pcalloc(r->connection->pool, sizeof(*ctx)))) {
88     gss_log(APLOG_MARK, APLOG_ERR, 0, r, "gss_create_conn_ctx: Can't allocate GSS context");
89     return NULL;
90   }
91   ctx->context = GSS_C_NO_CONTEXT;
92   ctx->state = GSS_CTX_EMPTY;
93   ctx->filter_stat = GSS_FILT_NEW;
94   ctx->user = NULL;
95   ctx->name_attributes = NULL;
96   apr_pool_create(&ctx->pool, r->connection->pool);
97   /* register the context in the memory pool, so it can be freed
98    * when the connection/request is terminated */
99   apr_pool_cleanup_register(ctx->pool, (void *)ctx,
100                               gss_cleanup_conn_ctx, apr_pool_cleanup_null);
101
102   /* Acquire and store server credentials */
103   if (0 == get_gss_creds(r, conf, &(ctx->server_creds))) {
104     gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gss_create_conn_ctx: Server credentials acquired");
105   } else {
106     gss_log(APLOG_MARK, APLOG_ERR, 0, r, "gss_create_conn_ctx: Error: Server credentials NOT acquired");
107     return NULL;
108   }
109
110   apr_pool_userdata_set(ctx, key, gss_cleanup_conn_ctx, r->connection->pool);
111
112   return ctx;
113 }
114
115 gss_conn_ctx
116 gss_retrieve_conn_ctx(request_rec *r)
117 {
118   char key[1024];
119   gss_conn_ctx ctx = NULL;
120
121   snprintf(key, sizeof(key), "mod_auth_gssweb:conn_ctx");
122   apr_pool_userdata_get((void **)&ctx, key, r->connection->pool);
123
124   if (NULL == ctx)
125     gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gss_retrieve_conn_ctx: No GSS context found");
126
127   return ctx;
128 }
129
130 void *
131 gss_config_dir_create(apr_pool_t *p, char *d)
132 {
133     gss_auth_config *conf;
134
135     conf = (gss_auth_config *) apr_pcalloc(p, sizeof(*conf));
136     conf->pool = p;
137
138     return conf;
139 }
140
141
142 const char *
143 get_gss_error(request_rec *r, OM_uint32 err_maj, OM_uint32 err_min, char *prefix)
144 {
145    OM_uint32 maj_stat, min_stat;
146    OM_uint32 msg_ctx = 0;
147    gss_buffer_desc status_string;
148    char *err_msg;
149    int first_pass;
150
151    gss_log(APLOG_MARK, APLOG_DEBUG, 0, r,
152            "GSS-API major_status:%8.8x, minor_status:%8.8x",
153            err_maj, err_min);
154
155    err_msg = apr_pstrdup(r->pool, prefix);
156    do {
157       maj_stat = gss_display_status (&min_stat,
158                                      err_maj,
159                                      GSS_C_GSS_CODE,
160                                      GSS_C_NO_OID,
161                                      &msg_ctx,
162                                      &status_string);
163       if (!GSS_ERROR(maj_stat)) {
164          err_msg = apr_pstrcat(r->pool, err_msg,
165                                ": ", (char*) status_string.value, NULL);
166          gss_release_buffer(&min_stat, &status_string);
167          first_pass = 0;
168       }
169    } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
170
171    msg_ctx = 0;
172    err_msg = apr_pstrcat(r->pool, err_msg, " (", NULL);
173    first_pass = 1;
174    do {
175       maj_stat = gss_display_status (&min_stat,
176                                      err_min,
177                                      GSS_C_MECH_CODE,
178                                      GSS_C_NULL_OID,
179                                      &msg_ctx,
180                                      &status_string);
181       if (!GSS_ERROR(maj_stat)) {
182          err_msg = apr_pstrcat(r->pool, err_msg,
183                                (first_pass) ? "" : ", ",
184                                (char *) status_string.value,
185                                NULL);
186          gss_release_buffer(&min_stat, &status_string);
187          first_pass = 0;
188       }
189    } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
190    err_msg = apr_pstrcat(r->pool, err_msg, ")", NULL);
191
192    return err_msg;
193 }
194
195 int
196 get_gss_creds(request_rec *r,
197               gss_auth_config *conf,
198               gss_cred_id_t *server_creds)
199 {
200    gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
201    OM_uint32 major_status, minor_status, minor_status2;
202    gss_name_t server_name = GSS_C_NO_NAME;
203    char buf[1024];
204    int have_server_princ;
205
206    if (conf->service_name && strcmp(conf->service_name, "Any") == 0) {
207        *server_creds = GSS_C_NO_CREDENTIAL;
208        return 0;
209    }
210
211    have_server_princ = conf->service_name && strchr(conf->service_name, '/') != NULL;
212    if (have_server_princ)
213        strncpy(buf, conf->service_name, sizeof(buf));
214    else
215        snprintf(buf, sizeof(buf), "%s@%s",
216                (conf->service_name) ? conf->service_name : SERVICE_NAME,
217                ap_get_server_name(r));
218
219    token.value = buf;
220    token.length = strlen(buf) + 1;
221
222    major_status = gss_import_name(&minor_status, &token,
223                                   (have_server_princ) ? (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME : (gss_OID) GSS_C_NT_HOSTBASED_SERVICE,
224                                   &server_name);
225    memset(&token, 0, sizeof(token));
226    if (GSS_ERROR(major_status)) {
227       gss_log(APLOG_MARK, APLOG_ERR, 0, r,
228               "%s", get_gss_error(r, major_status, minor_status,
229               "gss_import_name() failed"));
230       return HTTP_INTERNAL_SERVER_ERROR;
231    }
232
233    major_status = gss_display_name(&minor_status, server_name, &token, NULL);
234    if (GSS_ERROR(major_status)) {
235       /* Perhaps we could just ignore this error but it's safer to give up now,
236          I think */
237       gss_log(APLOG_MARK, APLOG_ERR, 0, r,
238               "%s", get_gss_error(r, major_status, minor_status,
239                                   "gss_display_name() failed"));
240       return HTTP_INTERNAL_SERVER_ERROR;
241    }
242
243    gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "Acquiring creds for %s", token.value);
244    gss_release_buffer(&minor_status, &token);
245
246    major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
247                                    GSS_C_NO_OID_SET, GSS_C_ACCEPT,
248                                    server_creds, NULL, NULL);
249    gss_release_name(&minor_status2, &server_name);
250    if (GSS_ERROR(major_status)) {
251       gss_log(APLOG_MARK, APLOG_ERR, 0, r,
252               "%s", get_gss_error(r, major_status, minor_status,
253                                   "Failed to load GSS-API credentials"));
254       return HTTP_INTERNAL_SERVER_ERROR;
255    }
256
257    return 0;
258 }
259
260 int
261 cmp_gss_type(gss_buffer_t token, gss_OID oid)
262 {
263    unsigned char *p;
264    size_t len;
265
266    if (token->length == 0)
267       return GSS_S_DEFECTIVE_TOKEN;
268
269    p = token->value;
270    if (*p++ != 0x60)
271       return GSS_S_DEFECTIVE_TOKEN;
272    len = *p++;
273    if (len & 0x80) {
274       if ((len & 0x7f) > 4)
275          return GSS_S_DEFECTIVE_TOKEN;
276       p += len & 0x7f;
277    }
278    if (*p++ != 0x06)
279       return GSS_S_DEFECTIVE_TOKEN;
280
281    if (((OM_uint32) *p++) != oid->length)
282       return GSS_S_DEFECTIVE_TOKEN;
283
284    return memcmp(p, oid->elements, oid->length);
285 }
286
287 /*
288  * Name attributes to environment variables code
289  * This code is strongly based on the code from https://github.com/modauthgssapi/mod_auth_gssapi
290  */
291
292 static char *mag_status(request_rec *req, int type, uint32_t err)
293 {
294     uint32_t maj_ret, min_ret;
295     gss_buffer_desc text;
296     uint32_t msg_ctx;
297     char *msg_ret;
298     int len;
299
300     msg_ret = NULL;
301     msg_ctx = 0;
302     do {
303         maj_ret = gss_display_status(&min_ret, err, type,
304                                      GSS_C_NO_OID, &msg_ctx, &text);
305         if (maj_ret != GSS_S_COMPLETE) {
306             return msg_ret;
307         }
308
309         len = text.length;
310         if (msg_ret) {
311             msg_ret = apr_psprintf(req->pool, "%s, %*s",
312                                    msg_ret, len, (char *)text.value);
313         } else {
314             msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
315         }
316         gss_release_buffer(&min_ret, &text);
317     } while (msg_ctx != 0);
318
319     return msg_ret;
320 }
321
322 char *mag_error(request_rec *req, const char *msg, uint32_t maj, uint32_t min)
323 {
324     char *msg_maj;
325     char *msg_min;
326
327     msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
328     msg_min = mag_status(req, GSS_C_MECH_CODE, min);
329     return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
330 }
331
332
333 static char mag_get_name_attr(request_rec *req,
334                               gss_name_t name, name_attr *attr)
335 {
336     uint32_t maj, min;
337
338     maj = gss_get_name_attribute(&min, name, &attr->name,
339                                  &attr->authenticated,
340                                  &attr->complete,
341                                  &attr->value,
342                                  &attr->display_value,
343                                  &attr->more);
344     if (GSS_ERROR(maj)) {
345         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
346                       "gss_get_name_attribute() failed on %.*s%s",
347                       (int)attr->name.length, (char *)attr->name.value,
348                       mag_error(req, "", maj, min));
349         return 0;
350     }
351
352     return 1;
353 }
354
355 static void mc_add_name_attribute(gss_conn_ctx_t *gss_ctx,
356                                   const char *name, const char *value)
357 {
358     size_t size;
359
360     if (gss_ctx->na_count % 16 == 0) {
361         size = sizeof(mag_attr) * (gss_ctx->na_count + 16);
362         gss_ctx->name_attributes = realloc(gss_ctx->name_attributes, size);
363         if (!gss_ctx->name_attributes) apr_pool_abort_get(gss_ctx->pool)(ENOMEM);
364     }
365
366     gss_ctx->name_attributes[gss_ctx->na_count].name = apr_pstrdup(gss_ctx->pool, name);
367     gss_ctx->name_attributes[gss_ctx->na_count].value = apr_pstrdup(gss_ctx->pool, value);
368     gss_ctx->na_count++;
369 }
370
371 static void mag_set_env_name_attr(request_rec *req, gss_conn_ctx_t *gss_ctx,
372                                   name_attr *attr)
373 {
374     char *value = "";
375     int len = 0;
376
377     /* Prefer a display_value, otherwise fallback to value */
378     if (attr->display_value.length != 0) {
379         len = attr->display_value.length;
380         value = (char *)attr->display_value.value;
381     } else if (attr->value.length != 0) {
382         len = apr_base64_encode_len(attr->value.length);
383         value = apr_pcalloc(req->pool, len);
384         len = apr_base64_encode(value,
385                                 (char *)attr->value.value,
386                                 attr->value.length);
387     }
388
389     if (attr->number == 1) {
390         mc_add_name_attribute(gss_ctx,
391                               attr->env_name,
392                               apr_psprintf(req->pool, "%.*s", len, value));
393     }
394     if (attr->more != 0 || attr->number > 1) {
395         mc_add_name_attribute(gss_ctx,
396                               apr_psprintf(req->pool, "%s_%d",
397                                            attr->env_name, attr->number),
398                               apr_psprintf(req->pool, "%.*s", len, value));
399     }
400     if (attr->more == 0 && attr->number > 1) {
401         mc_add_name_attribute(gss_ctx,
402                               apr_psprintf(req->pool, "%s_N", attr->env_name),
403                               apr_psprintf(req->pool, "%d", attr->number - 1));
404     }
405 }
406
407 static void mag_add_json_name_attr(request_rec *req, char first,
408                                    name_attr *attr, char **json)
409 {
410     const char *value = "";
411     int len = 0;
412     char *b64value = NULL;
413     int b64len = 0;
414     const char *vstart = "";
415     const char *vend = "";
416     const char *vformat;
417
418     if (attr->value.length != 0) {
419         b64len = apr_base64_encode_len(attr->value.length);
420         b64value = apr_pcalloc(req->pool, b64len);
421         b64len = apr_base64_encode(b64value,
422                                    (char *)attr->value.value,
423                                    attr->value.length);
424     }
425     if (attr->display_value.length != 0) {
426         len = attr->display_value.length;
427         value = (const char *)attr->display_value.value;
428     }
429     if (attr->number == 1) {
430         *json = apr_psprintf(req->pool,
431                             "%s%s\"%.*s\":{\"authenticated\":%s,"
432                                           "\"complete\":%s,"
433                                           "\"values\":[",
434                             *json, (first ? "" : ","),
435                             (int)attr->name.length, (char *)attr->name.value,
436                             attr->authenticated ? "true" : "false",
437                             attr->complete ? "true" : "false");
438     } else {
439         vstart = ",";
440     }
441
442     if (b64value) {
443         if (len) {
444             vformat = "%s%s{\"raw\":\"%s\",\"display\":\"%.*s\"}%s";
445         } else {
446             vformat = "%s%s{\"raw\":\"%s\",\"display\":%.*s}%s";
447         }
448     } else {
449         if (len) {
450             vformat = "%s%s{\"raw\":%s,\"display\":\"%.*s\"}%s";
451         } else {
452             vformat = "%s%s{\"raw\":%s,\"display\":%.*s}%s";
453         }
454     }
455
456     if (attr->more == 0) {
457         vend = "]}";
458     }
459
460     *json = apr_psprintf(req->pool, vformat, *json,
461                         vstart,
462                         b64value ? b64value : "null",
463                         len ? len : 4, len ? value : "null",
464                         vend);
465 }
466
467 gss_buffer_desc empty_buffer = GSS_C_EMPTY_BUFFER;
468
469 void mag_get_name_attributes(request_rec *req, gss_auth_config *cfg,
470                              gss_name_t name, gss_conn_ctx_t *gss_ctx)
471 {
472     if (!cfg->name_attributes) {
473         return;
474     }
475
476     uint32_t maj, min;
477     gss_buffer_set_t attrs = GSS_C_NO_BUFFER_SET;
478     name_attr attr;
479     char *json = NULL;
480     char *error;
481     int count = 0;
482     int i, j;
483
484     maj = gss_inquire_name(&min, name, NULL, NULL, &attrs);
485     if (GSS_ERROR(maj)) {
486         error = mag_error(req, "gss_inquire_name() failed", maj, min);
487         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s", error);
488         apr_table_set(req->subprocess_env, "GSS_NAME_ATTR_ERROR", error);
489         return;
490     }
491
492     if (!attrs || attrs->count == 0) {
493         mc_add_name_attribute(gss_ctx, "GSS_NAME_ATTR_ERROR", "0 attributes found");
494     }
495
496     if (cfg->name_attributes->output_json) {
497
498         if (attrs) count = attrs->count;
499
500         json = apr_psprintf(req->pool,
501                             "{\"name\":\"%s\",\"attributes\":{",
502                             gss_ctx->user);
503     } else {
504         count = cfg->name_attributes->map_count;
505     }
506
507     for (i = 0; i < count; i++) {
508         memset(&attr, 0, sizeof(name_attr));
509
510         if (cfg->name_attributes->output_json) {
511             attr.name = attrs->elements[i];
512             for (j = 0; j < cfg->name_attributes->map_count; j++) {
513                 if (strncmp(cfg->name_attributes->map[j].attr_name,
514                             attrs->elements[i].value,
515                             attrs->elements[i].length) == 0) {
516                     attr.env_name = cfg->name_attributes->map[j].env_name;
517                     break;
518                 }
519             }
520         } else {
521             attr.name.length = strlen(cfg->name_attributes->map[i].attr_name);
522             attr.name.value = cfg->name_attributes->map[i].attr_name;
523             attr.env_name = cfg->name_attributes->map[i].env_name;
524         }
525
526         attr.number = 0;
527         attr.more = -1;
528         do {
529             char code = 0;
530             attr.number++;
531             attr.value = empty_buffer;
532             attr.display_value = empty_buffer;
533             if (!mag_get_name_attr(req, name, &attr)) break;
534
535             if (cfg->name_attributes->output_json) {
536                 mag_add_json_name_attr(req, i == 0, &attr, &json);
537             }
538             if (attr.env_name) {
539                 mag_set_env_name_attr(req, gss_ctx, &attr);
540             }
541
542             gss_release_buffer(&min, &attr.value);
543             gss_release_buffer(&min, &attr.display_value);
544         } while (attr.more != 0);
545     }
546
547     if (cfg->name_attributes->output_json) {
548         json = apr_psprintf(req->pool, "%s}}", json);
549         mc_add_name_attribute(gss_ctx, "GSS_NAME_ATTRS_JSON", json);
550     }
551 }
552
553 static void mag_set_name_attributes(request_rec *req, gss_conn_ctx_t *gss_ctx)
554 {
555     int i = 0;
556     for (i = 0; i < gss_ctx->na_count; i++) {
557         apr_table_set(req->subprocess_env,
558                       gss_ctx->name_attributes[i].name,
559                       gss_ctx->name_attributes[i].value);
560     }
561 }
562
563 void mag_set_req_data(request_rec *req,
564                       gss_auth_config *cfg,
565                       gss_conn_ctx_t *gss_ctx)
566 {
567     if (gss_ctx->name_attributes) {
568         mag_set_name_attributes(req, gss_ctx);
569     }
570 }
571
572 #define GSS_NAME_ATTR_USERDATA "GSS Name Attributes Userdata"
573
574 static apr_status_t mag_name_attrs_cleanup(void *data)
575 {
576     gss_auth_config *cfg = (gss_auth_config *)data;
577     free(cfg->name_attributes);
578     cfg->name_attributes = NULL;
579     return 0;
580 }
581
582 const char *mag_name_attrs(cmd_parms *parms, void *mconfig,
583                                   const char *w)
584 {
585     gss_auth_config *cfg = (gss_auth_config *)mconfig;
586     void *tmp_na;
587     size_t size = 0;
588     char *p;
589     int c;
590
591     if (!cfg->name_attributes) {
592         size = sizeof(mag_name_attributes)
593                 + (sizeof(mag_na_map) * 16);
594     } else if (cfg->name_attributes->map_count % 16 == 0) {
595         size = sizeof(mag_name_attributes)
596                 + (sizeof(mag_na_map)
597                     * (cfg->name_attributes->map_count + 16));
598     }
599     if (size) {
600         tmp_na = realloc(cfg->name_attributes, size);
601         if (!tmp_na) apr_pool_abort_get(cfg->pool)(ENOMEM);
602
603         if (cfg->name_attributes) {
604             size_t empty = (sizeof(mag_na_map) * 16);
605             memset(tmp_na + size - empty, 0, empty);
606         } else {
607             memset(tmp_na, 0, size);
608         }
609         cfg->name_attributes = (mag_name_attributes *)tmp_na;
610         apr_pool_userdata_setn(cfg, GSS_NAME_ATTR_USERDATA,
611                                mag_name_attrs_cleanup, cfg->pool);
612     }
613
614
615     p = strchr(w, ' ');
616     if (p == NULL) {
617         if (strcmp(w, "json") == 0) {
618             cfg->name_attributes->output_json = 1;
619         } else {
620             ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
621                          "Invalid Name Attributes value [%s].", w);
622         }
623         return NULL;
624     }
625
626     c = cfg->name_attributes->map_count;
627     cfg->name_attributes->map[c].env_name = apr_pstrndup(cfg->pool, w, p-w);
628     p++;
629     cfg->name_attributes->map[c].attr_name = apr_pstrdup(cfg->pool, p);
630     cfg->name_attributes->map_count += 1;
631
632     return NULL;
633 }