Skip to content

Commit

Permalink
Add listener_allow_anonymous option.
Browse files Browse the repository at this point in the history
  • Loading branch information
ralight committed Nov 7, 2024
1 parent 7439a66 commit 304395f
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 8 deletions.
1 change: 1 addition & 0 deletions ChangeLog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Broker:
- Add -q option to allow logging to be disabled at the command line.
- Add suport for PROXY protocol v1 and v2.
- Log message if a client attempts to connect with TLS to a non-TLS listener.
- Add `listener_allow_anonymous` option.

Plugins / plugin interface:
- Add persist-sqlite plugin.
Expand Down
30 changes: 28 additions & 2 deletions man/mosquitto.conf.5.xml
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@
<varlistentry>
<term><option>allow_anonymous</option> [ true | false ]</term>
<listitem>
<para>Boolean value that determines whether clients that
<para>Global boolean value that determines whether clients that
connect without providing a username are allowed to
connect. If set to <replaceable>false</replaceable>
then another means of connection should be created to
Expand All @@ -262,7 +262,12 @@
file, in which case it set to <replaceable>true</replaceable>,
but connections are only allowed from the local machine.</para>

<para>If <option>per_listener_settings</option> is
<para>
If you want to control this setting for each listener individually,
use the <option>listener_allow_anonymous</option> option.
</para>

<para>(Note: Deprecated) If <option>per_listener_settings</option> is
<replaceable>true</replaceable>, this option applies to
the current listener being configured only. If
<option>per_listener_settings</option> is
Expand Down Expand Up @@ -1393,6 +1398,27 @@ accept_protocol_versions 3, 4</programlisting>
<para>Not reloaded on reload signal.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>listener_allow_anonymous</option> [ true | false ]</term>
<listitem>
<para>
Boolean value that determines whether clients that
connect without providing a username are allowed to
connect to this specific listener. If set, this overrides
the value set by <option>allow_anonymous</option>

If set to <replaceable>false</replaceable>
then another means of connection should be created to
control authenticated client access.
</para>

<para>
If not explicitly set, the value from <option>allow_anonymous</option> will be used.
</para>

<para>Not reloaded on reload signal.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>max_connections</option> <replaceable>count</replaceable></term>
<listitem>
Expand Down
11 changes: 9 additions & 2 deletions mosquitto.conf
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
# affected:
#
# acl_file
# allow_anonymous
# allow_anonymous - use listener_allow_anonymous instead
# allow_zero_length_clientid
# auto_id_prefix
# password_file
# plugin
# plugin - use plugin_load and plugin_use instead
# plugin_opt_*
# psk_file
#
Expand Down Expand Up @@ -318,6 +318,13 @@
# Defaults to allowing all versions.
#accept_protocol_versions 3,4,5

# If you wish to allow unauthenticated connections for a specific listener, use
# the listener_allow_anonymous option set to true. If not set, the value
# determined by the global allow_anonymous option will be used. If
# listener_allow_anonymous is set at the same time as allow_anonymous, the
# value set by listener_allow_anonymous will always take priority.
#listener_allow_anonymous

# Set use_username_as_clientid to true to replace the clientid that a client
# connected with with its username. This allows authentication to be tied to
# the clientid, which means that it is possible to prevent one client
Expand Down
9 changes: 9 additions & 0 deletions src/conf.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ extern SERVICE_STATUS_HANDLE service_handle;
} \
}while(0)

#define PER_LISTENER_ALTERNATIVE(A, B) \
if(config->per_listener_settings){ \
log__printf(NULL, MOSQ_LOG_NOTICE, "You are using the '%s' option with 'per_listener_settings true'. Please replace this with '%s'.", A, B); \
} \

static struct mosquitto__security_options *cur_security_options = NULL;

