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/*
23 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/*
28 * Password Keychain storage mechanism.
29 */
30
31#include <sys/types.h>
32#include <sys/param.h>
33#include <sys/errno.h>
34#include <sys/sysmacros.h>
35#include <sys/uio.h>
36#include <sys/buf.h>
37#include <sys/modctl.h>
38#include <sys/open.h>
39#include <sys/file.h>
40#include <sys/kmem.h>
41#include <sys/conf.h>
42#include <sys/cmn_err.h>
43#include <sys/stat.h>
44#include <sys/ddi.h>
45#include <sys/sunddi.h>
46#include <sys/sunldi.h>
47#include <sys/policy.h>
48#include <sys/zone.h>
49#include <sys/pathname.h>
50#include <sys/mount.h>
51#include <sys/sdt.h>
52#include <fs/fs_subr.h>
53#include <sys/devops.h>
54#include <sys/thread.h>
55#include <sys/mkdev.h>
56#include <sys/avl.h>
57#include <sys/avl_impl.h>
58#include <sys/u8_textprep.h>
59
60#include <netsmb/smb_osdep.h>
61
62#include <netsmb/smb.h>
63#include <netsmb/smb_conn.h>
64#include <netsmb/smb_subr.h>
65#include <netsmb/smb_dev.h>
66#include <netsmb/smb_pass.h>
67
68/*
69 * The smb_ptd is a cache of Uid's, User names, passwords and domain names.
70 * It will be used for storing the password information for a user and will
71 * be used to for connections without entering the pasword again if its
72 * already keyed in by the user. Its a kind of Key-Chain mechanism
73 * implemented by Apple folks.
74 */
75
76/*
77 * Information stored in the nodes:
78 * UID:  Uid of the person who initiated the login request.
79 * ZoneID: ZoneID of the zone from where the login request is initiated.
80 * Username: Username in the CIFS server.
81 * Srvdom: Domain name/ Server name of the CIFS server.
82 * Password: Password of the user.
83 * For more information, see smb_pass.h and sys/avl.h
84 */
85
86/*
87 * Information retrieved from the node.
88 * Node/password information can only be retrived with a call
89 * to smb_pkey_getpw(). Password never gets copied to the userspace.
90 * It will be copied to the Kernel data structure smbioc_ossn->ioc_password
91 * when needed for doing the "Session Setup". All other calls will return
92 * either a success or a failure.
93 */
94
95avl_tree_t smb_ptd; /* AVL password tree descriptor */
96unsigned int smb_list_len = 0;	/* No. of elements in the tree. */
97kmutex_t smb_ptd_lock; 	/* Mutex lock for controlled access */
98
99int smb_pkey_check(smbioc_pk_t *pk, cred_t *cr);
100int smb_pkey_deluid(uid_t ioc_uid, cred_t *cr);
101
102/*
103 * This routine is called by AVL tree calls when they want to find a
104 * node, find the next position in the tree to add or for deletion.
105 * Compare nodes from the tree to find the actual node based on
106 * uid/zoneid/username/domainname.
107 */
108int
109smb_pkey_cmp(const void *a, const void *b)
110{
111	const smb_passid_t *pa = (smb_passid_t *)a;
112	const smb_passid_t *pb = (smb_passid_t *)b;
113	int duser, dsrv, error;
114
115	ASSERT(MUTEX_HELD(&smb_ptd_lock));
116
117	/*
118	 * The nodes are added sorted on the uid/zoneid/domainname/username
119	 * We will do this:
120	 * Compare uid's. The owner who stored the node gets access.
121	 * Then zoneid to check if the access is from the same zone.
122	 * Compare usernames.
123	 * If the above are same, then compare domain/server names.
124	 */
125	if (pa->uid < pb->uid)
126		return (-1);
127	if (pa->uid > pb->uid)
128		return (+1);
129	if (pa->zoneid < pb->zoneid)
130		return (-1);
131	if (pa->zoneid > pb->zoneid)
132		return (+1);
133	dsrv = u8_strcmp(pa->srvdom, pb->srvdom, 0,
134	    U8_STRCMP_CI_LOWER, U8_UNICODE_LATEST, &error);
135	if (dsrv < 0)
136		return (-1);
137	if (dsrv > 0)
138		return (+1);
139	duser = u8_strcmp(pa->username, pb->username, 0,
140	    U8_STRCMP_CI_LOWER, U8_UNICODE_LATEST, &error);
141	if (duser < 0)
142		return (-1);
143	if (duser > 0)
144		return (+1);
145	return (0);
146}
147
148/*
149 * Initialization of the code that deals with uid and passwords.
150 */
151void
152smb_pkey_init()
153{
154	avl_create(&smb_ptd,
155	    smb_pkey_cmp,
156	    sizeof (smb_passid_t),
157	    offsetof(smb_passid_t,
158	    cpnode));
159	mutex_init(&smb_ptd_lock, NULL, MUTEX_DEFAULT, NULL);
160}
161
162/*
163 * Destroy the full AVL tree.
164 * Called just before unload.
165 */
166void
167smb_pkey_fini()
168{
169	(void) smb_pkey_deluid((uid_t)-1, kcred);
170	avl_destroy(&smb_ptd);
171	mutex_destroy(&smb_ptd_lock);
172}
173
174/*
175 * Driver unload calls this to ask if we
176 * have any stored passwords
177 */
178int
179smb_pkey_idle()
180{
181	int n;
182
183	mutex_enter(&smb_ptd_lock);
184	n = avl_numnodes(&smb_ptd);
185	mutex_exit(&smb_ptd_lock);
186
187	return ((n) ? EBUSY : 0);
188}
189
190static void
191smb_pkey_delete(smb_passid_t *tmp)
192{
193	ASSERT(MUTEX_HELD(&smb_ptd_lock));
194	avl_remove(&smb_ptd, tmp);
195	strfree(tmp->srvdom);
196	strfree(tmp->username);
197	kmem_free(tmp, sizeof (*tmp));
198}
199
200
201/*
202 * Remove a node from the AVL tree identified by cpid.
203 */
204int
205smb_pkey_del(smbioc_pk_t *pk, cred_t *cr)
206{
207	avl_index_t where;
208	smb_passid_t buf, *cpid, *tmp;
209	uid_t uid;
210
211	tmp = &buf;
212	uid = pk->pk_uid;
213	if (uid == (uid_t)-1)
214		uid = crgetruid(cr);
215	else {
216		if (secpolicy_smbfs_login(cr, uid))
217			return (EPERM);
218	}
219	tmp->uid = uid;
220	tmp->zoneid = getzoneid();
221	tmp->srvdom = pk->pk_dom;
222	tmp->username = pk->pk_usr;
223
224	mutex_enter(&smb_ptd_lock);
225	if ((cpid = (smb_passid_t *)avl_find(&smb_ptd,
226	    tmp, &where)) != NULL) {
227		smb_pkey_delete(cpid);
228	}
229	mutex_exit(&smb_ptd_lock);
230
231	return (0);
232}
233
234/*
235 * Delete the entries owned by a particular user
236 * based on uid. We go through all the nodes and
237 * delete the nodes whereever the uid matches.
238 *
239 * Also implements "delete all" when uid == -1.
240 *
241 * You must have privilege to use any uid other
242 * than your real uid.
243 */
244int
245smb_pkey_deluid(uid_t ioc_uid, cred_t *cr)
246{
247	smb_passid_t *cpid, *tmp;
248
249	if (secpolicy_smbfs_login(cr, ioc_uid))
250		return (EPERM);
251
252	mutex_enter(&smb_ptd_lock);
253	for (tmp = avl_first(&smb_ptd); tmp != NULL;
254	    tmp = cpid) {
255		cpid = AVL_NEXT(&smb_ptd, tmp);
256		if (ioc_uid == (uid_t)-1 ||
257		    ioc_uid == tmp->uid) {
258			/*
259			 * Delete the node.
260			 */
261			smb_pkey_delete(tmp);
262		}
263	}
264	mutex_exit(&smb_ptd_lock);
265
266	return (0);
267}
268
269/*
270 * Add entry or modify existing.
271 * Check for existing entry..
272 * If present, delete.
273 * Now, add the new entry.
274 */
275int
276smb_pkey_add(smbioc_pk_t *pk, cred_t *cr)
277{
278	avl_tree_t *t = &smb_ptd;
279	avl_index_t	where;
280	smb_passid_t *tmp, *cpid;
281	int ret;
282	uid_t uid;
283
284	uid = pk->pk_uid;
285	if (uid == (uid_t)-1)
286		uid = crgetruid(cr);
287	else {
288		if (secpolicy_smbfs_login(cr, uid))
289			return (EPERM);
290	}
291	cpid = kmem_zalloc(sizeof (smb_passid_t), KM_SLEEP);
292	cpid->uid = uid;
293	cpid->zoneid = getzoneid();
294	cpid->srvdom = strdup(pk->pk_dom);
295	cpid->username = strdup(pk->pk_usr);
296	bcopy(pk->pk_lmhash, cpid->lmhash, SMBIOC_HASH_SZ);
297	bcopy(pk->pk_nthash, cpid->nthash, SMBIOC_HASH_SZ);
298
299	/*
300	 * XXX: Instead of calling smb_pkey_check here,
301	 * should call avl_find directly, and hold the
302	 * lock across: avl_find, avl_remove, avl_insert.
303	 */
304
305	/* If it already exists, delete it. */
306	ret = smb_pkey_check(pk, cr);
307	if (ret == 0) {
308		(void) smb_pkey_del(pk, cr);
309	}
310
311	mutex_enter(&smb_ptd_lock);
312	tmp = (smb_passid_t *)avl_find(t, cpid, &where);
313	if (tmp == NULL) {
314		avl_insert(t, cpid, where);
315	} else {
316		strfree(cpid->srvdom);
317		strfree(cpid->username);
318		kmem_free(cpid, sizeof (smb_passid_t));
319	}
320	mutex_exit(&smb_ptd_lock);
321
322	return (0);
323}
324
325/*
326 * Determine if a node with uid,zoneid, uname & dname exists in the tree
327 * given the information, and if found, return the hashes.
328 */
329int
330smb_pkey_check(smbioc_pk_t *pk, cred_t *cr)
331{
332	avl_tree_t *t = &smb_ptd;
333	avl_index_t	where;
334	smb_passid_t *tmp, *cpid;
335	int error = ENOENT;
336	uid_t uid;
337
338	uid = pk->pk_uid;
339	if (uid == (uid_t)-1)
340		uid = crgetruid(cr);
341	else {
342		if (secpolicy_smbfs_login(cr, uid))
343			return (EPERM);
344	}
345	cpid = kmem_alloc(sizeof (smb_passid_t), KM_SLEEP);
346	cpid->uid = uid;
347	cpid->zoneid = getzoneid();
348	cpid->srvdom = pk->pk_dom;
349	cpid->username = pk->pk_usr;
350
351	mutex_enter(&smb_ptd_lock);
352	tmp = (smb_passid_t *)avl_find(t, cpid, &where);
353	mutex_exit(&smb_ptd_lock);
354
355	if (tmp != NULL) {
356		bcopy(tmp->lmhash, pk->pk_lmhash, SMBIOC_HASH_SZ);
357		bcopy(tmp->nthash, pk->pk_nthash, SMBIOC_HASH_SZ);
358		error = 0;
359	}
360
361	kmem_free(cpid, sizeof (smb_passid_t));
362	return (error);
363}
364
365
366int
367smb_pkey_ioctl(int cmd, intptr_t arg, int flags, cred_t *cr)
368{
369	smbioc_pk_t  *pk;
370	uid_t uid;
371	int err = 0;
372
373	pk = kmem_alloc(sizeof (*pk), KM_SLEEP);
374
375	switch (cmd) {
376	case SMBIOC_PK_ADD:
377	case SMBIOC_PK_DEL:
378	case SMBIOC_PK_CHK:
379		if (ddi_copyin((void *)arg, pk,
380		    sizeof (*pk), flags)) {
381			err = EFAULT;
382			goto out;
383		}
384		/* Make strlen (etc) on these safe. */
385		pk->pk_dom[SMBIOC_MAX_NAME-1] = '\0';
386		pk->pk_usr[SMBIOC_MAX_NAME-1] = '\0';
387		break;
388	}
389
390	switch (cmd) {
391	case SMBIOC_PK_ADD:
392		err = smb_pkey_add(pk, cr);
393		break;
394
395	case SMBIOC_PK_DEL:
396		err = smb_pkey_del(pk, cr);
397		break;
398
399	case SMBIOC_PK_CHK:
400		err = smb_pkey_check(pk, cr);
401		/* This is just a hash now. */
402		(void) ddi_copyout(pk, (void *)arg,
403		    sizeof (*pk), flags);
404		break;
405
406	case SMBIOC_PK_DEL_OWNER:
407		uid = crgetruid(cr);
408		err = smb_pkey_deluid(uid, cr);
409		break;
410
411	case SMBIOC_PK_DEL_EVERYONE:
412		uid = (uid_t)-1;
413		err = smb_pkey_deluid(uid, cr);
414		break;
415
416	default:
417		err = ENODEV;
418	}
419
420out:
421	kmem_free(pk, sizeof (*pk));
422	return (err);
423}
424