View Javadoc
1   /*
2    * Copyright 2020-2022 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.driver.windows.registry;
6   
7   import java.net.InetAddress;
8   import java.net.UnknownHostException;
9   import java.nio.ByteBuffer;
10  import java.nio.ByteOrder;
11  import java.nio.IntBuffer;
12  import java.util.ArrayList;
13  import java.util.Arrays;
14  import java.util.List;
15  
16  import com.sun.jna.Pointer;
17  import com.sun.jna.platform.win32.IPHlpAPI;
18  import com.sun.jna.platform.win32.VersionHelpers;
19  import com.sun.jna.platform.win32.WinBase;
20  import com.sun.jna.platform.win32.WinNT;
21  import com.sun.jna.platform.win32.Wtsapi32;
22  import com.sun.jna.platform.win32.Wtsapi32.WTSINFO;
23  import com.sun.jna.platform.win32.Wtsapi32.WTS_CLIENT_ADDRESS;
24  import com.sun.jna.platform.win32.Wtsapi32.WTS_SESSION_INFO;
25  
26  import oshi.annotation.concurrent.ThreadSafe;
27  import oshi.jna.ByRef.CloseableIntByReference;
28  import oshi.jna.ByRef.CloseablePointerByReference;
29  import oshi.software.os.OSSession;
30  import oshi.util.ParseUtil;
31  
32  /**
33   * Utility to read process data from HKEY_PERFORMANCE_DATA information with backup from Performance Counters or WMI
34   */
35  @ThreadSafe
36  public final class SessionWtsData {
37  
38      private static final int WTS_ACTIVE = 0;
39      private static final int WTS_CLIENTADDRESS = 14;
40      private static final int WTS_SESSIONINFO = 24;
41      private static final int WTS_CLIENTPROTOCOLTYPE = 16;
42  
43      private static final boolean IS_VISTA_OR_GREATER = VersionHelpers.IsWindowsVistaOrGreater();
44  
45      private static final Wtsapi32 WTS = Wtsapi32.INSTANCE;
46  
47      private SessionWtsData() {
48      }
49  
50      public static List<OSSession> queryUserSessions() {
51          List<OSSession> sessions = new ArrayList<>();
52          if (IS_VISTA_OR_GREATER) {
53              try (CloseablePointerByReference ppSessionInfo = new CloseablePointerByReference();
54                      CloseableIntByReference pCount = new CloseableIntByReference();
55                      CloseablePointerByReference ppBuffer = new CloseablePointerByReference();
56                      CloseableIntByReference pBytes = new CloseableIntByReference()) {
57                  if (WTS.WTSEnumerateSessions(Wtsapi32.WTS_CURRENT_SERVER_HANDLE, 0, 1, ppSessionInfo, pCount)) {
58                      Pointer pSessionInfo = ppSessionInfo.getValue();
59                      if (pCount.getValue() > 0) {
60                          WTS_SESSION_INFO sessionInfoRef = new WTS_SESSION_INFO(pSessionInfo);
61                          WTS_SESSION_INFO[] sessionInfo = (WTS_SESSION_INFO[]) sessionInfoRef.toArray(pCount.getValue());
62                          for (WTS_SESSION_INFO session : sessionInfo) {
63                              if (session.State == WTS_ACTIVE) {
64                                  // Use session id to fetch additional session information
65                                  WTS.WTSQuerySessionInformation(Wtsapi32.WTS_CURRENT_SERVER_HANDLE, session.SessionId,
66                                          WTS_CLIENTPROTOCOLTYPE, ppBuffer, pBytes);
67                                  Pointer pBuffer = ppBuffer.getValue(); // pointer to USHORT
68                                  short protocolType = pBuffer.getShort(0); // 0 = console, 2 = RDP
69                                  WTS.WTSFreeMemory(pBuffer);
70                                  // We've already got console from registry, only test RDP
71                                  if (protocolType > 0) {
72                                      // DEVICE
73                                      String device = session.pWinStationName;
74                                      // USER and LOGIN TIME
75                                      WTS.WTSQuerySessionInformation(Wtsapi32.WTS_CURRENT_SERVER_HANDLE,
76                                              session.SessionId, WTS_SESSIONINFO, ppBuffer, pBytes);
77                                      pBuffer = ppBuffer.getValue(); // returns WTSINFO
78                                      WTSINFO wtsInfo = new WTSINFO(pBuffer);
79                                      // Temporary due to broken LARGE_INTEGER, remove in JNA 5.6.0
80                                      long logonTime = new WinBase.FILETIME(
81                                              new WinNT.LARGE_INTEGER(wtsInfo.LogonTime.getValue())).toTime();
82                                      String userName = wtsInfo.getUserName();
83                                      WTS.WTSFreeMemory(pBuffer);
84                                      // HOST
85                                      WTS.WTSQuerySessionInformation(Wtsapi32.WTS_CURRENT_SERVER_HANDLE,
86                                              session.SessionId, WTS_CLIENTADDRESS, ppBuffer, pBytes);
87                                      pBuffer = ppBuffer.getValue(); // returns WTS_CLIENT_ADDRESS
88                                      WTS_CLIENT_ADDRESS addr = new WTS_CLIENT_ADDRESS(pBuffer);
89                                      WTS.WTSFreeMemory(pBuffer);
90                                      String host = "::";
91                                      if (addr.AddressFamily == IPHlpAPI.AF_INET) {
92                                          try {
93                                              host = InetAddress.getByAddress(Arrays.copyOfRange(addr.Address, 2, 6))
94                                                      .getHostAddress();
95                                          } catch (UnknownHostException e) {
96                                              // If array is not length of 4, shouldn't happen
97                                              host = "Illegal length IP Array";
98                                          }
99                                      } else if (addr.AddressFamily == IPHlpAPI.AF_INET6) {
100                                         // Get ints for address parsing
101                                         int[] ipArray = convertBytesToInts(addr.Address);
102                                         host = ParseUtil.parseUtAddrV6toIP(ipArray);
103                                     }
104                                     sessions.add(new OSSession(userName, device, logonTime, host));
105                                 }
106                             }
107                         }
108                     }
109                     WTS.WTSFreeMemory(pSessionInfo);
110                 }
111             }
112         }
113         return sessions;
114     }
115 
116     /**
117      * Per WTS_INFO_CLASS docs, the IP address is offset by two bytes from the start of the Address member of the
118      * WTS_CLIENT_ADDRESS structure. Also contrary to docs, IPv4 is not a null terminated string.
119      * <p>
120      * This method converts the byte[20] to an int[4] parseable by existing code
121      *
122      * @param address The 20-byte array from the WTS_CLIENT_ADDRESS structure
123      * @return A 4-int array for {@link ParseUtil#parseUtAddrV6toIP}
124      */
125     private static int[] convertBytesToInts(byte[] address) {
126         IntBuffer intBuf = ByteBuffer.wrap(Arrays.copyOfRange(address, 2, 18)).order(ByteOrder.BIG_ENDIAN)
127                 .asIntBuffer();
128         int[] array = new int[intBuf.remaining()];
129         intBuf.get(array);
130         return array;
131     }
132 }