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