| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.ftpserver.usermanager.impl; |
| |
| import java.sql.Connection; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| |
| import javax.sql.DataSource; |
| |
| import org.apache.ftpserver.FtpServerConfigurationException; |
| import org.apache.ftpserver.ftplet.Authentication; |
| import org.apache.ftpserver.ftplet.AuthenticationFailedException; |
| import org.apache.ftpserver.ftplet.Authority; |
| import org.apache.ftpserver.ftplet.FtpException; |
| import org.apache.ftpserver.ftplet.User; |
| import org.apache.ftpserver.usermanager.AnonymousAuthentication; |
| import org.apache.ftpserver.usermanager.DbUserManagerFactory; |
| import org.apache.ftpserver.usermanager.PasswordEncryptor; |
| import org.apache.ftpserver.usermanager.UsernamePasswordAuthentication; |
| import org.apache.ftpserver.util.StringUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * <strong>Internal class, do not use directly.</strong> |
| * |
| * This is another database based user manager class. It has been tested in |
| * MySQL and Oracle 8i database. The schema file is </code>res/ftp-db.sql</code> |
| * |
| * All the user attributes are replaced during run-time. So we can use your |
| * database schema. Then you need to modify the SQLs in the configuration file. |
| * |
| * @author <a href="http://mina.apache.org">Apache MINA Project</a> |
| */ |
| public class DbUserManager extends AbstractUserManager { |
| |
| private final Logger LOG = LoggerFactory.getLogger(DbUserManager.class); |
| |
| private String insertUserStmt; |
| |
| private String updateUserStmt; |
| |
| private String deleteUserStmt; |
| |
| private String selectUserStmt; |
| |
| private String selectAllStmt; |
| |
| private String isAdminStmt; |
| |
| private String authenticateStmt; |
| |
| private DataSource dataSource; |
| |
| /** |
| * Internal constructor, do not use directly. Use {@link DbUserManagerFactory} instead. |
| */ |
| public DbUserManager(DataSource dataSource, String selectAllStmt, |
| String selectUserStmt, String insertUserStmt, |
| String updateUserStmt, String deleteUserStmt, |
| String authenticateStmt, String isAdminStmt, |
| PasswordEncryptor passwordEncryptor, String adminName) { |
| super(adminName, passwordEncryptor); |
| this.dataSource = dataSource; |
| this.selectAllStmt = selectAllStmt; |
| this.selectUserStmt = selectUserStmt; |
| this.insertUserStmt = insertUserStmt; |
| this.updateUserStmt = updateUserStmt; |
| this.deleteUserStmt = deleteUserStmt; |
| this.authenticateStmt = authenticateStmt; |
| this.isAdminStmt = isAdminStmt; |
| |
| Connection con = null; |
| try { |
| // test the connection |
| con = createConnection(); |
| |
| LOG.info("Database connection opened."); |
| } catch (SQLException ex) { |
| LOG.error("Failed to open connection to user database", ex); |
| throw new FtpServerConfigurationException( |
| "Failed to open connection to user database", ex); |
| } finally{ |
| closeQuitely(con); |
| } |
| } |
| |
| /** |
| * Retrive the data source used by the user manager |
| * |
| * @return The current data source |
| */ |
| public DataSource getDataSource() { |
| return dataSource; |
| } |
| |
| /** |
| * Set the data source to be used by the user manager |
| * |
| * @param dataSource |
| * The data source to use |
| */ |
| public void setDataSource(DataSource dataSource) { |
| this.dataSource = dataSource; |
| } |
| |
| /** |
| * Get the SQL INSERT statement used to add a new user. |
| * |
| * @return The SQL statement |
| */ |
| public String getSqlUserInsert() { |
| return insertUserStmt; |
| } |
| |
| /** |
| * Set the SQL INSERT statement used to add a new user. All the dynamic |
| * values will be replaced during runtime. |
| * |
| * @param sql |
| * The SQL statement |
| */ |
| public void setSqlUserInsert(String sql) { |
| insertUserStmt = sql; |
| } |
| |
| /** |
| * Get the SQL DELETE statement used to delete an existing user. |
| * |
| * @return The SQL statement |
| */ |
| public String getSqlUserDelete() { |
| return deleteUserStmt; |
| } |
| |
| /** |
| * Set the SQL DELETE statement used to delete an existing user. All the |
| * dynamic values will be replaced during runtime. |
| * |
| * @param sql |
| * The SQL statement |
| */ |
| public void setSqlUserDelete(String sql) { |
| deleteUserStmt = sql; |
| } |
| |
| /** |
| * Get the SQL UPDATE statement used to update an existing user. |
| * |
| * @return The SQL statement |
| */ |
| public String getSqlUserUpdate() { |
| return updateUserStmt; |
| } |
| |
| /** |
| * Set the SQL UPDATE statement used to update an existing user. All the |
| * dynamic values will be replaced during runtime. |
| * |
| * @param sql |
| * The SQL statement |
| */ |
| public void setSqlUserUpdate(String sql) { |
| updateUserStmt = sql; |
| } |
| |
| /** |
| * Get the SQL SELECT statement used to select an existing user. |
| * |
| * @return The SQL statement |
| */ |
| public String getSqlUserSelect() { |
| return selectUserStmt; |
| } |
| |
| /** |
| * Set the SQL SELECT statement used to select an existing user. All the |
| * dynamic values will be replaced during runtime. |
| * |
| * @param sql |
| * The SQL statement |
| */ |
| public void setSqlUserSelect(String sql) { |
| selectUserStmt = sql; |
| } |
| |
| /** |
| * Get the SQL SELECT statement used to select all user ids. |
| * |
| * @return The SQL statement |
| */ |
| public String getSqlUserSelectAll() { |
| return selectAllStmt; |
| } |
| |
| /** |
| * Set the SQL SELECT statement used to select all user ids. All the dynamic |
| * values will be replaced during runtime. |
| * |
| * @param sql |
| * The SQL statement |
| */ |
| public void setSqlUserSelectAll(String sql) { |
| selectAllStmt = sql; |
| } |
| |
| /** |
| * Get the SQL SELECT statement used to authenticate user. |
| * |
| * @return The SQL statement |
| */ |
| public String getSqlUserAuthenticate() { |
| return authenticateStmt; |
| } |
| |
| /** |
| * Set the SQL SELECT statement used to authenticate user. All the dynamic |
| * values will be replaced during runtime. |
| * |
| * @param sql |
| * The SQL statement |
| */ |
| public void setSqlUserAuthenticate(String sql) { |
| authenticateStmt = sql; |
| } |
| |
| /** |
| * Get the SQL SELECT statement used to find whether an user is admin or |
| * not. |
| * |
| * @return The SQL statement |
| */ |
| public String getSqlUserAdmin() { |
| return isAdminStmt; |
| } |
| |
| /** |
| * Set the SQL SELECT statement used to find whether an user is admin or |
| * not. All the dynamic values will be replaced during runtime. |
| * |
| * @param sql |
| * The SQL statement |
| */ |
| public void setSqlUserAdmin(String sql) { |
| isAdminStmt = sql; |
| } |
| |
| /** |
| * @return true if user with this login is administrator |
| */ |
| @Override |
| public boolean isAdmin(String login) throws FtpException { |
| |
| // check input |
| if (login == null) { |
| return false; |
| } |
| |
| Statement stmt = null; |
| ResultSet rs = null; |
| try { |
| |
| // create the sql query |
| HashMap<String, Object> map = new HashMap<String, Object>(); |
| map.put(ATTR_LOGIN, escapeString(login)); |
| String sql = StringUtils.replaceString(isAdminStmt, map); |
| LOG.info(sql); |
| |
| // execute query |
| stmt = createConnection().createStatement(); |
| rs = stmt.executeQuery(sql); |
| return rs.next(); |
| } catch (SQLException ex) { |
| LOG.error("DbUserManager.isAdmin()", ex); |
| throw new FtpException("DbUserManager.isAdmin()", ex); |
| } finally { |
| closeQuitely(rs); |
| closeQuitely(stmt); |
| } |
| } |
| |
| /** |
| * Open connection to database. |
| */ |
| protected Connection createConnection() throws SQLException { |
| Connection connection = dataSource.getConnection(); |
| connection.setAutoCommit(true); |
| |
| return connection; |
| } |
| |
| |
| /** |
| * Delete user. Delete the row from the table. |
| */ |
| public void delete(String name) throws FtpException { |
| // create sql query |
| HashMap<String, Object> map = new HashMap<String, Object>(); |
| map.put(ATTR_LOGIN, escapeString(name)); |
| String sql = StringUtils.replaceString(deleteUserStmt, map); |
| LOG.info(sql); |
| |
| // execute query |
| Statement stmt = null; |
| try { |
| stmt = createConnection().createStatement(); |
| stmt.executeUpdate(sql); |
| } catch (SQLException ex) { |
| LOG.error("DbUserManager.delete()", ex); |
| throw new FtpException("DbUserManager.delete()", ex); |
| } finally { |
| closeQuitely(stmt); |
| } |
| } |
| |
| /** |
| * Save user. If new insert a new row, else update the existing row. |
| */ |
| public void save(User user) throws FtpException { |
| // null value check |
| if (user.getName() == null) { |
| throw new NullPointerException("User name is null."); |
| } |
| |
| Statement stmt = null; |
| try { |
| |
| // create sql query |
| HashMap<String, Object> map = new HashMap<String, Object>(); |
| map.put(ATTR_LOGIN, escapeString(user.getName())); |
| |
| String password = null; |
| if(user.getPassword() != null) { |
| // password provided, encrypt it and store the encrypted value |
| password= getPasswordEncryptor().encrypt(user.getPassword()); |
| } else { |
| // password was not provided, either load from the existing user and store that again |
| // or store as null |
| ResultSet rs = null; |
| |
| try { |
| User userWithPassword = selectUserByName(user.getName()); |
| |
| if(userWithPassword != null) { |
| // user exists, reuse password |
| password = userWithPassword.getPassword(); |
| } |
| } finally { |
| closeQuitely(rs); |
| } |
| } |
| map.put(ATTR_PASSWORD, escapeString(password)); |
| |
| |
| String home = user.getHomeDirectory(); |
| if (home == null) { |
| home = "/"; |
| } |
| map.put(ATTR_HOME, escapeString(home)); |
| map.put(ATTR_ENABLE, String.valueOf(user.getEnabled())); |
| |
| map.put(ATTR_WRITE_PERM, String.valueOf(user |
| .authorize(new WriteRequest()) != null)); |
| map.put(ATTR_MAX_IDLE_TIME, user.getMaxIdleTime()); |
| |
| TransferRateRequest transferRateRequest = new TransferRateRequest(); |
| transferRateRequest = (TransferRateRequest) user |
| .authorize(transferRateRequest); |
| |
| if (transferRateRequest != null) { |
| map.put(ATTR_MAX_UPLOAD_RATE, transferRateRequest |
| .getMaxUploadRate()); |
| map.put(ATTR_MAX_DOWNLOAD_RATE, transferRateRequest |
| .getMaxDownloadRate()); |
| } else { |
| map.put(ATTR_MAX_UPLOAD_RATE, 0); |
| map.put(ATTR_MAX_DOWNLOAD_RATE, 0); |
| } |
| |
| // request that always will succeed |
| ConcurrentLoginRequest concurrentLoginRequest = new ConcurrentLoginRequest( |
| 0, 0); |
| concurrentLoginRequest = (ConcurrentLoginRequest) user |
| .authorize(concurrentLoginRequest); |
| |
| if (concurrentLoginRequest != null) { |
| map.put(ATTR_MAX_LOGIN_NUMBER, concurrentLoginRequest |
| .getMaxConcurrentLogins()); |
| map.put(ATTR_MAX_LOGIN_PER_IP, concurrentLoginRequest |
| .getMaxConcurrentLoginsPerIP()); |
| } else { |
| map.put(ATTR_MAX_LOGIN_NUMBER, 0); |
| map.put(ATTR_MAX_LOGIN_PER_IP, 0); |
| } |
| |
| String sql = null; |
| if (!doesExist(user.getName())) { |
| sql = StringUtils.replaceString(insertUserStmt, map); |
| } else { |
| sql = StringUtils.replaceString(updateUserStmt, map); |
| } |
| LOG.info(sql); |
| |
| // execute query |
| stmt = createConnection().createStatement(); |
| stmt.executeUpdate(sql); |
| } catch (SQLException ex) { |
| LOG.error("DbUserManager.save()", ex); |
| throw new FtpException("DbUserManager.save()", ex); |
| } finally { |
| closeQuitely(stmt); |
| } |
| } |
| |
| private void closeQuitely(Statement stmt) { |
| if(stmt != null) { |
| Connection con = null; |
| try { |
| con = stmt.getConnection(); |
| } catch (Exception e) { |
| } |
| try { |
| stmt.close(); |
| } catch (SQLException e) { |
| // ignore |
| } |
| closeQuitely(con); |
| } |
| } |
| |
| private void closeQuitely(ResultSet rs) { |
| if(rs != null) { |
| try { |
| rs.close(); |
| } catch (SQLException e) { |
| // ignore |
| } |
| } |
| } |
| |
| protected void closeQuitely(Connection con) { |
| if (con != null) { |
| try { |
| con.close(); |
| } catch (SQLException e) { |
| // ignore |
| } |
| } |
| } |
| |
| private BaseUser selectUserByName(String name) throws SQLException { |
| // create sql query |
| HashMap<String, Object> map = new HashMap<String, Object>(); |
| map.put(ATTR_LOGIN, escapeString(name)); |
| String sql = StringUtils.replaceString(selectUserStmt, map); |
| LOG.info(sql); |
| |
| Statement stmt = null; |
| ResultSet rs = null; |
| try { |
| // execute query |
| stmt = createConnection().createStatement(); |
| rs = stmt.executeQuery(sql); |
| |
| // populate user object |
| BaseUser thisUser = null; |
| if (rs.next()) { |
| thisUser = new BaseUser(); |
| thisUser.setName(rs.getString(ATTR_LOGIN)); |
| thisUser.setPassword(rs.getString(ATTR_PASSWORD)); |
| thisUser.setHomeDirectory(rs.getString(ATTR_HOME)); |
| thisUser.setEnabled(rs.getBoolean(ATTR_ENABLE)); |
| thisUser.setMaxIdleTime(rs.getInt(ATTR_MAX_IDLE_TIME)); |
| |
| List<Authority> authorities = new ArrayList<Authority>(); |
| if (rs.getBoolean(ATTR_WRITE_PERM)) { |
| authorities.add(new WritePermission()); |
| } |
| |
| authorities.add(new ConcurrentLoginPermission(rs |
| .getInt(ATTR_MAX_LOGIN_NUMBER), rs |
| .getInt(ATTR_MAX_LOGIN_PER_IP))); |
| authorities.add(new TransferRatePermission(rs |
| .getInt(ATTR_MAX_DOWNLOAD_RATE), rs |
| .getInt(ATTR_MAX_UPLOAD_RATE))); |
| |
| thisUser.setAuthorities(authorities); |
| } |
| return thisUser; |
| |
| } finally { |
| closeQuitely(rs); |
| closeQuitely(stmt); |
| } |
| } |
| |
| /** |
| * Get the user object. Fetch the row from the table. |
| */ |
| public User getUserByName(String name) throws FtpException { |
| Statement stmt = null; |
| ResultSet rs = null; |
| try { |
| |
| BaseUser user = selectUserByName(name); |
| |
| if(user != null) { |
| // reset the password, not to be sent to API users |
| user.setPassword(null); |
| } |
| return user; |
| |
| |
| } catch (SQLException ex) { |
| LOG.error("DbUserManager.getUserByName()", ex); |
| throw new FtpException("DbUserManager.getUserByName()", ex); |
| } finally { |
| closeQuitely(rs); |
| closeQuitely(stmt); |
| } |
| } |
| |
| /** |
| * User existance check. |
| */ |
| public boolean doesExist(String name) throws FtpException { |
| Statement stmt = null; |
| ResultSet rs = null; |
| try { |
| |
| // create the sql |
| HashMap<String, Object> map = new HashMap<String, Object>(); |
| map.put(ATTR_LOGIN, escapeString(name)); |
| String sql = StringUtils.replaceString(selectUserStmt, map); |
| LOG.info(sql); |
| |
| // execute query |
| stmt = createConnection().createStatement(); |
| rs = stmt.executeQuery(sql); |
| return rs.next(); |
| } catch (SQLException ex) { |
| LOG.error("DbUserManager.doesExist()", ex); |
| throw new FtpException("DbUserManager.doesExist()", ex); |
| } finally { |
| closeQuitely(rs); |
| closeQuitely(stmt); |
| } |
| } |
| |
| /** |
| * Get all user names from the database. |
| */ |
| public String[] getAllUserNames() throws FtpException { |
| |
| Statement stmt = null; |
| ResultSet rs = null; |
| try { |
| |
| // create sql query |
| String sql = selectAllStmt; |
| LOG.info(sql); |
| |
| // execute query |
| stmt = createConnection().createStatement(); |
| rs = stmt.executeQuery(sql); |
| |
| // populate list |
| ArrayList<String> names = new ArrayList<String>(); |
| while (rs.next()) { |
| names.add(rs.getString(ATTR_LOGIN)); |
| } |
| return names.toArray(new String[0]); |
| } catch (SQLException ex) { |
| LOG.error("DbUserManager.getAllUserNames()", ex); |
| throw new FtpException("DbUserManager.getAllUserNames()", ex); |
| } finally { |
| closeQuitely(rs); |
| closeQuitely(stmt); |
| } |
| } |
| |
| /** |
| * User authentication. |
| */ |
| public User authenticate(Authentication authentication) |
| throws AuthenticationFailedException { |
| if (authentication instanceof UsernamePasswordAuthentication) { |
| UsernamePasswordAuthentication upauth = (UsernamePasswordAuthentication) authentication; |
| |
| String user = upauth.getUsername(); |
| String password = upauth.getPassword(); |
| |
| if (user == null) { |
| throw new AuthenticationFailedException("Authentication failed"); |
| } |
| |
| if (password == null) { |
| password = ""; |
| } |
| |
| Statement stmt = null; |
| ResultSet rs = null; |
| try { |
| |
| // create the sql query |
| HashMap<String, Object> map = new HashMap<String, Object>(); |
| map.put(ATTR_LOGIN, escapeString(user)); |
| String sql = StringUtils.replaceString(authenticateStmt, map); |
| LOG.info(sql); |
| |
| // execute query |
| stmt = createConnection().createStatement(); |
| rs = stmt.executeQuery(sql); |
| if (rs.next()) { |
| try { |
| String storedPassword = rs.getString(ATTR_PASSWORD); |
| if (getPasswordEncryptor().matches(password, storedPassword)) { |
| return getUserByName(user); |
| } else { |
| throw new AuthenticationFailedException( |
| "Authentication failed"); |
| } |
| } catch (FtpException e) { |
| throw new AuthenticationFailedException( |
| "Authentication failed", e); |
| } |
| } else { |
| throw new AuthenticationFailedException( |
| "Authentication failed"); |
| } |
| } catch (SQLException ex) { |
| LOG.error("DbUserManager.authenticate()", ex); |
| throw new AuthenticationFailedException( |
| "Authentication failed", ex); |
| } finally { |
| closeQuitely(rs); |
| closeQuitely(stmt); |
| } |
| } else if (authentication instanceof AnonymousAuthentication) { |
| try { |
| if (doesExist("anonymous")) { |
| return getUserByName("anonymous"); |
| } else { |
| throw new AuthenticationFailedException( |
| "Authentication failed"); |
| } |
| } catch (AuthenticationFailedException e) { |
| throw e; |
| } catch (FtpException e) { |
| throw new AuthenticationFailedException( |
| "Authentication failed", e); |
| } |
| } else { |
| throw new IllegalArgumentException( |
| "Authentication not supported by this user manager"); |
| } |
| } |
| |
| /** |
| * Escape string to be embedded in SQL statement. |
| */ |
| private String escapeString(String input) { |
| if (input == null) { |
| return input; |
| } |
| |
| StringBuilder valBuf = new StringBuilder(input); |
| for (int i = 0; i < valBuf.length(); i++) { |
| char ch = valBuf.charAt(i); |
| if (ch == '\'' || ch == '\\' || ch == '$' || ch == '^' || ch == '[' |
| || ch == ']' || ch == '{' || ch == '}') { |
| |
| valBuf.insert(i, '\\'); |
| i++; |
| } |
| } |
| return valBuf.toString(); |
| } |
| } |