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