From ef6806398507febf9d8a855a9aa581524ace4e73 Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Thu, 15 Oct 2020 14:50:46 +0100 Subject: [PATCH] Convert default security to "plugin" mode. This converts password_file and acl_file checks to act like a v5 plugin. --- src/mosquitto_broker_internal.h | 3 +- src/security.c | 47 ++++++---- src/security_default.c | 161 +++++++++++++++++--------------- 3 files changed, 115 insertions(+), 96 deletions(-) diff --git a/src/mosquitto_broker_internal.h b/src/mosquitto_broker_internal.h index 14e9c6e1f9..d06581fcec 100644 --- a/src/mosquitto_broker_internal.h +++ b/src/mosquitto_broker_internal.h @@ -256,6 +256,7 @@ struct mosquitto__security_options { char *auto_id_prefix; int auto_id_prefix_len; struct plugin__callbacks plugin_callbacks; + mosquitto_plugin_id_t *pid; /* For registering as a "plugin" */ }; struct mosquitto__listener { @@ -838,8 +839,6 @@ int mosquitto_psk_key_get(struct mosquitto_db *db, struct mosquitto *context, co int mosquitto_security_init_default(struct mosquitto_db *db, bool reload); int mosquitto_security_apply_default(struct mosquitto_db *db); int mosquitto_security_cleanup_default(struct mosquitto_db *db, bool reload); -int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *context, const char *topic, int access); -int mosquitto_unpwd_check_default(struct mosquitto_db *db, struct mosquitto *context); int mosquitto_psk_key_get_default(struct mosquitto_db *db, struct mosquitto *context, const char *hint, const char *identity, char *key, int max_key_len); int mosquitto_security_auth_start(struct mosquitto_db *db, struct mosquitto *context, bool reauth, const void *data_in, uint16_t data_in_len, void **data_out, uint16_t *data_out_len); diff --git a/src/security.c b/src/security.c index e09476133d..d411a2d744 100644 --- a/src/security.c +++ b/src/security.c @@ -679,21 +679,24 @@ int mosquitto_acl_check(struct mosquitto_db *db, struct mosquitto *context, cons if(!context->id){ return MOSQ_ERR_ACL_DENIED; } + if(context->bridge){ + return MOSQ_ERR_SUCCESS; + } rc = acl__check_dollar(topic, access); if(rc) return rc; - rc = mosquitto_acl_check_default(db, context, topic, access); - if(rc != MOSQ_ERR_PLUGIN_DEFER){ - return rc; - } - /* Default check has accepted or deferred at this point. + /* * If no plugins exist we should accept at this point so set rc to success. */ rc = MOSQ_ERR_SUCCESS; if(db->config->per_listener_settings){ - opts = &context->listener->security_options; + if(context->listener){ + opts = &context->listener->security_options; + }else{ + return MOSQ_ERR_ACL_DENIED; + } }else{ opts = &db->config->security_options; } @@ -747,14 +750,10 @@ int mosquitto_unpwd_check(struct mosquitto_db *db, struct mosquitto *context) struct mosquitto__security_options *opts; struct mosquitto_evt_basic_auth event_data; struct mosquitto__callback *cb_base; + bool plugin_used = false; + + rc = MOSQ_ERR_PLUGIN_DEFER; - rc = mosquitto_unpwd_check_default(db, context); - if(rc != MOSQ_ERR_PLUGIN_DEFER){ - return rc; - } - /* Default check has accepted or deferred at this point. - * If no plugins exist we should accept at this point so set rc to success. - */ if(db->config->per_listener_settings){ opts = &context->listener->security_options; }else{ @@ -770,6 +769,7 @@ int mosquitto_unpwd_check(struct mosquitto_db *db, struct mosquitto *context) if(rc != MOSQ_ERR_PLUGIN_DEFER){ return rc; } + plugin_used = true; } for(i=0; iauth_plugin_config_count; i++){ @@ -781,6 +781,7 @@ int mosquitto_unpwd_check(struct mosquitto_db *db, struct mosquitto *context) context, context->username, context->password); + plugin_used = true; }else if(opts->auth_plugin_configs[i].plugin.version == 3){ rc = opts->auth_plugin_configs[i].plugin.unpwd_check_v3( @@ -788,26 +789,38 @@ int mosquitto_unpwd_check(struct mosquitto_db *db, struct mosquitto *context) context, context->username, context->password); + plugin_used = true; }else if(opts->auth_plugin_configs[i].plugin.version == 2){ rc = opts->auth_plugin_configs[i].plugin.unpwd_check_v2( opts->auth_plugin_configs[i].plugin.user_data, context->username, context->password); + plugin_used = true; } } /* If all plugins deferred, this is a denial. If rc == MOSQ_ERR_SUCCESS * here, then no plugins were configured. Unless we have all deferred, and * anonymous logins are allowed. */ - if(rc == MOSQ_ERR_PLUGIN_DEFER){ - if(context->username == NULL && - ((db->config->per_listener_settings && context->listener->security_options.allow_anonymous == true) - || (!db->config->per_listener_settings && db->config->security_options.allow_anonymous == true))){ + if(plugin_used == false){ + if((db->config->per_listener_settings && context->listener->security_options.allow_anonymous != false) + || (!db->config->per_listener_settings && db->config->security_options.allow_anonymous != false)){ return MOSQ_ERR_SUCCESS; }else{ return MOSQ_ERR_AUTH; } + }else{ + if(rc == MOSQ_ERR_PLUGIN_DEFER){ + if(context->username == NULL && + ((db->config->per_listener_settings && context->listener->security_options.allow_anonymous != false) + || (!db->config->per_listener_settings && db->config->security_options.allow_anonymous != false))){ + + return MOSQ_ERR_SUCCESS; + }else{ + return MOSQ_ERR_AUTH; + } + } } return rc; diff --git a/src/security_default.c b/src/security_default.c index c2e7ff5402..567302454c 100644 --- a/src/security_default.c +++ b/src/security_default.c @@ -35,6 +35,8 @@ static int psk__file_parse(struct mosquitto_db *db, struct mosquitto__unpwd **ps #ifdef WITH_TLS static int pw__digest(const char *password, const unsigned char *salt, unsigned int salt_len, unsigned char *hash, unsigned int *hash_len, enum mosquitto_pwhash_type hashtype); #endif +static int mosquitto_unpwd_check_default(int event, void *event_data, void *userdata); +static int mosquitto_acl_check_default(int event, void *event_data, void *userdata); @@ -47,6 +49,24 @@ int mosquitto_security_init_default(struct mosquitto_db *db, bool reload) UNUSED(reload); + /* Configure plugin identifier */ + if(db->config->per_listener_settings){ + for(i=0; iconfig->listener_count; i++){ + db->config->listeners[i].security_options.pid = mosquitto__calloc(1, sizeof(mosquitto_plugin_id_t)); + if(db->config->listeners[i].security_options.pid == NULL){ + log__printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + db->config->listeners[i].security_options.pid->listener = &db->config->listeners[i]; + } + }else{ + db->config->security_options.pid = mosquitto__calloc(1, sizeof(mosquitto_plugin_id_t)); + if(db->config->security_options.pid == NULL){ + log__printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + } + /* Load username/password data if required. */ if(db->config->per_listener_settings){ for(i=0; iconfig->listener_count; i++){ @@ -57,6 +77,8 @@ int mosquitto_security_init_default(struct mosquitto_db *db, bool reload) log__printf(NULL, MOSQ_LOG_ERR, "Error opening password file \"%s\".", pwf); return rc; } + mosquitto_callback_register(db->config->listeners[i].security_options.pid, + MOSQ_EVT_BASIC_AUTH, mosquitto_unpwd_check_default, NULL, NULL); } } }else{ @@ -69,6 +91,8 @@ int mosquitto_security_init_default(struct mosquitto_db *db, bool reload) return rc; } } + mosquitto_callback_register(db->config->security_options.pid, + MOSQ_EVT_BASIC_AUTH, mosquitto_unpwd_check_default, NULL, NULL); } } @@ -81,6 +105,8 @@ int mosquitto_security_init_default(struct mosquitto_db *db, bool reload) log__printf(NULL, MOSQ_LOG_ERR, "Error opening acl file \"%s\".", db->config->listeners[i].security_options.acl_file); return rc; } + mosquitto_callback_register(db->config->listeners[i].security_options.pid, + MOSQ_EVT_ACL_CHECK, mosquitto_acl_check_default, NULL, NULL); } } }else{ @@ -90,6 +116,8 @@ int mosquitto_security_init_default(struct mosquitto_db *db, bool reload) log__printf(NULL, MOSQ_LOG_ERR, "Error opening acl file \"%s\".", db->config->security_options.acl_file); return rc; } + mosquitto_callback_register(db->config->security_options.pid, + MOSQ_EVT_ACL_CHECK, mosquitto_acl_check_default, NULL, NULL); } } @@ -147,6 +175,19 @@ int mosquitto_security_cleanup_default(struct mosquitto_db *db, bool reload) } } + if(db->config->per_listener_settings){ + for(i=0; iconfig->listener_count; i++){ + if(db->config->listeners[i].security_options.pid){ + mosquitto_callback_unregister(db->config->listeners[i].security_options.pid, + MOSQ_EVT_BASIC_AUTH, mosquitto_unpwd_check_default, NULL); + } + } + }else{ + if(db->config->security_options.pid){ + mosquitto_callback_unregister(db->config->security_options.pid, + MOSQ_EVT_BASIC_AUTH, mosquitto_unpwd_check_default, NULL); + } + } return MOSQ_ERR_SUCCESS; } @@ -314,8 +355,9 @@ int add__acl_pattern(struct mosquitto__security_options *security_opts, const ch return MOSQ_ERR_SUCCESS; } -int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *context, const char *topic, int access) +static int mosquitto_acl_check_default(int event, void *event_data, void *userdata) { + struct mosquitto_evt_acl_check *ed = event_data; char *local_acl; struct mosquitto__acl *acl_root; bool result; @@ -323,13 +365,15 @@ int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *conte int len, tlen, clen, ulen; char *s; struct mosquitto__security_options *security_opts = NULL; + struct mosquitto_db *db; - if(!db || !context || !topic) return MOSQ_ERR_INVAL; - if(context->bridge) return MOSQ_ERR_SUCCESS; + if(ed->client->bridge) return MOSQ_ERR_SUCCESS; + if(ed->access == MOSQ_ACL_SUBSCRIBE || ed->access == MOSQ_ACL_UNSUBSCRIBE) return MOSQ_ERR_SUCCESS; /* FIXME - implement ACL subscription strings. */ + db = mosquitto__get_db(); if(db->config->per_listener_settings){ - if(!context->listener) return MOSQ_ERR_ACL_DENIED; - security_opts = &context->listener->security_options; + if(!ed->client->listener) return MOSQ_ERR_ACL_DENIED; + security_opts = &ed->client->listener->security_options; }else{ security_opts = &db->config->security_options; } @@ -337,11 +381,10 @@ int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *conte return MOSQ_ERR_PLUGIN_DEFER; } - if(access == MOSQ_ACL_SUBSCRIBE || access == MOSQ_ACL_UNSUBSCRIBE) return MOSQ_ERR_SUCCESS; /* FIXME - implement ACL subscription strings. */ - if(!context->acl_list && !security_opts->acl_patterns) return MOSQ_ERR_ACL_DENIED; + if(!ed->client->acl_list && !security_opts->acl_patterns) return MOSQ_ERR_ACL_DENIED; - if(context->acl_list){ - acl_root = context->acl_list->acl; + if(ed->client->acl_list){ + acl_root = ed->client->acl_list->acl; }else{ acl_root = NULL; } @@ -351,17 +394,17 @@ int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *conte /* Loop through the topic looking for matches to this ACL. */ /* If subscription starts with $, acl_root->topic must also start with $. */ - if(topic[0] == '$' && acl_root->topic[0] != '$'){ + if(ed->topic[0] == '$' && acl_root->topic[0] != '$'){ acl_root = acl_root->next; continue; } - mosquitto_topic_matches_sub(acl_root->topic, topic, &result); + mosquitto_topic_matches_sub(acl_root->topic, ed->topic, &result); if(result){ if(acl_root->access == MOSQ_ACL_NONE){ /* Access was explicitly denied for this topic. */ return MOSQ_ERR_ACL_DENIED; } - if(access & acl_root->access){ + if(ed->access & acl_root->access){ /* And access is allowed. */ return MOSQ_ERR_SUCCESS; } @@ -379,31 +422,31 @@ int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *conte * id to bypass ACL checks (or have a username/client id that cannot * publish or receive messages to its own place in the hierarchy). */ - if(context->username && strpbrk(context->username, "+#")){ - log__printf(NULL, MOSQ_LOG_NOTICE, "ACL denying access to client with dangerous username \"%s\"", context->username); + if(ed->client->username && strpbrk(ed->client->username, "+#")){ + log__printf(NULL, MOSQ_LOG_NOTICE, "ACL denying access to client with dangerous username \"%s\"", ed->client->username); return MOSQ_ERR_ACL_DENIED; } - if(context->id && strpbrk(context->id, "+#")){ - log__printf(NULL, MOSQ_LOG_NOTICE, "ACL denying access to client with dangerous client id \"%s\"", context->id); + if(ed->client->id && strpbrk(ed->client->id, "+#")){ + log__printf(NULL, MOSQ_LOG_NOTICE, "ACL denying access to client with dangerous client id \"%s\"", ed->client->id); return MOSQ_ERR_ACL_DENIED; } } /* Loop through all pattern ACLs. ACL denial patterns are iterated over first. */ - if(!context->id) return MOSQ_ERR_ACL_DENIED; - clen = strlen(context->id); + if(!ed->client->id) return MOSQ_ERR_ACL_DENIED; + clen = strlen(ed->client->id); while(acl_root){ tlen = strlen(acl_root->topic); - if(acl_root->ucount && !context->username){ + if(acl_root->ucount && !ed->client->username){ acl_root = acl_root->next; continue; } - if(context->username){ - ulen = strlen(context->username); + if(ed->client->username){ + ulen = strlen(ed->client->username); len = tlen + acl_root->ccount*(clen-2) + acl_root->ucount*(ulen-2); }else{ ulen = 0; @@ -416,12 +459,12 @@ int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *conte if(itopic[i] == '%'){ if(acl_root->topic[i+1] == 'c'){ i++; - strncpy(s, context->id, clen); + strncpy(s, ed->client->id, clen); s+=clen; continue; - }else if(context->username && acl_root->topic[i+1] == 'u'){ + }else if(ed->client->username && acl_root->topic[i+1] == 'u'){ i++; - strncpy(s, context->username, ulen); + strncpy(s, ed->client->username, ulen); s+=ulen; continue; } @@ -431,14 +474,14 @@ int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *conte } local_acl[len] = '\0'; - mosquitto_topic_matches_sub(local_acl, topic, &result); + mosquitto_topic_matches_sub(local_acl, ed->topic, &result); mosquitto__free(local_acl); if(result){ if(acl_root->access == MOSQ_ACL_NONE){ /* Access was explicitly denied for this topic pattern. */ return MOSQ_ERR_ACL_DENIED; } - if(access & acl_root->access){ + if(ed->access & acl_root->access){ /* And access is allowed. */ return MOSQ_ERR_SUCCESS; } @@ -918,8 +961,10 @@ static int mosquitto__memcmp_const(const void *a, const void *b, size_t len) #endif -int mosquitto_unpwd_check_default(struct mosquitto_db *db, struct mosquitto *context) +static int mosquitto_unpwd_check_default(int event, void *event_data, void *userdata) { + struct mosquitto_db *db; + struct mosquitto_evt_basic_auth *ed = event_data; struct mosquitto__unpwd *u; struct mosquitto__unpwd *unpwd_ref; #ifdef WITH_TLS @@ -927,65 +972,27 @@ int mosquitto_unpwd_check_default(struct mosquitto_db *db, struct mosquitto *con unsigned int hash_len; int rc; #endif - bool allow_anonymous; - char *password_file; - if(!db) return MOSQ_ERR_INVAL; + if(ed->client->username == NULL){ + return MOSQ_ERR_PLUGIN_DEFER; + } + + db = mosquitto__get_db(); - /* - * If allow_anonymous is true, and there is no password file defined, then - * all users are treated as being anonymous and can connect. - * - * If allow_anonymous is false and there is no password file defined, then - * we defer the decision to other plugins (this is a rejection if no other - * plugins are defined) - * - * If allow_anonymous is true, and there is a password file defined, then - * all users with a username must authenticate. All anonymous users are - * allowed to connect. This is a valid mode, because authenticated users - * can be assigned permissions that anonymous users are not. - * - * If allow_anonymous is false, and there is a password file defined, then - * all users with a username must authenticate. All anonymous users are - * defered to other plugins, (this is a rejection if no other plugins are - * defined). - */ if(db->config->per_listener_settings){ - if(context->bridge) return MOSQ_ERR_SUCCESS; - if(!context->listener) return MOSQ_ERR_INVAL; - unpwd_ref = context->listener->security_options.unpwd; - password_file = context->listener->security_options.password_file; - allow_anonymous = context->listener->security_options.allow_anonymous; + if(ed->client->bridge) return MOSQ_ERR_SUCCESS; + if(!ed->client->listener) return MOSQ_ERR_INVAL; + unpwd_ref = ed->client->listener->security_options.unpwd; }else{ unpwd_ref = db->config->security_options.unpwd; - password_file = db->config->security_options.password_file; - allow_anonymous = db->config->security_options.allow_anonymous; - } - if(context->username){ - if(password_file != NULL){ - /* Client must authenticate below */ - }else{ - if(allow_anonymous == true){ - /* No password file, so treated as anonymous */ - return MOSQ_ERR_SUCCESS; - }else{ - return MOSQ_ERR_PLUGIN_DEFER; - } - } - }else{ - if(allow_anonymous){ - return MOSQ_ERR_SUCCESS; - }else{ - return MOSQ_ERR_PLUGIN_DEFER; - } } - HASH_FIND(hh, unpwd_ref, context->username, strlen(context->username), u); + HASH_FIND(hh, unpwd_ref, ed->client->username, strlen(ed->client->username), u); if(u){ if(u->password){ - if(context->password){ + if(ed->client->password){ #ifdef WITH_TLS - rc = pw__digest(context->password, u->salt, u->salt_len, hash, &hash_len, u->hashtype); + rc = pw__digest(ed->client->password, u->salt, u->salt_len, hash, &hash_len, u->hashtype); if(rc == MOSQ_ERR_SUCCESS){ if(hash_len == u->password_len && !mosquitto__memcmp_const(u->password, hash, hash_len)){ return MOSQ_ERR_SUCCESS; @@ -996,7 +1003,7 @@ int mosquitto_unpwd_check_default(struct mosquitto_db *db, struct mosquitto *con return rc; } #else - if(!strcmp(u->password, context->password)){ + if(!strcmp(u->password, ed->client->password)){ return MOSQ_ERR_SUCCESS; } #endif