/ kdc / connect.c
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  }