package org.openslx.imagemaster.ftp; import java.io.File; import java.sql.Timestamp; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import org.apache.ftpserver.FtpServer; import org.apache.ftpserver.FtpServerFactory; import org.apache.ftpserver.ftplet.Authority; import org.apache.ftpserver.ftplet.FtpException; import org.apache.ftpserver.ftplet.Ftplet; import org.apache.ftpserver.ftplet.UserManager; import org.apache.ftpserver.impl.FtpIoSession; import org.apache.ftpserver.listener.Listener; import org.apache.ftpserver.listener.ListenerFactory; import org.apache.ftpserver.ssl.SslConfigurationFactory; import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory; import org.apache.ftpserver.usermanager.SaltedPasswordEncryptor; import org.apache.ftpserver.usermanager.impl.BaseUser; import org.apache.ftpserver.usermanager.impl.WritePermission; import org.apache.log4j.Logger; import org.openslx.imagemaster.Globals; import org.openslx.imagemaster.Globals.PropString; import org.openslx.imagemaster.db.DbFtpUser; import org.openslx.imagemaster.thrift.iface.FtpCredentials; import org.openslx.imagemaster.util.RandomString; import org.openslx.imagemaster.util.Util; public class MasterFtpServer implements Runnable { private static Logger log = Logger.getLogger( MasterFtpServer.class ); private FtpServer server; private UserManager userManager; private Listener listener; // key: ftpUsername, value: infos public final HashMap users = new HashMap<>(); private boolean ini = false; public enum Mode { UPLOADING, DOWNLOADING; @Override public String toString() { if (this == UPLOADING) { return "uploading"; } else if (this == DOWNLOADING) { return "downloading"; } else { return ""; } } } /** * Class to hold infos of a ftp user. * * @author nils * */ public class Infos { private final Long createTime; private final Mode mode; private final String fileName; private final String serverSessionId; public Infos(Long createTime, Mode mode, String fileName, String serverSessionId) { this.createTime = createTime; this.mode = mode; this.fileName = fileName; this.serverSessionId = serverSessionId; } public Long getCreateTime() { return this.createTime; } public Mode getMode() { return this.mode; } public String getFileName() { return this.fileName; } public String getServerSessionId() { return this.serverSessionId; } } public void init( int port ) { if ( ini ) return; FtpServerFactory serverFactory = new FtpServerFactory(); ListenerFactory factory = new ListenerFactory(); // config ssl SslConfigurationFactory sslConfigFactory = new SslConfigurationFactory(); sslConfigFactory.setKeystoreFile( new File( Globals.getPropertyString( PropString.FTPKEYSTOREFILE ) ) ); sslConfigFactory.setKeyAlias( Globals.getPropertyString( PropString.FTPKEYSTOREALIAS ) ); sslConfigFactory.setKeystorePassword( Globals.getPropertyString( PropString.FTPKEYSTOREPASSWORD ) ); // set the port of the listener factory.setPort( port ); factory.setSslConfiguration( sslConfigFactory.createSslConfiguration() ); factory.setImplicitSsl( true ); // replace the default listener listener = factory.createListener(); serverFactory.addListener( "default", listener ); // create user manager PropertiesUserManagerFactory userManagerFactory = new PropertiesUserManagerFactory(); File userFile = new File( "ftp.properties" ); userManagerFactory.setFile( userFile ); userManagerFactory.setPasswordEncryptor( new SaltedPasswordEncryptor() ); userManager = userManagerFactory.createUserManager(); serverFactory.setUserManager( userManager ); // add the Ftplet HashMap map = new HashMap(); map.put( "Ftplet1", new MasterFtplet() ); serverFactory.setFtplets( map ); // start the server server = serverFactory.createServer(); ini = true; } /** * Add a user with username and password. * @param username * @param password * @param mode * @param fileName the filename of the file, that is allowed to access (if downloading) * @param add user to db * @return */ public boolean addUser( String serverSessionId, String username, String password, Mode mode, String fileName, boolean toDb) { String dir = Globals.getPropertyString( Globals.PropString.FTPBASEDIR ) + "/" + username + "/"; BaseUser user = new BaseUser(); user.setName( username ); user.setPassword( password ); List authorities = new ArrayList(); String file = ""; if (mode == Mode.UPLOADING) { user.setHomeDirectory( dir ); // uploading satellite is allowed to write authorities.add( new WritePermission() ); } else if (mode == Mode.DOWNLOADING) { // the downloading satellite may access the whole dir, but this is restricted in MasterFtplet user.setHomeDirectory( Globals.getPropertyString( Globals.PropString.FTPBASEDIR ) ); // downloading satellite is only allowed to read file = fileName; } user.setAuthorities( authorities ); try { userManager.save( user ); synchronized ( users ) { users.put( username, new Infos( System.currentTimeMillis(), mode, file, serverSessionId) ); if (toDb) DbFtpUser.addUser( new DbFtpUser( username, password, mode.toString(), fileName, serverSessionId, new Timestamp(System.currentTimeMillis()) ) ); } } catch ( FtpException e ) { return false; } return true; } public FtpCredentials addUser( final String serverSessionId, Mode mode, String fileName ) { FtpCredentials ftpCredentials = null; String generatedUser = RandomString.generate( 10, false ); String generatedPass = RandomString.generate( 16, true ); BaseUser user = new BaseUser(); user.setName( generatedUser ); user.setPassword( generatedPass ); List authorities = new ArrayList(); String file = ""; String dir = ""; if (mode == Mode.UPLOADING) { // generate the home dir dir = Globals.getPropertyString( Globals.PropString.FTPBASEDIR ) + "/" + generatedUser + "/"; if ( !new File( dir ).mkdir() ) { return null; } // uploading satellite is allowed to write authorities.add( new WritePermission() ); } else if (mode == Mode.DOWNLOADING) { // the downloading satellite may access the whole dir, but this is restricted in MasterFtplet dir = Globals.getPropertyString( Globals.PropString.FTPBASEDIR ); // downloading satellite is only allowed to read file = fileName; } user.setHomeDirectory( dir ); user.setAuthorities( authorities ); try { userManager.save( user ); ftpCredentials = new FtpCredentials( generatedUser, generatedPass, fileName ); synchronized ( users ) { users.put( ftpCredentials.username, new Infos( System.currentTimeMillis(), mode, file, serverSessionId) ); DbFtpUser.addUser( new DbFtpUser( generatedUser, generatedPass, mode.toString(), fileName, serverSessionId, new Timestamp(System.currentTimeMillis()) ) ); } } catch ( FtpException e ) { // TODO: handle this } log.info( "Generated user/pass: " + generatedUser + "\t" + generatedPass + "\n with home dir: " + dir ); return ftpCredentials; } public boolean removeUser( final String username ) { if ( !users.containsKey( username ) ) return false; try { // first find active session and close it Iterator iter = listener.getActiveSessions().iterator(); while ( iter.hasNext() ) { FtpIoSession session = (FtpIoSession)iter.next(); if ( session.getUser() == null ) continue; if ( session.getUser().getName() == username ) { session.close(); } } // afterwards delete user userManager.delete( username ); // remove user from map (cache) synchronized ( users ) { users.remove( username ); DbFtpUser.removeUserByName( username ); } // remove his home dir try { File dir = new File( Globals.getPropertyString( Globals.PropString.FTPBASEDIR ) + "/" + username ); Util.deleteFolder( dir ); } catch (Exception e) { // don't care because it could be a downloading user } return true; } catch ( FtpException e ) { return false; } } @Override public void run() { try { //if (!ini) throw new Exception("FTP server needs to be initalized."); log.info( "Starting FTP Sever" ); server.start(); } catch ( FtpException e1 ) { e1.printStackTrace(); } } public void addDbFtpUsers() { List list = DbFtpUser.getAllUsers(); Iterator iter = list.iterator(); int n = 0; while (iter.hasNext()) { DbFtpUser user = iter.next(); Mode mode; if ( user.mode.equalsIgnoreCase( "downloading" ) ) { mode = Mode.DOWNLOADING; } else { mode = Mode.UPLOADING; } // don't readd user if it timeouted if ( (System.currentTimeMillis() - user.timestamp.getTime()) < FtpCredentialsScheduler.timeout ) { this.addUser( user.sessionId, user.username, user.password, mode, user.filename, false ); n++; } else { DbFtpUser.removeUserByName( user.username ); } } log.info( "Added " + n + " FTP users from DB." ); } }