static int conf__parse_bool(char **token, const char *name, bool *value, char **saveptr);
Expand Down Expand Up @@ -1024,6 +1029,7 @@ static int config__read_file_core(struct mosquitto__config *config, bool reload,
#endif
}else if(!strcmp(token, "allow_anonymous")){
REQUIRE_LISTENER_IF_PER_LISTENER(token);
PER_LISTENER_ALTERNATIVE(token, "listener_allow_anonymous");
conf__set_cur_security_options(config, &cur_listener, &cur_security_options, token);
if(conf__parse_bool(&token, "allow_anonymous", (bool *)&cur_security_options->allow_anonymous, &saveptr)) return MOSQ_ERR_INVAL;
}else if(!strcmp(token, "allow_duplicate_messages")){
Expand Down Expand Up @@ -1785,6 +1791,9 @@ static int config__read_file_core(struct mosquitto__config *config, bool reload,
cur_listener->host = mosquitto_strdup(token);
}
}
}else if(!strcmp(token, "listener_allow_anonymous")){
REQUIRE_LISTENER(token);
if(conf__parse_bool(&token, "listener_allow_anonymous", (bool *)&cur_listener->security_options->allow_anonymous, &saveptr)) return MOSQ_ERR_INVAL;
}else if(!strcmp(token, "local_clientid")){
#ifdef WITH_BRIDGE
REQUIRE_BRIDGE(token);
Expand Down
10 changes: 6 additions & 4 deletions src/plugin_basic_auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ int mosquitto_basic_auth(struct mosquitto *context)
* here, then no plugins were configured.
* anonymous logins are allowed. */
if(plugin_used == false){
if((db.config->per_listener_settings && context->listener && context->listener->security_options->allow_anonymous != false)
|| (!db.config->per_listener_settings && db.config->security_options.allow_anonymous != false)){
if((context->listener && context->listener->security_options->allow_anonymous == true)
|| (!db.config->per_listener_settings && db.config->security_options.allow_anonymous == true
&& context->listener && context->listener->security_options->allow_anonymous != false)){

return MOSQ_ERR_SUCCESS;
}else{
Expand All @@ -97,8 +98,9 @@ int mosquitto_basic_auth(struct mosquitto *context)
/* Can't have got here without at least one plugin returning MOSQ_ERR_PLUGIN_DEFER.
* This will now be a denial, unless it is anon and allow anon is true. */
if(context->username == NULL &&
((db.config->per_listener_settings && context->listener && context->listener->security_options->allow_anonymous != false)
|| (!db.config->per_listener_settings && db.config->security_options.allow_anonymous != false))){
((context->listener && context->listener->security_options->allow_anonymous == true)
|| (!db.config->per_listener_settings && db.config->security_options.allow_anonymous == true
&& context->listener && context->listener->security_options->allow_anonymous != false))){

return MOSQ_ERR_SUCCESS;
}else{
Expand Down
18 changes: 18 additions & 0 deletions test/broker/01-connect-allow-anonymous.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ def write_config5(filename, port):
f.write("listener %d\n" % (port))
f.write("allow_anonymous true\n")

def write_config6(filename, port):
with open(filename, 'w') as f:
f.write("allow_anonymous false\n")
f.write("listener %d\n" % (port))
f.write("listener_allow_anonymous true\n")

def write_config7(filename, port):
with open(filename, 'w') as f:
f.write("allow_anonymous true\n")
f.write("listener %d\n" % (port))
f.write("listener_allow_anonymous false\n")


def do_test(use_conf, write_config, expect_success):
port = mosq_test.get_port()
Expand Down Expand Up @@ -89,4 +101,10 @@ def do_test(use_conf, write_config, expect_success):

# Config file with "listener" - allow_anonymous explicitly true
do_test(use_conf=True, write_config=write_config5, expect_success=True)

# Config file with "listener" - allow_anonymous explicitly false and listener_allow_anonymous explicitly true
do_test(use_conf=True, write_config=write_config6, expect_success=True)

# Config file with "listener" - allow_anonymous explicitly true and listener_allow_anonymous explicitly false
do_test(use_conf=True, write_config=write_config7, expect_success=False)
exit(0)
135 changes: 135 additions & 0 deletions test/broker/01-connect-listener-allow-anonymous.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env python3

# Test whether an anonymous connection is correctly denied.

from mosq_test_helper import *

def write_config1(filename, port1, port2):
with open(filename, 'w') as f:
f.write("listener %d\n" % (port1))
f.write("listener_allow_anonymous false\n")
f.write("listener %d\n" % (port2))
f.write("listener_allow_anonymous false\n")

def write_config2(filename, port1, port2):
with open(filename, 'w') as f:
f.write("listener %d\n" % (port1))
f.write("listener_allow_anonymous true\n")
f.write("listener %d\n" % (port2))
f.write("listener_allow_anonymous false\n")

def write_config3(filename, port1, port2):
with open(filename, 'w') as f:
f.write("listener %d\n" % (port1))
f.write("listener_allow_anonymous false\n")
f.write("listener %d\n" % (port2))
f.write("listener_allow_anonymous true\n")

def write_config4(filename, port1, port2):
with open(filename, 'w') as f:
f.write("allow_anonymous true\n")
f.write("listener %d\n" % (port1))
f.write("listener_allow_anonymous false\n")
f.write("listener %d\n" % (port2))
f.write("listener_allow_anonymous false\n")

def write_config5(filename, port1, port2):
with open(filename, 'w') as f:
f.write("allow_anonymous true\n")
f.write("listener %d\n" % (port1))
f.write("listener_allow_anonymous true\n")
f.write("listener %d\n" % (port2))
f.write("listener_allow_anonymous false\n")

def write_config6(filename, port1, port2):
with open(filename, 'w') as f:
f.write("allow_anonymous true\n")
f.write("listener %d\n" % (port1))
f.write("listener_allow_anonymous false\n")
f.write("listener %d\n" % (port2))
f.write("listener_allow_anonymous true\n")

def write_config7(filename, port1, port2):
with open(filename, 'w') as f:
f.write("allow_anonymous false\n")
f.write("listener %d\n" % (port1))
f.write("listener_allow_anonymous false\n")
f.write("listener %d\n" % (port2))
f.write("listener_allow_anonymous false\n")

def write_config8(filename, port1, port2):
with open(filename, 'w') as f:
f.write("allow_anonymous false\n")
f.write("listener %d\n" % (port1))
f.write("listener_allow_anonymous true\n")
f.write("listener %d\n" % (port2))
f.write("listener_allow_anonymous false\n")

def write_config9(filename, port1, port2):
with open(filename, 'w') as f:
f.write("allow_anonymous false\n")
f.write("listener %d\n" % (port1))
f.write("listener_allow_anonymous false\n")
f.write("listener %d\n" % (port2))
f.write("listener_allow_anonymous true\n")


def do_test(write_config, expect_success1, expect_success2):
port1, port2 = mosq_test.get_port(2)
if write_config is not None:
conf_file = os.path.basename(__file__).replace('.py', '.conf')
write_config(conf_file, port1, port2)

broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port1)

try:
for proto_ver in [4, 5]:
rc = 1
connect_packet = mosq_test.gen_connect(f"connect-anon-test-{proto_ver}-{expect_success1}-{expect_success2}", proto_ver=proto_ver)

if proto_ver == 5:
connack_packet_success = mosq_test.gen_connack(rc=0, proto_ver=proto_ver)
connack_packet_rejected = mosq_test.gen_connack(rc=mqtt5_rc.MQTT_RC_NOT_AUTHORIZED, proto_ver=proto_ver, properties=None)
else:
connack_packet_success = mosq_test.gen_connack(rc=0, proto_ver=proto_ver)
connack_packet_rejected = mosq_test.gen_connack(rc=5, proto_ver=proto_ver)


if expect_success1:
sock = mosq_test.do_client_connect(connect_packet, connack_packet_success, port=port1)
else:
sock = mosq_test.do_client_connect(connect_packet, connack_packet_rejected, port=port1)
sock.close()

if expect_success2:
sock = mosq_test.do_client_connect(connect_packet, connack_packet_success, port=port2)
else:
sock = mosq_test.do_client_connect(connect_packet, connack_packet_rejected, port=port2)
sock.close()
rc = 0
except mosq_test.TestError:
pass
finally:
if write_config is not None:
os.remove(conf_file)
broker.terminate()
if mosq_test.wait_for_subprocess(broker):
print("broker not terminated")
if rc == 0: rc=1
(stdo, stde) = broker.communicate()
if rc:
print(stde.decode('utf-8'))
print("proto_ver=%d" % (proto_ver))
exit(rc)


do_test(write_config=write_config1, expect_success1=False, expect_success2=False)
do_test(write_config=write_config2, expect_success1=True, expect_success2=False)
do_test(write_config=write_config3, expect_success1=False, expect_success2=True)
do_test(write_config=write_config4, expect_success1=False, expect_success2=False)
do_test(write_config=write_config5, expect_success1=True, expect_success2=False)
do_test(write_config=write_config6, expect_success1=False, expect_success2=True)
do_test(write_config=write_config7, expect_success1=False, expect_success2=False)
do_test(write_config=write_config8, expect_success1=True, expect_success2=False)
do_test(write_config=write_config9, expect_success1=False, expect_success2=True)
exit(0)
1 change: 1 addition & 0 deletions test/broker/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ msg_sequence_test:
./01-connect-disconnect-v5.py
./01-connect-global-max-clients.py
./01-connect-global-max-connections.py
./01-connect-listener-allow-anonymous.py
./01-connect-max-connections.py
./01-connect-max-keepalive.py
./01-connect-take-over.py
Expand Down
1 change: 1 addition & 0 deletions test/broker/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
(1, './01-connect-disconnect-v5.py'),
(1, './01-connect-global-max-clients.py'),
(1, './01-connect-global-max-connections.py'),
(2, './01-connect-listener-allow-anonymous.py'),
(1, './01-connect-max-connections.py'),
(1, './01-connect-max-keepalive.py'),
(1, './01-connect-take-over.py'),
Expand Down

0 comments on commit 304395f

Please sign in to comment.