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 38 scopedsocket.h
39 39 bindaddr.h
40 40 oneinstancelock.h
  41 + evpencodectxmanager.h
41 42  
42 43 mainapp.cpp
43 44 main.cpp
... ... @@ -65,6 +66,7 @@ add_executable(FlashMQ
65 66 scopedsocket.cpp
66 67 bindaddr.cpp
67 68 oneinstancelock.cpp
  69 + evpencodectxmanager.cpp
68 70 )
69 71  
70 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 21 #include <fcntl.h>
22 22 #include <sstream>
23 23 #include <dlfcn.h>
  24 +#include <fstream>
  25 +#include "sys/stat.h"
24 26  
25 27 #include "exceptions.h"
26 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 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 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 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 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 77 void *r = dlsym(handle, symbol);
55 78  
... ... @@ -62,7 +85,7 @@ void *AuthPlugin::loadSymbol(void *handle, const char *symbol) const
62 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 90 if (pathToSoFile.empty())
68 91 return;
... ... @@ -70,7 +93,7 @@ void AuthPlugin::loadPlugin(const std::string &amp;pathToSoFile)
70 93 logger->logf(LOG_NOTICE, "Loading auth plugin %s", pathToSoFile.c_str());
71 94  
72 95 initialized = false;
73   - wanted = true;
  96 + useExternalPlugin = true;
74 97  
75 98 if (access(pathToSoFile.c_str(), R_OK) != 0)
76 99 {
... ... @@ -105,9 +128,13 @@ void AuthPlugin::loadPlugin(const std::string &amp;pathToSoFile)
105 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 138 return;
112 139  
113 140 UnscopedLock lock(initMutex);
... ... @@ -123,7 +150,7 @@ void AuthPlugin::init()
123 150 throw FatalError("Error initialising auth plugin.");
124 151 }
125 152  
126   -void AuthPlugin::cleanup()
  153 +void Authentication::cleanup()
