2 * HTTP Negotiate authentication method -- based on GSSAPI
4 * The Microsoft version with SPNEGO is unsupported. If you look for way how
5 * extend this code with SPNEGO see libcurl or firefox source code where is
6 * supported GSSAPI+SPNEGO.
8 * Copyright (C) 2006 Red Hat, Inc.
9 * Karel Zak <kzak@redhat.com>
23 #include <gssapi/gssapi.h>
26 #include "network/connection.h"
27 #include "protocol/uri.h"
28 #include "protocol/http/http.h"
29 #include "protocol/http/http_negotiate.h"
30 #include "util/base64.h"
31 #include "main/object.h"
32 #include "util/lists.h"
35 OBJECT_HEAD(struct negotiate
);
39 int type
; /* GSS-Negotiate or Negotiate or zero */
42 gss_name_t server_name
;
43 gss_buffer_desc output_token
;
44 gss_buffer_desc input_token
;
47 static INIT_LIST_OF(struct negotiate
, negotiate_list
);
49 static struct negotiate
*
50 http_negotiate_get(struct uri
*uri
, int *isnew
, int alloc
)
52 struct negotiate
*neg
;
54 foreach (neg
, negotiate_list
) {
55 if (compare_uri(neg
->uri
, uri
, URI_HTTP_REFERRER_HOST
))
61 neg
= mem_calloc(1, sizeof(*neg
));
65 neg
->uri
= get_uri_reference(uri
);
74 http_negotiate_save(struct negotiate
*neg
)
76 add_to_list(negotiate_list
, neg
);
80 http_negotiate_cleanup(struct negotiate
*neg
, int full
)
82 OM_uint32 minor_status
;
84 if (neg
->context
!= GSS_C_NO_CONTEXT
)
85 gss_delete_sec_context(&minor_status
, &neg
->context
, GSS_C_NO_BUFFER
);
87 if (neg
->output_token
.length
!= 0)
88 gss_release_buffer(&minor_status
, &neg
->output_token
);
92 gss_release_name(&minor_status
, &neg
->server_name
);
94 if (neg
->input_token
.length
!= 0) {
95 /* allocated by mem_free().. so beter not use gss_release_buffer() */
96 mem_free(neg
->input_token
.value
);
97 neg
->input_token
.length
= 0;
100 memset(neg
, 0, sizeof(*neg
));
105 http_negotiate_get_name(struct connection
*conn
, struct negotiate
*neg
)
107 OM_uint32 major_status
, minor_status
;
108 gss_buffer_desc token
= GSS_C_EMPTY_BUFFER
;
111 struct uri
*uri
= conn
->proxied_uri
;
113 /* GSSAPI implementation by Globus (known as GSI) requires the name to be
114 * of form "<service>/<fqdn>" instead of <service>@<fqdn> (ie. slash instead
115 * of at-sign). Also GSI servers are often identified as 'host' not 'khttp'.
116 * Change following lines if you want to use GSI
118 * IIS uses the <service>@<fqdn> form but uses 'http' as the service name
120 if (neg
->type
== HTTPNEG_GSS
)
125 token
.length
= strlen(service
) + 1 + uri
->hostlen
+ 1;
126 if (token
.length
+ 1 > sizeof(name
))
129 snprintf(name
, token
.length
, "%s@%*s", service
, uri
->hostlen
, uri
->host
);
131 token
.value
= (void *) name
;
132 major_status
= gss_import_name(&minor_status
, &token
,
133 GSS_C_NT_HOSTBASED_SERVICE
,
136 return GSS_ERROR(major_status
) ? -1 : 0;
140 http_negotiate_parse_data(unsigned char *data
, int type
,
141 gss_buffer_desc
*token
)
146 if (data
== NULL
|| *data
== '\0')
149 if (type
== HTTPNEG_GSS
)
150 data
+= HTTPNEG_GSS_STRLEN
;
152 data
+= HTTPNEG_NEG_STRLEN
;
154 while (*data
&& isspace((int) *data
))
157 if (*data
== '\0' || *data
== ASCII_CR
|| *data
== ASCII_LF
)
158 return 0; /* no data */
161 while (isalnum((int) *end
) || *end
== '=')
164 /* Ignore line if we encountered an unexpected char. */
165 if (*end
!= ASCII_CR
&& *end
!= ASCII_LF
)
173 token
->value
= (void *) base64_decode_bin(data
, len
, &token
->length
);
182 http_negotiate_create_context(struct negotiate
*neg
)
184 OM_uint32 major_status
, minor_status
;
186 major_status
= gss_init_sec_context(&minor_status
,
193 GSS_C_NO_CHANNEL_BINDINGS
,
199 neg
->status
= major_status
;
201 if (GSS_ERROR(major_status
) || neg
->output_token
.length
== 0)
208 * Register new negotiate-auth request
210 * It's possible that server sends to client input token (at least
211 * libcurl supports it) in WWW-Authenticate header, but ususaly
212 * is this input token undefined.
215 http_negotiate_input(struct connection
*conn
, struct uri
*uri
,
216 int type
, unsigned char *data
)
218 struct negotiate
*neg
;
219 int ret
= 0, isnew
= 0;
221 neg
= http_negotiate_get(uri
, &isnew
, 1);
223 if (neg
->context
&& type
!= HTTPNEG_GSS
)
228 if (neg
->context
&& neg
->status
== GSS_S_COMPLETE
) {
229 /* We finished succesfully our part of authentication, but
230 * server rejected it (since we're again here). Exit with an
231 * error since we can't invent anything better
233 http_negotiate_cleanup(neg
, 1);
237 if (neg
->server_name
== NULL
&& http_negotiate_get_name(conn
, neg
) < 0)
240 if (data
&& http_negotiate_parse_data(data
, type
, &neg
->input_token
))
243 ret
= http_negotiate_create_context(neg
);
244 if (ret
== 0 && isnew
)
245 http_negotiate_save(neg
);
251 * Fill output token to "Authorization: Negotiate <token>".
254 http_negotiate_output(struct uri
*uri
, struct string
*header
)
256 struct negotiate
*neg
;
257 char *encoded
= NULL
;
260 neg
= http_negotiate_get(uri
, NULL
, 0);
264 if (neg
->output_token
.length
== 0) {
265 if (http_negotiate_create_context(neg
) < 0) {
266 /* full cleanup on error and ask for
267 * new WWW-Authenticate from server
269 http_negotiate_cleanup(neg
, 1);
274 encoded
= base64_encode_bin((unsigned char *) neg
->output_token
.value
,
275 neg
->output_token
.length
, &len
);
277 if (encoded
== NULL
|| len
== 0)
280 add_to_string(header
, "Authorization: ");
281 add_to_string(header
, neg
->type
== HTTPNEG_GSS
?
282 HTTPNEG_GSS_STR
: HTTPNEG_NEG_STR
);
283 add_char_to_string(header
, ' ');
284 add_to_string(header
, encoded
);
285 add_crlf_to_string(header
);
287 http_negotiate_cleanup(neg
, 0);