View Javadoc
1   /*
2    * MIT License
3    *
4    * Copyright (c) 2010-2024 The Waffle Project Contributors: https://github.com/Waffle/waffle/graphs/contributors
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining a copy
7    * of this software and associated documentation files (the "Software"), to deal
8    * in the Software without restriction, including without limitation the rights
9    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10   * copies of the Software, and to permit persons to whom the Software is
11   * furnished to do so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in all
14   * copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22   * SOFTWARE.
23   */
24  package waffle.windows.auth.impl;
25  
26  import com.sun.jna.platform.win32.Advapi32;
27  import com.sun.jna.platform.win32.Kernel32;
28  import com.sun.jna.platform.win32.Netapi32Util;
29  import com.sun.jna.platform.win32.Netapi32Util.DomainTrust;
30  import com.sun.jna.platform.win32.Secur32;
31  import com.sun.jna.platform.win32.Sspi;
32  import com.sun.jna.platform.win32.Sspi.CtxtHandle;
33  import com.sun.jna.platform.win32.SspiUtil.ManagedSecBufferDesc;
34  import com.sun.jna.platform.win32.Win32Exception;
35  import com.sun.jna.platform.win32.WinBase;
36  import com.sun.jna.platform.win32.WinError;
37  import com.sun.jna.platform.win32.WinNT.HANDLEByReference;
38  import com.sun.jna.ptr.IntByReference;
39  
40  import java.net.InetAddress;
41  import java.net.UnknownHostException;
42  import java.util.ArrayList;
43  import java.util.List;
44  
45  import waffle.util.cache.Cache;
46  import waffle.windows.auth.IWindowsAccount;
47  import waffle.windows.auth.IWindowsAuthProvider;
48  import waffle.windows.auth.IWindowsComputer;
49  import waffle.windows.auth.IWindowsCredentialsHandle;
50  import waffle.windows.auth.IWindowsDomain;
51  import waffle.windows.auth.IWindowsIdentity;
52  import waffle.windows.auth.IWindowsSecurityContext;
53  
54  /**
55   * Windows Auth Provider.
56   */
57  public class WindowsAuthProviderImpl implements IWindowsAuthProvider {
58  
59      /** The Continue Context Timeout. */
60      public static final int CONTINUE_CONTEXT_TIMEOUT = 30;
61  
62      /**
63       * The Class ContinueContext.
64       */
65      private static class ContinueContext {
66          /** The continue handle. */
67          CtxtHandle continueHandle;
68  
69          /** The server credential. */
70          IWindowsCredentialsHandle serverCredential;
71  
72          /**
73           * Instantiates a new continue context.
74           *
75           * @param handle
76           *            the handle
77           * @param windowsCredential
78           *            the windows credential
79           */
80          public ContinueContext(final CtxtHandle handle, final IWindowsCredentialsHandle windowsCredential) {
81              this.continueHandle = handle;
82              this.serverCredential = windowsCredential;
83          }
84      }
85  
86      /** The continue contexts. */
87      private final Cache<String, ContinueContext> continueContexts;
88  
89      /**
90       * Instantiates a new windows auth provider impl.
91       */
92      public WindowsAuthProviderImpl() {
93          this(WindowsAuthProviderImpl.CONTINUE_CONTEXT_TIMEOUT);
94      }
95  
96      /**
97       * A Windows authentication provider.
98       *
99       * @param continueContextsTimeout
100      *            Timeout for security contexts in seconds.
101      */
102     public WindowsAuthProviderImpl(final int continueContextsTimeout) {
103         this.continueContexts = Cache.newCache(continueContextsTimeout);
104     }
105 
106     @Override
107     public IWindowsSecurityContext acceptSecurityToken(final String connectionId, final byte[] token,
108             final String securityPackage) {
109 
110         if (token == null || token.length == 0) {
111             this.resetSecurityToken(connectionId);
112             throw new Win32Exception(WinError.SEC_E_INVALID_TOKEN);
113         }
114 
115         CtxtHandle continueHandle = null;
116         IWindowsCredentialsHandle serverCredential;
117         ContinueContext continueContext = this.continueContexts.get(connectionId);
118         if (continueContext != null) {
119             continueHandle = continueContext.continueHandle;
120             serverCredential = continueContext.serverCredential;
121         } else {
122             serverCredential = new WindowsCredentialsHandleImpl(null, Sspi.SECPKG_CRED_INBOUND, securityPackage);
123             serverCredential.initialize();
124         }
125 
126         WindowsSecurityContextImpl sc;
127 
128         int rc;
129         int tokenSize = Sspi.MAX_TOKEN_SIZE;
130 
131         do {
132             final ManagedSecBufferDesc pbServerToken = new ManagedSecBufferDesc(Sspi.SECBUFFER_TOKEN, tokenSize);
133             final ManagedSecBufferDesc pbClientToken = new ManagedSecBufferDesc(Sspi.SECBUFFER_TOKEN, token);
134             final IntByReference pfClientContextAttr = new IntByReference();
135 
136             final CtxtHandle phNewServerContext = new CtxtHandle();
137             rc = Secur32.INSTANCE.AcceptSecurityContext(serverCredential.getHandle(), continueHandle, pbClientToken,
138                     Sspi.ISC_REQ_CONNECTION, Sspi.SECURITY_NATIVE_DREP, phNewServerContext, pbServerToken,
139                     pfClientContextAttr, null);
140 
141             sc = new WindowsSecurityContextImpl();
142             sc.setCredentialsHandle(serverCredential);
143             sc.setSecurityPackage(securityPackage);
144             sc.setSecurityContext(phNewServerContext);
145 
146             switch (rc) {
147                 case WinError.SEC_E_BUFFER_TOO_SMALL:
148                     tokenSize += Sspi.MAX_TOKEN_SIZE;
149                     sc.dispose();
150                     WindowsSecurityContextImpl.dispose(continueHandle);
151                     break;
152                 case WinError.SEC_E_OK:
153                     // the security context received from the client was accepted
154                     this.resetSecurityToken(connectionId);
155                     // if an output token was generated by the function, it must be sent to the client process
156                     if (pbServerToken.pBuffers != null && pbServerToken.cBuffers == 1
157                             && pbServerToken.getBuffer(0).cbBuffer > 0) {
158                         sc.setToken(pbServerToken.getBuffer(0).getBytes() == null ? new byte[0]
159                                 : pbServerToken.getBuffer(0).getBytes().clone());
160                     }
161                     sc.setContinue(false);
162                     break;
163                 case WinError.SEC_I_CONTINUE_NEEDED:
164                     // the server must send the output token to the client and wait for a returned token
165                     continueContext = new ContinueContext(phNewServerContext, serverCredential);
166                     this.continueContexts.put(connectionId, continueContext);
167                     sc.setToken(pbServerToken.getBuffer(0).getBytes() == null ? new byte[0]
168                             : pbServerToken.getBuffer(0).getBytes().clone());
169                     sc.setContinue(true);
170                     break;
171                 default:
172                     sc.dispose();
173                     WindowsSecurityContextImpl.dispose(continueHandle);
174                     this.resetSecurityToken(connectionId);
175                     throw new Win32Exception(rc);
176             }
177         } while (rc == WinError.SEC_E_BUFFER_TOO_SMALL);
178 
179         return sc;
180     }
181 
182     @Override
183     public IWindowsComputer getCurrentComputer() {
184         try {
185             return new WindowsComputerImpl(InetAddress.getLocalHost().getHostName());
186         } catch (final UnknownHostException e) {
187             throw new RuntimeException(e);
188         }
189     }
190 
191     @Override
192     public IWindowsDomain[] getDomains() {
193         final List<IWindowsDomain> domains = new ArrayList<>();
194         final DomainTrust[] trusts = Netapi32Util.getDomainTrusts();
195         for (final DomainTrust trust : trusts) {
196             domains.add(new WindowsDomainImpl(trust));
197         }
198         return domains.toArray(new IWindowsDomain[0]);
199     }
200 
201     @Override
202     public IWindowsIdentity logonDomainUser(final String username, final String domain, final String password) {
203         return this.logonDomainUserEx(username, domain, password, WinBase.LOGON32_LOGON_NETWORK,
204                 WinBase.LOGON32_PROVIDER_DEFAULT);
205     }
206 
207     @Override
208     public IWindowsIdentity logonDomainUserEx(final String username, final String domain, final String password,
209             final int logonType, final int logonProvider) {
210         final HANDLEByReference phUser = new HANDLEByReference();
211         if (!Advapi32.INSTANCE.LogonUser(username, domain, password, logonType, logonProvider, phUser)) {
212             throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
213         }
214         return new WindowsIdentityImpl(phUser.getValue());
215     }
216 
217     @Override
218     public IWindowsIdentity logonUser(final String username, final String password) {
219         // username@domain UPN format is natively supported by the
220         // Windows LogonUser API process domain\\username format
221         final String[] userNameDomain = username.split("\\\\", 2);
222         if (userNameDomain.length == 2) {
223             return this.logonDomainUser(userNameDomain[1], userNameDomain[0], password);
224         }
225         return this.logonDomainUser(username, null, password);
226     }
227 
228     @Override
229     public IWindowsAccount lookupAccount(final String username) {
230         return new WindowsAccountImpl(username);
231     }
232 
233     @Override
234     public void resetSecurityToken(final String connectionId) {
235         this.continueContexts.remove(connectionId);
236     }
237 
238     /**
239      * Number of elements in the continue contexts map.
240      *
241      * @return Number of elements in the hash map.
242      */
243     public int getContinueContextsSize() {
244         return this.continueContexts.size();
245     }
246 }