127 154 {
128 155 if (!cleanup_v2)
129 156 return;
... ... @@ -136,9 +163,13 @@ void AuthPlugin::cleanup()
136 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 173 return;
143 174  
144 175 UnscopedLock lock(initMutex);
... ... @@ -157,9 +188,9 @@ void AuthPlugin::securityInit(bool reloading)
157 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 194 return;
164 195  
165 196 initialized = false;
... ... @@ -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 209 return AuthResult::success;
179 210  
180 211 if (!initialized)
... ... @@ -198,14 +229,19 @@ AuthResult AuthPlugin::aclCheck(const std::string &amp;clientid, const std::string &amp;
198 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 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 245 return AuthResult::error;
210 246 }
211 247  
... ... @@ -224,11 +260,126 @@ AuthResult AuthPlugin::unPwdCheck(const std::string &amp;username, const std::string
224 260 return r;
225 261 }
226 262  
227   -void AuthPlugin::setQuitting()
  263 +void Authentication::setQuitting()
228 264 {
229 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 383 std::string AuthResultToString(AuthResult r)
233 384 {
234 385 {
... ...
authplugin.h
... ... @@ -41,6 +41,26 @@ enum class AuthResult
41 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 64 typedef int (*F_auth_plugin_version)(void);
45 65  
46 66 typedef int (*F_auth_plugin_init_v2)(void **, struct mosquitto_auth_opt *, int);
... ... @@ -59,8 +79,11 @@ extern &quot;C&quot;
59 79  
60 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 88 F_auth_plugin_version version = nullptr;
66 89 F_auth_plugin_init_v2 init_v2 = nullptr;
... ... @@ -79,15 +102,30 @@ class AuthPlugin
79 102 void *pluginData = nullptr;
80 103 Logger *logger = nullptr;
81 104 bool initialized = false;
82   - bool wanted = false;
  105 + bool useExternalPlugin = false;
83 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 123 void *loadSymbol(void *handle, const char *symbol) const;
86 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 130 void loadPlugin(const std::string &pathToSoFile);
93 131 void init();
... ... @@ -98,6 +136,8 @@ public:
98 136 AuthResult unPwdCheck(const std::string &username, const std::string &password);
99 137  
100 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 85 validKeys.insert("max_packet_size");
86 86 validKeys.insert("log_debug");
87 87 validKeys.insert("log_subscriptions");
  88 + validKeys.insert("mosquitto_password_file");
  89 + validKeys.insert("allow_anonymous");
88 90  
89 91 validListenKeys.insert("port");
90 92 validListenKeys.insert("protocol");
... ... @@ -334,6 +336,17 @@ void ConfigFileParser::loadFile(bool test)
334 336 bool tmp = stringTruthiness(value);
335 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 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 186  
187 187 auto fKeepAlive = std::bind(&MainApp::queueKeepAliveCheckAtAllThreads, this);
188 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 194 MainApp::~MainApp()
... ... @@ -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 306 void MainApp::setFuzzFile(const std::string &fuzzFilePath)
296 307 {
297 308 this->fuzzFilePath = fuzzFilePath;
... ...
mainapp.h
... ... @@ -73,6 +73,7 @@ class MainApp
73 73 std::list<ScopedSocket> createListenSocket(const std::shared_ptr<Listener> &listener);
74 74 void wakeUpThread();
75 75 void queueKeepAliveCheckAtAllThreads();
  76 + void queuePasswordFileReloadAllThreads();
76 77 void setFuzzFile(const std::string &fuzzFilePath);
77 78  
78 79 MainApp(const std::string &configFilePath);
... ...
mqttpacket.cpp
... ... @@ -313,7 +313,7 @@ void MqttPacket::handleConnect()
313 313 sender->setDisconnectReason("Invalid username character");
314 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 318 accessGranted = true;
319 319 }
... ... @@ -462,7 +462,7 @@ void MqttPacket::handlePublish()
462 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 467 if (retain)
468 468 {
... ...
session.cpp
... ... @@ -52,7 +52,7 @@ void Session::writePacket(const MqttPacket &amp;packet, char max_qos)
52 52 {
53 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 57 const char qos = std::min<char>(packet.getQos(), max_qos);
58 58  
... ...
settings.h
... ... @@ -42,6 +42,8 @@ public:
42 42 int maxPacketSize = 268435461; // 256 MB + 5
43 43 bool logDebug = false;
44 44 bool logSubscriptions = false;
  45 + std::string mosquittoPasswordFile;
  46 + bool allowAnonymous = false;
45 47 std::list<std::shared_ptr<Listener>> listeners; // Default one is created later, when none are defined.
46 48  
47 49 AuthOptCompatWrap &getAuthOptsCompat();
... ...
threaddata.cpp
... ... @@ -22,7 +22,7 @@ License along with FlashMQ. If not, see &lt;https://www.gnu.org/licenses/&gt;.
22 22 ThreadData::ThreadData(int threadnr, std::shared_ptr<SubscriptionStore> &subscriptionStore, std::shared_ptr<Settings> settings) :
23 23 subscriptionStore(subscriptionStore),
24 24 settingsLocalCopy(*settings.get()),
25   - authPlugin(settingsLocalCopy),
  25 + authentication(settingsLocalCopy),
26 26 threadnr(threadnr)
27 27 {
28 28 logger = Logger::getInstance();
... ... @@ -132,7 +132,7 @@ void ThreadData::queueQuit()
132 132 auto f = std::bind(&ThreadData::quit, this);
133 133 taskQueue.push_front(f);
134 134  
135   - authPlugin.setQuitting();
  135 + authentication.setQuitting();
136 136  
137 137 wakeUpThread();
138 138 }
... ... @@ -142,6 +142,16 @@ void ThreadData::waitForQuit()
142 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 155 // TODO: profile how fast hash iteration is. Perhaps having a second list/vector is beneficial?
146 156 void ThreadData::doKeepAliveCheck()
147 157 {
... ... @@ -179,9 +189,10 @@ void ThreadData::doKeepAliveCheck()
179 189  
180 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 198 void ThreadData::reload(std::shared_ptr<Settings> settings)
... ... @@ -193,8 +204,8 @@ void ThreadData::reload(std::shared_ptr&lt;Settings&gt; settings)
193 204 // Because the auth plugin has a reference to it, it will also be updated.
194 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 210 catch (AuthPluginException &ex)
200 211 {
... ...
threaddata.h
... ... @@ -54,7 +54,7 @@ class ThreadData
54 54  
55 55 public:
56 56 Settings settingsLocalCopy; // Is updated on reload, within the thread loop.
57   - AuthPlugin authPlugin;
  57 + Authentication authentication;
58 58 bool running = true;
59 59 std::thread thread;
60 60 int threadnr = 0;
... ... @@ -80,6 +80,7 @@ public:
80 80 void queueDoKeepAliveCheck();
81 81 void queueQuit();
82 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 21 #include "sys/random.h"
22 22 #include <algorithm>
23 23 #include <cstdio>
  24 +#include <cstring>
24 25  
25 26 #include "openssl/ssl.h"
26 27 #include "openssl/err.h"
... ... @@ -29,6 +30,7 @@ License along with FlashMQ. If not, see &lt;https://www.gnu.org/licenses/&gt;.
29 30 #include "cirbuf.h"
30 31 #include "sslctxmanager.h"
31 32 #include "logger.h"
  33 +#include "evpencodectxmanager.h"
32 34  
33 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 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 386 std::string base64Encode(const unsigned char *input, const int length)
361 387 {
362 388 const int pl = 4*((length+2)/3);
... ...
... ... @@ -70,6 +70,7 @@ bool isPowerOfTwo(int val);
70 70  
71 71 bool parseHttpHeader(CirBuf &buf, std::string &websocket_key, int &websocket_version);
72 72  
  73 +std::vector<char> base64Decode(const std::string &s);
73 74 std::string base64Encode(const unsigned char *input, const int length);
74 75 std::string generateWebsocketAcceptString(const std::string &websocketKey);
75 76  
... ...