diff --git a/ChangeLog b/ChangeLog index 6a86de6f26..ab05782176 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,37 @@ +2016-04-29 Stephen Gallagher + Carlos O'Donell + + [BZ #19072] + * grp/Makefile (headers): Add grp-merge.h + (routines): Add grp-merge. + * grp/getgrgid_r.c: Include grp-merge.h. + (DEEPCOPY_FN): Define. + (MERGE_FN): Define. + * grp/getgrname_r.c: Include grp-merge.h. + (DEEPCOPY_FN): Define. + (MERGE_FN): Define. + * grp/grp-merge.c: New file. + * grp/grp-merge.h: New file. + * include/grp-merge.h: New file. + * grp/Versions: Define __merge_grp@GLIBC_PRIVATE, and + __copy_grp@GLIBC_PRIVATE. + * manual/nss.texi (Actions in the NSS configuration): Describe + return, continue, and merge. + * nscd/getgrgid_r.c: Include grp/grp-merge.h. + (DEEPCOPY_FN): Define. + (MERGE_FN): Define. + * nscd/getgrnam_r.c: Include grp/grp-merge.h. + (DEEPCOPY_FN): Define. + (MERGE_FN): Define. + * nss/getXXbyYY_r.c [!DEEPCOPY_FN]: Define __copy_einval. + [!MERGE_FN]: Define __merge_einval. + (CHECK_MERGE): Define. + (REENTRANT_NAME): Process merge if do_merge is true. + * nss/getnssent_r.c (__nss_setent): Process NSS_ACTION_MERGE. + (__nss_getent_r): Likewise. + * nss/nsswitch.c (nss_parse_service_list): Likewise. + * nss/nsswitch.h (lookup_actions): Define NSS_ACTION_MERGE. + 2016-04-29 Adhemerval Zanella [BZ #20012] diff --git a/NEWS b/NEWS index 24e13aeafa..1e12173dba 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,14 @@ Version 2.24 1990s and never part of POSIX. Application code should use the int type instead of “union wait”. +* A new NSS action is added to facilitate large distributed system + administration. The action, MERGE, allows remote user stores like LDAP + to be merged into local user stores like /etc/groups in order to provide + easy to use, updated, and managed sets of merged credentials. The new + action can be used by configuring it in /etc/nsswitch.conf: + group: files [SUCCESS=merge] nis + Implemented by Stephen Gallagher (Red Hat). + Security related changes: * An unnecessary stack copy in _nss_dns_getnetbyname_r was removed. It diff --git a/grp/Makefile b/grp/Makefile index 4f1809cea5..b4d52e2928 100644 --- a/grp/Makefile +++ b/grp/Makefile @@ -22,11 +22,12 @@ subdir := grp include ../Makeconfig -headers := grp.h +headers := grp.h grp-merge.h routines := fgetgrent initgroups setgroups \ getgrent getgrgid getgrnam putgrent \ - getgrent_r getgrgid_r getgrnam_r fgetgrent_r + getgrent_r getgrgid_r getgrnam_r fgetgrent_r \ + grp-merge tests := testgrp tst-putgrent diff --git a/grp/Versions b/grp/Versions index e01360da42..096caa47c5 100644 --- a/grp/Versions +++ b/grp/Versions @@ -28,4 +28,7 @@ libc { # g* getgrouplist; } + GLIBC_PRIVATE { + __merge_grp; __copy_grp; + } } diff --git a/grp/getgrgid_r.c b/grp/getgrgid_r.c index 9da834ac98..01d3b99166 100644 --- a/grp/getgrgid_r.c +++ b/grp/getgrgid_r.c @@ -18,6 +18,7 @@ #include +#include #define LOOKUP_TYPE struct group #define FUNCTION_NAME getgrgid @@ -25,5 +26,7 @@ #define ADD_PARAMS gid_t gid #define ADD_VARIABLES gid #define BUFLEN NSS_BUFLEN_GROUP +#define DEEPCOPY_FN __copy_grp +#define MERGE_FN __merge_grp #include diff --git a/grp/getgrnam_r.c b/grp/getgrnam_r.c index 80960825c0..3e8596222a 100644 --- a/grp/getgrnam_r.c +++ b/grp/getgrnam_r.c @@ -18,6 +18,7 @@ #include +#include #define LOOKUP_TYPE struct group #define FUNCTION_NAME getgrnam @@ -25,4 +26,7 @@ #define ADD_PARAMS const char *name #define ADD_VARIABLES name +#define DEEPCOPY_FN __copy_grp +#define MERGE_FN __merge_grp + #include diff --git a/grp/grp-merge.c b/grp/grp-merge.c new file mode 100644 index 0000000000..0a1eb38d2c --- /dev/null +++ b/grp/grp-merge.c @@ -0,0 +1,186 @@ +/* Group merging implementation. + Copyright (C) 2016 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include + +#define BUFCHECK(size) \ + ({ \ + do \ + { \ + if (c + (size) > buflen) \ + { \ + free (members); \ + return ERANGE; \ + } \ + } \ + while (0); \ + }) + +int +internal_function +__copy_grp (const struct group srcgrp, const size_t buflen, + struct group *destgrp, char *destbuf, char **endptr) +{ + size_t i; + size_t c = 0; + size_t len; + size_t memcount; + char **members = NULL; + + /* Copy the GID. */ + destgrp->gr_gid = srcgrp.gr_gid; + + /* Copy the name. */ + len = strlen (srcgrp.gr_name) + 1; + BUFCHECK (len); + memcpy (&destbuf[c], srcgrp.gr_name, len); + destgrp->gr_name = &destbuf[c]; + c += len; + + /* Copy the password. */ + len = strlen (srcgrp.gr_passwd) + 1; + BUFCHECK (len); + memcpy (&destbuf[c], srcgrp.gr_passwd, len); + destgrp->gr_passwd = &destbuf[c]; + c += len; + + /* Count all of the members. */ + for (memcount = 0; srcgrp.gr_mem[memcount]; memcount++) + ; + + /* Allocate a temporary holding area for the pointers to the member + contents, including space for a NULL-terminator. */ + members = malloc (sizeof (char *) * (memcount + 1)); + if (members == NULL) + return ENOMEM; + + /* Copy all of the group members to destbuf and add a pointer to each of + them into the 'members' array. */ + for (i = 0; srcgrp.gr_mem[i]; i++) + { + len = strlen (srcgrp.gr_mem[i]) + 1; + BUFCHECK (len); + memcpy (&destbuf[c], srcgrp.gr_mem[i], len); + members[i] = &destbuf[c]; + c += len; + } + members[i] = NULL; + + /* Copy the pointers from the members array into the buffer and assign them + to the gr_mem member of destgrp. */ + destgrp->gr_mem = (char **) &destbuf[c]; + len = sizeof (char *) * (memcount + 1); + BUFCHECK (len); + memcpy (&destbuf[c], members, len); + c += len; + free (members); + members = NULL; + + /* Save the count of members at the end. */ + BUFCHECK (sizeof (size_t)); + memcpy (&destbuf[c], &memcount, sizeof (size_t)); + c += sizeof (size_t); + + if (endptr) + *endptr = destbuf + c; + return 0; +} +libc_hidden_def (__copy_grp) + +/* Check that the name, GID and passwd fields match, then + copy in the gr_mem array. */ +int +internal_function +__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend, + size_t buflen, struct group *mergegrp, char *mergebuf) +{ + size_t c, i, len; + size_t savedmemcount; + size_t memcount; + size_t membersize; + char **members = NULL; + + /* We only support merging members of groups with identical names and + GID values. If we hit this case, we need to overwrite the current + buffer with the saved one (which is functionally equivalent to + treating the new lookup as NSS_STATUS_NOTFOUND). */ + if (mergegrp->gr_gid != savedgrp->gr_gid + || strcmp (mergegrp->gr_name, savedgrp->gr_name)) + return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL); + + /* Get the count of group members from the last sizeof (size_t) bytes in the + mergegrp buffer. */ + savedmemcount = (size_t) *(savedend - sizeof (size_t)); + + /* Get the count of new members to add. */ + for (memcount = 0; mergegrp->gr_mem[memcount]; memcount++) + ; + + /* Create a temporary array to hold the pointers to the member values from + both the saved and merge groups. */ + membersize = savedmemcount + memcount + 1; + members = malloc (sizeof (char *) * membersize); + if (members == NULL) + return ENOMEM; + + /* Copy in the existing member pointers from the saved group + Note: this is not NULL-terminated yet. */ + memcpy (members, savedgrp->gr_mem, sizeof (char *) * savedmemcount); + + /* Back up into the savedbuf until we get back to the NULL-terminator of the + group member list. (This means walking back savedmemcount + 1 (char *) pointers + and the member count value. + The value of c is going to be the used length of the buffer backed up by + the member count and further backed up by the size of the pointers. */ + c = savedend - savedbuf + - sizeof (size_t) + - sizeof (char *) * (savedmemcount + 1); + + /* Add all the new group members, overwriting the old NULL-terminator while + adding the new pointers to the temporary array. */ + for (i = 0; mergegrp->gr_mem[i]; i++) + { + len = strlen (mergegrp->gr_mem[i]) + 1; + BUFCHECK (len); + memcpy (&savedbuf[c], mergegrp->gr_mem[i], len); + members[savedmemcount + i] = &savedbuf[c]; + c += len; + } + /* Add the NULL-terminator. */ + members[savedmemcount + memcount] = NULL; + + /* Copy the member array back into the buffer after the member list and free + the member array. */ + savedgrp->gr_mem = (char **) &savedbuf[c]; + len = sizeof (char *) * membersize; + BUFCHECK (len); + memcpy (&savedbuf[c], members, len); + c += len; + + free (members); + members = NULL; + + /* Finally, copy the results back into mergebuf, since that's the buffer + that we were provided by the caller. */ + return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL); +} +libc_hidden_def (__merge_grp) diff --git a/grp/grp-merge.h b/grp/grp-merge.h new file mode 100644 index 0000000000..969612b34d --- /dev/null +++ b/grp/grp-merge.h @@ -0,0 +1,37 @@ +/* Group merging implementation. + Copyright (C) 2016 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _GRP_MERGE_H +#define _GRP_MERGE_H 1 + +#include + +/* Duplicate a grp struct (and its members). When no longer needed, the + calling function must free(newbuf). */ +int +__copy_grp (const struct group srcgrp, const size_t buflen, + struct group *destgrp, char *destbuf, char **endptr) + internal_function; + +/* Merge the member lists of two grp structs together. */ +int +__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend, + size_t buflen, struct group *mergegrp, char *mergebuf) + internal_function; + +#endif /* _GRP_MERGE_H */ diff --git a/include/grp-merge.h b/include/grp-merge.h new file mode 100644 index 0000000000..331ac20ea9 --- /dev/null +++ b/include/grp-merge.h @@ -0,0 +1,7 @@ +#ifndef _GRP_MERGE_H +#include + +libc_hidden_proto (__copy_grp) +libc_hidden_proto (__merge_grp) + +#endif /* _GRP_MERGE_H */ diff --git a/manual/nss.texi b/manual/nss.texi index 66dcceffe0..ddc16026c0 100644 --- a/manual/nss.texi +++ b/manual/nss.texi @@ -180,7 +180,7 @@ where The case of the keywords is insignificant. The @var{status} values are the results of a call to a lookup function of a specific -service. They mean +service. They mean: @ftable @samp @item success @@ -203,6 +203,50 @@ locked or a server currently cannot accept more connections. The default action is @code{continue}. @end ftable +@noindent +The @var{action} values mean: + +@ftable @samp +@item return + +If the status matches, stop the lookup process at this service +specification. If an entry is available, provide it to the application. +If an error occurred, report it to the application. In case of a prior +@samp{merge} action, the data is combined with previous lookup results, +as explained below. + +@item continue + +If the status matches, proceed with the lookup process at the next +entry, discarding the result of the current lookup (and any merged +data). An exception is the @samp{initgroups} database and the +@samp{success} status, where @samp{continue} acts like @code{merge} +below. + +@item merge + +Proceed with the lookup process, retaining the current lookup result. +This action is useful only with the @samp{success} status. If a +subsequent service lookup succeeds and has a matching @samp{return} +specification, the results are merged, the lookup process ends, and the +merged results are returned to the application. If the following service +has a matching @samp{merge} action, the lookup process continues, +retaining the combined data from this and any previous lookups. + +After a @code{merge} action, errors from subsequent lookups are ignored, +and the data gathered so far will be returned. + +The @samp{merge} only applies to the @samp{success} status. It is +currently implemented for the @samp{group} database and its group +members field, @samp{gr_mem}. If specified for other databases, it +causes the lookup to fail (if the @var{status} matches). + +When processing @samp{merge} for @samp{group} membership, the group GID +and name must be identical for both entries. If only one or the other is +a match, the behavior is undefined. + +@end ftable + @noindent If we have a line like diff --git a/nscd/getgrgid_r.c b/nscd/getgrgid_r.c index 8039f86ea8..77b6162927 100644 --- a/nscd/getgrgid_r.c +++ b/nscd/getgrgid_r.c @@ -17,6 +17,7 @@ #include +#include #define LOOKUP_TYPE struct group #define FUNCTION_NAME getgrgid @@ -25,6 +26,9 @@ #define ADD_VARIABLES gid #define BUFLEN NSS_BUFLEN_GROUP +#define DEEPCOPY_FN __copy_grp +#define MERGE_FN __merge_grp + /* We are nscd, so we don't want to be talking to ourselves. */ #undef USE_NSCD diff --git a/nscd/getgrnam_r.c b/nscd/getgrnam_r.c index 67e4cd1e0a..28a6ff6d1c 100644 --- a/nscd/getgrnam_r.c +++ b/nscd/getgrnam_r.c @@ -17,6 +17,7 @@ #include +#include #define LOOKUP_TYPE struct group #define FUNCTION_NAME getgrnam @@ -24,6 +25,9 @@ #define ADD_PARAMS const char *name #define ADD_VARIABLES name +#define DEEPCOPY_FN __copy_grp +#define MERGE_FN __merge_grp + /* We are nscd, so we don't want to be talking to ourselves. */ #undef USE_NSCD diff --git a/nss/getXXbyYY_r.c b/nss/getXXbyYY_r.c index 113c687e06..93af2538ec 100644 --- a/nss/getXXbyYY_r.c +++ b/nss/getXXbyYY_r.c @@ -131,6 +131,52 @@ # define AF_VAL AF_INET #endif + +/* Set defaults for merge functions that haven't been defined. */ +#ifndef DEEPCOPY_FN +static inline int +__copy_einval (LOOKUP_TYPE a, + const size_t b, + LOOKUP_TYPE *c, + char *d, + char **e) +{ + return EINVAL; +} +# define DEEPCOPY_FN __copy_einval +#endif + +#ifndef MERGE_FN +static inline int +__merge_einval (LOOKUP_TYPE *a, + char *b, + char *c, + size_t d, + LOOKUP_TYPE *e, + char *f) +{ + return EINVAL; +} +# define MERGE_FN __merge_einval +#endif + +#define CHECK_MERGE(err, status) \ + ({ \ + do \ + { \ + if (err) \ + { \ + __set_errno (err); \ + if (err == ERANGE) \ + status = NSS_STATUS_TRYAGAIN; \ + else \ + status = NSS_STATUS_UNAVAIL; \ + break; \ + } \ + } \ + while (0); \ + }) + /* Type of the lookup function we need here. */ typedef enum nss_status (*lookup_function) (ADD_PARAMS, LOOKUP_TYPE *, char *, size_t, int * H_ERRNO_PARM @@ -152,13 +198,16 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer, static service_user *startp; static lookup_function start_fct; service_user *nip; + int do_merge = 0; + LOOKUP_TYPE mergegrp; + char *mergebuf = NULL; + char *endptr = NULL; union { lookup_function l; void *ptr; } fct; - - int no_more; + int no_more, err; enum nss_status status = NSS_STATUS_UNAVAIL; #ifdef USE_NSCD int nscd_status; @@ -278,9 +327,66 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer, && errno == ERANGE) break; + if (do_merge) + { + + if (status == NSS_STATUS_SUCCESS) + { + /* The previous loop saved a buffer for merging. + Perform the merge now. */ + err = MERGE_FN (&mergegrp, mergebuf, endptr, buflen, resbuf, + buffer); + CHECK_MERGE (err,status); + do_merge = 0; + } + else + { + /* If the result wasn't SUCCESS, copy the saved buffer back + into the result buffer and set the status back to + NSS_STATUS_SUCCESS to match the previous pass through the + loop. + * If the next action is CONTINUE, it will overwrite the value + currently in the buffer and return the new value. + * If the next action is RETURN, we'll return the previously- + acquired values. + * If the next action is MERGE, then it will be added to the + buffer saved from the previous source. */ + err = DEEPCOPY_FN (mergegrp, buflen, resbuf, buffer, NULL); + CHECK_MERGE (err, status); + status = NSS_STATUS_SUCCESS; + } + } + + /* If we were are configured to merge this value with the next one, + save the current value of the group struct. */ + if (nss_next_action (nip, status) == NSS_ACTION_MERGE + && status == NSS_STATUS_SUCCESS) + { + /* Copy the current values into a buffer to be merged with the next + set of retrieved values. */ + if (mergebuf == NULL) + { + /* Only allocate once and reuse it for as many merges as we need + to perform. */ + mergebuf = malloc (buflen); + if (mergebuf == NULL) + { + __set_errno (ENOMEM); + status = NSS_STATUS_UNAVAIL; + break; + } + } + + err = DEEPCOPY_FN (*resbuf, buflen, &mergegrp, mergebuf, &endptr); + CHECK_MERGE (err, status); + do_merge = 1; + } + no_more = __nss_next2 (&nip, REENTRANT_NAME_STRING, REENTRANT2_NAME_STRING, &fct.ptr, status, 0); } + free (mergebuf); + mergebuf = NULL; #ifdef HANDLE_DIGITS_DOTS done: diff --git a/nss/getnssent_r.c b/nss/getnssent_r.c index 456907b018..f5092482ef 100644 --- a/nss/getnssent_r.c +++ b/nss/getnssent_r.c @@ -79,7 +79,18 @@ __nss_setent (const char *func_name, db_lookup_function lookup_fct, else status = DL_CALL_FCT (fct.f, (0)); - no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0); + + /* This is a special-case. When [SUCCESS=merge] is in play, + _nss_next2() will skip to the next database. Due to the + implementation of that function, we can't know whether we're + in an enumeration or an individual lookup, which behaves + differently with regards to merging. We'll treat SUCCESS as + an indication to start the enumeration at this database. */ + if (nss_next_action (*nip, status) == NSS_ACTION_MERGE) + no_more = 1; + else + no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0); + if (is_last_nip) *last_nip = *nip; } @@ -175,8 +186,18 @@ __nss_getent_r (const char *getent_func_name, do { - no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr, - status, 0); + /* This is a special-case. When [SUCCESS=merge] is in play, + _nss_next2() will skip to the next database. Due to the + implementation of that function, we can't know whether we're + in an enumeration or an individual lookup, which behaves + differently with regards to merging. We'll treat SUCCESS as + an indication to return the results here. */ + if (status == NSS_STATUS_SUCCESS + && nss_next_action (*nip, status) == NSS_ACTION_MERGE) + no_more = 1; + else + no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr, + status, 0); if (is_last_nip) *last_nip = *nip; diff --git a/nss/nsswitch.c b/nss/nsswitch.c index bb644cb373..d7706506f0 100644 --- a/nss/nsswitch.c +++ b/nss/nsswitch.c @@ -712,6 +712,9 @@ nss_parse_service_list (const char *line) else if (line - name == 8 && __strncasecmp (name, "CONTINUE", 8) == 0) action = NSS_ACTION_CONTINUE; + else if (line - name == 5 + && __strncasecmp (name, "MERGE", 5) == 0) + action = NSS_ACTION_MERGE; else goto finish; diff --git a/nss/nsswitch.h b/nss/nsswitch.h index 0074ee1d65..54c8b656f7 100644 --- a/nss/nsswitch.h +++ b/nss/nsswitch.h @@ -32,7 +32,8 @@ typedef enum { NSS_ACTION_CONTINUE, - NSS_ACTION_RETURN + NSS_ACTION_RETURN, + NSS_ACTION_MERGE } lookup_actions;