View Javadoc
1   package org.metricshub.winrm.shares;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * WinRM Java Client
6    * ჻჻჻჻჻჻
7    * Copyright 2023 - 2024 Metricshub
8    * ჻჻჻჻჻჻
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   *
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
21   */
22  
23  import com.hierynomus.security.bc.BCSecurityProvider;
24  import com.hierynomus.smbj.SMBClient;
25  import com.hierynomus.smbj.SmbConfig;
26  import com.hierynomus.smbj.auth.AuthenticationContext;
27  import com.hierynomus.smbj.connection.Connection;
28  import com.hierynomus.smbj.session.Session;
29  import com.hierynomus.smbj.share.DiskShare;
30  import java.io.IOException;
31  import java.nio.file.Path;
32  import java.util.List;
33  import java.util.concurrent.ConcurrentHashMap;
34  import java.util.concurrent.TimeUnit;
35  import java.util.concurrent.TimeoutException;
36  import java.util.concurrent.atomic.AtomicInteger;
37  import org.metricshub.winrm.Utils;
38  import org.metricshub.winrm.WindowsRemoteExecutor;
39  import org.metricshub.winrm.WindowsTempShare;
40  import org.metricshub.winrm.exceptions.WinRMException;
41  import org.metricshub.winrm.exceptions.WindowsRemoteException;
42  import org.metricshub.winrm.service.WinRMEndpoint;
43  import org.metricshub.winrm.service.WinRMService;
44  import org.metricshub.winrm.service.client.auth.AuthenticationEnum;
45  
46  public class SmbTempShare extends WindowsTempShare implements AutoCloseable {
47  
48  	private final WinRMEndpoint winRMEndpoint;
49  	private final SMBClient smbClient;
50  	private final Connection connection;
51  	private final Session session;
52  	private final DiskShare diskShare;
53  
54  	/**
55  	 * The SmbTempShare constructor.
56  	 *
57  	 * @param winRMService WinRMService instance
58  	 * @param winRMEndpoint Endpoint with credentials
59  	 * @param smbClient The SMB client
60  	 * @param connection The SMB connection
61  	 * @param session The SMB session
62  	 * @param diskShare The SMB disk share
63  	 * @param shareNameOrUnc The name of the share, or its full UNC path
64  	 * @param remotePath The path on the remote system of the directory being shared
65  	 */
66  	private SmbTempShare(
67  		final WinRMService winRMService,
68  		final WinRMEndpoint winRMEndpoint,
69  		final SMBClient smbClient,
70  		final Connection connection,
71  		final Session session,
72  		final DiskShare diskShare,
73  		final String shareNameOrUnc,
74  		final String remotePath
75  	) {
76  		super(winRMService, shareNameOrUnc, remotePath);
77  		this.winRMEndpoint = winRMEndpoint;
78  		this.smbClient = smbClient;
79  		this.connection = connection;
80  		this.session = session;
81  		this.diskShare = diskShare;
82  	}
83  
84  	private static final ConcurrentHashMap<WinRMEndpoint, SmbTempShare> CONNECTIONS_CACHE = new ConcurrentHashMap<>();
85  
86  	private final AtomicInteger useCount = new AtomicInteger(1);
87  
88  	/**
89  	 * Create a SmbTempShare instance.
90  	 * Get or create a temp share and connect to it with SMB.
91  	 *
92  	 * @param winRMEndpoint Endpoint with credentials (mandatory)
93  	 * @param timeout Timeout in milliseconds (throws an IllegalArgumentException if negative or zero)
94  	 * @param ticketCache The Ticket Cache path
95  	 * @param authentications List of authentications. only NTLM if absent
96  	 *
97  	 * @return SmbTempShare instance
98  	 *
99  	 * @throws IOException If an I/O error occurred
100 	 * @throws WinRMException For any problem encountered
101 	 * @throws TimeoutException To notify userName of timeout.
102 	 */
103 	public static SmbTempShare createInstance(
104 		final WinRMEndpoint winRMEndpoint,
105 		final long timeout,
106 		final Path ticketCache,
107 		final List<AuthenticationEnum> authentications
108 	) throws IOException, WinRMException, TimeoutException {
109 		Utils.checkNonNull(winRMEndpoint, "winRMEndpoint");
110 		Utils.checkNonNull(winRMEndpoint.getPassword(), "password");
111 		Utils.checkArgumentNotZeroOrNegative(timeout, "timeout");
112 
113 		try {
114 			return CONNECTIONS_CACHE.compute(
115 				winRMEndpoint,
116 				(key, smb) -> {
117 					if (smb == null) {
118 						WinRMService winRMService = null;
119 						SMBClient smbClient = null;
120 						Connection connection = null;
121 						Session session = null;
122 						DiskShare diskShare = null;
123 
124 						try {
125 							winRMService = WinRMService.createInstance(winRMEndpoint, timeout, ticketCache, authentications);
126 
127 							final WindowsTempShare windowsTempShare = getOrCreateShare(
128 								winRMService,
129 								timeout,
130 								(w, r, s, t) -> {
131 									try {
132 										shareRemoteDirectory(w, r, s, t);
133 									} catch (final TimeoutException | WindowsRemoteException e) {
134 										throw new RuntimeException(e);
135 									}
136 								}
137 							);
138 
139 							final SmbConfig smbConfig = SmbConfig
140 								.builder()
141 								.withSecurityProvider(new BCSecurityProvider())
142 								.withTimeout(timeout, TimeUnit.SECONDS)
143 								.build();
144 
145 							final AuthenticationContext authenticationContext = new AuthenticationContext(
146 								winRMEndpoint.getUsername(),
147 								winRMEndpoint.getPassword(),
148 								winRMEndpoint.getDomain()
149 							);
150 
151 							smbClient = createSmbClient(smbConfig);
152 							connection = smbClient.connect(winRMEndpoint.getHostname());
153 							session = connection.authenticate(authenticationContext);
154 							diskShare = (DiskShare) session.connectShare(windowsTempShare.getShareName());
155 
156 							return new SmbTempShare(
157 								winRMService,
158 								winRMEndpoint,
159 								smbClient,
160 								connection,
161 								session,
162 								diskShare,
163 								windowsTempShare.getUncSharePath(),
164 								windowsTempShare.getRemotePath()
165 							);
166 						} catch (final RuntimeException e) {
167 							closeResources(winRMService, smbClient, connection, session, diskShare);
168 
169 							throw e;
170 						} catch (final Exception e) {
171 							closeResources(winRMService, smbClient, connection, session, diskShare);
172 
173 							throw new RuntimeException(e);
174 						}
175 					} else {
176 						synchronized (smb) {
177 							smb.incrementUseCount();
178 
179 							return smb;
180 						}
181 					}
182 				}
183 			);
184 		} catch (final RuntimeException e) {
185 			final Throwable cause = e.getCause();
186 
187 			if (cause instanceof IOException) {
188 				throw (IOException) cause;
189 			}
190 
191 			if (cause instanceof TimeoutException) {
192 				throw (TimeoutException) cause;
193 			}
194 
195 			if (cause instanceof WindowsRemoteException) {
196 				throw (WinRMException) cause;
197 			}
198 
199 			throw e;
200 		}
201 	}
202 
203 	private static void closeResources(
204 		final WinRMService winRMService,
205 		final SMBClient smbClient,
206 		final Connection connection,
207 		final Session session,
208 		final DiskShare diskShare
209 	) {
210 		try {
211 			if (diskShare != null) {
212 				diskShare.close();
213 			}
214 
215 			if (session != null) {
216 				session.close();
217 			}
218 
219 			if (connection != null) {
220 				connection.close();
221 			}
222 		} catch (final IOException ioe) {
223 			throw new RuntimeException(ioe);
224 		}
225 
226 		if (smbClient != null) {
227 			smbClient.close();
228 		}
229 
230 		if (winRMService != null) {
231 			winRMService.close();
232 		}
233 	}
234 
235 	int getUseCount() {
236 		return useCount.get();
237 	}
238 
239 	void incrementUseCount() {
240 		useCount.incrementAndGet();
241 	}
242 
243 	/**
244 	 * @return whether this WbemServices instance is connected and usable
245 	 */
246 	boolean isConnected() {
247 		return getUseCount() > 0;
248 	}
249 
250 	/**
251 	 * Check if it's connected. If not, throw an IllegalStateException.
252 	 */
253 	public void checkConnectedFirst() {
254 		if (!isConnected()) {
255 			throw new IllegalStateException("This instance has been closed and a new one must be created.");
256 		}
257 	}
258 
259 	@Override
260 	public synchronized void close() throws IOException {
261 		if (useCount.decrementAndGet() == 0) {
262 			CONNECTIONS_CACHE.remove(winRMEndpoint);
263 
264 			if (diskShare != null) {
265 				diskShare.close();
266 			}
267 
268 			if (session != null) {
269 				session.close();
270 			}
271 
272 			if (connection != null) {
273 				connection.close();
274 			}
275 
276 			if (smbClient != null) {
277 				smbClient.close();
278 			}
279 
280 			((WinRMService) getWindowsRemoteExecutor()).close();
281 		}
282 	}
283 
284 	/**
285 	 * Share the remote directory on the host.
286 	 *
287 	 * @param windowsRemoteExecutor WinRMService instance.
288 	 * @param remotePath The remote path.
289 	 * @param shareName The Share Name.
290 	 * @param timeout Timeout in milliseconds.
291 	 *
292 	 * @throws TimeoutException To notify userName of timeout.
293 	 * @throws WindowsRemoteException For any problem encountered
294 	 *
295 	 */
296 	private static void shareRemoteDirectory(
297 		final WindowsRemoteExecutor windowsRemoteExecutor,
298 		final String remotePath,
299 		final String shareName,
300 		final long timeout
301 	) throws TimeoutException, WindowsRemoteException {
302 		final String command = String.format(
303 			"net share %s=%s /grant:%s,Full",
304 			shareName,
305 			remotePath,
306 			windowsRemoteExecutor.getUsername()
307 		);
308 
309 		windowsRemoteExecutor.executeCommand(command, null, null, timeout);
310 	}
311 
312 	static SMBClient createSmbClient(final SmbConfig smbConfig) {
313 		return new SMBClient(smbConfig);
314 	}
315 }