/**
* Copyright (c) 2007, Markus Jevring <markus@jevring.net>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The names of the contributors may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
package cu.ftpd;
import cu.settings.XMLSettings;
import cu.settings.ConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
/**
* @author Markus Jevring
* @since 2007-maj-11 : 11:29:10
* @version $Id: FtpdSettings.java 308 2011-02-27 21:58:19Z jevring $
*/
public class FtpdSettings extends XMLSettings {
protected File dataDirectory;
protected int pasvHighPort;
protected int pasvLowPort;
protected final Random random = new Random();
protected HashSet<InetAddress> bouncers;
protected final String settingsNamespace;
protected final Map<String, Document> moduleSettings = new HashMap<String, Document>();
/**
* Creates the settings object.
* The settings namespace is as follows:
* cuftpd: "/ftpd"
* cubnc: "/bnc"
* Anything else depends on when and where it is used.
* @param settingsNamespace the settings namespace. defines the name of the toplevel settings object. This helps us reuse the same classes for a lot of projects.
* @param xmlPath the path of the file containing the settings.
* @param schemaPath the path of the xsd schema. Often something like "/cuftpd.xsd"
* @throws cu.settings.ConfigurationException if something goes wrong when reading or checking the configuration
* @throws java.io.IOException if something goes wrong when reading the settings from file.
*/
public FtpdSettings(String settingsNamespace, String xmlPath, String schemaPath) throws IOException, ConfigurationException {
super(xmlPath, schemaPath);
this.settingsNamespace = settingsNamespace;
loadBouncers(get("/main/bouncers"));
loadPasvPorts(get("/main/pasv_port_range"));
loadDataDir(get("/main/data_directory"));
checkSanity();
}
/*
public void load(String xmlPath, String xsdPath) throws IOException, ConfigurationException {
super.load(xmlPath, xsdPath);
}
*/
private void loadBouncers(String listOfBouncers) throws ConfigurationException {
bouncers = new HashSet<InetAddress>();
try {
// if there are no bouncers, then we'll just have an empty set which will return quickly
String[] bouncerIps = listOfBouncers.split("\\s+|,");
for (int i = 0; i < bouncerIps.length; i++) {
String bouncerIp = bouncerIps[i];
if (bouncerIp != null && bouncerIp.length() > 0) {
bouncers.add(InetAddress.getByName(bouncerIp));
}
}
} catch (UnknownHostException e) {
throw new ConfigurationException("Failed to resolve bouncer address: " + e.getMessage(), e);
}
}
public boolean isBouncer(Socket connection) {
// no need to check for null, since we know that it'll never be null when we get it in here
return bouncers.contains(connection.getInetAddress());
}
private void checkSanity() throws ConfigurationException {
// check that the pasv_address resolves
// note: the values returned by get() are already trim():ed
try {
final String s = get("/main/pasv_address");
if (!"".equals(s)) {
InetAddress.getByName(s);
}
} catch (UnknownHostException e) {
throw new ConfigurationException("The supplied pasv_address did not resolve properly.", e);
}
try {
final String s = get("/main/bind_address");
if (!"".equals(s)) {
InetAddress.getByName(s);
}
} catch (UnknownHostException e) {
throw new ConfigurationException("The supplied bind_address did not resolve properly.", e);
}
try {
final String s = get("/epsv/ipv4_bind_address");
if (!"".equals(s)) {
InetAddress.getByName(s);
}
} catch (UnknownHostException e) {
throw new ConfigurationException("The supplied ipv4_bind_address did not resolve properly.", e);
}
try {
final String s = get("/epsv/ipv6_bind_address");
if (!"".equals(s)) {
InetAddress.getByName(s);
}
} catch (UnknownHostException e) {
throw new ConfigurationException("The supplied ipv6_bind_address did not resolve properly.", e);
}
// Note: moved section check to loadSections(), as it is now called externally
if (!dataDirectory.exists() || !dataDirectory.isDirectory()) {
throw new ConfigurationException("Could not find data_directory: " + dataDirectory.getAbsolutePath());
}
if (getInt("/user/authentication/type") == 2) {
if (get("/user/authentication/remote/host") == null || get("/user/authentication/remote/port") == null || get("/user/authentication/remote/retry_interval") == null) {
throw new ConfigurationException("must specify {host, port, retry_interval} when using remote authentication");
}
}
}
private void loadPasvPorts(String pasvPortsString) throws ConfigurationException {
if (pasvPortsString != null) {
String[] ports = pasvPortsString.split("-");
try {
if (ports.length == 2) {
pasvHighPort = Integer.parseInt(ports[1]);
pasvLowPort = Integer.parseInt(ports[0]);
}
} catch (Exception e) {
throw new ConfigurationException("pasv_port_range is not defined properly, make sure it contains two numbers between 0 and 65536, connected by a dash '-'. For example: 1024-2048", e);
}
}
}
public int getNextPassivePort() {
// read the ports, and when asked, just step then one at a time.
// if no ports are specified, always return 0
if (pasvHighPort > 0 && pasvHighPort > pasvLowPort && pasvLowPort > 0) {
int i = random.nextInt(pasvHighPort-pasvLowPort) + pasvLowPort;
//System.out.println("returning " + i + " as next passive port");
return i;
} else {
return 0;
}
}
private void loadDataDir(String dataDirectory) {
this.dataDirectory = new File(dataDirectory);
}
public File getDataDirectory() {
return dataDirectory;
}
@Override
public String get(String attributePath) {
return super.get(settingsNamespace + attributePath);
}
@Override
public Node getNode(String attributePath) {
return super.getNode(settingsNamespace + attributePath);
}
}