Commit b2c5fe90223bef9c7cdab7deb60dc64b6a2820eb

Authored by Wiebe Cazemier
1 parent 2ae1bb7e

Add support for Mosquitto's password file

Encrypted version only.
CMakeLists.txt
@@ -38,6 +38,7 @@ add_executable(FlashMQ @@ -38,6 +38,7 @@ add_executable(FlashMQ
38 scopedsocket.h 38 scopedsocket.h
39 bindaddr.h 39 bindaddr.h
40 oneinstancelock.h 40 oneinstancelock.h
  41 + evpencodectxmanager.h
41 42
42 mainapp.cpp 43 mainapp.cpp
43 main.cpp 44 main.cpp
@@ -65,6 +66,7 @@ add_executable(FlashMQ @@ -65,6 +66,7 @@ add_executable(FlashMQ
65 scopedsocket.cpp 66 scopedsocket.cpp
66 bindaddr.cpp 67 bindaddr.cpp
67 oneinstancelock.cpp 68 oneinstancelock.cpp
  69 + evpencodectxmanager.cpp
68 ) 70 )
69 71
70 target_link_libraries(FlashMQ pthread dl ssl crypto) 72 target_link_libraries(FlashMQ pthread dl ssl crypto)
authplugin.cpp
@@ -21,12 +21,15 @@ License along with FlashMQ. If not, see <https://www.gnu.org/licenses/>. @@ -21,12 +21,15 @@ License along with FlashMQ. If not, see <https://www.gnu.org/licenses/>.
21 #include <fcntl.h> 21 #include <fcntl.h>
22 #include <sstream> 22 #include <sstream>
23 #include <dlfcn.h> 23 #include <dlfcn.h>
  24 +#include <fstream>
  25 +#include "sys/stat.h"
24 26
25 #include "exceptions.h" 27 #include "exceptions.h"
26 #include "unscopedlock.h" 28 #include "unscopedlock.h"
  29 +#include "utils.h"
27 30
28 -std::mutex AuthPlugin::initMutex;  
29 -std::mutex AuthPlugin::authChecksMutex; 31 +std::mutex Authentication::initMutex;
  32 +std::mutex Authentication::authChecksMutex;
30 33
31 void mosquitto_log_printf(int level, const char *fmt, ...) 34 void mosquitto_log_printf(int level, const char *fmt, ...)
32 { 35 {
@@ -37,19 +40,39 @@ void mosquitto_log_printf(int level, const char *fmt, ...) @@ -37,19 +40,39 @@ void mosquitto_log_printf(int level, const char *fmt, ...)
37 va_end(valist); 40 va_end(valist);
38 } 41 }
39 42
  43 +MosquittoPasswordFileEntry::MosquittoPasswordFileEntry(const std::vector<char> &&salt, const std::vector<char> &&cryptedPassword) :
  44 + salt(salt),
  45 + cryptedPassword(cryptedPassword)
  46 +{
  47 +
  48 +}
40 49
41 -AuthPlugin::AuthPlugin(Settings &settings) :  
42 - settings(settings) 50 +
  51 +Authentication::Authentication(Settings &settings) :
  52 + settings(settings),
  53 + mosquittoPasswordFile(settings.mosquittoPasswordFile),
  54 + mosquittoDigestContext(EVP_MD_CTX_new())
43 { 55 {
44 logger = Logger::getInstance(); 56 logger = Logger::getInstance();
  57 +
  58 + if(!sha512)
  59 + {
  60 + throw std::runtime_error("Failed to initialize SHA512 for decoding auth entry");
  61 + }
  62 +
  63 + EVP_DigestInit_ex(mosquittoDigestContext, sha512, NULL);
  64 + memset(&mosquittoPasswordFileLastLoad, 0, sizeof(struct timespec));
45 } 65 }
46 66
47 -AuthPlugin::~AuthPlugin() 67 +Authentication::~Authentication()
48 { 68 {
49 cleanup(); 69 cleanup();
  70 +
  71 + if (mosquittoDigestContext)
  72 + EVP_MD_CTX_free(mosquittoDigestContext);
50 } 73 }
51 74
52 -void *AuthPlugin::loadSymbol(void *handle, const char *symbol) const 75 +void *Authentication::loadSymbol(void *handle, const char *symbol) const
53 { 76 {
54 void *r = dlsym(handle, symbol); 77 void *r = dlsym(handle, symbol);
55 78
@@ -62,7 +85,7 @@ void *AuthPlugin::loadSymbol(void *handle, const char *symbol) const @@ -62,7 +85,7 @@ void *AuthPlugin::loadSymbol(void *handle, const char *symbol) const
62 return r; 85 return r;
63 } 86 }
64 87
65 -void AuthPlugin::loadPlugin(const std::string &pathToSoFile) 88 +void Authentication::loadPlugin(const std::string &pathToSoFile)
66 { 89 {
67 if (pathToSoFile.empty()) 90 if (pathToSoFile.empty())
68 return; 91 return;
@@ -70,7 +93,7 @@ void AuthPlugin::loadPlugin(const std::string &amp;pathToSoFile) @@ -70,7 +93,7 @@ void AuthPlugin::loadPlugin(const std::string &amp;pathToSoFile)
70 logger->logf(LOG_NOTICE, "Loading auth plugin %s", pathToSoFile.c_str()); 93 logger->logf(LOG_NOTICE, "Loading auth plugin %s", pathToSoFile.c_str());
71 94
72 initialized = false; 95 initialized = false;
73 - wanted = true; 96 + useExternalPlugin = true;
74 97
75 if (access(pathToSoFile.c_str(), R_OK) != 0) 98 if (access(pathToSoFile.c_str(), R_OK) != 0)
76 { 99 {
@@ -105,9 +128,13 @@ void AuthPlugin::loadPlugin(const std::string &amp;pathToSoFile) @@ -105,9 +128,13 @@ void AuthPlugin::loadPlugin(const std::string &amp;pathToSoFile)
105 initialized = true; 128 initialized = true;
106 } 129 }
107 130
108 -void AuthPlugin::init() 131 +/**
  132 + * @brief AuthPlugin::init is like Mosquitto's init(), and is to allow the plugin to init memory. Plugins should not load
  133 + * their authentication data here. That's what securityInit() is for.
  134 + */
  135 +void Authentication::init()
109 { 136 {
110 - if (!wanted) 137 + if (!useExternalPlugin)
111 return; 138 return;
112 139
113 UnscopedLock lock(initMutex); 140 UnscopedLock lock(initMutex);
@@ -123,7 +150,7 @@ void AuthPlugin::init() @@ -123,7 +150,7 @@ void AuthPlugin::init()
123 throw FatalError("Error initialising auth plugin."); 150 throw FatalError("Error initialising auth plugin.");
124 } 151 }
125 152
126 -void AuthPlugin::cleanup() 153 +void Authentication::cleanup()
127 { 154 {
128 if (!cleanup_v2) 155 if (!cleanup_v2)
129 return; 156 return;
@@ -136,9 +163,13 @@ void AuthPlugin::cleanup() @@ -136,9 +163,13 @@ void AuthPlugin::cleanup()
136 logger->logf(LOG_ERR, "Error cleaning up auth plugin"); // Not doing exception, because we're shutting down anyway. 163 logger->logf(LOG_ERR, "Error cleaning up auth plugin"); // Not doing exception, because we're shutting down anyway.
137 } 164 }
138 165
139 -void AuthPlugin::securityInit(bool reloading) 166 +/**
  167 + * @brief AuthPlugin::securityInit initializes the security data, like loading users, ACL tables, etc.
  168 + * @param reloading
  169 + */
  170 +void Authentication::securityInit(bool reloading)
140 { 171 {
141 - if (!wanted) 172 + if (!useExternalPlugin)
142 return; 173 return;
143 174
144 UnscopedLock lock(initMutex); 175 UnscopedLock lock(initMutex);
@@ -157,9 +188,9 @@ void AuthPlugin::securityInit(bool reloading) @@ -157,9 +188,9 @@ void AuthPlugin::securityInit(bool reloading)
157 initialized = true; 188 initialized = true;
158 } 189 }
159 190
160 -void AuthPlugin::securityCleanup(bool reloading) 191 +void Authentication::securityCleanup(bool reloading)
161 { 192 {
162 - if (!wanted) 193 + if (!useExternalPlugin)
163 return; 194 return;
164 195
165 initialized = false; 196 initialized = false;
@@ -172,9 +203,9 @@ void AuthPlugin::securityCleanup(bool reloading) @@ -172,9 +203,9 @@ void AuthPlugin::securityCleanup(bool reloading)
172 } 203 }
173 } 204 }
174 205
175 -AuthResult AuthPlugin::aclCheck(const std::string &clientid, const std::string &username, const std::string &topic, AclAccess access) 206 +AuthResult Authentication::aclCheck(const std::string &clientid, const std::string &username, const std::string &topic, AclAccess access)
176 { 207 {
177 - if (!wanted) 208 + if (!useExternalPlugin)
178 return AuthResult::success; 209 return AuthResult::success;
179 210
180 if (!initialized) 211 if (!initialized)
@@ -198,14 +229,19 @@ AuthResult AuthPlugin::aclCheck(const std::string &amp;clientid, const std::string &amp; @@ -198,14 +229,19 @@ AuthResult AuthPlugin::aclCheck(const std::string &amp;clientid, const std::string &amp;
198 return result_; 229 return result_;
199 } 230 }
200 231
201 -AuthResult AuthPlugin::unPwdCheck(const std::string &username, const std::string &password) 232 +AuthResult Authentication::unPwdCheck(const std::string &username, const std::string &password)
202 { 233 {
203 - if (!wanted)  
204 - return AuthResult::success; 234 + AuthResult firstResult = unPwdCheckFromMosquittoPasswordFile(username, password);
  235 +
  236 + if (firstResult != AuthResult::success)
  237 + return firstResult;
  238 +
  239 + if (!useExternalPlugin)
  240 + return firstResult;
205 241
206 if (!initialized) 242 if (!initialized)
207 { 243 {
208 - logger->logf(LOG_ERR, "Username+password check wanted, but initialization failed. Can't perform check."); 244 + logger->logf(LOG_ERR, "Username+password check with plugin wanted, but initialization failed. Can't perform check.");
209 return AuthResult::error; 245 return AuthResult::error;
210 } 246 }
211 247
@@ -224,11 +260,126 @@ AuthResult AuthPlugin::unPwdCheck(const std::string &amp;username, const std::string @@ -224,11 +260,126 @@ AuthResult AuthPlugin::unPwdCheck(const std::string &amp;username, const std::string
224 return r; 260 return r;
225 } 261 }
226 262
227 -void AuthPlugin::setQuitting() 263 +void Authentication::setQuitting()
228 { 264 {
229 this->quitting = true; 265 this->quitting = true;
230 } 266 }
231 267
  268 +/**
  269 + * @brief Authentication::loadMosquittoPasswordFile is called once on startup, and on a frequent interval, and reloads the file if changed.
  270 + */
  271 +void Authentication::loadMosquittoPasswordFile()
  272 +{
  273 + if (this->mosquittoPasswordFile.empty())
  274 + return;
  275 +
  276 + if (access(this->mosquittoPasswordFile.c_str(), R_OK) != 0)
  277 + {
  278 + logger->logf(LOG_ERR, "Passwd file '%s' is not there or not readable.", this->mosquittoPasswordFile.c_str());
  279 + return;
  280 + }
  281 +
  282 + struct stat statbuf;
  283 + memset(&statbuf, 0, sizeof(struct stat));
  284 + check<std::runtime_error>(stat(mosquittoPasswordFile.c_str(), &statbuf));
  285 + struct timespec ctime = statbuf.st_ctim;
  286 +
  287 + if (ctime.tv_sec == this->mosquittoPasswordFileLastLoad.tv_sec)
  288 + return;
  289 +
  290 + logger->logf(LOG_NOTICE, "Change detected in '%s'. Reloading.", this->mosquittoPasswordFile.c_str());
  291 +
  292 + try
  293 + {
  294 + std::ifstream infile(this->mosquittoPasswordFile, std::ios::in);
  295 + std::unique_ptr<std::unordered_map<std::string, MosquittoPasswordFileEntry>> passwordEntries_tmp(new std::unordered_map<std::string, MosquittoPasswordFileEntry>());
  296 +
  297 + for(std::string line; getline(infile, line ); )
  298 + {
  299 + if (line.empty())
  300 + continue;
  301 +
  302 + try
  303 + {
  304 + std::vector<std::string> fields = splitToVector(line, ':');
  305 +
  306 + if (fields.size() != 2)
  307 + throw std::runtime_error(formatString("Passwd file line '%s' contains more than one ':'", line.c_str()));
  308 +
  309 + const std::string &username = fields[0];
  310 +
  311 + for (const std::string &field : fields)
  312 + {
  313 + if (field.size() == 0)
  314 + {
  315 + throw std::runtime_error(formatString("An empty field was found in '%'", line.c_str()));
  316 + }
  317 + }
  318 +
  319 + std::vector<std::string> fields2 = splitToVector(fields[1], '$', 3, false);
  320 +
  321 + if (fields2.size() != 3)
  322 + throw std::runtime_error(formatString("Invalid line format in '%s'. Expected three fields separated by '$'", line.c_str()));
  323 +
  324 + if (fields2[0] != "6")
  325 + throw std::runtime_error("Password fields must start with $6$");
  326 +
  327 + std::vector<char> salt = base64Decode(fields2[1]);
  328 + std::vector<char> cryptedPassword = base64Decode(fields2[2]);
  329 + passwordEntries_tmp->emplace(username, MosquittoPasswordFileEntry(std::move(salt), std::move(cryptedPassword)));
  330 + }
  331 + catch (std::exception &ex)
  332 + {
  333 + std::string lineCut = formatString("%s...", line.substr(0, 20).c_str());
  334 + logger->logf(LOG_ERR, "Dropping invalid username/password line: '%s'. Error: %s", lineCut.c_str(), ex.what());
  335 + }
  336 + }
  337 +
  338 + this->mosquittoPasswordEntries = std::move(passwordEntries_tmp);
  339 + this->mosquittoPasswordFileLastLoad = ctime;
  340 + }
  341 + catch (std::exception &ex)
  342 + {
  343 + logger->logf(LOG_ERR, "Error loading Mosquitto password file: '%s'. Authentication won't work.", ex.what());
  344 + }
  345 +}
  346 +
  347 +AuthResult Authentication::unPwdCheckFromMosquittoPasswordFile(const std::string &username, const std::string &password)
  348 +{
  349 + if (this->mosquittoPasswordFile.empty())
  350 + return AuthResult::success;
  351 +
  352 + if (!this->mosquittoPasswordEntries)
  353 + return AuthResult::login_denied;
  354 +
  355 + AuthResult result = settings.allowAnonymous ? AuthResult::success : AuthResult::login_denied;
  356 +
  357 + auto it = mosquittoPasswordEntries->find(username);
  358 + if (it != mosquittoPasswordEntries->end())
  359 + {
  360 + result = AuthResult::login_denied;
  361 +
  362 + unsigned char md_value[EVP_MAX_MD_SIZE];
  363 + unsigned int output_len = 0;
  364 +
  365 + const MosquittoPasswordFileEntry &entry = it->second;
  366 +
  367 + EVP_MD_CTX_reset(mosquittoDigestContext);
  368 + EVP_DigestInit_ex(mosquittoDigestContext, sha512, NULL);
  369 + EVP_DigestUpdate(mosquittoDigestContext, password.c_str(), password.length());
  370 + EVP_DigestUpdate(mosquittoDigestContext, entry.salt.data(), entry.salt.size());
  371 + EVP_DigestFinal_ex(mosquittoDigestContext, md_value, &output_len);
  372 +
  373 + std::vector<char> hashedSalted(output_len);
  374 + std::memcpy(hashedSalted.data(), md_value, output_len);
  375 +
  376 + if (hashedSalted == entry.cryptedPassword)
  377 + result = AuthResult::success;
  378 + }
  379 +
  380 + return result;
  381 +}
  382 +
232 std::string AuthResultToString(AuthResult r) 383 std::string AuthResultToString(AuthResult r)
233 { 384 {
234 { 385 {
authplugin.h
@@ -41,6 +41,26 @@ enum class AuthResult @@ -41,6 +41,26 @@ enum class AuthResult
41 error = 13 41 error = 13
42 }; 42 };
43 43
  44 +/**
  45 + * @brief The MosquittoPasswordFileEntry struct stores the decoded base64 password salt and hash.
  46 + *
  47 + * The Mosquitto encrypted format looks like that of crypt(2), but it's not. This is an example entry:
  48 + *
  49 + * one:$6$emTXKCHfxMnZLDWg$gDcJRPojvOX8l7W/DRhSPoxV3CgPfECJVGRzw2Sqjdc2KIQ/CVLS1mNEuZUsp/vLdj7RCuqXCkgG43+XIc8WBA==
  50 + *
  51 + * $ is the seperator. '6' is hard-coded by the 'mosquitto_passwd' utility.
  52 + */
  53 +struct MosquittoPasswordFileEntry
  54 +{
  55 + std::vector<char> salt;
  56 + std::vector<char> cryptedPassword;
  57 +
  58 + MosquittoPasswordFileEntry(const std::vector<char> &&salt, const std::vector<char> &&cryptedPassword);
  59 +
  60 + // The plan was that objects of this type wouldn't be copied, but I can't get emplacing to work without it...?
  61 + //MosquittoPasswordFileEntry(const MosquittoPasswordFileEntry &other) = delete;
  62 +};
  63 +
44 typedef int (*F_auth_plugin_version)(void); 64 typedef int (*F_auth_plugin_version)(void);
45 65
46 typedef int (*F_auth_plugin_init_v2)(void **, struct mosquitto_auth_opt *, int); 66 typedef int (*F_auth_plugin_init_v2)(void **, struct mosquitto_auth_opt *, int);
@@ -59,8 +79,11 @@ extern &quot;C&quot; @@ -59,8 +79,11 @@ extern &quot;C&quot;
59 79
60 std::string AuthResultToString(AuthResult r); 80 std::string AuthResultToString(AuthResult r);
61 81
62 -  
63 -class AuthPlugin 82 +/**
  83 + * @brief The Authentication class handles our integrated authentication, but also supports loading Mosquitto auth
  84 + * plugin compatible .so files.
  85 + */
  86 +class Authentication
64 { 87 {
65 F_auth_plugin_version version = nullptr; 88 F_auth_plugin_version version = nullptr;
66 F_auth_plugin_init_v2 init_v2 = nullptr; 89 F_auth_plugin_init_v2 init_v2 = nullptr;
@@ -79,15 +102,30 @@ class AuthPlugin @@ -79,15 +102,30 @@ class AuthPlugin
79 void *pluginData = nullptr; 102 void *pluginData = nullptr;
80 Logger *logger = nullptr; 103 Logger *logger = nullptr;
81 bool initialized = false; 104 bool initialized = false;
82 - bool wanted = false; 105 + bool useExternalPlugin = false;
83 bool quitting = false; 106 bool quitting = false;
84 107
  108 + /**
  109 + * @brief mosquittoPasswordFile is a once set value based on config. It's not reloaded on reload signal currently, because it
  110 + * forces some decisions when you change files or remove the config option. For instance, do you remove all accounts loaded
  111 + * from the previous one? Perhaps I'm overthinking it.
  112 + *
  113 + * Its content is, however, reloaded every two seconds.
  114 + */
  115 + const std::string mosquittoPasswordFile;
  116 +
  117 + struct timespec mosquittoPasswordFileLastLoad;
  118 +
  119 + std::unique_ptr<std::unordered_map<std::string, MosquittoPasswordFileEntry>> mosquittoPasswordEntries;
  120 + EVP_MD_CTX *mosquittoDigestContext = nullptr;
  121 + const EVP_MD *sha512 = EVP_sha512();
  122 +
85 void *loadSymbol(void *handle, const char *symbol) const; 123 void *loadSymbol(void *handle, const char *symbol) const;
86 public: 124 public:
87 - AuthPlugin(Settings &settings);  
88 - AuthPlugin(const AuthPlugin &other) = delete;  
89 - AuthPlugin(AuthPlugin &&other) = delete;  
90 - ~AuthPlugin(); 125 + Authentication(Settings &settings);
  126 + Authentication(const Authentication &other) = delete;
  127 + Authentication(Authentication &&other) = delete;
  128 + ~Authentication();
91 129
92 void loadPlugin(const std::string &pathToSoFile); 130 void loadPlugin(const std::string &pathToSoFile);
93 void init(); 131 void init();
@@ -98,6 +136,8 @@ public: @@ -98,6 +136,8 @@ public:
98 AuthResult unPwdCheck(const std::string &username, const std::string &password); 136 AuthResult unPwdCheck(const std::string &username, const std::string &password);
99 137
100 void setQuitting(); 138 void setQuitting();
  139 + void loadMosquittoPasswordFile();
  140 + AuthResult unPwdCheckFromMosquittoPasswordFile(const std::string &username, const std::string &password);
101 141
102 }; 142 };
103 143
configfileparser.cpp
@@ -85,6 +85,8 @@ ConfigFileParser::ConfigFileParser(const std::string &amp;path) : @@ -85,6 +85,8 @@ ConfigFileParser::ConfigFileParser(const std::string &amp;path) :
85 validKeys.insert("max_packet_size"); 85 validKeys.insert("max_packet_size");
86 validKeys.insert("log_debug"); 86 validKeys.insert("log_debug");
87 validKeys.insert("log_subscriptions"); 87 validKeys.insert("log_subscriptions");
  88 + validKeys.insert("mosquitto_password_file");
  89 + validKeys.insert("allow_anonymous");
88 90
89 validListenKeys.insert("port"); 91 validListenKeys.insert("port");
90 validListenKeys.insert("protocol"); 92 validListenKeys.insert("protocol");
@@ -334,6 +336,17 @@ void ConfigFileParser::loadFile(bool test) @@ -334,6 +336,17 @@ void ConfigFileParser::loadFile(bool test)
334 bool tmp = stringTruthiness(value); 336 bool tmp = stringTruthiness(value);
335 tmpSettings->logSubscriptions = tmp; 337 tmpSettings->logSubscriptions = tmp;
336 } 338 }
  339 +
  340 + if (key == "mosquitto_password_file")
  341 + {
  342 + tmpSettings->mosquittoPasswordFile = value;
  343 + }
  344 +
  345 + if (key == "allow_anonymous")
  346 + {
  347 + bool tmp = stringTruthiness(value);
  348 + tmpSettings->allowAnonymous = tmp;
  349 + }
337 } 350 }
338 } 351 }
339 catch (std::invalid_argument &ex) // catch for the stoi() 352 catch (std::invalid_argument &ex) // catch for the stoi()
evpencodectxmanager.cpp 0 โ†’ 100644
  1 +/*
  2 +This file is part of FlashMQ (https://www.flashmq.org)
  3 +Copyright (C) 2021 Wiebe Cazemier
  4 +
  5 +FlashMQ is free software: you can redistribute it and/or modify
  6 +it under the terms of the GNU Affero General Public License as
  7 +published by the Free Software Foundation, version 3.
  8 +
  9 +FlashMQ is distributed in the hope that it will be useful,
  10 +but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12 +GNU Affero General Public License for more details.
  13 +
  14 +You should have received a copy of the GNU Affero General Public
  15 +License along with FlashMQ. If not, see <https://www.gnu.org/licenses/>.
  16 +*/
  17 +
  18 +#include <stdexcept>
  19 +
  20 +#include "evpencodectxmanager.h"
  21 +
  22 +EvpEncodeCtxManager::EvpEncodeCtxManager()
  23 +{
  24 + ctx = EVP_ENCODE_CTX_new();
  25 +
  26 + if (!ctx)
  27 + throw std::runtime_error("Error allocating with EVP_ENCODE_CTX_new()");
  28 +
  29 + EVP_DecodeInit(ctx);
  30 +}
  31 +
  32 +EvpEncodeCtxManager::~EvpEncodeCtxManager()
  33 +{
  34 + if (ctx)
  35 + EVP_ENCODE_CTX_free(ctx);
  36 +}
evpencodectxmanager.h 0 โ†’ 100644
  1 +/*
  2 +This file is part of FlashMQ (https://www.flashmq.org)
  3 +Copyright (C) 2021 Wiebe Cazemier
  4 +
  5 +FlashMQ is free software: you can redistribute it and/or modify
  6 +it under the terms of the GNU Affero General Public License as
  7 +published by the Free Software Foundation, version 3.
  8 +
  9 +FlashMQ is distributed in the hope that it will be useful,
  10 +but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12 +GNU Affero General Public License for more details.
  13 +
  14 +You should have received a copy of the GNU Affero General Public
  15 +License along with FlashMQ. If not, see <https://www.gnu.org/licenses/>.
  16 +*/
  17 +
  18 +#ifndef EVPENCODECTXMANAGER_H
  19 +#define EVPENCODECTXMANAGER_H
  20 +
  21 +#include "openssl/evp.h"
  22 +
  23 +struct EvpEncodeCtxManager
  24 +{
  25 + EVP_ENCODE_CTX *ctx = nullptr;
  26 + EvpEncodeCtxManager();
  27 + ~EvpEncodeCtxManager();
  28 +};
  29 +
  30 +#endif // EVPENCODECTXMANAGER_H
mainapp.cpp
@@ -186,6 +186,9 @@ MainApp::MainApp(const std::string &amp;configFilePath) : @@ -186,6 +186,9 @@ MainApp::MainApp(const std::string &amp;configFilePath) :
186 186
187 auto fKeepAlive = std::bind(&MainApp::queueKeepAliveCheckAtAllThreads, this); 187 auto fKeepAlive = std::bind(&MainApp::queueKeepAliveCheckAtAllThreads, this);
188 timer.addCallback(fKeepAlive, 30000, "keep-alive check"); 188 timer.addCallback(fKeepAlive, 30000, "keep-alive check");
  189 +
  190 + auto fPasswordFileReload = std::bind(&MainApp::queuePasswordFileReloadAllThreads, this);
  191 + timer.addCallback(fPasswordFileReload, 2000, "Password file reload.");
189 } 192 }
190 193
191 MainApp::~MainApp() 194 MainApp::~MainApp()
@@ -292,6 +295,14 @@ void MainApp::queueKeepAliveCheckAtAllThreads() @@ -292,6 +295,14 @@ void MainApp::queueKeepAliveCheckAtAllThreads()
292 } 295 }
293 } 296 }
294 297
  298 +void MainApp::queuePasswordFileReloadAllThreads()
  299 +{
  300 + for (std::shared_ptr<ThreadData> &thread : threads)
  301 + {
  302 + thread->queuePasswdFileReload();
  303 + }
  304 +}
  305 +
