Add mod_auth_gssapi.h
[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 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
28
29 APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
30
31 static char *mag_status(request_rec *req, int type, uint32_t err)
32 {
33     uint32_t maj_ret, min_ret;
34     gss_buffer_desc text;
35     uint32_t msg_ctx;
36     char *msg_ret;
37     int len;
38
39     msg_ret = NULL;
40     msg_ctx = 0;
41     do {
42         maj_ret = gss_display_status(&min_ret, err, type,
43                                      GSS_C_NO_OID, &msg_ctx, &text);
44         if (maj_ret != GSS_S_COMPLETE) {
45             return msg_ret;
46         }
47
48         len = text.length;
49         if (msg_ret) {
50             msg_ret = apr_psprintf(req->pool, "%s, %*s",
51                                    msg_ret, len, (char *)text.value);
52         } else {
53             msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
54         }
55         gss_release_buffer(&min_ret, &text);
56     } while (msg_ctx != 0);
57
58     return msg_ret;
59 }
60
61 static char *mag_error(request_rec *req, const char *msg,
62                        uint32_t maj, uint32_t min)
63 {
64     char *msg_maj;
65     char *msg_min;
66
67     msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
68     msg_min = mag_status(req, GSS_C_MECH_CODE, min);
69     return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
70 }
71
72 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
73
74 static int mag_post_config(apr_pool_t *cfg, apr_pool_t *log,
75                            apr_pool_t *temp, server_rec *s)
76 {
77     /* FIXME: create mutex to deal with connections and contexts ? */
78     mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
79
80     return OK;
81 }
82
83
84 struct mag_conn {
85     apr_pool_t *parent;
86     gss_ctx_id_t ctx;
87     bool established;
88     char *user_name;
89     char *gss_name;
90 };
91
92 static int mag_pre_connection(conn_rec *c, void *csd)
93 {
94     struct mag_conn *mc;
95
96     mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
97     if (!mc) return DECLINED;
98
99     mc->parent = c->pool;
100     ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
101     return OK;
102 }
103
104 static apr_status_t mag_conn_destroy(void *ptr)
105 {
106     struct mag_conn *mc = (struct mag_conn *)ptr;
107     uint32_t min;
108
109     if (mc->ctx) {
110         (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
111         mc->established = false;
112     }
113     return APR_SUCCESS;
114 }
115
116 static bool mag_conn_is_https(conn_rec *c)
117 {
118     if (mag_is_https) {
119         if (mag_is_https(c)) return true;
120     }
121
122     return false;
123 }
124
125 static int mag_auth(request_rec *req)
126 {
127     const char *type;
128     struct mag_config *cfg;
129     const char *auth_header;
130     char *auth_header_type;
131     char *auth_header_value;
132     int ret = HTTP_UNAUTHORIZED;
133     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
134     gss_ctx_id_t *pctx;
135     gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
136     gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
137     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
138     gss_name_t client = GSS_C_NO_NAME;
139     gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
140     uint32_t flags;
141     uint32_t maj, min;
142     char *reply;
143     size_t replen;
144     char *clientname;
145     gss_OID mech_type = GSS_C_NO_OID;
146     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
147     struct mag_conn *mc = NULL;
148
149     type = ap_auth_type(req);
150     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
151         return DECLINED;
152     }
153
154     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
155
156     if (cfg->ssl_only) {
157         if (!mag_conn_is_https(req->connection)) {
158             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
159                           "Not a TLS connection, refusing to authenticate!");
160             goto done;
161         }
162     }
163
164     if (cfg->gss_conn_ctx) {
165         mc = (struct mag_conn *)ap_get_module_config(
166                                                 req->connection->conn_config,
167                                                 &auth_gssapi_module);
168         if (!mc) {
169             return DECLINED;
170         }
171         if (mc->established) {
172             ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
173                           "Connection bound pre-authentication found.");
174             apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
175             req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
176             req->user = apr_pstrdup(req->pool, mc->user_name);
177             ret = OK;
178             goto done;
179         }
180         pctx = &mc->ctx;
181     } else {
182         pctx = &ctx;
183     }
184
185     auth_header = apr_table_get(req->headers_in, "Authorization");
186     if (!auth_header) goto done;
187
188     auth_header_type = ap_getword_white(req->pool, &auth_header);
189     if (!auth_header_type) goto done;
190
191     if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
192
193     auth_header_value = ap_getword_white(req->pool, &auth_header);
194     if (!auth_header_value) goto done;
195     input.length = apr_base64_decode_len(auth_header_value) + 1;
196     input.value = apr_pcalloc(req->pool, input.length);
197     if (!input.value) goto done;
198     input.length = apr_base64_decode(input.value, auth_header_value);
199
200     maj = gss_accept_sec_context(&min, pctx, GSS_C_NO_CREDENTIAL,
201                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
202                                  &client, &mech_type, &output, &flags, NULL,
203                                  &delegated_cred);
204     if (GSS_ERROR(maj)) {
205         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
206                       mag_error(req, "gss_accept_sec_context() failed",
207                                 maj, min));
208         goto done;
209     }
210
211     /* register the context in the connection pool, so it can be freed
212      * when the connection is terminated */
213     apr_pool_userdata_set(mc, "mag_conn_ptr", mag_conn_destroy, mc->parent);
214
215     if (maj == GSS_S_CONTINUE_NEEDED) {
216         if (!cfg->gss_conn_ctx) {
217             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
218                           "Mechanism needs continuation but "
219                           "GssapiConnectionBound is off.");
220             gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
221             gss_release_buffer(&min, &output);
222             output.length = 0;
223         }
224         goto done;
225     }
226
227 #ifdef HAVE_GSS_STORE_CRED_INTO
228     if (cfg->cred_store.count != 0 && delegated_cred != GSS_C_NO_CREDENTIAL) {
229         gss_key_value_set_desc store = {0, NULL};
230         /* FIXME: run substitutions */
231
232         maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
233                                   GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
234     }
235 #endif
236
237     req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
238
239     /* Always set the GSS name in an env var */
240     maj = gss_display_name(&min, client, &name, NULL);
241     if (GSS_ERROR(maj)) {
242         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
243                       mag_error(req, "gss_accept_sec_context() failed",
244                                 maj, min));
245         goto done;
246     }
247     clientname = apr_pstrndup(req->pool, name.value, name.length);
248     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
249
250     if (cfg->map_to_local) {
251         maj = gss_localname(&min, client, mech_type, &lname);
252         if (maj != GSS_S_COMPLETE) {
253             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
254                           mag_error(req, "gss_localname() failed", maj, min));
255             goto done;
256         }
257         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
258     } else {
259         req->user = clientname;
260     }
261
262     if (mc) {
263         mc->user_name = apr_pstrdup(mc->parent, req->user);
264         mc->gss_name = apr_pstrdup(mc->parent, clientname);
265         mc->established = true;
266     }
267
268     ret = OK;
269
270 done:
271     if (ret == HTTP_UNAUTHORIZED) {
272         if (output.length != 0) {
273             replen = apr_base64_encode_len(output.length) + 1;
274             reply = apr_pcalloc(req->pool, 10 + replen);
275             if (reply) {
276                 memcpy(reply, "Negotiate ", 10);
277                 apr_base64_encode(&reply[10], output.value, output.length);
278                 apr_table_add(req->err_headers_out,
279                               "WWW-Authenticate", reply);
280             }
281         } else {
282             apr_table_add(req->err_headers_out,
283                           "WWW-Authenticate", "Negotiate");
284         }
285     }
286     gss_release_cred(&min, &delegated_cred);
287     gss_release_buffer(&min, &output);
288     gss_release_name(&min, &client);
289     gss_release_buffer(&min, &name);
290     gss_release_buffer(&min, &lname);
291     return ret;
292 }
293
294
295 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
296 {
297     struct mag_config *cfg;
298
299     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
300     if (!cfg) return NULL;
301
302     return cfg;
303 }
304
305 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
306 {
307     struct mag_config *cfg = (struct mag_config *)mconfig;
308     cfg->ssl_only = on ? true : false;
309     return NULL;
310 }
311
312 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
313 {
314     struct mag_config *cfg = (struct mag_config *)mconfig;
315     cfg->map_to_local = on ? true : false;
316     return NULL;
317 }
318
319 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
320 {
321     struct mag_config *cfg = (struct mag_config *)mconfig;
322     cfg->gss_conn_ctx = on ? true : false;
323     return NULL;
324 }
325
326 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
327                                   const char *w)
328 {
329     struct mag_config *cfg = (struct mag_config *)mconfig;
330     gss_key_value_element_desc *elements;
331     uint32_t count;
332     size_t size;
333     const char *p;
334     char *value;
335     char *key;
336
337     p = strchr(w, ':');
338     if (!p) {
339         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
340                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
341         return NULL;
342     }
343
344     key = apr_pstrndup(parms->pool, w, (p-w));
345     value = apr_pstrdup(parms->pool, p + 1);
346     if (!key || !value) {
347         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
348                      "%s", "OOM handling GssapiCredStore option");
349         return NULL;
350     }
351
352     size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
353     elements = apr_palloc(parms->pool, size);
354     if (!elements) {
355         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
356                      "%s", "OOM handling GssapiCredStore option");
357         return NULL;
358     }
359
360     for (count = 0; count < cfg->cred_store.count; count++) {
361         elements[count] = cfg->cred_store.elements[count];
362     }
363     elements[count].key = key;
364     elements[count].value = value;
365
366     cfg->cred_store.elements = elements;
367     cfg->cred_store.count = count;
368
369     return NULL;
370 }
371
372 static const command_rec mag_commands[] = {
373     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
374                   "Work only if connection is SSL Secured"),
375     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
376                   "Work only if connection is SSL Secured"),
377     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
378                   "Authentication is bound to the TCP connection"),
379     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
380                     "Credential Store"),
381     { NULL }
382 };
383
384 static void
385 mag_register_hooks(apr_pool_t *p)
386 {
387     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
388     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
389     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
390 }
391
392 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
393 {
394     STANDARD20_MODULE_STUFF,
395     mag_create_dir_config,
396     NULL,
397     NULL,
398     NULL,
399     mag_commands,
400     mag_register_hooks
401 };