Commit b2c5fe90223bef9c7cdab7deb60dc64b6a2820eb
1 parent
2ae1bb7e
Add support for Mosquitto's password file
Encrypted version only.
Showing
15 changed files
with
365 additions
and
40 deletions
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 &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 &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 &clientid, const std::string & |
| 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 &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 "C" |
| 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 &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 &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 &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 <https://www.gnu.org/licenses/>. |
| 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<Settings> 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 <https://www.gnu.org/licenses/>. |
| 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 <https://www.gnu.org/licenses/>. |
| 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 &buf, std::string &websocket_key, int &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); | ... | ... |
utils.h
| ... | ... | @@ -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 | ... | ... |