xref: /illumos-gate/usr/src/lib/smbsrv/libsmb/common/smb_sam.c (revision 09a48d4ca0ddda4ad26cc885769745870d989baf)
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 2011 Nexenta Systems, Inc.  All rights reserved.
23  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <strings.h>
28 #include <smbsrv/libsmb.h>
29 
30 extern int smb_pwd_num(void);
31 extern int smb_lgrp_numbydomain(smb_domain_type_t, int *);
32 
33 static uint32_t smb_sam_lookup_user(char *, smb_sid_t **);
34 static uint32_t smb_sam_lookup_group(char *, smb_sid_t **);
35 
36 /*
37  * Local well-known accounts data structure table and prototypes
38  */
39 typedef struct smb_lwka {
40 	uint32_t	lwka_rid;
41 	char		*lwka_name;
42 	uint16_t	lwka_type;
43 } smb_lwka_t;
44 
45 static smb_lwka_t lwka_tbl[] = {
46 	{ 500, "Administrator", SidTypeUser },
47 	{ 501, "Guest", SidTypeUser },
48 	{ 502, "KRBTGT", SidTypeUser },
49 	{ 512, "Domain Admins", SidTypeGroup },
50 	{ 513, "Domain Users", SidTypeGroup },
51 	{ 514, "Domain Guests", SidTypeGroup },
52 	{ 516, "Domain Controllers", SidTypeGroup },
53 	{ 517, "Cert Publishers", SidTypeGroup },
54 	{ 518, "Schema Admins", SidTypeGroup },
55 	{ 519, "Enterprise Admins", SidTypeGroup },
56 	{ 520, "Global Policy Creator Owners", SidTypeGroup },
57 	{ 533, "RAS and IAS Servers", SidTypeGroup }
58 };
59 
60 #define	SMB_LWKA_NUM	(sizeof (lwka_tbl)/sizeof (lwka_tbl[0]))
61 
62 static smb_lwka_t *smb_lwka_lookup_name(char *);
63 static smb_lwka_t *smb_lwka_lookup_sid(smb_sid_t *);
64 
65 /*
66  * Looks up the given name in local account databases:
67  *
68  * SMB Local users are looked up in /var/smb/smbpasswd
69  * SMB Local groups are looked up in /var/smb/smbgroup.db
70  *
71  * If the account is found, its information is populated
72  * in the passed smb_account_t structure. Caller must free
73  * allocated memories by calling smb_account_free() upon
74  * successful return.
75  *
76  * The type of account is specified by 'type', which can be user,
77  * alias (local group) or unknown. If the caller doesn't know
78  * whether the name is a user or group name then SidTypeUnknown
79  * should be passed.
80  *
81  * If a local user and group have the same name, the user will
82  * always be picked. Note that this situation cannot happen on
83  * Windows systems.
84  *
85  * If a SMB local user/group is found but it turns out that
86  * it'll be mapped to a domain user/group the lookup is considered
87  * failed and NT_STATUS_NONE_MAPPED is returned.
88  *
89  * Return status:
90  *
91  *   NT_STATUS_NOT_FOUND	This is not a local account
92  *   NT_STATUS_NONE_MAPPED	It's a local account but cannot be
93  *   				translated.
94  *   other error status codes.
95  */
96 uint32_t
97 smb_sam_lookup_name(char *domain, char *name, uint16_t type,
98     smb_account_t *account)
99 {
100 	smb_domain_t di;
101 	smb_sid_t *sid;
102 	uint32_t status;
103 	smb_lwka_t *lwka;
104 
105 	bzero(account, sizeof (smb_account_t));
106 
107 	if (domain != NULL) {
108 		if (!smb_domain_lookup_name(domain, &di) ||
109 		    (di.di_type != SMB_DOMAIN_LOCAL))
110 			return (NT_STATUS_NOT_FOUND);
111 
112 		/* Only Netbios hostname is accepted */
113 		if (smb_strcasecmp(domain, di.di_nbname, 0) != 0)
114 			return (NT_STATUS_NONE_MAPPED);
115 	} else {
116 		if (!smb_domain_lookup_type(SMB_DOMAIN_LOCAL, &di))
117 			return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO);
118 	}
119 
120 	if (smb_strcasecmp(name, di.di_nbname, 0) == 0) {
121 		/* This is the local domain name */
122 		account->a_type = SidTypeDomain;
123 		account->a_name = strdup("");
124 		account->a_domain = strdup(di.di_nbname);
125 		account->a_sid = smb_sid_dup(di.di_binsid);
126 		account->a_domsid = smb_sid_dup(di.di_binsid);
127 		account->a_rid = (uint32_t)-1;
128 
129 		if (!smb_account_validate(account)) {
130 			smb_account_free(account);
131 			return (NT_STATUS_NO_MEMORY);
132 		}
133 
134 		return (NT_STATUS_SUCCESS);
135 	}
136 
137 	if ((lwka = smb_lwka_lookup_name(name)) != NULL) {
138 		sid = smb_sid_splice(di.di_binsid, lwka->lwka_rid);
139 		type = lwka->lwka_type;
140 	} else {
141 		switch (type) {
142 		case SidTypeUser:
143 			status = smb_sam_lookup_user(name, &sid);
144 			if (status != NT_STATUS_SUCCESS)
145 				return (status);
146 			break;
147 
148 		case SidTypeAlias:
149 			status = smb_sam_lookup_group(name, &sid);
150 			if (status != NT_STATUS_SUCCESS)
151 				return (status);
152 			break;
153 
154 		case SidTypeUnknown:
155 			type = SidTypeUser;
156 			status = smb_sam_lookup_user(name, &sid);
157 			if (status == NT_STATUS_SUCCESS)
158 				break;
159 
160 			if (status == NT_STATUS_NONE_MAPPED)
161 				return (status);
162 
163 			type = SidTypeAlias;
164 			status = smb_sam_lookup_group(name, &sid);
165 			if (status != NT_STATUS_SUCCESS)
166 				return (status);
167 			break;
168 
169 		default:
170 			return (NT_STATUS_INVALID_PARAMETER);
171 		}
172 	}
173 
174 	account->a_name = strdup(name);
175 	account->a_sid = sid;
176 	account->a_domain = strdup(di.di_nbname);
177 	account->a_domsid = smb_sid_split(sid, &account->a_rid);
178 	account->a_type = type;
179 
180 	if (!smb_account_validate(account)) {
181 		smb_account_free(account);
182 		return (NT_STATUS_NO_MEMORY);
183 	}
184 
185 	return (NT_STATUS_SUCCESS);
186 }
187 
188 /*
189  * Looks up the given SID in local account databases:
190  *
191  * SMB Local users are looked up in /var/smb/smbpasswd
192  * SMB Local groups are looked up in /var/smb/smbgroup.db
193  *
194  * If the account is found, its information is populated
195  * in the passed smb_account_t structure. Caller must free
196  * allocated memories by calling smb_account_free() upon
197  * successful return.
198  *
199  * Return status:
200  *
201  *   NT_STATUS_NOT_FOUND	This is not a local account
202  *   NT_STATUS_NONE_MAPPED	It's a local account but cannot be
203  *   				translated.
204  *   other error status codes.
205  */
206 uint32_t
207 smb_sam_lookup_sid(smb_sid_t *sid, smb_account_t *account)
208 {
209 	char hostname[MAXHOSTNAMELEN];
210 	smb_passwd_t smbpw;
211 	smb_group_t grp;
212 	smb_lwka_t *lwka;
213 	smb_domain_t di;
214 	uint32_t rid;
215 	uid_t id;
216 	int id_type;
217 	int rc;
218 
219 	bzero(account, sizeof (smb_account_t));
220 
221 	if (!smb_domain_lookup_type(SMB_DOMAIN_LOCAL, &di))
222 		return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO);
223 
224 	if (smb_sid_cmp(sid, di.di_binsid)) {
225 		/* This is the local domain SID */
226 		account->a_type = SidTypeDomain;
227 		account->a_name = strdup("");
228 		account->a_domain = strdup(di.di_nbname);
229 		account->a_sid = smb_sid_dup(sid);
230 		account->a_domsid = smb_sid_dup(sid);
231 		account->a_rid = (uint32_t)-1;
232 
233 		if (!smb_account_validate(account)) {
234 			smb_account_free(account);
235 			return (NT_STATUS_NO_MEMORY);
236 		}
237 
238 		return (NT_STATUS_SUCCESS);
239 	}
240 
241 	if (!smb_sid_indomain(di.di_binsid, sid)) {
242 		/* This is not a local SID */
243 		return (NT_STATUS_NOT_FOUND);
244 	}
245 
246 	if ((lwka = smb_lwka_lookup_sid(sid)) != NULL) {
247 		account->a_type = lwka->lwka_type;
248 		account->a_name = strdup(lwka->lwka_name);
249 	} else {
250 		id_type = SMB_IDMAP_UNKNOWN;
251 		if (smb_idmap_getid(sid, &id, &id_type) != IDMAP_SUCCESS)
252 			return (NT_STATUS_NONE_MAPPED);
253 
254 		switch (id_type) {
255 		case SMB_IDMAP_USER:
256 			account->a_type = SidTypeUser;
257 			if (smb_pwd_getpwuid(id, &smbpw) == NULL)
258 				return (NT_STATUS_NO_SUCH_USER);
259 
260 			account->a_name = strdup(smbpw.pw_name);
261 			break;
262 
263 		case SMB_IDMAP_GROUP:
264 			account->a_type = SidTypeAlias;
265 			(void) smb_sid_getrid(sid, &rid);
266 			rc = smb_lgrp_getbyrid(rid, SMB_DOMAIN_LOCAL, &grp);
267 			if (rc != SMB_LGRP_SUCCESS)
268 				return (NT_STATUS_NO_SUCH_ALIAS);
269 
270 			account->a_name = strdup(grp.sg_name);
271 			smb_lgrp_free(&grp);
272 			break;
273 
274 		default:
275 			return (NT_STATUS_NONE_MAPPED);
276 		}
277 	}
278 
279 	if (smb_getnetbiosname(hostname, MAXHOSTNAMELEN) == 0)
280 		account->a_domain = strdup(hostname);
281 	account->a_sid = smb_sid_dup(sid);
282 	account->a_domsid = smb_sid_split(sid, &account->a_rid);
283 
284 	if (!smb_account_validate(account)) {
285 		smb_account_free(account);
286 		return (NT_STATUS_NO_MEMORY);
287 	}
288 
289 	return (NT_STATUS_SUCCESS);
290 }
291 
292 /*
293  * Returns number of SMB users, i.e. users who have entry
294  * in /var/smb/smbpasswd
295  */
296 int
297 smb_sam_usr_cnt(void)
298 {
299 	return (smb_pwd_num());
300 }
301 
302 /*
303  * Updates a list of groups in which the given user is a member
304  * by adding any local (SAM) groups.
305  *
306  * We are a member of local groups where the local group
307  * contains either the user's primary SID, or any of their
308  * other SIDs such as from domain groups, SID history, etc.
309  * We can have indirect membership via domain groups.
310  */
311 uint32_t
312 smb_sam_usr_groups(smb_sid_t *user_sid, smb_ids_t *gids)
313 {
314 	smb_ids_t new_gids;
315 	smb_id_t *ids, *new_ids;
316 	smb_giter_t gi;
317 	smb_group_t lgrp;
318 	int i, gcnt, total_cnt;
319 	uint32_t ret;
320 	boolean_t member;
321 
322 	/*
323 	 * First pass: count groups to be added (gcnt)
324 	 */
325 	gcnt = 0;
326 	if (smb_lgrp_iteropen(&gi) != SMB_LGRP_SUCCESS)
327 		return (NT_STATUS_INTERNAL_ERROR);
328 
329 	while (smb_lgrp_iterate(&gi, &lgrp) == SMB_LGRP_SUCCESS) {
330 		member = B_FALSE;
331 		if (smb_lgrp_is_member(&lgrp, user_sid))
332 			member = B_TRUE;
333 		else for (i = 0, ids = gids->i_ids;
334 		    i < gids->i_cnt; i++, ids++) {
335 			if (smb_lgrp_is_member(&lgrp, ids->i_sid)) {
336 				member = B_TRUE;
337 				break;
338 			}
339 		}
340 		/* Careful: only count lgrp once */
341 		if (member)
342 			gcnt++;
343 		smb_lgrp_free(&lgrp);
344 	}
345 	smb_lgrp_iterclose(&gi);
346 
347 	if (gcnt == 0)
348 		return (NT_STATUS_SUCCESS);
349 
350 	/*
351 	 * Second pass: add to groups list.
352 	 * Do not modify gcnt after here.
353 	 */
354 	if (smb_lgrp_iteropen(&gi) != SMB_LGRP_SUCCESS)
355 		return (NT_STATUS_INTERNAL_ERROR);
356 
357 	/*
358 	 * Expand the list (copy to a new, larger one)
359 	 * Note: were're copying pointers from the old
360 	 * array to the new (larger) array, and then
361 	 * adding new pointers after what we copied.
362 	 */
363 	ret = 0;
364 	new_gids.i_cnt = gids->i_cnt;
365 	total_cnt = gids->i_cnt + gcnt;
366 	new_gids.i_ids = malloc(total_cnt * sizeof (smb_id_t));
367 	if (new_gids.i_ids == NULL) {
368 		ret = NT_STATUS_NO_MEMORY;
369 		goto out;
370 	}
371 	(void) memcpy(new_gids.i_ids, gids->i_ids,
372 	    gids->i_cnt * sizeof (smb_id_t));
373 	new_ids = new_gids.i_ids + gids->i_cnt;
374 	(void) memset(new_ids, 0, gcnt * sizeof (smb_id_t));
375 
376 	/*
377 	 * Add group SIDs starting at the end of the
378 	 * previous list.  (new_ids)
379 	 */
380 	while (smb_lgrp_iterate(&gi, &lgrp) == SMB_LGRP_SUCCESS) {
381 		member = B_FALSE;
382 		if (smb_lgrp_is_member(&lgrp, user_sid))
383 			member = B_TRUE;
384 		else for (i = 0, ids = gids->i_ids;
385 		    i < gids->i_cnt; i++, ids++) {
386 			if (smb_lgrp_is_member(&lgrp, ids->i_sid)) {
387 				member = B_TRUE;
388 				break;
389 			}
390 		}
391 		if (member && (new_gids.i_cnt < (gids->i_cnt + gcnt))) {
392 			new_ids->i_sid = smb_sid_dup(lgrp.sg_id.gs_sid);
393 			if (new_ids->i_sid == NULL) {
394 				smb_lgrp_free(&lgrp);
395 				ret = NT_STATUS_NO_MEMORY;
396 				goto out;
397 			}
398 			new_ids->i_attrs = lgrp.sg_attr;
399 			new_ids++;
400 			new_gids.i_cnt++;
401 		}
402 		smb_lgrp_free(&lgrp);
403 	}
404 
405 out:
406 	smb_lgrp_iterclose(&gi);
407 
408 	if (ret != 0) {
409 		if (new_gids.i_ids != NULL) {
410 			/*
411 			 * Free only the new sids we added.
412 			 * The old ones were copied ptrs.
413 			 */
414 			ids = new_gids.i_ids + gids->i_cnt;
415 			for (i = 0; i < gcnt; i++, ids++) {
416 				smb_sid_free(ids->i_sid);
417 			}
418 			free(new_gids.i_ids);
419 		}
420 		return (ret);
421 	}
422 
423 	/*
424 	 * Success! Update passed gids and
425 	 * free the old array.
426 	 */
427 	free(gids->i_ids);
428 	*gids = new_gids;
429 
430 	return (NT_STATUS_SUCCESS);
431 }
432 
433 /*
434  * Returns the number of built-in or local groups stored
435  * in /var/smb/smbgroup.db
436  */
437 int
438 smb_sam_grp_cnt(smb_domain_type_t dtype)
439 {
440 	int grpcnt;
441 	int rc;
442 
443 	switch (dtype) {
444 	case SMB_DOMAIN_BUILTIN:
445 		rc = smb_lgrp_numbydomain(SMB_DOMAIN_BUILTIN, &grpcnt);
446 		break;
447 
448 	case SMB_DOMAIN_LOCAL:
449 		rc = smb_lgrp_numbydomain(SMB_DOMAIN_LOCAL, &grpcnt);
450 		break;
451 
452 	default:
453 		rc = SMB_LGRP_INVALID_ARG;
454 	}
455 
456 	return ((rc == SMB_LGRP_SUCCESS) ? grpcnt : 0);
457 }
458 
459 /*
460  * Determines whether the given SID is a member of the group
461  * specified by gname.
462  */
463 boolean_t
464 smb_sam_grp_ismember(const char *gname, smb_sid_t *sid)
465 {
466 	smb_group_t grp;
467 	boolean_t ismember = B_FALSE;
468 
469 	if (smb_lgrp_getbyname((char *)gname, &grp) == SMB_LGRP_SUCCESS) {
470 		ismember = smb_lgrp_is_member(&grp, sid);
471 		smb_lgrp_free(&grp);
472 	}
473 
474 	return (ismember);
475 }
476 
477 /*
478  * Frees memories allocated for the passed account fields.
479  */
480 void
481 smb_account_free(smb_account_t *account)
482 {
483 	free(account->a_name);
484 	free(account->a_domain);
485 	smb_sid_free(account->a_sid);
486 	smb_sid_free(account->a_domsid);
487 }
488 
489 /*
490  * Validates the given account.
491  */
492 boolean_t
493 smb_account_validate(smb_account_t *account)
494 {
495 	return ((account->a_name != NULL) && (account->a_sid != NULL) &&
496 	    (account->a_domain != NULL) && (account->a_domsid != NULL));
497 }
498 
499 /*
500  * Lookup local SMB user account database (/var/smb/smbpasswd)
501  * if there's a match query its SID from idmap service and make
502  * sure the SID is a local SID.
503  *
504  * The memory for the returned SID must be freed by the caller.
505  */
506 static uint32_t
507 smb_sam_lookup_user(char *name, smb_sid_t **sid)
508 {
509 	smb_passwd_t smbpw;
510 
511 	if (smb_pwd_getpwnam(name, &smbpw) == NULL)
512 		return (NT_STATUS_NO_SUCH_USER);
513 
514 	if (smb_idmap_getsid(smbpw.pw_uid, SMB_IDMAP_USER, sid)
515 	    != IDMAP_SUCCESS)
516 		return (NT_STATUS_NONE_MAPPED);
517 
518 	if (!smb_sid_islocal(*sid)) {
519 		smb_sid_free(*sid);
520 		return (NT_STATUS_NONE_MAPPED);
521 	}
522 
523 	return (NT_STATUS_SUCCESS);
524 }
525 
526 /*
527  * Lookup local SMB group account database (/var/smb/smbgroup.db)
528  * The memory for the returned SID must be freed by the caller.
529  */
530 static uint32_t
531 smb_sam_lookup_group(char *name, smb_sid_t **sid)
532 {
533 	smb_group_t grp;
534 
535 	if (smb_lgrp_getbyname(name, &grp) != SMB_LGRP_SUCCESS)
536 		return (NT_STATUS_NO_SUCH_ALIAS);
537 
538 	*sid = smb_sid_dup(grp.sg_id.gs_sid);
539 	smb_lgrp_free(&grp);
540 
541 	return ((*sid == NULL) ? NT_STATUS_NO_MEMORY : NT_STATUS_SUCCESS);
542 }
543 
544 static smb_lwka_t *
545 smb_lwka_lookup_name(char *name)
546 {
547 	int i;
548 
549 	for (i = 0; i < SMB_LWKA_NUM; i++) {
550 		if (smb_strcasecmp(name, lwka_tbl[i].lwka_name, 0) == 0)
551 			return (&lwka_tbl[i]);
552 	}
553 
554 	return (NULL);
555 }
556 
557 static smb_lwka_t *
558 smb_lwka_lookup_sid(smb_sid_t *sid)
559 {
560 	uint32_t rid;
561 	int i;
562 
563 	(void) smb_sid_getrid(sid, &rid);
564 	if (rid > 999)
565 		return (NULL);
566 
567 	for (i = 0; i < SMB_LWKA_NUM; i++) {
568 		if (rid == lwka_tbl[i].lwka_rid)
569 			return (&lwka_tbl[i]);
570 	}
571 
572 	return (NULL);
573 }
574