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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
23  * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
24  */
25 
26 #include <sys/types.h>
27 #include <sys/kmem.h>
28 #include <sys/ddi.h>
29 #include <sys/sunddi.h>
30 #include <sys/cmn_err.h>
31 #include <sys/door.h>
32 #include <smbsrv/smb_kproto.h>
33 #include <smbsrv/smb_door.h>
34 
35 #ifdef	_FAKE_KERNEL
36 #error	"See libfksmbsrv"
37 #endif	/* _FAKE_KERNEL */
38 
39 static int smb_kdoor_send(smb_server_t *, smb_doorarg_t *);
40 static int smb_kdoor_receive(smb_server_t *, smb_doorarg_t *);
41 static int smb_kdoor_upcall_private(smb_server_t *, smb_doorarg_t *);
42 static int smb_kdoor_encode(smb_doorarg_t *);
43 static int smb_kdoor_decode(smb_doorarg_t *);
44 static void smb_kdoor_sethdr(smb_doorarg_t *, uint32_t);
45 static boolean_t smb_kdoor_chkhdr(smb_doorarg_t *, smb_doorhdr_t *);
46 static void smb_kdoor_free(door_arg_t *);
47 
48 void
smb_kdoor_init(smb_server_t * sv)49 smb_kdoor_init(smb_server_t *sv)
50 {
51 	sv->sv_kdoor_id = -1;
52 	mutex_init(&sv->sv_kdoor_mutex, NULL, MUTEX_DEFAULT, NULL);
53 	cv_init(&sv->sv_kdoor_cv, NULL, CV_DEFAULT, NULL);
54 }
55 
56 void
smb_kdoor_fini(smb_server_t * sv)57 smb_kdoor_fini(smb_server_t *sv)
58 {
59 	smb_kdoor_close(sv);
60 	cv_destroy(&sv->sv_kdoor_cv);
61 	mutex_destroy(&sv->sv_kdoor_mutex);
62 }
63 
64 /*
65  * Open the door.  If the door is already open, close it first
66  * because the door-id has probably changed.
67  */
68 int
smb_kdoor_open(smb_server_t * sv,int door_id)69 smb_kdoor_open(smb_server_t *sv, int door_id)
70 {
71 	int rc;
72 
73 	smb_kdoor_close(sv);
74 
75 	mutex_enter(&sv->sv_kdoor_mutex);
76 	sv->sv_kdoor_ncall = 0;
77 
78 	if (sv->sv_kdoor_hd == NULL) {
79 		sv->sv_kdoor_id = door_id;
80 		sv->sv_kdoor_hd = door_ki_lookup(door_id);
81 	}
82 
83 	rc = (sv->sv_kdoor_hd == NULL)  ? -1 : 0;
84 	mutex_exit(&sv->sv_kdoor_mutex);
85 	return (rc);
86 }
87 
88 /*
89  * Close the door.
90  */
91 void
smb_kdoor_close(smb_server_t * sv)92 smb_kdoor_close(smb_server_t *sv)
93 {
94 	mutex_enter(&sv->sv_kdoor_mutex);
95 
96 	if (sv->sv_kdoor_hd != NULL) {
97 		while (sv->sv_kdoor_ncall > 0)
98 			cv_wait(&sv->sv_kdoor_cv, &sv->sv_kdoor_mutex);
99 
100 		door_ki_rele(sv->sv_kdoor_hd);
101 		sv->sv_kdoor_hd = NULL;
102 		sv->sv_kdoor_id = -1;
103 	}
104 
105 	mutex_exit(&sv->sv_kdoor_mutex);
106 }
107 
108 /*
109  * Wrapper to handle door call reference counting.
110  */
111 int
smb_kdoor_upcall(smb_server_t * sv,uint32_t cmd,void * req_data,xdrproc_t req_xdr,void * rsp_data,xdrproc_t rsp_xdr)112 smb_kdoor_upcall(smb_server_t *sv, uint32_t cmd,
113     void *req_data, xdrproc_t req_xdr,
114     void *rsp_data, xdrproc_t rsp_xdr)
115 {
116 	smb_doorarg_t	da;
117 	int		rc;
118 
119 	bzero(&da, sizeof (smb_doorarg_t));
120 	da.da_opcode = cmd;
121 	da.da_opname = smb_doorhdr_opname(cmd);
122 	da.da_req_xdr = req_xdr;
123 	da.da_rsp_xdr = rsp_xdr;
124 	da.da_req_data = req_data;
125 	da.da_rsp_data = rsp_data;
126 
127 	if ((req_data == NULL && req_xdr != NULL) ||
128 	    (rsp_data == NULL && rsp_xdr != NULL)) {
129 		cmn_err(CE_WARN, "smb_kdoor_upcall[%s]: invalid param",
130 		    da.da_opname);
131 		return (-1);
132 	}
133 
134 	if (rsp_data != NULL && rsp_xdr != NULL)
135 		da.da_flags = SMB_DF_ASYNC;
136 
137 	if ((da.da_event = smb_event_create(sv, SMB_EVENT_TIMEOUT)) == NULL)
138 		return (-1);
139 
140 	mutex_enter(&sv->sv_kdoor_mutex);
141 
142 	if (sv->sv_kdoor_hd == NULL) {
143 		mutex_exit(&sv->sv_kdoor_mutex);
144 
145 		if (smb_kdoor_open(sv, sv->sv_kdoor_id) != 0) {
146 			smb_event_destroy(da.da_event);
147 			return (-1);
148 		}
149 
150 		mutex_enter(&sv->sv_kdoor_mutex);
151 	}
152 
153 	sv->sv_kdoor_ncall++;
154 	mutex_exit(&sv->sv_kdoor_mutex);
155 
156 	if (da.da_flags & SMB_DF_ASYNC) {
157 		if ((rc = smb_kdoor_send(sv, &da)) == 0) {
158 			if (smb_event_wait(da.da_event) != 0)
159 				rc = -1;
160 			else
161 				rc = smb_kdoor_receive(sv, &da);
162 		}
163 	} else {
164 		if ((rc = smb_kdoor_encode(&da)) == 0) {
165 			if ((rc = smb_kdoor_upcall_private(sv, &da)) == 0)
166 				rc = smb_kdoor_decode(&da);
167 		}
168 		smb_kdoor_free(&da.da_arg);
169 	}
170 
171 	smb_event_destroy(da.da_event);
172 
173 	mutex_enter(&sv->sv_kdoor_mutex);
174 	if ((--sv->sv_kdoor_ncall) == 0)
175 		cv_signal(&sv->sv_kdoor_cv);
176 	mutex_exit(&sv->sv_kdoor_mutex);
177 	return (rc);
178 }
179 
180 /*
181  * Send the request half of the consumer's door call.
182  */
183 static int
smb_kdoor_send(smb_server_t * sv,smb_doorarg_t * outer_da)184 smb_kdoor_send(smb_server_t *sv, smb_doorarg_t *outer_da)
185 {
186 	smb_doorarg_t	da;
187 	int		rc;
188 
189 	bcopy(outer_da, &da, sizeof (smb_doorarg_t));
190 	da.da_rsp_xdr = NULL;
191 	da.da_rsp_data = NULL;
192 
193 	if (smb_kdoor_encode(&da) != 0)
194 		return (-1);
195 
196 	if ((rc = smb_kdoor_upcall_private(sv, &da)) == 0)
197 		rc = smb_kdoor_decode(&da);
198 
199 	smb_kdoor_free(&da.da_arg);
200 	return (rc);
201 }
202 
203 /*
204  * Get the response half for the consumer's door call.
205  */
206 static int
smb_kdoor_receive(smb_server_t * sv,smb_doorarg_t * outer_da)207 smb_kdoor_receive(smb_server_t *sv, smb_doorarg_t *outer_da)
208 {
209 	smb_doorarg_t	da;
210 	int		rc;
211 
212 	bcopy(outer_da, &da, sizeof (smb_doorarg_t));
213 	da.da_opcode = SMB_DR_ASYNC_RESPONSE;
214 	da.da_opname = smb_doorhdr_opname(da.da_opcode);
215 	da.da_flags &= ~SMB_DF_ASYNC;
216 	da.da_req_xdr = NULL;
217 	da.da_req_data = NULL;
218 
219 	if (smb_kdoor_encode(&da) != 0)
220 		return (-1);
221 
222 	if ((rc = smb_kdoor_upcall_private(sv, &da)) == 0)
223 		rc = smb_kdoor_decode(&da);
224 
225 	smb_kdoor_free(&da.da_arg);
226 	return (rc);
227 }
228 
229 /*
230  * We use a copy of the door arg because doorfs may change data_ptr
231  * and we want to detect that when freeing the door buffers.  After
232  * this call, response data must be referenced via rbuf and rsize.
233  */
234 static int
smb_kdoor_upcall_private(smb_server_t * sv,smb_doorarg_t * da)235 smb_kdoor_upcall_private(smb_server_t *sv, smb_doorarg_t *da)
236 {
237 	door_arg_t	door_arg;
238 	int		i;
239 	int		rc;
240 
241 	bcopy(&da->da_arg, &door_arg, sizeof (door_arg_t));
242 
243 	for (i = 0; i < SMB_DOOR_CALL_RETRIES; ++i) {
244 		if (smb_server_is_stopping(sv))
245 			return (-1);
246 
247 		if ((rc = door_ki_upcall_limited(sv->sv_kdoor_hd, &door_arg,
248 		    NULL, SIZE_MAX, 0)) == 0)
249 			break;
250 
251 		if (rc != EAGAIN && rc != EINTR)
252 			return (-1);
253 	}
254 
255 	if (rc != 0 || door_arg.data_size == 0 || door_arg.rsize == 0)
256 		return (-1);
257 
258 	da->da_arg.rbuf = door_arg.data_ptr;
259 	da->da_arg.rsize = door_arg.rsize;
260 	return (0);
261 }
262 
263 static int
smb_kdoor_encode(smb_doorarg_t * da)264 smb_kdoor_encode(smb_doorarg_t *da)
265 {
266 	XDR		xdrs;
267 	char		*buf;
268 	uint32_t	len;
269 
270 	len = xdr_sizeof(smb_doorhdr_xdr, &da->da_hdr);
271 	if (da->da_req_xdr != NULL)
272 		len += xdr_sizeof(da->da_req_xdr, da->da_req_data);
273 
274 	smb_kdoor_sethdr(da, len);
275 
276 	buf = kmem_zalloc(len, KM_SLEEP);
277 	xdrmem_create(&xdrs, buf, len, XDR_ENCODE);
278 
279 	if (!smb_doorhdr_xdr(&xdrs, &da->da_hdr)) {
280 		cmn_err(CE_WARN, "smb_kdoor_encode[%s]: header encode failed",
281 		    da->da_opname);
282 		kmem_free(buf, len);
283 		xdr_destroy(&xdrs);
284 		return (-1);
285 	}
286 
287 	if (da->da_req_xdr != NULL) {
288 		if (!da->da_req_xdr(&xdrs, da->da_req_data)) {
289 			cmn_err(CE_WARN, "smb_kdoor_encode[%s]: encode failed",
290 			    da->da_opname);
291 			kmem_free(buf, len);
292 			xdr_destroy(&xdrs);
293 			return (-1);
294 		}
295 	}
296 
297 	da->da_arg.data_ptr = buf;
298 	da->da_arg.data_size = len;
299 	da->da_arg.desc_ptr = NULL;
300 	da->da_arg.desc_num = 0;
301 	da->da_arg.rbuf = buf;
302 	da->da_arg.rsize = len;
303 
304 	xdr_destroy(&xdrs);
305 	return (0);
306 }
307 
308 /*
309  * Decode the response in rbuf and rsize.
310  */
311 static int
smb_kdoor_decode(smb_doorarg_t * da)312 smb_kdoor_decode(smb_doorarg_t *da)
313 {
314 	XDR		xdrs;
315 	smb_doorhdr_t	hdr;
316 	char		*rbuf = da->da_arg.rbuf;
317 	uint32_t	rsize = da->da_arg.rsize;
318 
319 	if (rbuf == NULL || rsize == 0) {
320 		cmn_err(CE_WARN, "smb_kdoor_decode[%s]: invalid param",
321 		    da->da_opname);
322 		return (-1);
323 	}
324 
325 	xdrmem_create(&xdrs, rbuf, rsize, XDR_DECODE);
326 
327 	if (!smb_doorhdr_xdr(&xdrs, &hdr)) {
328 		cmn_err(CE_WARN, "smb_kdoor_decode[%s]: header decode failed",
329 		    da->da_opname);
330 		xdr_destroy(&xdrs);
331 		return (-1);
332 	}
333 
334 	if (!smb_kdoor_chkhdr(da, &hdr)) {
335 		xdr_destroy(&xdrs);
336 		return (-1);
337 	}
338 
339 	if (hdr.dh_datalen != 0 && da->da_rsp_xdr != NULL) {
340 		if (!da->da_rsp_xdr(&xdrs, da->da_rsp_data)) {
341 			cmn_err(CE_WARN, "smb_kdoor_decode[%s]: decode failed",
342 			    da->da_opname);
343 			xdr_destroy(&xdrs);
344 			return (-1);
345 		}
346 	}
347 
348 	xdr_destroy(&xdrs);
349 	return (0);
350 }
351 
352 static void
smb_kdoor_sethdr(smb_doorarg_t * da,uint32_t datalen)353 smb_kdoor_sethdr(smb_doorarg_t *da, uint32_t datalen)
354 {
355 	smb_doorhdr_t	*hdr = &da->da_hdr;
356 
357 	bzero(hdr, sizeof (smb_doorhdr_t));
358 	hdr->dh_magic = SMB_DOOR_HDR_MAGIC;
359 	hdr->dh_flags = da->da_flags | SMB_DF_SYSSPACE;
360 	hdr->dh_op = da->da_opcode;
361 	hdr->dh_txid = smb_event_txid(da->da_event);
362 	hdr->dh_datalen = datalen;
363 	hdr->dh_door_rc = SMB_DOP_NOT_CALLED;
364 }
365 
366 static boolean_t
smb_kdoor_chkhdr(smb_doorarg_t * da,smb_doorhdr_t * hdr)367 smb_kdoor_chkhdr(smb_doorarg_t *da, smb_doorhdr_t *hdr)
368 {
369 	if ((hdr->dh_magic != SMB_DOOR_HDR_MAGIC) ||
370 	    (hdr->dh_op != da->da_hdr.dh_op) ||
371 	    (hdr->dh_txid != da->da_hdr.dh_txid)) {
372 		cmn_err(CE_WARN, "smb_kdoor_chkhdr[%s]: invalid header",
373 		    da->da_opname);
374 		return (B_FALSE);
375 	}
376 
377 	switch (hdr->dh_door_rc) {
378 	case SMB_DOP_SUCCESS:
379 		break;
380 
381 	/* SMB_DOP_EMPTYBUF is a "normal" error (silent). */
382 	case SMB_DOP_EMPTYBUF:
383 		return (B_FALSE);
384 
385 	default:
386 		cmn_err(CE_WARN, "smb_kdoor_chkhdr[%s]: call failed: %u",
387 		    da->da_opname, hdr->dh_door_rc);
388 		return (B_FALSE);
389 	}
390 
391 	return (B_TRUE);
392 }
393 
394 /*
395  * Free both the argument and result door buffers regardless of the status
396  * of the up-call.  The doorfs allocates a new buffer if the result buffer
397  * passed by the client is too small.
398  */
399 static void
smb_kdoor_free(door_arg_t * arg)400 smb_kdoor_free(door_arg_t *arg)
401 {
402 	if (arg->rbuf != NULL && arg->rbuf != arg->data_ptr)
403 		kmem_free(arg->rbuf, arg->rsize);
404 
405 	if (arg->data_ptr != NULL)
406 		kmem_free(arg->data_ptr, arg->data_size);
407 }
408