Fix module name
[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 <stdbool.h>
26 #include <stdint.h>
27 #include <gssapi/gssapi.h>
28 #include <gssapi/gssapi_ext.h>
29
30 #include <httpd.h>
31 #include <http_core.h>
32 #include <http_log.h>
33 #include <http_request.h>
34 #include <apr_strings.h>
35 #include <apr_base64.h>
36
37 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
38
39 struct mag_config {
40     bool ssl_only;
41     bool map_to_local;
42     gss_key_value_set_desc cred_store;
43 };
44
45 static char *mag_status(request_rec *req, int type, uint32_t err)
46 {
47     uint32_t maj_ret, min_ret;
48     gss_buffer_desc text;
49     uint32_t msg_ctx;
50     char *msg_ret;
51     int len;
52
53     msg_ret = NULL;
54     msg_ctx = 0;
55     do {
56         maj_ret = gss_display_status(&min_ret, err, type,
57                                      GSS_C_NO_OID, &msg_ctx, &text);
58         if (maj_ret != GSS_S_COMPLETE) {
59             return msg_ret;
60         }
61
62         len = text.length;
63         if (msg_ret) {
64             msg_ret = apr_psprintf(req->pool, "%s, %*s",
65                                    msg_ret, len, (char *)text.value);
66         } else {
67             msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
68         }
69         gss_release_buffer(&min_ret, &text);
70     } while (msg_ctx != 0);
71
72     return msg_ret;
73 }
74
75 static char *mag_error(request_rec *req, const char *msg,
76                        uint32_t maj, uint32_t min)
77 {
78     char *msg_maj;
79     char *msg_min;
80
81     msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
82     msg_min = mag_status(req, GSS_C_MECH_CODE, min);
83     return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
84 }
85
86 static int mag_auth(request_rec *req)
87 {
88     const char *type;
89     struct mag_config *cfg;
90     const char *auth_header;
91     char *auth_header_type;
92     char *auth_header_value;
93     int ret = HTTP_UNAUTHORIZED;
94     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
95     gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
96     gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
97     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
98     gss_name_t client = GSS_C_NO_NAME;
99     gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
100     uint32_t flags;
101     uint32_t maj, min;
102     char *reply;
103     size_t replen;
104     char *clientname;
105     gss_OID mech_type = GSS_C_NO_OID;
106     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
107
108     type = ap_auth_type(req);
109     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
110         return DECLINED;
111     }
112
113     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
114
115     if (cfg->ssl_only) {
116         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
117                       "FIXME: check for ssl!");
118     }
119
120     auth_header = apr_table_get(req->headers_in, "Authorization");
121     if (!auth_header) goto done;
122
123     auth_header_type = ap_getword_white(req->pool, &auth_header);
124     if (!auth_header_type) goto done;
125
126     if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
127
128     auth_header_value = ap_getword_white(req->pool, &auth_header);
129     if (!auth_header_value) goto done;
130     input.length = apr_base64_decode_len(auth_header_value) + 1;
131     input.value = apr_pcalloc(req->pool, input.length);
132     if (!input.value) goto done;
133     input.length = apr_base64_decode(input.value, auth_header_value);
134
135     /* FIXME: this works only with "one-roundtrip" gssapi auth for now,
136      * should work with Krb, will fail with NTLMSSP */
137     maj = gss_accept_sec_context(&min, &ctx, GSS_C_NO_CREDENTIAL,
138                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
139                                  &client, &mech_type, &output, &flags, NULL,
140                                  &delegated_cred);
141     if (GSS_ERROR(maj)) {
142         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
143                       mag_error(req, "gss_accept_sec_context() failed",
144                                 maj, min));
145         goto done;
146     }
147
148     if (output.length) {
149         replen = apr_base64_encode_len(output.length) + 1;
150         reply = apr_pcalloc(req->pool, 10 + replen);
151         if (!reply) goto done;
152         memcpy(reply, "Negotiate ", 10);
153         apr_base64_encode(&reply[10], output.value, output.length);
154         reply[replen] = '\0';
155         apr_table_add(req->err_headers_out, "WWW-Authenticate", reply);
156     }
157
158     maj = gss_display_name(&min, client, &name, NULL);
159     if (GSS_ERROR(maj)) {
160         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
161                       mag_error(req, "gss_accept_sec_context() failed",
162                                 maj, min));
163         goto done;
164     }
165
166 #ifdef HAVE_GSS_STORE_CRED_INTO
167     if (cfg->cred_store && delegated_cred != GSS_C_NO_CREDENTIAL) {
168         gss_key_value_set_desc store = {0, NULL};
169         /* FIXME: run substtutions */
170
171         maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
172                                   GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
173     }
174 #endif
175
176     req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
177
178     /* Always set the GSS name in an env var */
179     clientname = apr_pstrndup(req->pool, name.value, name.length);
180     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
181
182     if (cfg->map_to_local) {
183         maj = gss_localname(&min, client, mech_type, &lname);
184         if (maj != GSS_S_COMPLETE) {
185             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
186                           mag_error(req, "gss_localname() failed", maj, min));
187             goto done;
188         }
189         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
190     } else {
191         req->user = clientname;
192     }
193     ret = OK;
194
195 done:
196     if (ret == HTTP_UNAUTHORIZED) {
197         apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
198     }
199     gss_release_cred(&min, &delegated_cred);
200     gss_release_buffer(&min, &output);
201     gss_release_name(&min, &client);
202     gss_release_buffer(&min, &name);
203     gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
204     gss_release_buffer(&min, &lname);
205     return ret;
206 }
207
208
209 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
210 {
211     struct mag_config *cfg;
212
213     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
214     if (!cfg) return NULL;
215
216     return cfg;
217 }
218
219 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
220 {
221     struct mag_config *cfg = (struct mag_config *)mconfig;
222     cfg->ssl_only = on ? true : false;
223     return NULL;
224 }
225
226 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
227 {
228     struct mag_config *cfg = (struct mag_config *)mconfig;
229     cfg->map_to_local = on ? true : false;
230     return NULL;
231 }
232
233 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
234                                   const char *w)
235 {
236     struct mag_config *cfg = (struct mag_config *)mconfig;
237     gss_key_value_element_desc *elements;
238     uint32_t count;
239     size_t size;
240     const char *p;
241     char *value;
242     char *key;
243
244     p = strchr(w, ':');
245     if (!p) {
246         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
247                      "%s [%s]", "Invalid syntax for GSSCredStore option", w);
248         return NULL;
249     }
250
251     key = apr_pstrndup(parms->pool, w, (p-w));
252     value = apr_pstrdup(parms->pool, p + 1);
253     if (!key || !value) {
254         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
255                      "%s", "OOM handling GSSCredStore option");
256         return NULL;
257     }
258
259     size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
260     elements = apr_palloc(parms->pool, size);
261     if (!elements) {
262         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
263                      "%s", "OOM handling GSSCredStore option");
264         return NULL;
265     }
266
267     for (count = 0; count < cfg->cred_store.count; count++) {
268         elements[count] = cfg->cred_store.elements[count];
269     }
270     elements[count].key = key;
271     elements[count].value = value;
272
273     cfg->cred_store.elements = elements;
274     cfg->cred_store.count = count;
275
276     return NULL;
277 }
278
279 static const command_rec mag_commands[] = {
280     AP_INIT_FLAG("GSSSSLOnly", mag_ssl_only, NULL, OR_AUTHCFG,
281                   "Work only if connection is SSL Secured"),
282     AP_INIT_FLAG("GSSLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
283                   "Work only if connection is SSL Secured"),
284     AP_INIT_ITERATE("GSSCredStore", mag_cred_store, NULL, OR_AUTHCFG,
285                     "Credential Store"),
286     { NULL }
287 };
288
289 static void
290 mag_register_hooks(apr_pool_t *p)
291 {
292     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
293 }
294
295 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
296 {
297     STANDARD20_MODULE_STUFF,
298     mag_create_dir_config,
299     NULL,
300     NULL,
301     NULL,
302     mag_commands,
303     mag_register_hooks
304 };