compat-grp.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. /* Copyright (C) 1996-2026 Free Software Foundation, Inc.
  2. This file is part of the GNU C Library.
  3. The GNU C Library is free software; you can redistribute it and/or
  4. modify it under the terms of the GNU Lesser General Public
  5. License as published by the Free Software Foundation; either
  6. version 2.1 of the License, or (at your option) any later version.
  7. The GNU C Library is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  10. Lesser General Public License for more details.
  11. You should have received a copy of the GNU Lesser General Public
  12. License along with the GNU C Library; if not, see
  13. <https://www.gnu.org/licenses/>. */
  14. #include <ctype.h>
  15. #include <errno.h>
  16. #include <fcntl.h>
  17. #include <grp.h>
  18. #include <nss.h>
  19. #include <nsswitch.h>
  20. #include <stdio_ext.h>
  21. #include <string.h>
  22. #include <libc-lock.h>
  23. #include <kernel-features.h>
  24. #include <nss_files.h>
  25. NSS_DECLARE_MODULE_FUNCTIONS (compat)
  26. static nss_action_list ni;
  27. static enum nss_status (*setgrent_impl) (int stayopen);
  28. static enum nss_status (*getgrnam_r_impl) (const char *name,
  29. struct group * grp, char *buffer,
  30. size_t buflen, int *errnop);
  31. static enum nss_status (*getgrgid_r_impl) (gid_t gid, struct group * grp,
  32. char *buffer, size_t buflen,
  33. int *errnop);
  34. static enum nss_status (*getgrent_r_impl) (struct group * grp, char *buffer,
  35. size_t buflen, int *errnop);
  36. static enum nss_status (*endgrent_impl) (void);
  37. /* Get the declaration of the parser function. */
  38. #define ENTNAME grent
  39. #define STRUCTURE group
  40. #define EXTERN_PARSER
  41. #include <nss/nss_files/files-parse.c>
  42. /* Structure for remembering -group members ... */
  43. #define BLACKLIST_INITIAL_SIZE 512
  44. #define BLACKLIST_INCREMENT 256
  45. struct blacklist_t
  46. {
  47. char *data;
  48. int current;
  49. int size;
  50. };
  51. struct ent_t
  52. {
  53. bool files;
  54. enum nss_status setent_status;
  55. FILE *stream;
  56. struct blacklist_t blacklist;
  57. };
  58. typedef struct ent_t ent_t;
  59. static ent_t ext_ent = { true, NSS_STATUS_SUCCESS, NULL, { NULL, 0, 0 }};
  60. /* Protect global state against multiple changers. */
  61. __libc_lock_define_initialized (static, lock)
  62. /* Prototypes for local functions. */
  63. static void blacklist_store_name (const char *, ent_t *);
  64. static bool in_blacklist (const char *, int, ent_t *);
  65. /* Initialize the NSS interface/functions. The calling function must
  66. hold the lock. */
  67. static void
  68. init_nss_interface (void)
  69. {
  70. if (__nss_database_get (nss_database_group_compat, &ni))
  71. {
  72. setgrent_impl = __nss_lookup_function (ni, "setgrent");
  73. getgrnam_r_impl = __nss_lookup_function (ni, "getgrnam_r");
  74. getgrgid_r_impl = __nss_lookup_function (ni, "getgrgid_r");
  75. getgrent_r_impl = __nss_lookup_function (ni, "getgrent_r");
  76. endgrent_impl = __nss_lookup_function (ni, "endgrent");
  77. }
  78. }
  79. static enum nss_status
  80. internal_setgrent (ent_t *ent, int stayopen, int needent)
  81. {
  82. enum nss_status status = NSS_STATUS_SUCCESS;
  83. ent->files = true;
  84. if (ent->blacklist.data != NULL)
  85. {
  86. ent->blacklist.current = 1;
  87. ent->blacklist.data[0] = '|';
  88. ent->blacklist.data[1] = '\0';
  89. }
  90. else
  91. ent->blacklist.current = 0;
  92. if (ent->stream == NULL)
  93. {
  94. ent->stream = __nss_files_fopen ("/etc/group");
  95. if (ent->stream == NULL)
  96. status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
  97. }
  98. else
  99. rewind (ent->stream);
  100. if (needent && status == NSS_STATUS_SUCCESS && setgrent_impl)
  101. ent->setent_status = setgrent_impl (stayopen);
  102. return status;
  103. }
  104. enum nss_status
  105. _nss_compat_setgrent (int stayopen)
  106. {
  107. enum nss_status result;
  108. __libc_lock_lock (lock);
  109. if (ni == NULL)
  110. init_nss_interface ();
  111. result = internal_setgrent (&ext_ent, stayopen, 1);
  112. __libc_lock_unlock (lock);
  113. return result;
  114. }
  115. static enum nss_status __attribute_warn_unused_result__
  116. internal_endgrent (ent_t *ent)
  117. {
  118. if (ent->stream != NULL)
  119. {
  120. fclose (ent->stream);
  121. ent->stream = NULL;
  122. }
  123. if (ent->blacklist.data != NULL)
  124. {
  125. ent->blacklist.current = 1;
  126. ent->blacklist.data[0] = '|';
  127. ent->blacklist.data[1] = '\0';
  128. }
  129. else
  130. ent->blacklist.current = 0;
  131. return NSS_STATUS_SUCCESS;
  132. }
  133. /* Like internal_endgrent, but preserve errno in all cases. */
  134. static void
  135. internal_endgrent_noerror (ent_t *ent)
  136. {
  137. int saved_errno = errno;
  138. enum nss_status unused __attribute__ ((unused)) = internal_endgrent (ent);
  139. __set_errno (saved_errno);
  140. }
  141. enum nss_status
  142. _nss_compat_endgrent (void)
  143. {
  144. enum nss_status result;
  145. __libc_lock_lock (lock);
  146. if (endgrent_impl)
  147. endgrent_impl ();
  148. result = internal_endgrent (&ext_ent);
  149. __libc_lock_unlock (lock);
  150. return result;
  151. }
  152. /* get the next group from NSS (+ entry) */
  153. static enum nss_status
  154. getgrent_next_nss (struct group *result, ent_t *ent, char *buffer,
  155. size_t buflen, int *errnop)
  156. {
  157. if (!getgrent_r_impl)
  158. return NSS_STATUS_UNAVAIL;
  159. /* If the setgrent call failed, say so. */
  160. if (ent->setent_status != NSS_STATUS_SUCCESS)
  161. return ent->setent_status;
  162. do
  163. {
  164. enum nss_status status;
  165. if ((status = getgrent_r_impl (result, buffer, buflen, errnop))
  166. != NSS_STATUS_SUCCESS)
  167. return status;
  168. }
  169. while (in_blacklist (result->gr_name, strlen (result->gr_name), ent));
  170. return NSS_STATUS_SUCCESS;
  171. }
  172. /* This function handle the +group entries in /etc/group */
  173. static enum nss_status
  174. getgrnam_plusgroup (const char *name, struct group *result, ent_t *ent,
  175. char *buffer, size_t buflen, int *errnop)
  176. {
  177. if (!getgrnam_r_impl)
  178. return NSS_STATUS_UNAVAIL;
  179. enum nss_status status = getgrnam_r_impl (name, result, buffer, buflen,
  180. errnop);
  181. if (status != NSS_STATUS_SUCCESS)
  182. return status;
  183. if (in_blacklist (result->gr_name, strlen (result->gr_name), ent))
  184. return NSS_STATUS_NOTFOUND;
  185. /* We found the entry. */
  186. return NSS_STATUS_SUCCESS;
  187. }
  188. static enum nss_status
  189. getgrent_next_file (struct group *result, ent_t *ent,
  190. char *buffer, size_t buflen, int *errnop)
  191. {
  192. struct parser_data *data = (void *) buffer;
  193. while (1)
  194. {
  195. fpos_t pos;
  196. int parse_res = 0;
  197. char *p;
  198. do
  199. {
  200. /* We need at least 3 characters for one line. */
  201. if (__glibc_unlikely (buflen < 3))
  202. {
  203. erange:
  204. *errnop = ERANGE;
  205. return NSS_STATUS_TRYAGAIN;
  206. }
  207. fgetpos (ent->stream, &pos);
  208. buffer[buflen - 1] = '\xff';
  209. p = fgets_unlocked (buffer, buflen, ent->stream);
  210. if (p == NULL && feof_unlocked (ent->stream))
  211. return NSS_STATUS_NOTFOUND;
  212. if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
  213. {
  214. erange_reset:
  215. fsetpos (ent->stream, &pos);
  216. goto erange;
  217. }
  218. /* Terminate the line for any case. */
  219. buffer[buflen - 1] = '\0';
  220. /* Skip leading blanks. */
  221. while (isspace (*p))
  222. ++p;
  223. }
  224. while (*p == '\0' || *p == '#' /* Ignore empty and comment lines. */
  225. /* Parse the line. If it is invalid, loop to
  226. get the next line of the file to parse. */
  227. || !(parse_res = _nss_files_parse_grent (p, result, data, buflen,
  228. errnop)));
  229. if (__glibc_unlikely (parse_res == -1))
  230. /* The parser ran out of space. */
  231. goto erange_reset;
  232. if (result->gr_name[0] != '+' && result->gr_name[0] != '-')
  233. /* This is a real entry. */
  234. break;
  235. /* -group */
  236. if (result->gr_name[0] == '-' && result->gr_name[1] != '\0'
  237. && result->gr_name[1] != '@')
  238. {
  239. blacklist_store_name (&result->gr_name[1], ent);
  240. continue;
  241. }
  242. /* +group */
  243. if (result->gr_name[0] == '+' && result->gr_name[1] != '\0'
  244. && result->gr_name[1] != '@')
  245. {
  246. size_t len = strlen (result->gr_name);
  247. char buf[len];
  248. enum nss_status status;
  249. /* Store the group in the blacklist for the "+" at the end of
  250. /etc/group */
  251. memcpy (buf, &result->gr_name[1], len);
  252. status = getgrnam_plusgroup (&result->gr_name[1], result, ent,
  253. buffer, buflen, errnop);
  254. blacklist_store_name (buf, ent);
  255. if (status == NSS_STATUS_SUCCESS) /* We found the entry. */
  256. break;
  257. else if (status == NSS_STATUS_RETURN /* We couldn't parse the entry*/
  258. || status == NSS_STATUS_NOTFOUND) /* No group in NIS */
  259. continue;
  260. else
  261. {
  262. if (status == NSS_STATUS_TRYAGAIN)
  263. /* The parser ran out of space. */
  264. goto erange_reset;
  265. return status;
  266. }
  267. }
  268. /* +:... */
  269. if (result->gr_name[0] == '+' && result->gr_name[1] == '\0')
  270. {
  271. ent->files = false;
  272. return getgrent_next_nss (result, ent, buffer, buflen, errnop);
  273. }
  274. }
  275. return NSS_STATUS_SUCCESS;
  276. }
  277. enum nss_status
  278. _nss_compat_getgrent_r (struct group *grp, char *buffer, size_t buflen,
  279. int *errnop)
  280. {
  281. enum nss_status result = NSS_STATUS_SUCCESS;
  282. __libc_lock_lock (lock);
  283. /* Be prepared that the setgrent function was not called before. */
  284. if (ni == NULL)
  285. init_nss_interface ();
  286. if (ext_ent.stream == NULL)
  287. result = internal_setgrent (&ext_ent, 1, 1);
  288. if (result == NSS_STATUS_SUCCESS)
  289. {
  290. if (ext_ent.files)
  291. result = getgrent_next_file (grp, &ext_ent, buffer, buflen, errnop);
  292. else
  293. result = getgrent_next_nss (grp, &ext_ent, buffer, buflen, errnop);
  294. }
  295. __libc_lock_unlock (lock);
  296. return result;
  297. }
  298. /* Searches in /etc/group and the NIS/NIS+ map for a special group */
  299. static enum nss_status
  300. internal_getgrnam_r (const char *name, struct group *result, ent_t *ent,
  301. char *buffer, size_t buflen, int *errnop)
  302. {
  303. struct parser_data *data = (void *) buffer;
  304. while (1)
  305. {
  306. fpos_t pos;
  307. int parse_res = 0;
  308. char *p;
  309. do
  310. {
  311. /* We need at least 3 characters for one line. */
  312. if (__glibc_unlikely (buflen < 3))
  313. {
  314. erange:
  315. *errnop = ERANGE;
  316. return NSS_STATUS_TRYAGAIN;
  317. }
  318. fgetpos (ent->stream, &pos);
  319. buffer[buflen - 1] = '\xff';
  320. p = fgets_unlocked (buffer, buflen, ent->stream);
  321. if (p == NULL && feof_unlocked (ent->stream))
  322. return NSS_STATUS_NOTFOUND;
  323. if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
  324. {
  325. erange_reset:
  326. fsetpos (ent->stream, &pos);
  327. goto erange;
  328. }
  329. /* Terminate the line for any case. */
  330. buffer[buflen - 1] = '\0';
  331. /* Skip leading blanks. */
  332. while (isspace (*p))
  333. ++p;
  334. }
  335. while (*p == '\0' || *p == '#' /* Ignore empty and comment lines. */
  336. /* Parse the line. If it is invalid, loop to
  337. get the next line of the file to parse. */
  338. || !(parse_res = _nss_files_parse_grent (p, result, data, buflen,
  339. errnop)));
  340. if (__glibc_unlikely (parse_res == -1))
  341. /* The parser ran out of space. */
  342. goto erange_reset;
  343. /* This is a real entry. */
  344. if (result->gr_name[0] != '+' && result->gr_name[0] != '-')
  345. {
  346. if (strcmp (result->gr_name, name) == 0)
  347. return NSS_STATUS_SUCCESS;
  348. else
  349. continue;
  350. }
  351. /* -group */
  352. if (result->gr_name[0] == '-' && result->gr_name[1] != '\0')
  353. {
  354. if (strcmp (&result->gr_name[1], name) == 0)
  355. return NSS_STATUS_NOTFOUND;
  356. else
  357. continue;
  358. }
  359. /* +group */
  360. if (result->gr_name[0] == '+' && result->gr_name[1] != '\0')
  361. {
  362. if (strcmp (name, &result->gr_name[1]) == 0)
  363. {
  364. enum nss_status status;
  365. status = getgrnam_plusgroup (name, result, ent,
  366. buffer, buflen, errnop);
  367. if (status == NSS_STATUS_RETURN)
  368. /* We couldn't parse the entry */
  369. continue;
  370. else
  371. return status;
  372. }
  373. }
  374. /* +:... */
  375. if (result->gr_name[0] == '+' && result->gr_name[1] == '\0')
  376. {
  377. enum nss_status status;
  378. status = getgrnam_plusgroup (name, result, ent,
  379. buffer, buflen, errnop);
  380. if (status == NSS_STATUS_RETURN)
  381. /* We couldn't parse the entry */
  382. continue;
  383. else
  384. return status;
  385. }
  386. }
  387. return NSS_STATUS_SUCCESS;
  388. }
  389. enum nss_status
  390. _nss_compat_getgrnam_r (const char *name, struct group *grp,
  391. char *buffer, size_t buflen, int *errnop)
  392. {
  393. ent_t ent = { true, NSS_STATUS_SUCCESS, NULL, { NULL, 0, 0 }};
  394. enum nss_status result;
  395. if (name[0] == '-' || name[0] == '+')
  396. return NSS_STATUS_NOTFOUND;
  397. __libc_lock_lock (lock);
  398. if (ni == NULL)
  399. init_nss_interface ();
  400. __libc_lock_unlock (lock);
  401. result = internal_setgrent (&ent, 0, 0);
  402. if (result == NSS_STATUS_SUCCESS)
  403. result = internal_getgrnam_r (name, grp, &ent, buffer, buflen, errnop);
  404. internal_endgrent_noerror (&ent);
  405. return result;
  406. }
  407. /* Searches in /etc/group and the NIS/NIS+ map for a special group id */
  408. static enum nss_status
  409. internal_getgrgid_r (gid_t gid, struct group *result, ent_t *ent,
  410. char *buffer, size_t buflen, int *errnop)
  411. {
  412. struct parser_data *data = (void *) buffer;
  413. while (1)
  414. {
  415. fpos_t pos;
  416. int parse_res = 0;
  417. char *p;
  418. do
  419. {
  420. /* We need at least 3 characters for one line. */
  421. if (__glibc_unlikely (buflen < 3))
  422. {
  423. erange:
  424. *errnop = ERANGE;
  425. return NSS_STATUS_TRYAGAIN;
  426. }
  427. fgetpos (ent->stream, &pos);
  428. buffer[buflen - 1] = '\xff';
  429. p = fgets_unlocked (buffer, buflen, ent->stream);
  430. if (p == NULL && feof_unlocked (ent->stream))
  431. return NSS_STATUS_NOTFOUND;
  432. if (p == NULL || __builtin_expect (buffer[buflen - 1] != '\xff', 0))
  433. {
  434. erange_reset:
  435. fsetpos (ent->stream, &pos);
  436. goto erange;
  437. }
  438. /* Terminate the line for any case. */
  439. buffer[buflen - 1] = '\0';
  440. /* Skip leading blanks. */
  441. while (isspace (*p))
  442. ++p;
  443. }
  444. while (*p == '\0' || *p == '#' /* Ignore empty and comment lines. */
  445. /* Parse the line. If it is invalid, loop to
  446. get the next line of the file to parse. */
  447. || !(parse_res = _nss_files_parse_grent (p, result, data, buflen,
  448. errnop)));
  449. if (__glibc_unlikely (parse_res == -1))
  450. /* The parser ran out of space. */
  451. goto erange_reset;
  452. /* This is a real entry. */
  453. if (result->gr_name[0] != '+' && result->gr_name[0] != '-')
  454. {
  455. if (result->gr_gid == gid)
  456. return NSS_STATUS_SUCCESS;
  457. else
  458. continue;
  459. }
  460. /* -group */
  461. if (result->gr_name[0] == '-' && result->gr_name[1] != '\0')
  462. {
  463. blacklist_store_name (&result->gr_name[1], ent);
  464. continue;
  465. }
  466. /* +group */
  467. if (result->gr_name[0] == '+' && result->gr_name[1] != '\0')
  468. {
  469. /* Yes, no +1, see the memcpy call below. */
  470. size_t len = strlen (result->gr_name);
  471. char buf[len];
  472. enum nss_status status;
  473. /* Store the group in the blacklist for the "+" at the end of
  474. /etc/group */
  475. memcpy (buf, &result->gr_name[1], len);
  476. status = getgrnam_plusgroup (&result->gr_name[1], result, ent,
  477. buffer, buflen, errnop);
  478. blacklist_store_name (buf, ent);
  479. if (status == NSS_STATUS_SUCCESS && result->gr_gid == gid)
  480. break;
  481. else
  482. continue;
  483. }
  484. /* +:... */
  485. if (result->gr_name[0] == '+' && result->gr_name[1] == '\0')
  486. {
  487. if (!getgrgid_r_impl)
  488. return NSS_STATUS_UNAVAIL;
  489. enum nss_status status = getgrgid_r_impl (gid, result,
  490. buffer, buflen, errnop);
  491. if (status == NSS_STATUS_RETURN) /* We couldn't parse the entry */
  492. return NSS_STATUS_NOTFOUND;
  493. else
  494. return status;
  495. }
  496. }
  497. return NSS_STATUS_SUCCESS;
  498. }
  499. enum nss_status
  500. _nss_compat_getgrgid_r (gid_t gid, struct group *grp,
  501. char *buffer, size_t buflen, int *errnop)
  502. {
  503. ent_t ent = { true, NSS_STATUS_SUCCESS, NULL, { NULL, 0, 0 }};
  504. enum nss_status result;
  505. __libc_lock_lock (lock);
  506. if (ni == NULL)
  507. init_nss_interface ();
  508. __libc_lock_unlock (lock);
  509. result = internal_setgrent (&ent, 0, 0);
  510. if (result == NSS_STATUS_SUCCESS)
  511. result = internal_getgrgid_r (gid, grp, &ent, buffer, buflen, errnop);
  512. internal_endgrent_noerror (&ent);
  513. return result;
  514. }
  515. /* Support routines for remembering -@netgroup and -user entries.
  516. The names are stored in a single string with `|' as separator. */
  517. static void
  518. blacklist_store_name (const char *name, ent_t *ent)
  519. {
  520. int namelen = strlen (name);
  521. char *tmp;
  522. /* first call, setup cache */
  523. if (ent->blacklist.size == 0)
  524. {
  525. ent->blacklist.size = MAX (BLACKLIST_INITIAL_SIZE, 2 * namelen);
  526. ent->blacklist.data = malloc (ent->blacklist.size);
  527. if (ent->blacklist.data == NULL)
  528. return;
  529. ent->blacklist.data[0] = '|';
  530. ent->blacklist.data[1] = '\0';
  531. ent->blacklist.current = 1;
  532. }
  533. else
  534. {
  535. if (in_blacklist (name, namelen, ent))
  536. return; /* no duplicates */
  537. if (ent->blacklist.current + namelen + 1 >= ent->blacklist.size)
  538. {
  539. ent->blacklist.size += MAX (BLACKLIST_INCREMENT, 2 * namelen);
  540. tmp = realloc (ent->blacklist.data, ent->blacklist.size);
  541. if (tmp == NULL)
  542. {
  543. free (ent->blacklist.data);
  544. ent->blacklist.size = 0;
  545. return;
  546. }
  547. ent->blacklist.data = tmp;
  548. }
  549. }
  550. tmp = stpcpy (ent->blacklist.data + ent->blacklist.current, name);
  551. *tmp++ = '|';
  552. *tmp = '\0';
  553. ent->blacklist.current += namelen + 1;
  554. return;
  555. }
  556. /* Return whether ent->blacklist contains name. */
  557. static bool
  558. in_blacklist (const char *name, int namelen, ent_t *ent)
  559. {
  560. char buf[namelen + 3];
  561. char *cp;
  562. if (ent->blacklist.data == NULL)
  563. return false;
  564. buf[0] = '|';
  565. cp = stpcpy (&buf[1], name);
  566. *cp++ = '|';
  567. *cp = '\0';
  568. return strstr (ent->blacklist.data, buf) != NULL;
  569. }