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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2020 Tintri by DDN, Inc. All rights reserved.
25  * Copyright 2023 RackTop Systems, Inc.
26  */
27 
28 /*
29  * Client NDR RPC interface.
30  */
31 
32 #include <sys/types.h>
33 #include <sys/errno.h>
34 #include <sys/fcntl.h>
35 #include <time.h>
36 #include <strings.h>
37 #include <assert.h>
38 #include <errno.h>
39 #include <thread.h>
40 #include <syslog.h>
41 #include <synch.h>
42 
43 #include <libmlrpc/libmlrpc.h>
44 #include <netsmb/smbfs_api.h>
45 
46 #include <smbsrv/libsmb.h>
47 #include <smbsrv/libmlsvc.h>
48 #include <libsmbrdr.h>
49 #include <mlsvc.h>
50 
51 
52 /*
53  * This call must be made to initialize an RPC client structure and bind
54  * to the remote service before any RPCs can be exchanged with that service.
55  *
56  * The mlsvc_handle_t is a wrapper that is used to associate an RPC handle
57  * with the client context for an instance of the interface.  The handle
58  * is zeroed to ensure that it doesn't look like a valid handle -
59  * handle content is provided by the remove service.
60  *
61  * The client points to this top-level handle so that we know when to
62  * unbind and teardown the connection.  As each handle is initialized it
63  * will inherit a reference to the client context.
64  *
65  * Returns 0 or an NT_STATUS:		(failed in...)
66  *
67  *	NT_STATUS_BAD_NETWORK_PATH	(get server addr)
68  *	NT_STATUS_NETWORK_ACCESS_DENIED	(connect, auth)
69  *	NT_STATUS_BAD_NETWORK_NAME	(tcon)
70  *	RPC_NT_SERVER_TOO_BUSY		(open pipe)
71  *	RPC_NT_SERVER_UNAVAILABLE	(open pipe)
72  *	NT_STATUS_ACCESS_DENIED		(open pipe)
73  *	NT_STATUS_INVALID_PARAMETER	(rpc bind)
74  *	NT_STATUS_INTERNAL_ERROR	(bad args etc)
75  *	NT_STATUS_NO_MEMORY
76  */
77 static DWORD
ndr_rpc_bind_common(mlsvc_handle_t * handle,char * server,char * domain,char * username,const char * service,ndr_auth_ctx_t * auth_ctx)78 ndr_rpc_bind_common(mlsvc_handle_t *handle, char *server, char *domain,
79     char *username, const char *service, ndr_auth_ctx_t *auth_ctx)
80 {
81 	struct smb_ctx		*ctx = NULL;
82 	ndr_service_t		*svc;
83 	DWORD			status;
84 	int			rc;
85 
86 	if (handle == NULL || server == NULL || server[0] == '\0' ||
87 	    domain == NULL || username == NULL)
88 		return (NT_STATUS_INTERNAL_ERROR);
89 
90 	/* In case the service was not registered... */
91 	if ((svc = ndr_svc_lookup_name(service)) == NULL)
92 		return (NT_STATUS_INTERNAL_ERROR);
93 
94 	/*
95 	 * Some callers pass this when they want a NULL session.
96 	 * Todo: have callers pass an empty string for that.
97 	 */
98 	if (strcmp(username, MLSVC_ANON_USER) == 0)
99 		username = "";
100 
101 	/*
102 	 * Setup smbfs library handle, authenticate, connect to
103 	 * the IPC$ share.  This will reuse an existing connection
104 	 * if the driver already has one for this combination of
105 	 * server, user, domain.  It may return any of:
106 	 *	NT_STATUS_BAD_NETWORK_PATH	(get server addr)
107 	 *	NT_STATUS_NETWORK_ACCESS_DENIED	(connect, auth)
108 	 *	NT_STATUS_BAD_NETWORK_NAME	(tcon)
109 	 */
110 	status = smbrdr_ctx_new(&ctx, server, domain, username);
111 	if (status != NT_STATUS_SUCCESS) {
112 		syslog(LOG_ERR, "ndr_rpc_bind: smbrdr_ctx_new"
113 		    "(Srv=%s Dom=%s User=%s Svc=%s), %s (0x%x)",
114 		    server, domain, username, service,
115 		    xlate_nt_status(status), status);
116 		/*
117 		 * If the error is one where changing to a new DC
118 		 * might help, try looking for a different DC.
119 		 */
120 		switch (status) {
121 		case NT_STATUS_BAD_NETWORK_PATH:
122 		case NT_STATUS_BAD_NETWORK_NAME:
123 			/* Look for a new DC */
124 			smb_ddiscover_bad_dc(server);
125 		default:
126 			break;
127 		}
128 		return (status);
129 	}
130 
131 	/*
132 	 * Setup the RPC client handle.
133 	 */
134 	rc = mlrpc_clh_create(handle, ctx);
135 	if (rc != 0) {
136 		syslog(LOG_ERR, "ndr_rpc_bind: mlrpc_clh_create: rc=%d", rc);
137 		smbrdr_ctx_free(ctx);
138 		switch (rc) {
139 		case ENOMEM:
140 			return (NT_STATUS_NO_MEMORY);
141 		case EINVAL:
142 			return (NT_STATUS_INVALID_PARAMETER);
143 		default:
144 			return (NT_STATUS_INTERNAL_ERROR);
145 		}
146 	}
147 
148 	/*
149 	 * Setup authentication, if requested.
150 	 */
151 	status = mlrpc_clh_set_auth(handle, auth_ctx);
152 	if (status != 0) {
153 		syslog(LOG_DEBUG, "ndr_rpc_bind: "
154 		    "mlrpc_clh_set_auth, %s (0x%x)",
155 		    xlate_nt_status(status), status);
156 
157 		goto errout;
158 	}
159 
160 	/*
161 	 * This does the pipe open and OtW RPC bind.
162 	 * Handles pipe open retries.
163 	 */
164 	status = mlrpc_clh_bind(handle, svc);
165 	if (status != 0) {
166 		syslog(LOG_DEBUG, "ndr_rpc_bind: mlrpc_clh_bind"
167 		    "(Srv=%s Dom=%s User=%s Svc=%s), %s (0x%x)",
168 		    server, domain, username, service,
169 		    xlate_nt_status(status), status);
170 		switch (status) {
171 		case RPC_NT_SERVER_TOO_BUSY:
172 			/* Look for a new DC */
173 			smb_ddiscover_bad_dc(server);
174 			break;
175 		default:
176 			break;
177 		}
178 
179 		goto errout;
180 	}
181 
182 	return (NT_STATUS_SUCCESS);
183 
184 errout:
185 	ctx = mlrpc_clh_free(handle);
186 	if (ctx != NULL) {
187 		smbrdr_ctx_free(ctx);
188 	}
189 	return (status);
190 }
191 
192 DWORD
ndr_rpc_bind(mlsvc_handle_t * handle,char * server,char * domain,char * username,const char * service)193 ndr_rpc_bind(mlsvc_handle_t *handle, char *server, char *domain,
194     char *username, const char *service)
195 {
196 	return (ndr_rpc_bind_common(handle, server, domain, username, service,
197 	    NULL));
198 }
199 
200 DWORD
ndr_rpc_bind_secure(mlsvc_handle_t * handle,char * server,char * domain,char * username,const char * service,ndr_auth_ctx_t * auth_ctx)201 ndr_rpc_bind_secure(mlsvc_handle_t *handle, char *server, char *domain,
202     char *username, const char *service, ndr_auth_ctx_t *auth_ctx)
203 {
204 	return (ndr_rpc_bind_common(handle, server, domain, username, service,
205 	    auth_ctx));
206 }
207 
208 /*
209  * Unbind and close the pipe to an RPC service
210  * and cleanup the smb_ctx.
211  *
212  * The heap may or may not be destroyed (see mlrpc_clh_free)
213  */
214 void
ndr_rpc_unbind(mlsvc_handle_t * handle)215 ndr_rpc_unbind(mlsvc_handle_t *handle)
216 {
217 	struct smb_ctx *ctx;
218 
219 	ctx = mlrpc_clh_free(handle);
220 	if (ctx != NULL)
221 		smbrdr_ctx_free(ctx);
222 
223 	bzero(handle, sizeof (mlsvc_handle_t));
224 }
225 
226 void
ndr_rpc_status(mlsvc_handle_t * handle,int opnum,DWORD status)227 ndr_rpc_status(mlsvc_handle_t *handle, int opnum, DWORD status)
228 {
229 	ndr_service_t *svc;
230 	char *name = "NDR RPC";
231 	char *s = "unknown";
232 
233 	switch (NT_SC_SEVERITY(status)) {
234 	case NT_STATUS_SEVERITY_SUCCESS:
235 		s = "success";
236 		break;
237 	case NT_STATUS_SEVERITY_INFORMATIONAL:
238 		s = "info";
239 		break;
240 	case NT_STATUS_SEVERITY_WARNING:
241 		s = "warning";
242 		break;
243 	case NT_STATUS_SEVERITY_ERROR:
244 		s = "error";
245 		break;
246 	}
247 
248 	if (handle) {
249 		svc = handle->clnt->binding->service;
250 		name = svc->name;
251 	}
252 
253 	smb_tracef("%s[0x%02x]: %s: %s (0x%08x)",
254 	    name, opnum, s, xlate_nt_status(status), status);
255 }
256