1/*
2   Unix SMB/CIFS implementation.
3   kerberos locator plugin
4   Copyright (C) Guenther Deschner 2007-2008
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 3 of the License, or
9   (at your option) any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program.  If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#include "nsswitch/winbind_client.h"
21#include "libwbclient/wbclient.h"
22
23#ifndef DEBUG_KRB5
24#undef DEBUG_KRB5
25#endif
26
27#if defined(HAVE_KRB5) && defined(HAVE_KRB5_LOCATE_PLUGIN_H)
28
29#if HAVE_COM_ERR_H
30#include <com_err.h>
31#endif
32
33#include <krb5.h>
34#include <krb5/locate_plugin.h>
35
36#ifndef KRB5_PLUGIN_NO_HANDLE
37#define KRB5_PLUGIN_NO_HANDLE KRB5_KDC_UNREACH /* Heimdal */
38#endif
39
40static const char *get_service_from_locate_service_type(enum locate_service_type svc)
41{
42	switch (svc) {
43		case locate_service_kdc:
44		case locate_service_master_kdc:
45			return "88";
46		case locate_service_kadmin:
47		case locate_service_krb524:
48			/* not supported */
49			return NULL;
50		case locate_service_kpasswd:
51			return "464";
52		default:
53			break;
54	}
55	return NULL;
56
57}
58
59#ifdef DEBUG_KRB5
60static const char *locate_service_type_name(enum locate_service_type svc)
61{
62	switch (svc) {
63		case locate_service_kdc:
64			return "locate_service_kdc";
65		case locate_service_master_kdc:
66			return "locate_service_master_kdc";
67		case locate_service_kadmin:
68			return "locate_service_kadmin";
69		case locate_service_krb524:
70			return "locate_service_krb524";
71		case locate_service_kpasswd:
72			return "locate_service_kpasswd";
73		default:
74			break;
75	}
76	return NULL;
77}
78
79static const char *socktype_name(int socktype)
80{
81	switch (socktype) {
82		case SOCK_STREAM:
83			return "SOCK_STREAM";
84		case SOCK_DGRAM:
85			return "SOCK_DGRAM";
86		default:
87			break;
88	}
89	return "unknown";
90}
91
92static const char *family_name(int family)
93{
94	switch (family) {
95		case AF_UNSPEC:
96			return "AF_UNSPEC";
97		case AF_INET:
98			return "AF_INET";
99#if defined(HAVE_IPV6)
100		case AF_INET6:
101			return "AF_INET6";
102#endif
103		default:
104			break;
105	}
106	return "unknown";
107}
108#endif
109
110/**
111 * Check input parameters, return KRB5_PLUGIN_NO_HANDLE for unsupported ones
112 *
113 * @param svc
114 * @param realm string
115 * @param socktype integer
116 * @param family integer
117 *
118 * @return integer.
119 */
120
121static int smb_krb5_locator_lookup_sanity_check(enum locate_service_type svc,
122						const char *realm,
123						int socktype,
124						int family)
125{
126	if (!realm || strlen(realm) == 0) {
127		return EINVAL;
128	}
129
130	switch (svc) {
131		case locate_service_kdc:
132		case locate_service_master_kdc:
133		case locate_service_kpasswd:
134			break;
135		case locate_service_kadmin:
136		case locate_service_krb524:
137			return KRB5_PLUGIN_NO_HANDLE;
138		default:
139			return EINVAL;
140	}
141
142	switch (family) {
143		case AF_UNSPEC:
144		case AF_INET:
145			break;
146#if defined(HAVE_IPV6)
147		case AF_INET6:
148			break;
149#endif
150		default:
151			return EINVAL;
152	}
153
154	switch (socktype) {
155		case SOCK_STREAM:
156		case SOCK_DGRAM:
157		case 0: /* Heimdal uses that */
158			break;
159		default:
160			return EINVAL;
161	}
162
163	return 0;
164}
165
166/**
167 * Try to get addrinfo for a given host and call the krb5 callback
168 *
169 * @param name string
170 * @param service string
171 * @param in struct addrinfo hint
172 * @param cbfunc krb5 callback function
173 * @param cbdata void pointer cbdata
174 *
175 * @return krb5_error_code.
176 */
177
178static krb5_error_code smb_krb5_locator_call_cbfunc(const char *name,
179						    const char *service,
180						    struct addrinfo *in,
181						    int (*cbfunc)(void *, int, struct sockaddr *),
182						    void *cbdata)
183{
184	struct addrinfo *out = NULL;
185	int ret;
186	int count = 3;
187
188	while (count) {
189
190		ret = getaddrinfo(name, service, in, &out);
191		if (ret == 0) {
192			break;
193		}
194
195		if (ret == EAI_AGAIN) {
196			count--;
197			continue;
198		}
199
200#ifdef DEBUG_KRB5
201		fprintf(stderr, "[%5u]: smb_krb5_locator_lookup: "
202			"getaddrinfo failed: %s (%d)\n",
203			(unsigned int)getpid(), gai_strerror(ret), ret);
204#endif
205
206		return KRB5_PLUGIN_NO_HANDLE;
207	}
208
209	ret = cbfunc(cbdata, out->ai_socktype, out->ai_addr);
210#ifdef DEBUG_KRB5
211	if (ret) {
212		fprintf(stderr, "[%5u]: smb_krb5_locator_lookup: "
213			"failed to call callback: %s (%d)\n",
214			(unsigned int)getpid(), error_message(ret), ret);
215	}
216#endif
217
218	freeaddrinfo(out);
219	return ret;
220}
221
222/**
223 * PUBLIC INTERFACE: locate init
224 *
225 * @param context krb5_context
226 * @param privata_data pointer to private data pointer
227 *
228 * @return krb5_error_code.
229 */
230
231static krb5_error_code smb_krb5_locator_init(krb5_context context,
232					     void **private_data)
233{
234	return 0;
235}
236
237/**
238 * PUBLIC INTERFACE: close locate
239 *
240 * @param private_data pointer to private data
241 *
242 * @return void.
243 */
244
245static void smb_krb5_locator_close(void *private_data)
246{
247	return;
248}
249
250
251static bool ask_winbind(const char *realm, char **dcname)
252{
253	wbcErr wbc_status;
254	const char *dc = NULL;
255	struct wbcDomainControllerInfoEx *dc_info = NULL;
256	uint32_t flags;
257
258	flags = WBC_LOOKUP_DC_KDC_REQUIRED |
259		WBC_LOOKUP_DC_IS_DNS_NAME |
260		WBC_LOOKUP_DC_RETURN_DNS_NAME |
261		WBC_LOOKUP_DC_IP_REQUIRED;
262
263	wbc_status = wbcLookupDomainControllerEx(realm, NULL, NULL, flags, &dc_info);
264
265	if (!WBC_ERROR_IS_OK(wbc_status)) {
266#ifdef DEBUG_KRB5
267		fprintf(stderr,"[%5u]: smb_krb5_locator_lookup: failed with: %s\n",
268			(unsigned int)getpid(), wbcErrorString(wbc_status));
269#endif
270		return false;
271	}
272
273	if (dc_info->dc_address) {
274		dc = dc_info->dc_address;
275		if (dc[0] == '\\') dc++;
276		if (dc[0] == '\\') dc++;
277	}
278
279	if (!dc && dc_info->dc_unc) {
280		dc = dc_info->dc_unc;
281		if (dc[0] == '\\') dc++;
282		if (dc[0] == '\\') dc++;
283	}
284
285	if (!dc) {
286		wbcFreeMemory(dc_info);
287		return false;
288	}
289
290	*dcname = strdup(dc);
291	if (!*dcname) {
292		wbcFreeMemory(dc_info);
293		return false;
294	}
295
296	wbcFreeMemory(dc_info);
297	return true;
298}
299
300/**
301 * PUBLIC INTERFACE: locate lookup
302 *
303 * @param private_data pointer to private data
304 * @param svc enum locate_service_type.
305 * @param realm string
306 * @param socktype integer
307 * @param family integer
308 * @param cbfunc callback function to send back entries
309 * @param cbdata void pointer to cbdata
310 *
311 * @return krb5_error_code.
312 */
313
314static krb5_error_code smb_krb5_locator_lookup(void *private_data,
315					       enum locate_service_type svc,
316					       const char *realm,
317					       int socktype,
318					       int family,
319					       int (*cbfunc)(void *, int, struct sockaddr *),
320							void *cbdata)
321{
322	krb5_error_code ret;
323	struct addrinfo aihints;
324	char *kdc_name = NULL;
325	const char *service = get_service_from_locate_service_type(svc);
326
327	ZERO_STRUCT(aihints);
328
329#ifdef DEBUG_KRB5
330	fprintf(stderr,"[%5u]: smb_krb5_locator_lookup: called for '%s' "
331			"svc: '%s' (%d) "
332			"socktype: '%s' (%d), family: '%s' (%d)\n",
333			(unsigned int)getpid(), realm,
334			locate_service_type_name(svc), svc,
335			socktype_name(socktype), socktype,
336		        family_name(family), family);
337#endif
338	ret = smb_krb5_locator_lookup_sanity_check(svc, realm, socktype,
339						   family);
340	if (ret) {
341#ifdef DEBUG_KRB5
342		fprintf(stderr, "[%5u]: smb_krb5_locator_lookup: "
343			"returning ret: %s (%d)\n",
344			(unsigned int)getpid(), error_message(ret), ret);
345#endif
346		return ret;
347	}
348
349	if (!winbind_env_set()) {
350		if (!ask_winbind(realm, &kdc_name)) {
351#ifdef DEBUG_KRB5
352			fprintf(stderr, "[%5u]: smb_krb5_locator_lookup: "
353				"failed to query winbindd\n",
354				(unsigned int)getpid());
355#endif
356			goto failed;
357		}
358	} else {
359		const char *env = NULL;
360		char *var = NULL;
361		if (asprintf(&var, "%s_%s",
362			     WINBINDD_LOCATOR_KDC_ADDRESS, realm) == -1) {
363			goto failed;
364		}
365		env = getenv(var);
366		if (!env) {
367#ifdef DEBUG_KRB5
368			fprintf(stderr, "[%5u]: smb_krb5_locator_lookup: "
369				"failed to get kdc from env %s\n",
370				(unsigned int)getpid(), var);
371#endif
372			free(var);
373			goto failed;
374		}
375		free(var);
376
377		kdc_name = strdup(env);
378		if (!kdc_name) {
379			goto failed;
380		}
381	}
382#ifdef DEBUG_KRB5
383	fprintf(stderr, "[%5u]: smb_krb5_locator_lookup: "
384		"got '%s' for '%s' from winbindd\n", (unsigned int)getpid(),
385		kdc_name, realm);
386#endif
387
388	aihints.ai_family = family;
389	aihints.ai_socktype = socktype;
390
391	ret = smb_krb5_locator_call_cbfunc(kdc_name,
392					   service,
393					   &aihints,
394					   cbfunc, cbdata);
395	SAFE_FREE(kdc_name);
396
397	return ret;
398
399 failed:
400	return KRB5_PLUGIN_NO_HANDLE;
401}
402
403#ifdef HEIMDAL_KRB5_LOCATE_PLUGIN_H
404#define SMB_KRB5_LOCATOR_SYMBOL_NAME resolve /* Heimdal */
405#else
406#define SMB_KRB5_LOCATOR_SYMBOL_NAME service_locator /* MIT */
407#endif
408
409const krb5plugin_service_locate_ftable SMB_KRB5_LOCATOR_SYMBOL_NAME = {
410	0, /* version */
411	smb_krb5_locator_init,
412	smb_krb5_locator_close,
413	smb_krb5_locator_lookup,
414};
415
416#endif
417