295 void MainApp::setFuzzFile(const std::string &fuzzFilePath) 306 void MainApp::setFuzzFile(const std::string &fuzzFilePath)
296 { 307 {
297 this->fuzzFilePath = fuzzFilePath; 308 this->fuzzFilePath = fuzzFilePath;
mainapp.h
@@ -73,6 +73,7 @@ class MainApp @@ -73,6 +73,7 @@ class MainApp
73 std::list<ScopedSocket> createListenSocket(const std::shared_ptr<Listener> &listener); 73 std::list<ScopedSocket> createListenSocket(const std::shared_ptr<Listener> &listener);
74 void wakeUpThread(); 74 void wakeUpThread();
75 void queueKeepAliveCheckAtAllThreads(); 75 void queueKeepAliveCheckAtAllThreads();
  76 + void queuePasswordFileReloadAllThreads();
76 void setFuzzFile(const std::string &fuzzFilePath); 77 void setFuzzFile(const std::string &fuzzFilePath);
77 78
78 MainApp(const std::string &configFilePath); 79 MainApp(const std::string &configFilePath);
mqttpacket.cpp
@@ -313,7 +313,7 @@ void MqttPacket::handleConnect() @@ -313,7 +313,7 @@ void MqttPacket::handleConnect()
313 sender->setDisconnectReason("Invalid username character"); 313 sender->setDisconnectReason("Invalid username character");
314 accessGranted = false; 314 accessGranted = false;
315 } 315 }
316 - else if (sender->getThreadData()->authPlugin.unPwdCheck(username, password) == AuthResult::success) 316 + else if (sender->getThreadData()->authentication.unPwdCheck(username, password) == AuthResult::success)
317 { 317 {
318 accessGranted = true; 318 accessGranted = true;
319 } 319 }
@@ -462,7 +462,7 @@ void MqttPacket::handlePublish() @@ -462,7 +462,7 @@ void MqttPacket::handlePublish()
462 sender->writeMqttPacket(response); 462 sender->writeMqttPacket(response);
463 } 463 }
464 464
465 - if (sender->getThreadData()->authPlugin.aclCheck(sender->getClientId(), sender->getUsername(), topic, AclAccess::write) == AuthResult::success) 465 + if (sender->getThreadData()->authentication.aclCheck(sender->getClientId(), sender->getUsername(), topic, AclAccess::write) == AuthResult::success)
466 { 466 {
467 if (retain) 467 if (retain)
468 { 468 {
session.cpp
@@ -52,7 +52,7 @@ void Session::writePacket(const MqttPacket &amp;packet, char max_qos) @@ -52,7 +52,7 @@ void Session::writePacket(const MqttPacket &amp;packet, char max_qos)
52 { 52 {
53 assert(max_qos <= 2); 53 assert(max_qos <= 2);
54 54
55 - if (thread->authPlugin.aclCheck(client_id, username, packet.getTopic(), AclAccess::read) == AuthResult::success) 55 + if (thread->authentication.aclCheck(client_id, username, packet.getTopic(), AclAccess::read) == AuthResult::success)
56 { 56 {
57 const char qos = std::min<char>(packet.getQos(), max_qos); 57 const char qos = std::min<char>(packet.getQos(), max_qos);
58 58
settings.h
@@ -42,6 +42,8 @@ public: @@ -42,6 +42,8 @@ public:
42 int maxPacketSize = 268435461; // 256 MB + 5 42 int maxPacketSize = 268435461; // 256 MB + 5
43 bool logDebug = false; 43 bool logDebug = false;
44 bool logSubscriptions = false; 44 bool logSubscriptions = false;
  45 + std::string mosquittoPasswordFile;
  46 + bool allowAnonymous = false;
45 std::list<std::shared_ptr<Listener>> listeners; // Default one is created later, when none are defined. 47 std::list<std::shared_ptr<Listener>> listeners; // Default one is created later, when none are defined.
46 48
47 AuthOptCompatWrap &getAuthOptsCompat(); 49 AuthOptCompatWrap &getAuthOptsCompat();
threaddata.cpp
@@ -22,7 +22,7 @@ License along with FlashMQ. If not, see &lt;https://www.gnu.org/licenses/&gt;. @@ -22,7 +22,7 @@ License along with FlashMQ. If not, see &lt;https://www.gnu.org/licenses/&gt;.
22 ThreadData::ThreadData(int threadnr, std::shared_ptr<SubscriptionStore> &subscriptionStore, std::shared_ptr<Settings> settings) : 22 ThreadData::ThreadData(int threadnr, std::shared_ptr<SubscriptionStore> &subscriptionStore, std::shared_ptr<Settings> settings) :
23 subscriptionStore(subscriptionStore), 23 subscriptionStore(subscriptionStore),
24 settingsLocalCopy(*settings.get()), 24 settingsLocalCopy(*settings.get()),
25 - authPlugin(settingsLocalCopy), 25 + authentication(settingsLocalCopy),
26 threadnr(threadnr) 26 threadnr(threadnr)
27 { 27 {
28 logger = Logger::getInstance(); 28 logger = Logger::getInstance();
@@ -132,7 +132,7 @@ void ThreadData::queueQuit() @@ -132,7 +132,7 @@ void ThreadData::queueQuit()
132 auto f = std::bind(&ThreadData::quit, this); 132 auto f = std::bind(&ThreadData::quit, this);
133 taskQueue.push_front(f); 133 taskQueue.push_front(f);
134 134
135 - authPlugin.setQuitting(); 135 + authentication.setQuitting();
136 136
137 wakeUpThread(); 137 wakeUpThread();
138 } 138 }
@@ -142,6 +142,16 @@ void ThreadData::waitForQuit() @@ -142,6 +142,16 @@ void ThreadData::waitForQuit()
142 thread.join(); 142 thread.join();
143 } 143 }
144 144
  145 +void ThreadData::queuePasswdFileReload()
  146 +{
  147 + std::lock_guard<std::mutex> locker(taskQueueMutex);
  148 +
  149 + auto f = std::bind(&Authentication::loadMosquittoPasswordFile, &authentication);
  150 + taskQueue.push_front(f);
  151 +
  152 + wakeUpThread();
  153 +}
  154 +
145 // TODO: profile how fast hash iteration is. Perhaps having a second list/vector is beneficial? 155 // TODO: profile how fast hash iteration is. Perhaps having a second list/vector is beneficial?
146 void ThreadData::doKeepAliveCheck() 156 void ThreadData::doKeepAliveCheck()
147 { 157 {
@@ -179,9 +189,10 @@ void ThreadData::doKeepAliveCheck() @@ -179,9 +189,10 @@ void ThreadData::doKeepAliveCheck()
179 189
180 void ThreadData::initAuthPlugin() 190 void ThreadData::initAuthPlugin()
181 { 191 {
182 - authPlugin.loadPlugin(settingsLocalCopy.authPluginPath);  
183 - authPlugin.init();  
184 - authPlugin.securityInit(false); 192 + authentication.loadMosquittoPasswordFile();
  193 + authentication.loadPlugin(settingsLocalCopy.authPluginPath);
  194 + authentication.init();
  195 + authentication.securityInit(false);
185 } 196 }
186 197
187 void ThreadData::reload(std::shared_ptr<Settings> settings) 198 void ThreadData::reload(std::shared_ptr<Settings> settings)
@@ -193,8 +204,8 @@ void ThreadData::reload(std::shared_ptr&lt;Settings&gt; settings) @@ -193,8 +204,8 @@ void ThreadData::reload(std::shared_ptr&lt;Settings&gt; settings)
193 // Because the auth plugin has a reference to it, it will also be updated. 204 // Because the auth plugin has a reference to it, it will also be updated.
194 settingsLocalCopy = *settings.get(); 205 settingsLocalCopy = *settings.get();
195 206
196 - authPlugin.securityCleanup(true);  
197 - authPlugin.securityInit(true); 207 + authentication.securityCleanup(true);
  208 + authentication.securityInit(true);
198 } 209 }
199 catch (AuthPluginException &ex) 210 catch (AuthPluginException &ex)
200 { 211 {
threaddata.h
@@ -54,7 +54,7 @@ class ThreadData @@ -54,7 +54,7 @@ class ThreadData
54 54
55 public: 55 public:
56 Settings settingsLocalCopy; // Is updated on reload, within the thread loop. 56 Settings settingsLocalCopy; // Is updated on reload, within the thread loop.
57 - AuthPlugin authPlugin; 57 + Authentication authentication;
58 bool running = true; 58 bool running = true;
59 std::thread thread; 59 std::thread thread;
60 int threadnr = 0; 60 int threadnr = 0;
@@ -80,6 +80,7 @@ public: @@ -80,6 +80,7 @@ public:
80 void queueDoKeepAliveCheck(); 80 void queueDoKeepAliveCheck();
81 void queueQuit(); 81 void queueQuit();
82 void waitForQuit(); 82 void waitForQuit();
  83 + void queuePasswdFileReload();
83 84
84 }; 85 };
85 86
utils.cpp
@@ -21,6 +21,7 @@ License along with FlashMQ. If not, see &lt;https://www.gnu.org/licenses/&gt;. @@ -21,6 +21,7 @@ License along with FlashMQ. If not, see &lt;https://www.gnu.org/licenses/&gt;.
21 #include "sys/random.h" 21 #include "sys/random.h"
22 #include <algorithm> 22 #include <algorithm>
23 #include <cstdio> 23 #include <cstdio>
  24 +#include <cstring>
24 25
25 #include "openssl/ssl.h" 26 #include "openssl/ssl.h"
26 #include "openssl/err.h" 27 #include "openssl/err.h"
@@ -29,6 +30,7 @@ License along with FlashMQ. If not, see &lt;https://www.gnu.org/licenses/&gt;. @@ -29,6 +30,7 @@ License along with FlashMQ. If not, see &lt;https://www.gnu.org/licenses/&gt;.
29 #include "cirbuf.h" 30 #include "cirbuf.h"
30 #include "sslctxmanager.h" 31 #include "sslctxmanager.h"
31 #include "logger.h" 32 #include "logger.h"
  33 +#include "evpencodectxmanager.h"
32 34
33 std::list<std::__cxx11::string> split(const std::string &input, const char sep, size_t max, bool keep_empty_parts) 35 std::list<std::__cxx11::string> split(const std::string &input, const char sep, size_t max, bool keep_empty_parts)
34 { 36 {
@@ -357,6 +359,30 @@ bool parseHttpHeader(CirBuf &amp;buf, std::string &amp;websocket_key, int &amp;websocket_ver @@ -357,6 +359,30 @@ bool parseHttpHeader(CirBuf &amp;buf, std::string &amp;websocket_key, int &amp;websocket_ver
357 return doubleEmptyLine; 359 return doubleEmptyLine;
358 } 360 }
359 361
  362 +std::vector<char> base64Decode(const std::string &s)
  363 +{
  364 + if (s.length() % 4 != 0)
  365 + throw std::runtime_error("Decoding invalid base64 string");
  366 +
  367 + if (s.empty())
  368 + throw std::runtime_error("Trying to base64 decode an empty string.");
  369 +
  370 + std::vector<char> tmp(s.size());
  371 +
  372 + int outl = 0;
  373 + int outl_total = 0;
  374 +
  375 + EvpEncodeCtxManager b64_ctx;
  376 + if (EVP_DecodeUpdate(b64_ctx.ctx, reinterpret_cast<unsigned char*>(tmp.data()), &outl, reinterpret_cast<const unsigned char*>(s.c_str()), s.size()) < 0)
  377 + throw std::runtime_error("Failure in EVP_DecodeUpdate()");
  378 + outl_total += outl;
  379 + if (EVP_DecodeFinal(b64_ctx.ctx, reinterpret_cast<unsigned char*>(tmp[outl_total]), &outl) < 0)
  380 + throw std::runtime_error("Failure in EVP_DecodeFinal()");
  381 + std::vector<char> result(outl_total);
  382 + std::memcpy(result.data(), tmp.data(), outl_total);
  383 + return result;
  384 +}
  385 +
360 std::string base64Encode(const unsigned char *input, const int length) 386 std::string base64Encode(const unsigned char *input, const int length)
361 { 387 {
362 const int pl = 4*((length+2)/3); 388 const int pl = 4*((length+2)/3);
@@ -70,6 +70,7 @@ bool isPowerOfTwo(int val); @@ -70,6 +70,7 @@ bool isPowerOfTwo(int val);
70 70
71 bool parseHttpHeader(CirBuf &buf, std::string &websocket_key, int &websocket_version); 71 bool parseHttpHeader(CirBuf &buf, std::string &websocket_key, int &websocket_version);
72 72
  73 +std::vector<char> base64Decode(const std::string &s);
73 std::string base64Encode(const unsigned char *input, const int length); 74 std::string base64Encode(const unsigned char *input, const int length);
74 std::string generateWebsocketAcceptString(const std::string &websocketKey); 75 std::string generateWebsocketAcceptString(const std::string &websocketKey);
75 76