1 /*
   2  * CDDL HEADER START
   3  *
   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.
   7  *
   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.
  12  *
  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]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  *
  25  * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  26  */
  27 
  28 #include <grp.h>
  29 #include "ldap_common.h"
  30 #include <string.h>
  31 
  32 /* String which may need to be removed from beginning of group password */
  33 #define _CRYPT          "{CRYPT}"
  34 #define _NO_PASSWD_VAL  ""
  35 
  36 /* Group attributes filters */
  37 #define _G_NAME         "cn"
  38 #define _G_GID          "gidnumber"
  39 #define _G_PASSWD       "userpassword"
  40 #define _G_MEMUID       "memberuid"
  41 #define _G_MEM_DN       "member"        /* DN */
  42 
  43 #define _F_GETGRNAM     "(&(objectClass=posixGroup)(cn=%s))"
  44 #define _F_GETGRNAM_SSD "(&(%%s)(cn=%s))"
  45 #define _F_GETGRGID     "(&(objectClass=posixGroup)(gidNumber=%u))"
  46 #define _F_GETGRGID_SSD "(&(%%s)(gidNumber=%u))"
  47 
  48 /*
  49  * When searching for groups in which a specified user is a member,
  50  * there are a few different membership schema that might be in use.
  51  * We'll use a filter that should work with an of the common ones:
  52  * "memberUid=NAME", or "member=DN" (try uniquemember too?)
  53  * The first parameter in the filter string is replaced by username,
  54  * and the remaining ones by the full DN.
  55  */
  56 #define _F_GETGRMEM "(&(objectClass=posixGroup)" \
  57         "(|(memberUid=%s)(member=%s)))"
  58 #define _F_GETGRMEM_SSD "(&(%%s)" \
  59         "(|(memberUid=%s)(member=%s)))"
  60 
  61 static const char *gr_attrs[] = {
  62         _G_NAME,
  63         _G_GID,
  64         _G_PASSWD,
  65         _G_MEMUID,
  66         _G_MEM_DN,
  67         (char *)NULL
  68 };
  69 
  70 static int
  71 getmembers_UID(char **bufpp, int *lenp, ns_ldap_attr_t *members);
  72 static int
  73 getmembers_DN(char **bufpp, int *lenp, ns_ldap_attr_t *members);
  74 
  75 
  76 /*
  77  * _nss_ldap_group2str is the data marshaling method for the group getXbyY
  78  * (e.g., getgrnam(), getgrgid(), getgrent()) backend processes. This method
  79  * is called after a successful ldap search has been performed. This method
  80  * will parse the ldap search values into the file format.
  81  * e.g.
  82  *
  83  * adm::4:root,adm,daemon
  84  *
  85  */
  86 
  87 static int
  88 _nss_ldap_group2str(ldap_backend_ptr be, nss_XbyY_args_t *argp)
  89 {
  90         int             i;
  91         int             nss_result;
  92         int             buflen = 0, len;
  93         char            *buffer = NULL;
  94         ns_ldap_result_t        *result = be->result;
  95         char            **gname, **passwd, **gid, *password, *end;
  96         char            gid_nobody[NOBODY_STR_LEN];
  97         char            *gid_nobody_v[1];
  98         ns_ldap_attr_t  *members;
  99 
 100         (void) snprintf(gid_nobody, sizeof (gid_nobody), "%u", GID_NOBODY);
 101         gid_nobody_v[0] = gid_nobody;
 102 
 103         if (result == NULL)
 104                 return (NSS_STR_PARSE_PARSE);
 105         buflen = argp->buf.buflen;
 106 
 107         if (argp->buf.result != NULL) {
 108                 if ((be->buffer = calloc(1, buflen)) == NULL) {
 109                         nss_result = NSS_STR_PARSE_PARSE;
 110                         goto result_grp2str;
 111                 }
 112                 buffer = be->buffer;
 113         } else
 114                 buffer = argp->buf.buffer;
 115 
 116         nss_result = NSS_STR_PARSE_SUCCESS;
 117         (void) memset(buffer, 0, buflen);
 118 
 119         gname = __ns_ldap_getAttr(result->entry, _G_NAME);
 120         if (gname == NULL || gname[0] == NULL || (strlen(gname[0]) < 1)) {
 121                 nss_result = NSS_STR_PARSE_PARSE;
 122                 goto result_grp2str;
 123         }
 124         passwd = __ns_ldap_getAttr(result->entry, _G_PASSWD);
 125         if (passwd == NULL || passwd[0] == NULL || (strlen(passwd[0]) == 0)) {
 126                 /* group password could be NULL, replace it with "" */
 127                 password = _NO_PASSWD_VAL;
 128         } else {
 129                 /*
 130                  * Preen "{crypt}" if necessary.
 131                  * If the password does not include the {crypt} prefix
 132                  * then the password may be plain text.  And thus
 133                  * perhaps crypt(3c) should be used to encrypt it.
 134                  * Currently the password is copied verbatim.
 135                  */
 136                 if (strncasecmp(passwd[0], _CRYPT, strlen(_CRYPT)) == 0)
 137                         password = passwd[0] + strlen(_CRYPT);
 138                 else
 139                         password = passwd[0];
 140         }
 141         gid = __ns_ldap_getAttr(result->entry, _G_GID);
 142         if (gid == NULL || gid[0] == NULL || (strlen(gid[0]) < 1)) {
 143                 nss_result = NSS_STR_PARSE_PARSE;
 144                 goto result_grp2str;
 145         }
 146         /* Validate GID */
 147         if (strtoul(gid[0], &end, 10) > MAXUID)
 148                 gid = gid_nobody_v;
 149         len = snprintf(buffer, buflen, "%s:%s:%s:", gname[0], password, gid[0]);
 150         TEST_AND_ADJUST(len, buffer, buflen, result_grp2str);
 151 
 152         members = __ns_ldap_getAttrStruct(result->entry, _G_MEMUID);
 153         if (members != NULL && members->attrvalue != NULL) {
 154                 nss_result = getmembers_UID(&buffer, &buflen, members);
 155                 if (nss_result != 0)
 156                         goto result_grp2str;
 157         }
 158 
 159         members = __ns_ldap_getAttrStruct(result->entry, _G_MEM_DN);
 160         if (members != NULL && members->attrvalue != NULL) {
 161                 nss_result = getmembers_DN(&buffer, &buflen, members);
 162                 if (nss_result != 0)
 163                         goto result_grp2str;
 164         }
 165 
 166         /* The front end marshaller doesn't need the trailing nulls */
 167         if (argp->buf.result != NULL)
 168                 be->buflen = strlen(be->buffer);
 169 result_grp2str:
 170         (void) __ns_ldap_freeResult(&be->result);
 171         return (nss_result);
 172 }
 173 
 174 /*
 175  * Process the list values from the "memberUid" attribute of the
 176  * current group.  Note that this list is often empty, and we
 177  * get the real list of members via getmember_DN (see below).
 178  */
 179 static int
 180 getmembers_UID(char **bufpp, int *lenp, ns_ldap_attr_t *members)
 181 {
 182         char    *member_str, *strtok_state;
 183         char    *buffer;
 184         int     buflen;
 185         int     i, len;
 186         int     nss_result = 0;
 187         int     firsttime;
 188 
 189         buffer = *bufpp;
 190         buflen = *lenp;
 191         firsttime = (buffer[-1] == ':');
 192 
 193         for (i = 0; i < members->value_count; i++) {
 194                 member_str = members->attrvalue[i];
 195                 if (member_str == NULL)
 196                         goto out;
 197 
 198 #ifdef DEBUG
 199                 (void) fprintf(stdout, "getmembers_UID: uid=<%s>\n",
 200                     member_str);
 201 #endif
 202                 /*
 203                  * If not a valid Unix user name, or
 204                  * not valid in ldap, just skip.
 205                  */
 206                 if (member_str[0] == '\0' ||
 207                     strpbrk(member_str, " ,:=") != NULL)
 208                         continue;
 209 
 210                 if (firsttime)
 211                         len = snprintf(buffer, buflen, "%s", member_str);
 212                 else
 213                         len = snprintf(buffer, buflen, ",%s", member_str);
 214                 TEST_AND_ADJUST(len, buffer, buflen, out);
 215         }
 216 
 217 out:
 218         *bufpp = buffer;
 219         *lenp = buflen;
 220         return (nss_result);
 221 }
 222 
 223 /*
 224  * Process the list values from the "member" attribute of the
 225  * current group.  Note that this list is ONLY one that can be
 226  * assumed to be non-empty.  The problem here is that this list
 227  * contains the list of members as "distinguished names" (DN),
 228  * and we want the Unix names (known here as "uid").  We must
 229  * lookup the "uid" for each DN in the member list.  Example:
 230  * CN=Doe\, John,OU=Users,DC=contoso,DC=com => john.doe
 231  */
 232 static int
 233 getmembers_DN(char **bufpp, int *lenp, ns_ldap_attr_t *members)
 234 {
 235         ns_ldap_error_t *error = NULL;
 236         char    *member_dn, *member_uid;
 237         char    *buffer;
 238         int     buflen;
 239         int     i, len;
 240         int     nss_result = 0;
 241         int     firsttime;
 242 
 243         buffer = *bufpp;
 244         buflen = *lenp;
 245         firsttime = (buffer[-1] == ':');
 246 
 247         for (i = 0; i < members->value_count; i++) {
 248                 member_dn = members->attrvalue[i];
 249                 if (member_dn == NULL)
 250                         goto out;
 251 
 252                 /*
 253                  * The attribute name was "member", so these should be
 254                  * full distinguisned names (DNs).  We need to loookup
 255                  * the Unix UID (name) for each.
 256                  */
 257 #ifdef DEBUG
 258                 (void) fprintf(stdout, "getmembers_DN: dn=%s\n",
 259                     member_dn);
 260 #endif
 261                 if (member_dn[0] == '\0')
 262                         continue;
 263 
 264                 nss_result = __ns_ldap_dn2uid(member_dn,
 265                     &member_uid, NULL, &error);
 266                 if (nss_result != NS_LDAP_SUCCESS) {
 267                         (void) __ns_ldap_freeError(&error);
 268                         error = NULL;
 269                         continue;
 270                 }
 271 #ifdef DEBUG
 272                 (void) fprintf(stdout, "getmembers_DN: uid=<%s>\n",
 273                     member_uid);
 274 #endif
 275                 /* Skip invalid names. */
 276                 if (member_uid[0] == '\0' ||
 277                     strpbrk(member_uid, " ,:=") != NULL) {
 278                         free(member_uid);
 279                         continue;
 280                 }
 281 
 282                 if (firsttime)
 283                         len = snprintf(buffer, buflen, "%s", member_uid);
 284                 else
 285                         len = snprintf(buffer, buflen, ",%s", member_uid);
 286                 free(member_uid);
 287                 TEST_AND_ADJUST(len, buffer, buflen, out);
 288         }
 289 
 290 out:
 291         *bufpp = buffer;
 292         *lenp = buflen;
 293         return (nss_result);
 294 }
 295 
 296 /*
 297  * getbynam gets a group entry by name. This function constructs an ldap
 298  * search filter using the name invocation parameter and the getgrnam search
 299  * filter defined. Once the filter is constructed, we searche for a matching
 300  * entry and marshal the data results into struct group for the frontend
 301  * process. The function _nss_ldap_group2ent performs the data marshaling.
 302  */
 303 
 304 static nss_status_t
 305 getbynam(ldap_backend_ptr be, void *a)
 306 {
 307         nss_XbyY_args_t *argp = (nss_XbyY_args_t *)a;
 308         char            searchfilter[SEARCHFILTERLEN];
 309         char            userdata[SEARCHFILTERLEN];
 310         char            groupname[SEARCHFILTERLEN];
 311         int             ret;
 312 
 313         if (_ldap_filter_name(groupname, argp->key.name, sizeof (groupname)) !=
 314             0)
 315                 return ((nss_status_t)NSS_NOTFOUND);
 316 
 317         ret = snprintf(searchfilter, sizeof (searchfilter),
 318             _F_GETGRNAM, groupname);
 319         if (ret >= sizeof (searchfilter) || ret < 0)
 320                 return ((nss_status_t)NSS_NOTFOUND);
 321 
 322         ret = snprintf(userdata, sizeof (userdata), _F_GETGRNAM_SSD, groupname);
 323         if (ret >= sizeof (userdata) || ret < 0)
 324                 return ((nss_status_t)NSS_NOTFOUND);
 325 
 326         return ((nss_status_t)_nss_ldap_lookup(be, argp,
 327             _GROUP, searchfilter, NULL, _merge_SSD_filter, userdata));
 328 }
 329 
 330 
 331 /*
 332  * getbygid gets a group entry by number. This function constructs an ldap
 333  * search filter using the name invocation parameter and the getgrgid search
 334  * filter defined. Once the filter is constructed, we searche for a matching
 335  * entry and marshal the data results into struct group for the frontend
 336  * process. The function _nss_ldap_group2ent performs the data marshaling.
 337  */
 338 
 339 static nss_status_t
 340 getbygid(ldap_backend_ptr be, void *a)
 341 {
 342         nss_XbyY_args_t *argp = (nss_XbyY_args_t *)a;
 343         char searchfilter[SEARCHFILTERLEN];
 344         char userdata[SEARCHFILTERLEN];
 345         int ret;
 346 
 347         if (argp->key.uid > MAXUID)
 348                 return ((nss_status_t)NSS_NOTFOUND);
 349 
 350         ret = snprintf(searchfilter, sizeof (searchfilter),
 351             _F_GETGRGID, argp->key.uid);
 352         if (ret >= sizeof (searchfilter) || ret < 0)
 353                 return ((nss_status_t)NSS_NOTFOUND);
 354 
 355         ret = snprintf(userdata, sizeof (userdata),
 356             _F_GETGRGID_SSD, argp->key.uid);
 357         if (ret >= sizeof (userdata) || ret < 0)
 358                 return ((nss_status_t)NSS_NOTFOUND);
 359 
 360         return ((nss_status_t)_nss_ldap_lookup(be, argp,
 361             _GROUP, searchfilter, NULL, _merge_SSD_filter, userdata));
 362 
 363 }
 364 
 365 
 366 /*
 367  * Use a custom attributes list for getbymember, because the LDAP
 368  * query for this requests a list of groups, and the result can be
 369  * very large if it includes the list of members with each group.
 370  * We don't need or want the list of members in this case.
 371  */
 372 static const char *grbymem_attrs[] = {
 373         _G_NAME,        /* cn */
 374         _G_GID,         /* gidnumber */
 375         (char *)NULL
 376 };
 377 
 378 /*
 379  * getbymember returns all groups a user is defined in. This function
 380  * uses different architectural procedures than the other group backend
 381  * system calls because it's a private interface. This function constructs
 382  * an ldap search filter using the name invocation parameter. Once the
 383  * filter is constructed, we search for all matching groups counting
 384  * and storing each group name, gid, etc. Data marshaling is used for
 385  * group processing. The function _nss_ldap_group2ent() performs the
 386  * data marshaling.
 387  *
 388  * (const char *)argp->username;     (size_t)strlen(argp->username);
 389  * (gid_t)argp->gid_array;           (int)argp->maxgids;
 390  * (int)argp->numgids;
 391  */
 392 
 393 static nss_status_t
 394 getbymember(ldap_backend_ptr be, void *a)
 395 {
 396         ns_ldap_error_t         *error = NULL;
 397         int                     i, j, k;
 398         int                     gcnt = (int)0;
 399         char                    **groupvalue;
 400         nss_status_t            lstat;
 401         struct nss_groupsbymem  *argp = (struct nss_groupsbymem *)a;
 402         char                    searchfilter[SEARCHFILTERLEN];
 403         char                    userdata[SEARCHFILTERLEN];
 404         char                    name[SEARCHFILTERLEN];
 405         char                    escdn[SEARCHFILTERLEN];
 406         ns_ldap_result_t        *result;
 407         ns_ldap_entry_t         *curEntry;
 408         char                    *dn;
 409         gid_t                   gid;
 410         int                     ret1, ret2;
 411 
 412         if (strcmp(argp->username, "") == 0 ||
 413             strcmp(argp->username, "root") == 0)
 414                 return ((nss_status_t)NSS_NOTFOUND);
 415 
 416         if (_ldap_filter_name(name, argp->username, sizeof (name)) != 0)
 417                 return ((nss_status_t)NSS_NOTFOUND);
 418 
 419         /*
 420          * Look up the user DN in ldap. If it's not found, search solely by
 421          * username.
 422          */
 423         lstat = __ns_ldap_uid2dn(name, &dn, NULL, &error);
 424         if (lstat != (nss_status_t)NS_LDAP_SUCCESS) {
 425                 /* Can't get DN.  Use bare name */
 426                 (void) __ns_ldap_freeError(&error);
 427                 dn = name;
 428         }
 429         /* Note: must free dn if != name */
 430 
 431         /*
 432          * Compose filter patterns
 433          */
 434         ret1 = snprintf(searchfilter, sizeof (searchfilter),
 435             _F_GETGRMEM, name, dn);
 436         ret2 = snprintf(userdata, sizeof (userdata),
 437             _F_GETGRMEM_SSD, name, dn);
 438         if (dn != name)
 439                 free(dn);
 440         if (ret1 >= sizeof (searchfilter) || ret1 < 0)
 441                 return ((nss_status_t)NSS_NOTFOUND);
 442         if (ret2 >= sizeof (userdata) || ret2 < 0)
 443                 return ((nss_status_t)NSS_NOTFOUND);
 444 
 445         /*
 446          * Query for groups matching the filter.
 447          */
 448         lstat = (nss_status_t)_nss_ldap_nocb_lookup(be, NULL,
 449             _GROUP, searchfilter, grbymem_attrs,
 450             _merge_SSD_filter, userdata);
 451         if (lstat != (nss_status_t)NS_LDAP_SUCCESS)
 452                 return ((nss_status_t)lstat);
 453         if (be->result == NULL)
 454                 return (NSS_NOTFOUND);
 455 
 456         /*
 457          * Walk the query result, collecting GIDs.
 458          */
 459         result = (ns_ldap_result_t *)be->result;
 460         curEntry = (ns_ldap_entry_t *)result->entry;
 461         gcnt = (int)argp->numgids;
 462         for (i = 0; i < result->entries_count; i++) {
 463 
 464                 /*
 465                  * Does this group have a gidNumber attr?
 466                  */
 467                 groupvalue = __ns_ldap_getAttr(curEntry, _G_GID);
 468                 if (groupvalue == NULL || groupvalue[0] == NULL) {
 469                         /* Drop this group from the list */
 470                         goto next_group;
 471                 }
 472 
 473                 /*
 474                  * Convert it to a numeric GID
 475                  */
 476                 errno = 0;
 477                 gid = (gid_t)strtol(groupvalue[0], (char **)NULL, 10);
 478                 if (errno != 0)
 479                         goto next_group;
 480 
 481                 /*
 482                  * If we don't already have this GID, add it.
 483                  */
 484                 if (argp->numgids < argp->maxgids) {
 485                         for (k = 0; k < argp->numgids; k++) {
 486                                 if (argp->gid_array[k] == gid) {
 487                                         /* already have it */
 488                                         goto next_group;
 489                                 }
 490                         }
 491                         argp->gid_array[argp->numgids++] = gid;
 492                 }
 493 
 494         next_group:
 495                 curEntry = curEntry->next;
 496         }
 497 
 498         (void) __ns_ldap_freeResult((ns_ldap_result_t **)&be->result);
 499         if (gcnt == argp->numgids)
 500                 return ((nss_status_t)NSS_NOTFOUND);
 501 
 502         /*
 503          * Return NSS_SUCCESS only if array is full.
 504          * Explained in <nss_dbdefs.h>.
 505          */
 506         return ((nss_status_t)((argp->numgids == argp->maxgids)
 507             ? NSS_SUCCESS
 508             : NSS_NOTFOUND));
 509 }
 510 
 511 static ldap_backend_op_t gr_ops[] = {
 512         _nss_ldap_destr,
 513         _nss_ldap_endent,
 514         _nss_ldap_setent,
 515         _nss_ldap_getent,
 516         getbynam,
 517         getbygid,
 518         getbymember
 519 };
 520 
 521 
 522 /*ARGSUSED0*/
 523 nss_backend_t *
 524 _nss_ldap_group_constr(const char *dummy1, const char *dummy2,
 525                         const char *dummy3)
 526 {
 527 
 528         return ((nss_backend_t *)_nss_ldap_constr(gr_ops,
 529             sizeof (gr_ops)/sizeof (gr_ops[0]), _GROUP, gr_attrs,
 530             _nss_ldap_group2str));
 531 }