1 | /* Copyright (C) 1997-2016 Free Software Foundation, Inc. |
2 | This file is part of the GNU C Library. |
3 | Contributed by Thorsten Kukuk <kukuk@suse.de>, 1997. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <http://www.gnu.org/licenses/>. */ |
18 | |
19 | #include <assert.h> |
20 | #include <atomic.h> |
21 | #include <ctype.h> |
22 | #include <errno.h> |
23 | #include <netdb.h> |
24 | #include <nss.h> |
25 | #include <string.h> |
26 | #include <arpa/inet.h> |
27 | #include <netinet/in.h> |
28 | #include <rpcsvc/nis.h> |
29 | #include <libc-lock.h> |
30 | |
31 | #include "nss-nisplus.h" |
32 | |
33 | __libc_lock_define_initialized (static, lock) |
34 | |
35 | static nis_result *result; |
36 | static nis_name tablename_val; |
37 | static u_long tablename_len; |
38 | |
39 | #define NISENTRYVAL(idx, col, res) \ |
40 | (NIS_RES_OBJECT (res)[idx].EN_data.en_cols.en_cols_val[col].ec_value.ec_value_val) |
41 | |
42 | #define NISENTRYLEN(idx, col, res) \ |
43 | (NIS_RES_OBJECT (res)[idx].EN_data.en_cols.en_cols_val[col].ec_value.ec_value_len) |
44 | |
45 | /* Get implementation for some internal functions. */ |
46 | #include <resolv/mapv4v6addr.h> |
47 | |
48 | |
49 | static int |
50 | _nss_nisplus_parse_hostent (nis_result *result, int af, struct hostent *host, |
51 | char *buffer, size_t buflen, int *errnop, |
52 | int flags) |
53 | { |
54 | unsigned int i; |
55 | char *first_unused = buffer; |
56 | size_t room_left = buflen; |
57 | |
58 | if (result == NULL) |
59 | return 0; |
60 | |
61 | if ((result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) |
62 | || __type_of (NIS_RES_OBJECT (result)) != NIS_ENTRY_OBJ |
63 | || strcmp (NIS_RES_OBJECT (result)[0].EN_data.en_type, "hosts_tbl" ) != 0 |
64 | || NIS_RES_OBJECT (result)[0].EN_data.en_cols.en_cols_len < 4) |
65 | return 0; |
66 | |
67 | char *data = first_unused; |
68 | |
69 | if (room_left < (af != AF_INET || (flags & AI_V4MAPPED) != 0 |
70 | ? IN6ADDRSZ : INADDRSZ)) |
71 | { |
72 | no_more_room: |
73 | *errnop = ERANGE; |
74 | return -1; |
75 | } |
76 | |
77 | /* Parse address. */ |
78 | if (af != AF_INET6 |
79 | && inet_pton (AF_INET, NISENTRYVAL (0, 2, result), data) > 0) |
80 | { |
81 | assert ((flags & AI_V4MAPPED) == 0 || af != AF_UNSPEC); |
82 | if (flags & AI_V4MAPPED) |
83 | { |
84 | map_v4v6_address (data, data); |
85 | host->h_addrtype = AF_INET6; |
86 | host->h_length = IN6ADDRSZ; |
87 | } |
88 | else |
89 | { |
90 | host->h_addrtype = AF_INET; |
91 | host->h_length = INADDRSZ; |
92 | } |
93 | } |
94 | else if (af != AF_INET |
95 | && inet_pton (AF_INET6, NISENTRYVAL (0, 2, result), data) > 0) |
96 | { |
97 | host->h_addrtype = AF_INET6; |
98 | host->h_length = IN6ADDRSZ; |
99 | } |
100 | else |
101 | /* Illegal address: ignore line. */ |
102 | return 0; |
103 | |
104 | first_unused += host->h_length; |
105 | room_left -= host->h_length; |
106 | |
107 | if (NISENTRYLEN (0, 0, result) + 1 > room_left) |
108 | goto no_more_room; |
109 | |
110 | host->h_name = first_unused; |
111 | first_unused = __stpncpy (first_unused, NISENTRYVAL (0, 0, result), |
112 | NISENTRYLEN (0, 0, result)); |
113 | *first_unused++ = '\0'; |
114 | |
115 | room_left -= NISENTRYLEN (0, 0, result) + 1; |
116 | char *line = first_unused; |
117 | |
118 | /* When this is a call to gethostbyname4_r we do not need the aliases. */ |
119 | if (af != AF_UNSPEC) |
120 | { |
121 | /* XXX Rewrite at some point to allocate the array first and then |
122 | copy the strings. It is wasteful to first concatenate the strings |
123 | to just split them again later. */ |
124 | for (i = 0; i < NIS_RES_NUMOBJ (result); ++i) |
125 | { |
126 | if (strcmp (NISENTRYVAL (i, 1, result), host->h_name) != 0) |
127 | { |
128 | if (NISENTRYLEN (i, 1, result) + 2 > room_left) |
129 | goto no_more_room; |
130 | |
131 | *first_unused++ = ' '; |
132 | first_unused = __stpncpy (first_unused, |
133 | NISENTRYVAL (i, 1, result), |
134 | NISENTRYLEN (i, 1, result)); |
135 | *first_unused = '\0'; |
136 | room_left -= NISENTRYLEN (i, 1, result) + 1; |
137 | } |
138 | } |
139 | *first_unused++ = '\0'; |
140 | } |
141 | |
142 | /* Adjust the pointer so it is aligned for |
143 | storing pointers. */ |
144 | size_t adjust = ((__alignof__ (char *) |
145 | - (first_unused - (char *) 0) % __alignof__ (char *)) |
146 | % __alignof__ (char *)); |
147 | if (room_left < adjust + 3 * sizeof (char *)) |
148 | goto no_more_room; |
149 | first_unused += adjust; |
150 | room_left -= adjust; |
151 | host->h_addr_list = (char **) first_unused; |
152 | |
153 | room_left -= 3 * sizeof (char *); |
154 | host->h_addr_list[0] = data; |
155 | host->h_addr_list[1] = NULL; |
156 | host->h_aliases = &host->h_addr_list[2]; |
157 | |
158 | /* When this is a call to gethostbyname4_r we do not need the aliases. */ |
159 | if (af != AF_UNSPEC) |
160 | { |
161 | i = 0; |
162 | while (*line != '\0') |
163 | { |
164 | /* Skip leading blanks. */ |
165 | while (isspace (*line)) |
166 | ++line; |
167 | |
168 | if (*line == '\0') |
169 | break; |
170 | |
171 | if (room_left < sizeof (char *)) |
172 | goto no_more_room; |
173 | |
174 | room_left -= sizeof (char *); |
175 | host->h_aliases[i++] = line; |
176 | |
177 | while (*line != '\0' && *line != ' ') |
178 | ++line; |
179 | |
180 | if (*line == ' ') |
181 | *line++ = '\0'; |
182 | } |
183 | |
184 | host->h_aliases[i] = NULL; |
185 | } |
186 | |
187 | return 1; |
188 | } |
189 | |
190 | |
191 | static enum nss_status |
192 | _nss_create_tablename (int *errnop) |
193 | { |
194 | if (tablename_val == NULL) |
195 | { |
196 | const char *local_dir = nis_local_directory (); |
197 | size_t local_dir_len = strlen (local_dir); |
198 | static const char prefix[] = "hosts.org_dir." ; |
199 | |
200 | char *p = malloc (sizeof (prefix) + local_dir_len); |
201 | if (p == NULL) |
202 | { |
203 | *errnop = errno; |
204 | return NSS_STATUS_TRYAGAIN; |
205 | } |
206 | |
207 | memcpy (__stpcpy (p, prefix), local_dir, local_dir_len + 1); |
208 | |
209 | tablename_len = sizeof (prefix) - 1 + local_dir_len; |
210 | |
211 | atomic_write_barrier (); |
212 | |
213 | tablename_val = p; |
214 | } |
215 | |
216 | return NSS_STATUS_SUCCESS; |
217 | } |
218 | |
219 | |
220 | enum nss_status |
221 | _nss_nisplus_sethostent (int stayopen) |
222 | { |
223 | enum nss_status status = NSS_STATUS_SUCCESS; |
224 | int err; |
225 | |
226 | __libc_lock_lock (lock); |
227 | |
228 | if (result != NULL) |
229 | { |
230 | nis_freeresult (result); |
231 | result = NULL; |
232 | } |
233 | |
234 | if (tablename_val == NULL) |
235 | status = _nss_create_tablename (&err); |
236 | |
237 | __libc_lock_unlock (lock); |
238 | |
239 | return status; |
240 | } |
241 | |
242 | |
243 | enum nss_status |
244 | _nss_nisplus_endhostent (void) |
245 | { |
246 | __libc_lock_lock (lock); |
247 | |
248 | if (result != NULL) |
249 | { |
250 | nis_freeresult (result); |
251 | result = NULL; |
252 | } |
253 | |
254 | __libc_lock_unlock (lock); |
255 | |
256 | return NSS_STATUS_SUCCESS; |
257 | } |
258 | |
259 | |
260 | static enum nss_status |
261 | internal_nisplus_gethostent_r (struct hostent *host, char *buffer, |
262 | size_t buflen, int *errnop, int *herrnop) |
263 | { |
264 | int parse_res; |
265 | |
266 | /* Get the next entry until we found a correct one. */ |
267 | do |
268 | { |
269 | nis_result *saved_res; |
270 | |
271 | if (result == NULL) |
272 | { |
273 | saved_res = NULL; |
274 | if (tablename_val == NULL) |
275 | { |
276 | enum nss_status status = _nss_create_tablename (errnop); |
277 | |
278 | if (status != NSS_STATUS_SUCCESS) |
279 | return status; |
280 | } |
281 | |
282 | result = nis_first_entry (tablename_val); |
283 | if (result == NULL) |
284 | { |
285 | *errnop = errno; |
286 | return NSS_STATUS_TRYAGAIN; |
287 | } |
288 | if (niserr2nss (result->status) != NSS_STATUS_SUCCESS) |
289 | { |
290 | enum nss_status retval = niserr2nss (result->status); |
291 | if (retval == NSS_STATUS_TRYAGAIN) |
292 | { |
293 | *herrnop = NETDB_INTERNAL; |
294 | *errnop = errno; |
295 | } |
296 | return retval; |
297 | } |
298 | |
299 | } |
300 | else |
301 | { |
302 | saved_res = result; |
303 | result = nis_next_entry (tablename_val, &result->cookie); |
304 | if (result == NULL) |
305 | { |
306 | *errnop = errno; |
307 | return NSS_STATUS_TRYAGAIN; |
308 | } |
309 | if (niserr2nss (result->status) != NSS_STATUS_SUCCESS) |
310 | { |
311 | enum nss_status retval= niserr2nss (result->status); |
312 | |
313 | nis_freeresult (result); |
314 | result = saved_res; |
315 | if (retval == NSS_STATUS_TRYAGAIN) |
316 | { |
317 | *herrnop = NETDB_INTERNAL; |
318 | *errnop = errno; |
319 | } |
320 | return retval; |
321 | } |
322 | } |
323 | |
324 | if (_res.options & RES_USE_INET6) |
325 | parse_res = _nss_nisplus_parse_hostent (result, AF_INET6, host, buffer, |
326 | buflen, errnop, AI_V4MAPPED); |
327 | else |
328 | parse_res = _nss_nisplus_parse_hostent (result, AF_INET, host, buffer, |
329 | buflen, errnop, 0); |
330 | |
331 | if (parse_res == -1) |
332 | { |
333 | nis_freeresult (result); |
334 | result = saved_res; |
335 | *herrnop = NETDB_INTERNAL; |
336 | *errnop = ERANGE; |
337 | return NSS_STATUS_TRYAGAIN; |
338 | } |
339 | if (saved_res != NULL) |
340 | nis_freeresult (saved_res); |
341 | |
342 | } while (!parse_res); |
343 | |
344 | return NSS_STATUS_SUCCESS; |
345 | } |
346 | |
347 | |
348 | enum nss_status |
349 | _nss_nisplus_gethostent_r (struct hostent *result, char *buffer, |
350 | size_t buflen, int *errnop, int *herrnop) |
351 | { |
352 | int status; |
353 | |
354 | __libc_lock_lock (lock); |
355 | |
356 | status = internal_nisplus_gethostent_r (result, buffer, buflen, errnop, |
357 | herrnop); |
358 | |
359 | __libc_lock_unlock (lock); |
360 | |
361 | return status; |
362 | } |
363 | |
364 | |
365 | static enum nss_status |
366 | get_tablename (int *herrnop) |
367 | { |
368 | __libc_lock_lock (lock); |
369 | |
370 | enum nss_status status = _nss_create_tablename (herrnop); |
371 | |
372 | __libc_lock_unlock (lock); |
373 | |
374 | if (status != NSS_STATUS_SUCCESS) |
375 | *herrnop = NETDB_INTERNAL; |
376 | |
377 | return status; |
378 | } |
379 | |
380 | |
381 | static enum nss_status |
382 | internal_gethostbyname2_r (const char *name, int af, struct hostent *host, |
383 | char *buffer, size_t buflen, int *errnop, |
384 | int *herrnop, int flags) |
385 | { |
386 | if (tablename_val == NULL) |
387 | { |
388 | enum nss_status status = get_tablename (herrnop); |
389 | if (status != NSS_STATUS_SUCCESS) |
390 | return status; |
391 | } |
392 | |
393 | if (name == NULL) |
394 | { |
395 | *errnop = EINVAL; |
396 | *herrnop = NETDB_INTERNAL; |
397 | return NSS_STATUS_NOTFOUND; |
398 | } |
399 | |
400 | char buf[strlen (name) + 10 + tablename_len]; |
401 | int olderr = errno; |
402 | |
403 | /* Search at first in the alias list, and use the correct name |
404 | for the next search. */ |
405 | snprintf (buf, sizeof (buf), "[name=%s],%s" , name, tablename_val); |
406 | nis_result *result = nis_list (buf, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL); |
407 | |
408 | if (result != NULL) |
409 | { |
410 | /* If we did not find it, try it as original name. But if the |
411 | database is correct, we should find it in the first case, too. */ |
412 | char *bufptr = buf; |
413 | size_t buflen = sizeof (buf); |
414 | |
415 | if ((result->status == NIS_SUCCESS || result->status == NIS_S_SUCCESS) |
416 | && __type_of (result->objects.objects_val) == NIS_ENTRY_OBJ |
417 | && strcmp (result->objects.objects_val->EN_data.en_type, |
418 | "hosts_tbl" ) == 0 |
419 | && result->objects.objects_val->EN_data.en_cols.en_cols_len >= 3) |
420 | { |
421 | /* We need to allocate a new buffer since there is no |
422 | guarantee the returned alias name has a length limit. */ |
423 | name = NISENTRYVAL(0, 0, result); |
424 | size_t buflen = strlen (name) + 10 + tablename_len; |
425 | bufptr = alloca (buflen); |
426 | } |
427 | |
428 | snprintf (bufptr, buflen, "[cname=%s],%s" , name, tablename_val); |
429 | |
430 | nis_freeresult (result); |
431 | result = nis_list (bufptr, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL); |
432 | } |
433 | |
434 | if (result == NULL) |
435 | { |
436 | *errnop = ENOMEM; |
437 | *herrnop = NETDB_INTERNAL; |
438 | return NSS_STATUS_TRYAGAIN; |
439 | } |
440 | |
441 | int retval = niserr2nss (result->status); |
442 | if (__glibc_unlikely (retval != NSS_STATUS_SUCCESS)) |
443 | { |
444 | if (retval == NSS_STATUS_TRYAGAIN) |
445 | { |
446 | *errnop = errno; |
447 | *herrnop = TRY_AGAIN; |
448 | } |
449 | else |
450 | { |
451 | __set_errno (olderr); |
452 | *herrnop = NETDB_INTERNAL; |
453 | } |
454 | nis_freeresult (result); |
455 | return retval; |
456 | } |
457 | |
458 | int parse_res = _nss_nisplus_parse_hostent (result, af, host, buffer, |
459 | buflen, errnop, flags); |
460 | |
461 | nis_freeresult (result); |
462 | |
463 | if (parse_res > 0) |
464 | return NSS_STATUS_SUCCESS; |
465 | |
466 | *herrnop = NETDB_INTERNAL; |
467 | if (parse_res == -1) |
468 | { |
469 | *errnop = ERANGE; |
470 | return NSS_STATUS_TRYAGAIN; |
471 | } |
472 | |
473 | __set_errno (olderr); |
474 | return NSS_STATUS_NOTFOUND; |
475 | } |
476 | |
477 | |
478 | enum nss_status |
479 | _nss_nisplus_gethostbyname2_r (const char *name, int af, struct hostent *host, |
480 | char *buffer, size_t buflen, int *errnop, |
481 | int *herrnop) |
482 | { |
483 | if (af != AF_INET && af != AF_INET6) |
484 | { |
485 | *herrnop = HOST_NOT_FOUND; |
486 | return NSS_STATUS_NOTFOUND; |
487 | } |
488 | |
489 | return internal_gethostbyname2_r (name, af, host, buffer, buflen, errnop, |
490 | herrnop, |
491 | ((_res.options & RES_USE_INET6) ? AI_V4MAPPED : 0)); |
492 | } |
493 | |
494 | |
495 | enum nss_status |
496 | _nss_nisplus_gethostbyname_r (const char *name, struct hostent *host, |
497 | char *buffer, size_t buflen, int *errnop, |
498 | int *h_errnop) |
499 | { |
500 | if (_res.options & RES_USE_INET6) |
501 | { |
502 | enum nss_status status; |
503 | |
504 | status = internal_gethostbyname2_r (name, AF_INET6, host, buffer, |
505 | buflen, errnop, h_errnop, |
506 | AI_V4MAPPED); |
507 | if (status == NSS_STATUS_SUCCESS) |
508 | return status; |
509 | } |
510 | |
511 | return internal_gethostbyname2_r (name, AF_INET, host, buffer, |
512 | buflen, errnop, h_errnop, 0); |
513 | } |
514 | |
515 | |
516 | enum nss_status |
517 | _nss_nisplus_gethostbyaddr_r (const void *addr, socklen_t addrlen, int af, |
518 | struct hostent *host, char *buffer, |
519 | size_t buflen, int *errnop, int *herrnop) |
520 | { |
521 | if (tablename_val == NULL) |
522 | { |
523 | enum nss_status status = get_tablename (herrnop); |
524 | if (status != NSS_STATUS_SUCCESS) |
525 | return status; |
526 | } |
527 | |
528 | if (addr == NULL) |
529 | return NSS_STATUS_NOTFOUND; |
530 | |
531 | char buf[24 + tablename_len]; |
532 | int retval, parse_res; |
533 | int olderr = errno; |
534 | |
535 | snprintf (buf, sizeof (buf), "[addr=%s],%s" , |
536 | inet_ntoa (*(const struct in_addr *) addr), tablename_val); |
537 | nis_result *result = nis_list (buf, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL); |
538 | |
539 | if (result == NULL) |
540 | { |
541 | __set_errno (ENOMEM); |
542 | return NSS_STATUS_TRYAGAIN; |
543 | } |
544 | |
545 | retval = niserr2nss (result->status); |
546 | if (__glibc_unlikely (retval != NSS_STATUS_SUCCESS)) |
547 | { |
548 | if (retval == NSS_STATUS_TRYAGAIN) |
549 | { |
550 | *errnop = errno; |
551 | *herrnop = NETDB_INTERNAL; |
552 | } |
553 | else |
554 | __set_errno (olderr); |
555 | nis_freeresult (result); |
556 | return retval; |
557 | } |
558 | |
559 | parse_res = _nss_nisplus_parse_hostent (result, af, host, |
560 | buffer, buflen, errnop, |
561 | ((_res.options & RES_USE_INET6) |
562 | ? AI_V4MAPPED : 0)); |
563 | nis_freeresult (result); |
564 | |
565 | if (parse_res > 0) |
566 | return NSS_STATUS_SUCCESS; |
567 | |
568 | *herrnop = NETDB_INTERNAL; |
569 | if (parse_res == -1) |
570 | { |
571 | *errnop = ERANGE; |
572 | return NSS_STATUS_TRYAGAIN; |
573 | } |
574 | |
575 | __set_errno (olderr); |
576 | return NSS_STATUS_NOTFOUND; |
577 | } |
578 | |
579 | |
580 | enum nss_status |
581 | _nss_nisplus_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat, |
582 | char *buffer, size_t buflen, int *errnop, |
583 | int *herrnop, int32_t *ttlp) |
584 | { |
585 | struct hostent host; |
586 | |
587 | enum nss_status status = internal_gethostbyname2_r (name, AF_UNSPEC, &host, |
588 | buffer, buflen, |
589 | errnop, herrnop, 0); |
590 | if (__glibc_likely (status == NSS_STATUS_SUCCESS)) |
591 | { |
592 | if (*pat == NULL) |
593 | { |
594 | uintptr_t pad = (-(uintptr_t) buffer |
595 | % __alignof__ (struct gaih_addrtuple)); |
596 | buffer += pad; |
597 | buflen = buflen > pad ? buflen - pad : 0; |
598 | |
599 | if (__glibc_unlikely (buflen < sizeof (struct gaih_addrtuple))) |
600 | { |
601 | free (result); |
602 | *errnop = ERANGE; |
603 | *herrnop = NETDB_INTERNAL; |
604 | return NSS_STATUS_TRYAGAIN; |
605 | } |
606 | } |
607 | |
608 | (*pat)->next = NULL; |
609 | (*pat)->name = host.h_name; |
610 | (*pat)->family = host.h_addrtype; |
611 | |
612 | memcpy ((*pat)->addr, host.h_addr_list[0], host.h_length); |
613 | (*pat)->scopeid = 0; |
614 | assert (host.h_addr_list[1] == NULL); |
615 | } |
616 | |
617 | return status; |
618 | } |
619 | |