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_MEM          "memberuid"
  41 
  42 #define _F_GETGRNAM     "(&(objectClass=posixGroup)(cn=%s))"
  43 #define _F_GETGRNAM_SSD "(&(%%s)(cn=%s))"
  44 #define _F_GETGRGID     "(&(objectClass=posixGroup)(gidNumber=%u))"
  45 #define _F_GETGRGID_SSD "(&(%%s)(gidNumber=%u))"
  46 /*
  47  * Group membership can be defined by either username or DN, so when searching
  48  * for groups by member we need to consider both. The first parameter in the
  49  * filter is replaced by username, the second by DN.
  50  */
  51 #define _F_GETGRMEM \
  52         "(&(objectClass=posixGroup)(|(memberUid=%s)(memberUid=%s)))"
  53 #define _F_GETGRMEM_SSD "(&(%%s)(|(memberUid=%s)(memberUid=%s)))"
  54 
  55 /*
  56  * Copied from getpwnam.c, needed to look up user DN.
  57  * Would it be better to move to ldap_common.h rather than duplicate?
  58  */
  59 #define _F_GETPWNAM     "(&(objectClass=posixAccount)(uid=%s))"
  60 #define _F_GETPWNAM_SSD "(&(%%s)(uid=%s))"
  61 
  62 static const char *gr_attrs[] = {
  63         _G_NAME,
  64         _G_GID,
  65         _G_PASSWD,
  66         _G_MEM,
  67         (char *)NULL
  68 };
  69 
  70 
  71 /*
  72  * _nss_ldap_group2str is the data marshaling method for the group getXbyY
  73  * (e.g., getgrnam(), getgrgid(), getgrent()) backend processes. This method
  74  * is called after a successful ldap search has been performed. This method
  75  * will parse the ldap search values into the file format.
  76  * e.g.
  77  *
  78  * adm::4:root,adm,daemon
  79  *
  80  */
  81 
  82 static int
  83 _nss_ldap_group2str(ldap_backend_ptr be, nss_XbyY_args_t *argp)
  84 {
  85         int             i;
  86         int             nss_result;
  87         int             buflen = 0, len;
  88         int             firstime = 1;
  89         char            *buffer = NULL;
  90         ns_ldap_result_t        *result = be->result;
  91         char            **gname, **passwd, **gid, *password, *end;
  92         char            gid_nobody[NOBODY_STR_LEN];
  93         char            *gid_nobody_v[1];
  94         char            *member_str, *strtok_state;
  95         ns_ldap_attr_t  *members;
  96 
  97         (void) snprintf(gid_nobody, sizeof (gid_nobody), "%u", GID_NOBODY);
  98         gid_nobody_v[0] = gid_nobody;
  99 
 100         if (result == NULL)
 101                 return (NSS_STR_PARSE_PARSE);
 102         buflen = argp->buf.buflen;
 103 
 104         if (argp->buf.result != NULL) {
 105                 if ((be->buffer = calloc(1, buflen)) == NULL) {
 106                         nss_result = NSS_STR_PARSE_PARSE;
 107                         goto result_grp2str;
 108                 }
 109                 buffer = be->buffer;
 110         } else
 111                 buffer = argp->buf.buffer;
 112 
 113         nss_result = NSS_STR_PARSE_SUCCESS;
 114         (void) memset(buffer, 0, buflen);
 115 
 116         gname = __ns_ldap_getAttr(result->entry, _G_NAME);
 117         if (gname == NULL || gname[0] == NULL || (strlen(gname[0]) < 1)) {
 118                 nss_result = NSS_STR_PARSE_PARSE;
 119                 goto result_grp2str;
 120         }
 121         passwd = __ns_ldap_getAttr(result->entry, _G_PASSWD);
 122         if (passwd == NULL || passwd[0] == NULL || (strlen(passwd[0]) == 0)) {
 123                 /* group password could be NULL, replace it with "" */
 124                 password = _NO_PASSWD_VAL;
 125         } else {
 126                 /*
 127                  * Preen "{crypt}" if necessary.
 128                  * If the password does not include the {crypt} prefix
 129                  * then the password may be plain text.  And thus
 130                  * perhaps crypt(3c) should be used to encrypt it.
 131                  * Currently the password is copied verbatim.
 132                  */
 133                 if (strncasecmp(passwd[0], _CRYPT, strlen(_CRYPT)) == 0)
 134                         password = passwd[0] + strlen(_CRYPT);
 135                 else
 136                         password = passwd[0];
 137         }
 138         gid = __ns_ldap_getAttr(result->entry, _G_GID);
 139         if (gid == NULL || gid[0] == NULL || (strlen(gid[0]) < 1)) {
 140                 nss_result = NSS_STR_PARSE_PARSE;
 141                 goto result_grp2str;
 142         }
 143         /* Validate GID */
 144         if (strtoul(gid[0], &end, 10) > MAXUID)
 145                 gid = gid_nobody_v;
 146         len = snprintf(buffer, buflen, "%s:%s:%s:", gname[0], password, gid[0]);
 147         TEST_AND_ADJUST(len, buffer, buflen, result_grp2str);
 148 
 149         members = __ns_ldap_getAttrStruct(result->entry, _G_MEM);
 150         if (members == NULL || members->attrvalue == NULL) {
 151                 /* no member is fine, skip processing the member list */
 152                 goto nomember;
 153         }
 154 
 155         for (i = 0; i < members->value_count; i++) {
 156                 if (members->attrvalue[i] == NULL) {
 157                         nss_result = NSS_STR_PARSE_PARSE;
 158                         goto result_grp2str;
 159                 }
 160                 /*
 161                  * If we find an '=' in the member attribute value, treat it as
 162                  * a DN, otherwise as a username.
 163                  */
 164                 if (member_str = strchr(members->attrvalue[i], '=')) {
 165                         member_str++; /* skip over the '=' */
 166                         /* Fail if we can't pull a username out of the RDN */
 167                         if (! (member_str = strtok_r(member_str,
 168                             ",", &strtok_state))) {
 169                                 nss_result = NSS_STR_PARSE_PARSE;
 170                                 goto result_grp2str;
 171                         }
 172                 } else {
 173                         member_str = members->attrvalue[i];
 174                 }
 175                 if (*member_str != '\0') {
 176                         if (firstime) {
 177                                 len = snprintf(buffer, buflen, "%s",
 178                                     member_str);
 179                                 TEST_AND_ADJUST(len, buffer, buflen,
 180                                     result_grp2str);
 181                                 firstime = 0;
 182                         } else {
 183                                 len = snprintf(buffer, buflen, ",%s",
 184                                     member_str);
 185                                 TEST_AND_ADJUST(len, buffer, buflen,
 186                                     result_grp2str);
 187                         }
 188                 }
 189         }
 190 nomember:
 191         /* The front end marshaller doesn't need the trailing nulls */
 192         if (argp->buf.result != NULL)
 193                 be->buflen = strlen(be->buffer);
 194 result_grp2str:
 195         (void) __ns_ldap_freeResult(&be->result);
 196         return (nss_result);
 197 }
 198 
 199 /*
 200  * getbynam gets a group entry by name. This function constructs an ldap
 201  * search filter using the name invocation parameter and the getgrnam search
 202  * filter defined. Once the filter is constructed, we searche for a matching
 203  * entry and marshal the data results into struct group for the frontend
 204  * process. The function _nss_ldap_group2ent performs the data marshaling.
 205  */
 206 
 207 static nss_status_t
 208 getbynam(ldap_backend_ptr be, void *a)
 209 {
 210         nss_XbyY_args_t *argp = (nss_XbyY_args_t *)a;
 211         char            searchfilter[SEARCHFILTERLEN];
 212         char            userdata[SEARCHFILTERLEN];
 213         char            groupname[SEARCHFILTERLEN];
 214         int             ret;
 215 
 216         if (_ldap_filter_name(groupname, argp->key.name, sizeof (groupname)) !=
 217             0)
 218                 return ((nss_status_t)NSS_NOTFOUND);
 219 
 220         ret = snprintf(searchfilter, sizeof (searchfilter),
 221             _F_GETGRNAM, groupname);
 222         if (ret >= sizeof (searchfilter) || ret < 0)
 223                 return ((nss_status_t)NSS_NOTFOUND);
 224 
 225         ret = snprintf(userdata, sizeof (userdata), _F_GETGRNAM_SSD, groupname);
 226         if (ret >= sizeof (userdata) || ret < 0)
 227                 return ((nss_status_t)NSS_NOTFOUND);
 228 
 229         return ((nss_status_t)_nss_ldap_lookup(be, argp,
 230             _GROUP, searchfilter, NULL, _merge_SSD_filter, userdata));
 231 }
 232 
 233 
 234 /*
 235  * getbygid gets a group entry by number. This function constructs an ldap
 236  * search filter using the name invocation parameter and the getgrgid search
 237  * filter defined. Once the filter is constructed, we searche for a matching
 238  * entry and marshal the data results into struct group for the frontend
 239  * process. The function _nss_ldap_group2ent performs the data marshaling.
 240  */
 241 
 242 static nss_status_t
 243 getbygid(ldap_backend_ptr be, void *a)
 244 {
 245         nss_XbyY_args_t *argp = (nss_XbyY_args_t *)a;
 246         char searchfilter[SEARCHFILTERLEN];
 247         char userdata[SEARCHFILTERLEN];
 248         int ret;
 249 
 250         if (argp->key.uid > MAXUID)
 251                 return ((nss_status_t)NSS_NOTFOUND);
 252 
 253         ret = snprintf(searchfilter, sizeof (searchfilter),
 254             _F_GETGRGID, argp->key.uid);
 255         if (ret >= sizeof (searchfilter) || ret < 0)
 256                 return ((nss_status_t)NSS_NOTFOUND);
 257 
 258         ret = snprintf(userdata, sizeof (userdata),
 259             _F_GETGRGID_SSD, argp->key.uid);
 260         if (ret >= sizeof (userdata) || ret < 0)
 261                 return ((nss_status_t)NSS_NOTFOUND);
 262 
 263         return ((nss_status_t)_nss_ldap_lookup(be, argp,
 264             _GROUP, searchfilter, NULL, _merge_SSD_filter, userdata));
 265 
 266 }
 267 
 268 
 269 /*
 270  * getbymember returns all groups a user is defined in. This function
 271  * uses different architectural procedures than the other group backend
 272  * system calls because it's a private interface. This function constructs
 273  * an ldap search filter using the name invocation parameter. Once the
 274  * filter is constructed, we search for all matching groups counting
 275  * and storing each group name, gid, etc. Data marshaling is used for
 276  * group processing. The function _nss_ldap_group2ent() performs the
 277  * data marshaling.
 278  *
 279  * (const char *)argp->username;     (size_t)strlen(argp->username);
 280  * (gid_t)argp->gid_array;           (int)argp->maxgids;
 281  * (int)argp->numgids;
 282  */
 283 
 284 static nss_status_t
 285 getbymember(ldap_backend_ptr be, void *a)
 286 {
 287         int                     i, j, k;
 288         int                     gcnt = (int)0;
 289         char                    **groupvalue, **membervalue, *member_str;
 290         char                    *strtok_state;
 291         nss_status_t            lstat;
 292         struct nss_groupsbymem  *argp = (struct nss_groupsbymem *)a;
 293         char                    searchfilter[SEARCHFILTERLEN];
 294         char                    userdata[SEARCHFILTERLEN];
 295         char                    name[SEARCHFILTERLEN];
 296         ns_ldap_result_t        *result;
 297         ns_ldap_entry_t         *curEntry;
 298         char                    *username, **dn_attr, *dn;
 299         gid_t                   gid;
 300         int                     ret;
 301 
 302         if (strcmp(argp->username, "") == 0 ||
 303             strcmp(argp->username, "root") == 0)
 304                 return ((nss_status_t)NSS_NOTFOUND);
 305 
 306         if (_ldap_filter_name(name, argp->username, sizeof (name)) != 0)
 307                 return ((nss_status_t)NSS_NOTFOUND);
 308 
 309         ret = snprintf(searchfilter, sizeof (searchfilter), _F_GETPWNAM, name);
 310         if (ret >= sizeof (searchfilter) || ret < 0)
 311                 return ((nss_status_t)NSS_NOTFOUND);
 312 
 313         ret = snprintf(userdata, sizeof (userdata), _F_GETPWNAM_SSD, name);
 314         if (ret >= sizeof (userdata) || ret < 0)
 315                 return ((nss_status_t)NSS_NOTFOUND);
 316 
 317         /*
 318          * Look up the user DN in ldap. If it's not found, search solely by
 319          * username.
 320          */
 321         lstat = (nss_status_t)_nss_ldap_nocb_lookup(be, NULL,
 322             _PASSWD, searchfilter, NULL, _merge_SSD_filter, userdata);
 323         if (lstat != (nss_status_t)NS_LDAP_SUCCESS)
 324                 return ((nss_status_t)lstat);
 325 
 326         if (be->result == NULL ||
 327             !(dn_attr = __ns_ldap_getAttr(be->result->entry, "dn")))
 328                 dn = name;
 329         else
 330                 dn = dn_attr[0];
 331 
 332         ret = snprintf(searchfilter, sizeof (searchfilter), _F_GETGRMEM, name,
 333             dn);
 334         if (ret >= sizeof (searchfilter) || ret < 0)
 335                 return ((nss_status_t)NSS_NOTFOUND);
 336 
 337         ret = snprintf(userdata, sizeof (userdata), _F_GETGRMEM_SSD, name,
 338             dn);
 339         if (ret >= sizeof (userdata) || ret < 0)
 340                 return ((nss_status_t)NSS_NOTFOUND);
 341 
 342         /*
 343          * Free up resources from user DN search before performing group
 344          * search.
 345          */
 346         (void) __ns_ldap_freeResult((ns_ldap_result_t **)&be->result);
 347 
 348         gcnt = (int)argp->numgids;
 349         lstat = (nss_status_t)_nss_ldap_nocb_lookup(be, NULL,
 350             _GROUP, searchfilter, NULL, _merge_SSD_filter, userdata);
 351         if (lstat != (nss_status_t)NS_LDAP_SUCCESS)
 352                 return ((nss_status_t)lstat);
 353         if (be->result == NULL)
 354                 return (NSS_NOTFOUND);
 355         username = (char *)argp->username;
 356         result = (ns_ldap_result_t *)be->result;
 357         curEntry = (ns_ldap_entry_t *)result->entry;
 358         for (i = 0; i < result->entries_count && curEntry != NULL; i++) {
 359                 membervalue = __ns_ldap_getAttr(curEntry, "memberUid");
 360                 if (membervalue == NULL) {
 361                         curEntry = curEntry->next;
 362                         continue;
 363                 }
 364                 for (j = 0; membervalue[j]; j++) {
 365                         /*
 366                          * If we find an '=' in the member attribute
 367                          * value, treat it as a DN, otherwise as a
 368                          * username.
 369                          */
 370                         if (member_str = strchr(membervalue[j], '=')) {
 371                                 member_str++; /* skip over the '=' */
 372                                 member_str = strtok_r(member_str, ",",
 373                                     &strtok_state);
 374                         } else {
 375                                 member_str = membervalue[j];
 376                         }
 377                         if (member_str != NULL &&
 378                             strcmp(member_str, username) == 0) {
 379                                 groupvalue = __ns_ldap_getAttr(curEntry,
 380                                     "gidnumber");
 381                                 if (groupvalue == NULL ||
 382                                     groupvalue[0] == NULL) {
 383                                         /* Drop this group from the list */
 384                                         break;
 385                                 }
 386                                 errno = 0;
 387                                 gid = (gid_t)strtol(groupvalue[0],
 388                                     (char **)NULL, 10);
 389 
 390                                 if (errno == 0 &&
 391                                     argp->numgids < argp->maxgids) {
 392                                         for (k = 0; k < argp->numgids; k++) {
 393                                                 if (argp->gid_array[k] == gid)
 394                                                         /* already exists */
 395                                                         break;
 396                                         }
 397                                         if (k == argp->numgids)
 398                                                 argp->gid_array[argp->numgids++]
 399                                                     = gid;
 400                                 }
 401                                 break;
 402                         }
 403                 }
 404                 curEntry = curEntry->next;
 405         }
 406 
 407         (void) __ns_ldap_freeResult((ns_ldap_result_t **)&be->result);
 408         if (gcnt == argp->numgids)
 409                 return ((nss_status_t)NSS_NOTFOUND);
 410 
 411         /*
 412          * Return NSS_SUCCESS only if array is full.
 413          * Explained in <nss_dbdefs.h>.
 414          */
 415         return ((nss_status_t)((argp->numgids == argp->maxgids)
 416             ? NSS_SUCCESS
 417             : NSS_NOTFOUND));
 418 }
 419 
 420 static ldap_backend_op_t gr_ops[] = {
 421         _nss_ldap_destr,
 422         _nss_ldap_endent,
 423         _nss_ldap_setent,
 424         _nss_ldap_getent,
 425         getbynam,
 426         getbygid,
 427         getbymember
 428 };
 429 
 430 
 431 /*ARGSUSED0*/
 432 nss_backend_t *
 433 _nss_ldap_group_constr(const char *dummy1, const char *dummy2,
 434     const char *dummy3)
 435 {
 436 
 437         return ((nss_backend_t *)_nss_ldap_constr(gr_ops,
 438             sizeof (gr_ops)/sizeof (gr_ops[0]), _GROUP, gr_attrs,
 439             _nss_ldap_group2str));
 440 }