connect.c
1 /* 2 * Copyright (c) 1997-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 "kdc_locl.h" 35 36 /* Should we enable the HTTP hack? */ 37 int enable_http = -1; 38 39 /* Log over requests to the KDC */ 40 const char *request_log; 41 42 /* A string describing on what ports to listen */ 43 const char *port_str; 44 45 krb5_addresses explicit_addresses; 46 47 size_t max_request_udp; 48 size_t max_request_tcp; 49 50 /* 51 * a tuple describing on what to listen 52 */ 53 54 struct port_desc{ 55 int family; 56 int type; 57 int port; 58 }; 59 60 /* the current ones */ 61 62 static struct port_desc *ports; 63 static size_t num_ports; 64 65 static void 66 kdc_service(void *ctx, const heim_idata *req, 67 const heim_icred cred, 68 heim_ipc_complete complete, 69 heim_sipc_call cctx); 70 71 72 73 /* 74 * add `family, port, protocol' to the list with duplicate suppresion. 75 */ 76 77 static void 78 add_port(krb5_context context, 79 int family, int port, const char *protocol) 80 { 81 int type; 82 size_t i; 83 84 if(strcmp(protocol, "udp") == 0) 85 type = SOCK_DGRAM; 86 else if(strcmp(protocol, "tcp") == 0) 87 type = SOCK_STREAM; 88 else 89 return; 90 for(i = 0; i < num_ports; i++){ 91 if(ports[i].type == type 92 && ports[i].port == port 93 && ports[i].family == family) 94 return; 95 } 96 ports = realloc(ports, (num_ports + 1) * sizeof(*ports)); 97 if (ports == NULL) 98 krb5_err (context, 1, errno, "realloc"); 99 ports[num_ports].family = family; 100 ports[num_ports].type = type; 101 ports[num_ports].port = port; 102 num_ports++; 103 } 104 105 /* 106 * add a triple but with service -> port lookup 107 * (this prints warnings for stuff that does not exist) 108 */ 109 110 static void 111 add_port_service(krb5_context context, 112 int family, const char *service, int port, 113 const char *protocol) 114 { 115 port = krb5_getportbyname (context, service, protocol, port); 116 add_port (context, family, port, protocol); 117 } 118 119 /* 120 * add the port with service -> port lookup or string -> number 121 * (no warning is printed) 122 */ 123 124 static void 125 add_port_string (krb5_context context, 126 int family, const char *str, const char *protocol) 127 { 128 struct servent *sp; 129 int port; 130 131 sp = roken_getservbyname (str, protocol); 132 if (sp != NULL) { 133 port = sp->s_port; 134 } else { 135 char *end; 136 137 port = htons(strtol(str, &end, 0)); 138 if (end == str) 139 return; 140 } 141 add_port (context, family, port, protocol); 142 } 143 144 /* 145 * add the standard collection of ports for `family' 146 */ 147 148 static void 149 add_standard_ports (krb5_context context, 150 krb5_kdc_configuration *config, 151 int family) 152 { 153 add_port_service(context, family, "kerberos", 88, "udp"); 154 add_port_service(context, family, "kerberos", 88, "tcp"); 155 add_port_service(context, family, "kerberos-sec", 88, "udp"); 156 add_port_service(context, family, "kerberos-sec", 88, "tcp"); 157 if(enable_http) 158 add_port_service(context, family, "http", 80, "tcp"); 159 if(config->enable_kx509) { 160 add_port_service(context, family, "kca_service", 9878, "udp"); 161 add_port_service(context, family, "kca_service", 9878, "tcp"); 162 } 163 164 } 165 166 /* 167 * parse the set of space-delimited ports in `str' and add them. 168 * "+" => all the standard ones 169 * otherwise it's port|service[/protocol] 170 */ 171 172 static void 173 parse_ports(krb5_context context, 174 krb5_kdc_configuration *config, 175 const char *str) 176 { 177 char *pos = NULL; 178 char *p; 179 char *str_copy = strdup (str); 180 181 p = strtok_r(str_copy, " \t", &pos); 182 while(p != NULL) { 183 if(strcmp(p, "+") == 0) { 184 #ifdef HAVE_IPV6 185 add_standard_ports(context, config, AF_INET6); 186 #endif 187 add_standard_ports(context, config, AF_INET); 188 } else { 189 char *q = strchr(p, '/'); 190 if(q){ 191 *q++ = 0; 192 #ifdef HAVE_IPV6 193 add_port_string(context, AF_INET6, p, q); 194 #endif 195 add_port_string(context, AF_INET, p, q); 196 }else { 197 #ifdef HAVE_IPV6 198 add_port_string(context, AF_INET6, p, "udp"); 199 add_port_string(context, AF_INET6, p, "tcp"); 200 #endif 201 add_port_string(context, AF_INET, p, "udp"); 202 add_port_string(context, AF_INET, p, "tcp"); 203 } 204 } 205 206 p = strtok_r(NULL, " \t", &pos); 207 } 208 free (str_copy); 209 } 210 211 /* 212 * every socket we listen on 213 */ 214 215 struct descr { 216 krb5_socket_t s; 217 int type; 218 int port; 219 unsigned char *buf; 220 size_t size; 221 size_t len; 222 time_t timeout; 223 struct sockaddr_storage __ss; 224 struct sockaddr *sa; 225 socklen_t sock_len; 226 char addr_string[128]; 227 heim_sipc u; 228 }; 229 230 static void 231 init_descr(struct descr *d) 232 { 233 memset(d, 0, sizeof(*d)); 234 d->sa = (struct sockaddr *)&d->__ss; 235 d->s = rk_INVALID_SOCKET; 236 } 237 238 /* 239 * Create the socket (family, type, port) in `d' 240 */ 241 242 static void 243 init_socket(krb5_context context, 244 krb5_kdc_configuration *config, 245 struct descr *d, krb5_address *a, int family, int type, int port) 246 { 247 krb5_error_code ret; 248 struct sockaddr_storage __ss; 249 struct sockaddr *sa = (struct sockaddr *)&__ss; 250 krb5_socklen_t sa_size = sizeof(__ss); 251 int http_flag = 0; 252 253 if (enable_http == 1) 254 http_flag = HEIM_SIPC_TYPE_HTTP; 255 256 init_descr(d); 257 258 ret = krb5_addr2sockaddr (context, a, sa, &sa_size, port); 259 if (ret) { 260 krb5_warn(context, ret, "krb5_addr2sockaddr"); 261 rk_closesocket(d->s); 262 d->s = rk_INVALID_SOCKET; 263 return; 264 } 265 266 if (sa->sa_family != family) 267 return; 268 269 d->s = socket(family, type, 0); 270 if(rk_IS_BAD_SOCKET(d->s)){ 271 krb5_warn(context, errno, "socket(%d, %d, 0)", family, type); 272 d->s = rk_INVALID_SOCKET; 273 return; 274 } 275 socket_set_reuseaddr(d->s, 1); 276 socket_set_nopipe(d->s, 1); 277 #ifdef HAVE_IPV6 278 if (family == AF_INET6) 279 socket_set_ipv6only(d->s, 1); 280 #endif 281 d->type = type; 282 d->port = port; 283 284 if(rk_IS_SOCKET_ERROR(bind(d->s, sa, sa_size))){ 285 char a_str[256]; 286 size_t len; 287 288 krb5_print_address (a, a_str, sizeof(a_str), &len); 289 krb5_warn(context, errno, "bind %s/%d", a_str, ntohs(port)); 290 rk_closesocket(d->s); 291 d->s = rk_INVALID_SOCKET; 292 return; 293 } 294 295 if(type == SOCK_STREAM && listen(d->s, SOMAXCONN) < 0){ 296 char a_str[256]; 297 size_t len; 298 299 krb5_print_address (a, a_str, sizeof(a_str), &len); 300 krb5_warn(context, errno, "listen %s/%d", a_str, ntohs(port)); 301 rk_closesocket(d->s); 302 d->s = rk_INVALID_SOCKET; 303 return; 304 } 305 306 if (type == SOCK_STREAM) { 307 ret = heim_sipc_stream_listener(d->s, 308 HEIM_SIPC_TYPE_UINT32|http_flag|HEIM_SIPC_TYPE_ONE_REQUEST, 309 kdc_service, d, &d->u); 310 if (ret) 311 errx(1, "heim_sipc_stream_listener: %d", ret); 312 } else { 313 ret = heim_sipc_service_dgram(d->s, 0, 314 kdc_service, d, &d->u); 315 if (ret) 316 errx(1, "heim_sipc_service_dgram: %d", ret); 317 } 318 } 319 320 /* 321 * Allocate descriptors for all the sockets that we should listen on 322 * and return the number of them. 323 */ 324 325 static void 326 init_sockets(krb5_context context, 327 krb5_kdc_configuration *config) 328 { 329 krb5_error_code ret; 330 size_t i, j; 331 struct descr *d; 332 krb5_addresses addresses; 333 int found = 0; 334 335 if (explicit_addresses.len) { 336 addresses = explicit_addresses; 337 } else { 338 #if defined(IPV6_PKTINFO) && defined(IP_PKTINFO) 339 ret = krb5_get_all_any_addrs(context, &addresses); 340 #else 341 ret = krb5_get_all_server_addrs(context, &addresses); 342 #endif 343 if (ret) 344 krb5_err (context, 1, ret, "krb5_get_all_{server,any}_addrs"); 345 } 346 347 parse_ports(context, config, port_str); 348 349 for (i = 0; i < num_ports; i++){ 350 for (j = 0; j < addresses.len; ++j) { 351 char a_str[80]; 352 size_t len; 353 354 d = calloc(1, sizeof(*d)); 355 heim_assert(d != NULL, "out of memory"); 356 357 init_socket(context, config, d, &addresses.val[j], 358 ports[i].family, ports[i].type, ports[i].port); 359 krb5_print_address (&addresses.val[j], a_str, 360 sizeof(a_str), &len); 361 362 kdc_log(context, config, 5, "%slistening on %s port %u/%s", 363 d->s != rk_INVALID_SOCKET ? "" : "FAILED ", 364 a_str, 365 ntohs(ports[i].port), 366 (ports[i].type == SOCK_STREAM) ? "tcp" : "udp"); 367 368 if(d->s == rk_INVALID_SOCKET) 369 free(d); 370 else 371 found = 1; 372 } 373 } 374 375 if (explicit_addresses.len == 0) 376 krb5_free_addresses (context, &addresses); 377 378 if (!found) 379 krb5_errx(context, 1, "No sockets!"); 380 } 381 382 /* 383 * 384 */ 385 386 static krb5_kdc_configuration *kdc_config; 387 388 static HEIMDAL_MUTEX context_mutex = HEIMDAL_MUTEX_INITIALIZER; 389 static int created_key; 390 static HEIMDAL_thread_key context_key; 391 392 393 static void 394 destroy_context(void *ptr) 395 { 396 krb5_context c = ptr; 397 if (c) 398 krb5_free_context(c); 399 } 400 401 402 static krb5_context 403 thread_context(void) 404 { 405 krb5_context context = NULL; 406 int ret = 0; 407 408 HEIMDAL_MUTEX_lock(&context_mutex); 409 410 if (!created_key) { 411 HEIMDAL_key_create(&context_key, destroy_context, ret); 412 heim_assert(ret == 0, "failed to create context key"); 413 created_key = 1; 414 } 415 HEIMDAL_MUTEX_unlock(&context_mutex); 416 417 context = HEIMDAL_getspecific(context_key); 418 if (context == NULL) { 419 ret = krb5_init_context(&context); 420 heim_assert(ret == 0 && context != NULL, "failed to create context"); 421 422 HEIMDAL_setspecific(context_key, context, ret); 423 heim_assert(ret == 0, "failed to set context key"); 424 425 } 426 return context; 427 } 428 429 430 /* 431 * 432 */ 433 434 static void 435 kdc_service(void *ctx, const heim_idata *req, 436 const heim_icred cred, 437 heim_ipc_complete complete, 438 heim_sipc_call cctx) 439 { 440 krb5_socklen_t sasize; 441 struct sockaddr *sa; 442 struct descr *d = ctx; 443 int datagram_reply = (d->type == SOCK_DGRAM); 444 krb5_data reply; 445 krb5_error_code ret; 446 char addr[NI_MAXHOST], port[NI_MAXSERV]; 447 krb5_context context; 448 449 context = thread_context(); 450 451 krb5_kdc_update_time(NULL); 452 krb5_data_zero(&reply); 453 454 sa = heim_ipc_cred_get_client_address(cred, &sasize); 455 456 if (sa == NULL || getnameinfo(sa, sasize, addr, sizeof(addr), port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV) != 0) 457 strlcpy(addr, "unknown network address", sizeof(addr)); 458 else { 459 strlcat(addr, ":", sizeof(addr)); 460 strlcat(addr, port, sizeof(addr)); 461 } 462 463 ret = krb5_kdc_process_request(context, kdc_config, 464 req->data, req->length, 465 &reply, 466 addr, sa, 467 datagram_reply); 468 if(request_log) 469 krb5_kdc_save_request(context, request_log, 470 req->data, req->length, &reply, d->sa); 471 472 (*complete)(cctx, ret, &reply); 473 krb5_data_free(&reply); 474 } 475 476 static void 477 kdc_local(void *ctx, const heim_idata *req, 478 const heim_icred cred, 479 heim_ipc_complete complete, 480 heim_sipc_call cctx) 481 { 482 krb5_context context; 483 krb5_error_code ret; 484 krb5_data reply; 485 486 krb5_kdc_update_time(NULL); 487 krb5_data_zero(&reply); 488 489 context = thread_context(); 490 491 ret = krb5_kdc_process_request(context, kdc_config, 492 req->data, req->length, 493 &reply, 494 "local-ipc", NULL, 0); 495 (*complete)(cctx, ret, &reply); 496 krb5_data_free(&reply); 497 } 498 499 500 501 void 502 setup_listeners(krb5_context context, 503 krb5_kdc_configuration *config, 504 int ipc, int network) 505 { 506 kdc_config = config; 507 508 if (network) 509 init_sockets(context, config); 510 511 #ifdef __APPLE__ 512 if (ipc) { 513 heim_sipc mach; 514 heim_sipc_launchd_mach_init("org.h5l.kdc", kdc_local, NULL, &mach); 515 } 516 #endif 517 kdc_log(context, config, 0, "KDC started"); 518 }