1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-06-26 13:24:49 +02:00
Smack/extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5TestProxy.java
Florian Schmaus 4b56446e40 Introduce SmackException
SmackException (and it's subclasses) is for all errors/exceptions not
defined by any XMPP specification. XMPPException is now an abstract
class for all errors defined by the XMPP specifications.

Methods that involve an IQ exchange now either return the result, which
is obtained by IQ response, or they throw an XMPPErrorException if an IQ
error was the result of the IQ set/get. If there was no response from
the server within the default packet timeout, a NoResponseException will
be thrown.

XMPP SASL errors are now also reported accordingly.

SMACK-426
2014-03-14 01:46:42 +01:00

290 lines
8.6 KiB
Java

/**
*
* Copyright 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 org.jivesoftware.smackx.bytestreams.socks5;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smackx.bytestreams.socks5.Socks5Utils;
/**
* Simple SOCKS5 proxy for testing purposes. It is almost the same as the Socks5Proxy class but the
* port can be configured more easy and it all connections are allowed.
*
* @author Henning Staib
*/
public class Socks5TestProxy {
/* SOCKS5 proxy singleton */
private static Socks5TestProxy socks5Server;
/* reusable implementation of a SOCKS5 proxy server process */
private Socks5ServerProcess serverProcess;
/* thread running the SOCKS5 server process */
private Thread serverThread;
/* server socket to accept SOCKS5 connections */
private ServerSocket serverSocket;
/* assigns a connection to a digest */
private final Map<String, Socket> connectionMap = new ConcurrentHashMap<String, Socket>();
/* port of the test proxy */
private int port = 7777;
/**
* Private constructor.
*/
private Socks5TestProxy(int port) {
this.serverProcess = new Socks5ServerProcess();
this.port = port;
}
/**
* Returns the local SOCKS5 proxy server
*
* @param port of the test proxy
* @return the local SOCKS5 proxy server
*/
public static synchronized Socks5TestProxy getProxy(int port) {
if (socks5Server == null) {
socks5Server = new Socks5TestProxy(port);
socks5Server.start();
}
return socks5Server;
}
/**
* Stops the test proxy
*/
public static synchronized void stopProxy() {
if (socks5Server != null) {
socks5Server.stop();
socks5Server = null;
}
}
/**
* Starts the local SOCKS5 proxy server. If it is already running, this method does nothing.
*/
public synchronized void start() {
if (isRunning()) {
return;
}
try {
this.serverSocket = new ServerSocket(this.port);
this.serverThread = new Thread(this.serverProcess);
this.serverThread.start();
}
catch (IOException e) {
e.printStackTrace();
// do nothing
}
}
/**
* Stops the local SOCKS5 proxy server. If it is not running this method does nothing.
*/
public synchronized void stop() {
if (!isRunning()) {
return;
}
try {
this.serverSocket.close();
}
catch (IOException e) {
// do nothing
e.printStackTrace();
}
if (this.serverThread != null && this.serverThread.isAlive()) {
try {
this.serverThread.interrupt();
this.serverThread.join();
}
catch (InterruptedException e) {
// do nothing
e.printStackTrace();
}
}
this.serverThread = null;
this.serverSocket = null;
}
/**
* Returns the host address of the local SOCKS5 proxy server.
*
* @return the host address of the local SOCKS5 proxy server
*/
public String getAddress() {
try {
return InetAddress.getLocalHost().getHostAddress();
}
catch (UnknownHostException e) {
return null;
}
}
/**
* Returns the port of the local SOCKS5 proxy server. If it is not running -1 will be returned.
*
* @return the port of the local SOCKS5 proxy server or -1 if proxy is not running
*/
public int getPort() {
if (!isRunning()) {
return -1;
}
return this.serverSocket.getLocalPort();
}
/**
* Returns the socket for the given digest.
*
* @param digest identifying the connection
* @return socket or null if there is no socket for the given digest
*/
public Socket getSocket(String digest) {
return this.connectionMap.get(digest);
}
/**
* Returns true if the local SOCKS5 proxy server is running, otherwise false.
*
* @return true if the local SOCKS5 proxy server is running, otherwise false
*/
public boolean isRunning() {
return this.serverSocket != null;
}
/**
* Implementation of a simplified SOCKS5 proxy server.
*
* @author Henning Staib
*/
class Socks5ServerProcess implements Runnable {
public void run() {
while (true) {
Socket socket = null;
try {
if (Socks5TestProxy.this.serverSocket.isClosed()
|| Thread.currentThread().isInterrupted()) {
return;
}
// accept connection
socket = Socks5TestProxy.this.serverSocket.accept();
// initialize connection
establishConnection(socket);
}
catch (SocketException e) {
/* do nothing */
}
catch (Exception e) {
try {
e.printStackTrace();
socket.close();
}
catch (IOException e1) {
/* Do Nothing */
}
}
}
}
/**
* Negotiates a SOCKS5 connection and stores it on success.
*
* @param socket connection to the client
* @throws SmackException if client requests a connection in an unsupported way
* @throws IOException if a network error occurred
*/
private void establishConnection(Socket socket) throws IOException, SmackException {
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
DataInputStream in = new DataInputStream(socket.getInputStream());
// first byte is version should be 5
int b = in.read();
if (b != 5) {
throw new SmackException("Only SOCKS5 supported");
}
// second byte number of authentication methods supported
b = in.read();
// read list of supported authentication methods
byte[] auth = new byte[b];
in.readFully(auth);
byte[] authMethodSelectionResponse = new byte[2];
authMethodSelectionResponse[0] = (byte) 0x05; // protocol version
// only authentication method 0, no authentication, supported
boolean noAuthMethodFound = false;
for (int i = 0; i < auth.length; i++) {
if (auth[i] == (byte) 0x00) {
noAuthMethodFound = true;
break;
}
}
if (!noAuthMethodFound) {
authMethodSelectionResponse[1] = (byte) 0xFF; // no acceptable methods
out.write(authMethodSelectionResponse);
out.flush();
throw new SmackException("Authentication method not supported");
}
authMethodSelectionResponse[1] = (byte) 0x00; // no-authentication method
out.write(authMethodSelectionResponse);
out.flush();
// receive connection request
byte[] connectionRequest = Socks5Utils.receiveSocks5Message(in);
// extract digest
String responseDigest = new String(connectionRequest, 5, connectionRequest[4]);
connectionRequest[1] = (byte) 0x00; // set return status to 0 (success)
out.write(connectionRequest);
out.flush();
// store connection
Socks5TestProxy.this.connectionMap.put(responseDigest, socket);
}
}
}