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                         firsttime = 0;
 213                 } else {
 214                         len = snprintf(buffer, buflen, ",%s", member_str);
 215                 }
 216                 TEST_AND_ADJUST(len, buffer, buflen, out);
 217         }
 218 
 219 out:
 220         *bufpp = buffer;
 221         *lenp = buflen;
 222         return (nss_result);
 223 }
 224 
 225 /*
 226  * Process the list values from the "member" attribute of the
 227  * current group.  Note that this list is ONLY one that can be
 228  * assumed to be non-empty.  The problem here is that this list
 229  * contains the list of members as "distinguished names" (DN),
 230  * and we want the Unix names (known here as "uid").  We must
 231  * lookup the "uid" for each DN in the member list.  Example:
 232  * CN=Doe\, John,OU=Users,DC=contoso,DC=com => john.doe
 233  */
 234 static int
 235 getmembers_DN(char **bufpp, int *lenp, ns_ldap_attr_t *members)
 236 {
 237         ns_ldap_error_t *error = NULL;
 238         char    *member_dn, *member_uid;
 239         char    *buffer;
 240         int     buflen;
 241         int     i, len;
 242         int     nss_result = 0;
 243         int     firsttime;
 244 
 245         buffer = *bufpp;
 246         buflen = *lenp;
 247         firsttime = (buffer[-1] == ':');
 248 
 249         for (i = 0; i < members->value_count; i++) {
 250                 member_dn = members->attrvalue[i];
 251                 if (member_dn == NULL)
 252                         goto out;
 253 
 254                 /*
 255                  * The attribute name was "member", so these should be
 256                  * full distinguished names (DNs).  We need to loookup
 257                  * the Unix UID (name) for each.
 258                  */
 259 #ifdef DEBUG
 260                 (void) fprintf(stdout, "getmembers_DN: dn=%s\n",
 261                     member_dn);
 262 #endif
 263                 if (member_dn[0] == '\0')
 264                         continue;
 265 
 266                 nss_result = __ns_ldap_dn2uid(member_dn,
 267                     &member_uid, NULL, &error);
 268                 if (nss_result != 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 }