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