Initial code
[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
29 #include <httpd.h>
30 #include <http_core.h>
31 #include <http_log.h>
32 #include <http_request.h>
33 #include <apr_strings.h>
34 #include <apr_base64.h>
35
36 module AP_MODULE_DECLARE_DATA mag_module;
37
38 struct mag_config {
39     bool ssl_only;
40     bool save_creds;
41 };
42
43 static char *mag_status(request_rec *req, int type, uint32_t err)
44 {
45     uint32_t maj_ret, min_ret;
46     gss_buffer_desc text;
47     uint32_t msg_ctx;
48     char *msg_ret;
49     int len;
50
51     msg_ret = NULL;
52     msg_ctx = 0;
53     do {
54         maj_ret = gss_display_status(&min_ret, err, type,
55                                      GSS_C_NO_OID, &msg_ctx, &text);
56         if (maj_ret != GSS_S_COMPLETE) {
57             return msg_ret;
58         }
59
60         len = text.length;
61         if (msg_ret) {
62             msg_ret = apr_psprintf(req->pool, "%s, %*s",
63                                    msg_ret, len, (char *)text.value);
64         } else {
65             msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
66         }
67         gss_release_buffer(&min_ret, &text);
68     } while (msg_ctx != 0);
69
70     return msg_ret;
71 }
72
73 static char *mag_error(request_rec *req, const char *msg,
74                        uint32_t maj, uint32_t min)
75 {
76     char *msg_maj;
77     char *msg_min;
78
79     msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
80     msg_min = mag_status(req, GSS_C_MECH_CODE, min);
81     return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
82 }
83
84 static int mag_auth(request_rec *req)
85 {
86     const char *type;
87     struct mag_config *cfg;
88     const char *auth_header;
89     char *auth_header_type;
90     char *auth_header_value;
91     int ret = HTTP_UNAUTHORIZED;
92     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
93     gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
94     gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
95     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
96     gss_name_t client = GSS_C_NO_NAME;
97     gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
98     uint32_t flags;
99     uint32_t maj, min;
100     char *reply;
101     size_t replen;
102
103     type = ap_auth_type(req);
104     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
105         return DECLINED;
106     }
107
108     cfg = ap_get_module_config(req->per_dir_config, &mag_module);
109
110     /* FIXME: Checks for ssl only configuration */
111
112     auth_header = apr_table_get(req->headers_in, "Authorization");
113     if (!auth_header) goto done;
114
115     auth_header_type = ap_getword_white(req->pool, &auth_header);
116     if (!auth_header_type) goto done;
117
118     if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
119
120     auth_header_value = ap_getword_white(req->pool, &auth_header);
121     if (!auth_header_value) goto done;
122     input.length = apr_base64_decode_len(auth_header_value) + 1;
123     input.value = apr_pcalloc(req->pool, input.length);
124     if (!input.value) goto done;
125     input.length = apr_base64_decode(input.value, auth_header_value);
126
127     /* FIXME: this works only with "one-roundtrip" gssapi auth for now,
128      * should work with Krb, will fail with NTLMSSP */
129     maj = gss_accept_sec_context(&min, &ctx, GSS_C_NO_CREDENTIAL,
130                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
131                                  &client, NULL, &output, &flags, NULL,
132                                  &delegated_cred);
133     if (GSS_ERROR(maj)) {
134         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
135                       mag_error(req, "gss_accept_sec_context() failed",
136                                 maj, min));
137         goto done;
138     }
139
140     if (output.length) {
141         replen = apr_base64_encode_len(output.length) + 1;
142         reply = apr_pcalloc(req->pool, 10 + replen);
143         if (!reply) goto done;
144         memcpy(reply, "Negotiate ", 10);
145         apr_base64_encode(&reply[10], output.value, output.length);
146         reply[replen] = '\0';
147         apr_table_add(req->err_headers_out, "WWW-Authenticate", reply);
148     }
149
150     maj = gss_display_name(&min, client, &name, NULL);
151     if (GSS_ERROR(maj)) {
152         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
153                       mag_error(req, "gss_accept_sec_context() failed",
154                                 maj, min));
155         goto done;
156     }
157
158     /* FIXME: save creds */
159
160     req->ap_auth_type = "Negotiate";
161     req->user = apr_pstrndup(req->pool, name.value, name.length);
162     ret = OK;
163
164 done:
165     if (ret == HTTP_UNAUTHORIZED) {
166         apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
167     }
168     gss_release_cred(&min, &delegated_cred);
169     gss_release_buffer(&min, &output);
170     gss_release_name(&min, &client);
171     gss_release_buffer(&min, &name);
172     gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
173     return ret;
174 }
175
176
177 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
178 {
179     struct mag_config *cfg;
180
181     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
182     if (!cfg) return NULL;
183
184     return cfg;
185 }
186
187 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
188 {
189     struct mag_config *cfg = (struct mag_config *)mconfig;
190     cfg->ssl_only = on ? true : false;
191     return NULL;
192 }
193
194 static const char *mag_save_creds(cmd_parms *parms, void *mconfig, int on)
195 {
196     struct mag_config *cfg = (struct mag_config *)mconfig;
197     cfg->save_creds = on ? true : false;
198     return NULL;
199 }
200
201 static const command_rec mag_commands[] = {
202     AP_INIT_FLAG("GSSSSLOnly", mag_ssl_only, NULL, OR_AUTHCFG,
203                   "Work only if connection is SSL Secured"),
204     AP_INIT_FLAG("GSSSaveCreds", mag_save_creds, NULL, OR_AUTHCFG,
205                   "Save credentials"),
206     { NULL }
207 };
208
209 static void
210 mag_register_hooks(apr_pool_t *p)
211 {
212     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
213 }
214
215 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
216 {
217     STANDARD20_MODULE_STUFF,
218     mag_create_dir_config,
219     NULL,
220     NULL,
221     NULL,
222     mag_commands,
223     mag_register_hooks
224 };