diff --git a/ChangeLog.txt b/ChangeLog.txt index af7bd2b7..5c656f8e 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -10,6 +10,7 @@ Version 0.7: Added store path variable "persistent_store" to configuration file (picked from Steven Warwick fork) Removed OSGi bundle, developer time burner Fixed github issue #30, returning error on mapdb file not deleted that corrupt reiterated builds + Added handling of allow_anonymous clients (as in mosquitto issue #34) Version 0.6: Fix issue #42, bad password file path composition diff --git a/broker/config/moquette.conf b/broker/config/moquette.conf index 0835f78e..43ae2ebc 100644 --- a/broker/config/moquette.conf +++ b/broker/config/moquette.conf @@ -12,3 +12,7 @@ websocket_port 8080 host 0.0.0.0 password_file config/password_file.conf + +#false to accept only client connetions with credentials +#true to accept client connection without credentails, validating only the one that provides +allow_anonymous true \ No newline at end of file diff --git a/broker/src/main/java/org/eclipse/moquette/server/ConfigurationParser.java b/broker/src/main/java/org/eclipse/moquette/server/ConfigurationParser.java index cd556aef..a60037ff 100644 --- a/broker/src/main/java/org/eclipse/moquette/server/ConfigurationParser.java +++ b/broker/src/main/java/org/eclipse/moquette/server/ConfigurationParser.java @@ -59,6 +59,7 @@ class ConfigurationParser { m_properties.put(WEB_SOCKET_PORT_PROPERTY_NAME, Integer.toString(WEBSOCKET_PORT)); m_properties.put(PASSWORD_FILE_PROPERTY_NAME, ""); m_properties.put(PERSISTENT_STORE_PROPERTY_NAME, DEFAULT_PERSISTENT_PATH); + m_properties.put(ALLOW_ANONYMOUS, true); } /** diff --git a/broker/src/main/java/org/eclipse/moquette/spi/impl/ProtocolProcessor.java b/broker/src/main/java/org/eclipse/moquette/spi/impl/ProtocolProcessor.java index be4cc2de..5e918835 100644 --- a/broker/src/main/java/org/eclipse/moquette/spi/impl/ProtocolProcessor.java +++ b/broker/src/main/java/org/eclipse/moquette/spi/impl/ProtocolProcessor.java @@ -97,6 +97,7 @@ class ProtocolProcessor implements EventHandler { private Map m_clientIDs = new HashMap<>(); private SubscriptionsStore subscriptions; + private boolean allowAnonymous; private IMessagesStore m_messagesStore; private ISessionsStore m_sessionsStore; private IAuthenticator m_authenticator; @@ -114,13 +115,16 @@ class ProtocolProcessor implements EventHandler { * @param storageService the persistent store to use for save/load of messages * for QoS1 and QoS2 handling. * @param sessionsStore the clients sessions store, used to persist subscriptions. - * @param authenticator the authenticator used in connect messages + * @param authenticator the authenticator used in connect messages. + * @param allowAnonymous true connection to clients without credentials. */ void init(SubscriptionsStore subscriptions, IMessagesStore storageService, ISessionsStore sessionsStore, - IAuthenticator authenticator) { + IAuthenticator authenticator, + boolean allowAnonymous) { //m_clientIDs = clientIDs; this.subscriptions = subscriptions; + this.allowAnonymous = allowAnonymous; LOG.debug("subscription tree on init {}", subscriptions.dumpTree()); m_authenticator = authenticator; m_messagesStore = storageService; @@ -199,13 +203,17 @@ class ProtocolProcessor implements EventHandler { String pwd = null; if (msg.isPasswordFlag()) { pwd = msg.getPassword(); - } - if (!m_authenticator.checkValid(msg.getUsername(), pwd)) { - ConnAckMessage okResp = new ConnAckMessage(); - okResp.setReturnCode(ConnAckMessage.BAD_USERNAME_OR_PASSWORD); - session.write(okResp); + } else if (!this.allowAnonymous) { + failedCredetials(session); return; } + if (!m_authenticator.checkValid(msg.getUsername(), pwd)) { + failedCredetials(session); + return; + } + } else if (!this.allowAnonymous) { + failedCredetials(session); + return; } subscriptions.activate(msg.getClientID()); @@ -233,6 +241,12 @@ class ProtocolProcessor implements EventHandler { } } + private void failedCredetials(ServerChannel session) { + ConnAckMessage okResp = new ConnAckMessage(); + okResp.setReturnCode(ConnAckMessage.BAD_USERNAME_OR_PASSWORD); + session.write(okResp); + } + /** * Republish QoS1 and QoS2 messages stored into the session for the clientID. * */ diff --git a/broker/src/main/java/org/eclipse/moquette/spi/impl/SimpleMessaging.java b/broker/src/main/java/org/eclipse/moquette/spi/impl/SimpleMessaging.java index e46cf762..54d61883 100644 --- a/broker/src/main/java/org/eclipse/moquette/spi/impl/SimpleMessaging.java +++ b/broker/src/main/java/org/eclipse/moquette/spi/impl/SimpleMessaging.java @@ -42,6 +42,7 @@ import java.util.concurrent.TimeUnit; import static org.eclipse.moquette.commons.Constants.PASSWORD_FILE_PROPERTY_NAME; import static org.eclipse.moquette.commons.Constants.PERSISTENT_STORE_PROPERTY_NAME; +import static org.eclipse.moquette.commons.Constants.ALLOW_ANONYMOUS; /** * @@ -187,7 +188,7 @@ public class SimpleMessaging implements IMessaging, EventHandler { //List storedSubscriptions = m_sessionsStore.listAllSubscriptions(); //subscriptions.init(storedSubscriptions); subscriptions.init(m_sessionsStore); - + String passwdPath = props.getProperty(PASSWORD_FILE_PROPERTY_NAME, ""); String configPath = System.getProperty("moquette.path", null); IAuthenticator authenticator; @@ -196,8 +197,9 @@ public class SimpleMessaging implements IMessaging, EventHandler { } else { authenticator = new FileAuthenticator(configPath, passwdPath); } - - m_processor.init(subscriptions, m_storageService, m_sessionsStore, authenticator); + + boolean allowAnonymous = Boolean.parseBoolean(props.getProperty(ALLOW_ANONYMOUS, "true")); + m_processor.init(subscriptions, m_storageService, m_sessionsStore, authenticator, allowAnonymous); } diff --git a/broker/src/test/java/org/eclipse/moquette/spi/impl/ProtocolProcessorTest.java b/broker/src/test/java/org/eclipse/moquette/spi/impl/ProtocolProcessorTest.java index f49bf8c7..9d1ad31a 100644 --- a/broker/src/test/java/org/eclipse/moquette/spi/impl/ProtocolProcessorTest.java +++ b/broker/src/test/java/org/eclipse/moquette/spi/impl/ProtocolProcessorTest.java @@ -164,7 +164,7 @@ public class ProtocolProcessorTest { subscriptions = new SubscriptionsStore(); subscriptions.init(new MemoryStorageService()); m_processor = new ProtocolProcessor(); - m_processor.init(subscriptions, m_storageService, m_sessionStore, m_mockAuthenticator); + m_processor.init(subscriptions, m_storageService, m_sessionStore, m_mockAuthenticator, true); } @Test @@ -251,6 +251,44 @@ public class ProtocolProcessorTest { //Verify assertEquals(ConnAckMessage.BAD_USERNAME_OR_PASSWORD, m_returnCode); } + + @Test + public void prohibitAnonymousClient() { + connMsg.setClientID("123"); + m_processor.init(subscriptions, m_storageService, m_sessionStore, m_mockAuthenticator, false); + + //Exercise + m_processor.processConnect(m_session, connMsg); + + //Verify + assertEquals(ConnAckMessage.BAD_USERNAME_OR_PASSWORD, m_returnCode); + } + + @Test + public void prohibitAnonymousClient_providingUsername() { + connMsg.setClientID("123"); + connMsg.setUserFlag(true); + connMsg.setUsername(TEST_USER + "_fake"); + m_processor.init(subscriptions, m_storageService, m_sessionStore, m_mockAuthenticator, false); + + //Exercise + m_processor.processConnect(m_session, connMsg); + + //Verify + assertEquals(ConnAckMessage.BAD_USERNAME_OR_PASSWORD, m_returnCode); + } + + @Test + public void acceptAnonymousClient() { + connMsg.setClientID("123"); + m_processor.init(subscriptions, m_storageService, m_sessionStore, m_mockAuthenticator, true); + + //Exercise + m_processor.processConnect(m_session, connMsg); + + //Verify + assertEquals(ConnAckMessage.CONNECTION_ACCEPTED, m_returnCode); + } @Test @@ -298,7 +336,7 @@ public class ProtocolProcessorTest { //simulate a connect that register a clientID to an IoSession subs.init(new MemoryStorageService()); - m_processor.init(subs, m_storageService, m_sessionStore, null); + m_processor.init(subs, m_storageService, m_sessionStore, null, true); ConnectMessage connectMessage = new ConnectMessage(); connectMessage.setProcotolVersion((byte)3); connectMessage.setClientID(FAKE_CLIENT_ID); @@ -342,7 +380,7 @@ public class ProtocolProcessorTest { //simulate a connect that register a clientID to an IoSession subs.init(new MemoryStorageService()); - m_processor.init(subs, m_storageService, m_sessionStore, null); + m_processor.init(subs, m_storageService, m_sessionStore, null, true); MockReceiverChannel firstReceiverSession = new MockReceiverChannel(); ConnectMessage connectMessage = new ConnectMessage(); @@ -460,7 +498,7 @@ public class ProtocolProcessorTest { subs.init(new MemoryStorageService()); //simulate a connect that register a clientID to an IoSession - m_processor.init(subs, m_storageService, m_sessionStore, null); + m_processor.init(subs, m_storageService, m_sessionStore, null, true); ConnectMessage connectMessage = new ConnectMessage(); connectMessage.setClientID(FAKE_PUBLISHER_ID); connectMessage.setProcotolVersion((byte)3); @@ -499,7 +537,7 @@ public class ProtocolProcessorTest { ByteBuffer.wrap("Hello".getBytes()), true, FAKE_PUBLISHER_ID, 120); m_storageService.storePublishForFuture(retainedMessage); - m_processor.init(subs, m_storageService, m_sessionStore, null); + m_processor.init(subs, m_storageService, m_sessionStore, null, true); ConnectMessage connectMessage = new ConnectMessage(); connectMessage.setClientID(FAKE_PUBLISHER_ID); connectMessage.setProcotolVersion((byte)3); @@ -518,7 +556,7 @@ public class ProtocolProcessorTest { List inactiveSubscriptions = Arrays.asList(inactiveSub); when(mockedSubscriptions.matches(eq("/topic"))).thenReturn(inactiveSubscriptions); m_processor = new ProtocolProcessor(); - m_processor.init(mockedSubscriptions, m_storageService, m_sessionStore, null); + m_processor.init(mockedSubscriptions, m_storageService, m_sessionStore, null, true); //Exercise ByteBuffer buffer = ByteBuffer.allocate(5).put("Hello".getBytes()); @@ -543,7 +581,7 @@ public class ProtocolProcessorTest { List inactiveSubscriptions = Arrays.asList(inactiveSub); when(mockedSubscriptions.matches(eq("/topic"))).thenReturn(inactiveSubscriptions); m_processor = new ProtocolProcessor(); - m_processor.init(mockedSubscriptions, m_storageService, m_sessionStore, null); + m_processor.init(mockedSubscriptions, m_storageService, m_sessionStore, null, true); //Exercise ByteBuffer buffer = ByteBuffer.allocate(5).put("Hello".getBytes()); @@ -611,7 +649,7 @@ public class ProtocolProcessorTest { /*SubscriptionsStore subs = new SubscriptionsStore(); subs.init(new MemoryStorageService());*/ SubscriptionsStore subs = mock(SubscriptionsStore.class); - m_processor.init(subs, m_storageService, m_sessionStore, null); + m_processor.init(subs, m_storageService, m_sessionStore, null, true); //simulate a connect from client1 FAKE_CLIENT_ID from channel1 ConnectMessage connectMessage = new ConnectMessage(); diff --git a/broker/src/test/resources/config/moquette.conf b/broker/src/test/resources/config/moquette.conf index d10166e6..3d1bb4bb 100644 --- a/broker/src/test/resources/config/moquette.conf +++ b/broker/src/test/resources/config/moquette.conf @@ -18,3 +18,5 @@ ssl_port 8883 jks_path serverkeystore.jks key_store_password passw0rdsrv key_manager_password passw0rdsrv + +allow_anonymous true diff --git a/distribution/src/main/resources/moquette.conf b/distribution/src/main/resources/moquette.conf index 001f5889..d1e5492f 100644 --- a/distribution/src/main/resources/moquette.conf +++ b/distribution/src/main/resources/moquette.conf @@ -13,3 +13,7 @@ host 0.0.0.0 #Password file password_file config/password_file.conf + +#false to accept only client connetions with credentials +#true to accept client connection without credentails, validating only the one that provides +allow_anonymous true \ No newline at end of file diff --git a/parser_commons/src/main/java/org/eclipse/moquette/commons/Constants.java b/parser_commons/src/main/java/org/eclipse/moquette/commons/Constants.java index 676ab803..12ab66c3 100644 --- a/parser_commons/src/main/java/org/eclipse/moquette/commons/Constants.java +++ b/parser_commons/src/main/java/org/eclipse/moquette/commons/Constants.java @@ -36,4 +36,5 @@ public class Constants { public static final String JKS_PATH_PROPERTY_NAME = "jks_path"; public static final String KEY_STORE_PASSWORD_PROPERTY_NAME = "key_store_password"; public static final String KEY_MANAGER_PASSWORD_PROPERTY_NAME = "key_manager_password"; + public static final String ALLOW_ANONYMOUS = "allow_anonymous"; }