4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
26 * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
27 * Copyright (c) 2017, Joyent, Inc.
31 * Common code and structures used by name-service-switch "files" backends.
35 * An implementation that used mmap() sensibly would be a wonderful thing,
36 * but this here is just yer standard fgets() thang.
39 #include "files_common.h"
52 _nss_files_setent(be
, dummy
)
53 files_backend_ptr_t be
;
57 if (be
->filename
== 0) {
58 /* Backend isn't initialized properly? */
61 if ((be
->f
= fopen(be
->filename
, "rF")) == 0) {
72 _nss_files_endent(be
, dummy
)
73 files_backend_ptr_t be
;
88 * This routine reads a line, including the processing of continuation
89 * characters. It always leaves (or inserts) \n\0 at the end of the line.
90 * It returns the length of the line read, excluding the \n\0. Who's idea
94 * Note that since each concurrent call to _nss_files_read_line has
95 * it's own FILE pointer, we can use getc_unlocked w/o difficulties,
96 * a substantial performance win.
99 _nss_files_read_line(f
, buffer
, buflen
)
104 int linelen
; /* 1st unused slot in buffer */
110 while (linelen
< buflen
- 1) { /* "- 1" saves room for \n\0 */
111 switch (c
= getc_unlocked(f
)) {
114 buffer
[linelen
- 1] == '\\') {
117 buffer
[linelen
] = '\n';
118 buffer
[linelen
+ 1] = '\0';
123 buffer
[linelen
- 1] == '\\') {
124 --linelen
; /* remove the '\\' */
126 buffer
[linelen
] = '\n';
127 buffer
[linelen
+ 1] = '\0';
132 buffer
[linelen
++] = c
;
135 /* Buffer overflow -- eat rest of line and loop again */
136 /* ===> Should syslog() */
138 c
= getc_unlocked(f
);
148 * used only for getgroupbymem() now.
151 _nss_files_do_all(be
, args
, filter
, func
)
152 files_backend_ptr_t be
;
155 files_do_all_func_t func
;
163 if ((grlen
= sysconf(_SC_GETGR_R_SIZE_MAX
)) > 0)
165 if ((be
->buf
= malloc(be
->minbuf
)) == 0)
166 return (NSS_UNAVAIL
);
171 if ((res
= _nss_files_setent(be
, 0)) != NSS_SUCCESS
) {
180 if ((linelen
= _nss_files_read_line(be
->f
, buffer
,
185 if (filter
!= 0 && strstr(buffer
, filter
) == 0) {
187 * Optimization: if the entry doesn't contain the
188 * filter string then it can't be the entry we want,
189 * so don't bother looking more closely at it.
193 res
= (*func
)(buffer
, linelen
, args
);
195 } while (res
== NSS_NOTFOUND
);
197 (void) _nss_files_endent(be
, 0);
202 * Could implement this as an iterator function on top of _nss_files_do_all(),
203 * but the shared code is small enough that it'd be pretty silly.
206 _nss_files_XY_all(be
, args
, netdb
, filter
, check
)
207 files_backend_ptr_t be
;
208 nss_XbyY_args_t
*args
;
209 int netdb
; /* whether it uses netdb */
211 const char *filter
; /* advisory, to speed up */
213 files_XY_check_func check
; /* NULL means one-shot, for getXXent */
220 if (filter
!= NULL
&& *filter
== '\0')
221 return (NSS_NOTFOUND
);
222 if (be
->buf
== 0 || (be
->minbuf
< args
->buf
.buflen
)) {
223 if (be
->minbuf
< args
->buf
.buflen
) {
225 be
->minbuf
= args
->buf
.buflen
;
227 (r
= realloc(be
->buf
, args
->buf
.buflen
)) != NULL
) {
229 be
->minbuf
= args
->buf
.buflen
;
233 (be
->buf
= malloc(be
->minbuf
)) == 0)
234 return (NSS_UNAVAIL
);
237 if (check
!= 0 || be
->f
== 0) {
238 if ((res
= _nss_files_setent(be
, 0)) != NSS_SUCCESS
) {
247 char *instr
= be
->buf
;
250 if ((linelen
= _nss_files_read_line(be
->f
, instr
,
257 if (filter
!= 0 && strstr(instr
, filter
) == 0) {
259 * Optimization: if the entry doesn't contain the
260 * filter string then it can't be the entry we want,
261 * so don't bother looking more closely at it.
269 if ((last
= strchr(instr
, '#')) == 0) {
270 last
= instr
+ linelen
;
272 *last
-- = '\0'; /* Nuke '\n' or #comment */
275 * Skip leading whitespace. Normally there isn't
276 * any, so it's not worth calling strspn().
278 for (first
= instr
; isspace(*first
); first
++) {
281 if (*first
== '\0') {
285 * Found something non-blank on the line. Skip back
286 * over any trailing whitespace; since we know
287 * there's non-whitespace earlier in the line,
288 * checking for termination is easy.
290 while (isspace(*last
)) {
294 linelen
= last
- first
+ 1;
295 if (first
!= instr
) {
303 if (check
!= NULL
&& (*check
)(args
, instr
, linelen
) == 0)
306 parsestat
= NSS_STR_PARSE_SUCCESS
;
307 if (be
->filename
!= NULL
) {
309 * Special case for passwd and group wherein we
310 * replace uids/gids > MAXUID by ID_NOBODY
311 * because files backend does not support
314 if (strcmp(be
->filename
, PF_PATH
) == 0)
315 parsestat
= validate_passwd_ids(instr
,
316 &linelen
, be
->minbuf
, 2);
317 else if (strcmp(be
->filename
, GF_PATH
) == 0)
318 parsestat
= validate_group_ids(instr
,
319 &linelen
, be
->minbuf
, 2, check
);
322 if (parsestat
== NSS_STR_PARSE_SUCCESS
) {
323 func
= args
->str2ent
;
324 parsestat
= (*func
)(instr
, linelen
, args
->buf
.result
,
325 args
->buf
.buffer
, args
->buf
.buflen
);
328 if (parsestat
== NSS_STR_PARSE_SUCCESS
) {
329 args
->returnval
= (args
->buf
.result
!= NULL
)?
330 args
->buf
.result
: args
->buf
.buffer
;
331 args
->returnlen
= linelen
;
334 } else if (parsestat
== NSS_STR_PARSE_ERANGE
) {
337 } /* else if (parsestat == NSS_STR_PARSE_PARSE) don't care ! */
341 * stayopen is set to 0 by default in order to close the opened
342 * file. Some applications may break if it is set to 1.
344 if (check
!= 0 && !args
->stayopen
) {
345 (void) _nss_files_endent(be
, 0);
352 * File hashing support. Critical for sites with large (e.g. 1000+ lines)
353 * /etc/passwd or /etc/group files. Currently only used by getpw*() and
354 * getgr*() routines, but any files backend can use this stuff.
357 _nss_files_hash_destroy(files_hash_t
*fhp
)
360 fhp
->fh_table
= NULL
;
363 free(fhp
->fh_file_start
);
364 fhp
->fh_file_start
= NULL
;
368 * It turns out the hashing stuff really needs to be disabled for processes
369 * other than the nscd; the consumption of swap space and memory is otherwise
370 * unacceptable when the nscd is killed w/ a large passwd file (4M) active.
371 * See 4031930 for details.
372 * So we just use this psuedo function to enable the hashing feature. Since
373 * this function name is private, we just create a function w/ the name
374 * __nss_use_files_hash in the nscd itself and everyone else uses the old
376 * We also disable hashing for .a executables to avoid problems with large
380 #pragma weak __nss_use_files_hash
382 extern void __nss_use_files_hash(void);
387 _nss_files_XY_hash(files_backend_ptr_t be
, nss_XbyY_args_t
*args
,
388 int netdb
, files_hash_t
*fhp
, int hashop
, files_XY_check_func check
)
390 /* LINTED E_FUNC_VAR_UNUSED */
391 int fd
, retries
, ht
, stat
;
392 /* LINTED E_FUNC_VAR_UNUSED */
393 uint_t hash
, line
, f
;
394 /* LINTED E_FUNC_VAR_UNUSED */
395 files_hashent_t
*hp
, *htab
;
396 /* LINTED E_FUNC_VAR_UNUSED */
397 char *cp
, *first
, *last
;
398 /* LINTED E_FUNC_VAR_UNUSED */
399 nss_XbyY_args_t xargs
;
400 /* LINTED E_FUNC_VAR_UNUSED */
404 return (_nss_files_XY_all(be
, args
, netdb
, 0, check
));
407 if (__nss_use_files_hash
== 0)
408 return (_nss_files_XY_all(be
, args
, netdb
, 0, check
));
410 mutex_lock(&fhp
->fh_lock
);
413 while (stat64(be
->filename
, &st
) < 0) {
415 * This can happen only in two cases: Either the file is
416 * completely missing and we were not able to read it yet
417 * (fh_table is NULL), or there is some brief period when the
418 * file is being modified/renamed. Keep trying until things
419 * settle down, but eventually give up.
421 if (fhp
->fh_table
== NULL
|| --retries
== 0)
426 if (st
.st_mtim
.tv_sec
== fhp
->fh_mtime
.tv_sec
&&
427 st
.st_mtim
.tv_nsec
== fhp
->fh_mtime
.tv_nsec
&&
428 fhp
->fh_table
!= NULL
) {
429 htab
= &fhp
->fh_table
[hashop
* fhp
->fh_size
];
430 hash
= fhp
->fh_hash_func
[hashop
](args
, 1, NULL
, 0);
431 for (hp
= htab
[hash
% fhp
->fh_size
].h_first
; hp
!= NULL
;
433 if (hp
->h_hash
!= hash
)
436 if ((*check
)(args
, fhp
->fh_line
[line
].l_start
,
437 fhp
->fh_line
[line
].l_len
) == 0)
440 if (be
->filename
!= NULL
) {
441 stat
= NSS_STR_PARSE_SUCCESS
;
442 if (strcmp(be
->filename
, PF_PATH
) == 0)
443 stat
= validate_passwd_ids(
444 fhp
->fh_line
[line
].l_start
,
445 &fhp
->fh_line
[line
].l_len
,
446 fhp
->fh_line
[line
].l_len
+ 1,
448 else if (strcmp(be
->filename
, GF_PATH
) == 0)
449 stat
= validate_group_ids(
450 fhp
->fh_line
[line
].l_start
,
451 &fhp
->fh_line
[line
].l_len
,
452 fhp
->fh_line
[line
].l_len
+ 1,
454 if (stat
!= NSS_STR_PARSE_SUCCESS
) {
455 if (stat
== NSS_STR_PARSE_ERANGE
)
461 if ((*args
->str2ent
)(fhp
->fh_line
[line
].l_start
,
462 fhp
->fh_line
[line
].l_len
, args
->buf
.result
,
463 args
->buf
.buffer
, args
->buf
.buflen
) ==
464 NSS_STR_PARSE_SUCCESS
) {
465 args
->returnval
= (args
->buf
.result
)?
466 args
->buf
.result
:args
->buf
.buffer
;
467 args
->returnlen
= fhp
->fh_line
[line
].l_len
;
468 mutex_unlock(&fhp
->fh_lock
);
469 return (NSS_SUCCESS
);
476 mutex_unlock(&fhp
->fh_lock
);
477 return (NSS_NOTFOUND
);
480 _nss_files_hash_destroy(fhp
);
482 if (st
.st_size
> SSIZE_MAX
)
485 if ((fhp
->fh_file_start
= malloc((ssize_t
)st
.st_size
+ 1)) == NULL
)
488 if ((fd
= open(be
->filename
, O_RDONLY
)) < 0)
491 if (read(fd
, fhp
->fh_file_start
, (ssize_t
)st
.st_size
) !=
492 (ssize_t
)st
.st_size
) {
499 fhp
->fh_file_end
= fhp
->fh_file_start
+ (off_t
)st
.st_size
;
500 *fhp
->fh_file_end
= '\n';
501 fhp
->fh_mtime
= st
.st_mtim
;
504 * If the file changed since we read it, or if it's less than
505 * 1-2 seconds old, don't trust it; its modification may still
506 * be in progress. The latter is a heuristic hack to minimize
507 * the likelihood of damage if someone modifies /etc/mumble
508 * directly (as opposed to editing and renaming a temp file).
510 * Note: the cast to u_int is there in case (1) someone rdated
511 * the system backwards since the last modification of /etc/mumble
512 * or (2) this is a diskless client whose time is badly out of sync
513 * with its server. The 1-2 second age hack doesn't cover these
516 if (stat64(be
->filename
, &st
) < 0 ||
517 st
.st_mtim
.tv_sec
!= fhp
->fh_mtime
.tv_sec
||
518 st
.st_mtim
.tv_nsec
!= fhp
->fh_mtime
.tv_nsec
||
519 (uint_t
)(time(0) - st
.st_mtim
.tv_sec
+ 2) < 4) {
525 for (cp
= fhp
->fh_file_start
; cp
< fhp
->fh_file_end
; cp
++)
529 for (f
= 2; f
* f
<= line
; f
++) { /* find next largest prime */
537 fhp
->fh_line
= malloc(line
* sizeof (files_linetab_t
));
538 fhp
->fh_table
= calloc(line
* fhp
->fh_nhtab
, sizeof (files_hashent_t
));
539 if (fhp
->fh_line
== NULL
|| fhp
->fh_table
== NULL
)
543 cp
= fhp
->fh_file_start
;
544 while (cp
< fhp
->fh_file_end
) {
548 if (cp
> first
&& *(cp
- 1) == '\\') {
549 memmove(first
+ 2, first
, cp
- first
- 1);
556 if ((last
= strchr(first
, '#')) == 0)
558 *last
-- = '\0'; /* nuke '\n' or #comment */
559 while (isspace(*first
)) /* nuke leading whitespace */
561 if (*first
== '\0') /* skip content-free lines */
563 while (isspace(*last
)) /* nuke trailing whitespace */
567 for (ht
= 0; ht
< fhp
->fh_nhtab
; ht
++) {
568 hp
= &fhp
->fh_table
[ht
* fhp
->fh_size
+ line
];
569 hp
->h_hash
= fhp
->fh_hash_func
[ht
](&xargs
, 0, first
,
572 fhp
->fh_line
[line
].l_start
= first
;
573 fhp
->fh_line
[line
++].l_len
= last
- first
;
577 * Populate the hash tables in reverse order so that the hash chains
578 * end up in forward order. This ensures that hashed lookups find
579 * things in the same order that a linear search of the file would.
580 * This is essential in cases where there could be multiple matches.
581 * For example: until 2.7, root and smtp both had uid 0; but we
582 * certainly wouldn't want getpwuid(0) to return smtp.
584 for (ht
= 0; ht
< fhp
->fh_nhtab
; ht
++) {
585 htab
= &fhp
->fh_table
[ht
* fhp
->fh_size
];
586 for (hp
= &htab
[line
- 1]; hp
>= htab
; hp
--) {
587 uint_t bucket
= hp
->h_hash
% fhp
->fh_size
;
588 hp
->h_next
= htab
[bucket
].h_first
;
589 htab
[bucket
].h_first
= hp
;
596 _nss_files_hash_destroy(fhp
);
597 mutex_unlock(&fhp
->fh_lock
);
598 return (NSS_UNAVAIL
);
603 _nss_files_getent_rigid(be
, a
)
604 files_backend_ptr_t be
;
607 nss_XbyY_args_t
*args
= (nss_XbyY_args_t
*)a
;
609 return (_nss_files_XY_all(be
, args
, 0, 0, 0));
613 _nss_files_getent_netdb(be
, a
)
614 files_backend_ptr_t be
;
617 nss_XbyY_args_t
*args
= (nss_XbyY_args_t
*)a
;
619 return (_nss_files_XY_all(be
, args
, 1, 0, 0));
624 _nss_files_destr(be
, dummy
)
625 files_backend_ptr_t be
;
630 (void) _nss_files_endent(be
, 0);
632 if (be
->hashinfo
!= NULL
) {
633 (void) mutex_lock(&be
->hashinfo
->fh_lock
);
634 if (--be
->hashinfo
->fh_refcnt
== 0)
635 _nss_files_hash_destroy(be
->hashinfo
);
636 (void) mutex_unlock(&be
->hashinfo
->fh_lock
);
640 return (NSS_SUCCESS
); /* In case anyone is dumb enough to check */
644 _nss_files_constr(ops
, n_ops
, filename
, min_bufsize
, fhp
)
645 files_backend_op_t ops
[];
647 const char *filename
;
651 files_backend_ptr_t be
;
653 if ((be
= (files_backend_ptr_t
)malloc(sizeof (*be
))) == 0) {
658 be
->filename
= filename
;
659 be
->minbuf
= min_bufsize
;
665 (void) mutex_lock(&fhp
->fh_lock
);
667 (void) mutex_unlock(&fhp
->fh_lock
);
670 return ((nss_backend_t
*)be
);
674 _nss_files_check_name_colon(nss_XbyY_args_t
*argp
, const char *line
,
677 const char *linep
, *limit
;
678 const char *keyp
= argp
->key
.name
;
681 limit
= line
+ linelen
;
682 while (*keyp
&& linep
< limit
&& *keyp
== *linep
) {
686 return (linep
< limit
&& *keyp
== '\0' && *linep
== ':');
690 * This routine is used to parse lines of the form:
691 * name number aliases
692 * It returns 1 if the key in argp matches any one of the
693 * names in the line, otherwise 0
694 * Used by rpc, networks, protocols
697 _nss_files_check_name_aliases(nss_XbyY_args_t
*argp
, const char *line
,
700 const char *limit
, *linep
, *keyp
;
703 limit
= line
+ linelen
;
704 keyp
= argp
->key
.name
;
707 while (*keyp
&& linep
< limit
&& !isspace(*linep
) && *keyp
== *linep
) {
711 if (*keyp
== '\0' && linep
< limit
&& isspace(*linep
))
713 /* skip remainder of the name, if any */
714 while (linep
< limit
&& !isspace(*linep
))
716 /* skip the delimiting spaces */
717 while (linep
< limit
&& isspace(*linep
))
719 /* compare with the aliases */
720 while (linep
< limit
) {
722 * 1st pass: skip number
723 * Other passes: skip remainder of the alias name, if any
725 while (linep
< limit
&& !isspace(*linep
))
727 /* skip the delimiting spaces */
728 while (linep
< limit
&& isspace(*linep
))
730 /* compare with the alias name */
731 keyp
= argp
->key
.name
;
732 while (*keyp
&& linep
< limit
&& !isspace(*linep
) &&
737 if (*keyp
== '\0' && (linep
== limit
|| isspace(*linep
)))
744 * A few NSS modules hold onto data for the duration of their module. In this
745 * case, when that module goes away, we must free that data. This is a place
746 * that allows for modules to register items to take care of.
748 #pragma fini(_nss_files_fini)
750 _nss_files_fini(void)