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