/*
* This file is part of DrFTPD, Distributed FTP Daemon.
*
* DrFTPD is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* DrFTPD is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with DrFTPD; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.drftpd.config;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import org.apache.log4j.Logger;
import org.drftpd.GlobalContext;
import org.drftpd.dynamicdata.Key;
import org.drftpd.dynamicdata.KeyedMap;
import org.drftpd.master.config.ConfigInterface;
import org.drftpd.permissions.PathPermission;
import org.drftpd.permissions.Permission;
import org.drftpd.usermanager.User;
import org.drftpd.util.CommonPluginUtils;
import org.drftpd.util.PluginObjectContainer;
import org.drftpd.util.PortRange;
import org.drftpd.vfs.DirectoryHandle;
import org.drftpd.vfs.perms.VFSPermissions;
/**
* Handles the loading of 'master.conf' and 'conf/perms.conf'<br>
* The directives that are going to be handled by this class are loaded during
* the startup process and *MUST* be an extension of the master extension-point "ConfigHandler".<br>
* No hard coding is needed to handle new directives, simply create a new extension.
* @author fr0w
* @version $Id$
*/
public class ConfigManager implements ConfigInterface {
private static final Logger logger = Logger.getLogger(ConfigManager.class);
private static final File permsFile = new File("conf/perms.conf");
private static final File mainFile = new File("master.conf");
private static final Key<Hashtable<String, ArrayList<PathPermission>>> PATHPERMS
= new Key<Hashtable<String, ArrayList<PathPermission>>>(ConfigManager.class, "pathPerms");
private static final Key<Hashtable<String, Permission>> PERMS
= new Key<Hashtable<String, Permission>>(ConfigManager.class, "perms");
private HashMap<String, ConfigContainer> _directivesMap;
private KeyedMap<Key<?>, Object> _keyedMap;
private Properties _mainCfg;
private VFSPermissions _vfsPerms;
private ArrayList<InetAddress> _bouncerIps;
private String _loginPrompt = GlobalContext.VERSION + " http://drftpd.org";
private String _allowConnectionsDenyReason = "";
private String _pasvAddr = null;
private PortRange _portRange = new PortRange(0);
private boolean _hideIps = true;
private int _maxUsersTotal = Integer.MAX_VALUE;
private int _maxUsersExempt = 0;
private String[] _cipherSuites = null;
/**
* Reload all VFSPermHandlers and ConfigHandlers.
* Also re-read the config files.
*/
public void reload() {
loadVFSPermissions();
loadConfigHandlers();
loadMainProperties();
parseCipherSuites();
initializeKeyedMap();
_bouncerIps = new ArrayList<InetAddress>();
readConf();
}
private void loadVFSPermissions() {
_vfsPerms = new VFSPermissions();
}
/**
* @return the VFSPermission object.
* @see VFSPermissions
*/
public VFSPermissions getVFSPermissions() {
return _vfsPerms;
}
/**
* Returns the KeyedMap that allow dynamic and persistent storage of the loaded permissions.<br>
* For a better understanding see how does the 'msgpath' directive works.
* @see DefaultConfigHandler
* @see DefaultConfigPostHook
*/
public KeyedMap<Key<?>, Object> getKeyedMap() {
return _keyedMap;
}
/**
* Load all connected handlers.
*/
private void loadConfigHandlers() {
_directivesMap = new HashMap<String, ConfigContainer>();
try {
List<PluginObjectContainer<ConfigHandler>> loadedDirectives =
CommonPluginUtils.getPluginObjectsInContainer(this, "master", "ConfigHandler", "Class", "Method",
new Class[] { String.class, StringTokenizer.class });
for (PluginObjectContainer<ConfigHandler> container : loadedDirectives) {
String directive = container.getPluginExtension().getParameter("Directive").valueAsString();
if (_directivesMap.containsKey(directive)) {
logger.debug("A handler for "+ directive +" already loaded, check your plugin.xml's");
continue;
}
ConfigContainer cc = new ConfigContainer(container.getPluginObject(), container.getPluginMethod());
_directivesMap.put(directive, cc);
}
} catch (IllegalArgumentException e) {
logger.error("Failed to load plugins for master extension point 'Directive', possibly the master"
+" extension point definition has changed in the plugin.xml",e);
}
}
/**
* Reads 'master.conf' and save it in a Properties object.
* @see #getMainProperties()
*/
private void loadMainProperties() {
FileInputStream is = null;
try {
_mainCfg = new Properties();
is = new FileInputStream(mainFile);
_mainCfg.load(is);
} catch (IOException e) {
logger.error("Unable to read "+mainFile.getPath(), e);
} finally {
if (is != null) {
try { is.close(); }
catch (IOException e) { }
}
}
}
private void parseCipherSuites() {
ArrayList<String> cipherSuites = new ArrayList<String>();
for (int x = 1;; x++) {
String cipherSuite = _mainCfg.getProperty("cipher." + x);
if (cipherSuite != null) {
cipherSuites.add(cipherSuite);
} else {
break;
}
}
if (cipherSuites.size() == 0) {
_cipherSuites = null;
} else {
_cipherSuites = cipherSuites.toArray(new String[cipherSuites.size()]);
}
}
/**
* Initializes the KeyedMap.
* @see #getKeyedMap()
*/
private void initializeKeyedMap() {
_keyedMap = new KeyedMap<Key<?>, Object>();
_keyedMap.setObject(PATHPERMS, new Hashtable<String, ArrayList<PathPermission>>());
_keyedMap.setObject(PERMS, new Hashtable<String, Permission>());
}
private Hashtable<String, ArrayList<PathPermission>> getPathPermsMap() {
return _keyedMap.getObject(PATHPERMS, null);
}
private Hashtable<String, Permission> getPermissionsMap() {
return _keyedMap.getObject(PERMS, null);
}
/**
* @return a Properties object containing all data loaded from 'master.conf'.
*/
public Properties getMainProperties() {
return _mainCfg;
}
/**
* Reads 'conf/perms.conf' handling what can be handled, ignoring what's does not have an available handler.
*/
private void readConf() {
LineNumberReader in = null;
try {
in = new LineNumberReader(new FileReader(permsFile));
String line;
while ((line = in.readLine()) != null) {
StringTokenizer st = new StringTokenizer(line);
if (line.startsWith("#") || !st.hasMoreTokens()) {
continue;
}
String drct = st.nextToken().toLowerCase();
/*
* Built-in directives.
*/
if (drct.equals("login_prompt")) {
_loginPrompt = line.substring("login_prompt".length()).trim();
} else if (drct.equals("max_users")) {
_maxUsersTotal = Integer.parseInt(st.nextToken());
_maxUsersExempt = Integer.parseInt(st.nextToken());
} else if (drct.equals("pasv_addr")) {
_pasvAddr = st.nextToken();
} else if (drct.equals("pasv_ports")) {
String[] temp = st.nextToken().split("-");
_portRange = new PortRange(Integer.parseInt(temp[0]), Integer.parseInt(temp[1]), 0);
} else if (drct.equals("hide_ips")) {
_hideIps = st.nextToken().equalsIgnoreCase("true") ? true : false;
} else if (drct.equals("allow_connections")) {
getPermissionsMap().put("allow_connections", new Permission(Permission.makeUsers(st)));
} else if (drct.equals("allow_connections_deny_reason")) {
_allowConnectionsDenyReason = line.substring("allow_connections_deny_reason".length()).trim();
} else if (drct.equals("exempt")) {
getPermissionsMap().put("exempt", new Permission(Permission.makeUsers(st)));
} else if (drct.equals("bouncer_ips")) {
ArrayList<InetAddress> ips = new ArrayList<InetAddress>();
while (st.hasMoreTokens()) {
ips.add(InetAddress.getByName(st.nextToken()));
}
_bouncerIps = ips;
} else {
handleLine(drct, st);
}
}
} catch (IOException e) {
logger.info("Unable to parse "+permsFile.getName(), e);
} finally {
if (in != null) {
try { in.close(); }
catch (IOException e) { }
}
}
}
private void handleLine(String directive, StringTokenizer st) {
try {
getVFSPermissions().handleLine(directive, st);
return; // successfully handled by VFSPermissions, stop!
} catch (UnsupportedOperationException e) {
// could not be handled by VFSPermissions, let's try the other ones!
}
ConfigContainer cc = _directivesMap.get(directive);
if (cc == null) {
logger.debug("No handler found for '"+ directive+"' ignoring line");
return;
}
try {
cc.getMethod().invoke(cc.getInstance(), directive, st);
} catch (Exception e) {
logger.debug("Error while handling directive: "+directive, e);
}
}
public void addPathPermission(String directive, PathPermission perm) {
ArrayList<PathPermission> list;
if (!getPathPermsMap().containsKey(directive)) {
list = new ArrayList<PathPermission>();
getPathPermsMap().put(directive, list);
} else {
list = getPathPermsMap().get(directive);
}
list.add(perm);
}
public boolean checkPathPermission(String directive, User user, DirectoryHandle path) {
return checkPathPermission(directive, user, path, false);
}
public boolean checkPathPermission(String directive, User user,
DirectoryHandle path, boolean defaults) {
ArrayList<PathPermission> perms = getPathPermsMap().get(directive);
if (perms != null && !perms.isEmpty()) {
for (PathPermission perm : perms) {
if (perm.checkPath(path)) {
return perm.check(user);
}
}
}
return defaults;
}
public void addPermission(String directive, Permission permission) {
boolean alreadyExists = getPermissionsMap().containsKey(directive);
if (alreadyExists) {
// TODO what's best replace the existing one or keep it?
logger.info("The directive '"+directive+"' is already on the permission map, check out your "+permsFile.getName());
return;
}
getPermissionsMap().put(directive, permission);
}
public boolean checkPermission(String key, User user) {
Permission perm = getPermissionsMap().get(key);
return (perm == null) ? false : perm.check(user);
}
public List<InetAddress> getBouncerIps() {
return _bouncerIps;
}
public boolean getHideIps() {
return _hideIps;
}
public String getLoginPrompt() {
return _loginPrompt;
}
public String getAllowConnectionsDenyReason() {
return _allowConnectionsDenyReason;
}
public int getMaxUsersExempt() {
return _maxUsersExempt;
}
public int getMaxUsersTotal() {
return _maxUsersTotal;
}
public String getPasvAddress() throws NullPointerException {
if (_pasvAddr == null)
throw new NullPointerException("pasv_addr not configured");
return _pasvAddr;
}
public PortRange getPortRange() {
return _portRange;
}
public boolean isLoginAllowed(User user) {
Permission perm = getPermissionsMap().get("allow_connections");
if (perm == null) {
return true;
}
return perm.check(user);
}
public boolean isLoginExempt(User user) {
Permission perm = getPermissionsMap().get("exempt");
if (perm == null) {
return true;
}
return perm.check(user);
}
public String[] getCipherSuites() {
return _cipherSuites;
}
}