krb5_locator: Slightly simplify code
[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 #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
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
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
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
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
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
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
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
254 static void smb_krb5_locator_close(void *private_data)
255 {
256         return;
257 }
258
259
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
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         0, /* version */
413         smb_krb5_locator_init,
414         smb_krb5_locator_close,
415         smb_krb5_locator_lookup,
416 };
417
418 #endif