From 51072f0a59ee6069015a03eed4d06efaa7db5d6b Mon Sep 17 00:00:00 2001 From: Zii Date: Fri, 21 Jul 2023 19:30:31 -0400 Subject: [PATCH] v5.2.2.1 -the built in ssl cert generater was deprecated so it needed to be updated or removed. I opted to calling openssl via external command line execution. doing it this way make it easier to update if deprecated again and reduces dependancy on libssl. -tweaked the documentation a little. -updated build.py to explicitly get the plugins directoy instead of deriving it from the bin directory. --- README.md | 12 +- build.py | 47 ++++++-- docs/modules.md | 4 +- docs/shared_data.md | 2 +- src/common.h | 6 +- src/main.cpp | 2 - src/make_cert.cpp | 262 ++++++++++---------------------------------- src/make_cert.h | 40 +------ src/tcp_server.cpp | 2 + src/unix_signal.cpp | 24 +++- src/unix_signal.h | 1 + 11 files changed, 130 insertions(+), 272 deletions(-) diff --git a/README.md b/README.md index 468fb56..8f52e26 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Usage: mrci -load_ssl : re-load the host SSL certificate without stopping the host instance. -elevate : elevate any user account to rank 1. -res_pw : reset a user account password with a randomized one time password. - -add_admin : create a rank 1 account with a randomized password. + -add_admin : create a rank 1 account with a randomized one time password. Internal module | -public_cmds, -user_cmds, -exempt_cmds, -run_cmd |: @@ -46,9 +46,9 @@ add_admin - this argument takes a single string representing a user name to crea elevate - this argument takes a single string representing a user name to an account to promote to rank 1. example: -elevate somebody -run_cmd - this argument is used by the host itself, along side the internal module arguments below to run - the internal command names passed by it. this is not meant to be run directly by human input. - the executable will auto close if it fails to connect to the pipe and/or shared memory segments +run_cmd - this argument is used by the host itself along with the internal module arguments to run the + internal command names passed by it. this is not ment to be run directly by human input. the + executable will auto close if it fails to connect to the pipe and/or shared memory segments ``` The host can be managed via a connected client that supports text input/output so the host application is always listening for clients while running entirely in the background. By default the host listen for clients on address 0.0.0.0 and port 35516, effectively making it reachable on any network interface of the host platform via that specific port. @@ -60,7 +60,7 @@ Typical use for a MRCI host is to run commands on a remote host that clients ask * Broadcast any type of data to all peers connected to the host. * Run remote commands on connected peers. * Host object positioning data for peers (online games do this). -* Send data to/from a peer client directly. +* Send data to/from a peer clients directly. * Fully featured user account management system. * Built in permissions and command access management. * Host limits management (max concurrent users, max failed password attempts, etc...). @@ -103,7 +103,7 @@ Python3 ### Build ### -To build this project from source you just need to run the build.py and then the install.py python scripts. While running the build the script, it will try to find the Qt API installed in your machine according to the PATH env variable. If not found, it will ask you to input where it can find the Qt bin folder where the qmake executable exists or you can bypass all of this by passing the -qt_dir option on it's command line. +To build this project from source you just need to run the ```build.py``` and then the ```install.py``` python scripts. While running the build the script, it will try to find the Qt API installed in your machine according to the PATH env variable. If not found, it will ask you to input where it can find the Qt bin folder where the qmake executable exists or you can bypass all of this by passing the -qt_dir option on it's command line. while running the install script, it will ask you to input 1 of 3 options: diff --git a/build.py b/build.py index 5cb2963..5f86659 100644 --- a/build.py +++ b/build.py @@ -24,6 +24,15 @@ def get_qt_path(): print("A direct call to 'qtpaths' has failed so automatic retrieval of the QT bin folder is not possible.") return input("Please enter the QT bin path (leave blank to cancel the build): ") + +def get_qt_plugin_path(): + try: + return str(subprocess.check_output(["qtpaths", "--plugin-dir"]), 'utf-8').strip() + + except: + print("A direct call to 'qtpaths' has failed so automatic retrieval of the QT plugins folder is not possible.") + + return input("Please enter the QT plugins path (leave blank to cancel the build): ") def get_qt_from_cli(): for arg in sys.argv: @@ -37,6 +46,19 @@ def get_qt_from_cli(): return "" return "" + +def get_qt_plug_from_cli(): + for arg in sys.argv: + if arg == "-qt_plugin": + index = sys.argv.index(arg) + + try: + return sys.argv[index + 1] + + except: + return "" + + return "" def get_info_header(): current_dir = os.path.dirname(__file__) @@ -130,17 +152,17 @@ def verbose_copy(src, dst): else: print("wrn: " + src + " does not exists. skipping.") -def linux_build_app_dir(app_ver, app_name, app_target, qt_bin): +def linux_build_app_dir(app_ver, app_name, app_target, qt_bin, qt_plug): if not os.path.exists("app_dir/linux/sqldrivers"): os.makedirs("app_dir/linux/sqldrivers") if not os.path.exists("app_dir/linux/lib"): os.makedirs("app_dir/linux/lib") - verbose_copy(qt_bin + "/../plugins/sqldrivers/libqsqlite.so", "app_dir/linux/sqldrivers/libqsqlite.so") - verbose_copy(qt_bin + "/../plugins/sqldrivers/libqsqlodbc.so", "app_dir/linux/sqldrivers/libqsqlodbc.so") - verbose_copy(qt_bin + "/../plugins/sqldrivers/libqsqlpsql.so", "app_dir/linux/sqldrivers/libqsqlpsql.so") - verbose_copy(qt_bin + "/../plugins/sqldrivers/libqsqlmysql.so", "app_dir/linux/sqldrivers/libqsqlmysql.so") + verbose_copy(qt_plug + "/sqldrivers/libqsqlite.so", "app_dir/linux/sqldrivers/libqsqlite.so") + verbose_copy(qt_plug + "/sqldrivers/libqsqlodbc.so", "app_dir/linux/sqldrivers/libqsqlodbc.so") + verbose_copy(qt_plug + "/sqldrivers/libqsqlpsql.so", "app_dir/linux/sqldrivers/libqsqlpsql.so") + verbose_copy(qt_plug + "/sqldrivers/libqsqlmysql.so", "app_dir/linux/sqldrivers/libqsqlmysql.so") verbose_copy("build/linux/" + app_target, "app_dir/linux/" + app_target) shutil.copyfile("build/linux/" + app_target, "/tmp/" + app_target) @@ -221,17 +243,26 @@ def main(): app_ver = get_app_ver(text) app_name = get_app_name(text) qt_bin = get_qt_from_cli() + qt_plug = get_qt_plug_from_cli() if qt_bin == "": qt_bin = get_qt_path() + + if qt_plug == "": + if platform.system() == "Linux": + qt_plug = get_qt_plugin_path() + + else: + qt_plug = "not-needed" maker = get_maker(qt_bin) - if qt_bin != "": + if qt_bin != "" and qt_plug != "": print("app_target = " + app_target) print("app_version = " + app_ver) print("app_name = " + app_name) print("qt_bin = " + qt_bin) + print("qt_plugins = " + qt_plug) print("maker = " + maker + "\n") if maker == "": @@ -264,7 +295,7 @@ def main(): info_file.write(app_name + "\n") if platform.system() == "Linux": - linux_build_app_dir(app_ver, app_name, app_target, qt_bin) + linux_build_app_dir(app_ver, app_name, app_target, qt_bin, qt_plug) elif platform.system() == "Windows": windows_build_app_dir(app_ver, app_name, app_target, qt_bin) @@ -274,4 +305,4 @@ def main(): print(" output from platform.system() = " + platform.system()) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/docs/modules.md b/docs/modules.md index 2bd4bca..05b5aff 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -47,7 +47,7 @@ notes: * When the session detects that the module successfully established a pipe connection, it will send a [HOST_VER](type_ids.md) frame to the module so the module can decide if it supports the host. If the host is not compatible or the module fails for what ever other reason, the module can send a useful error message [ERR](type_ids.md) and then terminate gracefully. The error message will be added to the host debug log where it can be used by host admins to find out what went wrong. The HOST_VER frame is sent only when the module is called with the -public_cmds, -exempt_cmds or -user_cmds parameter. -* When the module sends a [NEW_CMD](type_ids.md) frame, the 16bit command id is needed but does not need to be valid, it just needs to be there as a place holder. The session will auto fill a valid command id before sending the data to the client. A valid NEW_CMD frame must have a minimum of 259 bytes and a valid command name. the session will ignore all NEW_CMD frames the doesn't meet these requirements. See section [6.3](shared_data.md) for what would be considered a valid command name. +* When the module sends a [NEW_CMD](type_ids.md) frame, the 16bit command id is needed but does not need to be valid, it just needs to be there as a place holder. The session will auto fill a valid command id before sending the data to the client. A valid NEW_CMD frame must have a minimum of 259 bytes and a valid command name. the session will ignore all NEW_CMD frames the doesn't meet these requirements. See section [6.4](shared_data.md) for what would be considered a valid command name. * When a session starts, it will call all modules with the -public_cmds and it doesn't matter if the command names returned to the session overlap with -exempt_cmd or -user_cmds. When a user is logged in, it will then call 2 instances of each module with the -exempt_cmds and -user_cmds parameters so the command names should not overlap when these parameters are active. @@ -57,4 +57,4 @@ notes: ### 2.4 Module Standard Output/Error ### -The host captures all text written to standard out/err. Although modules can send text data to clients via the [TEXT](type_ids.md) frame, another way to do the same thing is to write to stdout. The host however treats stderr differently, it sends all text written to stderr to the host logging system. On Linux systems, syslog is used. On windows systems, a local log file in %PROGRAMDATA%\mrci\messages.log is used. When logging messages, the host will send a generic error message to the client but the full error details will be sent to the logs along with the a generated message id and the module executable. \ No newline at end of file +The host captures all text written to standard out/err. Although modules can send text data to clients via the [TEXT](type_ids.md) frame, another way to do the same thing is to write to stdout. The host however treats stderr differently, it sends all text written to stderr to the host logging system. On Linux systems, syslog is used. On windows systems, a local log file in %PROGRAMDATA%\mrci\messages.log is used. When logging messages, the host will send a generic error message to the client but the full error details will be sent to the logs along with the a generated message id and the module executable. diff --git a/docs/shared_data.md b/docs/shared_data.md index a181b32..11a7d89 100644 --- a/docs/shared_data.md +++ b/docs/shared_data.md @@ -42,7 +42,7 @@ notes: | Data | Type | Vaild Size Range | Forbidden Chars | Must Contain | | ---------------------- | ------ | ---------------- | ------------------ | ----------------------------------------------------------- | -| Module Executable Path | String | 1-512 | ```|*:\"?<>``` | | +| Module Executable Path | String | 1-512 | ```\|*:"?<>``` | | | User Name | String | 2-24 | spaces or newlines | | | Email Address | String | 4-64 | spaces or newlines | ```@``` and ```.``` | | Password | String | 8-200 | | special chars, numbers, capital letters and common letters. | diff --git a/src/common.h b/src/common.h index 43f2cc2..32a8a7b 100644 --- a/src/common.h +++ b/src/common.h @@ -80,7 +80,7 @@ #include "mem_share.h" #define APP_NAME "MRCI" -#define APP_VER "5.1.2.1" +#define APP_VER "5.2.2.1" #define APP_TARGET "mrci" #define SERVER_HEADER_TAG "MRCI" #define HOST_CONTROL_PIPE "MRCI_HOST_CONTROL" @@ -111,8 +111,8 @@ #define DEFAULT_DB_DRIVER "QSQLITE" #define DEFAULT_DB_FILENAME "data.db" #define DEFAULT_LOG_FILENAME "messages.log" -#define DEFAULT_CERT_FILENAME "tls_chain.pem" -#define DEFAULT_PRIV_FILENAME "tls_priv.pem" +#define DEFAULT_CERT_FILENAME "tls_chain.crt" +#define DEFAULT_PRIV_FILENAME "tls_priv.key" #define DEFAULT_RES_PW_FILENAME "res_pw_template.txt" #define DEFAULT_EVERIFY_FILENAME "email_verify_template.txt" #define DEFAULT_CONFIRM_SUBJECT "Email Verification" diff --git a/src/main.cpp b/src/main.cpp index ba580ec..d6cae92 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -186,8 +186,6 @@ int main(int argc, char *argv[]) QCoreApplication::setApplicationName(APP_NAME); QCoreApplication::setApplicationVersion(APP_VER); - // args.append("-ls_sql_drvs"); // debug - if (args.contains("-run_cmd", Qt::CaseInsensitive) || args.contains("-public_cmds", Qt::CaseInsensitive) || args.contains("-exempt_cmds", Qt::CaseInsensitive) || diff --git a/src/make_cert.cpp b/src/make_cert.cpp index 23e90a5..62e12ce 100644 --- a/src/make_cert.cpp +++ b/src/make_cert.cpp @@ -16,217 +16,65 @@ // along with MRCI_Client under the LICENSE.md file. If not, see // . -Cert::Cert(QObject *parent) : QObject(parent) -{ - pKey = EVP_PKEY_new(); - x509 = X509_new(); - bne = BN_new(); - rsa = RSA_new(); -} - -void Cert::cleanup() -{ - EVP_PKEY_free(pKey); - X509_free(x509); - BN_free(bne); -} - -bool genRSAKey(Cert *cert, QTextStream &msg) -{ - bool ret = false; - - if (cert->pKey && cert->bne && cert->rsa) - { - if (BN_set_word(cert->bne, RSA_F4)) - { - if (RSA_generate_key_ex(cert->rsa, 2048, cert->bne, NULL)) - { - if (EVP_PKEY_assign_RSA(cert->pKey, cert->rsa)) - { - ret = true; - } - else - { - msg << "Failed to assign the generated RSA key to a PKEY object." << Qt::endl; - } - } - else - { - msg << "Failed to generate the RSA private key." << Qt::endl; - } - } - else - { - msg << "Failed to initialize a BIGNUM object needed to generate the RSA key." << Qt::endl; - } - } - else - { - msg << "The x509 object did not initialize correctly." << Qt::endl; - } - - return ret; -} - -bool genX509(Cert *cert, const QString &outsideAddr, QTextStream &msg) -{ - auto ret = false; - auto interfaces = QNetworkInterface::allAddresses(); - - QList cnNames; - - if (!outsideAddr.isEmpty()) - { - msg << "x509 gen_wan_ip: " << outsideAddr << Qt::endl; - - cnNames.append(outsideAddr.toUtf8()); - } - - for (auto&& addr : interfaces) - { - if (addr.isGlobal()) - { - msg << "x509 gen_lan_ip: " << addr.toString() << Qt::endl; - - cnNames.append(addr.toString().toUtf8()); - } - } - - if (cert->x509 && cert->pKey && !cnNames.isEmpty()) - { - ASN1_INTEGER_set(X509_get_serialNumber(cert->x509), QDateTime::currentDateTime().toSecsSinceEpoch()); - - X509_gmtime_adj(X509_get_notBefore(cert->x509), 0); // now - X509_gmtime_adj(X509_get_notAfter(cert->x509), 31536000L); // 365 days - X509_set_pubkey(cert->x509, cert->pKey); - - // copy the subject name to the issuer name. - - auto *name = X509_get_subject_name(cert->x509); - - X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *) cnNames[0].data(), -1, -1, 0); - - X509_set_issuer_name(cert->x509, name); - - cnNames.removeAt(0); - - QByteArray sanField; - - for (int i = 0; i < cnNames.size(); ++i) - { - sanField.append("DNS:" + cnNames[i]); - - if (i != cnNames.size() - 1) - { - sanField.append(", "); - } - } - - if (!sanField.isEmpty()) - { - addExt(cert->x509, NID_subject_alt_name, sanField.data()); - } - - if (X509_sign(cert->x509, cert->pKey, EVP_sha1())) - { - ret = true; - } - else - { - msg << "Failed to self-sign the generated x509 cert." << Qt::endl; - } - } - else - { - msg << "No usable IP addresses could be found to be used as common names in the self-signed cert." << Qt::endl; - } - - return ret; -} - -void addExt(X509 *cert, int nid, char *value) -{ - X509_EXTENSION *ext = X509V3_EXT_conf_nid(NULL, NULL, nid, value); - - if (ext != NULL) - { - X509_add_ext(cert, ext, -1); - X509_EXTENSION_free(ext); - } -} - -FILE *openFileForWrite(const char *path, QTextStream &msg) -{ - auto file = fopen(path, "wb"); - - if (!file) - { - msg << "Cannot open file: '" << path << "' for writing. " << strerror(errno); - } - - return file; -} - -void encodeErr(const char *path, QTextStream &msg) -{ - msg << "Failed to encode file '" << path << "' to PEM format." << Qt::endl; -} - -bool writePrivateKey(const char *path, Cert* cert, QTextStream &msg) -{ - auto ret = false; - FILE *file = openFileForWrite(path, msg); - - if (file) - { - if (PEM_write_PrivateKey(file, cert->pKey, NULL, NULL, 0, NULL, NULL)) - { - ret = true; - } - else - { - encodeErr(path, msg); - } - } - - fclose(file); - - return ret; -} - -bool writeX509(const char *path, Cert *cert, QTextStream &msg) -{ - auto ret = false; - FILE *file = openFileForWrite(path, msg); - - if (file) - { - if (PEM_write_X509(file, cert->x509)) - { - ret = true; - } - else - { - encodeErr(path, msg); - } - } - - fclose(file); - - return ret; -} - bool genDefaultSSLFiles(const QString &outsideAddr, QTextStream &msg) { - auto *cert = new Cert(); - auto ret = genRSAKey(cert, msg); + QProcess proc; + QStringList args; - if (ret) ret = genX509(cert, outsideAddr, msg); - if (ret) ret = writePrivateKey(DEFAULT_PRIV_FILENAME, cert, msg); - if (ret) ret = writeX509(DEFAULT_CERT_FILENAME, cert, msg); + args << "req"; + args << "-newkey" << "rsa:4096"; + args << "-x509"; + args << "-sha256"; + args << "-days" << "365"; + args << "-nodes"; + args << "-out" << getLocalFilePath(DEFAULT_CERT_FILENAME); + args << "-keyout" << getLocalFilePath(DEFAULT_PRIV_FILENAME); + args << "-subj" << "/CN=" + outsideAddr; - cert->cleanup(); - cert->deleteLater(); + proc.setProgram("openssl"); + proc.setArguments(args); + proc.start(); + + msg << "openssl args: " << args.join(' ') << Qt::endl; + + auto ret = false; + + if (!proc.waitForStarted()) + { + msg << "err: openssl failed to start, reason: " << proc.errorString() << Qt::endl; + } + else + { + proc.waitForFinished(); + + msg << "--openssl-output--" << Qt::endl; + + auto outText = proc.readAllStandardOutput(); + auto errText = proc.readAllStandardError(); + + msg << "std-out:" << Qt::endl; + + for (auto line : outText.split('\n')) + { + msg << line << Qt::endl; + } + + msg << "std-err:" << Qt::endl; + + for (auto line: errText.split('\n')) + { + msg << line << Qt::endl; + } + + if (proc.exitCode() != 0) + { + msg << "err: openssl failed with return code: " << proc.exitCode() << Qt::endl; + } + else + { + ret = true; + } + } return ret; } diff --git a/src/make_cert.h b/src/make_cert.h index 7d45051..f73516f 100644 --- a/src/make_cert.h +++ b/src/make_cert.h @@ -17,46 +17,8 @@ // along with MRCI_Client under the LICENSE.md file. If not, see // . -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include "common.h" -class Cert : public QObject -{ - Q_OBJECT - -public: - - EVP_PKEY *pKey; - X509 *x509; - BIGNUM *bne; - RSA *rsa; - - void cleanup(); - - explicit Cert(QObject *parent = nullptr); -}; - -FILE *openFileForWrite(const char *path, QTextStream &msg); -bool genRSAKey(Cert *cert, QTextStream &msg); -bool genX509(Cert *cert, const QString &outsideAddr, QTextStream &msg); -bool writePrivateKey(const char *path, Cert *cert, QTextStream &msg); -bool writeX509(const char *path, Cert *cert, QTextStream &msg); -bool genDefaultSSLFiles(const QString &outsideAddr, QTextStream &msg); -void addExt(X509 *cert, int nid, char *value); -void encodeErr(const char *path, QTextStream &msg); +bool genDefaultSSLFiles(const QString &outsideAddr, QTextStream &msg); #endif // MAKE_CERT_H diff --git a/src/tcp_server.cpp b/src/tcp_server.cpp index 892d266..b4abd19 100644 --- a/src/tcp_server.cpp +++ b/src/tcp_server.cpp @@ -26,6 +26,8 @@ TCPServer::TCPServer(QObject *parent) : QTcpServer(parent) controlSocket = nullptr; flags = 0; + controlPipe->setSocketOptions(QLocalServer::OtherAccessOption); + #ifdef Q_OS_LINUX setupUnixSignalHandlers(); diff --git a/src/unix_signal.cpp b/src/unix_signal.cpp index a68c3ba..c02368a 100644 --- a/src/unix_signal.cpp +++ b/src/unix_signal.cpp @@ -55,15 +55,23 @@ UnixSignalHandler::UnixSignalHandler(QObject *parent) : QObject(parent) void UnixSignalHandler::hupSignalHandler(int) { char chr = 1; + auto sz = static_cast(sizeof(chr)); - write(sighupFd[0], &chr, sizeof(chr)); + if (write(sighupFd[0], &chr, sz) != sz) + { + QTextStream(stderr) << "err: Failed write out HUP signal in the signal handler." << Qt::endl; + } } void UnixSignalHandler::termSignalHandler(int) { char chr = 1; + auto sz = static_cast(sizeof(chr)); - write(sigtermFd[0], &chr, sizeof(chr)); + if (write(sigtermFd[0], &chr, sz) != sz) + { + QTextStream(stderr) << "err: Failed write out TERM signal in the signal handler." << Qt::endl; + } } void UnixSignalHandler::handleSigTerm() @@ -71,8 +79,12 @@ void UnixSignalHandler::handleSigTerm() snTerm->setEnabled(false); char chr; + auto sz = static_cast(sizeof(chr)); - read(sigtermFd[1], &chr, sizeof(chr)); + if (read(sigtermFd[1], &chr, sz) != sz) + { + QTextStream(stderr) << "wrn: Failed to read TERM signal data, however this should not affect signal handler operation." << Qt::endl; + } emit closeServer(); @@ -84,8 +96,12 @@ void UnixSignalHandler::handleSigHup() snHup->setEnabled(false); char chr; + auto sz = static_cast(sizeof(chr)); - read(sighupFd[1], &chr, sizeof(chr)); + if (read(sighupFd[1], &chr, sz) != sz) + { + QTextStream(stderr) << "wrn: Failed to read HUP signal data, however this should not affect signal handler operation." << Qt::endl; + } emit closeServer(); diff --git a/src/unix_signal.h b/src/unix_signal.h index 2eb0b6b..3bb3c4b 100644 --- a/src/unix_signal.h +++ b/src/unix_signal.h @@ -22,6 +22,7 @@ #ifdef Q_OS_LINUX #include +#include #include #include #include