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