Add GssapiAllowedMech option
[mod_auth_gssapi.git] / src / mod_auth_gssapi.c
1 /*
2    MOD AUTH GSSAPI
3
4    Copyright (C) 2014 Simo Sorce <simo@redhat.com>
5
6    Permission is hereby granted, free of charge, to any person obtaining a
7    copy of this software and associated documentation files (the "Software"),
8    to deal in the Software without restriction, including without limitation
9    the rights to use, copy, modify, merge, publish, distribute, sublicense,
10    and/or sell copies of the Software, and to permit persons to whom the
11    Software is furnished to do so, subject to the following conditions:
12
13    The above copyright notice and this permission notice shall be included in
14    all copies or substantial portions of the Software.
15
16    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22    DEALINGS IN THE SOFTWARE.
23 */
24
25 #include "mod_auth_gssapi.h"
26
27 const gss_OID_desc gss_mech_ntlmssp = {
28     GSS_NTLMSSP_OID_LENGTH, GSS_NTLMSSP_OID_STRING
29 };
30
31 #define MOD_AUTH_GSSAPI_VERSION PACKAGE_NAME "/" PACKAGE_VERSION
32
33 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
34
35 APLOG_USE_MODULE(auth_gssapi);
36
37 static char *mag_status(request_rec *req, int type, uint32_t err)
38 {
39     uint32_t maj_ret, min_ret;
40     gss_buffer_desc text;
41     uint32_t msg_ctx;
42     char *msg_ret;
43     int len;
44
45     msg_ret = NULL;
46     msg_ctx = 0;
47     do {
48         maj_ret = gss_display_status(&min_ret, err, type,
49                                      GSS_C_NO_OID, &msg_ctx, &text);
50         if (maj_ret != GSS_S_COMPLETE) {
51             return msg_ret;
52         }
53
54         len = text.length;
55         if (msg_ret) {
56             msg_ret = apr_psprintf(req->pool, "%s, %*s",
57                                    msg_ret, len, (char *)text.value);
58         } else {
59             msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
60         }
61         gss_release_buffer(&min_ret, &text);
62     } while (msg_ctx != 0);
63
64     return msg_ret;
65 }
66
67 static char *mag_error(request_rec *req, const char *msg,
68                        uint32_t maj, uint32_t min)
69 {
70     char *msg_maj;
71     char *msg_min;
72
73     msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
74     msg_min = mag_status(req, GSS_C_MECH_CODE, min);
75     return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
76 }
77
78 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
79
80 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
81                            apr_pool_t *temp, server_rec *s)
82 {
83     /* FIXME: create mutex to deal with connections and contexts ? */
84     mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
85     mag_post_config_session();
86     ap_add_version_component(cfgpool, MOD_AUTH_GSSAPI_VERSION);
87
88     return OK;
89 }
90
91 static int mag_pre_connection(conn_rec *c, void *csd)
92 {
93     struct mag_conn *mc;
94
95     mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
96
97     mc->parent = c->pool;
98     ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
99     return OK;
100 }
101
102 static apr_status_t mag_conn_destroy(void *ptr)
103 {
104     struct mag_conn *mc = (struct mag_conn *)ptr;
105     uint32_t min;
106
107     if (mc->ctx) {
108         (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
109         mc->established = false;
110     }
111     return APR_SUCCESS;
112 }
113
114 static bool mag_conn_is_https(conn_rec *c)
115 {
116     if (mag_is_https) {
117         if (mag_is_https(c)) return true;
118     }
119
120     return false;
121 }
122
123 static bool mag_acquire_creds(request_rec *req,
124                               struct mag_config *cfg,
125                               gss_OID_set desired_mechs,
126                               gss_cred_usage_t cred_usage,
127                               gss_cred_id_t *creds)
128 {
129     uint32_t maj, min;
130 #ifdef HAVE_CRED_STORE
131     gss_const_key_value_set_t store = cfg->cred_store;
132
133     maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
134                                 desired_mechs, cred_usage, store, creds,
135                                 NULL, NULL);
136 #else
137     maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
138                            desired_mechs, cred_usage, creds, NULL, NULL);
139 #endif
140
141     if (GSS_ERROR(maj)) {
142         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
143                       mag_error(req, "gss_acquire_cred[_from]() "
144                                 "failed to get server creds",
145                                 maj, min));
146         return false;
147     }
148
149     return true;
150 }
151
152 #ifdef HAVE_CRED_STORE
153 static char *escape(apr_pool_t *pool, const char *name,
154                     char find, const char *replace)
155 {
156     char *escaped = NULL;
157     char *namecopy;
158     char *n;
159     char *p;
160
161     namecopy = apr_pstrdup(pool, name);
162
163     p = strchr(namecopy, find);
164     if (!p) return namecopy;
165
166     /* first segment */
167     n = namecopy;
168     while (p) {
169         /* terminate previous segment */
170         *p = '\0';
171         if (escaped) {
172             escaped = apr_pstrcat(pool, escaped, n, replace, NULL);
173         } else {
174             escaped = apr_pstrcat(pool, n, replace, NULL);
175         }
176         /* move to next segment */
177         n = p + 1;
178         p = strchr(n, find);
179     }
180     /* append last segment if any */
181     if (*n) {
182         escaped = apr_pstrcat(pool, escaped, n, NULL);
183     }
184
185     return escaped;
186 }
187
188 static void mag_store_deleg_creds(request_rec *req,
189                                   char *dir, char *clientname,
190                                   gss_cred_id_t delegated_cred,
191                                   char **ccachefile)
192 {
193     gss_key_value_element_desc element;
194     gss_key_value_set_desc store;
195     char *value;
196     uint32_t maj, min;
197     char *escaped;
198
199     /* We need to escape away '/', we can't have path separators in
200      * a ccache file name */
201     /* first double escape the esacping char (~) if any */
202     escaped = escape(req->pool, clientname, '~', "~~");
203     if (!escaped) return;
204     /* then escape away the separator (/) if any */
205     escaped = escape(req->pool, escaped, '/', "~");
206     if (!escaped) return;
207
208     value = apr_psprintf(req->pool, "FILE:%s/%s", dir, escaped);
209
210     element.key = "ccache";
211     element.value = value;
212     store.elements = &element;
213     store.count = 1;
214
215     maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
216                               GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
217     if (GSS_ERROR(maj)) {
218         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
219                       mag_error(req, "failed to store delegated creds",
220                                 maj, min));
221     }
222
223     *ccachefile = value;
224 }
225 #endif
226
227 static int mag_auth(request_rec *req)
228 {
229     const char *type;
230     const char *auth_type;
231     struct mag_config *cfg;
232     const char *auth_header;
233     char *auth_header_type;
234     char *auth_header_value;
235     int ret = HTTP_UNAUTHORIZED;
236     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
237     gss_ctx_id_t *pctx;
238     gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
239     gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
240     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
241     gss_name_t client = GSS_C_NO_NAME;
242     gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
243     gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
244     gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
245     gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
246     gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
247     uint32_t flags;
248     uint32_t vtime;
249     uint32_t maj, min;
250     char *reply;
251     size_t replen;
252     char *clientname;
253     gss_OID mech_type = GSS_C_NO_OID;
254     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
255     struct mag_conn *mc = NULL;
256     bool is_basic = false;
257     gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
258     gss_name_t server = GSS_C_NO_NAME;
259 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
260     const char *user_ccache = NULL;
261     const char *orig_ccache = NULL;
262 #endif
263     uint32_t init_flags = 0;
264     time_t expiration;
265
266     type = ap_auth_type(req);
267     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
268         return DECLINED;
269     }
270
271     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
272
273     /* implicit auth for subrequests if main auth already happened */
274     if (!ap_is_initial_req(req)) {
275         type = ap_auth_type(req->main);
276         if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
277             /* warn if the subrequest location and the main request
278              * location have different configs */
279             if (cfg != ap_get_module_config(req->main->per_dir_config,
280                                             &auth_gssapi_module)) {
281                 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
282                               req, "Subrequest authentication bypass on "
283                                    "location with different configuration!");
284             }
285             if (req->main->user) {
286                 req->user = apr_pstrdup(req->pool, req->main->user);
287                 return OK;
288             } else {
289                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
290                               "The main request is tasked to establish the "
291                               "security context, can't proceed!");
292                 return HTTP_UNAUTHORIZED;
293             }
294         } else {
295             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
296                           "Subrequest GSSAPI auth with no auth on the main "
297                           "request. This operation may fail if other "
298                           "subrequests already established a context or the "
299                           "mechanism requires multiple roundtrips.");
300         }
301     }
302
303     if (cfg->ssl_only) {
304         if (!mag_conn_is_https(req->connection)) {
305             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
306                           "Not a TLS connection, refusing to authenticate!");
307             goto done;
308         }
309     }
310
311     if (cfg->gss_conn_ctx) {
312         mc = (struct mag_conn *)ap_get_module_config(
313                                                 req->connection->conn_config,
314                                                 &auth_gssapi_module);
315         if (!mc) {
316             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
317                           "Failed to retrieve connection context!");
318             goto done;
319         }
320     }
321
322     /* if available, session always supersedes connection bound data */
323     if (cfg->use_sessions) {
324         mag_check_session(req, cfg, &mc);
325     }
326
327     if (mc) {
328         /* register the context in the memory pool, so it can be freed
329          * when the connection/request is terminated */
330         apr_pool_userdata_set(mc, "mag_conn_ptr",
331                               mag_conn_destroy, mc->parent);
332
333         if (mc->established) {
334             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
335                           "Already established context found!");
336             apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
337             apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
338                           apr_psprintf(req->pool,
339                                        "%ld", (long)mc->expiration));
340             req->ap_auth_type = apr_pstrdup(req->pool, mc->auth_type);
341             req->user = apr_pstrdup(req->pool, mc->user_name);
342             ret = OK;
343             goto done;
344         }
345         pctx = &mc->ctx;
346     } else {
347         pctx = &ctx;
348     }
349
350     auth_header = apr_table_get(req->headers_in, "Authorization");
351     if (!auth_header) goto done;
352
353     auth_header_type = ap_getword_white(req->pool, &auth_header);
354     if (!auth_header_type) goto done;
355
356     if (strcasecmp(auth_header_type, "Negotiate") == 0) {
357         auth_type = "Negotiate";
358
359         auth_header_value = ap_getword_white(req->pool, &auth_header);
360         if (!auth_header_value) goto done;
361         input.length = apr_base64_decode_len(auth_header_value) + 1;
362         input.value = apr_pcalloc(req->pool, input.length);
363         if (!input.value) goto done;
364         input.length = apr_base64_decode(input.value, auth_header_value);
365     } else if ((strcasecmp(auth_header_type, "Basic") == 0) &&
366                (cfg->use_basic_auth == true)) {
367         auth_type = "Basic";
368         is_basic = true;
369
370         gss_buffer_desc ba_user;
371         gss_buffer_desc ba_pwd;
372
373         ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
374         if (!ba_pwd.value) goto done;
375         ba_user.value = ap_getword_nulls_nc(req->pool,
376                                             (char **)&ba_pwd.value, ':');
377         if (!ba_user.value) goto done;
378         if (((char *)ba_user.value)[0] == '\0' ||
379             ((char *)ba_pwd.value)[0] == '\0') {
380             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
381                           "Invalid empty user or password for Basic Auth");
382             goto done;
383         }
384         ba_user.length = strlen(ba_user.value);
385         ba_pwd.length = strlen(ba_pwd.value);
386         maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &client);
387         if (GSS_ERROR(maj)) {
388             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
389                           "In Basic Auth, %s",
390                           mag_error(req, "gss_import_name() failed",
391                                     maj, min));
392             goto done;
393         }
394 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
395         /* Set a per-thread ccache in case we are using kerberos,
396          * it is not elegant but avoids interference between threads */
397         long long unsigned int rndname;
398         apr_status_t rs;
399         rs = apr_generate_random_bytes((unsigned char *)(&rndname),
400                                        sizeof(long long unsigned int));
401         if (rs != APR_SUCCESS) {
402             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
403                           "Failed to generate random ccache name");
404             goto done;
405         }
406         user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
407         maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
408         if (GSS_ERROR(maj)) {
409             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
410                           "In Basic Auth, %s",
411                           mag_error(req, "gss_krb5_ccache_name() "
412                                     "failed", maj, min));
413             goto done;
414         }
415 #endif
416         maj = gss_acquire_cred_with_password(&min, client, &ba_pwd,
417                                              GSS_C_INDEFINITE,
418                                              cfg->allowed_mechs,
419                                              GSS_C_INITIATE,
420                                              &user_cred, NULL, NULL);
421         if (GSS_ERROR(maj)) {
422             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
423                           "In Basic Auth, %s",
424                           mag_error(req, "gss_acquire_cred_with_password() "
425                                     "failed", maj, min));
426             goto done;
427         }
428         gss_release_name(&min, &client);
429     } else {
430         goto done;
431     }
432
433     req->ap_auth_type = apr_pstrdup(req->pool, auth_type);
434
435 #ifdef HAVE_CRED_STORE
436     if (cfg->use_s4u2proxy) {
437         cred_usage = GSS_C_BOTH;
438     }
439 #endif
440     if (!mag_acquire_creds(req, cfg, GSS_C_NO_OID_SET,
441                            cred_usage, &acquired_cred)) {
442         goto done;
443     }
444
445     if (is_basic) {
446         if (cred_usage == GSS_C_BOTH) {
447             /* If GSS_C_BOTH is used then inquire_cred will return the client
448              * name instead of the SPN of the server credentials. Therefore we
449              * need to acquire a different set of credential setting
450              * GSS_C_ACCEPT explicitly */
451             if (!mag_acquire_creds(req, cfg, GSS_C_NO_OID_SET,
452                                    GSS_C_ACCEPT, &server_cred)) {
453                 goto done;
454             }
455         } else {
456             server_cred = acquired_cred;
457         }
458         maj = gss_inquire_cred(&min, server_cred, &server,
459                                NULL, NULL, NULL);
460         if (GSS_ERROR(maj)) {
461             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
462                           "%s", mag_error(req, "gss_inquired_cred_() "
463                                           "failed", maj, min));
464             goto done;
465         }
466         if (server_cred != acquired_cred) {
467             gss_release_cred(&min, &server_cred);
468         }
469
470 #ifdef HAVE_CRED_STORE
471         if (cfg->deleg_ccache_dir) {
472             /* delegate ourselves credentials so we store them as requested */
473             init_flags |= GSS_C_DELEG_FLAG;
474         }
475 #endif
476
477         /* output and input are inverted here, this is intentional */
478         maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
479                                    GSS_C_NO_OID, init_flags, 300,
480                                    GSS_C_NO_CHANNEL_BINDINGS, &output,
481                                    NULL, &input, NULL, NULL);
482         if (GSS_ERROR(maj)) {
483             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
484                           "%s", mag_error(req, "gss_init_sec_context() "
485                                           "failed", maj, min));
486             goto done;
487         }
488     }
489
490     if (!is_basic && cfg->allowed_mechs != GSS_C_NO_OID_SET) {
491         maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
492         if (GSS_ERROR(maj)) {
493             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
494                           mag_error(req, "gss_set_neg_mechs() failed",
495                                     maj, min));
496             goto done;
497         }
498     }
499
500     maj = gss_accept_sec_context(&min, pctx, acquired_cred,
501                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
502                                  &client, &mech_type, &output, &flags, &vtime,
503                                  &delegated_cred);
504     if (GSS_ERROR(maj)) {
505         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
506                       mag_error(req, "gss_accept_sec_context() failed",
507                                 maj, min));
508         goto done;
509     }
510     if (is_basic) {
511         while (maj == GSS_S_CONTINUE_NEEDED) {
512             gss_release_buffer(&min, &input);
513             /* output and input are inverted here, this is intentional */
514             maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
515                                        GSS_C_NO_OID, init_flags, 300,
516                                        GSS_C_NO_CHANNEL_BINDINGS, &output,
517                                        NULL, &input, NULL, NULL);
518             if (GSS_ERROR(maj)) {
519                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
520                               "%s", mag_error(req, "gss_init_sec_context() "
521                                               "failed", maj, min));
522                 goto done;
523             }
524             gss_release_buffer(&min, &output);
525             maj = gss_accept_sec_context(&min, pctx, acquired_cred,
526                                          &input, GSS_C_NO_CHANNEL_BINDINGS,
527                                          &client, &mech_type, &output, &flags,
528                                          &vtime, &delegated_cred);
529             if (GSS_ERROR(maj)) {
530                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
531                               "%s", mag_error(req, "gss_accept_sec_context()"
532                                               " failed", maj, min));
533                 goto done;
534             }
535         }
536     } else if (maj == GSS_S_CONTINUE_NEEDED) {
537         if (!mc) {
538             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
539                           "Mechanism needs continuation but neither "
540                           "GssapiConnectionBound nor "
541                           "GssapiUseSessions are available");
542             gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
543             gss_release_buffer(&min, &output);
544             output.length = 0;
545         }
546         /* auth not complete send token and wait next packet */
547         goto done;
548     }
549
550     /* Always set the GSS name in an env var */
551     maj = gss_display_name(&min, client, &name, NULL);
552     if (GSS_ERROR(maj)) {
553         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
554                       mag_error(req, "gss_display_name() failed",
555                                 maj, min));
556         goto done;
557     }
558     clientname = apr_pstrndup(req->pool, name.value, name.length);
559     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
560     expiration = time(NULL) + vtime;
561     apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
562                   apr_psprintf(req->pool, "%ld", (long)expiration));
563
564 #ifdef HAVE_CRED_STORE
565     if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
566         char *ccachefile = NULL;
567
568         mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
569                               delegated_cred, &ccachefile);
570
571         if (ccachefile) {
572             apr_table_set(req->subprocess_env, "KRB5CCNAME", ccachefile);
573         }
574     }
575 #endif
576
577     if (cfg->map_to_local) {
578         maj = gss_localname(&min, client, mech_type, &lname);
579         if (maj != GSS_S_COMPLETE) {
580             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
581                           mag_error(req, "gss_localname() failed", maj, min));
582             goto done;
583         }
584         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
585     } else {
586         req->user = clientname;
587     }
588
589     if (mc) {
590         mc->user_name = apr_pstrdup(mc->parent, req->user);
591         mc->gss_name = apr_pstrdup(mc->parent, clientname);
592         mc->established = true;
593         if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
594             vtime = MIN_SESS_EXP_TIME;
595         }
596         mc->expiration = expiration;
597         if (cfg->use_sessions) {
598             mag_attempt_session(req, cfg, mc);
599         }
600         mc->auth_type = auth_type;
601     }
602
603     if (cfg->send_persist)
604         apr_table_set(req->headers_out, "Persistent-Auth",
605             cfg->gss_conn_ctx ? "true" : "false");
606
607     ret = OK;
608
609 done:
610     if ((!is_basic) && (output.length != 0)) {
611         replen = apr_base64_encode_len(output.length) + 1;
612         reply = apr_pcalloc(req->pool, 10 + replen);
613         if (reply) {
614             memcpy(reply, "Negotiate ", 10);
615             apr_base64_encode(&reply[10], output.value, output.length);
616             apr_table_add(req->err_headers_out,
617                           "WWW-Authenticate", reply);
618         }
619     } else if (ret == HTTP_UNAUTHORIZED) {
620         apr_table_add(req->err_headers_out,
621                       "WWW-Authenticate", "Negotiate");
622         if (cfg->use_basic_auth) {
623             apr_table_add(req->err_headers_out,
624                           "WWW-Authenticate",
625                           apr_psprintf(req->pool, "Basic realm=\"%s\"",
626                                        ap_auth_name(req)));
627         }
628     }
629 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
630     if (user_ccache != NULL) {
631         maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
632         if (maj != GSS_S_COMPLETE) {
633             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
634                           "Failed to restore per-thread ccache, %s",
635                           mag_error(req, "gss_krb5_ccache_name() "
636                                     "failed", maj, min));
637         }
638     }
639 #endif
640     gss_delete_sec_context(&min, &user_ctx, &output);
641     gss_release_cred(&min, &user_cred);
642     gss_release_cred(&min, &acquired_cred);
643     gss_release_cred(&min, &delegated_cred);
644     gss_release_buffer(&min, &output);
645     gss_release_name(&min, &client);
646     gss_release_name(&min, &server);
647     gss_release_buffer(&min, &name);
648     gss_release_buffer(&min, &lname);
649     return ret;
650 }
651
652
653 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
654 {
655     struct mag_config *cfg;
656
657     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
658     cfg->pool = p;
659
660     return cfg;
661 }
662
663 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
664 {
665     struct mag_config *cfg = (struct mag_config *)mconfig;
666     cfg->ssl_only = on ? true : false;
667     return NULL;
668 }
669
670 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
671 {
672     struct mag_config *cfg = (struct mag_config *)mconfig;
673     cfg->map_to_local = on ? true : false;
674     return NULL;
675 }
676
677 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
678 {
679     struct mag_config *cfg = (struct mag_config *)mconfig;
680     cfg->gss_conn_ctx = on ? true : false;
681     return NULL;
682 }
683
684 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
685 {
686     struct mag_config *cfg = (struct mag_config *)mconfig;
687     cfg->send_persist = on ? true : false;
688     return NULL;
689 }
690
691 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
692 {
693     struct mag_config *cfg = (struct mag_config *)mconfig;
694     cfg->use_sessions = on ? true : false;
695     return NULL;
696 }
697
698 #ifdef HAVE_CRED_STORE
699 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
700 {
701     struct mag_config *cfg = (struct mag_config *)mconfig;
702     cfg->use_s4u2proxy = on ? true : false;
703
704     if (cfg->deleg_ccache_dir == NULL) {
705         cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
706     }
707     return NULL;
708 }
709 #endif
710
711 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
712 {
713     struct mag_config *cfg = (struct mag_config *)mconfig;
714     struct databuf keys;
715     unsigned char *val;
716     apr_status_t rc;
717     const char *k;
718     int l;
719
720     if (strncmp(w, "key:", 4) != 0) {
721         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
722                      "Invalid key format, expected prefix 'key:'");
723         return NULL;
724     }
725     k = w + 4;
726
727     l = apr_base64_decode_len(k);
728     val = apr_palloc(parms->temp_pool, l);
729
730     keys.length = (int)apr_base64_decode_binary(val, k);
731     keys.value = (unsigned char *)val;
732
733     if (keys.length != 32) {
734         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
735                      "Invalid key length, expected 32 got %d", keys.length);
736         return NULL;
737     }
738
739     rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
740     if (rc != OK) {
741         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
742                      "Failed to import sealing key!");
743     }
744     return NULL;
745 }
746
747 #ifdef HAVE_CRED_STORE
748
749 #define MAX_CRED_OPTIONS 10
750
751 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
752                                   const char *w)
753 {
754     struct mag_config *cfg = (struct mag_config *)mconfig;
755     gss_key_value_element_desc *elements;
756     uint32_t count;
757     size_t size;
758     const char *p;
759     char *value;
760     char *key;
761
762     p = strchr(w, ':');
763     if (!p) {
764         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
765                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
766         return NULL;
767     }
768
769     key = apr_pstrndup(parms->pool, w, (p-w));
770     value = apr_pstrdup(parms->pool, p + 1);
771
772     if (!cfg->cred_store) {
773         cfg->cred_store = apr_pcalloc(parms->pool,
774                                       sizeof(gss_key_value_set_desc));
775         size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
776         cfg->cred_store->elements = apr_palloc(parms->pool, size);
777     }
778
779     elements = cfg->cred_store->elements;
780     count = cfg->cred_store->count;
781
782     if (count >= MAX_CRED_OPTIONS) {
783         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
784                      "Too many GssapiCredStore options (MAX: %d)",
785                      MAX_CRED_OPTIONS);
786         return NULL;
787     }
788     cfg->cred_store->count++;
789
790     elements[count].key = key;
791     elements[count].value = value;
792
793     return NULL;
794 }
795
796 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
797                                         const char *value)
798 {
799     struct mag_config *cfg = (struct mag_config *)mconfig;
800
801     cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
802
803     return NULL;
804 }
805 #endif
806
807 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
808 {
809     struct mag_config *cfg = (struct mag_config *)mconfig;
810
811     cfg->use_basic_auth = on ? true : false;
812     return NULL;
813 }
814
815 #define MAX_ALLOWED_MECHS 10
816
817 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
818                                   const char *w)
819 {
820     struct mag_config *cfg = (struct mag_config *)mconfig;
821     gss_const_OID oid;
822     size_t size;
823
824     if (!cfg->allowed_mechs) {
825         cfg->allowed_mechs = apr_pcalloc(parms->pool,
826                                          sizeof(gss_OID_set_desc));
827         size = sizeof(gss_OID) * MAX_ALLOWED_MECHS;
828         cfg->allowed_mechs->elements = apr_palloc(parms->pool, size);
829     }
830
831     if (strcmp(w, "krb5") == 0) {
832         oid = gss_mech_krb5;
833     } else if (strcmp(w, "iakerb") == 0) {
834         oid = gss_mech_iakerb;
835     } else if (strcmp(w, "ntlmssp") == 0) {
836         oid = &gss_mech_ntlmssp;
837     } else {
838         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
839                      "Unrecognized GSSAPI Mechanism: %s", w);
840         return NULL;
841     }
842
843     if (cfg->allowed_mechs->count >= MAX_ALLOWED_MECHS) {
844         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
845                      "Too many GssapiAllowedMech options (MAX: %d)",
846                      MAX_ALLOWED_MECHS);
847         return NULL;
848     }
849     cfg->allowed_mechs->elements[cfg->allowed_mechs->count] = *oid;
850     cfg->allowed_mechs->count++;
851
852     return NULL;
853 }
854
855 static const command_rec mag_commands[] = {
856     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
857                   "Work only if connection is SSL Secured"),
858     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
859                   "Translate principals to local names"),
860     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
861                   "Authentication is bound to the TCP connection"),
862     AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
863                   "Send Persitent-Auth header according to connection bound"),
864     AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
865                   "Authentication uses mod_sessions to hold status"),
866     AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
867                      "Key Used to seal session data."),
868 #ifdef HAVE_CRED_STORE
869     AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
870                   "Initializes credentials for s4u2proxy usage"),
871     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
872                     "Credential Store"),
873     AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
874                      OR_AUTHCFG, "Directory to store delegated credentials"),
875 #endif
876 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
877     AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
878                      "Allows use of Basic Auth for authentication"),
879 #endif
880     AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
881                     "Allowed Mechanisms"),
882     { NULL }
883 };
884
885 static void
886 mag_register_hooks(apr_pool_t *p)
887 {
888     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
889     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
890     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
891 }
892
893 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
894 {
895     STANDARD20_MODULE_STUFF,
896     mag_create_dir_config,
897     NULL,
898     NULL,
899     NULL,
900     mag_commands,
901     mag_register_hooks
902 };