View Javadoc
1   package org.metricshub.wmi.shares;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * WMI Java Client
6    * ჻჻჻჻჻჻
7    * Copyright (C) 2023 - 2025 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.sun.jna.Native;
24  import com.sun.jna.platform.win32.Advapi32;
25  import com.sun.jna.platform.win32.Advapi32Util;
26  import com.sun.jna.platform.win32.COM.WbemcliUtil;
27  import com.sun.jna.platform.win32.COM.util.Factory;
28  import com.sun.jna.platform.win32.Kernel32;
29  import com.sun.jna.platform.win32.LMAccess;
30  import com.sun.jna.platform.win32.LMShare;
31  import com.sun.jna.platform.win32.LMShare.SHARE_INFO_502;
32  import com.sun.jna.platform.win32.Netapi32;
33  import com.sun.jna.platform.win32.Win32Exception;
34  import com.sun.jna.platform.win32.WinDef.DWORD;
35  import com.sun.jna.platform.win32.WinNT;
36  import com.sun.jna.platform.win32.WinNT.ACCESS_ALLOWED_ACE;
37  import com.sun.jna.platform.win32.WinNT.ACL;
38  import com.sun.jna.platform.win32.WinNT.PSID;
39  import com.sun.jna.platform.win32.WinNT.SECURITY_DESCRIPTOR;
40  import com.sun.jna.platform.win32.WinNT.WELL_KNOWN_SID_TYPE;
41  import com.sun.jna.ptr.IntByReference;
42  import java.util.concurrent.ConcurrentHashMap;
43  import java.util.concurrent.TimeoutException;
44  import java.util.concurrent.atomic.AtomicInteger;
45  import org.metricshub.wmi.Utils;
46  import org.metricshub.wmi.WmiHelper;
47  import org.metricshub.wmi.exceptions.WindowsRemoteException;
48  import org.metricshub.wmi.exceptions.WmiComException;
49  import org.metricshub.wmi.wbem.WmiWbemServices;
50  import org.metricshub.wmi.windows.remote.WindowsRemoteExecutor;
51  import org.metricshub.wmi.windows.remote.share.WindowsTempShare;
52  
53  /**
54   * Class representing a connection to a Windows share
55   *
56   */
57  public class WinTempShare extends WindowsTempShare implements AutoCloseable {
58  
59  	private static final int CONNECTION_MAX = 10;
60  	private static final String SHARE_DESCRIPTION = "Share created by Metricshub to store results of commands";
61  	private static final int SUCCESS = 0;
62  	private static final int ALREADY_EXIST = 2118;
63  
64  	private static final Factory FACTORY = new Factory();
65  	private static final WindowsScriptHostNetworkInterface WSH = FACTORY.createObject(
66  		WindowsScriptHostNetworkInterface.class
67  	);
68  
69  	private static final ConcurrentHashMap<String, WinTempShare> CONNECTIONS_CACHE = new ConcurrentHashMap<>();
70  
71  	/** The hostname of the server */
72  	private final String hostname;
73  
74  	/** How many "clients" are using this instance */
75  	private final AtomicInteger useCount = new AtomicInteger(1);
76  
77  	/**
78  	 * Constructor of WinTempShare
79  	 * <p>
80  	 * <b>DON'T USE IT OUTSIDE OF WinTempShare</b>
81  	 * <p>
82  	 * @param wmiWbemServices the WbemServices instance connected to the remote host
83  	 * @param shareNameOrUnc The name of the share, or its full UNC path
84  	 * @param remotePath The path on the remote system of the directory being shared
85  	 */
86  	WinTempShare(final WmiWbemServices wmiWbemServices, final String shareNameOrUnc, final String remotePath) {
87  		super(wmiWbemServices, shareNameOrUnc, remotePath);
88  		this.hostname = wmiWbemServices.getHostname();
89  	}
90  
91  	/**
92  	 * Create or get a cached instance of a shared temporary directory on the specified host.
93  	 * <p>
94  	 * This method ensures only one instance of this class is created per host.
95  	 * @param hostname Host to connect to
96  	 * @param username Username (may be null)
97  	 * @param password Password (may be null)
98  	 * @param timeout Timeout in milliseconds
99  	 * @return the WinTempShare instance
100 	 *
101 	 * @throws TimeoutException To notify userName of timeout.
102 	 * @throws WmiComException For any problem encountered with JNA.
103 	 */
104 	public static WinTempShare getInstance(
105 		final String hostname,
106 		final String username,
107 		final char[] password,
108 		final long timeout
109 	) throws TimeoutException, WmiComException {
110 		Utils.checkNonNull(hostname, "hostname");
111 		Utils.checkArgumentNotZeroOrNegative(timeout, "timeout");
112 
113 		try {
114 			return CONNECTIONS_CACHE.compute(
115 				hostname.toLowerCase(),
116 				(h, winTempShare) -> {
117 					if (winTempShare == null) {
118 						WmiWbemServices wmiWbemServices = null;
119 
120 						try {
121 							// Get the WbemServices
122 							final String networkResource = WmiHelper.createNetworkResource(hostname, WbemcliUtil.DEFAULT_NAMESPACE);
123 							wmiWbemServices = WmiWbemServices.getInstance(networkResource, username, password);
124 
125 							// Get the share if it exists, or create it
126 							final WindowsTempShare share = getOrCreateShare(
127 								wmiWbemServices,
128 								timeout,
129 								(w, r, s, t) -> {
130 									try {
131 										shareRemoteDirectory(w, r, s, t);
132 									} catch (final WindowsRemoteException e) {
133 										// Throw as a RuntimeException, so we catch it later (see below)
134 										throw new RuntimeException(e);
135 									}
136 								}
137 							);
138 
139 							// Connect to it
140 							getWindowsScriptHostNetwork()
141 								.mapNetworkDrive(
142 									Utils.EMPTY,
143 									share.getUncSharePath(),
144 									false,
145 									username,
146 									password == null ? null : String.valueOf(password)
147 								);
148 
149 							return new WinTempShare(
150 								(WmiWbemServices) share.getWindowsRemoteExecutor(),
151 								share.getUncSharePath(),
152 								share.getRemotePath()
153 							);
154 						} catch (final RuntimeException e) {
155 							if (wmiWbemServices != null) {
156 								wmiWbemServices.close();
157 							}
158 
159 							throw e;
160 						} catch (final Exception e) {
161 							if (wmiWbemServices != null) {
162 								wmiWbemServices.close();
163 							}
164 
165 							// Throw as a RuntimeException, so we catch it later (see below)
166 							throw new RuntimeException(e);
167 						}
168 					} else {
169 						// We already have this share
170 						synchronized (winTempShare) {
171 							// Increment the number of callers
172 							winTempShare.incrementUseCount();
173 
174 							// And simply return the same reference (so that the map is not changed)
175 							return winTempShare;
176 						}
177 					}
178 				}
179 			);
180 		} catch (final RuntimeException e) {
181 			final Throwable cause = e.getCause();
182 			if (cause != null) {
183 				if (cause instanceof TimeoutException) {
184 					throw (TimeoutException) cause;
185 				} else if (cause instanceof WmiComException) {
186 					throw (WmiComException) cause;
187 				} else if (cause instanceof InterruptedException) {
188 					throw new TimeoutException(cause.getClass().getSimpleName() + ": " + cause.getMessage());
189 				}
190 			}
191 
192 			throw e;
193 		}
194 	}
195 
196 	@Override
197 	public synchronized void close() {
198 		if (useCount.decrementAndGet() == 0) {
199 			CONNECTIONS_CACHE.remove(hostname.toLowerCase());
200 
201 			((WmiWbemServices) getWindowsRemoteExecutor()).close();
202 
203 			// Disconnect from the share
204 			getWindowsScriptHostNetwork().removeNetworkDrive(getUncSharePath(), true, false);
205 		}
206 	}
207 
208 	/**
209 	 * @return whether we are connected to this share and can interact with it
210 	 */
211 	public boolean isConnected() {
212 		return useCount.get() > 0;
213 	}
214 
215 	/**
216 	 * Check if it's connected. If not, throw an IllegalStateException.
217 	 */
218 	public void checkConnectedFirst() {
219 		if (!isConnected()) {
220 			throw new IllegalStateException("This instance has been closed and a new one must be created.");
221 		}
222 	}
223 
224 	/**
225 	 * Share the remote directory on the host.
226 	 *
227 	 * @param wmiWbemServices WbemServices connected to the host
228 	 * @param remotePath The remote path.
229 	 * @param shareName The Share Name.
230 	 * @param timeout Timeout in milliseconds.
231 	 *
232 	 * @throws WmiComException For any problem encountered with JNA
233 	 */
234 	private static void shareRemoteDirectory(
235 		final WindowsRemoteExecutor wmiWbemServices,
236 		final String remotePath,
237 		final String shareName,
238 		final long timeout
239 	) throws WmiComException {
240 		final SECURITY_DESCRIPTOR securityDescriptor = initializeSecurityDescriptor();
241 
242 		final SHARE_INFO_502 shareInfo502 = new SHARE_INFO_502();
243 		shareInfo502.shi502_netname = shareName;
244 		shareInfo502.shi502_type = LMShare.STYPE_DISKTREE;
245 		shareInfo502.shi502_remark = SHARE_DESCRIPTION;
246 		shareInfo502.shi502_permissions = LMAccess.ACCESS_ALL;
247 		shareInfo502.shi502_max_uses = CONNECTION_MAX;
248 		shareInfo502.shi502_current_uses = 0;
249 		shareInfo502.shi502_path = remotePath;
250 		shareInfo502.shi502_passwd = null;
251 		shareInfo502.shi502_reserved = 0;
252 		shareInfo502.shi502_security_descriptor = securityDescriptor.getPointer();
253 
254 		// Write from struct to native memory.
255 		shareInfo502.write();
256 
257 		final IntByReference parmErr = new IntByReference(0);
258 		final int result = Netapi32.INSTANCE.NetShareAdd(
259 			wmiWbemServices.getHostname(),
260 			502,
261 			shareInfo502.getPointer(),
262 			parmErr
263 		);
264 		if (result != SUCCESS && result != ALREADY_EXIST) {
265 			final Exception e = new Win32Exception(result);
266 			throw new WmiComException(e, e.getMessage());
267 		}
268 	}
269 
270 	/**
271 	 * <p>Initialize a Security Descriptor with:
272 	 * <li>Full control permissions for system and administrator</li>
273 	 * <li>SE_DACL_PRESENT</li>
274 	 * <li>SE_DACL_DEFAULTED</li></p>
275 	 *
276 	 * @return The Security Descriptor
277 	 * @throws WmiComException For any problem encountered with JNA
278 	 */
279 	private static SECURITY_DESCRIPTOR initializeSecurityDescriptor() throws WmiComException {
280 		final PSID pSidSystem = createSID(WELL_KNOWN_SID_TYPE.WinLocalSystemSid);
281 
282 		final PSID pSidAdministrator = createSID(WELL_KNOWN_SID_TYPE.WinBuiltinAdministratorsSid);
283 
284 		final int sidSysLength = Advapi32.INSTANCE.GetLengthSid(pSidSystem);
285 		final int sidAdminLength = Advapi32.INSTANCE.GetLengthSid(pSidAdministrator);
286 		int cbAcl = Native.getNativeSize(ACL.class, null);
287 		cbAcl += Native.getNativeSize(ACCESS_ALLOWED_ACE.class, null);
288 		cbAcl += (sidSysLength - DWORD.SIZE);
289 		cbAcl += Native.getNativeSize(ACCESS_ALLOWED_ACE.class, null);
290 		cbAcl += (sidAdminLength - DWORD.SIZE);
291 		cbAcl = Advapi32Util.alignOnDWORD(cbAcl);
292 		final ACL pAcl = new ACL(cbAcl);
293 
294 		checkWin32Result(Advapi32.INSTANCE.InitializeAcl(pAcl, cbAcl, WinNT.ACL_REVISION));
295 
296 		checkWin32Result(Advapi32.INSTANCE.AddAccessAllowedAce(pAcl, WinNT.ACL_REVISION, WinNT.GENERIC_ALL, pSidSystem));
297 
298 		checkWin32Result(
299 			Advapi32.INSTANCE.AddAccessAllowedAce(pAcl, WinNT.ACL_REVISION, WinNT.GENERIC_ALL, pSidAdministrator)
300 		);
301 
302 		final SECURITY_DESCRIPTOR securityDescriptor = new SECURITY_DESCRIPTOR(64 * 1024);
303 
304 		checkWin32Result(
305 			Advapi32.INSTANCE.InitializeSecurityDescriptor(securityDescriptor, WinNT.SECURITY_DESCRIPTOR_REVISION)
306 		);
307 
308 		checkWin32Result(Advapi32.INSTANCE.SetSecurityDescriptorDacl(securityDescriptor, true, pAcl, false));
309 
310 		return securityDescriptor;
311 	}
312 
313 	/**
314 	 *  Create a SID for predefined SID type aliases.
315 	 *
316 	 * @param wellKnownSidTypeAlias Member of the WELL_KNOWN_SID_TYPE enumeration that specifies what the SID will identify.
317 	 * @return The new SID
318 	 * @throws WmiComException For any problem encountered with JNA.
319 	 */
320 	private static PSID createSID(final int wellKnownSidTypeAlias) throws WmiComException {
321 		final PSID pSid = new PSID(WinNT.SECURITY_MAX_SID_SIZE);
322 		final IntByReference cbSid = new IntByReference(WinNT.SECURITY_MAX_SID_SIZE);
323 
324 		checkWin32Result(Advapi32.INSTANCE.CreateWellKnownSid(wellKnownSidTypeAlias, null, pSid, cbSid));
325 		return pSid;
326 	}
327 
328 	/**
329 	 * Check the result from an Advanced 32 API function.
330 	 * Throw a WmiComException if the result is ko.
331 	 *
332 	 * @param result The result to check.
333 	 * @throws WmiComException For any problem encountered with JNA.
334 	 */
335 	private static void checkWin32Result(final boolean result) throws WmiComException {
336 		if (!result) {
337 			final Exception e = new Win32Exception(Kernel32.INSTANCE.GetLastError());
338 			throw new WmiComException(e, e.getMessage());
339 		}
340 	}
341 
342 	int getUseCount() {
343 		return useCount.get();
344 	}
345 
346 	void incrementUseCount() {
347 		useCount.incrementAndGet();
348 	}
349 
350 	static WindowsScriptHostNetworkInterface getWindowsScriptHostNetwork() {
351 		return WSH;
352 	}
353 }