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 } 441