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