001 /* 002 * ftp4j - A pure Java FTP client library 003 * 004 * Copyright (C) 2008 Carlo Pelliccia (www.sauronsoftware.it) 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <http://www.gnu.org/licenses/>. 018 */ 019 package it.sauronsoftware.ftp4j.connectors; 020 021 import it.sauronsoftware.ftp4j.FTPConnection; 022 import it.sauronsoftware.ftp4j.FTPConnector; 023 024 import java.io.IOException; 025 import java.io.InputStream; 026 import java.io.OutputStream; 027 import java.net.Socket; 028 029 /** 030 * This one connects a remote ftp host through a SOCKS5 proxy server. 031 * 032 * @author Carlo Pelliccia 033 */ 034 public class SOCKS5Connector implements FTPConnector { 035 036 /** 037 * The socks5 proxy host name. 038 */ 039 private String socks5host; 040 041 /** 042 * The socks5 proxy port. 043 */ 044 private int socks5port; 045 046 /** 047 * The socks5 proxy user (optional). 048 */ 049 private String socks5user; 050 051 /** 052 * The socks5 proxy password (optional). 053 */ 054 private String socks5pass; 055 056 /** 057 * It builds the connector. 058 * 059 * @param socks5host 060 * The socks5 proxy host name. 061 * @param socks5port 062 * The socks5 proxy port. 063 * @param socks5user 064 * The socks5 proxy user (optional, can be set to null). 065 * @param socks5pass 066 * The socks5 proxy password (optional, can be set to null if 067 * also socks5user is null). 068 */ 069 public SOCKS5Connector(String socks5host, int socks5port, 070 String socks5user, String socks5pass) { 071 this.socks5host = socks5host; 072 this.socks5port = socks5port; 073 this.socks5user = socks5user; 074 this.socks5pass = socks5pass; 075 } 076 077 /** 078 * It builds the connector. 079 * 080 * @param socks5host 081 * The socks5 proxy host name. 082 * @param socks5port 083 * The socks5 proxy port. 084 */ 085 public SOCKS5Connector(String socks5host, int socks5port) { 086 this(socks5host, socks5port, null, null); 087 } 088 089 private FTPConnection connect(String host, int port) throws IOException { 090 // Authentication flag 091 boolean authentication = socks5user != null && socks5pass != null; 092 // A connection status flag. 093 boolean connected = false; 094 // The socket for the connection with the proxy. 095 Socket socket = null; 096 InputStream in = null; 097 OutputStream out = null; 098 // FTPConnection routine. 099 try { 100 socket = new Socket(socks5host, socks5port); 101 in = socket.getInputStream(); 102 out = socket.getOutputStream(); 103 int aux; 104 // Version 5. 105 out.write(0x05); 106 // Authentication? 107 if (authentication) { 108 // Authentication with username/password. 109 out.write(0x01); 110 out.write(0x02); 111 } else { 112 // No authentication. 113 out.write(0x01); 114 out.write(0x00); 115 } 116 // Get the response. 117 aux = read(in); 118 if (aux != 0x05) { 119 throw new IOException("SOCKS5Connector: invalid proxy response"); 120 } 121 aux = read(in); 122 if (authentication) { 123 if (aux != 0x02) { 124 throw new IOException( 125 "SOCKS5Connector: proxy doesn't support " 126 + "username/password authentication method"); 127 } 128 // Authentication with username/password. 129 byte[] user = socks5user.getBytes("UTF-8"); 130 byte[] pass = socks5pass.getBytes("UTF-8"); 131 int userLength = user.length; 132 int passLength = pass.length; 133 // Check sizes. 134 if (userLength > 0xff) { 135 throw new IOException("SOCKS5Connector: username too long"); 136 } 137 if (passLength > 0xff) { 138 throw new IOException("SOCKS5Connector: password too long"); 139 } 140 // Version 1. 141 out.write(0x01); 142 // Username. 143 out.write(userLength); 144 out.write(user); 145 // Password. 146 out.write(passLength); 147 out.write(pass); 148 // Check the response. 149 aux = read(in); 150 if (aux != 0x01) { 151 throw new IOException( 152 "SOCKS5Connector: invalid proxy response"); 153 } 154 aux = read(in); 155 if (aux != 0x00) { 156 throw new IOException( 157 "SOCKS5Connector: authentication failed"); 158 } 159 } else { 160 if (aux != 0x00) { 161 throw new IOException( 162 "SOCKS5Connector: proxy requires authentication"); 163 } 164 } 165 // FTPConnection request. 166 // Version 5. 167 out.write(0x05); 168 // CONNECT method 169 out.write(0x01); 170 // Reserved. 171 out.write(0x00); 172 // Address type -> domain. 173 out.write(0x03); 174 // Domain. 175 byte[] domain = host.getBytes("UTF-8"); 176 if (domain.length > 0xff) { 177 throw new IOException("SOCKS5Connector: domain name too long"); 178 } 179 out.write(domain.length); 180 out.write(domain); 181 // Port number. 182 out.write(port >> 8); 183 out.write(port); 184 185 // FTPConnection response 186 // Version? 187 aux = read(in); 188 if (aux != 0x05) { 189 throw new IOException("SOCKS5Connector: invalid proxy response"); 190 } 191 // Status? 192 aux = read(in); 193 switch (aux) { 194 case 0x00: 195 // Connected! 196 break; 197 case 0x01: 198 throw new IOException("SOCKS5Connector: general failure"); 199 case 0x02: 200 throw new IOException( 201 "SOCKS5Connector: connection not allowed by ruleset"); 202 case 0x03: 203 throw new IOException("SOCKS5Connector: network unreachable"); 204 case 0x04: 205 throw new IOException("SOCKS5Connector: host unreachable"); 206 case 0x05: 207 throw new IOException( 208 "SOCKS5Connector: connection refused by destination host"); 209 case 0x06: 210 throw new IOException("SOCKS5Connector: TTL expired"); 211 case 0x07: 212 throw new IOException( 213 "SOCKS5Connector: command not supported / protocol error"); 214 case 0x08: 215 throw new IOException( 216 "SOCKS5Connector: address type not supported"); 217 default: 218 throw new IOException("SOCKS5Connector: invalid proxy response"); 219 } 220 // Reserved. 221 in.skip(1); 222 // Address type. 223 aux = read(in); 224 if (aux == 0x01) { 225 // IPv4. 226 in.skip(4); 227 } else if (aux == 0x03) { 228 // Domain name. 229 aux = read(in); 230 in.skip(aux); 231 } else if (aux == 0x04) { 232 // IPv6. 233 in.skip(16); 234 } else { 235 throw new IOException("SOCKS5Connector: invalid proxy response"); 236 } 237 // Port number. 238 in.skip(2); 239 // Well done! 240 connected = true; 241 } catch (IOException e) { 242 throw e; 243 } finally { 244 if (!connected) { 245 if (out != null) { 246 try { 247 out.close(); 248 } catch (Throwable t) { 249 ; 250 } 251 } 252 if (in != null) { 253 try { 254 in.close(); 255 } catch (Throwable t) { 256 ; 257 } 258 } 259 if (socket != null) { 260 try { 261 socket.close(); 262 } catch (Throwable t) { 263 ; 264 } 265 } 266 } 267 } 268 return new SocketConnection(socket, in, out); 269 } 270 271 private int read(InputStream in) throws IOException { 272 int aux = in.read(); 273 if (aux < 0) { 274 throw new IOException( 275 "SOCKS5Connector: connection closed by the proxy"); 276 } 277 return aux; 278 } 279 280 public FTPConnection connectForCommunicationChannel(String host, int port) 281 throws IOException { 282 return connect(host, port); 283 } 284 285 public FTPConnection connectForDataTransferChannel(String host, int port) 286 throws IOException { 287 return connect(host, port); 288 } 289 290 }