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