Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright 2025 GNOME Foundation, Inc.
4 : : *
5 : : * SPDX-License-Identifier: GPL-2.0-or-later
6 : : *
7 : : * This program is free software; you can redistribute it and/or modify
8 : : * it under the terms of the GNU General Public License as published by
9 : : * the Free Software Foundation; either version 2 of the License, or
10 : : * (at your option) any later version.
11 : : *
12 : : * This program is distributed in the hope that it will be useful,
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : : * GNU General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU General Public License
18 : : * along with this program; if not, see <http://www.gnu.org/licenses/>.
19 : : *
20 : : * Authors:
21 : : * - Philip Withnall <pwithnall@gnome.org>
22 : : */
23 : :
24 : : #include <arpa/inet.h>
25 : : #include <assert.h>
26 : : #include <ctype.h>
27 : : #include <errno.h>
28 : : #include <err.h>
29 : : #include <fcntl.h>
30 : : #include <limits.h>
31 : : #include <nss.h>
32 : : #include <netdb.h>
33 : : #include <pwd.h>
34 : : #include <stdbool.h>
35 : : #include <stddef.h>
36 : : #include <stdio.h>
37 : : #include <stdlib.h>
38 : : #include <string.h>
39 : : #include <sys/types.h>
40 : : #include <unistd.h>
41 : :
42 : :
43 : : /**
44 : : * NSS hardcoded test module
45 : : *
46 : : * This is an NSS module with some hardcoded `gethostbyname()` results, which we
47 : : * can use as a fallback behind the `malcontent` NSS module to test whether it
48 : : * works (and whether it’s recursing lookups and passing through lookups
49 : : * correctly).
50 : : *
51 : : * It is *not* meant to be used in production.
52 : : *
53 : : * NSS documentation:
54 : : * - https://www.gnu.org/software/libc/manual/html_node/NSS-Modules-Interface.html
55 : : * - https://www.gnu.org/software/libc/manual/html_node/NSS-Module-Function-Internals.html
56 : : * - https://elixir.bootlin.com/glibc/glibc-2.41/source/nss/getaddrinfo.c
57 : : */
58 : :
59 : : /* Exported module API: */
60 : : enum nss_status _nss_hardcoded_gethostbyname3_r (const char *name,
61 : : int af,
62 : : struct hostent *result,
63 : : char *buffer,
64 : : size_t buffer_len,
65 : : int *errnop,
66 : : int *h_errnop,
67 : : int32_t *ttlp,
68 : : char **canonp);
69 : : enum nss_status _nss_hardcoded_gethostbyname2_r (const char *name,
70 : : int af,
71 : : struct hostent *result,
72 : : char *buffer,
73 : : size_t buffer_len,
74 : : int *errnop,
75 : : int *h_errnop);
76 : :
77 : : static inline size_t
78 : 16 : align_as_pointer (size_t in)
79 : : {
80 : 16 : const size_t ptr_alignment = __alignof__ (void *);
81 : 16 : return (in + (ptr_alignment - 1)) & ~(ptr_alignment - 1);
82 : : }
83 : :
84 : : /* As per https://elixir.bootlin.com/glibc/glibc-2.41/source/nss/getaddrinfo.c,
85 : : * glibc only calls gethostbyname4_r and gethostbyname3_r conditionally. If we
86 : : * want to support the most possible queries (and versions of glibc), provide
87 : : * gethostbyname2_r. glibc will handle the fallbacks for other API versions.
88 : : *
89 : : * We do need to provide a gethostbyname3_r() function, though, as that’s
90 : : * explicitly called when AI_CANONNAME is set in the request flags. */
91 : : enum nss_status
92 : 44 : _nss_hardcoded_gethostbyname3_r (const char *name,
93 : : int af,
94 : : struct hostent *result,
95 : : char *buffer,
96 : : size_t buffer_len,
97 : : int *errnop,
98 : : int *h_errnop,
99 : : int32_t *ttlp,
100 : : char **canonp)
101 : : {
102 : 44 : const char *result_name = NULL;
103 : 44 : const char *result_addr_str = NULL;
104 : 44 : const char *result_addr6_str = NULL;
105 : 44 : struct in_addr result_addr = { .s_addr = 0 };
106 : 44 : struct in6_addr result_addr6 = { .s6_addr = { 0, } };
107 : :
108 : : /* Is the app querying for a protocol which we support? */
109 [ + + - + ]: 44 : if (af != AF_INET && af != AF_INET6)
110 : : {
111 : 0 : *errnop = EAFNOSUPPORT;
112 : 0 : *h_errnop = HOST_NOT_FOUND;
113 : 0 : return NSS_STATUS_UNAVAIL;
114 : : }
115 : :
116 : : /* Define certain well-known domains with well-known resolutions, which we can
117 : : * then use in the unit tests to check results. The actual IP addresses are
118 : : * arbitrary and meaningless, they just need to be distinct between domains.
119 : : */
120 [ + + ]: 44 : if (strcmp (name, "always-resolves.com") == 0)
121 : : {
122 : 2 : result_name = name;
123 : 2 : result_addr_str = "1.2.3.4";
124 : 2 : result_addr6_str = "2001:0DB8:AC10:FE01::";
125 : : }
126 [ + + ]: 42 : else if (strcmp (name, "never-resolves.com") == 0)
127 : : {
128 : 6 : result_name = name;
129 : 6 : result_addr_str = NULL;
130 : 6 : result_addr6_str = NULL;
131 : : }
132 [ - + ]: 36 : else if (strcmp (name, "redirects.com") == 0)
133 : : {
134 : 0 : result_name = "safe.redirects.com";
135 : 0 : result_addr_str = "1.2.3.5";
136 : 0 : result_addr6_str = "2001:0DB8:AC10:FE02::";
137 : : }
138 : :
139 [ + + + + : 44 : if (result_name != NULL && result_addr_str != NULL && result_addr6_str != NULL)
+ - ]
140 : : {
141 : 2 : size_t buffer_offset = 0;
142 [ + + ]: 2 : size_t h_length = (af == AF_INET6) ? sizeof (struct in6_addr) : sizeof (struct in_addr);
143 : :
144 : : /* Check the buffer size first. */
145 [ - + ]: 2 : if (buffer_len < align_as_pointer (strlen (result_name) + 1) + align_as_pointer (sizeof (void *)) + align_as_pointer (sizeof (void *) * 2) + align_as_pointer (h_length))
146 : : {
147 : 0 : *errnop = ERANGE;
148 : 0 : *h_errnop = NO_RECOVERY;
149 : 0 : return NSS_STATUS_TRYAGAIN;
150 : : }
151 : :
152 : : /* Build the result. Even though we never set any h_aliases, tools like
153 : : * `getent` expect a non-NULL (though potentially empty) array. */
154 [ + - - + ]: 4 : if (inet_pton (AF_INET, result_addr_str, &result_addr) != 1 ||
155 : 2 : inet_pton (AF_INET6, result_addr6_str, &result_addr6) != 1)
156 : 0 : assert (0);
157 : :
158 : 2 : strcpy (buffer, result_name);
159 : 2 : result->h_name = buffer;
160 : 2 : buffer_offset = align_as_pointer (strlen (result_name) + 1);
161 : :
162 : 2 : result->h_aliases = (char **) (buffer + buffer_offset);
163 : 2 : buffer_offset += align_as_pointer (sizeof (void *));
164 : 2 : result->h_aliases[0] = NULL;
165 : :
166 : 2 : result->h_addrtype = af;
167 : 2 : result->h_length = h_length;
168 : :
169 : 2 : result->h_addr_list = (char **) (buffer + buffer_offset);
170 : 2 : buffer_offset += align_as_pointer (sizeof (void *) * 2);
171 [ + + ]: 2 : memcpy (buffer + buffer_offset, (af == AF_INET6) ? (char *) &result_addr6 : (char *) &result_addr, result->h_length);
172 : 2 : result->h_addr_list[0] = buffer + buffer_offset;
173 : 2 : buffer_offset += align_as_pointer (result->h_length);
174 : 2 : result->h_addr_list[1] = NULL;
175 : :
176 [ - + ]: 2 : assert (buffer_offset <= buffer_len);
177 : :
178 [ - + ]: 2 : if (ttlp != NULL)
179 : 0 : *ttlp = 0;
180 [ - + ]: 2 : if (canonp != NULL)
181 : 0 : *canonp = result->h_name;
182 : :
183 : 2 : *errnop = 0;
184 : 2 : *h_errnop = 0;
185 : 2 : return NSS_STATUS_SUCCESS;
186 : : }
187 : : else
188 : : {
189 : : /* Not found in the filter list, so let another module actually resolve it. */
190 : 42 : *errnop = EINVAL;
191 : 42 : *h_errnop = NO_ADDRESS;
192 : 42 : return NSS_STATUS_NOTFOUND;
193 : : }
194 : : }
195 : :
196 : : enum nss_status
197 : 44 : _nss_hardcoded_gethostbyname2_r (const char *name,
198 : : int af,
199 : : struct hostent *result,
200 : : char *buffer,
201 : : size_t buffer_len,
202 : : int *errnop,
203 : : int *h_errnop)
204 : : {
205 : 44 : return _nss_hardcoded_gethostbyname3_r (name, af, result, buffer, buffer_len,
206 : : errnop, h_errnop, NULL, NULL);
207 : : }
|