1 /* Copyright 2009 Google Inc.
2 *
3 * This 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 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
16 * USA
17 */
18
19 /* An NSS module which adds supports for file maps with a trailing .cache
20 * suffix (/etc/passwd.cache, /etc/group.cache, and /etc/shadow.cache)
21 */
22
23 #include "nss_cache.h"
24
25 #include <sys/mman.h>
26
27 /* Locking implementation: use pthreads. */
28 #include <pthread.h>
29 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
30 #define NSS_CACHE_LOCK() \
31 do { \
32 pthread_mutex_lock(&mutex); \
33 } while (0)
34 #define NSS_CACHE_UNLOCK() \
35 do { \
36 pthread_mutex_unlock(&mutex); \
37 } while (0)
38
39 static FILE *p_file = NULL;
40 static FILE *g_file = NULL;
41 static char p_filename[NSS_CACHE_PATH_LENGTH] = "/etc/passwd.cache";
42 static char g_filename[NSS_CACHE_PATH_LENGTH] = "/etc/group.cache";
43 #ifndef BSD
44 static FILE *s_file = NULL;
45 static char s_filename[NSS_CACHE_PATH_LENGTH] = "/etc/shadow.cache";
46 #else
47 extern int fgetpwent_r(FILE *, struct passwd *, char *, size_t,
48 struct passwd **);
49 extern int fgetgrent_r(FILE *, struct group *, char *, size_t, struct group **);
50 #endif /* ifndef BSD */
51
52 /* Common return code routine for all *ent_r_locked functions.
53 * We need to return TRYAGAIN if the underlying files guy raises ERANGE,
54 * so that our caller knows to try again with a bigger buffer.
55 */
56
_nss_cache_ent_bad_return_code(int errnoval)57 static inline enum nss_status _nss_cache_ent_bad_return_code(int errnoval) {
58 enum nss_status ret;
59
60 switch (errnoval) {
61 case ERANGE:
62 DEBUG("ERANGE: Try again with a bigger buffer\n");
63 ret = NSS_STATUS_TRYAGAIN;
64 break;
65 case ENOENT:
66 default:
67 DEBUG("ENOENT or default case: Not found\n");
68 ret = NSS_STATUS_NOTFOUND;
69 };
70 return ret;
71 }
72
73 //
74 // Binary search routines below here
75 //
76
_nss_cache_bsearch2_compare(const void * key,const void * value)77 static int _nss_cache_bsearch2_compare(const void *key, const void *value) {
78 struct nss_cache_args *args = (struct nss_cache_args *)key;
79 const char *value_text = (const char *)value;
80
81 // Using strcmp as the generation of the index sorts without
82 // locale awareness.
83 return strcmp(args->lookup_key, value_text);
84 }
85
_nss_cache_bsearch2(struct nss_cache_args * args,int * errnop)86 enum nss_status _nss_cache_bsearch2(struct nss_cache_args *args, int *errnop) {
87 enum nss_cache_match (*lookup)(FILE *, struct nss_cache_args *) =
88 args->lookup_function;
89 FILE *file = NULL;
90 FILE *system_file_stream = NULL;
91 struct stat system_file;
92 struct stat sorted_file;
93 enum nss_status ret = 100;
94 long offset = 0;
95 void *mapped_data = NULL;
96
97 file = fopen(args->sorted_filename, "r");
98 if (file == NULL) {
99 DEBUG("error opening %s\n", args->sorted_filename);
100 return NSS_STATUS_UNAVAIL;
101 }
102
103 // if the sorted file is older than the system file, do not risk stale
104 // data and abort
105 // TODO(vasilios): should be a compile or runtime option
106 if (stat(args->system_filename, &system_file) != 0) {
107 DEBUG("failed to stat %s\n", args->system_filename);
108 fclose(file);
109 return NSS_STATUS_UNAVAIL;
110 }
111 if (fstat(fileno(file), &sorted_file) != 0) {
112 DEBUG("failed to stat %s\n", args->sorted_filename);
113 fclose(file);
114 return NSS_STATUS_UNAVAIL;
115 }
116 if (difftime(system_file.st_mtime, sorted_file.st_mtime) > 0) {
117 DEBUG("%s may be stale, aborting lookup\n", args->sorted_filename);
118 fclose(file);
119 return NSS_STATUS_UNAVAIL;
120 }
121
122 mapped_data =
123 mmap(NULL, sorted_file.st_size, PROT_READ, MAP_PRIVATE, fileno(file), 0);
124 if (mapped_data == MAP_FAILED) {
125 DEBUG("mmap failed\n");
126 fclose(file);
127 return NSS_STATUS_UNAVAIL;
128 }
129
130 const char *data = (const char *)mapped_data;
131 while (*data != '\n') {
132 ++data;
133 }
134 long entry_size = data - (const char *)mapped_data + 1;
135 long entry_count = sorted_file.st_size / entry_size;
136
137 void *entry = bsearch(args, mapped_data, entry_count, entry_size,
138 &_nss_cache_bsearch2_compare);
139 if (entry != NULL) {
140 const char *entry_text = entry;
141 int r = sscanf(entry_text + strlen(entry_text) + 1, "%ld", &offset);
142 if (r != 1) {
143 DEBUG("sscanf expected 1 conversion, got %d\n", r);
144 }
145 }
146
147 if (munmap(mapped_data, sorted_file.st_size) == -1) {
148 DEBUG("munmap failed\n");
149 }
150 fclose(file);
151
152 if (entry == NULL) {
153 return NSS_STATUS_NOTFOUND;
154 }
155
156 system_file_stream = fopen(args->system_filename, "r");
157 if (system_file_stream == NULL) {
158 DEBUG("error opening %s\n", args->system_filename);
159 return NSS_STATUS_UNAVAIL;
160 }
161
162 if (fseek(system_file_stream, offset, SEEK_SET) != 0) {
163 DEBUG("fseek fail\n");
164 return NSS_STATUS_UNAVAIL;
165 }
166
167 switch (lookup(system_file_stream, args)) {
168 case NSS_CACHE_EXACT:
169 ret = NSS_STATUS_SUCCESS;
170 break;
171 case NSS_CACHE_ERROR:
172 if (errno == ERANGE) {
173 // let the caller retry
174 *errnop = errno;
175 ret = _nss_cache_ent_bad_return_code(*errnop);
176 }
177 break;
178 default:
179 ret = NSS_STATUS_UNAVAIL;
180 break;
181 }
182
183 fclose(system_file_stream);
184 return ret;
185 }
186
187 //
188 // Routines for passwd map defined below here
189 //
190
191 // _nss_cache_setpwent_path()
192 // Helper function for testing
193
_nss_cache_setpwent_path(const char * path)194 extern char *_nss_cache_setpwent_path(const char *path) {
195 DEBUG("%s %s\n", "Setting p_filename to", path);
196 return strncpy(p_filename, path, NSS_CACHE_PATH_LENGTH - 1);
197 }
198
199 // _nss_cache_pwuid_wrap()
200 // Internal wrapper for binary searches, using uid-specific calls.
201
_nss_cache_pwuid_wrap(FILE * file,struct nss_cache_args * args)202 static enum nss_cache_match _nss_cache_pwuid_wrap(FILE *file,
203 struct nss_cache_args *args) {
204 struct passwd *result = args->lookup_result;
205 uid_t *uid = args->lookup_value;
206
207 if (fgetpwent_r(file, result, args->buffer, args->buflen, &result) == 0) {
208 if (result->pw_uid == *uid) {
209 DEBUG("SUCCESS: found user %d:%s\n", result->pw_uid, result->pw_name);
210 return NSS_CACHE_EXACT;
211 }
212 DEBUG("Failed match at uid %d\n", result->pw_uid);
213 if (result->pw_uid > *uid) {
214 return NSS_CACHE_HIGH;
215 } else {
216 return NSS_CACHE_LOW;
217 }
218 }
219
220 return NSS_CACHE_ERROR;
221 }
222
223 // _nss_cache_pwnam_wrap()
224 // Internal wrapper for binary searches, using username-specific calls.
225
_nss_cache_pwnam_wrap(FILE * file,struct nss_cache_args * args)226 static enum nss_cache_match _nss_cache_pwnam_wrap(FILE *file,
227 struct nss_cache_args *args) {
228 struct passwd *result = args->lookup_result;
229 char *name = args->lookup_value;
230 int ret;
231
232 if (fgetpwent_r(file, result, args->buffer, args->buflen, &result) == 0) {
233 ret = strcoll(result->pw_name, name);
234 if (ret == 0) {
235 DEBUG("SUCCESS: found user %s\n", result->pw_name);
236 return NSS_CACHE_EXACT;
237 }
238 DEBUG("Failed match at name %s\n", result->pw_name);
239 if (ret > 0) {
240 return NSS_CACHE_HIGH;
241 } else {
242 return NSS_CACHE_LOW;
243 }
244 }
245
246 return NSS_CACHE_ERROR;
247 }
248
249 // _nss_cache_setpwent_locked()
250 // Internal setup routine
251
_nss_cache_setpwent_locked(void)252 static enum nss_status _nss_cache_setpwent_locked(void) {
253 DEBUG("%s %s\n", "Opening", p_filename);
254 p_file = fopen(p_filename, "r");
255
256 if (p_file) {
257 return NSS_STATUS_SUCCESS;
258 } else {
259 return NSS_STATUS_UNAVAIL;
260 }
261 }
262
263 // _nss_cache_setpwent()
264 // Called by NSS to open the passwd file
265 // 'stayopen' parameter is ignored.
266
_nss_cache_setpwent(int stayopen)267 enum nss_status _nss_cache_setpwent(int stayopen) {
268 enum nss_status ret;
269 NSS_CACHE_LOCK();
270 ret = _nss_cache_setpwent_locked();
271 NSS_CACHE_UNLOCK();
272 return ret;
273 }
274
275 // _nss_cache_endpwent_locked()
276 // Internal close routine
277
_nss_cache_endpwent_locked(void)278 static enum nss_status _nss_cache_endpwent_locked(void) {
279 DEBUG("Closing passwd.cache\n");
280 if (p_file) {
281 fclose(p_file);
282 p_file = NULL;
283 }
284 return NSS_STATUS_SUCCESS;
285 }
286
287 // _nss_cache_endpwent()
288 // Called by NSS to close the passwd file
289
_nss_cache_endpwent(void)290 enum nss_status _nss_cache_endpwent(void) {
291 enum nss_status ret;
292 NSS_CACHE_LOCK();
293 ret = _nss_cache_endpwent_locked();
294 NSS_CACHE_UNLOCK();
295 return ret;
296 }
297
298 // _nss_cache_getpwent_r_locked()
299 // Called internally to return the next entry from the passwd file
300
_nss_cache_getpwent_r_locked(struct passwd * result,char * buffer,size_t buflen,int * errnop)301 static enum nss_status _nss_cache_getpwent_r_locked(struct passwd *result,
302 char *buffer, size_t buflen,
303 int *errnop) {
304 enum nss_status ret = NSS_STATUS_SUCCESS;
305
306 if (p_file == NULL) {
307 DEBUG("p_file == NULL, going to setpwent\n");
308 ret = _nss_cache_setpwent_locked();
309 }
310
311 if (ret == NSS_STATUS_SUCCESS) {
312 if (fgetpwent_r(p_file, result, buffer, buflen, &result) == 0) {
313 DEBUG("Returning user %d:%s\n", result->pw_uid, result->pw_name);
314 } else {
315 if (errno == ENOENT) {
316 errno = 0;
317 }
318 *errnop = errno;
319 ret = _nss_cache_ent_bad_return_code(*errnop);
320 }
321 }
322
323 return ret;
324 }
325
326 // _nss_cache_getpwent_r()
327 // Called by NSS to look up next entry in passwd file
328
_nss_cache_getpwent_r(struct passwd * result,char * buffer,size_t buflen,int * errnop)329 enum nss_status _nss_cache_getpwent_r(struct passwd *result, char *buffer,
330 size_t buflen, int *errnop) {
331 enum nss_status ret;
332 NSS_CACHE_LOCK();
333 ret = _nss_cache_getpwent_r_locked(result, buffer, buflen, errnop);
334 NSS_CACHE_UNLOCK();
335 return ret;
336 }
337
338 // _nss_cache_getpwuid_r()
339 // Find a user account by uid
340
_nss_cache_getpwuid_r(uid_t uid,struct passwd * result,char * buffer,size_t buflen,int * errnop)341 enum nss_status _nss_cache_getpwuid_r(uid_t uid, struct passwd *result,
342 char *buffer, size_t buflen,
343 int *errnop) {
344 char filename[NSS_CACHE_PATH_LENGTH];
345 struct nss_cache_args args;
346 enum nss_status ret;
347
348 strncpy(filename, p_filename, NSS_CACHE_PATH_LENGTH - 1);
349 int n = strlen(filename);
350 if (n > NSS_CACHE_PATH_LENGTH - 7) {
351 DEBUG("filename too long\n");
352 return NSS_STATUS_UNAVAIL;
353 }
354 strncat(filename, ".ixuid", NSS_CACHE_PATH_LENGTH - n - 1);
355
356 args.sorted_filename = filename;
357 args.system_filename = p_filename;
358 args.lookup_function = _nss_cache_pwuid_wrap;
359 args.lookup_value = &uid;
360 args.lookup_result = result;
361 args.buffer = buffer;
362 args.buflen = buflen;
363 char uid_text[11];
364 snprintf(uid_text, sizeof(uid_text), "%d", uid);
365 args.lookup_key = uid_text;
366 args.lookup_key_length = strlen(uid_text);
367
368 DEBUG("Binary search for uid %d\n", uid);
369 NSS_CACHE_LOCK();
370 ret = _nss_cache_bsearch2(&args, errnop);
371
372 if (ret == NSS_STATUS_UNAVAIL) {
373 DEBUG("Binary search failed, falling back to full linear search\n");
374 ret = _nss_cache_setpwent_locked();
375
376 if (ret == NSS_STATUS_SUCCESS) {
377 while ((ret = _nss_cache_getpwent_r_locked(
378 result, buffer, buflen, errnop)) == NSS_STATUS_SUCCESS) {
379 if (result->pw_uid == uid) break;
380 }
381 }
382 }
383
384 _nss_cache_endpwent_locked();
385 NSS_CACHE_UNLOCK();
386
387 return ret;
388 }
389
390 // _nss_cache_getpwnam_r()
391 // Find a user account by name
392
_nss_cache_getpwnam_r(const char * name,struct passwd * result,char * buffer,size_t buflen,int * errnop)393 enum nss_status _nss_cache_getpwnam_r(const char *name, struct passwd *result,
394 char *buffer, size_t buflen,
395 int *errnop) {
396 char *pw_name;
397 char filename[NSS_CACHE_PATH_LENGTH];
398 struct nss_cache_args args;
399 enum nss_status ret;
400
401 NSS_CACHE_LOCK();
402
403 // name is a const char, we need a non-const copy
404 pw_name = malloc(strlen(name) + 1);
405 if (pw_name == NULL) {
406 DEBUG("malloc error\n");
407 return NSS_STATUS_UNAVAIL;
408 }
409 strncpy(pw_name, name, strlen(name) + 1);
410
411 strncpy(filename, p_filename, NSS_CACHE_PATH_LENGTH - 1);
412 int n = strlen(filename);
413 if (n > NSS_CACHE_PATH_LENGTH - 8) {
414 DEBUG("filename too long\n");
415 free(pw_name);
416 return NSS_STATUS_UNAVAIL;
417 }
418 strncat(filename, ".ixname", NSS_CACHE_PATH_LENGTH - n - 1);
419
420 args.sorted_filename = filename;
421 args.system_filename = p_filename;
422 args.lookup_function = _nss_cache_pwnam_wrap;
423 args.lookup_value = pw_name;
424 args.lookup_result = result;
425 args.buffer = buffer;
426 args.buflen = buflen;
427 args.lookup_key = pw_name;
428 args.lookup_key_length = strlen(pw_name);
429
430 DEBUG("Binary search for user %s\n", pw_name);
431 ret = _nss_cache_bsearch2(&args, errnop);
432
433 if (ret == NSS_STATUS_UNAVAIL) {
434 DEBUG("Binary search failed, falling back to full linear search\n");
435 ret = _nss_cache_setpwent_locked();
436
437 if (ret == NSS_STATUS_SUCCESS) {
438 while ((ret = _nss_cache_getpwent_r_locked(
439 result, buffer, buflen, errnop)) == NSS_STATUS_SUCCESS) {
440 if (!strcmp(result->pw_name, name)) break;
441 }
442 }
443 }
444
445 free(pw_name);
446 _nss_cache_endpwent_locked();
447 NSS_CACHE_UNLOCK();
448
449 return ret;
450 }
451
452 //
453 // Routines for group map defined here.
454 //
455
456 // _nss_cache_setgrent_path()
457 // Helper function for testing
458
_nss_cache_setgrent_path(const char * path)459 extern char *_nss_cache_setgrent_path(const char *path) {
460 DEBUG("%s %s\n", "Setting g_filename to", path);
461 return strncpy(g_filename, path, NSS_CACHE_PATH_LENGTH - 1);
462 }
463
464 // _nss_cache_setgrent_locked()
465 // Internal setup routine
466
_nss_cache_setgrent_locked(void)467 static enum nss_status _nss_cache_setgrent_locked(void) {
468 DEBUG("%s %s\n", "Opening", g_filename);
469 g_file = fopen(g_filename, "r");
470
471 if (g_file) {
472 return NSS_STATUS_SUCCESS;
473 } else {
474 return NSS_STATUS_UNAVAIL;
475 }
476 }
477
478 // _nss_cache_grgid_wrap()
479 // Internal wrapper for binary searches, using gid-specific calls.
480
_nss_cache_grgid_wrap(FILE * file,struct nss_cache_args * args)481 static enum nss_cache_match _nss_cache_grgid_wrap(FILE *file,
482 struct nss_cache_args *args) {
483 struct group *result = args->lookup_result;
484 gid_t *gid = args->lookup_value;
485
486 if (fgetgrent_r(file, result, args->buffer, args->buflen, &result) == 0) {
487 if (result->gr_gid == *gid) {
488 DEBUG("SUCCESS: found group %d:%s\n", result->gr_gid, result->gr_name);
489 return NSS_CACHE_EXACT;
490 }
491 DEBUG("Failed match at gid %d\n", result->gr_gid);
492 if (result->gr_gid > *gid) {
493 return NSS_CACHE_HIGH;
494 } else {
495 return NSS_CACHE_LOW;
496 }
497 }
498
499 return NSS_CACHE_ERROR;
500 }
501
502 // _nss_cache_grnam_wrap()
503 // Internal wrapper for binary searches, using groupname-specific calls.
504
_nss_cache_grnam_wrap(FILE * file,struct nss_cache_args * args)505 static enum nss_cache_match _nss_cache_grnam_wrap(FILE *file,
506 struct nss_cache_args *args) {
507 struct group *result = args->lookup_result;
508 char *name = args->lookup_value;
509 int ret;
510
511 if (fgetgrent_r(file, result, args->buffer, args->buflen, &result) == 0) {
512 ret = strcoll(result->gr_name, name);
513 if (ret == 0) {
514 DEBUG("SUCCESS: found group %s\n", result->gr_name);
515 return NSS_CACHE_EXACT;
516 }
517 DEBUG("Failed match at name %s\n", result->gr_name);
518 if (ret > 0) {
519 return NSS_CACHE_HIGH;
520 } else {
521 return NSS_CACHE_LOW;
522 }
523 }
524
525 return NSS_CACHE_ERROR;
526 }
527
528 // _nss_cache_setgrent()
529 // Called by NSS to open the group file
530 // 'stayopen' parameter is ignored.
531
_nss_cache_setgrent(int stayopen)532 enum nss_status _nss_cache_setgrent(int stayopen) {
533 enum nss_status ret;
534 NSS_CACHE_LOCK();
535 ret = _nss_cache_setgrent_locked();
536 NSS_CACHE_UNLOCK();
537 return ret;
538 }
539
540 // _nss_cache_endgrent_locked()
541 // Internal close routine
542
_nss_cache_endgrent_locked(void)543 static enum nss_status _nss_cache_endgrent_locked(void) {
544 DEBUG("Closing group.cache\n");
545 if (g_file) {
546 fclose(g_file);
547 g_file = NULL;
548 }
549 return NSS_STATUS_SUCCESS;
550 }
551
552 // _nss_cache_endgrent()
553 // Called by NSS to close the group file
554
_nss_cache_endgrent(void)555 enum nss_status _nss_cache_endgrent(void) {
556 enum nss_status ret;
557 NSS_CACHE_LOCK();
558 ret = _nss_cache_endgrent_locked();
559 NSS_CACHE_UNLOCK();
560 return ret;
561 }
562
563 // _nss_cache_getgrent_r_locked()
564 // Called internally to return the next entry from the group file
565
_nss_cache_getgrent_r_locked(struct group * result,char * buffer,size_t buflen,int * errnop)566 static enum nss_status _nss_cache_getgrent_r_locked(struct group *result,
567 char *buffer, size_t buflen,
568 int *errnop) {
569 enum nss_status ret = NSS_STATUS_SUCCESS;
570
571 if (g_file == NULL) {
572 DEBUG("g_file == NULL, going to setgrent\n");
573 ret = _nss_cache_setgrent_locked();
574 }
575
576 if (ret == NSS_STATUS_SUCCESS) {
577 fpos_t position;
578
579 fgetpos(g_file, &position);
580 if (fgetgrent_r(g_file, result, buffer, buflen, &result) == 0) {
581 DEBUG("Returning group %s (%d)\n", result->gr_name, result->gr_gid);
582 } else {
583 /* Rewind back to where we were just before, otherwise the data read
584 * into the buffer is probably going to be lost because there's no
585 * guarantee that the caller is going to have preserved the line we
586 * just read. Note that glibc's nss/nss_files/files-XXX.c does
587 * something similar in CONCAT(_nss_files_get,ENTNAME_r) (around
588 * line 242 in glibc 2.4 sources).
589 */
590 if (errno == ENOENT) {
591 errno = 0;
592 } else {
593 fsetpos(g_file, &position);
594 }
595 *errnop = errno;
596 ret = _nss_cache_ent_bad_return_code(*errnop);
597 }
598 }
599
600 return ret;
601 }
602
603 // _nss_cache_getgrent_r()
604 // Called by NSS to look up next entry in group file
605
_nss_cache_getgrent_r(struct group * result,char * buffer,size_t buflen,int * errnop)606 enum nss_status _nss_cache_getgrent_r(struct group *result, char *buffer,
607 size_t buflen, int *errnop) {
608 enum nss_status ret;
609 NSS_CACHE_LOCK();
610 ret = _nss_cache_getgrent_r_locked(result, buffer, buflen, errnop);
611 NSS_CACHE_UNLOCK();
612 return ret;
613 }
614
615 // _nss_cache_getgrgid_r()
616 // Find a group by gid
617
_nss_cache_getgrgid_r(gid_t gid,struct group * result,char * buffer,size_t buflen,int * errnop)618 enum nss_status _nss_cache_getgrgid_r(gid_t gid, struct group *result,
619 char *buffer, size_t buflen,
620 int *errnop) {
621 char filename[NSS_CACHE_PATH_LENGTH];
622 struct nss_cache_args args;
623 enum nss_status ret;
624
625 // Since we binary search over the groups using the user provided
626 // buffer, we do not start searching before we have a buffer that
627 // is big enough to have a high chance of succeeding.
628 if (buflen < (1 << 20)) {
629 *errnop = ERANGE;
630 return NSS_STATUS_TRYAGAIN;
631 }
632
633 strncpy(filename, g_filename, NSS_CACHE_PATH_LENGTH - 1);
634 int n = strlen(filename);
635 if (n > NSS_CACHE_PATH_LENGTH - 7) {
636 DEBUG("filename too long\n");
637 return NSS_STATUS_UNAVAIL;
638 }
639 strncat(filename, ".ixgid", NSS_CACHE_PATH_LENGTH - n - 1);
640
641 args.sorted_filename = filename;
642 args.system_filename = g_filename;
643 args.lookup_function = _nss_cache_grgid_wrap;
644 args.lookup_value = &gid;
645 args.lookup_result = result;
646 args.buffer = buffer;
647 args.buflen = buflen;
648 char gid_text[11];
649 snprintf(gid_text, sizeof(gid_text), "%d", gid);
650 args.lookup_key = gid_text;
651 args.lookup_key_length = strlen(gid_text);
652
653 DEBUG("Binary search for gid %d\n", gid);
654 NSS_CACHE_LOCK();
655 ret = _nss_cache_bsearch2(&args, errnop);
656
657 if (ret == NSS_STATUS_UNAVAIL) {
658 DEBUG("Binary search failed, falling back to full linear search\n");
659 ret = _nss_cache_setgrent_locked();
660
661 if (ret == NSS_STATUS_SUCCESS) {
662 while ((ret = _nss_cache_getgrent_r_locked(
663 result, buffer, buflen, errnop)) == NSS_STATUS_SUCCESS) {
664 if (result->gr_gid == gid) break;
665 }
666 }
667 }
668
669 _nss_cache_endgrent_locked();
670 NSS_CACHE_UNLOCK();
671
672 return ret;
673 }
674
675 // _nss_cache_getgrnam_r()
676 // Find a group by name
677
_nss_cache_getgrnam_r(const char * name,struct group * result,char * buffer,size_t buflen,int * errnop)678 enum nss_status _nss_cache_getgrnam_r(const char *name, struct group *result,
679 char *buffer, size_t buflen,
680 int *errnop) {
681 char *gr_name;
682 char filename[NSS_CACHE_PATH_LENGTH];
683 struct nss_cache_args args;
684 enum nss_status ret;
685
686 NSS_CACHE_LOCK();
687
688 // name is a const char, we need a non-const copy
689 gr_name = malloc(strlen(name) + 1);
690 if (gr_name == NULL) {
691 DEBUG("malloc error\n");
692 return NSS_STATUS_UNAVAIL;
693 }
694 strncpy(gr_name, name, strlen(name) + 1);
695
696 strncpy(filename, g_filename, NSS_CACHE_PATH_LENGTH - 1);
697 int n = strlen(filename);
698 if (n > NSS_CACHE_PATH_LENGTH - 8) {
699 DEBUG("filename too long\n");
700 free(gr_name);
701 return NSS_STATUS_UNAVAIL;
702 }
703 strncat(filename, ".ixname", NSS_CACHE_PATH_LENGTH - n - 1);
704
705 args.sorted_filename = filename;
706 args.system_filename = g_filename;
707 args.lookup_function = _nss_cache_grnam_wrap;
708 args.lookup_value = gr_name;
709 args.lookup_result = result;
710 args.buffer = buffer;
711 args.buflen = buflen;
712 args.lookup_key = gr_name;
713 args.lookup_key_length = strlen(gr_name);
714
715 DEBUG("Binary search for group %s\n", gr_name);
716 ret = _nss_cache_bsearch2(&args, errnop);
717
718 if (ret == NSS_STATUS_UNAVAIL) {
719 DEBUG("Binary search failed, falling back to full linear search\n");
720 ret = _nss_cache_setgrent_locked();
721
722 if (ret == NSS_STATUS_SUCCESS) {
723 while ((ret = _nss_cache_getgrent_r_locked(
724 result, buffer, buflen, errnop)) == NSS_STATUS_SUCCESS) {
725 if (!strcmp(result->gr_name, name)) break;
726 }
727 }
728 }
729
730 free(gr_name);
731 _nss_cache_endgrent_locked();
732 NSS_CACHE_UNLOCK();
733
734 return ret;
735 }
736
737 //
738 // Routines for shadow map defined here.
739 //
740 #if defined(__linux__) && defined(__GLIBC__)
741 // This is only built on GLIBC as caching the shadow file is generally
742 // not permissable from the perspective of other libc's, so the
743 // symbols are simply unused in those environments.
744
745 // _nss_cache_setspent_path()
746 // Helper function for testing
747
_nss_cache_setspent_path(const char * path)748 extern char *_nss_cache_setspent_path(const char *path) {
749 DEBUG("%s %s\n", "Setting s_filename to", path);
750 return strncpy(s_filename, path, NSS_CACHE_PATH_LENGTH - 1);
751 }
752
753 // _nss_cache_setspent_locked()
754 // Internal setup routine
755
_nss_cache_setspent_locked(void)756 static enum nss_status _nss_cache_setspent_locked(void) {
757 DEBUG("%s %s\n", "Opening", s_filename);
758 s_file = fopen(s_filename, "r");
759
760 if (s_file) {
761 return NSS_STATUS_SUCCESS;
762 } else {
763 return NSS_STATUS_UNAVAIL;
764 }
765 }
766
767 // _nss_cache_spnam_wrap()
768 // Internal wrapper for binary searches, using shadow-specific calls.
769
_nss_cache_spnam_wrap(FILE * file,struct nss_cache_args * args)770 static enum nss_cache_match _nss_cache_spnam_wrap(FILE *file,
771 struct nss_cache_args *args) {
772 struct spwd *result = args->lookup_result;
773 char *name = args->lookup_value;
774 int ret;
775
776 if (fgetspent_r(file, result, args->buffer, args->buflen, &result) == 0) {
777 ret = strcoll(result->sp_namp, name);
778 if (ret == 0) {
779 DEBUG("SUCCESS: found user %s\n", result->sp_namp);
780 return NSS_CACHE_EXACT;
781 }
782 DEBUG("Failed match at name %s\n", result->sp_namp);
783 if (ret > 0) {
784 return NSS_CACHE_HIGH;
785 } else {
786 return NSS_CACHE_LOW;
787 }
788 }
789
790 return NSS_CACHE_ERROR;
791 }
792
793 // _nss_cache_setspent()
794 // Called by NSS to open the shadow file
795 // 'stayopen' parameter is ignored.
796
_nss_cache_setspent(int stayopen)797 enum nss_status _nss_cache_setspent(int stayopen) {
798 enum nss_status ret;
799 NSS_CACHE_LOCK();
800 ret = _nss_cache_setspent_locked();
801 NSS_CACHE_UNLOCK();
802 return ret;
803 }
804
805 // _nss_cache_endspent_locked()
806 // Internal close routine
807
_nss_cache_endspent_locked(void)808 static enum nss_status _nss_cache_endspent_locked(void) {
809 DEBUG("Closing shadow.cache\n");
810 if (s_file) {
811 fclose(s_file);
812 s_file = NULL;
813 }
814 return NSS_STATUS_SUCCESS;
815 }
816
817 // _nss_cache_endspent()
818 // Called by NSS to close the shadow file
819
_nss_cache_endspent(void)820 enum nss_status _nss_cache_endspent(void) {
821 enum nss_status ret;
822 NSS_CACHE_LOCK();
823 ret = _nss_cache_endspent_locked();
824 NSS_CACHE_UNLOCK();
825 return ret;
826 }
827
828 // _nss_cache_getspent_r_locked()
829 // Called internally to return the next entry from the shadow file
830
_nss_cache_getspent_r_locked(struct spwd * result,char * buffer,size_t buflen,int * errnop)831 static enum nss_status _nss_cache_getspent_r_locked(struct spwd *result,
832 char *buffer, size_t buflen,
833 int *errnop) {
834 enum nss_status ret = NSS_STATUS_SUCCESS;
835
836 if (s_file == NULL) {
837 DEBUG("s_file == NULL, going to setspent\n");
838 ret = _nss_cache_setspent_locked();
839 }
840
841 if (ret == NSS_STATUS_SUCCESS) {
842 if (fgetspent_r(s_file, result, buffer, buflen, &result) == 0) {
843 DEBUG("Returning shadow entry %s\n", result->sp_namp);
844 } else {
845 if (errno == ENOENT) {
846 errno = 0;
847 }
848 *errnop = errno;
849 ret = _nss_cache_ent_bad_return_code(*errnop);
850 }
851 }
852
853 return ret;
854 }
855
856 // _nss_cache_getspent_r()
857 // Called by NSS to look up next entry in the shadow file
858
_nss_cache_getspent_r(struct spwd * result,char * buffer,size_t buflen,int * errnop)859 enum nss_status _nss_cache_getspent_r(struct spwd *result, char *buffer,
860 size_t buflen, int *errnop) {
861 enum nss_status ret;
862 NSS_CACHE_LOCK();
863 ret = _nss_cache_getspent_r_locked(result, buffer, buflen, errnop);
864 NSS_CACHE_UNLOCK();
865 return ret;
866 }
867
868 // _nss_cache_getspnam_r()
869 // Find a user by name
870
_nss_cache_getspnam_r(const char * name,struct spwd * result,char * buffer,size_t buflen,int * errnop)871 enum nss_status _nss_cache_getspnam_r(const char *name, struct spwd *result,
872 char *buffer, size_t buflen,
873 int *errnop) {
874 char *sp_namp;
875 char filename[NSS_CACHE_PATH_LENGTH];
876 struct nss_cache_args args;
877 enum nss_status ret;
878
879 NSS_CACHE_LOCK();
880
881 // name is a const char, we need a non-const copy
882 sp_namp = malloc(strlen(name) + 1);
883 if (sp_namp == NULL) {
884 DEBUG("malloc error\n");
885 return NSS_STATUS_UNAVAIL;
886 }
887 strncpy(sp_namp, name, strlen(name) + 1);
888
889 strncpy(filename, s_filename, NSS_CACHE_PATH_LENGTH - 1);
890 int n = strlen(filename);
891 if (n > NSS_CACHE_PATH_LENGTH - 8) {
892 DEBUG("filename too long\n");
893 free(sp_namp);
894 return NSS_STATUS_UNAVAIL;
895 }
896 strncat(filename, ".ixname", NSS_CACHE_PATH_LENGTH - n - 1);
897
898 args.sorted_filename = filename;
899 args.system_filename = s_filename;
900 args.lookup_function = _nss_cache_spnam_wrap;
901 args.lookup_value = sp_namp;
902 args.lookup_result = result;
903 args.buffer = buffer;
904 args.buflen = buflen;
905 args.lookup_key = sp_namp;
906 args.lookup_key_length = strlen(sp_namp);
907
908 DEBUG("Binary search for user %s\n", sp_namp);
909 ret = _nss_cache_bsearch2(&args, errnop);
910
911 if (ret == NSS_STATUS_UNAVAIL) {
912 DEBUG("Binary search failed, falling back to full linear search\n");
913 ret = _nss_cache_setspent_locked();
914
915 if (ret == NSS_STATUS_SUCCESS) {
916 while ((ret = _nss_cache_getspent_r_locked(
917 result, buffer, buflen, errnop)) == NSS_STATUS_SUCCESS) {
918 if (!strcmp(result->sp_namp, name)) break;
919 }
920 }
921 }
922
923 free(sp_namp);
924 _nss_cache_endspent_locked();
925 NSS_CACHE_UNLOCK();
926
927 return ret;
928 }
929 #endif
930
931 #ifdef BSD
932 #include "bsdnss.c"
933 #endif // #if defined(__linux__) && defined(__GLIBC__)
934