Experimental brotli encoding support.
[elinks.git] / src / protocol / http / http_negotiate.c
blobaa0f755917a96d5f5dfc3581379da74a92164901
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;
145 int bytelen = 0;
147 if (data == NULL || *data == '\0')
148 return 0;
150 if (type == HTTPNEG_GSS)
151 data += HTTPNEG_GSS_STRLEN;
152 else
153 data += HTTPNEG_NEG_STRLEN;
155 while (*data && isspace((int) *data))
156 data++;
158 if (*data == '\0' || *data == ASCII_CR || *data == ASCII_LF)
159 return 0; /* no data */
161 end = data;
162 while (isalnum((int) *end) || *end == '=')
163 end++;
165 /* Ignore line if we encountered an unexpected char. */
166 if (*end != ASCII_CR && *end != ASCII_LF)
167 return 0;
169 len = end - data;
171 if (!len)
172 return 0;
174 token->value = (void *) base64_decode_bin(data, len, &bytelen);
175 token->length = bytelen; /* convert int to size_t */
177 if (!token->value)
178 return -1;
180 return 0;
183 static int
184 http_negotiate_create_context(struct negotiate *neg)
186 OM_uint32 major_status, minor_status;
188 major_status = gss_init_sec_context(&minor_status,
189 GSS_C_NO_CREDENTIAL,
190 &neg->context,
191 neg->server_name,
192 GSS_C_NO_OID,
195 GSS_C_NO_CHANNEL_BINDINGS,
196 &neg->input_token,
197 NULL,
198 &neg->output_token,
199 NULL,
200 NULL);
201 neg->status = major_status;
203 if (GSS_ERROR(major_status) || neg->output_token.length == 0)
204 return -1;
206 return 0;
210 * Register new negotiate-auth request
212 * It's possible that server sends to client input token (at least
213 * libcurl supports it) in WWW-Authenticate header, but ususaly
214 * is this input token undefined.
217 http_negotiate_input(struct connection *conn, struct uri *uri,
218 int type, unsigned char *data)
220 struct negotiate *neg;
221 int ret = 0, isnew = 0;
223 neg = http_negotiate_get(uri, &isnew, 1);
225 if (neg->context && type != HTTPNEG_GSS)
226 return -1;
228 neg->type = type;
230 if (neg->context && neg->status == GSS_S_COMPLETE) {
231 /* We finished succesfully our part of authentication, but
232 * server rejected it (since we're again here). Exit with an
233 * error since we can't invent anything better
235 http_negotiate_cleanup(neg, 1);
236 return -1;
239 if (neg->server_name == NULL && http_negotiate_get_name(conn, neg) < 0)
240 return -1;
242 if (data && http_negotiate_parse_data(data, type, &neg->input_token))
243 return -1;
245 ret = http_negotiate_create_context(neg);
246 if (ret == 0 && isnew)
247 http_negotiate_save(neg);
249 return ret;
253 * Fill output token to "Authorization: Negotiate <token>".
256 http_negotiate_output(struct uri *uri, struct string *header)
258 struct negotiate *neg;
259 char *encoded = NULL;
260 int len = 0;
262 neg = http_negotiate_get(uri, NULL, 0);
263 if (!neg)
264 return -1;
266 if (neg->output_token.length == 0) {
267 if (http_negotiate_create_context(neg) < 0) {
268 /* full cleanup on error and ask for
269 * new WWW-Authenticate from server
271 http_negotiate_cleanup(neg, 1);
272 return -1;
276 encoded = base64_encode_bin((unsigned char *) neg->output_token.value,
277 neg->output_token.length, &len);
279 if (encoded == NULL || len == 0)
280 return -1;
282 add_to_string(header, "Authorization: ");
283 add_to_string(header, neg->type == HTTPNEG_GSS ?
284 HTTPNEG_GSS_STR : HTTPNEG_NEG_STR);
285 add_char_to_string(header, ' ');
286 add_to_string(header, encoded);
287 add_crlf_to_string(header);
289 http_negotiate_cleanup(neg, 0);
291 mem_free(encoded);
293 return 0;