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