Adding testing CLI client (based off the Heimdal testing sample)
[mod_auth_kerb.cvs/.git] / gss.c
1 #include "mod_auth_gssapi.h"
2
3 static const char *
4 get_gss_error(request_rec *r, OM_uint32 err_maj, OM_uint32 err_min, char *prefix)
5 {
6    OM_uint32 maj_stat, min_stat; 
7    OM_uint32 msg_ctx = 0;
8    gss_buffer_desc status_string;
9    char *err_msg;
10    int first_pass;
11
12    gss_log(APLOG_MARK, APLOG_DEBUG, 0, r,
13            "GSS-API major_status:%8.8x, minor_status:%8.8x",
14            err_maj, err_min);
15
16    err_msg = apr_pstrdup(r->pool, prefix);
17    do {
18       maj_stat = gss_display_status (&min_stat,
19                                      err_maj,
20                                      GSS_C_GSS_CODE,
21                                      GSS_C_NO_OID,
22                                      &msg_ctx,
23                                      &status_string);
24       if (!GSS_ERROR(maj_stat)) {
25          err_msg = apr_pstrcat(r->pool, err_msg,
26                                ": ", (char*) status_string.value, NULL);
27          gss_release_buffer(&min_stat, &status_string);
28          first_pass = 0;
29       }
30    } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
31
32    msg_ctx = 0;
33    err_msg = apr_pstrcat(r->pool, err_msg, " (", NULL);
34    first_pass = 1;
35    do {
36       maj_stat = gss_display_status (&min_stat,
37                                      err_min,
38                                      GSS_C_MECH_CODE,
39                                      GSS_C_NULL_OID,
40                                      &msg_ctx,
41                                      &status_string);
42       if (!GSS_ERROR(maj_stat)) {
43          err_msg = apr_pstrcat(r->pool, err_msg,
44                                (first_pass) ? "" : ", ",
45                                (char *) status_string.value,
46                                NULL);
47          gss_release_buffer(&min_stat, &status_string);
48          first_pass = 0;
49       }
50    } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
51    err_msg = apr_pstrcat(r->pool, err_msg, ")", NULL);
52
53    return err_msg;
54 }
55
56 static int
57 get_gss_creds(request_rec *r,
58               gss_auth_config *conf,
59               gss_cred_id_t *server_creds)
60 {
61    gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
62    OM_uint32 major_status, minor_status, minor_status2;
63    gss_name_t server_name = GSS_C_NO_NAME;
64    char buf[1024];
65    int have_server_princ;
66
67    if (conf->service_name && strcmp(conf->service_name, "Any") == 0) {
68        *server_creds = GSS_C_NO_CREDENTIAL;
69        return 0;
70    }
71
72    have_server_princ = conf->service_name && strchr(conf->service_name, '/') != NULL;
73    if (have_server_princ)
74        strncpy(buf, conf->service_name, sizeof(buf));
75    else
76        snprintf(buf, sizeof(buf), "%s@%s",
77                (conf->service_name) ? conf->service_name : SERVICE_NAME,
78                ap_get_server_name(r));
79
80    token.value = buf;
81    token.length = strlen(buf) + 1;
82
83    major_status = gss_import_name(&minor_status, &token,
84                                   (have_server_princ) ? (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME : (gss_OID) GSS_C_NT_HOSTBASED_SERVICE,
85                                   &server_name);
86    memset(&token, 0, sizeof(token));
87    if (GSS_ERROR(major_status)) {
88       gss_log(APLOG_MARK, APLOG_ERR, 0, r,
89               "%s", get_gss_error(r, major_status, minor_status,
90               "gss_import_name() failed"));
91       return HTTP_INTERNAL_SERVER_ERROR;
92    }
93
94    major_status = gss_display_name(&minor_status, server_name, &token, NULL);
95    if (GSS_ERROR(major_status)) {
96       /* Perhaps we could just ignore this error but it's safer to give up now,
97          I think */
98       gss_log(APLOG_MARK, APLOG_ERR, 0, r,
99               "%s", get_gss_error(r, major_status, minor_status,
100                                   "gss_display_name() failed"));
101       return HTTP_INTERNAL_SERVER_ERROR;
102    }
103
104    gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "Acquiring creds for %s", token.value);
105    gss_release_buffer(&minor_status, &token);
106    
107    major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
108                                    GSS_C_NO_OID_SET, GSS_C_ACCEPT,
109                                    server_creds, NULL, NULL);
110    gss_release_name(&minor_status2, &server_name);
111    if (GSS_ERROR(major_status)) {
112       gss_log(APLOG_MARK, APLOG_ERR, 0, r,
113               "%s", get_gss_error(r, major_status, minor_status,
114                                   "Failed to load GSS-API credentials"));
115       return HTTP_INTERNAL_SERVER_ERROR;
116    }
117
118    return 0;
119 }
120
121 static int
122 cmp_gss_type(gss_buffer_t token, gss_OID oid)
123 {
124    unsigned char *p;
125    size_t len;
126
127    if (token->length == 0)
128       return GSS_S_DEFECTIVE_TOKEN;
129
130    p = token->value;
131    if (*p++ != 0x60)
132       return GSS_S_DEFECTIVE_TOKEN;
133    len = *p++;
134    if (len & 0x80) {
135       if ((len & 0x7f) > 4)
136          return GSS_S_DEFECTIVE_TOKEN;
137       p += len & 0x7f;
138    }
139    if (*p++ != 0x06)
140       return GSS_S_DEFECTIVE_TOKEN;
141
142    if (((OM_uint32) *p++) != oid->length)
143       return GSS_S_DEFECTIVE_TOKEN;
144
145    return memcmp(p, oid->elements, oid->length);
146 }
147
148 int
149 gss_authenticate(request_rec *r, gss_auth_config *conf, gss_conn_ctx ctx,
150                  const char *auth_line, char **negotiate_ret_value)
151 {
152   OM_uint32 major_status, minor_status, minor_status2;
153   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
154   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
155   const char *auth_param = NULL;
156   int ret;
157   gss_name_t client_name = GSS_C_NO_NAME;
158   gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
159   gss_cred_id_t server_creds = GSS_C_NO_CREDENTIAL;
160   OM_uint32 ret_flags = 0;
161   gss_OID_desc spnego_oid;
162   OM_uint32 (*accept_sec_context)
163                 (OM_uint32 *, gss_ctx_id_t *, const gss_cred_id_t,
164                  const gss_buffer_t, const gss_channel_bindings_t,
165                  gss_name_t *, gss_OID *, gss_buffer_t, OM_uint32 *,
166                  OM_uint32 *, gss_cred_id_t *);
167
168   *negotiate_ret_value = "\0";
169
170   spnego_oid.length = 6;
171   spnego_oid.elements = (void *)"\x2b\x06\x01\x05\x05\x02";
172
173   if (conf->krb5_keytab) {
174      char *ktname;
175      /* we don't use the ap_* calls here, since the string passed to putenv()
176       * will become part of the enviroment and shouldn't be free()ed by apache
177       */
178      ktname = malloc(strlen("KRB5_KTNAME=") + strlen(conf->krb5_keytab) + 1);
179      if (ktname == NULL) {
180         gss_log(APLOG_MARK, APLOG_ERR, 0, r, "malloc() failed: not enough memory");
181         ret = HTTP_INTERNAL_SERVER_ERROR;
182         goto end;
183      }
184      sprintf(ktname, "KRB5_KTNAME=%s", conf->krb5_keytab);
185      putenv(ktname);
186 #ifdef HEIMDAL
187      /* Seems to be also supported by latest MIT */
188      gsskrb5_register_acceptor_identity(conf->krb_5_keytab);
189 #endif
190   }
191
192   ret = get_gss_creds(r, conf, &server_creds);
193   if (ret)
194      goto end;
195
196   /* ap_getword() shifts parameter */
197   auth_param = ap_getword_white(r->pool, &auth_line);
198   if (auth_param == NULL) {
199      gss_log(APLOG_MARK, APLOG_ERR, 0, r,
200              "No Authorization parameter in request from client");
201      ret = HTTP_UNAUTHORIZED;
202      goto end;
203   }
204
205   if (ctx->state == GSS_CTX_ESTABLISHED) {
206       gss_delete_sec_context(&minor_status, &ctx->context, GSS_C_NO_BUFFER);
207       ctx->context = GSS_C_NO_CONTEXT;
208       ctx->state = GSS_CTX_EMPTY;
209   }
210
211   input_token.length = apr_base64_decode_len(auth_param) + 1;
212   input_token.value = apr_pcalloc(r->connection->pool, input_token.length);
213   if (input_token.value == NULL) {
214      gss_log(APLOG_MARK, APLOG_ERR, 0, r,
215              "ap_pcalloc() failed (not enough memory)");
216      ret = HTTP_INTERNAL_SERVER_ERROR;
217      goto end;
218   }
219   input_token.length = apr_base64_decode(input_token.value, auth_param);
220
221   /* LOG length, type */
222
223 #ifdef GSSAPI_SUPPORTS_SPNEGO
224   accept_sec_context = gss_accept_sec_context;
225 #else
226   accept_sec_context = (cmp_gss_type(&input_token, &spnego_oid) == 0) ?
227                       gss_accept_sec_context_spnego : gss_accept_sec_context;
228 #endif  
229
230   major_status = accept_sec_context(&minor_status,
231                                   &ctx->context,
232                                   server_creds,
233                                   &input_token,
234                                   GSS_C_NO_CHANNEL_BINDINGS,
235                                   NULL,
236                                   NULL,
237                                   &output_token,
238                                   &ret_flags,
239                                   NULL,
240                                   &delegated_cred);
241   gss_log(APLOG_MARK, APLOG_DEBUG, 0, r,
242           "Client %s us their credential",
243           (ret_flags & GSS_C_DELEG_FLAG) ? "delegated" : "didn't delegate");
244   if (output_token.length) {
245      char *token = NULL;
246      size_t len;
247      
248      len = apr_base64_encode_len(output_token.length) + 1;
249      token = apr_pcalloc(r->connection->pool, len + 1);
250      if (token == NULL) {
251         gss_log(APLOG_MARK, APLOG_ERR, 0, r,
252                 "ap_pcalloc() failed (not enough memory)");
253         ret = HTTP_INTERNAL_SERVER_ERROR;
254         gss_release_buffer(&minor_status2, &output_token);
255         goto end;
256      }
257      apr_base64_encode(token, output_token.value, output_token.length);
258      token[len] = '\0';
259      *negotiate_ret_value = token;
260      gss_log(APLOG_MARK, APLOG_DEBUG, 0, r,
261              "GSS-API token of length %d bytes will be sent back",
262              output_token.length);
263      gss_release_buffer(&minor_status2, &output_token);
264   }
265
266   if (GSS_ERROR(major_status)) {
267      gss_log(APLOG_MARK, APLOG_ERR, 0, r,
268              "%s", get_gss_error(r, major_status, minor_status,
269                                  "Failed to establish authentication"));
270      /* Don't offer the Negotiate method again if call to GSS layer failed */
271      /* XXX ... which means we don't return the "error" output */
272      *negotiate_ret_value = NULL;
273      ret = HTTP_UNAUTHORIZED;
274      goto end;
275   }
276
277   if (major_status & GSS_S_CONTINUE_NEEDED) {
278      ctx->state = GSS_CTX_IN_PROGRESS;
279      ret = HTTP_UNAUTHORIZED;
280      goto end;
281   }
282
283   major_status = gss_inquire_context(&minor_status, ctx->context, &client_name,
284                                      NULL, NULL, NULL, NULL, NULL, NULL);
285   if (GSS_ERROR(major_status)) {
286       gss_log(APLOG_MARK, APLOG_ERR, 0, r,
287               "%s", get_gss_error(r, major_status, minor_status, "gss_inquire_context() failed"));
288       ret = HTTP_INTERNAL_SERVER_ERROR;
289       goto end;
290   }
291
292   major_status = gss_display_name(&minor_status, client_name, &output_token, NULL);
293   gss_release_name(&minor_status, &client_name); 
294   if (GSS_ERROR(major_status)) {
295     gss_log(APLOG_MARK, APLOG_ERR, 0, r,
296             "%s", get_gss_error(r, major_status, minor_status,
297                                 "gss_display_name() failed"));
298     ret = HTTP_INTERNAL_SERVER_ERROR;
299     goto end;
300   }
301
302   ctx->state = GSS_CTX_ESTABLISHED;
303   ctx->user = apr_pstrdup(r->pool, output_token.value);
304   gss_release_buffer(&minor_status, &output_token);
305
306   ret = OK;
307
308 end:
309   if (delegated_cred)
310      gss_release_cred(&minor_status, &delegated_cred);
311
312   if (output_token.length) 
313      gss_release_buffer(&minor_status, &output_token);
314
315   if (client_name != GSS_C_NO_NAME)
316      gss_release_name(&minor_status, &client_name);
317
318   if (server_creds != GSS_C_NO_CREDENTIAL)
319      gss_release_cred(&minor_status, &server_creds);
320
321   return ret;
322 }