001: /*
002: * This file is part of DrFTPD, Distributed FTP Daemon.
003: *
004: * DrFTPD is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * DrFTPD is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with DrFTPD; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018: package net.sf.drftpd.master.command.plugins;
019:
020: import java.io.FileNotFoundException;
021: import java.io.IOException;
022: import java.io.OutputStreamWriter;
023: import java.io.PrintWriter;
024: import java.io.Writer;
025: import java.net.Socket;
026: import java.text.DateFormat;
027: import java.text.SimpleDateFormat;
028: import java.util.Calendar;
029: import java.util.Collection;
030: import java.util.Date;
031: import java.util.GregorianCalendar;
032: import java.util.Iterator;
033: import java.util.List;
034: import java.util.StringTokenizer;
035:
036: import net.sf.drftpd.master.BaseFtpConnection;
037: import net.sf.drftpd.master.FtpRequest;
038: import net.sf.drftpd.master.command.CommandManager;
039: import net.sf.drftpd.master.command.CommandManagerFactory;
040:
041: import org.apache.log4j.Logger;
042: import org.drftpd.commands.CommandHandler;
043: import org.drftpd.commands.CommandHandlerFactory;
044: import org.drftpd.commands.Reply;
045: import org.drftpd.remotefile.LinkedRemoteFileInterface;
046: import org.drftpd.remotefile.ListUtils;
047: import org.drftpd.remotefile.RemoteFileInterface;
048:
049: /**
050: * @author mog
051: *
052: * @version $Id: LIST.java 1568 2007-01-06 16:00:54Z fr0w $
053: */
054: public class LIST implements CommandHandler, CommandHandlerFactory {
055: private final static DateFormat AFTER_SIX = new SimpleDateFormat(
056: " yyyy");
057: private final static DateFormat BEFORE_SIX = new SimpleDateFormat(
058: "HH:mm");
059: private final static String DELIM = " ";
060: private final static DateFormat FULL = new SimpleDateFormat(
061: "HH:mm:ss yyyy");
062: private static final Logger logger = Logger.getLogger(LIST.class);
063: private final static String[] MONTHS = { "Jan", "Feb", "Mar",
064: "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
065: "Dec" };
066: private final static String NEWLINE = "\r\n";
067:
068: /**
069: * Get size
070: * @deprecated
071: */
072: private static String getLength(RemoteFileInterface fl) {
073: String initStr = " ";
074: String szStr = Long.toString(fl.length());
075:
076: if (szStr.length() > initStr.length()) {
077: return szStr;
078: }
079:
080: return initStr.substring(0, initStr.length() - szStr.length())
081: + szStr;
082: }
083:
084: /* */
085:
086: /**
087: * Get file name.
088: */
089:
090: /*
091: private static String getName(LinkedRemoteFileInterface fl) {
092: String flName = fl.getName();
093:
094: int lastIndex = flName.lastIndexOf("/");
095:
096: if (lastIndex == -1) {
097: return flName;
098: } else {
099: return flName.substring(lastIndex + 1);
100: }
101: }*/
102:
103: /**
104: * Get permission string.
105: */
106: private static String getPermission(RemoteFileInterface fl) {
107: StringBuffer sb = new StringBuffer(13);
108:
109: if (fl.isLink()) {
110: sb.append("l");
111: } else if (fl.isDirectory()) {
112: sb.append("d");
113: } else {
114: sb.append("-");
115: }
116:
117: sb.append("rw");
118: sb.append(fl.isDirectory() ? "x" : "-");
119:
120: sb.append("rw");
121: sb.append(fl.isDirectory() ? "x" : "-");
122:
123: sb.append("rw");
124: sb.append(fl.isDirectory() ? "x" : "-");
125:
126: return sb.toString();
127: }
128:
129: public static String getUnixDate(long date, boolean fulldate) {
130: Date date1 = new Date(date);
131: long dateTime = date1.getTime();
132:
133: if (dateTime < 0) {
134: return "------------";
135: }
136:
137: Calendar cal = new GregorianCalendar();
138: cal.setTime(date1);
139:
140: String firstPart = MONTHS[cal.get(Calendar.MONTH)] + ' ';
141:
142: String dateStr = String.valueOf(cal.get(Calendar.DATE));
143:
144: if (dateStr.length() == 1) {
145: dateStr = ' ' + dateStr;
146: }
147:
148: firstPart += (dateStr + ' ');
149:
150: long nowTime = System.currentTimeMillis();
151:
152: if (fulldate) {
153: return firstPart + FULL.format(date1);
154: } else if (Math.abs(nowTime - dateTime) > (183L * 24L * 60L * 60L * 1000L)) {
155: return firstPart + AFTER_SIX.format(date1);
156: } else {
157: return firstPart + BEFORE_SIX.format(date1);
158: }
159: }
160:
161: /**
162: * Get each directory line.
163: */
164: private static void printLine(RemoteFileInterface fl, Writer out,
165: boolean fulldate) throws IOException {
166: StringBuffer line = new StringBuffer();
167:
168: if (fl instanceof LinkedRemoteFileInterface
169: && !((LinkedRemoteFileInterface) fl).isAvailable()) {
170: line.append("----------");
171: } else {
172: line.append(getPermission(fl));
173: }
174:
175: line.append(DELIM);
176: line.append((fl.isDirectory() ? "3" : "1"));
177: line.append(DELIM);
178: line.append(ListUtils.padToLength(fl.getUsername(), 8));
179: line.append(DELIM);
180: line.append(ListUtils.padToLength(fl.getGroupname(), 8));
181: line.append(DELIM);
182: line.append(getLength(fl));
183: line.append(DELIM);
184: line.append(getUnixDate(fl.lastModified(), fulldate));
185: line.append(DELIM);
186: line.append(fl.getName());
187: if (fl.isLink()) {
188: line.append(DELIM + "->" + DELIM + fl.getLinkPath());
189: }
190: line.append(NEWLINE);
191: out.write(line.toString());
192: }
193:
194: /**
195: * Print file list. Detail listing.
196: * <pre>
197: * -a : display all (including hidden files)
198: * </pre>
199: * @return true if success
200: */
201: private static void printList(Collection files, Writer os,
202: boolean fulldate) throws IOException {
203: //out = new BufferedWriter(out);
204: os.write("total 0" + NEWLINE);
205:
206: // print file list
207: for (Iterator iter = files.iterator(); iter.hasNext();) {
208: RemoteFileInterface file = (RemoteFileInterface) iter
209: .next();
210: LIST.printLine(file, os, fulldate);
211: }
212: }
213:
214: /**
215: * Print file list.
216: * <pre>
217: * -l : detail listing
218: * -a : display all (including hidden files)
219: * </pre>
220: * @return true if success
221: */
222: private static void printNList(Collection fileList,
223: boolean bDetail, Writer out) throws IOException {
224: for (Iterator iter = fileList.iterator(); iter.hasNext();) {
225: RemoteFileInterface file = (RemoteFileInterface) iter
226: .next();
227:
228: if (bDetail) {
229: printLine(file, out, false);
230: } else {
231: out.write(file.getName() + NEWLINE);
232: }
233: }
234: }
235:
236: /**
237: * <code>NLST [<SP> <pathname>] <CRLF></code><br>
238: *
239: * This command causes a directory listing to be sent from
240: * server to user site. The pathname should specify a
241: * directory or other system-specific file group descriptor; a
242: * null argument implies the current directory. The server
243: * will return a stream of names of files and no other
244: * information.
245: *
246: *
247: * <code>LIST [<SP> <pathname>] <CRLF></code><br>
248: *
249: * This command causes a list to be sent from the server to the
250: * passive DTP. If the pathname specifies a directory or other
251: * group of files, the server should transfer a list of files
252: * in the specified directory. If the pathname specifies a
253: * file then the server should send current information on the
254: * file. A null argument implies the user's current working or
255: * default directory. The data transfer is over the data
256: * connection
257: *
258: * LIST
259: * 125, 150
260: * 226, 250
261: * 425, 426, 451
262: * 450
263: * 500, 501, 502, 421, 530
264: */
265: public Reply execute(BaseFtpConnection conn) {
266: FtpRequest request = conn.getRequest();
267:
268: String directoryName = null;
269: String options = "";
270:
271: //String pattern = "*";
272: // get options, directory name and pattern
273: //argument == null if there was no argument for LIST
274: if (request.hasArgument()) {
275: //argument = argument.trim();
276: StringBuffer optionsSb = new StringBuffer(4);
277: StringTokenizer st = new StringTokenizer(request
278: .getArgument(), " ");
279:
280: while (st.hasMoreTokens()) {
281: String token = st.nextToken();
282:
283: if (token.charAt(0) == '-') {
284: if (token.length() > 1) {
285: optionsSb.append(token.substring(1));
286: }
287: } else {
288: directoryName = token;
289: }
290: }
291:
292: options = optionsSb.toString();
293: }
294:
295: // check options
296: // boolean allOption = options.indexOf('a') != -1;
297: boolean fulldate = options.indexOf('T') != -1;
298: boolean detailOption = request.getCommand().equals("LIST")
299: || request.getCommand().equals("STAT")
300: || (options.indexOf('l') != -1);
301:
302: // boolean directoryOption = options.indexOf("d") != -1;
303: DataConnectionHandler dataconn = null;
304:
305: if (!request.getCommand().equals("STAT")) {
306: dataconn = conn.getDataConnectionHandler();
307:
308: if (!dataconn.isPasv() && !dataconn.isPort()) {
309: return Reply.RESPONSE_503_BAD_SEQUENCE_OF_COMMANDS;
310: }
311: }
312:
313: LinkedRemoteFileInterface directoryFile;
314:
315: if (directoryName != null) {
316: try {
317: directoryFile = conn.getCurrentDirectory().lookupFile(
318: directoryName);
319: } catch (FileNotFoundException ex) {
320: return Reply.RESPONSE_550_REQUESTED_ACTION_NOT_TAKEN;
321: }
322:
323: if (!conn.getGlobalContext().getConfig()
324: .checkPathPermission("privpath",
325: conn.getUserNull(), directoryFile, true)) {
326: return Reply.RESPONSE_550_REQUESTED_ACTION_NOT_TAKEN;
327: }
328: } else {
329: directoryFile = conn.getCurrentDirectory();
330: }
331:
332: PrintWriter out = conn.getControlWriter();
333: Socket dataSocket = null;
334: Writer os;
335:
336: if (request.getCommand().equals("STAT")) {
337: os = out;
338: out.write("213- Status of " + request.getArgument() + ":"
339: + NEWLINE);
340: } else {
341: if (!dataconn.isEncryptedDataChannel()
342: && conn.getGlobalContext().getConfig()
343: .checkPermission("denydiruncrypted",
344: conn.getUserNull())) {
345: return new Reply(550, "Secure Listing Required");
346: }
347:
348: out.write(Reply.RESPONSE_150_OK);
349: out.flush();
350:
351: try {
352: dataSocket = dataconn.getDataSocket();
353: os = new PrintWriter(new OutputStreamWriter(dataSocket
354: .getOutputStream()));
355:
356: //out2 = dataSocket.getChannel();
357: } catch (IOException ex) {
358: logger.warn("from master", ex);
359:
360: return new Reply(425, ex.getMessage());
361: }
362: }
363:
364: ////////////////
365: List listFiles = ListUtils.list(directoryFile, conn);
366:
367: ////////////////
368: try {
369: if (request.getCommand().equals("LIST")
370: || request.getCommand().equals("STAT")) {
371: printList(listFiles, os, fulldate);
372: } else if (request.getCommand().equals("NLST")) {
373: printNList(listFiles, detailOption, os);
374: }
375:
376: Reply response = (Reply) Reply.RESPONSE_226_CLOSING_DATA_CONNECTION
377: .clone();
378:
379: try {
380: if (!request.getCommand().equals("STAT")) {
381: os.close();
382: dataSocket.close();
383: response.addComment(conn.status());
384:
385: return response;
386: }
387:
388: return new Reply(213, "End of Status");
389: } catch (IOException ioe) {
390: logger.error("", ioe);
391:
392: return new Reply(450, ioe.getMessage());
393: }
394: } catch (IOException ex) {
395: logger.warn("from master", ex);
396:
397: return new Reply(450, ex.getMessage());
398: }
399:
400: //redo connection handling
401: //conn.reset();
402: }
403:
404: public String[] getFeatReplies() {
405: return null;
406: }
407:
408: /**
409: * <code>STAT [<SP> <pathname>] <CRLF></code><br>
410: *
411: * This command shall cause a status response to be sent over
412: * the control connection in the form of a reply.
413: */
414:
415: // public void doSTAT(FtpRequest request, PrintWriter out) {
416: // reset();
417: // if (request.hasArgument()) {
418: // doLIST(request, out);
419: // } else {
420: // out.print(FtpReply.RESPONSE_504_COMMAND_NOT_IMPLEMENTED_FOR_PARM);
421: // }
422: // return;
423: // }
424: /* (non-Javadoc)
425: * @see net.sf.drftpd.master.command.CommandHandler#initialize(net.sf.drftpd.master.BaseFtpConnection)
426: */
427: public CommandHandler initialize(BaseFtpConnection conn,
428: CommandManager initializer) {
429: return this ;
430: }
431:
432: public void load(CommandManagerFactory initializer) {
433: }
434:
435: public void unload() {
436: }
437: }
|