/* * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package sockslib.common.methods; import sockslib.client.Socks5; import sockslib.client.SocksProxy; import sockslib.common.AuthenticationException; import sockslib.common.Credentials; import sockslib.common.SocksException; import sockslib.common.UsernamePasswordCredentials; import sockslib.server.Session; import sockslib.server.UsernamePasswordAuthenticator; import sockslib.server.msg.UsernamePasswordMessage; import sockslib.server.msg.UsernamePasswordResponseMessage; import sockslib.utils.LogMessageBuilder; import sockslib.utils.LogMessageBuilder.MsgType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import static com.google.common.base.Preconditions.checkNotNull; /** * The class <code>UsernamePasswordMethod</code> represents the method that need USERNAME/PASSWORD * authentication.<br> * <b>Notice:</b> This method is only supported by SOCKS5 protocol. It will be used in client and * server. * * @author Youchao Feng * @version 1.0 * @date Mar 17, 2015 5:09:23 PM * @see <a href="http://www.ietf.org/rfc/rfc1928.txt">SOCKS Protocol Version 5</a> */ public class UsernamePasswordMethod extends AbstractSocksMethod { /** * Logger. */ private static final Logger logger = LoggerFactory.getLogger(UsernamePasswordMethod.class); /** * USERNAME/PASSWORD authenticator. */ private UsernamePasswordAuthenticator authenticator = new UsernamePasswordAuthenticator(); /** * Constructs an instance of {@link UsernamePasswordMethod}. */ public UsernamePasswordMethod() { } /** * Constructs an instance of {@link UsernamePasswordMethod} with * {@link UsernamePasswordAuthenticator}. * * @param authenticator Authenticator. */ public UsernamePasswordMethod(UsernamePasswordAuthenticator authenticator) { this.authenticator = authenticator; } @Override public final int getByte() { return 0x02; } /** * Do authentication. */ @Override public void doMethod(SocksProxy socksProxy) throws SocksException, IOException { checkNotNull(socksProxy, "Argument [socksProxy] may not be null"); Credentials credentials = socksProxy.getCredentials(); if (credentials == null || !(credentials instanceof UsernamePasswordCredentials)) { throw new SocksException("Need Username/Password authentication"); } // UsernamePasswordAuthentication authentication = (UsernamePasswordAuthentication) auth; String username = credentials.getUserPrincipal().getName(); String password = credentials.getPassword(); InputStream inputStream = socksProxy.getInputStream(); OutputStream outputStream = socksProxy.getOutputStream(); /* * RFC 1929 * * +----+------+----------+------+----------+ * |VER | ULEN | UNAME | PLEN | PASSWD | | * +----+------+----------+------+----------+ | 1 | 1 | 1 to 255 | 1 | 1 to 255 | * +----+------+----------+------+----------+ The VER field contains the current version of the * subnegotiation, which is X’01’. The ULEN field contains the length of the UNAME field that * follows. The UNAME field contains the username as known to the source operating system. The * PLEN field contains the length of the PASSWD field that follows. The PASSWD field contains * the password association with the given UNAME. */ final int USERNAME_LENGTH = username.getBytes().length; final int PASSWORD_LENGTH = password.getBytes().length; final byte[] bytesOfUsername = username.getBytes(); final byte[] bytesOfPassword = password.getBytes(); final byte[] bufferSent = new byte[3 + USERNAME_LENGTH + PASSWORD_LENGTH]; bufferSent[0] = 0x01; // VER bufferSent[1] = (byte) USERNAME_LENGTH; // ULEN System.arraycopy(bytesOfUsername, 0, bufferSent, 2, USERNAME_LENGTH);// UNAME bufferSent[2 + USERNAME_LENGTH] = (byte) PASSWORD_LENGTH; // PLEN System.arraycopy(bytesOfPassword, 0, bufferSent, 3 + USERNAME_LENGTH, // PASSWD PASSWORD_LENGTH); outputStream.write(bufferSent); outputStream.flush(); // logger send bytes logger.debug("{}", LogMessageBuilder.build(bufferSent, MsgType.SEND)); byte[] authenticationResult = new byte[2]; inputStream.read(authenticationResult); // logger logger.debug("{}", LogMessageBuilder.build(authenticationResult, MsgType.RECEIVE)); if (authenticationResult[1] != Socks5.AUTHENTICATION_SUCCEEDED) { // Close connection if authentication is failed. outputStream.close(); inputStream.close(); socksProxy.getProxySocket().close(); throw new AuthenticationException("Username or password error"); } } @Override public void doMethod(Session session) throws SocksException, IOException { checkNotNull(session, "Argument [session] may not be null"); checkNotNull(authenticator, "Please set an authenticator"); UsernamePasswordMessage usernamePasswordMessage = new UsernamePasswordMessage(); session.read(usernamePasswordMessage); logger.debug("SESSION[{}] Receive credentials: {}", session.getId(), usernamePasswordMessage .getUsernamePasswordCredentials()); try { authenticator.doAuthenticate(usernamePasswordMessage.getUsernamePasswordCredentials(), session); } catch (AuthenticationException e) { session.write(new UsernamePasswordResponseMessage(false)); throw e; } session.write(new UsernamePasswordResponseMessage(true)); } @Override public String getMethodName() { return "USERNAME/PASSWORD authentication"; } public UsernamePasswordAuthenticator getAuthenticator() { return authenticator; } public void setAuthenticator(UsernamePasswordAuthenticator authenticator) { this.authenticator = authenticator; } }