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 #ifdef 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 
get_service_from_locate_service_type(enum locate_service_type svc)40 static 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
locate_service_type_name(enum locate_service_type svc)60 static 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 
socktype_name(int socktype)79 static 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 
family_name(int family)92 static 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 
smb_krb5_locator_lookup_sanity_check(enum locate_service_type svc,const char * realm,int socktype,int family)121 static 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 #if defined(HAVE_IPV6)
146 		case AF_INET6:
147 #endif
148 			break;
149 		default:
150 			return EINVAL;
151 	}
152 
153 	switch (socktype) {
154 		case SOCK_STREAM:
155 		case SOCK_DGRAM:
156 		case 0: /* Heimdal uses that */
157 			break;
158 		default:
159 			return EINVAL;
160 	}
161 
162 	return 0;
163 }
164 
165 /**
166  * Try to get addrinfo for a given host and call the krb5 callback
167  *
168  * @param name string
169  * @param service string
170  * @param in struct addrinfo hint
171  * @param cbfunc krb5 callback function
172  * @param cbdata void pointer cbdata
173  *
174  * @return krb5_error_code.
175  */
176 
smb_krb5_locator_call_cbfunc(const char * name,const char * service,struct addrinfo * in,int (* cbfunc)(void *,int,struct sockaddr *),void * cbdata)177 static krb5_error_code smb_krb5_locator_call_cbfunc(const char *name,
178 						    const char *service,
179 						    struct addrinfo *in,
180 						    int (*cbfunc)(void *, int, struct sockaddr *),
181 						    void *cbdata)
182 {
183 	struct addrinfo *out = NULL;
184 	int ret = 0;
185 	struct addrinfo *res = NULL;
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) && (count > 1)) {
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 	for (res = out; res; res = res->ai_next) {
210 		if (!res->ai_addr || res->ai_addrlen == 0) {
211 			continue;
212 		}
213 
214 		ret = cbfunc(cbdata, res->ai_socktype, res->ai_addr);
215 		if (ret) {
216 #ifdef DEBUG_KRB5
217 			fprintf(stderr, "[%5u]: smb_krb5_locator_lookup: "
218 				"failed to call callback: %s (%d)\n",
219 				(unsigned int)getpid(), error_message(ret), ret);
220 #endif
221 			break;
222 		}
223 	}
224 
225 	if (out) {
226 		freeaddrinfo(out);
227 	}
228 	return ret;
229 }
230 
231 /**
232  * PUBLIC INTERFACE: locate init
233  *
234  * @param context krb5_context
235  * @param privata_data pointer to private data pointer
236  *
237  * @return krb5_error_code.
238  */
239 
smb_krb5_locator_init(krb5_context context,void ** private_data)240 static krb5_error_code smb_krb5_locator_init(krb5_context context,
241 					     void **private_data)
242 {
243 	return 0;
244 }
245 
246 /**
247  * PUBLIC INTERFACE: close locate
248  *
249  * @param private_data pointer to private data
250  *
251  * @return void.
252  */
253 
smb_krb5_locator_close(void * private_data)254 static void smb_krb5_locator_close(void *private_data)
255 {
256 	return;
257 }
258 
259 
ask_winbind(const char * realm,char ** dcname)260 static bool ask_winbind(const char *realm, char **dcname)
261 {
262 	wbcErr wbc_status;
263 	const char *dc = NULL;
264 	struct wbcDomainControllerInfoEx *dc_info = NULL;
265 	uint32_t flags;
266 
267 	flags = WBC_LOOKUP_DC_KDC_REQUIRED |
268 		WBC_LOOKUP_DC_IS_DNS_NAME |
269 		WBC_LOOKUP_DC_RETURN_DNS_NAME;
270 
271 	wbc_status = wbcLookupDomainControllerEx(realm, NULL, NULL, flags, &dc_info);
272 
273 	if (!WBC_ERROR_IS_OK(wbc_status)) {
274 #ifdef DEBUG_KRB5
275 		fprintf(stderr,"[%5u]: smb_krb5_locator_lookup: failed with: %s\n",
276 			(unsigned int)getpid(), wbcErrorString(wbc_status));
277 #endif
278 		return false;
279 	}
280 
281 	if (!dc && dc_info->dc_unc) {
282 		dc = dc_info->dc_unc;
283 		if (dc[0] == '\\') dc++;
284 		if (dc[0] == '\\') dc++;
285 	}
286 
287 	if (!dc) {
288 		wbcFreeMemory(dc_info);
289 		return false;
290 	}
291 
292 	*dcname = strdup(dc);
293 	if (!*dcname) {
294 		wbcFreeMemory(dc_info);
295 		return false;
296 	}
297 
298 	wbcFreeMemory(dc_info);
299 	return true;
300 }
301 
302 /**
303  * PUBLIC INTERFACE: locate lookup
304  *
305  * @param private_data pointer to private data
306  * @param svc enum locate_service_type.
307  * @param realm string
308  * @param socktype integer
309  * @param family integer
310  * @param cbfunc callback function to send back entries
311  * @param cbdata void pointer to cbdata
312  *
313  * @return krb5_error_code.
314  */
315 
smb_krb5_locator_lookup(void * private_data,enum locate_service_type svc,const char * realm,int socktype,int family,int (* cbfunc)(void *,int,struct sockaddr *),void * cbdata)316 static krb5_error_code smb_krb5_locator_lookup(void *private_data,
317 					       enum locate_service_type svc,
318 					       const char *realm,
319 					       int socktype,
320 					       int family,
321 					       int (*cbfunc)(void *, int, struct sockaddr *),
322 							void *cbdata)
323 {
324 	krb5_error_code ret;
325 	struct addrinfo aihints;
326 	char *kdc_name = NULL;
327 	const char *service = get_service_from_locate_service_type(svc);
328 
329 	ZERO_STRUCT(aihints);
330 
331 #ifdef DEBUG_KRB5
332 	fprintf(stderr,"[%5u]: smb_krb5_locator_lookup: called for '%s' "
333 			"svc: '%s' (%d) "
334 			"socktype: '%s' (%d), family: '%s' (%d)\n",
335 			(unsigned int)getpid(), realm,
336 			locate_service_type_name(svc), svc,
337 			socktype_name(socktype), socktype,
338 		        family_name(family), family);
339 #endif
340 	ret = smb_krb5_locator_lookup_sanity_check(svc, realm, socktype,
341 						   family);
342 	if (ret) {
343 #ifdef DEBUG_KRB5
344 		fprintf(stderr, "[%5u]: smb_krb5_locator_lookup: "
345 			"returning ret: %s (%d)\n",
346 			(unsigned int)getpid(), error_message(ret), ret);
347 #endif
348 		return ret;
349 	}
350 
351 	if (!winbind_env_set()) {
352 		if (!ask_winbind(realm, &kdc_name)) {
353 #ifdef DEBUG_KRB5
354 			fprintf(stderr, "[%5u]: smb_krb5_locator_lookup: "
355 				"failed to query winbindd\n",
356 				(unsigned int)getpid());
357 #endif
358 			goto failed;
359 		}
360 	} else {
361 		const char *env = NULL;
362 		char *var = NULL;
363 		if (asprintf(&var, "%s_%s",
364 			     WINBINDD_LOCATOR_KDC_ADDRESS, realm) == -1) {
365 			goto failed;
366 		}
367 		env = getenv(var);
368 		if (!env) {
369 #ifdef DEBUG_KRB5
370 			fprintf(stderr, "[%5u]: smb_krb5_locator_lookup: "
371 				"failed to get kdc from env %s\n",
372 				(unsigned int)getpid(), var);
373 #endif
374 			free(var);
375 			goto failed;
376 		}
377 		free(var);
378 
379 		kdc_name = strdup(env);
380 		if (!kdc_name) {
381 			goto failed;
382 		}
383 	}
384 #ifdef DEBUG_KRB5
385 	fprintf(stderr, "[%5u]: smb_krb5_locator_lookup: "
386 		"got '%s' for '%s' from winbindd\n", (unsigned int)getpid(),
387 		kdc_name, realm);
388 #endif
389 
390 	aihints.ai_family = family;
391 	aihints.ai_socktype = socktype;
392 
393 	ret = smb_krb5_locator_call_cbfunc(kdc_name,
394 					   service,
395 					   &aihints,
396 					   cbfunc, cbdata);
397 	SAFE_FREE(kdc_name);
398 
399 	return ret;
400 
401  failed:
402 	return KRB5_PLUGIN_NO_HANDLE;
403 }
404 
405 #ifdef HEIMDAL_KRB5_LOCATE_PLUGIN_H
406 #define SMB_KRB5_LOCATOR_SYMBOL_NAME resolve /* Heimdal */
407 #else
408 #define SMB_KRB5_LOCATOR_SYMBOL_NAME service_locator /* MIT */
409 #endif
410 
411 const krb5plugin_service_locate_ftable SMB_KRB5_LOCATOR_SYMBOL_NAME = {
412 	.minor_version	= 0,
413 	.init		= smb_krb5_locator_init,
414 	.fini		= smb_krb5_locator_close,
415 #ifdef KRB5_PLUGIN_LOCATE_VERSION_2
416 	.old_lookup	= smb_krb5_locator_lookup,
417 #else
418 	.lookup	= smb_krb5_locator_lookup,
419 #endif
420 };
421 
422 #endif
423