001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.server.ftp;
007:
008: import java.io.BufferedReader;
009: import java.io.IOException;
010: import java.io.InputStreamReader;
011: import java.io.OutputStreamWriter;
012: import java.io.PrintWriter;
013: import java.net.InetAddress;
014: import java.net.ServerSocket;
015: import java.net.Socket;
016: import java.sql.SQLException;
017:
018: import org.h2.engine.Constants;
019: import org.h2.store.fs.FileSystem;
020: import org.h2.util.StringUtils;
021:
022: /**
023: * The implementation of the control channel of the FTP server.
024: */
025: public class FtpControl extends Thread {
026:
027: private static final String SERVER_NAME = "Small FTP Server";
028:
029: private FtpServer server;
030: private FileSystem fs;
031: private Socket control;
032: private FtpData data;
033: private PrintWriter output;
034: private String userName;
035: private boolean connected, readonly;
036: private String currentDir = "/";
037: private String serverIpAddress;
038: private boolean stop;
039: private String renameFrom;
040: private boolean replied;
041: private long restart;
042:
043: public FtpControl(Socket control, FtpServer server, boolean stop) {
044: this .server = server;
045: this .fs = server.getFileSystem();
046: this .control = control;
047: this .stop = stop;
048: }
049:
050: public void run() {
051: try {
052: output = new PrintWriter(new OutputStreamWriter(control
053: .getOutputStream(), Constants.UTF8));
054: if (stop) {
055: reply(421, "Too many users");
056: } else {
057: reply(220, SERVER_NAME);
058: // TODO need option to configure the serverIpAddress
059: serverIpAddress = control.getLocalAddress()
060: .getHostAddress().replace('.', ',');
061: BufferedReader input = new BufferedReader(
062: new InputStreamReader(control.getInputStream()));
063: while (!stop) {
064: String command = null;
065: try {
066: command = input.readLine();
067: } catch (IOException e) {
068: // ignore
069: }
070: if (command == null) {
071: break;
072: }
073: process(command);
074: }
075: if (data != null) {
076: data.close();
077: }
078: }
079: } catch (Throwable t) {
080: server.logError(t);
081: }
082: server.closeConnection();
083: }
084:
085: private void process(String command) throws SQLException,
086: IOException {
087: int idx = command.indexOf(' ');
088: String param = "";
089: if (idx >= 0) {
090: param = command.substring(idx).trim();
091: command = command.substring(0, idx);
092: }
093: command = StringUtils.toUpperEnglish(command);
094: if (command.length() == 0) {
095: reply(506, "No command");
096: return;
097: }
098: server.log(">" + command);
099: FtpEventListener listener = server.getEventListener();
100: FtpEvent event = null;
101: if (listener != null) {
102: event = new FtpEvent(this , command, param);
103: listener.beforeCommand(event);
104: }
105: replied = false;
106: if (connected) {
107: processConnected(command, param);
108: }
109: if (!replied) {
110: if ("USER".equals(command)) {
111: userName = param;
112: reply(331, "Need password");
113: } else if ("QUIT".equals(command)) {
114: reply(221, "Bye");
115: stop = true;
116: } else if ("PASS".equals(command)) {
117: if (userName == null) {
118: reply(332, "Need username");
119: } else if (server.checkUserPassword(userName, param)) {
120: reply(230, "Ok");
121: readonly = false;
122: connected = true;
123: } else if (server.checkUserPasswordReadOnly(userName,
124: param)) {
125: reply(230, "Ok, readonly");
126: readonly = true;
127: connected = true;
128: } else {
129: reply(431, "Wrong user/password");
130: }
131: } else if ("REIN".equals(command)) {
132: userName = null;
133: connected = false;
134: currentDir = "/";
135: reply(200, "Ok");
136: } else if ("HELP".equals(command)) {
137: reply(214, SERVER_NAME);
138: }
139: }
140: if (!replied) {
141: listener.onUnsupportedCommand(event);
142: reply(506, "Invalid command");
143: }
144: if (listener != null) {
145: listener.afterCommand(event);
146: }
147: }
148:
149: private void processConnected(String command, String param)
150: throws SQLException, IOException {
151: switch (command.charAt(0)) {
152: case 'C':
153: if ("CWD".equals(command)) {
154: String path = getPath(param);
155: String fileName = getFileName(path);
156: if (fs.exists(fileName) && fs.isDirectory(fileName)) {
157: if (!path.endsWith("/")) {
158: path += "/";
159: }
160: currentDir = path;
161: reply(250, "Ok");
162: } else {
163: reply(550, "Failed");
164: }
165: } else if ("CDUP".equals(command)) {
166: if (currentDir.length() > 1) {
167: int idx = currentDir.lastIndexOf("/", currentDir
168: .length() - 2);
169: currentDir = currentDir.substring(0, idx + 1);
170: reply(250, "Ok");
171: } else {
172: reply(550, "Failed");
173: }
174: }
175: break;
176: case 'D':
177: if ("DELE".equals(command)) {
178: String fileName = getFileName(param);
179: if (!readonly && fs.exists(fileName)
180: && !fs.isDirectory(fileName)
181: && fs.tryDelete(fileName)) {
182: if (server.getAllowTask()
183: && fileName.endsWith(FtpServer.TASK_SUFFIX)) {
184: server.stopTask(fileName);
185: }
186: reply(250, "Ok");
187: } else {
188: reply(500, "Delete failed");
189: }
190: }
191: break;
192: case 'L':
193: if ("LIST".equals(command)) {
194: processList(param, true);
195: }
196: break;
197: case 'M':
198: if ("MKD".equals(command)) {
199: processMakeDir(param);
200: } else if ("MODE".equals(command)) {
201: if ("S".equals(StringUtils.toUpperEnglish(param))) {
202: reply(200, "Ok");
203: } else {
204: reply(504, "Invalid");
205: }
206: } else if ("MDTM".equals(command)) {
207: String fileName = getFileName(param);
208: if (fs.exists(fileName) && !fs.isDirectory(fileName)) {
209: reply(213, server.formatLastModified(fileName));
210: } else {
211: reply(550, "Failed");
212: }
213: }
214: break;
215: case 'N':
216: if ("NLST".equals(command)) {
217: processList(param, false);
218: } else if ("NOOP".equals(command)) {
219: reply(200, "Ok");
220: }
221: break;
222: case 'P':
223: if ("PWD".equals(command)) {
224: reply(257, StringUtils.quoteIdentifier(currentDir)
225: + " directory");
226: } else if ("PASV".equals(command)) {
227: ServerSocket dataSocket = server.createDataSocket();
228: data = new FtpData(server, control.getInetAddress(),
229: dataSocket);
230: data.start();
231: int port = dataSocket.getLocalPort();
232: reply(227, "Passive Mode (" + serverIpAddress + ","
233: + (port >> 8) + "," + (port & 255) + ")");
234: } else if ("PORT".equals(command)) {
235: String[] list = StringUtils
236: .arraySplit(param, ',', true);
237: String host = list[0] + "." + list[1] + "." + list[2]
238: + "." + list[3];
239: int port = (Integer.parseInt(list[4]) << 8)
240: | Integer.parseInt(list[5]);
241: InetAddress address = InetAddress.getByName(host);
242: if (address.equals(control.getInetAddress())) {
243: data = new FtpData(server, address, port);
244: reply(200, "Ok");
245: } else {
246: server.log("Port REJECTED:" + address
247: + " expected:" + control.getInetAddress());
248: reply(550, "Failed");
249: }
250: }
251: break;
252: case 'R':
253: if ("RNFR".equals(command)) {
254: String fileName = getFileName(param);
255: if (fs.exists(fileName)) {
256: renameFrom = fileName;
257: reply(350, "Ok");
258: } else {
259: reply(450, "Not found");
260: }
261: } else if ("RNTO".equals(command)) {
262: if (renameFrom == null) {
263: reply(503, "RNFR required");
264: } else {
265: String fileOld = renameFrom;
266: String fileNew = getFileName(param);
267: boolean ok = false;
268: if (!readonly) {
269: try {
270: fs.rename(fileOld, fileNew);
271: reply(250, "Ok");
272: ok = true;
273: } catch (SQLException e) {
274: server.logError(e);
275: }
276: }
277: if (!ok) {
278: reply(550, "Failed");
279: }
280: }
281: } else if ("RETR".equals(command)) {
282: String fileName = getFileName(param);
283: if (fs.exists(fileName) && !fs.isDirectory(fileName)) {
284: reply(150, "Starting transfer");
285: try {
286: data.send(fs, fileName, restart);
287: reply(226, "Ok");
288: } catch (IOException e) {
289: server.logError(e);
290: reply(426, "Failed");
291: }
292: restart = 0;
293: } else {
294: processList(param, true); // Firefox compatibility (still
295: // not good)
296: // reply(426, "Not a file");
297: }
298: } else if ("RMD".equals(command)) {
299: processRemoveDir(param);
300: } else if ("REST".equals(command)) {
301: try {
302: restart = Integer.parseInt(param);
303: reply(350, "Ok");
304: } catch (NumberFormatException e) {
305: reply(500, "Invalid");
306: }
307: }
308: break;
309: case 'S':
310: if ("SYST".equals(command)) {
311: reply(215, "UNIX Type: L8");
312: } else if ("SITE".equals(command)) {
313: reply(500, "Not understood");
314: } else if ("SIZE".equals(command)) {
315: param = getFileName(param);
316: if (fs.exists(param) && !fs.isDirectory(param)) {
317: reply(250, String.valueOf(fs.length(param)));
318: } else {
319: reply(500, "Failed");
320: }
321: } else if ("STOR".equals(command)) {
322: String fileName = getFileName(param);
323: if (!readonly && !fs.exists(fileName)
324: || !fs.isDirectory(fileName)) {
325: reply(150, "Starting transfer");
326: try {
327: data.receive(fs, fileName);
328: if (server.getAllowTask()
329: && param
330: .endsWith(FtpServer.TASK_SUFFIX)) {
331: server.startTask(fileName);
332: }
333: reply(226, "Ok");
334: } catch (Exception e) {
335: server.logError(e);
336: reply(426, "Failed");
337: }
338: } else {
339: reply(550, "Failed");
340: }
341: } else if ("STRU".equals(command)) {
342: if ("F".equals(StringUtils.toUpperEnglish(param))) {
343: reply(200, "Ok");
344: } else {
345: reply(504, "Invalid");
346: }
347: }
348: break;
349: case 'T':
350: if ("TYPE".equals(command)) {
351: param = StringUtils.toUpperEnglish(param);
352: if ("A".equals(param) || "A N".equals(param)) {
353: reply(200, "Ok");
354: } else if ("I".equals(param) || "L 8".equals(param)) {
355: reply(200, "Ok");
356: } else {
357: reply(500, "Invalid");
358: }
359: }
360: break;
361: case 'X':
362: if ("XMKD".equals(command)) {
363: processMakeDir(param);
364: } else if ("XRMD".equals(command)) {
365: processRemoveDir(param);
366: }
367: break;
368: default:
369: break;
370: }
371: }
372:
373: void processMakeDir(String param) throws IOException {
374: String fileName = getFileName(param);
375: boolean ok = false;
376: if (!readonly) {
377: try {
378: fs.mkdirs(fileName);
379: reply(257, StringUtils.quoteIdentifier(param)
380: + " directory");
381: ok = true;
382: } catch (SQLException e) {
383: server.logError(e);
384: }
385: }
386: if (!ok) {
387: reply(500, "Failed");
388: }
389: }
390:
391: void processRemoveDir(String param) throws IOException {
392: String fileName = getFileName(param);
393: if (!readonly && fs.exists(fileName)
394: && fs.isDirectory(fileName) && fs.tryDelete(fileName)) {
395: reply(250, "Ok");
396: } else {
397: reply(500, "Failed");
398: }
399: }
400:
401: private String getFileName(String file) {
402: return server.getFileName(file.startsWith("/") ? file
403: : currentDir + file);
404: }
405:
406: private String getPath(String path) {
407: return path.startsWith("/") ? path : currentDir + path;
408: }
409:
410: private void processList(String param, boolean directories)
411: throws SQLException, IOException {
412: String directory = getFileName(param);
413: if (!fs.exists(directory)) {
414: reply(450, "Directory does not exist");
415: return;
416: } else if (!fs.isDirectory(directory)) {
417: reply(450, "Not a directory");
418: return;
419: }
420: String list = server
421: .getDirectoryListing(directory, directories);
422: reply(150, "Starting transfer");
423: server.log(list);
424: // need to use the current locale (UTF-8 would be wrong for the Windows
425: // Explorer)
426: data.send(list.getBytes());
427: reply(226, "Done");
428: }
429:
430: private void reply(int code, String message) throws IOException {
431: server.log(code + " " + message);
432: output.print(code + " " + message + "\r\n");
433: output.flush();
434: replied = true;
435: }
436:
437: }
|