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