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