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.File;
009: import java.io.IOException;
010: import java.io.InputStream;
011: import java.io.OutputStream;
012: import java.net.ServerSocket;
013: import java.net.Socket;
014: import java.sql.SQLException;
015: import java.text.SimpleDateFormat;
016: import java.util.Date;
017: import java.util.HashMap;
018: import java.util.Locale;
019: import java.util.Properties;
020:
021: import org.h2.engine.Constants;
022: import org.h2.server.Service;
023: import org.h2.store.fs.FileSystem;
024: import org.h2.util.FileUtils;
025: import org.h2.util.IOUtils;
026: import org.h2.util.MathUtils;
027: import org.h2.util.NetUtils;
028:
029: /**
030: * Small FTP Server. Intended for ad-hoc networks in a secure environment.
031: * Remote connections are possible.
032: * See also http://cr.yp.to/ftp.html http://www.ftpguide.com/
033: */
034: public class FtpServer implements Service {
035:
036: public static final String DEFAULT_ROOT = "ftp";
037: public static final String DEFAULT_READ = "guest";
038: public static final String DEFAULT_WRITE = "sa";
039: public static final String DEFAULT_WRITE_PASSWORD = "sa";
040:
041: private ServerSocket serverSocket;
042: private int port = Constants.DEFAULT_FTP_PORT;
043: private int openConnectionCount;
044: private int maxConnectionCount = 100;
045:
046: private SimpleDateFormat dateFormatNew = new SimpleDateFormat(
047: "MMM dd HH:mm", Locale.ENGLISH);
048: private SimpleDateFormat dateFormatOld = new SimpleDateFormat(
049: "MMM dd yyyy", Locale.ENGLISH);
050: private SimpleDateFormat dateFormat = new SimpleDateFormat(
051: "yyyyMMddHHmmss");
052:
053: private String root = DEFAULT_ROOT;
054: private String writeUserName = DEFAULT_WRITE,
055: writePassword = DEFAULT_WRITE_PASSWORD;
056: private String readUserName = DEFAULT_READ;
057: private HashMap tasks = new HashMap();
058:
059: private FileSystem fs;
060: private boolean log;
061: private boolean allowTask;
062: static final String TASK_SUFFIX = ".task";
063:
064: private FtpEventListener eventListener;
065:
066: public void listen() {
067: try {
068: while (serverSocket != null) {
069: Socket s = serverSocket.accept();
070: boolean stop;
071: synchronized (this ) {
072: openConnectionCount++;
073: stop = openConnectionCount > maxConnectionCount;
074: }
075: FtpControl c = new FtpControl(s, this , stop);
076: c.start();
077: }
078: } catch (Exception e) {
079: logError(e);
080: }
081: }
082:
083: void closeConnection() {
084: synchronized (this ) {
085: openConnectionCount--;
086: }
087: }
088:
089: public ServerSocket createDataSocket() throws SQLException {
090: ServerSocket dataSocket = NetUtils.createServerSocket(0, false);
091: return dataSocket;
092: }
093:
094: void appendFile(StringBuffer buff, String fileName)
095: throws SQLException {
096: buff.append(fs.isDirectory(fileName) ? 'd' : '-');
097: buff.append('r');
098: buff.append(fs.canWrite(fileName) ? 'w' : '-');
099: buff.append("------- 1 owner group ");
100: String size = String.valueOf(fs.length(fileName));
101: for (int i = size.length(); i < 15; i++) {
102: buff.append(' ');
103: }
104: buff.append(size);
105: buff.append(' ');
106: Date now = new Date(), mod = new Date(fs
107: .getLastModified(fileName));
108: String date;
109: if (mod.after(now)
110: || Math.abs((now.getTime() - mod.getTime()) / 1000 / 60
111: / 60 / 24) > 180) {
112: synchronized (dateFormatOld) {
113: date = dateFormatOld.format(mod);
114: }
115: } else {
116: synchronized (dateFormatNew) {
117: date = dateFormatNew.format(mod);
118: }
119: }
120: buff.append(date);
121: buff.append(' ');
122: buff.append(FileUtils.getFileName(fileName));
123: buff.append("\r\n");
124: }
125:
126: String formatLastModified(String fileName) {
127: synchronized (dateFormat) {
128: return dateFormat.format(new Date(fs
129: .getLastModified(fileName)));
130: }
131: }
132:
133: String getFileName(String path) {
134: return root + getPath(path);
135: }
136:
137: String getPath(String path) {
138: if (path.indexOf("..") > 0) {
139: path = "/";
140: }
141: while (path.startsWith("/") && root.endsWith("/")) {
142: path = path.substring(1);
143: }
144: while (path.endsWith("/")) {
145: path = path.substring(0, path.length() - 1);
146: }
147: log("path: " + path);
148: return path;
149: }
150:
151: String getDirectoryListing(String directory, boolean listDirectories)
152: throws SQLException {
153: String[] list = fs.listFiles(directory);
154: StringBuffer buff = new StringBuffer();
155: for (int i = 0; list != null && i < list.length; i++) {
156: String fileName = list[i];
157: if (!fs.isDirectory(fileName)
158: || (fs.isDirectory(fileName) && listDirectories)) {
159: appendFile(buff, fileName);
160: }
161: }
162: return buff.toString();
163: }
164:
165: public boolean checkUserPassword(String userName, String password) {
166: return userName.equals(this .writeUserName)
167: && password.equals(this .writePassword);
168: }
169:
170: public boolean checkUserPasswordReadOnly(String userName,
171: String param) {
172: return userName.equals(this .readUserName);
173: }
174:
175: public void init(String[] args) throws Exception {
176: for (int i = 0; args != null && i < args.length; i++) {
177: String a = args[i];
178: if ("-ftpPort".equals(a)) {
179: port = MathUtils.decodeInt(args[++i]);
180: } else if ("-ftpDir".equals(a)) {
181: root = FileUtils.normalize(args[++i]);
182: } else if ("-ftpRead".equals(a)) {
183: readUserName = args[++i];
184: } else if ("-ftpWrite".equals(a)) {
185: writeUserName = args[++i];
186: } else if ("-ftpWritePassword".equals(a)) {
187: writePassword = args[++i];
188: } else if ("-log".equals(a)) {
189: log = Boolean.valueOf(args[++i]).booleanValue();
190: } else if ("-ftpTask".equals(a)) {
191: allowTask = Boolean.valueOf(args[++i]).booleanValue();
192: }
193: }
194: fs = FileSystem.getInstance(root);
195: root = fs.normalize(root);
196: }
197:
198: public String getURL() {
199: return "ftp://" + NetUtils.getLocalAddress() + ":" + port;
200: }
201:
202: public void start() throws SQLException {
203: fs.mkdirs(root);
204: serverSocket = NetUtils.createServerSocket(port, false);
205: }
206:
207: public void stop() {
208: try {
209: serverSocket.close();
210: } catch (IOException e) {
211: logError(e);
212: }
213: serverSocket = null;
214: }
215:
216: public boolean isRunning() {
217: if (serverSocket == null) {
218: return false;
219: }
220: try {
221: Socket s = NetUtils.createLoopbackSocket(port, false);
222: s.close();
223: return true;
224: } catch (Exception e) {
225: return false;
226: }
227: }
228:
229: public boolean getAllowOthers() {
230: return true;
231: }
232:
233: public String getType() {
234: return "FTP";
235: }
236:
237: public String getName() {
238: return "H2 FTP Server";
239: }
240:
241: void log(String s) {
242: if (log) {
243: System.out.println(s);
244: }
245: }
246:
247: void logError(Throwable e) {
248: if (log) {
249: e.printStackTrace();
250: }
251: }
252:
253: public boolean getAllowTask() {
254: return allowTask;
255: }
256:
257: void startTask(String path) throws IOException {
258: stopTask(path);
259: if (path.endsWith(".zip.task")) {
260: log("expand: " + path);
261: Process p = Runtime.getRuntime().exec("jar -xf " + path,
262: null, new File(root));
263: new StreamRedirect(path, p.getInputStream(), null).start();
264: return;
265: }
266: Properties prop = FileUtils.loadProperties(path);
267: String command = prop.getProperty("command");
268: String outFile = path.substring(0, path.length()
269: - TASK_SUFFIX.length());
270: String errorFile = root + "/"
271: + prop.getProperty("error", outFile + ".err.txt");
272: String outputFile = root + "/"
273: + prop.getProperty("output", outFile + ".out.txt");
274: log("start process: " + path + " / " + command);
275: Process p = Runtime.getRuntime().exec(command, null,
276: new File(root));
277: new StreamRedirect(path, p.getErrorStream(), errorFile).start();
278: new StreamRedirect(path, p.getInputStream(), outputFile)
279: .start();
280: tasks.put(path, p);
281: }
282:
283: private static class StreamRedirect extends Thread {
284: private InputStream in;
285: private OutputStream out;
286: private String outFile;
287: private String processFile;
288:
289: StreamRedirect(String processFile, InputStream in,
290: String outFile) {
291: this .processFile = processFile;
292: this .in = in;
293: this .outFile = outFile;
294: }
295:
296: private void openOutput() {
297: if (outFile != null) {
298: try {
299: this .out = FileUtils.openFileOutputStream(outFile,
300: false);
301: } catch (Exception e) {
302: // ignore
303: }
304: outFile = null;
305: }
306: }
307:
308: public void run() {
309: while (true) {
310: try {
311: int x = in.read();
312: if (x < 0) {
313: break;
314: }
315: openOutput();
316: if (out != null) {
317: out.write(x);
318: }
319: } catch (IOException e) {
320: // ignore
321: }
322: }
323: IOUtils.closeSilently(out);
324: IOUtils.closeSilently(in);
325: new File(processFile).delete();
326: }
327: }
328:
329: void stopTask(String processName) {
330: log("kill process: " + processName);
331: Process p = (Process) tasks.remove(processName);
332: if (p == null) {
333: return;
334: }
335: p.destroy();
336: }
337:
338: /**
339: * Get the file system used by this FTP server.
340: *
341: * @return the file system
342: */
343: public FileSystem getFileSystem() {
344: return fs;
345: }
346:
347: /**
348: * Set the event listener. Only one listener can be registered.
349: *
350: * @param eventListener the new listener, or null to de-register
351: */
352: public void setEventListener(FtpEventListener eventListener) {
353: this .eventListener = eventListener;
354: }
355:
356: /**
357: * Get the registered event listener.
358: *
359: * @return the event listener, or null if non is registered
360: */
361: public FtpEventListener getEventListener() {
362: return eventListener;
363: }
364:
365: }
|