/* * ftp4j - A pure Java FTP client library * * Copyright (C) 2008-2010 Carlo Pelliccia (www.sauronsoftware.it) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version * 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License 2.1 for more details. * * You should have received a copy of the GNU Lesser General Public * License version 2.1 along with this program. * If not, see . */ package it.sauronsoftware.ftp4j.connectors; import it.sauronsoftware.ftp4j.FTPConnector; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; /** * This one connects a remote ftp host through a SOCKS5 proxy server. * * The connector's default value for the * useSuggestedAddressForDataConnections flag is false. * * @author Carlo Pelliccia */ public class SOCKS5Connector extends FTPConnector { /** * The socks5 proxy host name. */ private String socks5host; /** * The socks5 proxy port. */ private int socks5port; /** * The socks5 proxy user (optional). */ private String socks5user; /** * The socks5 proxy password (optional). */ private String socks5pass; /** * It builds the connector. * * @param socks5host * The socks5 proxy host name. * @param socks5port * The socks5 proxy port. * @param socks5user * The socks5 proxy user (optional, can be set to null). * @param socks5pass * The socks5 proxy password (optional, can be set to null if * also socks5user is null). */ public SOCKS5Connector(String socks5host, int socks5port, String socks5user, String socks5pass) { this.socks5host = socks5host; this.socks5port = socks5port; this.socks5user = socks5user; this.socks5pass = socks5pass; } /** * It builds the connector. * * @param socks5host * The socks5 proxy host name. * @param socks5port * The socks5 proxy port. */ public SOCKS5Connector(String socks5host, int socks5port) { this(socks5host, socks5port, null, null); } private Socket socksConnect(String host, int port, boolean forDataTransfer) throws IOException { // Authentication flag boolean authentication = socks5user != null && socks5pass != null; // A connection status flag. boolean connected = false; // The socket for the connection with the proxy. Socket socket = null; InputStream in = null; OutputStream out = null; // FTPConnection routine. try { if (forDataTransfer) { socket = tcpConnectForDataTransferChannel(socks5host, socks5port); } else { socket = tcpConnectForCommunicationChannel(socks5host, socks5port); } in = socket.getInputStream(); out = socket.getOutputStream(); int aux; // Version 5. out.write(0x05); // Authentication? if (authentication) { // Authentication with username/password. out.write(0x01); out.write(0x02); } else { // No authentication. out.write(0x01); out.write(0x00); } // Get the response. aux = read(in); if (aux != 0x05) { throw new IOException("SOCKS5Connector: invalid proxy response"); } aux = read(in); if (authentication) { if (aux != 0x02) { throw new IOException( "SOCKS5Connector: proxy doesn't support " + "username/password authentication method"); } // Authentication with username/password. byte[] user = socks5user.getBytes("UTF-8"); byte[] pass = socks5pass.getBytes("UTF-8"); int userLength = user.length; int passLength = pass.length; // Check sizes. if (userLength > 0xff) { throw new IOException("SOCKS5Connector: username too long"); } if (passLength > 0xff) { throw new IOException("SOCKS5Connector: password too long"); } // Version 1. out.write(0x01); // Username. out.write(userLength); out.write(user); // Password. out.write(passLength); out.write(pass); // Check the response. aux = read(in); if (aux != 0x01) { throw new IOException( "SOCKS5Connector: invalid proxy response"); } aux = read(in); if (aux != 0x00) { throw new IOException( "SOCKS5Connector: authentication failed"); } } else { if (aux != 0x00) { throw new IOException( "SOCKS5Connector: proxy requires authentication"); } } // FTPConnection request. // Version 5. out.write(0x05); // CONNECT method out.write(0x01); // Reserved. out.write(0x00); // Address type -> domain. out.write(0x03); // Domain. byte[] domain = host.getBytes("UTF-8"); if (domain.length > 0xff) { throw new IOException("SOCKS5Connector: domain name too long"); } out.write(domain.length); out.write(domain); // Port number. out.write(port >> 8); out.write(port); // FTPConnection response // Version? aux = read(in); if (aux != 0x05) { throw new IOException("SOCKS5Connector: invalid proxy response"); } // Status? aux = read(in); switch (aux) { case 0x00: // Connected! break; case 0x01: throw new IOException("SOCKS5Connector: general failure"); case 0x02: throw new IOException( "SOCKS5Connector: connection not allowed by ruleset"); case 0x03: throw new IOException("SOCKS5Connector: network unreachable"); case 0x04: throw new IOException("SOCKS5Connector: host unreachable"); case 0x05: throw new IOException( "SOCKS5Connector: connection refused by destination host"); case 0x06: throw new IOException("SOCKS5Connector: TTL expired"); case 0x07: throw new IOException( "SOCKS5Connector: command not supported / protocol error"); case 0x08: throw new IOException( "SOCKS5Connector: address type not supported"); default: throw new IOException("SOCKS5Connector: invalid proxy response"); } // Reserved. in.skip(1); // Address type. aux = read(in); if (aux == 0x01) { // IPv4. in.skip(4); } else if (aux == 0x03) { // Domain name. aux = read(in); in.skip(aux); } else if (aux == 0x04) { // IPv6. in.skip(16); } else { throw new IOException("SOCKS5Connector: invalid proxy response"); } // Port number. in.skip(2); // Well done! connected = true; } catch (IOException e) { throw e; } finally { if (!connected) { if (out != null) { try { out.close(); } catch (Throwable t) { ; } } if (in != null) { try { in.close(); } catch (Throwable t) { ; } } if (socket != null) { try { socket.close(); } catch (Throwable t) { ; } } } } return socket; } private int read(InputStream in) throws IOException { int aux = in.read(); if (aux < 0) { throw new IOException( "SOCKS5Connector: connection closed by the proxy"); } return aux; } public Socket connectForCommunicationChannel(String host, int port) throws IOException { return socksConnect(host, port, false); } public Socket connectForDataTransferChannel(String host, int port) throws IOException { return socksConnect(host, port, true); } }