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