diff --git a/broker/src/main/java/io/moquette/server/config/ClasspathConfig.java b/broker/src/main/java/io/moquette/server/config/ClasspathConfig.java index a9c54e1b..53015220 100644 --- a/broker/src/main/java/io/moquette/server/config/ClasspathConfig.java +++ b/broker/src/main/java/io/moquette/server/config/ClasspathConfig.java @@ -15,55 +15,15 @@ */ package io.moquette.server.config; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.text.ParseException; -import java.util.Properties; - /** * Configuration that loads file from the classpath * * @author andrea */ -public class ClasspathConfig extends IConfig { - - private static final Logger LOG = LoggerFactory.getLogger(ClasspathConfig.class); - - private final Properties m_properties; +public class ClasspathConfig extends ResourceLoaderConfig { public ClasspathConfig() { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - InputStream is = classLoader.getResourceAsStream("config/moquette.conf"); - if (is == null) { - throw new RuntimeException("Can't locate the resource \"config/moquette.conf\""); - } - Reader configReader = new InputStreamReader(is); - ConfigurationParser confParser = new ConfigurationParser(); - m_properties = confParser.getProperties(); - assignDefaults(); - try { - confParser.parse(configReader); - } catch (ParseException pex) { - LOG.warn("An error occurred in parsing configuration, fallback on default configuration", pex); - } + super(new ClasspathResourceLoader()); } - @Override - public void setProperty(String name, String value) { - m_properties.setProperty(name, value); - } - - @Override - public String getProperty(String name) { - return m_properties.getProperty(name); - } - - @Override - public String getProperty(String name, String defaultValue) { - return m_properties.getProperty(name, defaultValue); - } } diff --git a/broker/src/main/java/io/moquette/server/config/ClasspathResourceLoader.java b/broker/src/main/java/io/moquette/server/config/ClasspathResourceLoader.java new file mode 100644 index 00000000..9db40642 --- /dev/null +++ b/broker/src/main/java/io/moquette/server/config/ClasspathResourceLoader.java @@ -0,0 +1,41 @@ +package io.moquette.server.config; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; + +public class ClasspathResourceLoader implements IResourceLoader { + + private final String defaultResource; + private final ClassLoader classLoader; + + public ClasspathResourceLoader() { + this(IConfig.DEFAULT_CONFIG); + } + + public ClasspathResourceLoader(String defaultResource) { + this(defaultResource, Thread.currentThread().getContextClassLoader()); + } + + public ClasspathResourceLoader(String defaultResource, ClassLoader classLoader) { + this.defaultResource = defaultResource; + this.classLoader = classLoader; + } + + @Override + public Reader loadDefaultResource() { + return loadResource(defaultResource); + } + + @Override + public Reader loadResource(String relativePath) { + InputStream is = this.classLoader.getResourceAsStream(relativePath); + return is != null ? new InputStreamReader(is) : null; + } + + @Override + public String getName() { + return "classpath resource"; + } + +} diff --git a/broker/src/main/java/io/moquette/server/config/ConfigurationParser.java b/broker/src/main/java/io/moquette/server/config/ConfigurationParser.java index f44ea25b..743cd964 100644 --- a/broker/src/main/java/io/moquette/server/config/ConfigurationParser.java +++ b/broker/src/main/java/io/moquette/server/config/ConfigurationParser.java @@ -15,11 +15,15 @@ */ package io.moquette.server.config; -import io.moquette.BrokerConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; import java.text.ParseException; import java.util.Properties; @@ -100,6 +104,14 @@ class ConfigurationParser { } catch (IOException ex) { throw new ParseException("Failed to read", 1); } + finally { + try { + reader.close(); + } + catch (IOException e) { + // ignore + } + } } Properties getProperties() { diff --git a/broker/src/main/java/io/moquette/server/config/FileResourceLoader.java b/broker/src/main/java/io/moquette/server/config/FileResourceLoader.java new file mode 100644 index 00000000..9ebbc3ba --- /dev/null +++ b/broker/src/main/java/io/moquette/server/config/FileResourceLoader.java @@ -0,0 +1,60 @@ +package io.moquette.server.config; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.Reader; + +public class FileResourceLoader implements IResourceLoader { + + private final File defaultFile; + private final String parentPath; + + public FileResourceLoader() { + this((File) null); + } + + public FileResourceLoader(File defaultFile) { + this(defaultFile, System.getProperty("moquette.path", null)); + } + + public FileResourceLoader(String parentPath) { + this(null, parentPath); + } + + public FileResourceLoader(File defaultFile, String parentPath) { + this.defaultFile = defaultFile; + this.parentPath = parentPath; + } + + @Override + public Reader loadDefaultResource() { + if (defaultFile != null) { + return loadResource(defaultFile); + } else { + throw new IllegalArgumentException("Default file not set!"); + } + } + + @Override + public Reader loadResource(String relativePath) { + return loadResource(new File(parentPath, relativePath)); + } + + public Reader loadResource(File f) { + if (f.isDirectory()) { + throw new ResourceIsDirectoryException("File \"" + f + "\" is a directory!"); + } + try { + return new FileReader(f); + } catch (FileNotFoundException e) { + return null; + } + } + + @Override + public String getName() { + return "file"; + } + +} diff --git a/broker/src/main/java/io/moquette/server/config/FilesystemConfig.java b/broker/src/main/java/io/moquette/server/config/FilesystemConfig.java index afe06c24..dd8d7547 100644 --- a/broker/src/main/java/io/moquette/server/config/FilesystemConfig.java +++ b/broker/src/main/java/io/moquette/server/config/FilesystemConfig.java @@ -15,32 +15,17 @@ */ package io.moquette.server.config; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.File; -import java.text.ParseException; -import java.util.Properties; /** * Configuration that loads file from the file system * * @author andrea */ -public class FilesystemConfig extends IConfig { - private static final Logger LOG = LoggerFactory.getLogger(FilesystemConfig.class); - - private Properties m_properties = new Properties(); +public class FilesystemConfig extends ResourceLoaderConfig { public FilesystemConfig(File file) { - ConfigurationParser confParser = new ConfigurationParser(); - m_properties = confParser.getProperties(); - assignDefaults(); - try { - confParser.parse(file); - } catch (ParseException pex) { - LOG.warn("An error occurred in parsing configuration, fallback on default configuration", pex); - } + super(new FileResourceLoader(file)); } public FilesystemConfig() { @@ -49,21 +34,7 @@ public class FilesystemConfig extends IConfig { private static File defaultConfigFile() { String configPath = System.getProperty("moquette.path", null); - return new File(configPath, "config/moquette.conf"); + return new File(configPath, IConfig.DEFAULT_CONFIG); } - @Override - public void setProperty(String name, String value) { - m_properties.setProperty(name, value); - } - - @Override - public String getProperty(String name) { - return m_properties.getProperty(name); - } - - @Override - public String getProperty(String name, String defaultValue) { - return m_properties.getProperty(name, defaultValue); - } } diff --git a/broker/src/main/java/io/moquette/server/config/IConfig.java b/broker/src/main/java/io/moquette/server/config/IConfig.java index 87be7958..938bc639 100644 --- a/broker/src/main/java/io/moquette/server/config/IConfig.java +++ b/broker/src/main/java/io/moquette/server/config/IConfig.java @@ -23,6 +23,8 @@ import io.moquette.BrokerConstants; * @author andrea */ public abstract class IConfig { + public static final String DEFAULT_CONFIG = "config/moquette.conf"; + public abstract void setProperty(String name, String value); public abstract String getProperty(String name); @@ -39,4 +41,7 @@ public abstract class IConfig { setProperty(BrokerConstants.AUTHENTICATOR_CLASS_NAME, ""); setProperty(BrokerConstants.AUTHORIZATOR_CLASS_NAME, ""); } + + public abstract IResourceLoader getResourceLoader(); + } diff --git a/broker/src/main/java/io/moquette/server/config/IResourceLoader.java b/broker/src/main/java/io/moquette/server/config/IResourceLoader.java new file mode 100644 index 00000000..e1a035ac --- /dev/null +++ b/broker/src/main/java/io/moquette/server/config/IResourceLoader.java @@ -0,0 +1,19 @@ +package io.moquette.server.config; + +import java.io.Reader; + +public interface IResourceLoader { + + Reader loadDefaultResource(); + + Reader loadResource(String relativePath); + + String getName(); + + class ResourceIsDirectoryException extends RuntimeException { + public ResourceIsDirectoryException(String message) { + super(message); + } + } + +} diff --git a/broker/src/main/java/io/moquette/server/config/MemoryConfig.java b/broker/src/main/java/io/moquette/server/config/MemoryConfig.java index 96da35da..6515cceb 100644 --- a/broker/src/main/java/io/moquette/server/config/MemoryConfig.java +++ b/broker/src/main/java/io/moquette/server/config/MemoryConfig.java @@ -15,8 +15,6 @@ */ package io.moquette.server.config; -import io.moquette.BrokerConstants; - import java.util.Map; import java.util.Properties; @@ -61,4 +59,10 @@ public class MemoryConfig extends IConfig { public String getProperty(String name, String defaultValue) { return m_properties.getProperty(name, defaultValue); } + + @Override + public IResourceLoader getResourceLoader() { + return new FileResourceLoader(); + } + } diff --git a/broker/src/main/java/io/moquette/server/config/ResourceLoaderConfig.java b/broker/src/main/java/io/moquette/server/config/ResourceLoaderConfig.java new file mode 100644 index 00000000..5624e74f --- /dev/null +++ b/broker/src/main/java/io/moquette/server/config/ResourceLoaderConfig.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2012-2015 The original author or authors + * ------------------------------------------------------ + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + */ +package io.moquette.server.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Reader; +import java.text.ParseException; +import java.util.Properties; + +/** + * Configuration that loads config stream from a {@link IResourceLoader} instance. + */ +public class ResourceLoaderConfig extends IConfig { + + private static final Logger LOG = LoggerFactory.getLogger(ResourceLoaderConfig.class); + + private final Properties m_properties; + private final IResourceLoader resourceLoader; + + public ResourceLoaderConfig(IResourceLoader resourceLoader) { + this(resourceLoader, null); + } + + public ResourceLoaderConfig(IResourceLoader resourceLoader, String configName) { + this.resourceLoader = resourceLoader; + Reader configReader = configName != null ? resourceLoader.loadResource(configName) : resourceLoader.loadDefaultResource(); + if (configReader == null) { + throw new IllegalArgumentException( + "Can't locate " + resourceLoader.getName() + " \"" + configName + "\""); + } + ConfigurationParser confParser = new ConfigurationParser(); + m_properties = confParser.getProperties(); + assignDefaults(); + try { + confParser.parse(configReader); + } catch (ParseException pex) { + LOG.warn("An error occurred in parsing configuration, fallback on default configuration", pex); + } + } + + @Override + public void setProperty(String name, String value) { + m_properties.setProperty(name, value); + } + + @Override + public String getProperty(String name) { + return m_properties.getProperty(name); + } + + @Override + public String getProperty(String name, String defaultValue) { + return m_properties.getProperty(name, defaultValue); + } + + @Override + public IResourceLoader getResourceLoader() { + return resourceLoader; + } + +} diff --git a/broker/src/main/java/io/moquette/spi/impl/ProtocolProcessorBootstrapper.java b/broker/src/main/java/io/moquette/spi/impl/ProtocolProcessorBootstrapper.java index 64c52ea7..7e481a72 100644 --- a/broker/src/main/java/io/moquette/spi/impl/ProtocolProcessorBootstrapper.java +++ b/broker/src/main/java/io/moquette/spi/impl/ProtocolProcessorBootstrapper.java @@ -16,12 +16,17 @@ package io.moquette.spi.impl; import io.moquette.BrokerConstants; -import io.moquette.server.Server; -import io.moquette.spi.IMessagesStore; import io.moquette.interception.InterceptHandler; +import io.moquette.server.Server; import io.moquette.server.config.IConfig; +import io.moquette.server.config.IResourceLoader; +import io.moquette.spi.IMessagesStore; import io.moquette.spi.ISessionsStore; -import io.moquette.spi.impl.security.*; +import io.moquette.spi.impl.security.ACLFileParser; +import io.moquette.spi.impl.security.AcceptAllAuthenticator; +import io.moquette.spi.impl.security.DenyAllAuthorizator; +import io.moquette.spi.impl.security.PermitAllAuthorizator; +import io.moquette.spi.impl.security.ResourceAuthenticator; import io.moquette.spi.impl.subscriptions.Subscription; import io.moquette.spi.impl.subscriptions.SubscriptionsStore; import io.moquette.spi.persistence.MapDBPersistentStore; @@ -30,7 +35,6 @@ import io.moquette.spi.security.IAuthorizator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -108,12 +112,13 @@ public class ProtocolProcessorBootstrapper { LOG.info("Loaded custom authenticator {}", authenticatorClassName); } + IResourceLoader resourceLoader = props.getResourceLoader(); if (authenticator == null) { String passwdPath = props.getProperty(BrokerConstants.PASSWORD_FILE_PROPERTY_NAME, ""); if (passwdPath.isEmpty()) { authenticator = new AcceptAllAuthenticator(); } else { - authenticator = new FileAuthenticator(configPath, passwdPath); + authenticator = new ResourceAuthenticator(resourceLoader, passwdPath); } } @@ -127,11 +132,10 @@ public class ProtocolProcessorBootstrapper { String aclFilePath = props.getProperty(BrokerConstants.ACL_FILE_PROPERTY_NAME, ""); if (aclFilePath != null && !aclFilePath.isEmpty()) { authorizator = new DenyAllAuthorizator(); - File aclFile = new File(configPath, aclFilePath); try { - authorizator = ACLFileParser.parse(aclFile); + authorizator = ACLFileParser.parse(resourceLoader.loadResource(aclFilePath)); } catch (ParseException pex) { - LOG.error(String.format("Format error in parsing acl file %s", aclFile), pex); + LOG.error(String.format("Format error in parsing acl %s %s", resourceLoader.getName(), aclFilePath), pex); } LOG.info("Using acl file defined at path {}", aclFilePath); } else { diff --git a/broker/src/main/java/io/moquette/spi/impl/security/FileAuthenticator.java b/broker/src/main/java/io/moquette/spi/impl/security/FileAuthenticator.java index 51615e26..6c5574e3 100644 --- a/broker/src/main/java/io/moquette/spi/impl/security/FileAuthenticator.java +++ b/broker/src/main/java/io/moquette/spi/impl/security/FileAuthenticator.java @@ -15,15 +15,7 @@ */ package io.moquette.spi.impl.security; -import io.moquette.spi.security.IAuthenticator; -import org.apache.commons.codec.digest.DigestUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.text.ParseException; -import java.util.HashMap; -import java.util.Map; +import io.moquette.server.config.FileResourceLoader; /** * Load user credentials from a text file. @@ -36,80 +28,11 @@ import java.util.Map; * NB -n is important because echo append a newline by default at the of string. -n avoid this behaviour. * * @author andrea + * @deprecated user {@link ResourceAuthenticator} instead */ -public class FileAuthenticator implements IAuthenticator { - - private static final Logger LOG = LoggerFactory.getLogger(FileAuthenticator.class); - - private Map m_identities = new HashMap<>(); +public class FileAuthenticator extends ResourceAuthenticator { public FileAuthenticator(String parent, String filePath) { - File file = new File(parent, filePath); - LOG.info("Loading password file: " + file); - if (file.isDirectory()) { - LOG.warn(String.format("Bad file reference %s is a directory", file)); - return; - } - - try { - FileReader reader = new FileReader(file); - parse(reader); - } catch (FileNotFoundException fex) { - LOG.warn(String.format("Parsing not existing file %s", file), fex); - } catch (ParseException pex) { - LOG.warn(String.format("Format error in parsing password file %s", file), pex); - } + super(new FileResourceLoader(parent), filePath); } - - private void parse(Reader reader) throws ParseException { - if (reader == null) { - return; - } - - BufferedReader br = new BufferedReader(reader); - String line; - try { - while ((line = br.readLine()) != null) { - int commentMarker = line.indexOf('#'); - if (commentMarker != -1) { - if (commentMarker == 0) { - //skip its a comment - continue; - } else { - //it's a malformed comment - throw new ParseException(line, commentMarker); - } - } else { - if (line.isEmpty() || line.matches("^\\s*$")) { - //skip it's a black line - continue; - } - - //split till the first space - int delimiterIdx = line.indexOf(':'); - String username = line.substring(0, delimiterIdx).trim(); - String password = line.substring(delimiterIdx + 1).trim(); - - m_identities.put(username, password); - } - } - } catch (IOException ex) { - throw new ParseException("Failed to read", 1); - } - } - - @Override - public boolean checkValid(String clientId, String username, byte[] password) { - if (username == null || password == null) { - LOG.info("username or password was null"); - return false; - } - String foundPwq = m_identities.get(username); - if (foundPwq == null) { - return false; - } - String encodedPasswd = DigestUtils.sha256Hex(password); - return foundPwq.equals(encodedPasswd); - } - } diff --git a/broker/src/main/java/io/moquette/spi/impl/security/ResourceAuthenticator.java b/broker/src/main/java/io/moquette/spi/impl/security/ResourceAuthenticator.java new file mode 100644 index 00000000..406b8353 --- /dev/null +++ b/broker/src/main/java/io/moquette/spi/impl/security/ResourceAuthenticator.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2012-2015 The original author or authors + * ------------------------------------------------------ + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + */ +package io.moquette.spi.impl.security; + +import io.moquette.server.config.IResourceLoader; +import io.moquette.spi.security.IAuthenticator; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.DigestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.util.HashMap; +import java.util.Map; + +/** + * Load user credentials from a text resource. + * Each line of the file is formatted as ":". The username mustn't contains : char. + * + * To encode your password from command line on Linux systems, you could use: + *
+ *     echo -n "yourpassword" | sha256sum
+ * 
+ * NB -n is important because echo append a newline by default at the of string. -n avoid this behaviour. + * + * @author andrea + */ +public class ResourceAuthenticator implements IAuthenticator { + + protected static final Logger LOG = LoggerFactory.getLogger(ResourceAuthenticator.class); + + private Map m_identities = new HashMap<>(); + private MessageDigest m_digest; + + public ResourceAuthenticator(IResourceLoader resourceLoader, String resourceName) { + try { + this.m_digest = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException nsaex) { + LOG.error("Can't find SHA-256 for password encoding", nsaex); + throw new RuntimeException(nsaex); + } + + LOG.info(String.format("Loading password %s %s", resourceLoader.getName(), resourceName)); + Reader reader = null; + try { + reader = resourceLoader.loadResource(resourceName); + if (reader == null) { + LOG.warn(String.format("Parsing not existing %s %s", resourceLoader.getName(), resourceName)); + } + else { + parse(reader); + } + } + catch (IResourceLoader.ResourceIsDirectoryException e) { + LOG.warn(String.format("Trying to parse directory %s", resourceName)); + } + catch (ParseException pex) { + LOG.warn(String.format("Format error in parsing password %s %s", resourceLoader.getName(), resourceName), pex); + } + } + + private void parse(Reader reader) throws ParseException { + if (reader == null) { + return; + } + + BufferedReader br = new BufferedReader(reader); + String line; + try { + while ((line = br.readLine()) != null) { + int commentMarker = line.indexOf('#'); + if (commentMarker != -1) { + if (commentMarker == 0) { + //skip its a comment + continue; + } else { + //it's a malformed comment + throw new ParseException(line, commentMarker); + } + } else { + if (line.isEmpty() || line.matches("^\\s*$")) { + //skip it's a black line + continue; + } + + //split till the first space + int delimiterIdx = line.indexOf(':'); + String username = line.substring(0, delimiterIdx).trim(); + String password = line.substring(delimiterIdx + 1).trim(); + + m_identities.put(username, password); + } + } + } catch (IOException ex) { + throw new ParseException("Failed to read", 1); + } + } + + @Override + public boolean checkValid(String clientId, String username, byte[] password) { + if (username == null || password == null) { + LOG.info("username or password was null"); + return false; + } + String foundPwq = m_identities.get(username); + if (foundPwq == null) { + return false; + } + String encodedPasswd = DigestUtils.sha256Hex(password); + return foundPwq.equals(encodedPasswd); + } + +}