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