1/*
2 * Copyright (c) 2003 - 2005 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include "test_locl.h"
35#include <gssapi.h>
36#include <gssapi_krb5.h>
37#include <gssapi_spnego.h>
38#include <gssapi_ntlm.h>
39#include "gss_common.h"
40#include <base64.h>
41
42static void
43storage_printf(krb5_storage *sp, const char *fmt, ...)
44    __attribute__((format (printf, 2, 3)));
45
46/*
47 * A simplistic client implementing draft-brezak-spnego-http-04.txt
48 */
49
50static int
51do_connect (const char *hostname, const char *port)
52{
53    struct addrinfo *ai, *a;
54    struct addrinfo hints;
55    int error;
56    int s = -1;
57
58    memset (&hints, 0, sizeof(hints));
59    hints.ai_family = PF_UNSPEC;
60    hints.ai_socktype = SOCK_STREAM;
61    hints.ai_protocol = 0;
62
63    error = getaddrinfo (hostname, port, &hints, &ai);
64    if (error)
65	errx (1, "getaddrinfo(%s): %s", hostname, gai_strerror(error));
66
67    for (a = ai; a != NULL; a = a->ai_next) {
68	s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
69	if (s < 0)
70	    continue;
71	socket_set_nopipe(s, 1);
72	if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
73	    warn ("connect(%s)", hostname);
74 	    close (s);
75 	    continue;
76	}
77	break;
78    }
79    freeaddrinfo (ai);
80    if (a == NULL)
81	errx (1, "failed to contact %s", hostname);
82
83    return s;
84}
85
86static void
87storage_printf(krb5_storage *sp, const char *fmt, ...)
88{
89    size_t len;
90    ssize_t ret;
91    va_list ap;
92    char *str;
93
94    va_start(ap, fmt);
95    vasprintf(&str, fmt, ap);
96    va_end(ap);
97
98    if (str == NULL)
99	errx(1, "vasprintf");
100
101    len = strlen(str);
102
103    ret = krb5_storage_write(sp, str, len);
104    if (ret < 0 || (size_t)ret != len)
105	errx(1, "failed to write to server");
106
107    free(str);
108}
109
110static int help_flag;
111static int version_flag;
112static int verbose_flag;
113static int mutual_flag = 1;
114static int delegate_flag;
115static int policy_flag;
116static char *port_str = "http";
117static char *gss_service = "HTTP";
118static char *client_str = NULL;
119static char *cred_mech_str = NULL;
120
121static struct getargs http_args[] = {
122    { "verbose", 'v', arg_flag, &verbose_flag, "verbose logging", },
123    { "port", 'p', arg_string, &port_str, "port to connect to", "port" },
124    { "delegate", 0, arg_flag, &delegate_flag, "gssapi delegate credential" },
125    { "policy", 0, arg_flag, &policy_flag, "gssapi delegate policy credential" },
126    { "gss-service", 's', arg_string, &gss_service, "gssapi service to use",
127      "service" },
128    { "mech", 'm', arg_string, &mech, "gssapi mech to use", "mech" },
129    { "cred-mech", 'c', arg_string, &cred_mech_str, "gssapi mech to use for the cred", "mech" },
130    { "mutual", 0, arg_negative_flag, &mutual_flag, "no gssapi mutual auth" },
131    { "client", 0, arg_string, &client_str, "client_name" },
132    { "help", 'h', arg_flag, &help_flag },
133    { "version", 0, arg_flag, &version_flag }
134};
135
136static int num_http_args = sizeof(http_args) / sizeof(http_args[0]);
137
138static void
139usage(int code)
140{
141    arg_printusage(http_args, num_http_args, NULL, "host [page]");
142    exit(code);
143}
144
145/*
146 *
147 */
148
149struct http_req {
150    char *response;
151    char **headers;
152    unsigned num_headers;
153    void *body;
154    size_t body_size;
155};
156
157
158static void
159http_req_zero(struct http_req *req)
160{
161    req->response = NULL;
162    req->headers = NULL;
163    req->num_headers = 0;
164    req->body = NULL;
165    req->body_size = 0;
166}
167
168static void
169http_req_free(struct http_req *req)
170{
171    unsigned i;
172
173    free(req->response);
174    for (i = 0; i < req->num_headers; i++)
175	free(req->headers[i]);
176    free(req->headers);
177    free(req->body);
178    http_req_zero(req);
179}
180
181static const char *
182http_find_header(struct http_req *req, const char *header)
183{
184    size_t len = strlen(header);
185    unsigned i;
186
187    for (i = 0; i < req->num_headers; i++) {
188	if (strncasecmp(header, req->headers[i], len) == 0) {
189	    return req->headers[i] + len + 1;
190	}
191    }
192    return NULL;
193}
194
195
196static int
197http_query(krb5_storage *sp,
198	   const char *host, const char *page,
199	   char **headers, unsigned num_headers, struct http_req *req)
200{
201    enum { RESPONSE, HEADER, BODY } state;
202    ssize_t ret;
203    char in_buf[1024];
204    size_t in_len = 0, content_length;
205    unsigned i;
206
207    http_req_zero(req);
208
209    if (verbose_flag) {
210	for (i = 0; i < num_headers; i++)
211	    printf("outheader[%d]: %s\n", i, headers[0]);
212    }
213
214    storage_printf(sp, "GET %s HTTP/1.1\r\n", page);
215    for (i = 0; i < num_headers; i++)
216	storage_printf(sp, "%s\r\n", headers[i]);
217    storage_printf(sp, "Host: %s\r\n\r\n", host);
218
219    state = RESPONSE;
220
221    while (1) {
222	char *p;
223
224	ret = krb5_storage_read(sp, in_buf + in_len, 1);
225	if (ret != 1)
226	    errx(1, "storage foo");
227
228	in_len += 1;
229
230	in_buf[in_len] = '\0';
231
232	p = strstr(in_buf, "\r\n");
233
234	if (p == NULL)
235	    continue;
236
237	if (p == in_buf) {
238	    memmove(in_buf, in_buf + 2, sizeof(in_buf) - 2);
239	    state = BODY;
240	    break;
241	} else if (state == RESPONSE) {
242	    req->response = strndup(in_buf, p - in_buf);
243	    state = HEADER;
244	} else {
245	    req->headers = realloc(req->headers,
246				   (req->num_headers + 1) * sizeof(req->headers[0]));
247	    req->headers[req->num_headers] = strndup(in_buf, p - in_buf);
248	    if (req->headers[req->num_headers] == NULL)
249		errx(1, "strdup");
250	    req->num_headers++;
251	}
252	in_len = 0;
253    }
254
255    if (state != BODY)
256	abort();
257
258    const char *h = http_find_header(req, "Content-Length:");
259    if (h == NULL)
260	errx(1, "Missing `Content-Length'");
261
262    content_length = atoi(h);
263
264    req->body_size = content_length;
265    req->body = erealloc(req->body, content_length + 1);
266
267    ret = krb5_storage_read(sp, req->body, req->body_size);
268    if (ret < 0 || (size_t)ret != req->body_size)
269	errx(1, "failed to read body");
270
271    ((char *)req->body)[req->body_size] = '\0';
272
273    if (verbose_flag) {
274	printf("response: %s\n", req->response);
275	for (i = 0; i < req->num_headers; i++)
276	    printf("response-header[%d] %s\n", i, req->headers[i]);
277	printf("body: %.*s\n", (int)req->body_size, (char *)req->body);
278    }
279
280    return 0;
281}
282
283
284int
285main(int argc, char **argv)
286{
287    int i, s, done, print_body, gssapi_done, gssapi_started, optidx = 0;
288    const char *host, *page;
289    struct http_req req;
290    char *headers[99]; /* XXX */
291    int num_headers;
292    krb5_storage *sp;
293
294    gss_cred_id_t client_cred = GSS_C_NO_CREDENTIAL;
295    gss_ctx_id_t context_hdl = GSS_C_NO_CONTEXT;
296    gss_name_t server = GSS_C_NO_NAME;
297    gss_OID mech_oid, cred_mech_oid;
298    OM_uint32 flags;
299    OM_uint32 maj_stat, min_stat;
300
301    setprogname(argv[0]);
302
303    if(getarg(http_args, num_http_args, argc, argv, &optidx))
304	usage(1);
305
306    if (help_flag)
307	usage (0);
308
309    if(version_flag) {
310	print_version(NULL);
311	exit(0);
312    }
313
314    argc -= optidx;
315    argv += optidx;
316
317    mech_oid = select_mech(mech);
318
319    if (cred_mech_str)
320	cred_mech_oid = select_mech(cred_mech_str);
321    else
322	cred_mech_oid = mech_oid;
323
324    if (argc != 1 && argc != 2)
325	errx(1, "usage: %s host [page]", getprogname());
326    host = argv[0];
327    if (argc == 2)
328	page = argv[1];
329    else
330	page = "/";
331
332    flags = 0;
333    if (delegate_flag)
334	flags |= GSS_C_DELEG_FLAG;
335    if (policy_flag)
336	flags |= GSS_C_DELEG_POLICY_FLAG;
337    if (mutual_flag)
338	flags |= GSS_C_MUTUAL_FLAG;
339
340    done = 0;
341    num_headers = 0;
342    gssapi_done = 0;
343    gssapi_started = 0;
344
345    if (client_str) {
346	gss_buffer_desc name_buffer;
347	gss_name_t name;
348	gss_OID_set mechset = GSS_C_NO_OID_SET;
349
350	name_buffer.value = client_str;
351	name_buffer.length = strlen(client_str);
352
353	maj_stat = gss_import_name(&min_stat, &name_buffer, GSS_C_NT_USER_NAME, &name);
354	if (maj_stat)
355	    errx(1, "failed to import name");
356
357	if (cred_mech_oid) {
358	    gss_create_empty_oid_set(&min_stat, &mechset);
359	    gss_add_oid_set_member(&min_stat, cred_mech_oid, &mechset);
360	}
361
362	maj_stat = gss_acquire_cred(&min_stat, name, GSS_C_INDEFINITE,
363				    mechset, GSS_C_INITIATE,
364				    &client_cred, NULL, NULL);
365	gss_release_name(&min_stat, &name);
366	gss_release_oid_set(&min_stat, &mechset);
367	if (maj_stat)
368	    errx(1, "failed to find cred of name %s", client_str);
369    }
370
371    {
372	gss_buffer_desc name_token;
373	char *name;
374	asprintf(&name, "%s@%s", gss_service, host);
375	name_token.length = strlen(name);
376	name_token.value = name;
377
378	maj_stat = gss_import_name(&min_stat,
379				   &name_token,
380				   GSS_C_NT_HOSTBASED_SERVICE,
381				   &server);
382	if (GSS_ERROR(maj_stat))
383	    gss_err (1, min_stat, "gss_inport_name: %s", name);
384	free(name);
385    }
386
387    s = do_connect(host, port_str);
388    if (s < 0)
389	errx(1, "connection failed");
390
391    sp = krb5_storage_from_fd(s);
392    if (sp == NULL)
393	errx(1, "krb5_storage_from_fd");
394
395    do {
396	print_body = 0;
397
398	http_query(sp, host, page, headers, num_headers, &req);
399	for (i = 0 ; i < num_headers; i++)
400	    free(headers[i]);
401	num_headers = 0;
402
403	if (strstr(req.response, " 200 ") != NULL) {
404	    print_body = 1;
405	    done = 1;
406	} else if (strstr(req.response, " 401 ") != NULL) {
407	    if (http_find_header(&req, "WWW-Authenticate:") == NULL)
408		errx(1, "Got %s but missed `WWW-Authenticate'", req.response);
409	}
410
411	if (!gssapi_done) {
412	    const char *h = http_find_header(&req, "WWW-Authenticate:");
413	    if (h == NULL)
414		errx(1, "Got %s but missed `WWW-Authenticate'", req.response);
415
416	    if (strncasecmp(h, "Negotiate", 9) == 0) {
417		gss_buffer_desc input_token, output_token;
418
419		if (verbose_flag)
420		    printf("Negotiate found\n");
421
422		i = 9;
423		while(h[i] && isspace((unsigned char)h[i]))
424		    i++;
425		if (h[i] != '\0') {
426		    size_t len = strlen(&h[i]);
427		    int slen;
428		    if (len == 0)
429			errx(1, "invalid Negotiate token");
430		    input_token.value = emalloc(len);
431		    slen = base64_decode(&h[i], input_token.value);
432		    if (slen < 0)
433			errx(1, "invalid base64 Negotiate token %s", &h[i]);
434		    input_token.length = slen;
435		} else {
436		    if (gssapi_started)
437			errx(1, "Negotiate already started");
438		    gssapi_started = 1;
439
440		    input_token.length = 0;
441		    input_token.value = NULL;
442		}
443
444		if (strstr(req.response, " 200 ") != NULL)
445		    sleep(1);
446
447		maj_stat =
448		    gss_init_sec_context(&min_stat,
449					 client_cred,
450					 &context_hdl,
451					 server,
452					 mech_oid,
453					 flags,
454					 0,
455					 GSS_C_NO_CHANNEL_BINDINGS,
456					 &input_token,
457					 NULL,
458					 &output_token,
459					 NULL,
460					 NULL);
461		if (maj_stat == GSS_S_CONTINUE_NEEDED) {
462
463		} else if (maj_stat == GSS_S_COMPLETE) {
464		    gss_name_t targ_name, src_name;
465		    gss_buffer_desc name_buffer;
466		    gss_OID mech_type;
467
468		    gssapi_done = 1;
469
470		    maj_stat = gss_inquire_context(&min_stat,
471						   context_hdl,
472						   &src_name,
473						   &targ_name,
474						   NULL,
475						   &mech_type,
476						   NULL,
477						   NULL,
478						   NULL);
479		    if (GSS_ERROR(maj_stat))
480			gss_err (1, min_stat, "gss_inquire_context");
481
482		    printf("Negotiate done: %s\n", mech);
483
484		    maj_stat = gss_display_name(&min_stat,
485						src_name,
486						&name_buffer,
487						NULL);
488		    if (GSS_ERROR(maj_stat))
489			gss_print_errors(min_stat);
490		    else
491			printf("Source: %.*s\n",
492			       (int)name_buffer.length,
493			       (char *)name_buffer.value);
494
495		    gss_release_buffer(&min_stat, &name_buffer);
496
497		    maj_stat = gss_display_name(&min_stat,
498						targ_name,
499						&name_buffer,
500						NULL);
501		    if (GSS_ERROR(maj_stat))
502			gss_print_errors(min_stat);
503		    else
504			printf("Target: %.*s\n",
505			       (int)name_buffer.length,
506			       (char *)name_buffer.value);
507
508		    gss_release_name(&min_stat, &targ_name);
509		    gss_release_buffer(&min_stat, &name_buffer);
510		} else {
511		    gss_err (1, min_stat, "gss_init_sec_context");
512		}
513
514
515		if (output_token.length) {
516		    char *neg_token;
517
518		    base64_encode(output_token.value,
519				  (int)output_token.length,
520				  &neg_token);
521
522		    asprintf(&headers[0], "Authorization: Negotiate %s",
523			     neg_token);
524
525		    num_headers = 1;
526		    free(neg_token);
527		    gss_release_buffer(&min_stat, &output_token);
528		}
529		if (input_token.length)
530		    free(input_token.value);
531
532	    } else
533		done = 1;
534	} else
535	    done = 1;
536
537	if (print_body || verbose_flag)
538	    printf("%.*s\n", (int)req.body_size, (char *)req.body);
539
540	http_req_free(&req);
541    } while (!done);
542
543    if (gssapi_done == 0)
544	errx(1, "gssapi not done but http dance done");
545
546    krb5_storage_free(sp);
547    close(s);
548
549    return 0;
550}
551