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