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 <atomic.h> |
20 | #include <ctype.h> |
21 | #include <errno.h> |
22 | #include <netdb.h> |
23 | #include <nss.h> |
24 | #include <string.h> |
25 | #include <rpcsvc/nis.h> |
26 | #include <libc-lock.h> |
27 | |
28 | #include "nss-nisplus.h" |
29 | |
30 | __libc_lock_define_initialized (static, lock); |
31 | |
32 | static nis_result *result; |
33 | static nis_name tablename_val; |
34 | static u_long tablename_len; |
35 | |
36 | #define NISENTRYVAL(idx, col, res) \ |
37 | (NIS_RES_OBJECT (res)[idx].EN_data.en_cols.en_cols_val[col].ec_value.ec_value_val) |
38 | |
39 | #define NISENTRYLEN(idx, col, res) \ |
40 | (NIS_RES_OBJECT (res)[idx].EN_data.en_cols.en_cols_val[col].ec_value.ec_value_len) |
41 | |
42 | |
43 | static int |
44 | _nss_nisplus_parse_servent (nis_result *result, struct servent *serv, |
45 | char *buffer, size_t buflen, int *errnop) |
46 | { |
47 | char *first_unused = buffer; |
48 | size_t room_left = buflen; |
49 | |
50 | if (result == NULL) |
51 | return 0; |
52 | |
53 | if ((result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) |
54 | || __type_of (NIS_RES_OBJECT (result)) != NIS_ENTRY_OBJ |
55 | || strcmp (NIS_RES_OBJECT (result)->EN_data.en_type, "services_tbl" ) != 0 |
56 | || NIS_RES_OBJECT (result)->EN_data.en_cols.en_cols_len < 4) |
57 | return 0; |
58 | |
59 | if (NISENTRYLEN (0, 0, result) >= room_left) |
60 | { |
61 | no_more_room: |
62 | *errnop = ERANGE; |
63 | return -1; |
64 | } |
65 | strncpy (first_unused, NISENTRYVAL (0, 0, result), |
66 | NISENTRYLEN (0, 0, result)); |
67 | first_unused[NISENTRYLEN (0, 0, result)] = '\0'; |
68 | serv->s_name = first_unused; |
69 | size_t len = strlen (first_unused) + 1; |
70 | room_left -= len; |
71 | first_unused += len; |
72 | |
73 | if (NISENTRYLEN (0, 2, result) >= room_left) |
74 | goto no_more_room; |
75 | strncpy (first_unused, NISENTRYVAL (0, 2, result), |
76 | NISENTRYLEN (0, 2, result)); |
77 | first_unused[NISENTRYLEN (0, 2, result)] = '\0'; |
78 | serv->s_proto = first_unused; |
79 | len = strlen (first_unused) + 1; |
80 | room_left -= len; |
81 | first_unused += len; |
82 | |
83 | serv->s_port = htons (atoi (NISENTRYVAL (0, 3, result))); |
84 | |
85 | /* XXX Rewrite at some point to allocate the array first and then |
86 | copy the strings. It wasteful to first concatenate the strings |
87 | to just split them again later. */ |
88 | char *line = first_unused; |
89 | for (unsigned int i = 0; i < NIS_RES_NUMOBJ (result); ++i) |
90 | { |
91 | if (strcmp (NISENTRYVAL (i, 1, result), serv->s_name) != 0) |
92 | { |
93 | if (NISENTRYLEN (i, 1, result) + 2 > room_left) |
94 | goto no_more_room; |
95 | *first_unused++ = ' '; |
96 | first_unused = __stpncpy (first_unused, NISENTRYVAL (i, 1, result), |
97 | NISENTRYLEN (i, 1, result)); |
98 | room_left -= NISENTRYLEN (i, 1, result) + 1; |
99 | } |
100 | } |
101 | *first_unused++ = '\0'; |
102 | |
103 | /* Adjust the pointer so it is aligned for |
104 | storing pointers. */ |
105 | size_t adjust = ((__alignof__ (char *) |
106 | - (first_unused - (char *) 0) % __alignof__ (char *)) |
107 | % __alignof__ (char *)); |
108 | if (room_left < adjust + sizeof (char *)) |
109 | goto no_more_room; |
110 | first_unused += adjust; |
111 | room_left -= adjust; |
112 | serv->s_aliases = (char **) first_unused; |
113 | |
114 | /* For the terminating NULL pointer. */ |
115 | room_left -= (sizeof (char *)); |
116 | |
117 | unsigned int i = 0; |
118 | while (*line != '\0') |
119 | { |
120 | /* Skip leading blanks. */ |
121 | while (isspace (*line)) |
122 | ++line; |
123 | |
124 | if (*line == '\0') |
125 | break; |
126 | |
127 | if (room_left < sizeof (char *)) |
128 | goto no_more_room; |
129 | |
130 | room_left -= sizeof (char *); |
131 | serv->s_aliases[i++] = line; |
132 | |
133 | while (*line != '\0' && *line != ' ') |
134 | ++line; |
135 | |
136 | if (*line == ' ') |
137 | *line++ = '\0'; |
138 | } |
139 | serv->s_aliases[i] = NULL; |
140 | |
141 | return 1; |
142 | } |
143 | |
144 | |
145 | static enum nss_status |
146 | _nss_create_tablename (int *errnop) |
147 | { |
148 | if (tablename_val == NULL) |
149 | { |
150 | const char *local_dir = nis_local_directory (); |
151 | size_t local_dir_len = strlen (local_dir); |
152 | static const char prefix[] = "services.org_dir." ; |
153 | |
154 | char *p = malloc (sizeof (prefix) + local_dir_len); |
155 | if (p == NULL) |
156 | { |
157 | *errnop = errno; |
158 | return NSS_STATUS_TRYAGAIN; |
159 | } |
160 | |
161 | memcpy (__stpcpy (p, prefix), local_dir, local_dir_len + 1); |
162 | |
163 | tablename_len = sizeof (prefix) - 1 + local_dir_len; |
164 | |
165 | atomic_write_barrier (); |
166 | |
167 | tablename_val = p; |
168 | } |
169 | |
170 | return NSS_STATUS_SUCCESS; |
171 | } |
172 | |
173 | |
174 | enum nss_status |
175 | _nss_nisplus_setservent (int stayopen) |
176 | { |
177 | enum nss_status status = NSS_STATUS_SUCCESS; |
178 | int err; |
179 | |
180 | __libc_lock_lock (lock); |
181 | |
182 | if (result != NULL) |
183 | { |
184 | nis_freeresult (result); |
185 | result = NULL; |
186 | } |
187 | |
188 | if (tablename_val == NULL) |
189 | status = _nss_create_tablename (&err); |
190 | |
191 | __libc_lock_unlock (lock); |
192 | |
193 | return status; |
194 | } |
195 | |
196 | enum nss_status |
197 | _nss_nisplus_endservent (void) |
198 | { |
199 | __libc_lock_lock (lock); |
200 | |
201 | if (result != NULL) |
202 | { |
203 | nis_freeresult (result); |
204 | result = NULL; |
205 | } |
206 | |
207 | __libc_lock_unlock (lock); |
208 | |
209 | return NSS_STATUS_SUCCESS; |
210 | } |
211 | |
212 | static enum nss_status |
213 | internal_nisplus_getservent_r (struct servent *serv, char *buffer, |
214 | size_t buflen, int *errnop) |
215 | { |
216 | int parse_res; |
217 | |
218 | /* Get the next entry until we found a correct one. */ |
219 | do |
220 | { |
221 | nis_result *saved_res; |
222 | |
223 | if (result == NULL) |
224 | { |
225 | saved_res = NULL; |
226 | if (tablename_val == NULL) |
227 | { |
228 | enum nss_status status = _nss_create_tablename (errnop); |
229 | |
230 | if (status != NSS_STATUS_SUCCESS) |
231 | return status; |
232 | } |
233 | |
234 | result = nis_first_entry (tablename_val); |
235 | if (result == NULL) |
236 | { |
237 | *errnop = errno; |
238 | return NSS_STATUS_TRYAGAIN; |
239 | } |
240 | if (niserr2nss (result->status) != NSS_STATUS_SUCCESS) |
241 | return niserr2nss (result->status); |
242 | } |
243 | else |
244 | { |
245 | saved_res = result; |
246 | result = nis_next_entry (tablename_val, &result->cookie); |
247 | if (result == NULL) |
248 | { |
249 | *errnop = errno; |
250 | return NSS_STATUS_TRYAGAIN; |
251 | } |
252 | if (niserr2nss (result->status) != NSS_STATUS_SUCCESS) |
253 | { |
254 | nis_freeresult (saved_res); |
255 | return niserr2nss (result->status); |
256 | } |
257 | } |
258 | |
259 | parse_res = _nss_nisplus_parse_servent (result, serv, buffer, |
260 | buflen, errnop); |
261 | if (__glibc_unlikely (parse_res == -1)) |
262 | { |
263 | nis_freeresult (result); |
264 | result = saved_res; |
265 | *errnop = ERANGE; |
266 | return NSS_STATUS_TRYAGAIN; |
267 | } |
268 | else |
269 | { |
270 | if (saved_res) |
271 | nis_freeresult (saved_res); |
272 | } |
273 | } |
274 | while (!parse_res); |
275 | |
276 | return NSS_STATUS_SUCCESS; |
277 | } |
278 | |
279 | enum nss_status |
280 | _nss_nisplus_getservent_r (struct servent *result, char *buffer, |
281 | size_t buflen, int *errnop) |
282 | { |
283 | __libc_lock_lock (lock); |
284 | |
285 | int status = internal_nisplus_getservent_r (result, buffer, buflen, errnop); |
286 | |
287 | __libc_lock_unlock (lock); |
288 | |
289 | return status; |
290 | } |
291 | |
292 | enum nss_status |
293 | _nss_nisplus_getservbyname_r (const char *name, const char *protocol, |
294 | struct servent *serv, |
295 | char *buffer, size_t buflen, int *errnop) |
296 | { |
297 | if (tablename_val == NULL) |
298 | { |
299 | __libc_lock_lock (lock); |
300 | |
301 | enum nss_status status = _nss_create_tablename (errnop); |
302 | |
303 | __libc_lock_unlock (lock); |
304 | |
305 | if (status != NSS_STATUS_SUCCESS) |
306 | return status; |
307 | } |
308 | |
309 | if (name == NULL || protocol == NULL) |
310 | { |
311 | *errnop = EINVAL; |
312 | return NSS_STATUS_NOTFOUND; |
313 | } |
314 | |
315 | size_t protocol_len = strlen (protocol); |
316 | char buf[strlen (name) + protocol_len + 17 + tablename_len]; |
317 | int olderr = errno; |
318 | |
319 | /* Search at first in the alias list, and use the correct name |
320 | for the next search */ |
321 | snprintf (buf, sizeof (buf), "[name=%s,proto=%s],%s" , name, protocol, |
322 | tablename_val); |
323 | nis_result *result = nis_list (buf, FOLLOW_PATH | FOLLOW_LINKS | USE_DGRAM, |
324 | NULL, NULL); |
325 | |
326 | if (result != NULL) |
327 | { |
328 | char *bufptr = buf; |
329 | |
330 | /* If we did not find it, try it as original name. But if the |
331 | database is correct, we should find it in the first case, too */ |
332 | if ((result->status != NIS_SUCCESS |
333 | && result->status != NIS_S_SUCCESS) |
334 | || __type_of (NIS_RES_OBJECT (result)) != NIS_ENTRY_OBJ |
335 | || strcmp (NIS_RES_OBJECT (result)->EN_data.en_type, |
336 | "services_tbl" ) != 0 |
337 | || NIS_RES_OBJECT (result)->EN_data.en_cols.en_cols_len < 4) |
338 | snprintf (buf, sizeof (buf), "[cname=%s,proto=%s],%s" , name, protocol, |
339 | tablename_val); |
340 | else |
341 | { |
342 | /* We need to allocate a new buffer since there is no |
343 | guarantee the returned name has a length limit. */ |
344 | const char *entryval = NISENTRYVAL(0, 0, result); |
345 | size_t buflen = (strlen (entryval) + protocol_len + 17 |
346 | + tablename_len); |
347 | bufptr = alloca (buflen); |
348 | snprintf (bufptr, buflen, "[cname=%s,proto=%s],%s" , |
349 | entryval, protocol, tablename_val); |
350 | } |
351 | |
352 | nis_freeresult (result); |
353 | result = nis_list (bufptr, FOLLOW_PATH | FOLLOW_LINKS | USE_DGRAM, |
354 | NULL, NULL); |
355 | } |
356 | |
357 | if (result == NULL) |
358 | { |
359 | *errnop = ENOMEM; |
360 | return NSS_STATUS_TRYAGAIN; |
361 | } |
362 | |
363 | if (__glibc_unlikely (niserr2nss (result->status) != NSS_STATUS_SUCCESS)) |
364 | { |
365 | enum nss_status status = niserr2nss (result->status); |
366 | |
367 | __set_errno (olderr); |
368 | |
369 | nis_freeresult (result); |
370 | return status; |
371 | } |
372 | |
373 | int parse_res = _nss_nisplus_parse_servent (result, serv, buffer, buflen, |
374 | errnop); |
375 | nis_freeresult (result); |
376 | |
377 | if (__glibc_unlikely (parse_res < 1)) |
378 | { |
379 | if (parse_res == -1) |
380 | { |
381 | *errnop = ERANGE; |
382 | return NSS_STATUS_TRYAGAIN; |
383 | } |
384 | else |
385 | { |
386 | __set_errno (olderr); |
387 | return NSS_STATUS_NOTFOUND; |
388 | } |
389 | } |
390 | |
391 | return NSS_STATUS_SUCCESS; |
392 | } |
393 | |
394 | enum nss_status |
395 | _nss_nisplus_getservbyport_r (const int number, const char *protocol, |
396 | struct servent *serv, |
397 | char *buffer, size_t buflen, int *errnop) |
398 | { |
399 | if (tablename_val == NULL) |
400 | { |
401 | __libc_lock_lock (lock); |
402 | |
403 | enum nss_status status = _nss_create_tablename (errnop); |
404 | |
405 | __libc_lock_unlock (lock); |
406 | |
407 | if (status != NSS_STATUS_SUCCESS) |
408 | return status; |
409 | } |
410 | |
411 | if (protocol == NULL) |
412 | { |
413 | *errnop = EINVAL; |
414 | return NSS_STATUS_NOTFOUND; |
415 | } |
416 | |
417 | char buf[17 + 3 * sizeof (int) + strlen (protocol) + tablename_len]; |
418 | int olderr = errno; |
419 | |
420 | snprintf (buf, sizeof (buf), "[port=%d,proto=%s],%s" , |
421 | number, protocol, tablename_val); |
422 | |
423 | nis_result *result = nis_list (buf, FOLLOW_PATH | FOLLOW_LINKS | USE_DGRAM, |
424 | NULL, NULL); |
425 | |
426 | if (result == NULL) |
427 | { |
428 | *errnop = ENOMEM; |
429 | return NSS_STATUS_TRYAGAIN; |
430 | } |
431 | |
432 | if (__glibc_unlikely (niserr2nss (result->status) != NSS_STATUS_SUCCESS)) |
433 | { |
434 | enum nss_status status = niserr2nss (result->status); |
435 | |
436 | __set_errno (olderr); |
437 | |
438 | nis_freeresult (result); |
439 | return status; |
440 | } |
441 | |
442 | int parse_res = _nss_nisplus_parse_servent (result, serv, buffer, buflen, |
443 | errnop); |
444 | nis_freeresult (result); |
445 | |
446 | if (__glibc_unlikely (parse_res < 1)) |
447 | { |
448 | if (parse_res == -1) |
449 | { |
450 | *errnop = ERANGE; |
451 | return NSS_STATUS_TRYAGAIN; |
452 | } |
453 | else |
454 | { |
455 | __set_errno (olderr); |
456 | return NSS_STATUS_NOTFOUND; |
457 | } |
458 | } |
459 | |
460 | return NSS_STATUS_SUCCESS; |
461 | } |
462 | |