http_negotiate: do not delegate GSSAPI credentials
[elinks.git] / src / protocol / http / http_negotiate.c
blob271b443583b54e5928ec85f872e9e268250b208d
1 /*
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>
12 #ifdef HAVE_CONFIG_H
13 #include "config.h"
14 #endif
16 #include <stdio.h>
17 #include <string.h>
18 #include <stdarg.h>
19 #include <stdlib.h>
20 #include <ctype.h>
21 #include <errno.h>
23 #include <gssapi/gssapi.h>
25 #include "elinks.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"
34 struct negotiate {
35 OBJECT_HEAD(struct negotiate);
37 struct uri *uri;
39 int type; /* GSS-Negotiate or Negotiate or zero */
40 OM_uint32 status;
41 gss_ctx_id_t context;
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))
56 return neg;
58 if (!alloc)
59 return NULL;
61 neg = mem_calloc(1, sizeof(*neg));
62 if (!neg)
63 return NULL;
65 neg->uri = get_uri_reference(uri);
67 if (isnew)
68 *isnew = 1;
70 return neg;
73 static void
74 http_negotiate_save(struct negotiate *neg)
76 add_to_list(negotiate_list, neg);
79 static void
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);
90 if (full) {
91 if (neg->server_name)
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));
104 static int
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;
109 char name[2048];
110 const char *service;
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)
121 service = "KHTTP";
122 else
123 service = "HTTP";
125 token.length = strlen(service) + 1 + uri->hostlen + 1;
126 if (token.length + 1 > sizeof(name))
127 return -1;
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,
134 &neg->server_name);
136 return GSS_ERROR(major_status) ? -1 : 0;
139 static int
140 http_negotiate_parse_data(unsigned char *data, int type,
141 gss_buffer_desc *token)
143 int len = 0;
144 unsigned char *end;
146 if (data == NULL || *data == '\0')
147 return 0;
149 if (type == HTTPNEG_GSS)
150 data += HTTPNEG_GSS_STRLEN;
151 else
152 data += HTTPNEG_NEG_STRLEN;
154 while (*data && isspace((int) *data))
155 data++;
157 if (*data == '\0' || *data == ASCII_CR || *data == ASCII_LF)
158 return 0; /* no data */
160 end = data;
161 while (isalnum((int) *end) || *end == '=')
162 end++;
164 /* Ignore line if we encountered an unexpected char. */
165 if (*end != ASCII_CR && *end != ASCII_LF)
166 return 0;
168 len = end - data;
170 if (!len)
171 return 0;
173 token->value = (void *) base64_decode_bin(data, len, &token->length);
175 if (!token->value)
176 return -1;
178 return 0;
181 static int
182 http_negotiate_create_context(struct negotiate *neg)
184 OM_uint32 major_status, minor_status;
186 major_status = gss_init_sec_context(&minor_status,
187 GSS_C_NO_CREDENTIAL,
188 &neg->context,
189 neg->server_name,
190 GSS_C_NO_OID,
193 GSS_C_NO_CHANNEL_BINDINGS,
194 &neg->input_token,
195 NULL,
196 &neg->output_token,
197 NULL,
198 NULL);
199 neg->status = major_status;
201 if (GSS_ERROR(major_status) || neg->output_token.length == 0)
202 return -1;
204 return 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)
224 return -1;
226 neg->type = type;
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);
234 return -1;
237 if (neg->server_name == NULL && http_negotiate_get_name(conn, neg) < 0)
238 return -1;
240 if (data && http_negotiate_parse_data(data, type, &neg->input_token))
241 return -1;
243 ret = http_negotiate_create_context(neg);
244 if (ret == 0 && isnew)
245 http_negotiate_save(neg);
247 return ret;
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;
258 int len = 0;
260 neg = http_negotiate_get(uri, NULL, 0);
261 if (!neg)
262 return -1;
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);
270 return -1;
274 encoded = base64_encode_bin((unsigned char *) neg->output_token.value,
275 neg->output_token.length, &len);
277 if (encoded == NULL || len == 0)
278 return -1;
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);
289 mem_free(encoded);
291 return 0;