| /* | 
| Copyright (c) 2011-2019 Roger Light <roger@atchoo.org> | 
|   | 
| All rights reserved. This program and the accompanying materials | 
| are made available under the terms of the Eclipse Public License v1.0 | 
| and Eclipse Distribution License v1.0 which accompany this distribution. | 
|   | 
| The Eclipse Public License is available at | 
|    http://www.eclipse.org/legal/epl-v10.html | 
| and the Eclipse Distribution License is available at | 
|   http://www.eclipse.org/org/documents/edl-v10.php. | 
|   | 
| Contributors: | 
|    Roger Light - initial implementation and documentation. | 
| */ | 
|   | 
| #include "config.h" | 
|   | 
| #include <stdio.h> | 
| #include <string.h> | 
|   | 
| #include "mosquitto_broker_internal.h" | 
| #include "memory_mosq.h" | 
| #include "util_mosq.h" | 
|   | 
| static int aclfile__parse(struct mosquitto_db *db, struct mosquitto__security_options *security_opts); | 
| static int unpwd__file_parse(struct mosquitto__unpwd **unpwd, const char *password_file); | 
| static int acl__cleanup(struct mosquitto_db *db, bool reload); | 
| static int unpwd__cleanup(struct mosquitto__unpwd **unpwd, bool reload); | 
| static int psk__file_parse(struct mosquitto_db *db, struct mosquitto__unpwd **psk_id, const char *psk_file); | 
| #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); | 
| static int base64__decode(char *in, unsigned char **decoded, unsigned int *decoded_len); | 
| static int mosquitto__memcmp_const(const void *ptr1, const void *b, size_t len); | 
| #endif | 
|   | 
|   | 
|   | 
| int mosquitto_security_init_default(struct mosquitto_db *db, bool reload) | 
| { | 
|     int rc; | 
|     int i; | 
|     char *pwf; | 
|     char *pskf; | 
|   | 
|     UNUSED(reload); | 
|   | 
|     /* Load username/password data if required. */ | 
|     if(db->config->per_listener_settings){ | 
|         for(i=0; i<db->config->listener_count; i++){ | 
|             pwf = db->config->listeners[i].security_options.password_file; | 
|             if(pwf){ | 
|                 rc = unpwd__file_parse(&db->config->listeners[i].unpwd, pwf); | 
|                 if(rc){ | 
|                     log__printf(NULL, MOSQ_LOG_ERR, "Error opening password file \"%s\".", pwf); | 
|                     return rc; | 
|                 } | 
|             } | 
|         } | 
|     }else{ | 
|         if(db->config->security_options.password_file){ | 
|             pwf = db->config->security_options.password_file; | 
|             if(pwf){ | 
|                 rc = unpwd__file_parse(&db->unpwd, pwf); | 
|                 if(rc){ | 
|                     log__printf(NULL, MOSQ_LOG_ERR, "Error opening password file \"%s\".", pwf); | 
|                     return rc; | 
|                 } | 
|             } | 
|         } | 
|     } | 
|   | 
|     /* Load acl data if required. */ | 
|     if(db->config->per_listener_settings){ | 
|         for(i=0; i<db->config->listener_count; i++){ | 
|             if(db->config->listeners[i].security_options.acl_file){ | 
|                 rc = aclfile__parse(db, &db->config->listeners[i].security_options); | 
|                 if(rc){ | 
|                     log__printf(NULL, MOSQ_LOG_ERR, "Error opening acl file \"%s\".", db->config->listeners[i].security_options.acl_file); | 
|                     return rc; | 
|                 } | 
|             } | 
|         } | 
|     }else{ | 
|         if(db->config->security_options.acl_file){ | 
|             rc = aclfile__parse(db, &db->config->security_options); | 
|             if(rc){ | 
|                 log__printf(NULL, MOSQ_LOG_ERR, "Error opening acl file \"%s\".", db->config->security_options.acl_file); | 
|                 return rc; | 
|             } | 
|         } | 
|     } | 
|   | 
|     /* Load psk data if required. */ | 
|     if(db->config->per_listener_settings){ | 
|         for(i=0; i<db->config->listener_count; i++){ | 
|             pskf = db->config->listeners[i].security_options.psk_file; | 
|             if(pskf){ | 
|                 rc = psk__file_parse(db, &db->config->listeners[i].psk_id, pskf); | 
|                 if(rc){ | 
|                     log__printf(NULL, MOSQ_LOG_ERR, "Error opening psk file \"%s\".", pskf); | 
|                     return rc; | 
|                 } | 
|             } | 
|         } | 
|     }else{ | 
|         char *pskf = db->config->security_options.psk_file; | 
|         if(pskf){ | 
|             rc = psk__file_parse(db, &db->psk_id, pskf); | 
|             if(rc){ | 
|                 log__printf(NULL, MOSQ_LOG_ERR, "Error opening psk file \"%s\".", pskf); | 
|                 return rc; | 
|             } | 
|         } | 
|     } | 
|   | 
|     return MOSQ_ERR_SUCCESS; | 
| } | 
|   | 
| int mosquitto_security_cleanup_default(struct mosquitto_db *db, bool reload) | 
| { | 
|     int rc; | 
|     int i; | 
|   | 
|     rc = acl__cleanup(db, reload); | 
|     if(rc != MOSQ_ERR_SUCCESS) return rc; | 
|   | 
|     rc = unpwd__cleanup(&db->unpwd, reload); | 
|     if(rc != MOSQ_ERR_SUCCESS) return rc; | 
|   | 
|     for(i=0; i<db->config->listener_count; i++){ | 
|         if(db->config->listeners[i].unpwd){ | 
|             rc = unpwd__cleanup(&db->config->listeners[i].unpwd, reload); | 
|             if(rc != MOSQ_ERR_SUCCESS) return rc; | 
|         } | 
|     } | 
|   | 
|     rc = unpwd__cleanup(&db->psk_id, reload); | 
|     if(rc != MOSQ_ERR_SUCCESS) return rc; | 
|   | 
|     for(i=0; i<db->config->listener_count; i++){ | 
|         if(db->config->listeners[i].psk_id){ | 
|             rc = unpwd__cleanup(&db->config->listeners[i].psk_id, reload); | 
|             if(rc != MOSQ_ERR_SUCCESS) return rc; | 
|         } | 
|     } | 
|   | 
|     return MOSQ_ERR_SUCCESS; | 
| } | 
|   | 
|   | 
| int add__acl(struct mosquitto__security_options *security_opts, const char *user, const char *topic, int access) | 
| { | 
|     struct mosquitto__acl_user *acl_user=NULL, *user_tail; | 
|     struct mosquitto__acl *acl, *acl_tail; | 
|     char *local_topic; | 
|     bool new_user = false; | 
|   | 
|     if(!security_opts || !topic) return MOSQ_ERR_INVAL; | 
|   | 
|     local_topic = mosquitto__strdup(topic); | 
|     if(!local_topic){ | 
|         return MOSQ_ERR_NOMEM; | 
|     } | 
|   | 
|     if(security_opts->acl_list){ | 
|         user_tail = security_opts->acl_list; | 
|         while(user_tail){ | 
|             if(user == NULL){ | 
|                 if(user_tail->username == NULL){ | 
|                     acl_user = user_tail; | 
|                     break; | 
|                 } | 
|             }else if(user_tail->username && !strcmp(user_tail->username, user)){ | 
|                 acl_user = user_tail; | 
|                 break; | 
|             } | 
|             user_tail = user_tail->next; | 
|         } | 
|     } | 
|     if(!acl_user){ | 
|         acl_user = mosquitto__malloc(sizeof(struct mosquitto__acl_user)); | 
|         if(!acl_user){ | 
|             mosquitto__free(local_topic); | 
|             return MOSQ_ERR_NOMEM; | 
|         } | 
|         new_user = true; | 
|         if(user){ | 
|             acl_user->username = mosquitto__strdup(user); | 
|             if(!acl_user->username){ | 
|                 mosquitto__free(local_topic); | 
|                 mosquitto__free(acl_user); | 
|                 return MOSQ_ERR_NOMEM; | 
|             } | 
|         }else{ | 
|             acl_user->username = NULL; | 
|         } | 
|         acl_user->next = NULL; | 
|         acl_user->acl = NULL; | 
|     } | 
|   | 
|     acl = mosquitto__malloc(sizeof(struct mosquitto__acl)); | 
|     if(!acl){ | 
|         mosquitto__free(local_topic); | 
|         mosquitto__free(acl_user->username); | 
|         mosquitto__free(acl_user); | 
|         return MOSQ_ERR_NOMEM; | 
|     } | 
|     acl->access = access; | 
|     acl->topic = local_topic; | 
|     acl->next = NULL; | 
|     acl->ccount = 0; | 
|     acl->ucount = 0; | 
|   | 
|     /* Add acl to user acl list */ | 
|     if(acl_user->acl){ | 
|         acl_tail = acl_user->acl; | 
|         while(acl_tail->next){ | 
|             acl_tail = acl_tail->next; | 
|         } | 
|         acl_tail->next = acl; | 
|     }else{ | 
|         acl_user->acl = acl; | 
|     } | 
|   | 
|     if(new_user){ | 
|         /* Add to end of list */ | 
|         if(security_opts->acl_list){ | 
|             user_tail = security_opts->acl_list; | 
|             while(user_tail->next){ | 
|                 user_tail = user_tail->next; | 
|             } | 
|             user_tail->next = acl_user; | 
|         }else{ | 
|             security_opts->acl_list = acl_user; | 
|         } | 
|     } | 
|   | 
|     return MOSQ_ERR_SUCCESS; | 
| } | 
|   | 
| int add__acl_pattern(struct mosquitto__security_options *security_opts, const char *topic, int access) | 
| { | 
|     struct mosquitto__acl *acl, *acl_tail; | 
|     char *local_topic; | 
|     char *s; | 
|   | 
|     if(!security_opts| !topic) return MOSQ_ERR_INVAL; | 
|   | 
|     local_topic = mosquitto__strdup(topic); | 
|     if(!local_topic){ | 
|         return MOSQ_ERR_NOMEM; | 
|     } | 
|   | 
|     acl = mosquitto__malloc(sizeof(struct mosquitto__acl)); | 
|     if(!acl){ | 
|         mosquitto__free(local_topic); | 
|         return MOSQ_ERR_NOMEM; | 
|     } | 
|     acl->access = access; | 
|     acl->topic = local_topic; | 
|     acl->next = NULL; | 
|   | 
|     acl->ccount = 0; | 
|     s = local_topic; | 
|     while(s){ | 
|         s = strstr(s, "%c"); | 
|         if(s){ | 
|             acl->ccount++; | 
|             s+=2; | 
|         } | 
|     } | 
|   | 
|     acl->ucount = 0; | 
|     s = local_topic; | 
|     while(s){ | 
|         s = strstr(s, "%u"); | 
|         if(s){ | 
|             acl->ucount++; | 
|             s+=2; | 
|         } | 
|     } | 
|   | 
|     if(acl->ccount == 0 && acl->ucount == 0){ | 
|         log__printf(NULL, MOSQ_LOG_WARNING, | 
|                 "Warning: ACL pattern '%s' does not contain '%%c' or '%%u'.", | 
|                 topic); | 
|     } | 
|   | 
|     if(security_opts->acl_patterns){ | 
|         acl_tail = security_opts->acl_patterns; | 
|         while(acl_tail->next){ | 
|             acl_tail = acl_tail->next; | 
|         } | 
|         acl_tail->next = acl; | 
|     }else{ | 
|         security_opts->acl_patterns = acl; | 
|     } | 
|   | 
|     return MOSQ_ERR_SUCCESS; | 
| } | 
|   | 
| int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *context, const char *topic, int access) | 
| { | 
|     char *local_acl; | 
|     struct mosquitto__acl *acl_root; | 
|     bool result; | 
|     int i; | 
|     int len, tlen, clen, ulen; | 
|     char *s; | 
|     struct mosquitto__security_options *security_opts = NULL; | 
|   | 
|     if(!db || !context || !topic) return MOSQ_ERR_INVAL; | 
|     if(context->bridge) return MOSQ_ERR_SUCCESS; | 
|   | 
|     if(db->config->per_listener_settings){ | 
|         if(!context->listener) return MOSQ_ERR_ACL_DENIED; | 
|         security_opts = &context->listener->security_options; | 
|     }else{ | 
|         security_opts = &db->config->security_options; | 
|     } | 
|     if(!security_opts->acl_file && !security_opts->acl_list && !security_opts->acl_patterns){ | 
|             return MOSQ_ERR_PLUGIN_DEFER; | 
|     } | 
|   | 
|     if(access == MOSQ_ACL_SUBSCRIBE) return MOSQ_ERR_SUCCESS; /* FIXME - implement ACL subscription strings. */ | 
|     if(!context->acl_list && !security_opts->acl_patterns) return MOSQ_ERR_ACL_DENIED; | 
|   | 
|     if(context->acl_list){ | 
|         acl_root = context->acl_list->acl; | 
|     }else{ | 
|         acl_root = NULL; | 
|     } | 
|   | 
|     /* Loop through all ACLs for this client. */ | 
|     while(acl_root){ | 
|         /* 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] != '$'){ | 
|             acl_root = acl_root->next; | 
|             continue; | 
|         } | 
|         mosquitto_topic_matches_sub(acl_root->topic, topic, &result); | 
|         if(result){ | 
|             if(access & acl_root->access){ | 
|                 /* And access is allowed. */ | 
|                 return MOSQ_ERR_SUCCESS; | 
|             } | 
|         } | 
|         acl_root = acl_root->next; | 
|     } | 
|   | 
|     acl_root = security_opts->acl_patterns; | 
|   | 
|     if(acl_root){ | 
|         /* We are using pattern based acls. Check whether the username or | 
|          * client id contains a + or # and if so deny access. | 
|          * | 
|          * Without this, a malicious client may configure its username/client | 
|          * 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); | 
|             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); | 
|             return MOSQ_ERR_ACL_DENIED; | 
|         } | 
|     } | 
|   | 
|     /* Loop through all pattern ACLs. */ | 
|     if(!context->id) return MOSQ_ERR_ACL_DENIED; | 
|     clen = strlen(context->id); | 
|   | 
|     while(acl_root){ | 
|         tlen = strlen(acl_root->topic); | 
|   | 
|         if(acl_root->ucount && !context->username){ | 
|             acl_root = acl_root->next; | 
|             continue; | 
|         } | 
|   | 
|         if(context->username){ | 
|             ulen = strlen(context->username); | 
|             len = tlen + acl_root->ccount*(clen-2) + acl_root->ucount*(ulen-2); | 
|         }else{ | 
|             ulen = 0; | 
|             len = tlen + acl_root->ccount*(clen-2); | 
|         } | 
|         local_acl = mosquitto__malloc(len+1); | 
|         if(!local_acl) return 1; // FIXME | 
|         s = local_acl; | 
|         for(i=0; i<tlen; i++){ | 
|             if(i<tlen-1 && acl_root->topic[i] == '%'){ | 
|                 if(acl_root->topic[i+1] == 'c'){ | 
|                     i++; | 
|                     strncpy(s, context->id, clen); | 
|                     s+=clen; | 
|                     continue; | 
|                 }else if(context->username && acl_root->topic[i+1] == 'u'){ | 
|                     i++; | 
|                     strncpy(s, context->username, ulen); | 
|                     s+=ulen; | 
|                     continue; | 
|                 } | 
|             } | 
|             s[0] = acl_root->topic[i]; | 
|             s++; | 
|         } | 
|         local_acl[len] = '\0'; | 
|   | 
|         mosquitto_topic_matches_sub(local_acl, topic, &result); | 
|         mosquitto__free(local_acl); | 
|         if(result){ | 
|             if(access & acl_root->access){ | 
|                 /* And access is allowed. */ | 
|                 return MOSQ_ERR_SUCCESS; | 
|             } | 
|         } | 
|   | 
|         acl_root = acl_root->next; | 
|     } | 
|   | 
|     return MOSQ_ERR_ACL_DENIED; | 
| } | 
|   | 
|   | 
| static int aclfile__parse(struct mosquitto_db *db, struct mosquitto__security_options *security_opts) | 
| { | 
|     FILE *aclfptr; | 
|     char buf[1024]; | 
|     char *token; | 
|     char *user = NULL; | 
|     char *topic; | 
|     char *access_s; | 
|     int access; | 
|     int rc; | 
|     int slen; | 
|     int topic_pattern; | 
|     char *saveptr = NULL; | 
|   | 
|     if(!db || !db->config) return MOSQ_ERR_INVAL; | 
|     if(!security_opts) return MOSQ_ERR_INVAL; | 
|     if(!security_opts->acl_file) return MOSQ_ERR_SUCCESS; | 
|   | 
|     aclfptr = mosquitto__fopen(security_opts->acl_file, "rt", false); | 
|     if(!aclfptr){ | 
|         log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to open acl_file \"%s\".", security_opts->acl_file); | 
|         return 1; | 
|     } | 
|   | 
|     // topic [read|write] <topic>  | 
|     // user <user> | 
|   | 
|     while(fgets(buf, 1024, aclfptr)){ | 
|         slen = strlen(buf); | 
|         while(slen > 0 && (buf[slen-1] == 10 || buf[slen-1] == 13)){ | 
|             buf[slen-1] = '\0'; | 
|             slen = strlen(buf); | 
|         } | 
|         if(buf[0] == '#'){ | 
|             continue; | 
|         } | 
|         token = strtok_r(buf, " ", &saveptr); | 
|         if(token){ | 
|             if(!strcmp(token, "topic") || !strcmp(token, "pattern")){ | 
|                 if(!strcmp(token, "topic")){ | 
|                     topic_pattern = 0; | 
|                 }else{ | 
|                     topic_pattern = 1; | 
|                 } | 
|   | 
|                 access_s = strtok_r(NULL, " ", &saveptr); | 
|                 if(!access_s){ | 
|                     log__printf(NULL, MOSQ_LOG_ERR, "Error: Empty topic in acl_file \"%s\".", security_opts->acl_file); | 
|                     mosquitto__free(user); | 
|                     fclose(aclfptr); | 
|                     return MOSQ_ERR_INVAL; | 
|                 } | 
|                 token = strtok_r(NULL, "", &saveptr); | 
|                 if(token){ | 
|                     topic = token; | 
|                     /* Ignore duplicate spaces */ | 
|                     while(topic[0] == ' '){ | 
|                         topic++; | 
|                     } | 
|                 }else{ | 
|                     topic = access_s; | 
|                     access_s = NULL; | 
|                 } | 
|                 if(access_s){ | 
|                     if(!strcmp(access_s, "read")){ | 
|                         access = MOSQ_ACL_READ; | 
|                     }else if(!strcmp(access_s, "write")){ | 
|                         access = MOSQ_ACL_WRITE; | 
|                     }else if(!strcmp(access_s, "readwrite")){ | 
|                         access = MOSQ_ACL_READ | MOSQ_ACL_WRITE; | 
|                     }else{ | 
|                         log__printf(NULL, MOSQ_LOG_ERR, "Error: Invalid topic access type \"%s\" in acl_file \"%s\".", access_s, security_opts->acl_file); | 
|                         mosquitto__free(user); | 
|                         fclose(aclfptr); | 
|                         return MOSQ_ERR_INVAL; | 
|                     } | 
|                 }else{ | 
|                     access = MOSQ_ACL_READ | MOSQ_ACL_WRITE; | 
|                 } | 
|                 if(topic_pattern == 0){ | 
|                     rc = add__acl(security_opts, user, topic, access); | 
|                 }else{ | 
|                     rc = add__acl_pattern(security_opts, topic, access); | 
|                 } | 
|                 if(rc){ | 
|                     mosquitto__free(user); | 
|                     fclose(aclfptr); | 
|                     return rc; | 
|                 } | 
|             }else if(!strcmp(token, "user")){ | 
|                 token = strtok_r(NULL, "", &saveptr); | 
|                 if(token){ | 
|                     /* Ignore duplicate spaces */ | 
|                     while(token[0] == ' '){ | 
|                         token++; | 
|                     } | 
|                     mosquitto__free(user); | 
|                     user = mosquitto__strdup(token); | 
|                     if(!user){ | 
|                         fclose(aclfptr); | 
|                         return MOSQ_ERR_NOMEM; | 
|                     } | 
|                 }else{ | 
|                     log__printf(NULL, MOSQ_LOG_ERR, "Error: Missing username in acl_file \"%s\".", security_opts->acl_file); | 
|                     mosquitto__free(user); | 
|                     fclose(aclfptr); | 
|                     return 1; | 
|                 } | 
|             }else{ | 
|                 log__printf(NULL, MOSQ_LOG_ERR, "Error: Invalid line in acl_file \"%s\": %s.", security_opts->acl_file, buf); | 
|                 fclose(aclfptr); | 
|                 return 1; | 
|             } | 
|         } | 
|     } | 
|   | 
|     mosquitto__free(user); | 
|     fclose(aclfptr); | 
|   | 
|     return MOSQ_ERR_SUCCESS; | 
| } | 
|   | 
| static void free__acl(struct mosquitto__acl *acl) | 
| { | 
|     if(!acl) return; | 
|   | 
|     if(acl->next){ | 
|         free__acl(acl->next); | 
|     } | 
|     mosquitto__free(acl->topic); | 
|     mosquitto__free(acl); | 
| } | 
|   | 
|   | 
| static void acl__cleanup_single(struct mosquitto__security_options *security_opts) | 
| { | 
|     struct mosquitto__acl_user *user_tail; | 
|   | 
|     while(security_opts->acl_list){ | 
|         user_tail = security_opts->acl_list->next; | 
|   | 
|         free__acl(security_opts->acl_list->acl); | 
|         mosquitto__free(security_opts->acl_list->username); | 
|         mosquitto__free(security_opts->acl_list); | 
|   | 
|         security_opts->acl_list = user_tail; | 
|     } | 
|   | 
|     if(security_opts->acl_patterns){ | 
|         free__acl(security_opts->acl_patterns); | 
|         security_opts->acl_patterns = NULL; | 
|     } | 
| } | 
|   | 
|   | 
| static int acl__cleanup(struct mosquitto_db *db, bool reload) | 
| { | 
|     struct mosquitto *context, *ctxt_tmp; | 
|     int i; | 
|   | 
|     UNUSED(reload); | 
|   | 
|     if(!db) return MOSQ_ERR_INVAL; | 
|   | 
|     /* As we're freeing ACLs, we must clear context->acl_list to ensure no | 
|      * invalid memory accesses take place later. | 
|      * This *requires* the ACLs to be reapplied after acl__cleanup() | 
|      * is called if we are reloading the config. If this is not done, all  | 
|      * access will be denied to currently connected clients. | 
|      */ | 
|     HASH_ITER(hh_id, db->contexts_by_id, context, ctxt_tmp){ | 
|         context->acl_list = NULL; | 
|     } | 
|   | 
|     if(db->config->per_listener_settings){ | 
|         for(i=0; i<db->config->listener_count; i++){ | 
|             acl__cleanup_single(&db->config->listeners[i].security_options); | 
|         } | 
|     }else{ | 
|         acl__cleanup_single(&db->config->security_options); | 
|     } | 
|   | 
|     return MOSQ_ERR_SUCCESS; | 
| } | 
|   | 
|   | 
| int acl__find_acls(struct mosquitto_db *db, struct mosquitto *context) | 
| { | 
|     struct mosquitto__acl_user *acl_tail; | 
|     struct mosquitto__security_options *security_opts; | 
|   | 
|     /* Associate user with its ACL, assuming we have ACLs loaded. */ | 
|     if(db->config->per_listener_settings){ | 
|         if(!context->listener){ | 
|             return MOSQ_ERR_INVAL; | 
|         } | 
|         security_opts = &context->listener->security_options; | 
|     }else{ | 
|         security_opts = &db->config->security_options; | 
|     } | 
|   | 
|     if(security_opts->acl_list){ | 
|         acl_tail = security_opts->acl_list; | 
|         while(acl_tail){ | 
|             if(context->username){ | 
|                 if(acl_tail->username && !strcmp(context->username, acl_tail->username)){ | 
|                     context->acl_list = acl_tail; | 
|                     break; | 
|                 } | 
|             }else{ | 
|                 if(acl_tail->username == NULL){ | 
|                     context->acl_list = acl_tail; | 
|                     break; | 
|                 } | 
|             } | 
|             acl_tail = acl_tail->next; | 
|         } | 
|     }else{ | 
|         context->acl_list = NULL; | 
|     } | 
|   | 
|     return MOSQ_ERR_SUCCESS; | 
| } | 
|   | 
|   | 
| static int pwfile__parse(const char *file, struct mosquitto__unpwd **root) | 
| { | 
|     FILE *pwfile; | 
|     struct mosquitto__unpwd *unpwd; | 
|     char buf[256]; | 
|     char *username, *password; | 
|     int len; | 
|     char *saveptr = NULL; | 
|   | 
|     pwfile = mosquitto__fopen(file, "rt", false); | 
|     if(!pwfile){ | 
|         log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to open pwfile \"%s\".", file); | 
|         return 1; | 
|     } | 
|   | 
|     while(!feof(pwfile)){ | 
|         if(fgets(buf, 256, pwfile)){ | 
|             if(buf[0] == '#') continue; | 
|             if(!strchr(buf, ':')) continue; | 
|   | 
|             username = strtok_r(buf, ":", &saveptr); | 
|             if(username){ | 
|                 unpwd = mosquitto__calloc(1, sizeof(struct mosquitto__unpwd)); | 
|                 if(!unpwd){ | 
|                     fclose(pwfile); | 
|                     return MOSQ_ERR_NOMEM; | 
|                 } | 
|                 unpwd->username = mosquitto__strdup(username); | 
|                 if(!unpwd->username){ | 
|                     mosquitto__free(unpwd); | 
|                     fclose(pwfile); | 
|                     return MOSQ_ERR_NOMEM; | 
|                 } | 
|                 len = strlen(unpwd->username); | 
|                 while(unpwd->username[len-1] == 10 || unpwd->username[len-1] == 13){ | 
|                     unpwd->username[len-1] = '\0'; | 
|                     len = strlen(unpwd->username); | 
|                 } | 
|                 password = strtok_r(NULL, ":", &saveptr); | 
|                 if(password){ | 
|                     unpwd->password = mosquitto__strdup(password); | 
|                     if(!unpwd->password){ | 
|                         fclose(pwfile); | 
|                         mosquitto__free(unpwd->username); | 
|                         mosquitto__free(unpwd); | 
|                         return MOSQ_ERR_NOMEM; | 
|                     } | 
|                     len = strlen(unpwd->password); | 
|                     while(len && (unpwd->password[len-1] == 10 || unpwd->password[len-1] == 13)){ | 
|                         unpwd->password[len-1] = '\0'; | 
|                         len = strlen(unpwd->password); | 
|                     } | 
|   | 
|                     HASH_ADD_KEYPTR(hh, *root, unpwd->username, strlen(unpwd->username), unpwd); | 
|                 }else{ | 
|                     log__printf(NULL, MOSQ_LOG_NOTICE, "Warning: Invalid line in password file '%s': %s", file, buf); | 
|                     mosquitto__free(unpwd->username); | 
|                     mosquitto__free(unpwd); | 
|                 } | 
|             } | 
|         } | 
|     } | 
|     fclose(pwfile); | 
|   | 
|     return MOSQ_ERR_SUCCESS; | 
| } | 
|   | 
|   | 
| #ifdef WITH_TLS | 
|   | 
| static void unpwd__free_item(struct mosquitto__unpwd **unpwd, struct mosquitto__unpwd *item) | 
| { | 
|     mosquitto__free(item->username); | 
|     mosquitto__free(item->password); | 
|     mosquitto__free(item->salt); | 
|     HASH_DEL(*unpwd, item); | 
|     mosquitto__free(item); | 
| } | 
|   | 
|   | 
| static int unpwd__decode_passwords(struct mosquitto__unpwd **unpwd) | 
| { | 
|     struct mosquitto__unpwd *u, *tmp; | 
|     char *token; | 
|     unsigned char *salt; | 
|     unsigned int salt_len; | 
|     unsigned char *password; | 
|     unsigned int password_len; | 
|     int rc; | 
|   | 
|     HASH_ITER(hh, *unpwd, u, tmp){ | 
|         /* Need to decode password into hashed data + salt. */ | 
|         if(u->password){ | 
|             token = strtok(u->password, "$"); | 
|             if(token && !strcmp(token, "6")){ | 
|                 token = strtok(NULL, "$"); | 
|                 if(token){ | 
|                     rc = base64__decode(token, &salt, &salt_len); | 
|                     if(rc == MOSQ_ERR_SUCCESS && salt_len == 12){ | 
|                         u->salt = salt; | 
|                         u->salt_len = salt_len; | 
|                         token = strtok(NULL, "$"); | 
|                         if(token){ | 
|                             rc = base64__decode(token, &password, &password_len); | 
|                             if(rc == MOSQ_ERR_SUCCESS && password_len == 64){ | 
|                                 mosquitto__free(u->password); | 
|                                 u->password = (char *)password; | 
|                                 u->password_len = password_len; | 
|                             }else{ | 
|                                 log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to decode password for user %s, removing entry.", u->username); | 
|                                 unpwd__free_item(unpwd, u); | 
|                             } | 
|                         }else{ | 
|                             log__printf(NULL, MOSQ_LOG_ERR, "Error: Invalid password hash for user %s, removing entry.", u->username); | 
|                             unpwd__free_item(unpwd, u); | 
|                         } | 
|                     }else{ | 
|                         log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to decode password salt for user %s, removing entry.", u->username); | 
|                         unpwd__free_item(unpwd, u); | 
|                     } | 
|                 }else{ | 
|                     log__printf(NULL, MOSQ_LOG_ERR, "Error: Invalid password hash for user %s, removing entry.", u->username); | 
|                     unpwd__free_item(unpwd, u); | 
|                 } | 
|             }else{ | 
|                 log__printf(NULL, MOSQ_LOG_ERR, "Error: Invalid password hash for user %s, removing entry.", u->username); | 
|                 unpwd__free_item(unpwd, u); | 
|             } | 
|         }else{ | 
|             log__printf(NULL, MOSQ_LOG_ERR, "Error: Missing password hash for user %s, removing entry.", u->username); | 
|             unpwd__free_item(unpwd, u); | 
|         } | 
|     } | 
|   | 
|     return MOSQ_ERR_SUCCESS; | 
| } | 
| #endif | 
|   | 
|   | 
| static int unpwd__file_parse(struct mosquitto__unpwd **unpwd, const char *password_file) | 
| { | 
|     int rc; | 
|     if(!unpwd) return MOSQ_ERR_INVAL; | 
|   | 
|     if(!password_file) return MOSQ_ERR_SUCCESS; | 
|   | 
|     rc = pwfile__parse(password_file, unpwd); | 
|   | 
| #ifdef WITH_TLS | 
|     if(rc) return rc; | 
|     rc = unpwd__decode_passwords(unpwd); | 
| #endif | 
|   | 
|     return rc; | 
| } | 
|   | 
| static int psk__file_parse(struct mosquitto_db *db, struct mosquitto__unpwd **psk_id, const char *psk_file) | 
| { | 
|     int rc; | 
|     struct mosquitto__unpwd *u, *tmp; | 
|   | 
|     if(!db || !db->config || !psk_id) return MOSQ_ERR_INVAL; | 
|   | 
|     /* We haven't been asked to parse a psk file. */ | 
|     if(!psk_file) return MOSQ_ERR_SUCCESS; | 
|   | 
|     rc = pwfile__parse(psk_file, psk_id); | 
|     if(rc) return rc; | 
|   | 
|     HASH_ITER(hh, (*psk_id), u, tmp){ | 
|         /* Check for hex only digits */ | 
|         if(!u->password){ | 
|             log__printf(NULL, MOSQ_LOG_ERR, "Error: Empty psk for identity \"%s\".", u->username); | 
|             return MOSQ_ERR_INVAL; | 
|         } | 
|         if(strspn(u->password, "0123456789abcdefABCDEF") < strlen(u->password)){ | 
|             log__printf(NULL, MOSQ_LOG_ERR, "Error: psk for identity \"%s\" contains non-hexadecimal characters.", u->username); | 
|             return MOSQ_ERR_INVAL; | 
|         } | 
|     } | 
|     return MOSQ_ERR_SUCCESS; | 
| } | 
|   | 
|   | 
| #ifdef WITH_TLS | 
| static int mosquitto__memcmp_const(const void *a, const void *b, size_t len) | 
| { | 
|     size_t i; | 
|     int rc = 0; | 
|   | 
|     if(!a || !b) return 1; | 
|   | 
|     for(i=0; i<len; i++){ | 
|         if( ((char *)a)[i] != ((char *)b)[i] ){ | 
|             rc = 1; | 
|         } | 
|     } | 
|     return rc; | 
| } | 
| #endif | 
|   | 
|   | 
| int mosquitto_unpwd_check_default(struct mosquitto_db *db, struct mosquitto *context, const char *username, const char *password) | 
| { | 
|     struct mosquitto__unpwd *u, *tmp; | 
|     struct mosquitto__unpwd *unpwd_ref; | 
| #ifdef WITH_TLS | 
|     unsigned char hash[EVP_MAX_MD_SIZE]; | 
|     unsigned int hash_len; | 
|     int rc; | 
| #endif | 
|   | 
|     if(!db) return MOSQ_ERR_INVAL; | 
|   | 
|     if(db->config->per_listener_settings){ | 
|         if(context->bridge) return MOSQ_ERR_SUCCESS; | 
|         if(!context->listener) return MOSQ_ERR_INVAL; | 
|         if(!context->listener->unpwd) return MOSQ_ERR_PLUGIN_DEFER; | 
|         unpwd_ref = context->listener->unpwd; | 
|     }else{ | 
|         if(!db->unpwd) return MOSQ_ERR_PLUGIN_DEFER; | 
|         unpwd_ref = db->unpwd; | 
|     } | 
|     if(!username){ | 
|         /* Check must be made only after checking unpwd_ref. | 
|          * This is DENY here, because in MQTT v5 username can be missing when | 
|          * password is present, but we don't support that. */ | 
|         return MOSQ_ERR_AUTH; | 
|     } | 
|   | 
|     HASH_ITER(hh, unpwd_ref, u, tmp){ | 
|         if(!strcmp(u->username, username)){ | 
|             if(u->password){ | 
|                 if(password){ | 
| #ifdef WITH_TLS | 
|                     rc = pw__digest(password, u->salt, u->salt_len, hash, &hash_len); | 
|                     if(rc == MOSQ_ERR_SUCCESS){ | 
|                         if(hash_len == u->password_len && !mosquitto__memcmp_const(u->password, hash, hash_len)){ | 
|                             return MOSQ_ERR_SUCCESS; | 
|                         }else{ | 
|                             return MOSQ_ERR_AUTH; | 
|                         } | 
|                     }else{ | 
|                         return rc; | 
|                     } | 
| #else | 
|                     if(!strcmp(u->password, password)){ | 
|                         return MOSQ_ERR_SUCCESS; | 
|                     } | 
| #endif | 
|                 }else{ | 
|                     return MOSQ_ERR_AUTH; | 
|                 } | 
|             }else{ | 
|                 return MOSQ_ERR_SUCCESS; | 
|             } | 
|         } | 
|     } | 
|   | 
|     return MOSQ_ERR_AUTH; | 
| } | 
|   | 
| static int unpwd__cleanup(struct mosquitto__unpwd **root, bool reload) | 
| { | 
|     struct mosquitto__unpwd *u, *tmp; | 
|   | 
|     UNUSED(reload); | 
|   | 
|     if(!root) return MOSQ_ERR_INVAL; | 
|   | 
|     HASH_ITER(hh, *root, u, tmp){ | 
|         HASH_DEL(*root, u); | 
|         mosquitto__free(u->password); | 
|         mosquitto__free(u->username); | 
| #ifdef WITH_TLS | 
|         mosquitto__free(u->salt); | 
| #endif | 
|         mosquitto__free(u); | 
|     } | 
|   | 
|     *root = NULL; | 
|   | 
|     return MOSQ_ERR_SUCCESS; | 
| } | 
|   | 
| /* Apply security settings after a reload. | 
|  * Includes: | 
|  * - Disconnecting anonymous users if appropriate | 
|  * - Disconnecting users with invalid passwords | 
|  * - Reapplying ACLs | 
|  */ | 
| int mosquitto_security_apply_default(struct mosquitto_db *db) | 
| { | 
|     struct mosquitto *context, *ctxt_tmp; | 
|     struct mosquitto__acl_user *acl_user_tail; | 
|     bool allow_anonymous; | 
|     struct mosquitto__security_options *security_opts = NULL; | 
|   | 
|     if(!db) return MOSQ_ERR_INVAL; | 
|   | 
|      | 
|     HASH_ITER(hh_id, db->contexts_by_id, context, ctxt_tmp){ | 
|         /* Check for anonymous clients when allow_anonymous is false */ | 
|         if(db->config->per_listener_settings){ | 
|             if(context->listener){ | 
|                 allow_anonymous = context->listener->security_options.allow_anonymous; | 
|             }else{ | 
|                 /* Client not currently connected, so defer judgement until it does connect */ | 
|                 allow_anonymous = true; | 
|             } | 
|         }else{ | 
|             allow_anonymous = db->config->security_options.allow_anonymous; | 
|         } | 
|   | 
|         if(!allow_anonymous && !context->username){ | 
|             context__set_state(context, mosq_cs_disconnecting); | 
|             do_disconnect(db, context, MOSQ_ERR_AUTH); | 
|             continue; | 
|         } | 
|         /* Check for connected clients that are no longer authorised */ | 
|         if(mosquitto_unpwd_check(db, context, context->username, context->password) != MOSQ_ERR_SUCCESS){ | 
|             context__set_state(context, mosq_cs_disconnecting); | 
|             do_disconnect(db, context, MOSQ_ERR_AUTH); | 
|             continue; | 
|         } | 
|         /* Check for ACLs and apply to user. */ | 
|         if(db->config->per_listener_settings){ | 
|             if(context->listener){ | 
|                 security_opts = &context->listener->security_options; | 
|             }else{ | 
|                 if(context->state != mosq_cs_connected){ | 
|                     context__set_state(context, mosq_cs_disconnecting); | 
|                     do_disconnect(db, context, MOSQ_ERR_AUTH); | 
|                     continue; | 
|                 } | 
|             } | 
|         }else{ | 
|             security_opts = &db->config->security_options; | 
|         } | 
|   | 
|         if(security_opts && security_opts->acl_list){ | 
|             acl_user_tail = security_opts->acl_list; | 
|             while(acl_user_tail){ | 
|                 if(acl_user_tail->username){ | 
|                     if(context->username){ | 
|                         if(!strcmp(acl_user_tail->username, context->username)){ | 
|                             context->acl_list = acl_user_tail; | 
|                             break; | 
|                         } | 
|                     } | 
|                 }else{ | 
|                     if(!context->username){ | 
|                         context->acl_list = acl_user_tail; | 
|                         break; | 
|                     } | 
|                 } | 
|                 acl_user_tail = acl_user_tail->next; | 
|             } | 
|         } | 
|     } | 
|     return MOSQ_ERR_SUCCESS; | 
| } | 
|   | 
| 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) | 
| { | 
|     struct mosquitto__unpwd *u, *tmp; | 
|     struct mosquitto__unpwd *psk_id_ref = NULL; | 
|   | 
|     if(!db || !hint || !identity || !key) return MOSQ_ERR_INVAL; | 
|   | 
|     if(db->config->per_listener_settings){ | 
|         if(!context->listener) return MOSQ_ERR_INVAL; | 
|         if(!context->listener->psk_id) return MOSQ_ERR_PLUGIN_DEFER; | 
|         psk_id_ref = context->listener->psk_id; | 
|     }else{ | 
|         if(!db->psk_id) return MOSQ_ERR_PLUGIN_DEFER; | 
|         psk_id_ref = db->psk_id; | 
|     } | 
|     if(!psk_id_ref) return MOSQ_ERR_PLUGIN_DEFER; | 
|   | 
|     HASH_ITER(hh, psk_id_ref, u, tmp){ | 
|         if(!strcmp(u->username, identity)){ | 
|             strncpy(key, u->password, max_key_len); | 
|             return MOSQ_ERR_SUCCESS; | 
|         } | 
|     } | 
|   | 
|     return MOSQ_ERR_AUTH; | 
| } | 
|   | 
| #ifdef WITH_TLS | 
| int pw__digest(const char *password, const unsigned char *salt, unsigned int salt_len, unsigned char *hash, unsigned int *hash_len) | 
| { | 
|     const EVP_MD *digest; | 
| #if OPENSSL_VERSION_NUMBER < 0x10100000L | 
|     EVP_MD_CTX context; | 
|   | 
|     digest = EVP_get_digestbyname("sha512"); | 
|     if(!digest){ | 
|         // FIXME fprintf(stderr, "Error: Unable to create openssl digest.\n"); | 
|         return 1; | 
|     } | 
|   | 
|     EVP_MD_CTX_init(&context); | 
|     EVP_DigestInit_ex(&context, digest, NULL); | 
|     EVP_DigestUpdate(&context, password, strlen(password)); | 
|     EVP_DigestUpdate(&context, salt, salt_len); | 
|     /* hash is assumed to be EVP_MAX_MD_SIZE bytes long. */ | 
|     EVP_DigestFinal_ex(&context, hash, hash_len); | 
|     EVP_MD_CTX_cleanup(&context); | 
| #else | 
|     EVP_MD_CTX *context; | 
|   | 
|     digest = EVP_get_digestbyname("sha512"); | 
|     if(!digest){ | 
|         // FIXME fprintf(stderr, "Error: Unable to create openssl digest.\n"); | 
|         return 1; | 
|     } | 
|   | 
|     context = EVP_MD_CTX_new(); | 
|     EVP_DigestInit_ex(context, digest, NULL); | 
|     EVP_DigestUpdate(context, password, strlen(password)); | 
|     EVP_DigestUpdate(context, salt, salt_len); | 
|     /* hash is assumed to be EVP_MAX_MD_SIZE bytes long. */ | 
|     EVP_DigestFinal_ex(context, hash, hash_len); | 
|     EVP_MD_CTX_free(context); | 
| #endif | 
|   | 
|     return MOSQ_ERR_SUCCESS; | 
| } | 
|   | 
| int base64__decode(char *in, unsigned char **decoded, unsigned int *decoded_len) | 
| { | 
|     BIO *bmem, *b64; | 
|     int slen; | 
|   | 
|     slen = strlen(in); | 
|   | 
|     b64 = BIO_new(BIO_f_base64()); | 
|     if(!b64){ | 
|         return 1; | 
|     } | 
|     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); | 
|   | 
|     bmem = BIO_new(BIO_s_mem()); | 
|     if(!bmem){ | 
|         BIO_free_all(b64); | 
|         return 1; | 
|     } | 
|     b64 = BIO_push(b64, bmem); | 
|     BIO_write(bmem, in, slen); | 
|   | 
|     if(BIO_flush(bmem) != 1){ | 
|         BIO_free_all(b64); | 
|         return 1; | 
|     } | 
|     *decoded = mosquitto__calloc(slen, 1); | 
|     if(!(*decoded)){ | 
|         BIO_free_all(b64); | 
|         return 1; | 
|     } | 
|     *decoded_len =  BIO_read(b64, *decoded, slen); | 
|     BIO_free_all(b64); | 
|   | 
|     if(*decoded_len <= 0){ | 
|         mosquitto__free(*decoded); | 
|         *decoded = NULL; | 
|         *decoded_len = 0; | 
|         return 1; | 
|     } | 
|   | 
|     return 0; | 
| } | 
|   | 
| #endif |