1 /* 2 * Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.io.*; 25 import java.net.*; 26 import jdk.test.lib.net.IPSupport; 27 28 /* 29 * @test 30 * @bug 4398880 31 * @summary FTP URL processing modified to conform to RFC 1738 32 * @library /test/lib 33 * @run main/othervm FtpURL 34 * @run main/othervm -Djava.net.preferIPv4Stack=true FtpURL 35 * @run main/othervm -Djava.net.preferIPv6Addresses=true FtpURL 36 */ 37 38 public class FtpURL { 39 /** 40 * A class that simulates, on a separate, an FTP server. 41 */ 42 43 private class FtpServer extends Thread { 44 private final ServerSocket server; 45 private final int port; 46 private boolean done = false; 47 private boolean portEnabled = true; 48 private boolean pasvEnabled = true; 49 private boolean extendedEnabled = true; 50 private String username; 51 private String password; 52 private String cwd; 53 private String filename; 54 private String type; 55 private boolean list = false; 56 57 /** 58 * This Inner class will handle ONE client at a time. 59 * That's where 99% of the protocol handling is done. 60 */ 61 62 private class FtpServerHandler { 63 BufferedReader in; 64 PrintWriter out; 65 Socket client; 66 private final int ERROR = 0; 67 private final int USER = 1; 68 private final int PASS = 2; 69 private final int CWD = 3; 70 private final int CDUP = 4; 71 private final int PWD = 5; 72 private final int TYPE = 6; 73 private final int NOOP = 7; 74 private final int RETR = 8; 75 private final int PASV = 9; 76 private final int PORT = 10; 77 private final int LIST = 11; 78 private final int REIN = 12; 79 private final int QUIT = 13; 80 private final int STOR = 14; 81 private final int NLST = 15; 82 private final int RNFR = 16; 83 private final int RNTO = 17; 84 private final int EPSV = 18; 85 String[] cmds = { "USER", "PASS", "CWD", "CDUP", "PWD", "TYPE", 86 "NOOP", "RETR", "PASV", "PORT", "LIST", "REIN", 87 "QUIT", "STOR", "NLST", "RNFR", "RNTO", "EPSV" }; 88 private String arg = null; 89 private ServerSocket pasv = null; 90 private int data_port = 0; 91 private InetAddress data_addr = null; 92 93 /** 94 * Parses a line to match it with one of the supported FTP commands. 95 * Returns the command number. 96 */ 97 parseCmd(String cmd)98 private int parseCmd(String cmd) { 99 System.out.println("Received command: " + cmd); 100 if (cmd == null || cmd.length() < 3) 101 return ERROR; 102 int blank = cmd.indexOf(' '); 103 if (blank < 0) 104 blank = cmd.length(); 105 if (blank < 3) 106 return ERROR; 107 String s = cmd.substring(0, blank); 108 if (cmd.length() > blank+1) 109 arg = cmd.substring(blank+1, cmd.length()); 110 else 111 arg = null; 112 for (int i = 0; i < cmds.length; i++) { 113 if (s.equalsIgnoreCase(cmds[i])) 114 return i+1; 115 } 116 return ERROR; 117 } 118 FtpServerHandler(Socket cl)119 public FtpServerHandler(Socket cl) { 120 client = cl; 121 } 122 isPasvSet()123 protected boolean isPasvSet() { 124 if (pasv != null && !pasvEnabled) { 125 try { 126 pasv.close(); 127 } catch (IOException ex) { 128 } 129 pasv = null; 130 } 131 if (pasvEnabled && pasv != null) 132 return true; 133 return false; 134 } 135 136 /** 137 * Open the data socket with the client. This can be the 138 * result of a "EPSV", "PASV" or "PORT" command. 139 */ 140 getOutDataStream()141 protected OutputStream getOutDataStream() { 142 try { 143 if (isPasvSet()) { 144 Socket s = pasv.accept(); 145 return s.getOutputStream(); 146 } 147 if (data_addr != null) { 148 Socket s = new Socket(data_addr, data_port); 149 data_addr = null; 150 data_port = 0; 151 return s.getOutputStream(); 152 } 153 } catch (Exception e) { 154 e.printStackTrace(); 155 } 156 return null; 157 } 158 getInDataStream()159 protected InputStream getInDataStream() { 160 try { 161 if (isPasvSet()) { 162 Socket s = pasv.accept(); 163 return s.getInputStream(); 164 } 165 if (data_addr != null) { 166 Socket s = new Socket(data_addr, data_port); 167 data_addr = null; 168 data_port = 0; 169 return s.getInputStream(); 170 } 171 } catch (Exception e) { 172 e.printStackTrace(); 173 } 174 return null; 175 } 176 177 /** 178 * Handles the protocol exchange with the client. 179 */ 180 run()181 public void run() { 182 boolean done = false; 183 String str; 184 int res; 185 boolean logged = false; 186 boolean waitpass = false; 187 188 try { 189 in = new BufferedReader(new InputStreamReader(client.getInputStream())); 190 out = new PrintWriter(client.getOutputStream(), true); 191 out.println("220 tatooine FTP server (SunOS 5.8) ready."); 192 } catch (Exception ex) { 193 return; 194 } 195 synchronized (FtpServer.this) { 196 while (!done) { 197 try { 198 str = in.readLine(); 199 res = parseCmd(str); 200 if ((res > PASS && res != QUIT) && !logged) { 201 out.println("530 Not logged in."); 202 continue; 203 } 204 switch (res) { 205 case ERROR: 206 out.println("500 '" + str + "': command not understood."); 207 break; 208 case USER: 209 if (!logged && !waitpass) { 210 username = str.substring(5); 211 password = null; 212 cwd = null; 213 if ("user2".equals(username)) { 214 out.println("230 Guest login ok, access restrictions apply."); 215 logged = true; 216 } else { 217 out.println("331 Password required for " + arg); 218 waitpass = true; 219 } 220 } else { 221 out.println("503 Bad sequence of commands."); 222 } 223 break; 224 case PASS: 225 if (!logged && waitpass) { 226 out.println("230 Guest login ok, access restrictions apply."); 227 password = str.substring(5); 228 logged = true; 229 waitpass = false; 230 } else 231 out.println("503 Bad sequence of commands."); 232 break; 233 case QUIT: 234 out.println("221 Goodbye."); 235 out.flush(); 236 out.close(); 237 if (pasv != null) 238 pasv.close(); 239 done = true; 240 break; 241 case TYPE: 242 out.println("200 Type set to " + arg + "."); 243 type = arg; 244 break; 245 case CWD: 246 out.println("250 CWD command successful."); 247 if (cwd == null) 248 cwd = str.substring(4); 249 else 250 cwd = cwd + "/" + str.substring(4); 251 break; 252 case CDUP: 253 out.println("250 CWD command successful."); 254 break; 255 case PWD: 256 out.println("257 \"" + cwd + "\" is current directory"); 257 break; 258 case EPSV: 259 if (!extendedEnabled || !pasvEnabled) { 260 out.println("500 EPSV is disabled, " + 261 "use PORT instead."); 262 continue; 263 } 264 if (!(server.getInetAddress() instanceof Inet6Address)) { 265 // pretend EPSV is not implemented 266 out.println("500 '" + str + "': command not understood."); 267 break; 268 } 269 if ("all".equalsIgnoreCase(arg)) { 270 out.println("200 EPSV ALL command successful."); 271 continue; 272 } 273 try { 274 if (pasv == null) 275 pasv = new ServerSocket(0, 0, server.getInetAddress()); 276 int port = pasv.getLocalPort(); 277 out.println("229 Entering Extended" + 278 " Passive Mode (|||" + port + "|)"); 279 } catch (IOException ssex) { 280 out.println("425 Can't build data connection:" + 281 " Connection refused."); 282 } 283 break; 284 285 case PASV: 286 if (!pasvEnabled) { 287 out.println("500 PASV is disabled, use PORT instead."); 288 continue; 289 } 290 try { 291 if (pasv == null) { 292 // Not sure how to support PASV mode over 293 // IPv6 294 pasv = new ServerSocket(); 295 pasv.bind(new InetSocketAddress("127.0.0.1", 0)); 296 } 297 int port = pasv.getLocalPort(); 298 out.println("227 Entering Passive Mode (127,0,0,1," + 299 (port >> 8) + "," + (port & 0xff) +")"); 300 } catch (IOException ssex) { 301 out.println("425 Can't build data connection: Connection refused."); 302 } 303 break; 304 case PORT: 305 if (!portEnabled) { 306 out.println("500 PORT is disabled, use PASV instead"); 307 continue; 308 } 309 StringBuffer host; 310 int i=0, j=4; 311 while (j>0) { 312 i = arg.indexOf(',', i+1); 313 if (i < 0) 314 break; 315 j--; 316 } 317 if (j != 0) { 318 out.println("500 '" + arg + "': command not understood."); 319 continue; 320 } 321 try { 322 host = new StringBuffer(arg.substring(0,i)); 323 for (j=0; j < host.length(); j++) 324 if (host.charAt(j) == ',') 325 host.setCharAt(j, '.'); 326 String ports = arg.substring(i+1); 327 i = ports.indexOf(','); 328 data_port = Integer.parseInt(ports.substring(0,i)) << 8; 329 data_port += (Integer.parseInt(ports.substring(i+1))); 330 data_addr = InetAddress.getByName(host.toString()); 331 out.println("200 Command okay."); 332 } catch (Exception ex3) { 333 data_port = 0; 334 data_addr = null; 335 out.println("500 '" + arg + "': command not understood."); 336 } 337 break; 338 case RETR: 339 { 340 filename = str.substring(5); 341 OutputStream dout = getOutDataStream(); 342 if (dout != null) { 343 out.println("200 Command okay."); 344 PrintWriter pout = new PrintWriter(new BufferedOutputStream(dout)); 345 pout.println("Hello World!"); 346 pout.flush(); 347 pout.close(); 348 list = false; 349 } else 350 out.println("425 Can't build data connection: Connection refused."); 351 } 352 break; 353 case NLST: 354 filename = arg; 355 case LIST: 356 { 357 OutputStream dout = getOutDataStream(); 358 if (dout != null) { 359 out.println("200 Command okay."); 360 PrintWriter pout = new PrintWriter(new BufferedOutputStream(dout)); 361 pout.println("total 130"); 362 pout.println("drwxrwxrwt 7 sys sys 577 May 12 03:30 ."); 363 pout.println("drwxr-xr-x 39 root root 1024 Mar 27 12:55 .."); 364 pout.println("drwxrwxr-x 2 root root 176 Apr 10 12:02 .X11-pipe"); 365 pout.println("drwxrwxr-x 2 root root 176 Apr 10 12:02 .X11-unix"); 366 pout.println("drwxrwxrwx 2 root root 179 Mar 30 15:09 .pcmcia"); 367 pout.println("drwxrwxrwx 2 jladen staff 117 Mar 30 18:18 .removable"); 368 pout.println("drwxrwxrwt 2 root root 327 Mar 30 15:08 .rpc_door"); 369 pout.println("-rw-r--r-- 1 root other 21 May 5 16:59 hello2.txt"); 370 pout.println("-rw-rw-r-- 1 root sys 5968 Mar 30 15:08 ps_data"); 371 pout.flush(); 372 pout.close(); 373 list = true; 374 try { 375 FtpServer.this.wait (); 376 } catch (Exception e) {} 377 } else 378 out.println("425 Can't build data connection: Connection refused."); 379 } 380 break; 381 case STOR: 382 { 383 InputStream is = getInDataStream(); 384 if (is != null) { 385 out.println("200 Command okay."); 386 BufferedInputStream din = new BufferedInputStream(is); 387 int val; 388 do { 389 val = din.read(); 390 } while (val != -1); 391 din.close(); 392 } else 393 out.println("425 Can't build data connection: Connection refused."); 394 } 395 break; 396 } 397 } catch (IOException ioe) { 398 ioe.printStackTrace(); 399 try { 400 out.close(); 401 } catch (Exception ex2) { 402 } 403 done = true; 404 } 405 } 406 } 407 } 408 } 409 FtpServer(int port)410 public FtpServer(int port) { 411 this(InetAddress.getLoopbackAddress(), port); 412 } 413 FtpServer(InetAddress address, int port)414 public FtpServer(InetAddress address, int port) { 415 this.port = port; 416 try { 417 if (address == null) { 418 server = new ServerSocket(port); 419 } else { 420 server = new ServerSocket(); 421 server.bind(new InetSocketAddress(address, port)); 422 } 423 } catch (IOException e) { 424 throw new UncheckedIOException(e); 425 } 426 } 427 FtpServer()428 public FtpServer() { 429 this(null, 21); 430 } 431 getPort()432 public int getPort() { 433 return server.getLocalPort(); 434 } 435 getAuthority()436 public String getAuthority() { 437 InetAddress address = server.getInetAddress(); 438 String hostaddr = address.isAnyLocalAddress() 439 ? "localhost" : address.getHostAddress(); 440 if (hostaddr.indexOf(':') > -1) { 441 hostaddr = "[" + hostaddr +"]"; 442 } 443 return hostaddr + ":" + getPort(); 444 } 445 446 /** 447 * A way to tell the server that it can stop. 448 */ terminate()449 synchronized public void terminate() { 450 done = true; 451 } 452 setPortEnabled(boolean ok)453 synchronized public void setPortEnabled(boolean ok) { 454 portEnabled = ok; 455 } 456 setPasvEnabled(boolean ok)457 synchronized public void setPasvEnabled(boolean ok) { 458 pasvEnabled = ok; 459 } 460 getUsername()461 String getUsername() { 462 return username; 463 } 464 getPassword()465 String getPassword() { 466 return password; 467 } 468 pwd()469 String pwd() { 470 return cwd; 471 } 472 getFilename()473 String getFilename() { 474 return filename; 475 } 476 getType()477 String getType() { 478 return type; 479 } 480 getList()481 synchronized boolean getList() { 482 notify (); 483 return list; 484 } 485 486 /* 487 * All we got to do here is create a ServerSocket and wait for connections. 488 * When a connection happens, we just have to create a thread that will 489 * handle it. 490 */ run()491 public void run() { 492 try { 493 Socket client; 494 for (int i=0; i<2; i++) { 495 client = server.accept(); 496 (new FtpServerHandler(client)).run(); 497 } 498 } catch(Exception e) { 499 } finally { 500 try { server.close(); } catch (IOException unused) {} 501 } 502 } 503 } main(String[] args)504 public static void main(String[] args) throws Exception { 505 IPSupport.throwSkippedExceptionIfNonOperational(); 506 FtpURL test = new FtpURL(); 507 } 508 FtpURL()509 public FtpURL() throws Exception { 510 FtpServer server = new FtpServer(InetAddress.getLoopbackAddress(), 0); 511 BufferedReader in = null; 512 try { 513 server.start(); 514 String authority = server.getAuthority(); 515 System.out.println("FTP server waiting for connections at: " + authority); 516 assert authority != null; 517 518 // Now let's check the URL handler 519 520 URL url = new URL("ftp://user:password@" + authority + "/%2Fetc/motd;type=a"); 521 URLConnection con = url.openConnection(Proxy.NO_PROXY); 522 in = new BufferedReader(new InputStreamReader(con.getInputStream())); 523 String s; 524 do { 525 s = in.readLine(); 526 } while (s != null); 527 if (!("user".equals(server.getUsername()))) 528 throw new RuntimeException("Inccorect username received"); 529 if (!("password".equals(server.getPassword()))) 530 throw new RuntimeException("Inccorect password received"); 531 if (!("/etc".equals(server.pwd()))) 532 throw new RuntimeException("Inccorect directory received"); 533 if (!("motd".equals(server.getFilename()))) 534 throw new RuntimeException("Inccorect username received"); 535 if (!("A".equals(server.getType()))) 536 throw new RuntimeException("Incorrect type received"); 537 538 in.close(); 539 // We're done! 540 541 // Second URL test 542 543 // Now let's check the URL handler 544 545 url = new URL("ftp://user2@" + authority + "/%2Fusr/bin;type=d"); 546 con = url.openConnection(Proxy.NO_PROXY); 547 in = new BufferedReader(new InputStreamReader(con.getInputStream())); 548 do { 549 s = in.readLine(); 550 } while (s != null); 551 if (!server.getList()) 552 throw new RuntimeException(";type=d didn't generate a NLST"); 553 if (server.getPassword() != null) 554 throw new RuntimeException("password should be null!"); 555 if (! "bin".equals(server.getFilename())) 556 throw new RuntimeException("Incorrect filename received"); 557 if (! "/usr".equals(server.pwd())) 558 throw new RuntimeException("Incorrect pwd received"); 559 // We're done! 560 561 } catch (Exception e) { 562 throw new RuntimeException("FTP support error: " + e.getMessage(), e); 563 } finally { 564 try { in.close(); } catch (Exception unused) {} 565 server.terminate(); 566 server.server.close(); 567 } 568 } 569 } 570