xref: /illumos-gate/usr/src/lib/libsmbfs/smb/connect.c (revision adee678425979226b2b55d1a0b39ce4c989382e9)
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  * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
27  */
28 
29 /*
30  * Functions to setup connections (TCP and/or NetBIOS)
31  * This has the fall-back logic for IP6, IP4, NBT
32  */
33 
34 #include <errno.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <strings.h>
38 #include <stdlib.h>
39 #include <unistd.h>
40 #include <netdb.h>
41 #include <libintl.h>
42 #include <xti.h>
43 #include <assert.h>
44 
45 #include <sys/types.h>
46 #include <sys/time.h>
47 #include <sys/byteorder.h>
48 #include <sys/socket.h>
49 #include <sys/fcntl.h>
50 
51 #include <netinet/in.h>
52 #include <netinet/tcp.h>
53 #include <arpa/inet.h>
54 #include <uuid/uuid.h>
55 
56 #include <netsmb/smb.h>
57 #include <netsmb/smb_lib.h>
58 #include <netsmb/mchain.h>
59 #include <netsmb/netbios.h>
60 #include <netsmb/nb_lib.h>
61 #include <netsmb/smb_dev.h>
62 
63 #include <cflib.h>
64 
65 #include "charsets.h"
66 #include "private.h"
67 #include "smb_crypt.h"
68 
69 static int
70 smb__ssnsetup(struct smb_ctx *ctx,
71 	struct mbdata *mbc1, struct mbdata *mbc2);
72 
73 int smb_ssnsetup_spnego(struct smb_ctx *, struct mbdata *);
74 
75 const char *
smb_iod_state_name(enum smbiod_state st)76 smb_iod_state_name(enum smbiod_state st)
77 {
78 	const char *n = "(?)";
79 
80 	switch (st) {
81 	case SMBIOD_ST_UNINIT:
82 		n = "UNINIT!";
83 		break;
84 	case SMBIOD_ST_IDLE:
85 		n = "IDLE";
86 		break;
87 	case SMBIOD_ST_RECONNECT:
88 		n = "RECONNECT";
89 		break;
90 	case SMBIOD_ST_RCFAILED:
91 		n = "RCFAILED";
92 		break;
93 	case SMBIOD_ST_CONNECTED:
94 		n = "CONNECTED";
95 		break;
96 	case SMBIOD_ST_NEGOTIATED:
97 		n = "NEGOTIATED";
98 		break;
99 	case SMBIOD_ST_AUTHCONT:
100 		n = "AUTHCONT";
101 		break;
102 	case SMBIOD_ST_AUTHFAIL:
103 		n = "AUTHFAIL";
104 		break;
105 	case SMBIOD_ST_AUTHOK:
106 		n = "AUTHOK";
107 		break;
108 	case SMBIOD_ST_VCACTIVE:
109 		n = "VCACTIVE";
110 		break;
111 	case SMBIOD_ST_DEAD:
112 		n = "DEAD";
113 		break;
114 	}
115 
116 	return (n);
117 }
118 
119 /*
120  * Make a new connection, or reconnect.
121  *
122  * This is called first from the door service thread in smbiod
123  * (so that can report success or failure to the door client)
124  * and thereafter it's called when we need to reconnect after a
125  * network outage (or whatever might cause connection loss).
126  */
127 int
smb_iod_connect(smb_ctx_t * ctx)128 smb_iod_connect(smb_ctx_t *ctx)
129 {
130 	smbioc_ossn_t *ossn = &ctx->ct_ssn;
131 	smbioc_ssn_work_t *work = &ctx->ct_work;
132 	char *uuid_str;
133 	int err;
134 	struct mbdata blob;
135 	char *nego_buf = NULL;
136 	uint32_t nego_len;
137 
138 	memset(&blob, 0, sizeof (blob));
139 
140 	if (ctx->ct_srvname[0] == '\0') {
141 		DPRINT("sername not set!");
142 		return (EINVAL);
143 	}
144 	DPRINT("server: %s", ctx->ct_srvname);
145 
146 	if (smb_debug)
147 		dump_ctx("smb_iod_connect", ctx);
148 
149 	/*
150 	 * Get local machine name.
151 	 * Full name - not a NetBIOS name.
152 	 */
153 	if (ctx->ct_locname == NULL) {
154 		err = smb_getlocalname(&ctx->ct_locname);
155 		if (err) {
156 			smb_error(dgettext(TEXT_DOMAIN,
157 			    "can't get local name"), err);
158 			return (err);
159 		}
160 	}
161 
162 	/*
163 	 * Get local machine uuid.
164 	 */
165 	uuid_str = cf_get_client_uuid();
166 	if (uuid_str == NULL) {
167 		err = EINVAL;
168 		smb_error(dgettext(TEXT_DOMAIN,
169 		    "can't get local UUID"), err);
170 			return (err);
171 	}
172 	(void) uuid_parse(uuid_str, ctx->ct_work.wk_cl_guid);
173 	free(uuid_str);
174 	uuid_str = NULL;
175 
176 	/*
177 	 * We're called with each IP address
178 	 * already copied into ct_srvaddr.
179 	 */
180 	ctx->ct_flags |= SMBCF_RESOLVED;
181 
182 	/*
183 	 * Ask the drvier to connect.
184 	 */
185 	DPRINT("Try ioctl connect...");
186 	if (nsmb_ioctl(ctx->ct_dev_fd, SMBIOC_IOD_CONNECT, work) < 0) {
187 		err = errno;
188 		smb_error(dgettext(TEXT_DOMAIN,
189 		    "%s: connect failed"),
190 		    err, ossn->ssn_srvname);
191 		return (err);
192 	}
193 	DPRINT("Connect OK, new state=%s",
194 	    smb_iod_state_name(work->wk_out_state));
195 
196 	/*
197 	 * Setup a buffer to recv the nego. hint.
198 	 */
199 	nego_len = 4096;
200 	err = mb_init_sz(&blob, nego_len);
201 	if (err)
202 		goto out;
203 	nego_buf = blob.mb_top->m_data;
204 	work->wk_u_auth_rbuf.lp_ptr = nego_buf;
205 	work->wk_u_auth_rlen = nego_len;
206 
207 	/*
208 	 * Ask the driver for SMB negotiate
209 	 */
210 	DPRINT("Try ioctl negotiate...");
211 	if (nsmb_ioctl(ctx->ct_dev_fd, SMBIOC_IOD_NEGOTIATE, work) < 0) {
212 		err = errno;
213 		smb_error(dgettext(TEXT_DOMAIN,
214 		    "%s: negotiate failed"),
215 		    err, ossn->ssn_srvname);
216 		goto out;
217 	}
218 	DPRINT("Negotiate OK, new state=%s",
219 	    smb_iod_state_name(work->wk_out_state));
220 
221 	nego_len = work->wk_u_auth_rlen;
222 	blob.mb_top->m_len = nego_len;
223 
224 	if (smb_debug) {
225 		DPRINT("Sec. blob: %d", nego_len);
226 		smb_hexdump(nego_buf, nego_len);
227 	}
228 
229 	/*
230 	 * Do SMB Session Setup (authenticate)
231 	 * Always "extended security" now (SPNEGO)
232 	 */
233 	DPRINT("Do session setup...");
234 	err = smb_ssnsetup_spnego(ctx, &blob);
235 	if (err != 0) {
236 		DPRINT("Session setup err=%d", err);
237 		goto out;
238 	}
239 
240 	/*
241 	 * Success! We return zero now, and our caller (normally
242 	 * the smbiod program) will then call smb_iod_work in a
243 	 * new thread to service this VC as long as necessary.
244 	 */
245 	DPRINT("Session setup OK");
246 
247 out:
248 	mb_done(&blob);
249 
250 	return (err);
251 }
252 
253 /*
254  * smb_ssnsetup_spnego
255  *
256  * This does an SMB session setup sequence using SPNEGO.
257  * The state changes seen during this sequence are there
258  * just to help track what's going on.
259  */
260 int
smb_ssnsetup_spnego(struct smb_ctx * ctx,struct mbdata * hint_mb)261 smb_ssnsetup_spnego(struct smb_ctx *ctx, struct mbdata *hint_mb)
262 {
263 	struct mbdata send_mb, recv_mb;
264 	smbioc_ssn_work_t *work = &ctx->ct_work;
265 	int		err;
266 
267 	bzero(&send_mb, sizeof (send_mb));
268 	bzero(&recv_mb, sizeof (recv_mb));
269 
270 	err = ssp_ctx_create_client(ctx, hint_mb);
271 	if (err)
272 		goto out;
273 
274 	/* NULL input indicates first call. */
275 	err = ssp_ctx_next_token(ctx, NULL, &send_mb);
276 	if (err) {
277 		DPRINT("smb__ssnsetup, ssp next, err=%d", err);
278 		goto out;
279 	}
280 	for (;;) {
281 		err = smb__ssnsetup(ctx, &send_mb, &recv_mb);
282 		DPRINT("smb__ssnsetup rc=%d, new state=%s", err,
283 		    smb_iod_state_name(work->wk_out_state));
284 
285 		if (err == 0) {
286 			/*
287 			 * Session setup complete w/ success.
288 			 * Should have state AUTHOK
289 			 */
290 			if (work->wk_out_state != SMBIOD_ST_AUTHOK) {
291 				DPRINT("Wrong state (expected AUTHOK)");
292 			}
293 			break;
294 		}
295 
296 		if (err != EINPROGRESS) {
297 			/*
298 			 * Session setup complete w/ failure.
299 			 * Should have state AUTHFAIL
300 			 */
301 			if (work->wk_out_state != SMBIOD_ST_AUTHFAIL) {
302 				DPRINT("Wrong state (expected AUTHFAIL)");
303 			}
304 			goto out;
305 		}
306 
307 		/*
308 		 * err == EINPROGRESS
309 		 * Session setup continuing.
310 		 * Should have state AUTHCONT
311 		 */
312 		if (work->wk_out_state != SMBIOD_ST_AUTHCONT) {
313 			DPRINT("Wrong state (expected AUTHCONT)");
314 		}
315 
316 		/* middle calls get both in, out */
317 		err = ssp_ctx_next_token(ctx, &recv_mb, &send_mb);
318 		if (err) {
319 			DPRINT("smb__ssnsetup, ssp next, err=%d", err);
320 			goto out;
321 		}
322 	}
323 
324 	/*
325 	 * Only get here via break in the err==0 case above,
326 	 * so we're finalizing a successful session setup.
327 	 *
328 	 * NULL output token here indicates the final call.
329 	 */
330 	(void) ssp_ctx_next_token(ctx, &recv_mb, NULL);
331 
332 	/*
333 	 * The session key is in ctx->ct_ssnkey_buf
334 	 * (a.k.a. ct_work.wk_u_ssn_key_buf)
335 	 */
336 
337 out:
338 	/* Done with ctx->ct_ssp_ctx */
339 	ssp_ctx_destroy(ctx);
340 
341 	return (err);
342 }
343 
344 int smb_max_authtok_sz = 0x10000;
345 
346 /*
347  * Session Setup function, calling the nsmb driver.
348  *
349  * Args
350  *	send_mb: [in]  outgoing blob data to send
351  *	recv_mb: [out] received blob data buffer
352  */
353 static int
smb__ssnsetup(struct smb_ctx * ctx,struct mbdata * send_mb,struct mbdata * recv_mb)354 smb__ssnsetup(struct smb_ctx *ctx,
355 	struct mbdata *send_mb, struct mbdata *recv_mb)
356 {
357 	smbioc_ossn_t *ossn = &ctx->ct_ssn;
358 	smbioc_ssn_work_t *work = &ctx->ct_work;
359 	mbuf_t *m;
360 	int err;
361 
362 	/* Setup receive buffer for the auth data. */
363 	err = mb_init_sz(recv_mb, smb_max_authtok_sz);
364 	if (err != 0)
365 		return (err);
366 	m = recv_mb->mb_top;
367 	work->wk_u_auth_rbuf.lp_ptr = m->m_data;
368 	work->wk_u_auth_rlen        = m->m_maxlen;
369 
370 	/* ... and the auth data to send. */
371 	m = send_mb->mb_top;
372 	work->wk_u_auth_wbuf.lp_ptr = m->m_data;
373 	work->wk_u_auth_wlen        = m->m_len;
374 
375 	DPRINT("Session setup ioctl...");
376 	if (nsmb_ioctl(ctx->ct_dev_fd, SMBIOC_IOD_SSNSETUP, work) < 0) {
377 		err = errno;
378 		if (err != 0 && err != EINPROGRESS) {
379 			smb_error(dgettext(TEXT_DOMAIN,
380 			    "%s: session setup "),
381 			    err, ossn->ssn_srvname);
382 		}
383 	}
384 	DPRINT("Session setup ret %d", err);
385 
386 	/* Free the auth data we sent. */
387 	mb_done(send_mb);
388 
389 	/* Setup length of received auth data */
390 	m = recv_mb->mb_top;
391 	m->m_len = work->wk_u_auth_rlen;
392 
393 	return (err);
394 }
395