1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
|
/* $Id: gssapi.c 553 2004-06-18 06:34:34Z eagle $
**
** GSS-API implementation for requester.
**
** Written by Russ Allbery <rra@stanford.edu>
**
** This file contains the requester portion of a GSS-API S/Ident protocol
** implementation. As written, it only supports Kerberos v5.
*/
#include <ctype.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "sident.h"
#include <krb5.h>
#ifdef HAVE_GSSAPI_H
# include <gssapi.h>
#else
# include <gssapi/gssapi.h>
# include <gssapi/gssapi_generic.h>
#endif
/* Handle compatibility to older versions of MIT Kerberos. */
#ifndef HAVE_GSS_RFC_OIDS
# define GSS_C_NT_USER_NAME gss_nt_user_name
#endif
extern struct ident_requester_auth *global_auth_method;
extern int ident_decodebase64();
extern void ident_writebase64();
extern int do_out();
extern int id_read_response();
extern int ident_result_code();
#ifndef MAXHOSTNAMELEN
# define MAXHOSTNAMELEN 256
#endif
/* Stub out some debugging functions if needed. */
#ifndef DEBUG
# define debug_show_status(p, maj, min) /* empty */
#endif
/* The steps of the authentication exchange. */
enum gssapi_step {
STEP_ERROR = -1, /* An error has occurred. */
STEP_CONTEXT, /* Need to establish a context. */
STEP_FINAL, /* Need to send the authenticator. */
STEP_DONE /* Completed the exchange. */
};
/* Holds the current GSS-API authentication exchange state. */
struct gssapi_state {
enum gssapi_step step; /* Step of the authentication exchange. */
char *service; /* ASCII name of requester. */
gss_name_t service_name; /* GSS-API internal name of requester. */
gss_cred_id_t credentials; /* GSS-API credentials (from keytab). */
gss_ctx_id_t context; /* GSS-API context with requester. */
gss_buffer_desc responder; /* ASCII name of user identity. */
gss_name_t responder_name; /* GSS-API internal name of responder. */
time_t expires; /* Expiration time of context. */
char response[2048]; /* Store generated response. */
};
#ifdef DEBUG
/*
** Show the GSS-API error message for a particular type of status. Takes a
** prefix to put in front of any printed messages, the return code, and the
** type of the code.
*/
static void
debug_show_status_type(const char *prefix, OM_uint32 code, int type)
{
OM_uint32 min_stat;
gss_buffer_desc message;
OM_uint32 context;
context = 0;
while (1) {
gss_display_status(&min_stat, code, type, GSS_C_NULL_OID, &context,
&message);
printf("GSS-API error %s: %s\n", prefix, (char *) message.value);
gss_release_buffer(&min_stat, &message);
if (!context)
break;
}
}
/*
** Show the GSS-API error message of a failure. Takes a prefix to put in
** front of any printed message, the major status, and the mechanism status.
*/
static void
debug_show_status(const char *prefix, OM_uint32 maj_stat, OM_uint32 min_stat)
{
debug_show_status_type(prefix, maj_stat, GSS_C_GSS_CODE);
debug_show_status_type(prefix, min_stat, GSS_C_MECH_CODE);
}
#endif /* DEBUG */
/*
** Lowercase a string in place.
*/
static void
lowercase(char *string)
{
char *p;
for (p = string; *p != '\0'; p++)
*p = tolower((unsigned char) *p);
}
/*
** Convert a fully-qualified principal, taken from gss_display_name to a
** local principal without the realm information, if and only if the realms
** match. If this operation fails for any reason, just put the empty string
** into the output parameter. Takes a buffer into which to write the result
** and the length of the buffer.
*/
static void
gssapi_local_principal(const char *name, char *buffer, size_t length)
{
krb5_context context;
krb5_principal principal;
*buffer = '\0';
if (krb5_init_context(&context) != 0)
return;
if (krb5_parse_name(context, name, &principal) != 0)
return;
if (krb5_aname_to_localname(context, principal, length, buffer) != 0)
*buffer = '\0';
krb5_free_principal(context, principal);
krb5_free_context(context);
}
/*
** Start the requester side of the authentication exchange. Returns an error
** code or IDENT_AUTH_OKAY.
*/
char *
gssapi_requester_start(struct ident_auth_client_data *auth_data)
{
struct gssapi_state *gstate;
gss_buffer_desc token;
OM_uint32 maj_stat, min_stat;
char hostname[MAXHOSTNAMELEN];
struct hostent *hp;
gstate = malloc(sizeof(struct gssapi_state));
if (gstate == NULL)
return NULL;
auth_data->auth_method_data = gstate;
/* Initialize the state. */
gstate->service = NULL;
gstate->service_name = GSS_C_NO_NAME;
gstate->step = STEP_CONTEXT;
gstate->context = GSS_C_NO_CONTEXT;
gstate->credentials = GSS_C_NO_CREDENTIAL;
gstate->responder.length = 0;
gstate->responder.value = NULL;
/* Import the server name. It would be nice to also be able to specify a
different keytab file, but I don't see how to do that via GSS-API. */
if (gethostname(hostname, sizeof(hostname)) < 0)
return NULL;
hp = gethostbyname(hostname);
if (hp == NULL)
return NULL;
gstate->service = malloc(strlen("ident/") + strlen(hp->h_name) + 1);
if (gstate->service == NULL)
return NULL;
strcpy(gstate->service, "ident/");
strcat(gstate->service, hp->h_name);
lowercase(gstate->service);
/* Establish the internal name. */
token.value = gstate->service;
token.length = strlen(gstate->service) + 1;
maj_stat = gss_import_name(&min_stat, &token, GSS_C_NT_USER_NAME,
&gstate->service_name);
if (maj_stat != GSS_S_COMPLETE)
return NULL;
/* Acquire credentials. */
maj_stat = gss_acquire_cred(&min_stat, gstate->service_name, 0,
GSS_C_NULL_OID_SET, GSS_C_ACCEPT,
&gstate->credentials, NULL, NULL);
if (maj_stat != GSS_S_COMPLETE)
return NULL;
/* Prepare the initial request. */
if (global_auth_method->auth_flags != NULL)
sprintf(gstate->response, "GSSAPI , , %s",
global_auth_method->auth_flags);
else
sprintf(gstate->response, "GSSAPI , ");
return gstate->response;
}
/*
** Send a token to the responder. Takes the auth_data struct and the GSS-API
** token to send, and returns an error code or IDENT_AUTH_OKAY. Does the
** base64-encoding and protocol encapsulation necessary.
*/
static int
gssapi_send_token(struct ident_auth_client_data *auth_data,
gss_buffer_t token)
{
char output[IDENT_BUFFER_SIZE];
char line[IDENT_BUFFER_SIZE];
ident_writebase64(token->value, output, token->length);
sprintf(line, "%s , %s ", auth_data->auth_struct->auth_method, output);
if (do_out(line, auth_data) < 0)
return IDENT_SYSTEM_ERROR;
return IDENT_AUTH_OKAY;
}
/*
** Receive and decode a token from the responder. Takes the auth_data
** struct, the buffer into which to write the received GSS-API token, and a
** flag saying whether to read data from the network. If that flag is not
** set, expects the line to already be in the auth_data struct. Returns an
** error code or IDENT_AUTH_OKAY.
*/
static int
gssapi_recv_token(struct ident_auth_client_data *auth_data,
gss_buffer_t token, int network)
{
char data[IDENT_BUFFER_SIZE];
int status;
if (network) {
ident_t id;
int resp_port, req_port;
char *p;
id.fd = auth_data->fd;
id.buf[0] = '\0';
status = id_read_response(&id, auth_data->timeout);
if (status != IDENT_AUTH_OKAY)
return status;
/* This is really ugly. */
if (sscanf(id.buf, "%d , %d : AUTHENTICATE : GSSAPI ,", &resp_port,
&req_port) != 2) {
if (sscanf(id.buf, "%d , %d : ERROR : %[^ \t\r\n,]", &resp_port,
&req_port, data) == 3)
return ident_result_code(data);
return IDENT_INVALID_RESP_INFO;
}
if ((resp_port != auth_data->responder_port)
|| (req_port != auth_data->requester_port))
return IDENT_INVALID_RESP_INFO;
p = strchr(id.buf, 'G');
if (p == NULL)
return IDENT_INVALID_RESP_INFO;
if (sscanf(p, "GSSAPI , %[A-Za-z0-9+/=]", data) != 1)
data[0] = '\0';
} else {
sscanf(auth_data->authenticate_data, " GSSAPI , %[A-Za-z0-9+/=]",
data);
}
/* Extract the data. */
status = ident_decodebase64(data);
/* Put it into the token. */
token->length = status;
token->value = malloc(status);
if (token->value == NULL)
return IDENT_SYSTEM_ERROR;
memcpy(token->value, data, status);
return IDENT_AUTH_OKAY;
}
/*
** Establish a GSS-API context with the responder. This is the first step of
** the GSS-API exchange and happens before the ident information is sent.
** Returns an error code or IDENT_AUTH_OKAY.
*/
static int
gssapi_requester_context(struct ident_auth_client_data *auth_data)
{
struct gssapi_state *gstate = auth_data->auth_method_data;
gss_buffer_desc send_token, recv_token;
gss_name_t responder_name;
OM_uint32 maj_stat, min_stat, flags, lifetime;
gss_OID oid;
int status, sent;
int first = 1;
/* GSS-API negotiation may take multiple rounds, so repeat until it is
complete. */
do {
sent = 0;
status = gssapi_recv_token(auth_data, &recv_token, !first);
first = 0;
if (status < 0)
goto fail;
/* Perform the next step of the GSS-API negotiation. */
maj_stat = gss_accept_sec_context(&min_stat,
&gstate->context,
gstate->credentials,
&recv_token,
GSS_C_NO_CHANNEL_BINDINGS,
&responder_name,
&oid,
&send_token,
&flags,
&lifetime,
NULL); /* ignore del_cred_handle */
gss_release_buffer(&min_stat, &recv_token);
/* If the status returned by the GSS-API call is abnormal, abort. */
if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
goto fail;
/* Send the resulting token to the other side. We may have a final
token to send even if the status says that we're done. */
if (send_token.length > 0) {
status = gssapi_send_token(auth_data, &send_token);
gss_release_buffer(&min_stat, &send_token);
if (status < 0)
goto fail;
sent = 1;
}
} while (maj_stat == GSS_S_CONTINUE_NEEDED);
/* If we sent a final token, we'll get a final response. */
if (sent) {
status = gssapi_recv_token(auth_data, &recv_token, 1);
if (status < 0)
goto fail;
}
/* Obtain the client identity. */
maj_stat = gss_display_name(&min_stat, responder_name, &gstate->responder,
&oid);
if (maj_stat != GSS_S_COMPLETE) {
debug_show_status("display_name", maj_stat, min_stat);
return IDENT_SYSTEM_ERROR;
}
gss_release_name(&min_stat, &responder_name);
/* Convert the expiration time. */
if (lifetime == GSS_C_INDEFINITE)
gstate->expires = (time_t) -1;
else
gstate->expires = time(NULL) + lifetime;
/* Completed successfully. */
return IDENT_AUTH_OKAY;
fail:
if (gstate->context != GSS_C_NO_CONTEXT)
gss_delete_sec_context(&min_stat, &gstate->context, GSS_C_NO_BUFFER);
return (status != 0) ? status : IDENT_AUTH_FAIL;
}
/*
** Receive the S/Ident authenticator from the responder, done after a context
** is established. Note that the authorization identity sent in the
** authenticator is fairly worthless given how S/Ident is normally used, but
** we pass it back in the auth_data struct regardless. The really
** interesting bit is the authentication identity, however, which we can
** extract from the security context. Returns an error code or
** IDENT_AUTH_OKAY.
*/
int
gssapi_requester_recvauth(struct ident_auth_client_data *auth_data)
{
struct gssapi_state *gstate = auth_data->auth_method_data;
gss_buffer_desc send_token, recv_token, token;
OM_uint32 maj_stat, min_stat, value;
IDENT_INT16 resp_port, req_port, auth_len;
char *principal, *identity;
int conf_state, status;
#ifdef DEBUG
int i;
#endif
/* Start by sending to the responder four octets, the first of which is a
bitmask specifying the protection, and the second through fourth of
which specify the maximum message length. We want the protection to
always be zero. Pick 2048 for the length, very arbitrarily. */
value = htonl(2048);
token.value = malloc(4);
if (token.value == NULL)
return IDENT_SYSTEM_ERROR;
memcpy(token.value, &value, 4);
token.length = 4;
maj_stat = gss_wrap(&min_stat, gstate->context, 0, 0, &token, NULL,
&send_token);
gss_release_buffer(&min_stat, &token);
if (maj_stat != GSS_S_COMPLETE) {
debug_show_status("gss_wrap", maj_stat, min_stat);
return IDENT_SYSTEM_ERROR;
}
status = gssapi_send_token(auth_data, &send_token);
gss_release_buffer(&min_stat, &send_token);
if (status != IDENT_AUTH_OKAY)
return status;
/* Receive the response from the responder. */
status = gssapi_recv_token(auth_data, &recv_token, 1);
if (status != IDENT_AUTH_OKAY)
return status;
/* Take the response apart and check the ports. Then fill out the
identification portion of auth_data. */
maj_stat = gss_unwrap(&min_stat, gstate->context, &recv_token, &token,
&conf_state, NULL);
gss_release_buffer(&min_stat, &recv_token);
if (maj_stat != GSS_S_COMPLETE) {
debug_show_status("gss_unwrap", maj_stat, min_stat);
return IDENT_INVALID_RESP_INFO;
}
#ifdef DEBUG
printf("Got token: ");
for (i = 0; i < token.length; i++)
printf("%u ", ((unsigned char *)(token.value))[i]);
printf("\n");
#endif
memcpy(&resp_port, (char *) token.value + 6, 2);
resp_port = ntohs(resp_port);
memcpy(&req_port, (char *) token.value + 8, 2);
req_port = ntohs(req_port);
memcpy(&auth_len, (char *) token.value + 10, 2);
auth_len = ntohs(auth_len);
if ((resp_port != auth_data->responder_port)
|| (req_port != auth_data->requester_port)
|| auth_len <= 0)
return IDENT_INVALID_RESP_INFO;
identity = malloc(auth_len + 1);
if (identity == NULL)
return IDENT_SYSTEM_ERROR;
memcpy(identity, (char *) token.value + 12, auth_len);
identity[auth_len] = '\0';
principal = (char *) gstate->responder.value;
sprintf(auth_data->user_ident, "%s:%s", principal, identity);
auth_data->expires = gstate->expires;
gssapi_local_principal(principal, auth_data->user_principal,
sizeof(auth_data->user_principal));
/* All done. */
return IDENT_AUTH_OKAY;
}
/*
** Perform one step of the authentication exchange. This is the main routine
** that runs the GSS-API authentication process, calling the appropriate
** functions until authentication is complete. Returns an error code or
** IDENT_AUTH_OKAY.
*/
int
gssapi_requester_auth(struct ident_auth_client_data *auth_data)
{
struct gssapi_state *gstate = auth_data->auth_method_data;
int status;
while (gstate->step != STEP_ERROR) {
switch (gstate->step) {
case STEP_CONTEXT:
status = gssapi_requester_context(auth_data);
if (status != IDENT_AUTH_OKAY) {
gstate->step = STEP_ERROR;
return status;
}
gstate->step = STEP_FINAL;
break;
case STEP_FINAL:
status = gssapi_requester_recvauth(auth_data);
if (status != IDENT_AUTH_OKAY) {
gstate->step = STEP_ERROR;
return status;
}
gstate->step = STEP_DONE;
break;
case STEP_DONE:
return IDENT_AUTH_OKAY;
break;
default:
gstate->step = STEP_ERROR;
return IDENT_SYSTEM_ERROR;
break;
}
}
return IDENT_SYSTEM_ERROR;
}
/*
** Free GSS-API-specific requester state.
*/
void
gssapi_free_state(void *state)
{
struct gssapi_state *gstate = state;
OM_uint32 min_stat;
if (gstate->service != NULL)
free(gstate->service);
if (gstate->service_name != GSS_C_NO_NAME)
gss_release_name(&min_stat, gstate->service_name);
if (gstate->credentials != GSS_C_NO_CREDENTIAL)
gss_release_cred(&min_stat, gstate->credentials);
if (gstate->context != GSS_C_NO_CONTEXT)
gss_delete_sec_context(&min_stat, gstate->context, GSS_C_NO_BUFFER);
gss_release_buffer(&min_stat, &gstate->responder);
free(gstate);
}
|