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;
019:
020: import java.io.BufferedOutputStream;
021: import java.io.BufferedReader;
022: import java.io.IOException;
023: import java.io.InputStreamReader;
024: import java.io.InterruptedIOException;
025: import java.io.OutputStream;
026: import java.io.OutputStreamWriter;
027: import java.io.PrintWriter;
028: import java.io.StringWriter;
029: import java.net.InetAddress;
030: import java.net.Socket;
031: import java.net.SocketException;
032: import java.util.ArrayList;
033: import java.util.Iterator;
034: import java.util.Map;
035:
036: import javax.net.ssl.SSLSocket;
037:
038: import net.sf.drftpd.ObjectNotFoundException;
039: import net.sf.drftpd.SlaveUnavailableException;
040: import net.sf.drftpd.event.ConnectionEvent;
041: import net.sf.drftpd.master.command.CommandManager;
042: import net.sf.drftpd.master.command.plugins.DataConnectionHandler;
043: import net.sf.drftpd.util.ReplacerUtils;
044:
045: import org.apache.log4j.Level;
046: import org.apache.log4j.Logger;
047: import org.drftpd.Bytes;
048: import org.drftpd.GlobalContext;
049: import org.drftpd.Time;
050: import org.drftpd.commands.Reply;
051: import org.drftpd.commands.ReplyException;
052: import org.drftpd.commands.UserManagement;
053: import org.drftpd.dynamicdata.Key;
054: import org.drftpd.io.AddAsciiOutputStream;
055: import org.drftpd.remotefile.LinkedRemoteFileInterface;
056: import org.drftpd.slave.Transfer;
057: import org.drftpd.usermanager.NoSuchUserException;
058: import org.drftpd.usermanager.User;
059: import org.drftpd.usermanager.UserFileException;
060: import org.tanesha.replacer.FormatterException;
061: import org.tanesha.replacer.ReplacerEnvironment;
062: import org.tanesha.replacer.ReplacerFormat;
063: import org.tanesha.replacer.SimplePrintf;
064:
065: /**
066: * This is a generic ftp connection handler. It delegates
067: * the request to appropriate methods in subclasses.
068: *
069: * @author <a href="mailto:rana_b@yahoo.com">Rana Bhattacharyya</a>
070: * @author mog
071: * @version $Id: BaseFtpConnection.java 1451 2006-03-30 00:45:05Z zubov $
072: */
073: public class BaseFtpConnection implements Runnable {
074: private static final Logger debuglogger = Logger
075: .getLogger(BaseFtpConnection.class.getName() + ".service");
076: private static final Logger logger = Logger
077: .getLogger(BaseFtpConnection.class);
078: public static final String NEWLINE = "\r\n";
079:
080: /**
081: * Is the current password authenticated?
082: */
083: protected boolean _authenticated = false;
084:
085: //protected ConnectionManager _cm;
086: private CommandManager _commandManager;
087: protected Socket _controlSocket;
088: protected LinkedRemoteFileInterface _currentDirectory;
089:
090: /**
091: * Is the client running a command?
092: */
093: protected boolean _executing;
094: private BufferedReader _in;
095:
096: /**
097: * time when last command from the client finished execution
098: */
099: protected long _lastActive;
100: protected PrintWriter _out;
101: protected FtpRequest _request;
102:
103: /**
104: * Should this thread stop insted of continue looping?
105: */
106: protected boolean _stopRequest = false;
107: protected String _stopRequestMessage;
108: protected Thread _thread;
109: protected GlobalContext _gctx;
110: protected String _user;
111:
112: protected BaseFtpConnection() {
113: }
114:
115: public BaseFtpConnection(GlobalContext gctx, Socket soc)
116: throws IOException {
117: _gctx = gctx;
118: _commandManager = getGlobalContext().getConnectionManager()
119: .getCommandManagerFactory().initialize(this );
120: setControlSocket(soc);
121: _lastActive = System.currentTimeMillis();
122: setCurrentDirectory(getGlobalContext().getRoot());
123: }
124:
125: public static ReplacerEnvironment getReplacerEnvironment(
126: ReplacerEnvironment env, User user) {
127: env = new ReplacerEnvironment(env);
128:
129: if (user != null) {
130: for (Map.Entry<Key, Object> o : user.getKeyedMap()
131: .getAllObjects().entrySet()) {
132: env.add(o.getKey().toString(), o.getKey().toString(
133: o.getValue()));
134: //logger.debug("Added "+o.getKey().toString()+" "+o.getKey().toString(o.getValue()));
135: }
136: env.add("user", user.getName());
137: env.add("username", user.getName());
138: env.add("idletime", "" + user.getIdleTime());
139: env.add("credits", Bytes.formatBytes(user.getCredits()));
140: env.add("ratio", ""
141: + user.getKeyedMap().get((UserManagement.RATIO)));
142: env.add("tagline", user.getKeyedMap().get(
143: (UserManagement.TAGLINE)));
144: env.add("uploaded", Bytes.formatBytes(user
145: .getUploadedBytes()));
146: env.add("downloaded", Bytes.formatBytes(user
147: .getDownloadedBytes()));
148: env.add("group", user.getGroup());
149: env.add("groups", user.getGroups());
150: env.add("averagespeed", Bytes.formatBytes(user
151: .getUploadedTime()
152: + (user.getDownloadedTime() / 2)));
153: env.add("ipmasks", user.getHostMaskCollection().toString());
154: env.add("isbanned", ""
155: + ((user.getKeyedMap()
156: .getObjectDate(UserManagement.BAN_TIME))
157: .getTime() > System.currentTimeMillis()));
158: // } else {
159: // env.add("user", "<unknown>");
160: }
161: return env;
162: }
163:
164: public static String jprintf(ReplacerFormat format,
165: ReplacerEnvironment env, User user)
166: throws FormatterException {
167: env = getReplacerEnvironment(env, user);
168:
169: return SimplePrintf.jprintf(format, env);
170: }
171:
172: public static String jprintf(Class class1, String key,
173: ReplacerEnvironment env, User user) {
174: env = getReplacerEnvironment(env, user);
175:
176: return ReplacerUtils.jprintf(key, env, class1);
177: }
178:
179: public static String jprintfExceptionStatic(Class class1,
180: String key, ReplacerEnvironment env, User user)
181: throws FormatterException {
182: env = getReplacerEnvironment(env, user);
183:
184: return SimplePrintf.jprintf(ReplacerUtils.finalFormat(class1,
185: key), env);
186: }
187:
188: /**
189: * Get client address
190: */
191: public InetAddress getClientAddress() {
192: return _controlSocket.getInetAddress();
193: }
194:
195: public CommandManager getCommandManager() {
196: return _commandManager;
197: }
198:
199: public GlobalContext getGlobalContext() {
200: return _gctx;
201: }
202:
203: public BufferedReader getControlReader() {
204: return _in;
205: }
206:
207: public Socket getControlSocket() {
208: return _controlSocket;
209: }
210:
211: public PrintWriter getControlWriter() {
212: return _out;
213: }
214:
215: public LinkedRemoteFileInterface getCurrentDirectory() {
216: return _currentDirectory;
217: }
218:
219: public DataConnectionHandler getDataConnectionHandler() {
220: try {
221: return (DataConnectionHandler) getCommandManager()
222: .getCommandHandler(DataConnectionHandler.class);
223: } catch (ObjectNotFoundException e) {
224: throw new RuntimeException(
225: "DataConnectionHandler must be available", e);
226: }
227: }
228:
229: public char getDirection() {
230: String cmd = getRequest().getCommand();
231:
232: if ("RETR".equals(cmd)) {
233: return Transfer.TRANSFER_SENDING_DOWNLOAD;
234: }
235:
236: if ("STOR".equals(cmd) || "APPE".equals(cmd)) {
237: return Transfer.TRANSFER_RECEIVING_UPLOAD;
238: }
239:
240: return Transfer.TRANSFER_UNKNOWN;
241: }
242:
243: /**
244: * Returns the "currentTimeMillis" when last command finished executing.
245: */
246: public long getLastActive() {
247: return _lastActive;
248: }
249:
250: /**
251: * Returns the FtpRequest of current or last command executed.
252: */
253: public FtpRequest getRequest() {
254: return _request;
255: }
256:
257: /**
258: * Returns Transfer.TRANSFER_SENDING_DOWNLOAD if this connection is processing a RETR command
259: * or Transfer.TRANSFER_RECEIVING_UPLOAD if this connection is processing a STOR command.
260: * @throws IllegalStateException if the connection isn't processing a STOR or RETR command.
261: */
262: public char getTransferDirection() {
263: String cmd = getRequest().getCommand();
264:
265: if (cmd.equals("RETR")) {
266: return Transfer.TRANSFER_SENDING_DOWNLOAD;
267: } else if (cmd.equals("STOR")) {
268: return Transfer.TRANSFER_RECEIVING_UPLOAD;
269: } else {
270: throw new IllegalStateException("Not transfering");
271: }
272: }
273:
274: /**
275: * Get user object
276: */
277: public User getUser() throws NoSuchUserException {
278: if ((_user == null) || !isAuthenticated()) {
279: throw new NoSuchUserException(
280: "no user logged in for connection");
281: }
282: try {
283: return getGlobalContext().getUserManager()
284: .getUserByNameUnchecked(_user);
285: } catch (UserFileException e) {
286: throw new NoSuchUserException(e);
287: }
288: }
289:
290: public User getUserNull() {
291: if (_user == null) {
292: return null;
293: }
294: try {
295: return getGlobalContext().getUserManager()
296: .getUserByNameUnchecked(_user);
297: } catch (NoSuchUserException e) {
298: return null;
299: } catch (UserFileException e) {
300: return null;
301: }
302: }
303:
304: protected boolean hasPermission(FtpRequest request) {
305: if (isAuthenticated()) {
306: return true;
307: }
308:
309: String cmd = request.getCommand();
310:
311: if ("USER".equals(cmd) || "PASS".equals(cmd)
312: || "QUIT".equals(cmd) || "HELP".equals(cmd)
313: || "AUTH".equals(cmd) || "PBSZ".equals(cmd)
314: || "IDNT".equals(cmd) || "PROT".equals(cmd)) {
315: return true;
316: }
317:
318: return false;
319: }
320:
321: public boolean isAuthenticated() {
322: return _authenticated;
323: }
324:
325: /**
326: * Returns true if client is executing a command.
327: */
328: public boolean isExecuting() {
329: return _executing;
330: }
331:
332: public boolean isSecure() {
333: return _controlSocket instanceof SSLSocket;
334: }
335:
336: public String jprintf(Class baseName, String key) {
337: return jprintf(baseName, key, null, getUserNull());
338: }
339:
340: public String jprintf(Class class1, String string,
341: ReplacerEnvironment env) {
342: return jprintf(class1, string, env, getUserNull());
343: }
344:
345: public String jprintfException(Class class1, String key,
346: ReplacerEnvironment env) throws FormatterException {
347: env = getReplacerEnvironment(env, getUserNull());
348:
349: return jprintfExceptionStatic(class1, key, env, getUserNull());
350: }
351:
352: /**
353: * Server one FTP connection.
354: */
355: public void run() {
356: _lastActive = System.currentTimeMillis();
357: if (!getGlobalContext().getConnectionManager()
358: .getGlobalContext().getConfig().getHideIps()) {
359: logger.info("Handling new request from "
360: + getClientAddress().getHostAddress());
361: _thread.setName("FtpConn thread " + _thread.getId()
362: + " from " + getClientAddress().getHostAddress());
363: } else {
364: logger.info("Handling new request from <iphidden>");
365: _thread.setName("FtpConn thread " + _thread.getId()
366: + " from <iphidden>");
367: }
368:
369: try {
370: // in =
371: // new BufferedReader(
372: // new InputStreamReader(_controlSocket.getInputStream()));
373: // out = new PrintWriter(
374: // //new FtpWriter( no need for spying :P
375: // new BufferedWriter(
376: // new OutputStreamWriter(_controlSocket.getOutputStream())));
377: _controlSocket.setSoTimeout(1000);
378:
379: if (getGlobalContext().getConnectionManager()
380: .getGlobalContext().isShutdown()) {
381: stop(getGlobalContext().getConnectionManager()
382: .getGlobalContext().getShutdownMessage());
383: } else {
384: Reply response = new Reply(220, getGlobalContext()
385: .getConnectionManager().getGlobalContext()
386: .getConfig().getLoginPrompt());
387: _out.print(response);
388: }
389:
390: while (!_stopRequest) {
391: _out.flush();
392:
393: //notifyObserver();
394: String commandLine = null;
395:
396: try {
397: commandLine = _in.readLine();
398: // will block for a maximum of _controlSocket.getSoTimeout() milliseconds
399: } catch (InterruptedIOException ex) {
400: if (_controlSocket == null) {
401: stop("Control socket is null");
402: break;
403: }
404: if (!_controlSocket.isConnected()) {
405: stop("Socket unexpectedly closed");
406: break;
407: }
408: int idleTime;
409: try {
410: idleTime = getUser().getIdleTime();
411: } catch (NoSuchUserException e) {
412: idleTime = 60;
413: // user not logged in yet
414: }
415: if (idleTime > 0
416: && ((System.currentTimeMillis() - _lastActive) / 1000 >= idleTime)) {
417: stop("IdleTimeout");
418: break;
419: }
420: continue;
421: }
422:
423: if (_stopRequest) {
424: break;
425: }
426:
427: // test command line
428: if (commandLine == null) {
429: break;
430: }
431:
432: //spyRequest(commandLine);
433: if (commandLine.equals("")) {
434: continue;
435: }
436:
437: _request = new FtpRequest(commandLine);
438:
439: if (!_request.getCommand().equals("PASS")) {
440: debuglogger
441: .debug("<< " + _request.getCommandLine());
442: }
443:
444: if (!hasPermission(_request)) {
445: _out.print(Reply.RESPONSE_530_NOT_LOGGED_IN);
446:
447: continue;
448: }
449:
450: // execute command
451: _executing = true;
452: service(_request, _out);
453: _executing = false;
454: _lastActive = System.currentTimeMillis();
455: }
456:
457: if (_stopRequestMessage != null) {
458: _out.print(new Reply(421, _stopRequestMessage));
459: } else {
460: _out.println("421 Connection closing");
461: }
462:
463: _out.flush();
464: } catch (SocketException ex) {
465: logger
466: .log(Level.INFO, ex.getMessage()
467: + ", closing for user "
468: + ((_user == null) ? "<not logged in>"
469: : _user), ex);
470: } catch (Exception ex) {
471: logger.log(Level.INFO, "Exception, closing", ex);
472: } finally {
473: try {
474: _in.close();
475: _out.close();
476: } catch (Exception ex2) {
477: logger.log(Level.WARN, "Exception closing stream", ex2);
478: }
479:
480: if (isAuthenticated()) {
481: try {
482: getUser().updateLastAccessTime();
483: } catch (NoSuchUserException e) {
484: logger
485: .error("User does not exist, yet user is authenticated, this is a bug");
486: }
487: getGlobalContext().dispatchFtpEvent(
488: new ConnectionEvent(getUserNull(), "LOGOUT"));
489: }
490:
491: getGlobalContext().getConnectionManager().remove(this );
492: }
493: }
494:
495: /**
496: * Execute the ftp command.
497: */
498: public void service(FtpRequest request, PrintWriter out)
499: throws IOException {
500: Reply reply;
501:
502: try {
503: reply = _commandManager.execute(this );
504: } catch (Throwable e) {
505: int replycode = e instanceof ReplyException ? ((ReplyException) e)
506: .getReplyCode()
507: : 500;
508: reply = new Reply(replycode, e.getMessage());
509: try {
510: if (getUser().getKeyedMap().getObjectBoolean(
511: UserManagement.DEBUG)) {
512: StringWriter sw = new StringWriter();
513: e.printStackTrace(new PrintWriter(sw));
514: reply.addComment(sw.toString());
515: }
516: } catch (NoSuchUserException e1) {
517: }
518: logger.warn("", e);
519: }
520:
521: if (reply != null) {
522: out.print(reply);
523: }
524: }
525:
526: public void setAuthenticated(boolean authenticated) {
527: _authenticated = authenticated;
528:
529: if (isAuthenticated()) {
530: try {
531: // If hideips is on, hide ip but not user/group
532: if (getGlobalContext().getConnectionManager()
533: .getGlobalContext().getConfig().getHideIps()) {
534: _thread.setName("FtpConn thread " + _thread.getId()
535: + " servicing " + _user + "/"
536: + getUser().getGroup());
537: } else {
538: _thread.setName("FtpConn thread " + _thread.getId()
539: + " from "
540: + getClientAddress().getHostAddress() + " "
541: + _user + "/" + getUser().getGroup());
542: }
543: } catch (NoSuchUserException e) {
544: logger
545: .error("User does not exist, yet user is authenticated, this is a bug");
546: }
547: }
548: }
549:
550: public void setControlSocket(Socket socket) {
551: try {
552: _controlSocket = socket;
553: _in = new BufferedReader(new InputStreamReader(
554: _controlSocket.getInputStream(), "ISO-8859-1"));
555:
556: _out = new PrintWriter(new OutputStreamWriter(
557: new AddAsciiOutputStream(new BufferedOutputStream(
558: _controlSocket.getOutputStream())),
559: "ISO-8859-1"));
560: } catch (IOException e) {
561: throw new RuntimeException(e);
562: }
563: }
564:
565: public void setCurrentDirectory(LinkedRemoteFileInterface file) {
566: _currentDirectory = file;
567: }
568:
569: public void setUser(String user) {
570: _user = user;
571: }
572:
573: public void start() {
574: _thread = new Thread(this );
575: _thread.start();
576:
577: // start() calls run() and execution will start in the background.
578: }
579:
580: /**
581: * returns a two-line status
582: */
583: public String status() {
584: return jprintf(BaseFtpConnection.class, "statusline");
585: }
586:
587: /**
588: * User logout and stop this thread.
589: */
590: public void stop() {
591: synchronized (getDataConnectionHandler()) {
592: if (getDataConnectionHandler().isTransfering()) {
593: try {
594: getDataConnectionHandler().getTransfer().abort(
595: "Control connection dropped");
596: } catch (ObjectNotFoundException e) {
597: logger.debug("This is a bug, please report it", e);
598: }
599: }
600: }
601: _stopRequest = true;
602: }
603:
604: public void stop(String message) {
605: _stopRequestMessage = message;
606: stop();
607: }
608:
609: public String toString() {
610: StringBuffer buf = new StringBuffer("[BaseFtpConnection");
611:
612: if (_user != null) {
613: buf.append("[user: " + _user + "]");
614: }
615:
616: if (_request != null) {
617: buf.append("[command: " + _request.getCommand() + "]");
618: }
619:
620: if (isExecuting()) {
621: buf.append("[executing]");
622: } else {
623: buf.append("[idle: "
624: + Time.formatTime(System.currentTimeMillis()
625: - getLastActive()));
626: }
627:
628: buf.append("]");
629:
630: return buf.toString();
631: }
632:
633: public OutputStream getOutputStream() throws IOException {
634: return _controlSocket.getOutputStream();
635: }
636:
637: public int transferCounter(char transferDirection) {
638: ArrayList<BaseFtpConnection> conns = new ArrayList<BaseFtpConnection>(
639: getGlobalContext().getConnectionManager()
640: .getConnections());
641: int count = 0;
642: for (Iterator<BaseFtpConnection> iter = conns.iterator(); iter
643: .hasNext();) {
644: BaseFtpConnection conn2 = iter.next();
645:
646: synchronized (conn2.getDataConnectionHandler()) {
647:
648: if (conn2.getUserNull() == getUserNull()) {
649: if (!conn2.isExecuting()) {
650: continue;
651: }
652: if (conn2.getDataConnectionHandler()
653: .isTransfering()
654: && (conn2.getTransferDirection() == transferDirection)) {
655: count++;
656: }
657: }
658: }
659: }
660: return count;
661: }
662: }
|