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