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? */
37int enable_http = -1;
38
39/* Log over requests to the KDC */
40const char *request_log;
41
42/* A string describing on what ports to listen */
43const char *port_str;
44
45krb5_addresses explicit_addresses;
46
47size_t max_request_udp;
48size_t max_request_tcp;
49
50/*
51 * a tuple describing on what to listen
52 */
53
54struct port_desc{
55    int family;
56    int type;
57    int port;
58};
59
60/* the current ones */
61
62static struct port_desc *ports;
63static size_t num_ports;
64
65static void
66kdc_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
77static void
78add_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
110static void
111add_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
124static void
125add_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
148static void
149add_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
172static void
173parse_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
215struct 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
230static void
231init_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 * re-initialize all `n' ->sa in `d'.
240 */
241
242static void
243reinit_descrs (struct descr *d, int n)
244{
245    int i;
246
247    for (i = 0; i < n; ++i)
248	d[i].sa = (struct sockaddr *)&d[i].__ss;
249}
250
251/*
252 * Create the socket (family, type, port) in `d'
253 */
254
255static void
256init_socket(krb5_context context,
257	    krb5_kdc_configuration *config,
258	    struct descr *d, krb5_address *a, int family, int type, int port)
259{
260    krb5_error_code ret;
261    struct sockaddr_storage __ss;
262    struct sockaddr *sa = (struct sockaddr *)&__ss;
263    krb5_socklen_t sa_size = sizeof(__ss);
264    int http_flag = 0;
265
266    if (enable_http == 1)
267	http_flag = HEIM_SIPC_TYPE_HTTP;
268
269    init_descr (d);
270
271    ret = krb5_addr2sockaddr (context, a, sa, &sa_size, port);
272    if (ret) {
273	krb5_warn(context, ret, "krb5_addr2sockaddr");
274	rk_closesocket(d->s);
275	d->s = rk_INVALID_SOCKET;
276	return;
277    }
278
279    if (sa->sa_family != family)
280	return;
281
282    d->s = socket(family, type, 0);
283    if(rk_IS_BAD_SOCKET(d->s)){
284	krb5_warn(context, errno, "socket(%d, %d, 0)", family, type);
285	d->s = rk_INVALID_SOCKET;
286	return;
287    }
288    socket_set_reuseaddr(d->s, 1);
289    socket_set_nopipe(d->s, 1);
290#ifdef HAVE_IPV6
291    if (family == AF_INET6)
292	socket_set_ipv6only(d->s, 1);
293#endif
294    d->type = type;
295    d->port = port;
296
297    if(rk_IS_SOCKET_ERROR(bind(d->s, sa, sa_size))){
298	char a_str[256];
299	size_t len;
300
301	krb5_print_address (a, a_str, sizeof(a_str), &len);
302	krb5_warn(context, errno, "bind %s/%d", a_str, ntohs(port));
303	rk_closesocket(d->s);
304	d->s = rk_INVALID_SOCKET;
305	return;
306    }
307
308    if(type == SOCK_STREAM && listen(d->s, SOMAXCONN) < 0){
309	char a_str[256];
310	size_t len;
311
312	krb5_print_address (a, a_str, sizeof(a_str), &len);
313	krb5_warn(context, errno, "listen %s/%d", a_str, ntohs(port));
314	rk_closesocket(d->s);
315	d->s = rk_INVALID_SOCKET;
316	return;
317    }
318
319    if (type == SOCK_STREAM) {
320	ret = heim_sipc_stream_listener(d->s,
321					HEIM_SIPC_TYPE_UINT32|http_flag|HEIM_SIPC_TYPE_ONE_REQUEST,
322					kdc_service, d, &d->u);
323	if (ret)
324	    errx(1, "heim_sipc_stream_listener: %d", ret);
325    } else {
326	ret = heim_sipc_service_dgram(d->s, 0,
327				      kdc_service, d, &d->u);
328	if (ret)
329	    errx(1, "heim_sipc_service_dgram: %d", ret);
330    }
331}
332
333/*
334 * Allocate descriptors for all the sockets that we should listen on
335 * and return the number of them.
336 */
337
338static int
339init_sockets(krb5_context context,
340	     krb5_kdc_configuration *config,
341	     struct descr **desc)
342{
343    krb5_error_code ret;
344    size_t i, j;
345    struct descr *d;
346    int num = 0;
347    krb5_addresses addresses;
348
349    if (explicit_addresses.len) {
350	addresses = explicit_addresses;
351    } else {
352#if defined(IPV6_PKTINFO) && defined(IP_PKTINFO)
353	ret = krb5_get_all_any_addrs(context, &addresses);
354#else
355	ret = krb5_get_all_server_addrs(context, &addresses);
356#endif
357	if (ret)
358	    krb5_err (context, 1, ret, "krb5_get_all_{server,any}_addrs");
359    }
360
361    parse_ports(context, config, port_str);
362    d = calloc(addresses.len * num_ports, sizeof(*d));
363    if (d == NULL)
364	krb5_errx(context, 1, "malloc(%lu) failed",
365		  (unsigned long)num_ports * sizeof(*d));
366
367    for (i = 0; i < num_ports; i++){
368	for (j = 0; j < addresses.len; ++j) {
369	    char a_str[80];
370	    size_t len;
371
372	    init_socket(context, config, &d[num], &addresses.val[j],
373			ports[i].family, ports[i].type, ports[i].port);
374	    krb5_print_address (&addresses.val[j], a_str,
375				sizeof(a_str), &len);
376
377	    kdc_log(context, config, 5, "%slistening on %s port %u/%s",
378		    d[num].s != rk_INVALID_SOCKET ? "" : "FAILED ",
379		    a_str,
380		    ntohs(ports[i].port),
381		    (ports[i].type == SOCK_STREAM) ? "tcp" : "udp");
382
383	    if(d[num].s != rk_INVALID_SOCKET)
384		num++;
385	}
386    }
387    krb5_free_addresses (context, &addresses);
388    d = realloc(d, num * sizeof(*d));
389    if (d == NULL && num != 0)
390	krb5_errx(context, 1, "realloc(%lu) failed",
391		  (unsigned long)num * sizeof(*d));
392    reinit_descrs (d, num);
393    *desc = d;
394    return num;
395}
396
397/*
398 *
399 */
400
401static krb5_context kdc_context;
402static krb5_kdc_configuration *kdc_config;
403
404/*
405 *
406 */
407
408static void
409kdc_service(void *ctx, const heim_idata *req,
410	     const heim_icred cred,
411	     heim_ipc_complete complete,
412	     heim_sipc_call cctx)
413{
414    krb5_socklen_t sasize;
415    struct sockaddr *sa;
416    struct descr *d = ctx;
417    int datagram_reply = (d->type == SOCK_DGRAM);
418    krb5_data reply;
419    krb5_error_code ret;
420    char addr[NI_MAXHOST], port[NI_MAXSERV];
421
422    krb5_kdc_update_time(NULL);
423    krb5_data_zero(&reply);
424
425    sa = heim_ipc_cred_get_client_address(cred, &sasize);
426
427    if (sa == NULL || getnameinfo(sa, sasize, addr, sizeof(addr), port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV) != 0)
428	strlcpy(addr, "unknown network address", sizeof(addr));
429    else {
430	strlcat(addr, ":", sizeof(addr));
431	strlcat(addr, port, sizeof(addr));
432    }
433
434    ret = krb5_kdc_process_request(kdc_context, kdc_config,
435				   req->data, req->length,
436				   &reply,
437				   addr, sa,
438				   datagram_reply);
439    if(request_log)
440	krb5_kdc_save_request(kdc_context, request_log,
441			      req->data, req->length, &reply, d->sa);
442
443    (*complete)(cctx, ret, &reply);
444    krb5_data_free(&reply);
445}
446
447static void
448kdc_local(void *ctx, const heim_idata *req,
449	  const heim_icred cred,
450	  heim_ipc_complete complete,
451	  heim_sipc_call cctx)
452{
453    krb5_error_code ret;
454    krb5_data reply;
455
456    krb5_kdc_update_time(NULL);
457    krb5_data_zero(&reply);
458
459    ret = krb5_kdc_process_request(kdc_context, kdc_config,
460				   req->data, req->length,
461				   &reply,
462				   "local-ipc", NULL, 0);
463    (*complete)(cctx, ret, &reply);
464    krb5_data_free(&reply);
465}
466
467
468
469static struct descr *kdc_descrs;
470static unsigned int kdc_ndescr;
471
472void
473setup_listeners(krb5_context context,
474		krb5_kdc_configuration *config,
475		int ipc, int network)
476{
477    kdc_context = context;
478    kdc_config = config;
479
480    if (network) {
481	kdc_ndescr = init_sockets(context, config, &kdc_descrs);
482	if(kdc_ndescr <= 0)
483	    krb5_errx(context, 1, "No sockets!");
484    }
485
486#ifdef __APPLE__
487    if (ipc) {
488	heim_sipc mach;
489	heim_sipc_launchd_mach_init("org.h5l.kdc", kdc_local, NULL, &mach);
490    }
491#endif
492    kdc_log(context, config, 0, "KDC started");
493}
494