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