xref: /illumos-gate/usr/src/uts/common/fs/smbclnt/netsmb/smb_pass.c (revision 66582b606a8194f7f3ba5b3a3a6dca5b0d346361)
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 
95 avl_tree_t smb_ptd; /* AVL password tree descriptor */
96 unsigned int smb_list_len = 0;	/* No. of elements in the tree. */
97 kmutex_t smb_ptd_lock; 	/* Mutex lock for controlled access */
98 
99 int smb_pkey_check(smbioc_pk_t *pk, cred_t *cr);
100 int 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  */
108 int
109 smb_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  */
151 void
152 smb_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  */
166 void
167 smb_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  */
178 int
179 smb_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 
190 static void
191 smb_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  */
204 int
205 smb_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  */
244 int
245 smb_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  */
275 int
276 smb_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  */
329 int
330 smb_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 
366 int
367 smb_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 
420 out:
421 	kmem_free(pk, sizeof (*pk));
422 	return (err);
423 }
424