Moved logging out of the host database and a few bug fixes

The host will no longer store logs in the database. Instead,
logging is done by syslog if running linux. If running
Windows, logs are now stored in a local file in the app
config directory.

Fixed a bug with -add_admin that would fail to create more
than one admin accounts in sequence due to the blank email
addresses being blank. It will now create fake email
addresses unique to each admin account.

Added -res_pw to reset user acccount passwords from the CLI
if needed.

Logging was also expanded to capture and log all failures
reported by all modules stderr output.

Updated build.py and install.py for QT6 support and moved
the linux .service file from /etc to /lib to conform to
systemd standards.

Removed the ls_dbg command because in database logging is
no longer done.
This commit is contained in:
Maurice ONeal 2021-02-27 11:19:44 -05:00
parent c8f53d1e5c
commit 508af40359
30 changed files with 438 additions and 295 deletions

View File

@ -112,10 +112,23 @@ def verbose_copy(src, dst):
if os.path.exists(dst) and os.path.isdir(dst): if os.path.exists(dst) and os.path.isdir(dst):
shutil.rmtree(dst) shutil.rmtree(dst)
shutil.copytree(src, dst) try:
# ignore errors thrown by shutil.copytree()
# it's likely not actually failing to copy
# the directory but still throws errors if
# it fails to apply the same file stats as
# the source. this type of errors can be
# ignored.
shutil.copytree(src, dst)
except:
pass
elif os.path.exists(src):
shutil.copyfile(src, dst)
else: else:
shutil.copyfile(src, dst) 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):
if not os.path.exists("app_dir/linux/sqldrivers"): if not os.path.exists("app_dir/linux/sqldrivers"):
@ -127,6 +140,7 @@ def linux_build_app_dir(app_ver, app_name, app_target, qt_bin):
verbose_copy(qt_bin + "/../plugins/sqldrivers/libqsqlite.so", "app_dir/linux/sqldrivers/libqsqlite.so") 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/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/libqsqlpsql.so", "app_dir/linux/sqldrivers/libqsqlpsql.so")
verbose_copy(qt_bin + "/../plugins/sqldrivers/libqsqlmysql.so", "app_dir/linux/sqldrivers/libqsqlmysql.so")
verbose_copy("build/linux/" + app_target, "app_dir/linux/" + app_target) verbose_copy("build/linux/" + app_target, "app_dir/linux/" + app_target)
shutil.copyfile("build/linux/" + app_target, "/tmp/" + app_target) shutil.copyfile("build/linux/" + app_target, "/tmp/" + app_target)
@ -226,9 +240,18 @@ def main():
else: else:
cd() cd()
if platform.system() == "Linux":
if os.path.exists("build/linux"):
shutil.rmtree("build/linux")
elif platform.system() == "Windows":
if os.path.exists("build/windows"):
shutil.rmtree("build/windows")
result = subprocess.run([qt_bin + os.sep + "qmake", "-config", "release"]) result = subprocess.run([qt_bin + os.sep + "qmake", "-config", "release"])
if result.returncode == 0: if result.returncode == 0:
result = subprocess.run([maker]) result = subprocess.run([maker])
if result.returncode == 0: if result.returncode == 0:

View File

@ -31,7 +31,6 @@
<file>docs/intern_commands/ls_auth_log.md</file> <file>docs/intern_commands/ls_auth_log.md</file>
<file>docs/intern_commands/ls_ch_members.md</file> <file>docs/intern_commands/ls_ch_members.md</file>
<file>docs/intern_commands/ls_chs.md</file> <file>docs/intern_commands/ls_chs.md</file>
<file>docs/intern_commands/ls_dbg.md</file>
<file>docs/intern_commands/ls_mods.md</file> <file>docs/intern_commands/ls_mods.md</file>
<file>docs/intern_commands/ls_open_chs.md</file> <file>docs/intern_commands/ls_open_chs.md</file>
<file>docs/intern_commands/ls_p2p.md</file> <file>docs/intern_commands/ls_p2p.md</file>

View File

@ -65,7 +65,7 @@ Typical use for a MRCI host is to run commands on a remote host that clients ask
Because the host is modular, the things you can customize it to do is almost limitless by simply adding more commands. Because the host is modular, the things you can customize it to do is almost limitless by simply adding more commands.
** The email system of this application depends on external email clients that run on the command line. The default is [mutt](http://www.mutt.org/). If you want emails to work out of the box, consider installing and configuring mutt. It just needs to be configured with a smtp account to send emails with. You don't have to use mutt though, the host does have options to change the email client to any other application that has a command line interface. ** The email system of this application depends on external email clients that run on the command line. The default is [mutt](http://www.mutt.org/). If you want emails to work out of the box, consider installing and configuring mutt. It just needs to be configured with a smtp account to send emails with. You don't have to use mutt though, the host does have the option to change the email client to any other application that has a command line interface.
### Documentation ### ### Documentation ###

View File

@ -281,7 +281,7 @@ This internal only async command doesn't carry any data. The session object norm
This internal only async command carries a [TEXT](type_ids.md) path that sets the working directory for the local session. All module processes started by the session will use this directory as the working directory and it is not shared among peer sessions. nothing happens if the path is invalid or does not exists. This internal only async command carries a [TEXT](type_ids.md) path that sets the working directory for the local session. All module processes started by the session will use this directory as the working directory and it is not shared among peer sessions. nothing happens if the path is invalid or does not exists.
```ASYNC_DEBUG_TEXT (44)``` ```ASYNC_DEBUG_TEXT (44)```
This internal only async command carries a [TEXT](type_ids.md) debug message to be logged into the host debug log from the module. Modules can use this to help with debugging issues if it doesn't have direct access to the host database. This internal only async command carries a [TEXT](type_ids.md) debug message to be logged into the host logging system. On Linux systems, syslog is used. On windows systems, a local log file in %PROGRAMDATA%\mrci\messages.log is used. This is useful if you want create custom log messages because it doesn't add any extras to the messages like the module path and message id. It is recommanded to have those extras added for easier debugging so module makers are encouraged simply use stderr output because the host will capture those messages for logging and will add the extras to them.
```ASYNC_HOOK_INPUT (45)``` ```ASYNC_HOOK_INPUT (45)```
This async command doesn't carry any data. This just indicate to the local session that the module command is requesting to hook the tcp data input from the client. when the tcp input is hooked, all data sent from the client is redirected to the command object/process that initiated the hook until reqested to unhook. If the command that initiated the hook terminates in anyway with an active hook, the hook will automatically be removed. This async command doesn't carry any data. This just indicate to the local session that the module command is requesting to hook the tcp data input from the client. when the tcp input is hooked, all data sent from the client is redirected to the command object/process that initiated the hook until reqested to unhook. If the command that initiated the hook terminates in anyway with an active hook, the hook will automatically be removed.

View File

@ -106,7 +106,7 @@ db_driver : string
db_host_name : string db_host_name : string
This value contains the network, internet or file location address This value contains the network, internet address or file location
of the database. of the database.
db_password : string db_password : string

View File

@ -66,8 +66,6 @@ The host is extendable via 3rd party modules but the host itself is an internal
* [ls_chs](intern_commands/ls_chs.md) - list all channels you are currently a member of and all pending invites. * [ls_chs](intern_commands/ls_chs.md) - list all channels you are currently a member of and all pending invites.
* [ls_dbg](intern_commands/ls_dbg.md) - display all debug log messages.
* [ls_mods](intern_commands/ls_mods.md) - list all available modules currently configured in the host. * [ls_mods](intern_commands/ls_mods.md) - list all available modules currently configured in the host.
* [ls_open_chs](intern_commands/ls_open_chs.md) - list all of the sub-channels that are currently open. * [ls_open_chs](intern_commands/ls_open_chs.md) - list all of the sub-channels that are currently open.

View File

@ -1,16 +0,0 @@
### Summary ###
display all debug log messages.
### IO ###
```[{search_terms} {-delete}]/[text]```
### Description ###
by default, all entries in the table are displayed in 50 entries per page. you can pass the column names as -column_name (text) to refine your search to specific entries. this command can handle the following columns:
-time_stamp
-log_entry
you can also pass -delete that will cause the command to delete the entries instead of displaying them. note: passing no search terms with this option will delete all entries in the table.

View File

@ -54,3 +54,7 @@ notes:
* Modules called with -run_cmd does not need to terminate after running the requested command, instead it must send an [IDLE](type_ids.md) frame to indicate that the command is finished when it eventually does finish. This is desired because not only it tells the client that the command is finished but it also makes it so the session doesn't need to recreate the module process on every subsequent call to the command. * Modules called with -run_cmd does not need to terminate after running the requested command, instead it must send an [IDLE](type_ids.md) frame to indicate that the command is finished when it eventually does finish. This is desired because not only it tells the client that the command is finished but it also makes it so the session doesn't need to recreate the module process on every subsequent call to the command.
* The session will send a [KILL_CMD](type_ids.md) to the module after 2 mins of being idle (no IPC/Pipe activity). The module will have 3 seconds to do this before it is force killed. This will also happen when the user ends the session and the module process needs to terminate. Modules must still send an [IDLE](type_ids.md) frame to indicate the command is finished if a command was running. * The session will send a [KILL_CMD](type_ids.md) to the module after 2 mins of being idle (no IPC/Pipe activity). The module will have 3 seconds to do this before it is force killed. This will also happen when the user ends the session and the module process needs to terminate. Modules must still send an [IDLE](type_ids.md) frame to indicate the command is finished if a command was running.
### 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.

View File

@ -160,7 +160,7 @@ def local_install(app_target, app_name):
template_to_deploy("app_dir/linux/" + app_target + ".sh", install_dir + "/" + app_target + ".sh", install_dir, app_name, app_target) template_to_deploy("app_dir/linux/" + app_target + ".sh", install_dir + "/" + app_target + ".sh", install_dir, app_name, app_target)
template_to_deploy("app_dir/linux/uninstall.sh", install_dir + "/uninstall.sh", install_dir, app_name, app_target) template_to_deploy("app_dir/linux/uninstall.sh", install_dir + "/uninstall.sh", install_dir, app_name, app_target)
template_to_deploy("app_dir/linux/" + app_target + ".service", "/etc/systemd/system/" + app_target + ".service", install_dir, app_name, app_target) template_to_deploy("app_dir/linux/" + app_target + ".service", "/lib/systemd/system/" + app_target + ".service", install_dir, app_name, app_target)
verbose_copy("app_dir/linux/" + app_target, install_dir + "/" + app_target) verbose_copy("app_dir/linux/" + app_target, install_dir + "/" + app_target)
verbose_copy("app_dir/linux/lib", install_dir + "/lib") verbose_copy("app_dir/linux/lib", install_dir + "/lib")
@ -170,7 +170,7 @@ def local_install(app_target, app_name):
subprocess.run(["useradd", "-r", app_target]) subprocess.run(["useradd", "-r", app_target])
subprocess.run(["chmod", "-R", "755", install_dir]) subprocess.run(["chmod", "-R", "755", install_dir])
subprocess.run(["chmod", "755", "/etc/systemd/system/" + app_target + ".service"]) subprocess.run(["chmod", "644", "/lib/systemd/system/" + app_target + ".service"])
subprocess.run(["chown", "-R", app_target + ":" + app_target, "/var/opt/" + app_target]) subprocess.run(["chown", "-R", app_target + ":" + app_target, "/var/opt/" + app_target])
subprocess.run(["chown", "-R", app_target + ":" + app_target, "/etc/" + app_target]) subprocess.run(["chown", "-R", app_target + ":" + app_target, "/etc/" + app_target])
subprocess.run(["systemctl", "start", app_target]) subprocess.run(["systemctl", "start", app_target])

View File

@ -79,6 +79,7 @@ CmdObject::CmdObject(QObject *parent) : MemShare(parent)
ipcWorker = new IPCWorker(pipe, nullptr); ipcWorker = new IPCWorker(pipe, nullptr);
keepAliveTimer = new QTimer(this); keepAliveTimer = new QTimer(this);
progTimer = new QTimer(this); progTimer = new QTimer(this);
dProc = new QProcess(this);
progCurrent = 0; progCurrent = 0;
progMax = 0; progMax = 0;
@ -120,6 +121,11 @@ void CmdObject::term()
flags = 0; flags = 0;
retCode = ABORTED; retCode = ABORTED;
if (dProc->state() == QProcess::Running)
{
dProc->kill();
}
onTerminate(); onTerminate();
postProc(); postProc();
} }
@ -132,6 +138,30 @@ void CmdObject::kill()
QCoreApplication::instance()->exit(); QCoreApplication::instance()->exit();
} }
bool CmdObject::runDetachedProc(const QStringList &args)
{
auto ret = false;
if (args.isEmpty())
{
qCritical() << "The external command call has no arguments.";
}
else
{
dProc->setProgram(args[0]);
dProc->setArguments(args.mid(1));
ret = dProc->startDetached();
if (!ret)
{
qCritical() << "External command execution failure: " + dProc->errorString();
}
}
return ret;
}
void CmdObject::preProc(const QByteArray &data, quint8 typeId) void CmdObject::preProc(const QByteArray &data, quint8 typeId)
{ {
if (typeId == TERM_CMD) if (typeId == TERM_CMD)

View File

@ -67,6 +67,7 @@ protected:
QTimer *keepAliveTimer; QTimer *keepAliveTimer;
QTimer *progTimer; QTimer *progTimer;
IPCWorker *ipcWorker; IPCWorker *ipcWorker;
QProcess *dProc;
quint32 flags; quint32 flags;
quint16 retCode; quint16 retCode;
qint64 progCurrent; qint64 progCurrent;
@ -81,6 +82,7 @@ protected:
void startProgPulse(); void startProgPulse();
void stopProgPulse(); void stopProgPulse();
void postProc(); void postProc();
bool runDetachedProc(const QStringList &args);
QString libName(); QString libName();
virtual void procIn(const QByteArray &, quint8) {} virtual void procIn(const QByteArray &, quint8) {}

View File

@ -47,9 +47,18 @@ ModProcess::ModProcess(const QString &app, const QString &memSes, const QString
setProgram(app); setProgram(app);
} }
void ModProcess::logErrMsgs(quint32 id)
{
auto msgId = genMsgNumber();
emit dataToClient(id, "The command module generated an error, msg_id: " + msgId.toUtf8() + "\n", ERR);
qCritical() << "Module: " + program() + " " + readAllStandardError() + " msg_id: " + msgId.toUtf8();
}
void ModProcess::rdFromStdErr() void ModProcess::rdFromStdErr()
{ {
emit dataToClient(toCmdId32(ASYNC_SYS_MSG, 0), readAllStandardError(), ERR); logErrMsgs(toCmdId32(ASYNC_SYS_MSG, 0));
} }
void ModProcess::rdFromStdOut() void ModProcess::rdFromStdOut()
@ -71,8 +80,9 @@ quint16 ModProcess::genCmdId()
QString ModProcess::makeCmdUnique(const QString &name) QString ModProcess::makeCmdUnique(const QString &name)
{ {
QString strNum; QString strNum;
QStringList names = cmdUniqueNames->values();
auto names = cmdUniqueNames->values();
for (int i = 1; names.contains(name + strNum); ++i) for (int i = 1; names.contains(name + strNum); ++i)
{ {
@ -180,7 +190,7 @@ void ModProcess::onDataFromProc(quint8 typeId, const QByteArray &data)
} }
else if (typeId == ERR) else if (typeId == ERR)
{ {
qDebug() << QString::fromUtf8(data); qCritical() << "Module: " << program() << " - " << QString::fromUtf8(data);
} }
} }
@ -267,7 +277,6 @@ void ModProcess::setSessionParams(QHash<quint16, QString> *uniqueNames,
void ModProcess::onFailToStart() void ModProcess::onFailToStart()
{ {
emit dataToClient(toCmdId32(ASYNC_SYS_MSG, 0), "\nerr: A module failed to start so some commands may not have loaded. detailed error information was logged for admin review.\n", ERR);
emit modProcFinished(); emit modProcFinished();
deleteLater(); deleteLater();
@ -277,7 +286,8 @@ void ModProcess::err(QProcess::ProcessError error)
{ {
if (error == QProcess::FailedToStart) if (error == QProcess::FailedToStart)
{ {
qDebug() << "err: Module process: " << program() << " failed to start. reason: " << errorString(); qCritical() << "Module process: " << program() << " failed to start. reason: " << errorString();
qCritical() << "Args in use: " << arguments().join(' ');
onFailToStart(); onFailToStart();
} }
@ -439,7 +449,6 @@ void CmdProcess::onReady()
void CmdProcess::onFailToStart() void CmdProcess::onFailToStart()
{ {
emit dataToClient(cmdId, "err: The command failed to start. error details were logged for admin review.\n", ERR);
emit dataToClient(cmdId, wrInt(FAILED_TO_START, 16), IDLE); emit dataToClient(cmdId, wrInt(FAILED_TO_START, 16), IDLE);
emit cmdProcFinished(cmdId); emit cmdProcFinished(cmdId);
@ -453,7 +462,9 @@ void CmdProcess::onFinished(int exitCode, QProcess::ExitStatus exitStatus)
if (!cmdIdle) if (!cmdIdle)
{ {
emit dataToClient(cmdId, "err: The command has stopped unexpectedly or it has failed to send an IDLE frame before exiting.\n", ERR); qCritical() << "Module: " + program() + "Command: '" + cmdName + "' has crashed or failed to return an IDLE frame when it terminated.";
emit dataToClient(cmdId, "err: The command '" + cmdName.toUtf8() + "' has stopped unexpectedly.\n", ERR);
emit dataToClient(cmdId, wrInt(CRASH, 16), IDLE); emit dataToClient(cmdId, wrInt(CRASH, 16), IDLE);
} }
@ -463,16 +474,16 @@ void CmdProcess::onFinished(int exitCode, QProcess::ExitStatus exitStatus)
deleteLater(); deleteLater();
} }
void CmdProcess::rdFromStdErr()
{
emit dataToClient(cmdId, readAllStandardError(), ERR);
}
void CmdProcess::rdFromStdOut() void CmdProcess::rdFromStdOut()
{ {
emit dataToClient(cmdId, readAllStandardOutput(), TEXT); emit dataToClient(cmdId, readAllStandardOutput(), TEXT);
} }
void CmdProcess::rdFromStdErr()
{
logErrMsgs(cmdId);
}
void CmdProcess::dataFromSession(quint32 id, const QByteArray &data, quint8 dType) void CmdProcess::dataFromSession(quint32 id, const QByteArray &data, quint8 dType)
{ {
if (id == cmdId) if (id == cmdId)
@ -673,7 +684,7 @@ void CmdProcess::onDataFromProc(quint8 typeId, const QByteArray &data)
} }
else else
{ {
qDebug() << "async id: " << async << " from command id: " << toCmdId16(cmdId) << " blocked. reason: " << errMsg; qCritical() << "async id: " << async << " from command id/name: " << toCmdId16(cmdId) << "/" << cmdName << " blocked. reason: " << errMsg;
} }
} }
} }

View File

@ -57,6 +57,7 @@ protected:
virtual void onDataFromProc(quint8 typeId, const QByteArray &data); virtual void onDataFromProc(quint8 typeId, const QByteArray &data);
void cleanupPipe(); void cleanupPipe();
void logErrMsgs(quint32 id);
void wrIpcFrame(quint8 typeId, const QByteArray &data); void wrIpcFrame(quint8 typeId, const QByteArray &data);
bool startProc(const QStringList &args); bool startProc(const QStringList &args);
bool isCmdLoaded(const QString &name); bool isCmdLoaded(const QString &name);
@ -125,8 +126,8 @@ private:
private slots: private slots:
void rdFromStdErr();
void rdFromStdOut(); void rdFromStdOut();
void rdFromStdErr();
public slots: public slots:

View File

@ -16,15 +16,15 @@
// along with MRCI under the LICENSE.md file. If not, see // along with MRCI under the LICENSE.md file. If not, see
// <http://www.gnu.org/licenses/>. // <http://www.gnu.org/licenses/>.
RecoverAcct::RecoverAcct(QObject *parent) : CmdObject(parent) {} RecoverAcct::RecoverAcct(QObject *parent) : CmdObject(parent) {}
ResetPwRequest::ResetPwRequest(QObject *parent) : CmdObject(parent) {} IsEmailVerified::IsEmailVerified(QObject *parent) : CmdObject(parent) {}
VerifyEmail::VerifyEmail(QObject *parent) : CmdObject(parent) {} ResetPwRequest::ResetPwRequest(QObject *parent) : CmdObject(parent) {}
IsEmailVerified::IsEmailVerified(QObject *parent) : CmdObject(parent) {} VerifyEmail::VerifyEmail(QObject *parent) : CmdObject(parent) {}
QString RecoverAcct::cmdName() {return "recover_acct";} QString RecoverAcct::cmdName() {return "recover_acct";}
QString ResetPwRequest::cmdName() {return "request_pw_reset";} QString ResetPwRequest::cmdName() {return "request_pw_reset";}
QString VerifyEmail::cmdName() {return "verify_email";} QString VerifyEmail::cmdName() {return "verify_email";}
QString IsEmailVerified::cmdName() {return "is_email_verified";} QString IsEmailVerified::cmdName() {return "is_email_verified";}
void delRecoverPw(const QByteArray &uId) void delRecoverPw(const QByteArray &uId)
{ {
@ -55,7 +55,7 @@ bool expired(const QByteArray &uId)
auto expiry = db.getData(COLUMN_TIME).toDateTime().addSecs(3600); // pw datetime + 1hour; auto expiry = db.getData(COLUMN_TIME).toDateTime().addSecs(3600); // pw datetime + 1hour;
if (expiry > QDateTime::currentDateTime().toUTC()) if (expiry > QDateTime::currentDateTimeUtc())
{ {
ret = false; ret = false;
} }
@ -71,6 +71,7 @@ void RecoverAcct::addToThreshold()
{ {
Query db(this); Query db(this);
// log recovery attempt failure
db.setType(Query::PUSH, TABLE_AUTH_LOG); db.setType(Query::PUSH, TABLE_AUTH_LOG);
db.addColumn(COLUMN_USER_ID, uId); db.addColumn(COLUMN_USER_ID, uId);
db.addColumn(COLUMN_IPADDR, rdStringFromBlock(clientIp, BLKSIZE_CLIENT_IP)); db.addColumn(COLUMN_IPADDR, rdStringFromBlock(clientIp, BLKSIZE_CLIENT_IP));
@ -83,6 +84,7 @@ void RecoverAcct::addToThreshold()
auto confObj = confObject(); auto confObj = confObject();
auto maxAttempts = confObj[CONF_AUTO_LOCK_LIM].toInt(); auto maxAttempts = confObj[CONF_AUTO_LOCK_LIM].toInt();
// pull how many failed recovery attempts were made on this account
db.setType(Query::PULL, TABLE_AUTH_LOG); db.setType(Query::PULL, TABLE_AUTH_LOG);
db.addColumn(COLUMN_IPADDR); db.addColumn(COLUMN_IPADDR);
db.addCondition(COLUMN_USER_ID, uId); db.addCondition(COLUMN_USER_ID, uId);
@ -95,11 +97,22 @@ void RecoverAcct::addToThreshold()
{ {
delRecoverPw(uId); delRecoverPw(uId);
flags &= ~MORE_INPUT; // reset recovery attempts
db.setType(Query::UPDATE, TABLE_AUTH_LOG);
db.addColumn(COLUMN_COUNT, false);
db.addCondition(COLUMN_USER_ID, uId);
db.addCondition(COLUMN_RECOVER_ATTEMPT, true);
db.addCondition(COLUMN_ACCEPTED, false);
db.exec();
retCode = INVALID_PARAMS;
flags &= ~MORE_INPUT;
errTxt("err: You have exceeded the maximum amount of recovery attempts, recovery password deleted.\n");
} }
else else
{ {
errTxt("err: Access denied.\n"); errTxt("err: Access denied.\n\n");
privTxt("Enter the temporary password (leave blank to cancel): "); privTxt("Enter the temporary password (leave blank to cancel): ");
} }
} }
@ -118,18 +131,42 @@ void RecoverAcct::procIn(const QByteArray &binIn, quint8 dType)
{ {
retCode = ABORTED; retCode = ABORTED;
flags &= ~MORE_INPUT; flags &= ~MORE_INPUT;
mainTxt("\n");
} }
else if (!acceptablePw(pw, uId, &errMsg)) else if (!acceptablePw(pw, uId, &errMsg))
{ {
errTxt(errMsg + "\n"); errTxt(errMsg + "\n\n");
privTxt("Enter a new password (leave blank to cancel): "); privTxt("Enter a new password (leave blank to cancel): ");
} }
else else
{ {
Query db(this);
// reset recovery attempts
db.setType(Query::UPDATE, TABLE_AUTH_LOG);
db.addColumn(COLUMN_COUNT, false);
db.addCondition(COLUMN_USER_ID, uId);
db.addCondition(COLUMN_RECOVER_ATTEMPT, true);
db.addCondition(COLUMN_ACCEPTED, false);
db.exec();
// log recovery accepted
db.setType(Query::PUSH, TABLE_AUTH_LOG);
db.addColumn(COLUMN_USER_ID, uId);
db.addColumn(COLUMN_IPADDR, rdStringFromBlock(clientIp, BLKSIZE_CLIENT_IP));
db.addColumn(COLUMN_AUTH_ATTEMPT, false);
db.addColumn(COLUMN_RECOVER_ATTEMPT, true);
db.addColumn(COLUMN_COUNT, false);
db.addColumn(COLUMN_ACCEPTED, true);
db.exec();
updatePassword(uId, pw, TABLE_USERS); updatePassword(uId, pw, TABLE_USERS);
delRecoverPw(uId); delRecoverPw(uId);
flags &= ~MORE_INPUT; flags &= ~MORE_INPUT;
mainTxt("\n");
} }
} }
else else
@ -138,9 +175,12 @@ void RecoverAcct::procIn(const QByteArray &binIn, quint8 dType)
{ {
retCode = ABORTED; retCode = ABORTED;
flags &= ~MORE_INPUT; flags &= ~MORE_INPUT;
mainTxt("\n");
} }
else if (!validPassword(pw)) else if (!validPassword(pw))
{ {
errTxt("err: Invalid password.\n");
addToThreshold(); addToThreshold();
} }
else if (!auth(uId, pw, TABLE_PW_RECOVERY)) else if (!auth(uId, pw, TABLE_PW_RECOVERY))
@ -209,7 +249,6 @@ void ResetPwRequest::procIn(const QByteArray &binIn, uchar dType)
QByteArray uId; QByteArray uId;
QString body; QString body;
QString err;
if (!email.isEmpty() && validEmailAddr(email)) if (!email.isEmpty() && validEmailAddr(email))
{ {
@ -218,11 +257,7 @@ void ResetPwRequest::procIn(const QByteArray &binIn, uchar dType)
retCode = INVALID_PARAMS; retCode = INVALID_PARAMS;
if (!getEmailParams(cmdLine, msgFile, &body, &err)) if (name.isEmpty() || !validUserName(name))
{
errTxt(err);
}
else if (name.isEmpty() || !validUserName(name))
{ {
errTxt("err: The -user or -email argument is empty, not found or invalid.\n"); errTxt("err: The -user or -email argument is empty, not found or invalid.\n");
} }
@ -230,40 +265,39 @@ void ResetPwRequest::procIn(const QByteArray &binIn, uchar dType)
{ {
errTxt("err: No such user.\n"); errTxt("err: No such user.\n");
} }
else else if (getEmailParams(cmdLine, msgFile, &body))
{ {
retCode = NO_ERRORS; retCode = EXECUTION_FAIL;
auto pw = genPw(); auto pw = genPw();
auto date = QDateTime::currentDateTimeUtc().toString("YYYY-MM-DD HH:MM:SS"); auto date = QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd HH:mm:ss (t)");
auto mailArgs = parseArgs(cmdLine.toUtf8(), -1); auto dbrdy = false;
if (recoverPWExists(uId)) if (recoverPWExists(uId))
{ {
updatePassword(uId, pw, TABLE_PW_RECOVERY); dbrdy = updatePassword(uId, pw, TABLE_PW_RECOVERY);
} }
else else
{ {
createTempPw(uId, pw); dbrdy = createTempPw(uId, pw);
} }
body.replace(DATE_SUB, date); if (dbrdy)
body.replace(USERNAME_SUB, name);
body.replace(OTP_SUB, pw);
cmdLine.replace(TARGET_EMAIL_SUB, email);
cmdLine.replace(SUBJECT_SUB, "'" + escapeChars(subject, '\\', '\'') + "'");
cmdLine.replace(MSG_SUB, "'" + escapeChars(body, '\\', '\'') + "'");
if (QProcess::startDetached(expandEnvVariables(mailArgs[0]), mailArgs.mid(1)))
{ {
mainTxt("A temporary password was sent to the email address associated with the account. this password will expire in 1 hour.\n"); body.replace(DATE_SUB, date);
} body.replace(USERNAME_SUB, name);
else body.replace(OTP_SUB, pw);
{
retCode = EXECUTION_FAIL;
errTxt("err: The host email system has reported an internal error, please try again later.\n"); cmdLine.replace(TARGET_EMAIL_SUB, email);
cmdLine.replace(SUBJECT_SUB, "'" + escapeChars(subject, '\\', '\'') + "'");
cmdLine.replace(MSG_SUB, "'" + escapeChars(body, '\\', '\'') + "'");
if (runDetachedProc(parseArgs(cmdLine.toUtf8(), -1)))
{
retCode = NO_ERRORS;
mainTxt("A temporary password was sent to the email address associated with the account. this password will expire in 1 hour.\n");
}
} }
} }
} }
@ -277,6 +311,8 @@ void VerifyEmail::procIn(const QByteArray &binIn, quint8 dType)
if (txt.isEmpty()) if (txt.isEmpty())
{ {
mainTxt("\n");
retCode = ABORTED; retCode = ABORTED;
flags &= ~MORE_INPUT; flags &= ~MORE_INPUT;
} }
@ -291,11 +327,14 @@ void VerifyEmail::procIn(const QByteArray &binIn, quint8 dType)
async(ASYNC_RW_MY_INFO, uId); async(ASYNC_RW_MY_INFO, uId);
flags &= ~MORE_INPUT; retCode = NO_ERRORS;
flags &= ~MORE_INPUT;
mainTxt("\n");
} }
else else
{ {
errTxt("err: The code you entered does not match.\n"); errTxt("err: The code you entered does not match.\n\n");
privTxt("Please try again: "); privTxt("Please try again: ");
} }
} }
@ -312,26 +351,15 @@ void VerifyEmail::procIn(const QByteArray &binIn, quint8 dType)
QString body; QString body;
QString err; QString err;
if (email.isEmpty()) retCode = INVALID_PARAMS;
{
retCode = INVALID_PARAMS;
errTxt("err: Your account currently has no email address, please contact an admin to update it.\n"); if (getEmailParams(cmdLine, msgFile, &body))
}
else if (!getEmailParams(cmdLine, msgFile, &body, &err))
{
retCode = INVALID_PARAMS;
errTxt(err);
}
else
{ {
flags |= MORE_INPUT; flags |= MORE_INPUT;
code = QString::number(QRandomGenerator::global()->bounded(100000, 999999)); code = QString::number(QRandomGenerator::global()->bounded(100000, 999999));
auto uName = rdStringFromBlock(userName, BLKSIZE_USER_NAME); auto uName = rdStringFromBlock(userName, BLKSIZE_USER_NAME);
auto date = QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd HH:mm:ss"); auto date = QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd HH:mm:ss (t)");
auto mailArgs = parseArgs(cmdLine.toUtf8(), -1);
body.replace(DATE_SUB, date); body.replace(DATE_SUB, date);
body.replace(USERNAME_SUB, uName); body.replace(USERNAME_SUB, uName);
@ -341,15 +369,13 @@ void VerifyEmail::procIn(const QByteArray &binIn, quint8 dType)
cmdLine.replace(SUBJECT_SUB, "'" + escapeChars(subject, '\\', '\'') + "'"); cmdLine.replace(SUBJECT_SUB, "'" + escapeChars(subject, '\\', '\'') + "'");
cmdLine.replace(MSG_SUB, "'" + escapeChars(body, '\\', '\'') + "'"); cmdLine.replace(MSG_SUB, "'" + escapeChars(body, '\\', '\'') + "'");
if (QProcess::startDetached(expandEnvVariables(mailArgs[0]), mailArgs.mid(1))) if (runDetachedProc(parseArgs(cmdLine.toUtf8(), -1)))
{ {
privTxt("A confirmation code was sent to your email address: " + email + "\n\n" + "Please enter that code now or leave blank to cancel: "); privTxt("A confirmation code was sent to your email address: " + email + "\n\n" + "Please enter that code now or leave blank to cancel: ");
} }
else else
{ {
retCode = EXECUTION_FAIL; retCode = EXECUTION_FAIL;
errTxt("err: The host email system has reported an internal error, please try again later.\n");
} }
} }
} }

View File

@ -38,6 +38,7 @@ void Auth::addToThreshold()
{ {
Query db(this); Query db(this);
// log the failed login attempt
db.setType(Query::PUSH, TABLE_AUTH_LOG); db.setType(Query::PUSH, TABLE_AUTH_LOG);
db.addColumn(COLUMN_USER_ID, uId); db.addColumn(COLUMN_USER_ID, uId);
db.addColumn(COLUMN_IPADDR, ip); db.addColumn(COLUMN_IPADDR, ip);
@ -49,6 +50,7 @@ void Auth::addToThreshold()
auto maxAttempts = confObject()[CONF_AUTO_LOCK_LIM].toInt(); auto maxAttempts = confObject()[CONF_AUTO_LOCK_LIM].toInt();
// pull all login attempts on the account
db.setType(Query::PULL, TABLE_AUTH_LOG); db.setType(Query::PULL, TABLE_AUTH_LOG);
db.addColumn(COLUMN_IPADDR); db.addColumn(COLUMN_IPADDR);
db.addCondition(COLUMN_USER_ID, uId); db.addCondition(COLUMN_USER_ID, uId);
@ -59,12 +61,24 @@ void Auth::addToThreshold()
if (db.rows() > maxAttempts) if (db.rows() > maxAttempts)
{ {
// reset login attempts
db.setType(Query::UPDATE, TABLE_AUTH_LOG);
db.addColumn(COLUMN_COUNT, false);
db.addCondition(COLUMN_COUNT, true);
db.addCondition(COLUMN_USER_ID, uId);
db.addCondition(COLUMN_AUTH_ATTEMPT, true);
db.exec();
// lock account
db.setType(Query::UPDATE, TABLE_USERS); db.setType(Query::UPDATE, TABLE_USERS);
db.addColumn(COLUMN_LOCKED, true); db.addColumn(COLUMN_LOCKED, true);
db.addCondition(COLUMN_USER_ID, uId); db.addCondition(COLUMN_USER_ID, uId);
db.exec(); db.exec();
flags &= ~MORE_INPUT; flags &= ~MORE_INPUT;
retCode = INVALID_PARAMS;
errTxt("err: Maximum login attempts exceeded, the account is now locked.\n");
} }
else else
{ {
@ -77,6 +91,7 @@ void Auth::confirmAuth()
{ {
Query db(this); Query db(this);
// reset login attempts
db.setType(Query::UPDATE, TABLE_AUTH_LOG); db.setType(Query::UPDATE, TABLE_AUTH_LOG);
db.addColumn(COLUMN_COUNT, false); db.addColumn(COLUMN_COUNT, false);
db.addCondition(COLUMN_COUNT, true); db.addCondition(COLUMN_COUNT, true);
@ -84,6 +99,7 @@ void Auth::confirmAuth()
db.addCondition(COLUMN_AUTH_ATTEMPT, true); db.addCondition(COLUMN_AUTH_ATTEMPT, true);
db.exec(); db.exec();
// log the login attempt as accepted
db.setType(Query::PUSH, TABLE_AUTH_LOG); db.setType(Query::PUSH, TABLE_AUTH_LOG);
db.addColumn(COLUMN_USER_ID, uId); db.addColumn(COLUMN_USER_ID, uId);
db.addColumn(COLUMN_IPADDR, ip); db.addColumn(COLUMN_IPADDR, ip);
@ -205,6 +221,7 @@ void Auth::procIn(const QByteArray &binIn, quint8 dType)
} }
else if (!validPassword(text)) else if (!validPassword(text))
{ {
errTxt("err: Invalid password.\n");
addToThreshold(); addToThreshold();
} }
else if (!auth(uId, text, TABLE_USERS)) else if (!auth(uId, text, TABLE_USERS))

View File

@ -26,13 +26,6 @@ IPHist::IPHist(QObject *parent) : TableViewer(parent)
addTableColumn(TABLE_IPHIST, COLUMN_LOGENTRY); addTableColumn(TABLE_IPHIST, COLUMN_LOGENTRY);
} }
ListDBG::ListDBG(QObject *parent) : TableViewer(parent)
{
setParams(TABLE_DMESG, true);
addTableColumn(TABLE_DMESG, COLUMN_TIME);
addTableColumn(TABLE_DMESG, COLUMN_LOGENTRY);
}
ListCommands::ListCommands(const QStringList &cmdList, QObject *parent) : CmdObject(parent) ListCommands::ListCommands(const QStringList &cmdList, QObject *parent) : CmdObject(parent)
{ {
list = cmdList; list = cmdList;
@ -43,7 +36,6 @@ MyInfo::MyInfo(QObject *parent) : CmdObject(parent) {}
QString HostInfo::cmdName() {return "host_info";} QString HostInfo::cmdName() {return "host_info";}
QString IPHist::cmdName() {return "ls_act_log";} QString IPHist::cmdName() {return "ls_act_log";}
QString ListDBG::cmdName() {return "ls_dbg";}
QString MyInfo::cmdName() {return "my_info";} QString MyInfo::cmdName() {return "my_info";}
QString ListCommands::shortText(const QString &cmdName) QString ListCommands::shortText(const QString &cmdName)

View File

@ -88,17 +88,4 @@ public:
explicit MyInfo(QObject *parent = nullptr); explicit MyInfo(QObject *parent = nullptr);
}; };
//-----------------------------------
class ListDBG : public TableViewer
{
Q_OBJECT
public:
static QString cmdName();
explicit ListDBG(QObject *parent = nullptr);
};
#endif // INFO_H #endif // INFO_H

View File

@ -218,7 +218,6 @@ void TableViewer::procIn(const QByteArray &binIn, quint8 dType)
{ {
delQuery.exec(); delQuery.exec();
mainTxt(delQuery.errDetail());
idle(); idle();
onDel(); onDel();
} }

View File

@ -150,20 +150,17 @@ void updateConf(const char *key, const QJsonValue &value)
updateConf(obj); updateConf(obj);
} }
bool getEmailParams(const QString &mailCmd, const QString &bodyFile, QString *bodyText, QString *errMsg) bool getEmailParams(const QString &mailCmd, const QString &bodyFile, QString *bodyText)
{ {
auto ret = false; auto ret = false;
errMsg->clear();
bodyText->clear(); bodyText->clear();
QFile file(bodyFile); QFile file(bodyFile);
if (!file.open(QFile::ReadOnly)) if (!file.open(QFile::ReadOnly))
{ {
errMsg->append("err: The host could not open the email message template, please notify a system administrator.\n"); qCritical() << "Could not open the email message template file '" << bodyFile << "' reason: " << file.errorString();
qDebug() << "err: Could not open the email message template file '" << bodyFile << "' reason: " << file.errorString();
} }
else else
{ {
@ -173,17 +170,14 @@ bool getEmailParams(const QString &mailCmd, const QString &bodyFile, QString *bo
(!body.contains(USERNAME_SUB, Qt::CaseInsensitive)) || (!body.contains(USERNAME_SUB, Qt::CaseInsensitive)) ||
(!body.contains(OTP_SUB, Qt::CaseInsensitive))) (!body.contains(OTP_SUB, Qt::CaseInsensitive)))
{ {
errMsg->append("err: The host email message template is invalid, please notify a system administrator.\n"); qCritical() << "Email message template '" << bodyFile << "' is missing one or more of the following key words: " << DATE_SUB << ", " << USERNAME_SUB << ", " << OTP_SUB;
qDebug() << "err: Email message template '" << bodyFile << "' is missing one of the following key words: " << DATE_SUB << ", " << USERNAME_SUB << ", " << OTP_SUB;
} }
else if ((mailCmd.contains(SUBJECT_SUB, Qt::CaseInsensitive)) || else if ((mailCmd.contains(SUBJECT_SUB, Qt::CaseInsensitive)) ||
(mailCmd.contains(MSG_SUB, Qt::CaseInsensitive)) || (mailCmd.contains(MSG_SUB, Qt::CaseInsensitive)) ||
(mailCmd.contains(TARGET_EMAIL_SUB, Qt::CaseInsensitive))) (mailCmd.contains(TARGET_EMAIL_SUB, Qt::CaseInsensitive)))
{ {
errMsg->append("err: The host email client parameters are invalid, please notify a system administrator.\n"); qCritical() << "Email client command line '" << mailCmd << "' is missing one or more of the following key words: " << SUBJECT_SUB << ", " << MSG_SUB << ", " << TARGET_EMAIL_SUB;
qCritical() << "Mail command: " << mailCmd;
qDebug() << "err: Email client command line '" << mailCmd << "' is missing one of the following key words: " << SUBJECT_SUB << ", " << MSG_SUB << ", " << TARGET_EMAIL_SUB;
} }
else else
{ {
@ -239,9 +233,16 @@ QString boolStr(bool state)
QString genSerialNumber() QString genSerialNumber()
{ {
Serial::serialIndex++; Serial::threadIndex++;
return QString::number(QDateTime::currentDateTime().toMSecsSinceEpoch()) + "-" + QString::number(Serial::serialIndex); return QString::number(QDateTime::currentDateTime().toMSecsSinceEpoch()) + "-" + QString::number(Serial::threadIndex);
}
QString genMsgNumber()
{
Serial::msgIndex++;
return QDateTime::currentDateTime().toString("yyyy.MM.dd.hh.mm.ss.") + QString::number(Serial::msgIndex).rightJustified(5, ' ', true);
} }
void serializeThread(QThread *thr) void serializeThread(QThread *thr)
@ -1053,7 +1054,7 @@ QString getParam(const QString &key, const QStringList &args)
QString ret; QString ret;
int pos = args.indexOf(QRegExp(key, Qt::CaseInsensitive)); int pos = args.indexOf(QRegularExpression(key, QRegularExpression::CaseInsensitiveOption));
if (pos != -1) if (pos != -1)
{ {
@ -1121,8 +1122,6 @@ ShellIPC::ShellIPC(const QStringList &args, bool supressErr, QObject *parent) :
bool ShellIPC::connectToHost() bool ShellIPC::connectToHost()
{ {
auto pipeInfo = QFileInfo(HOST_CONTROL_PIPE);
connectToServer(HOST_CONTROL_PIPE); connectToServer(HOST_CONTROL_PIPE);
if (!waitForConnected() && !holdErrs) if (!waitForConnected() && !holdErrs)
@ -1154,4 +1153,6 @@ void ShellIPC::dataIn()
emit closeInstance(); emit closeInstance();
} }
quint64 Serial::serialIndex = 0; quint64 Serial::threadIndex = 0;
quint16 Serial::msgIndex = 0;
bool Serial::msgDetails = true;

View File

@ -26,12 +26,11 @@
#include <QRandomGenerator> #include <QRandomGenerator>
#include <QProcess> #include <QProcess>
#include <QHash> #include <QHash>
#include <QRegExp> #include <QRegularExpression>
#include <QStringList> #include <QStringList>
#include <QDateTime> #include <QDateTime>
#include <QHostAddress> #include <QHostAddress>
#include <QCoreApplication> #include <QCoreApplication>
#include <QTextCodec>
#include <QFileInfo> #include <QFileInfo>
#include <QDir> #include <QDir>
#include <QSysInfo> #include <QSysInfo>
@ -71,7 +70,6 @@
#include <QSqlError> #include <QSqlError>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
#include <QTextCodec>
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QDateTime> #include <QDateTime>
@ -82,7 +80,7 @@
#include "mem_share.h" #include "mem_share.h"
#define APP_NAME "MRCI" #define APP_NAME "MRCI"
#define APP_VER "5.0.2.1" #define APP_VER "5.1.2.1"
#define APP_TARGET "mrci" #define APP_TARGET "mrci"
#define SERVER_HEADER_TAG "MRCI" #define SERVER_HEADER_TAG "MRCI"
#define HOST_CONTROL_PIPE "MRCI_HOST_CONTROL" #define HOST_CONTROL_PIPE "MRCI_HOST_CONTROL"
@ -91,6 +89,7 @@
#define LOCAL_BUFFSIZE 16777215 #define LOCAL_BUFFSIZE 16777215
#define CLIENT_HEADER_LEN 292 #define CLIENT_HEADER_LEN 292
#define MAX_LS_ENTRIES 50 #define MAX_LS_ENTRIES 50
#define MAX_LOG_SIZE 100000000
#define SUBJECT_SUB "%subject%" #define SUBJECT_SUB "%subject%"
#define MSG_SUB "%message_body%" #define MSG_SUB "%message_body%"
@ -111,6 +110,7 @@
#define DEFAULT_DB_DRIVER "QSQLITE" #define DEFAULT_DB_DRIVER "QSQLITE"
#define DEFAULT_DB_FILENAME "data.db" #define DEFAULT_DB_FILENAME "data.db"
#define DEFAULT_LOG_FILENAME "messages.log"
#define DEFAULT_CERT_FILENAME "tls_chain.pem" #define DEFAULT_CERT_FILENAME "tls_chain.pem"
#define DEFAULT_PRIV_FILENAME "tls_priv.pem" #define DEFAULT_PRIV_FILENAME "tls_priv.pem"
#define DEFAULT_RES_PW_FILENAME "res_pw_template.txt" #define DEFAULT_RES_PW_FILENAME "res_pw_template.txt"
@ -152,7 +152,6 @@
#define TABLE_CMD_RANKS "command_ranks" #define TABLE_CMD_RANKS "command_ranks"
#define TABLE_AUTH_LOG "auth_log" #define TABLE_AUTH_LOG "auth_log"
#define TABLE_PW_RECOVERY "pw_recovery" #define TABLE_PW_RECOVERY "pw_recovery"
#define TABLE_DMESG "host_debug_messages"
#define TABLE_CHANNELS "channels" #define TABLE_CHANNELS "channels"
#define TABLE_CH_MEMBERS "channel_members" #define TABLE_CH_MEMBERS "channel_members"
#define TABLE_SUB_CHANNELS "sub_channels" #define TABLE_SUB_CHANNELS "sub_channels"
@ -357,7 +356,7 @@ void containsActiveCh(const char *subChs, char *actBlock);
void updateConf(const char *key, const QJsonValue &value); void updateConf(const char *key, const QJsonValue &value);
void updateConf(const QJsonObject &obj); void updateConf(const QJsonObject &obj);
void wrDefaultMailTemplates(const QJsonObject &obj); void wrDefaultMailTemplates(const QJsonObject &obj);
bool getEmailParams(const QString &mailCmd, const QString &bodyFile, QString *bodyText, QString *errMsg); bool getEmailParams(const QString &mailCmd, const QString &bodyFile, QString *bodyText);
bool acceptablePw(const QString &pw, const QByteArray &uId, QString *errMsg); bool acceptablePw(const QString &pw, const QByteArray &uId, QString *errMsg);
bool acceptablePw(const QString &pw, const QString &uName, const QString &dispName, const QString &email, QString *errMsg); bool acceptablePw(const QString &pw, const QString &uName, const QString &dispName, const QString &email, QString *errMsg);
bool containsNewLine(const QString &str); bool containsNewLine(const QString &str);
@ -399,6 +398,7 @@ QString boolStr(bool state);
QString getParam(const QString &key, const QStringList &args); QString getParam(const QString &key, const QStringList &args);
QString escapeChars(const QString &str, const QChar &escapeChr, const QChar &chr); QString escapeChars(const QString &str, const QChar &escapeChr, const QChar &chr);
QString genSerialNumber(); QString genSerialNumber();
QString genMsgNumber();
QString getLocalFilePath(const QString &fileName, bool var = false); QString getLocalFilePath(const QString &fileName, bool var = false);
QStringList parseArgs(const QByteArray &data, int maxArgs, int *pos = nullptr); QStringList parseArgs(const QByteArray &data, int maxArgs, int *pos = nullptr);
QJsonObject confObject(); QJsonObject confObject();
@ -467,7 +467,9 @@ class Serial
public: public:
static quint64 serialIndex; static quint64 threadIndex;
static quint16 msgIndex;
static bool msgDetails;
}; };
#endif // COMMON_H #endif // COMMON_H

View File

@ -283,7 +283,7 @@ bool auth(const QByteArray &uId, const QString &password, const QString &table)
QCryptographicHash hasher(QCryptographicHash::Keccak_512); QCryptographicHash hasher(QCryptographicHash::Keccak_512);
hasher.addData(QTextCodec::codecForName("UTF-16LE")->fromUnicode(password) + salt); hasher.addData(password.toUtf8() + salt);
db.setType(Query::PULL, table); db.setType(Query::PULL, table);
db.addColumn(COLUMN_HASH); db.addColumn(COLUMN_HASH);
@ -348,35 +348,19 @@ Query::Query(QObject *parent) : QObject(parent)
if (!testDbWritable()) if (!testDbWritable())
{ {
queryOk = false; queryOk = false;
lastErr = "Write access to the database is denied.";
qCritical("%s", "Write access to the database is denied.");
} }
} }
else else
{ {
queryOk = false; queryOk = false;
lastErr = db.lastError().databaseText().trimmed();
qCritical("%s", db.lastError().databaseText().trimmed().toUtf8().constData());
} }
} }
} }
QString Query::errDetail()
{
QString ret;
QString errTxt = "none";
if (!lastErr.isEmpty())
{
errTxt = lastErr;
}
QTextStream txtOut(&ret);
txtOut << " driver error: " << errTxt << Qt::endl;
txtOut << " query: " << qStr << jStr << wStr << limit << Qt::endl;
return ret;
}
bool Query::inErrorstate() bool Query::inErrorstate()
{ {
return !queryOk; return !queryOk;
@ -440,7 +424,6 @@ void Query::setType(QueryType qType, const QString &tbl)
limit.clear(); limit.clear();
columnList.clear(); columnList.clear();
bindValues.clear(); bindValues.clear();
lastErr.clear();
directBind.clear(); directBind.clear();
whereBinds.clear(); whereBinds.clear();
columnsAsPassed.clear(); columnsAsPassed.clear();
@ -837,7 +820,6 @@ bool Query::exec()
} }
queryOk = query.exec(); queryOk = query.exec();
lastErr = query.lastError().driverText().trimmed();
rowsAffected = query.numRowsAffected(); rowsAffected = query.numRowsAffected();
if (queryOk && query.isSelect()) if (queryOk && query.isSelect())
@ -858,6 +840,24 @@ bool Query::exec()
{ {
createRan = true; createRan = true;
} }
else if (!queryOk)
{
auto errobj = query.lastError();
qCritical() << "Database failure";
qCritical() << "Query prep string: " + qStr + jStr + wStr + limit + ";";
qCritical() << "Driver text: " + errobj.driverText();
qCritical() << "Database text: " + errobj.databaseText();
switch (errobj.type())
{
case QSqlError::NoError: qCritical() << "Error type: NoError"; break;
case QSqlError::ConnectionError: qCritical() << "Error type: ConnectionError"; break;
case QSqlError::StatementError: qCritical() << "Error type: StatementError"; break;
case QSqlError::TransactionError: qCritical() << "Error type: TransactionError"; break;
case QSqlError::UnknownError: qCritical() << "Error type: UnknownError"; break;
}
}
} }
return queryOk; return queryOk;

View File

@ -105,8 +105,6 @@ public:
QStringList tables(); QStringList tables();
QStringList columnsInTable(const QString &tbl); QStringList columnsInTable(const QString &tbl);
QVariant getData(const QString &column, int row = 0); QVariant getData(const QString &column, int row = 0);
QString errDetail();
QString errString();
QList<QList<QVariant> > &allData(); QList<QList<QVariant> > &allData();
private: private:
@ -116,7 +114,6 @@ private:
bool queryOk; bool queryOk;
int rowsAffected; int rowsAffected;
QString table; QString table;
QString lastErr;
QString limit; QString limit;
QString qStr; QString qStr;
QString wStr; QString wStr;

View File

@ -16,20 +16,15 @@
// along with MRCI under the LICENSE.md file. If not, see // along with MRCI under the LICENSE.md file. If not, see
// <http://www.gnu.org/licenses/>. // <http://www.gnu.org/licenses/>.
bool setupDb(QString *errMsg) bool setupDb()
{ {
auto ret = true; auto ret = true;
errMsg->clear();
Query query(QThread::currentThread()); Query query(QThread::currentThread());
if (query.inErrorstate()) if (query.inErrorstate())
{ {
ret = false; ret = false;
errMsg->append("database open failure: \n");
errMsg->append(query.errDetail());
} }
if (ret) if (ret)
@ -74,15 +69,6 @@ bool setupDb(QString *errMsg)
ret = query.exec(); ret = query.exec();
} }
if (ret)
{
query.setType(Query::CREATE_TABLE, TABLE_DMESG);
query.addColumn(COLUMN_TIME);
query.addColumn(COLUMN_LOGENTRY);
ret = query.exec();
}
if (ret) if (ret)
{ {
query.setType(Query::CREATE_TABLE, TABLE_USERS); query.setType(Query::CREATE_TABLE, TABLE_USERS);
@ -169,11 +155,5 @@ bool setupDb(QString *errMsg)
ret = query.exec(); ret = query.exec();
} }
if (query.inErrorstate())
{
errMsg->append("main setup: \n");
errMsg->append(query.errDetail());
}
return ret; return ret;
} }

View File

@ -19,6 +19,6 @@
#include "db.h" #include "db.h"
bool setupDb(QString *errMsg = nullptr); bool setupDb();
#endif // DB_SETUP_H #endif // DB_SETUP_H

View File

@ -37,20 +37,80 @@ extern "C"
// a "no OPENSSL_Applink" error. // a "no OPENSSL_Applink" error.
#include <src/applink.c> #include <src/applink.c>
} }
#else
#include <syslog.h>
#endif
#ifdef Q_OS_WINDOWS
void windowsLog(const QByteArray &id, const QByteArray &msg)
{
auto file = QFile(getLocalFilePath(DEFAULT_LOG_FILENAME));
auto date = QDateTime::currentDateTime();
if (file.exists())
{
if (file.size() >= MAX_LOG_SIZE)
{
file.remove();
windowsLog(id, msg);
}
else if (file.open(QIODevice::Append))
{
file.write(date.toString(Qt::ISODateWithMs).toUtf8() + ": msg_id: " + id + " " + msg + "\n");
}
}
else
{
if (file.open(QIODevice::WriteOnly))
{
file.write(date.toString(Qt::ISODateWithMs).toUtf8() + ": msg_id: " + id + " " + msg + "\n");
}
}
file.close();
}
#endif #endif
void msgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) void msgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{ {
Q_UNUSED(type)
Q_UNUSED(context) Q_UNUSED(context)
if (!msg.contains("QSslSocket: cannot resolve")) if (!msg.contains("QSslSocket: cannot resolve"))
{ {
Query db; auto logMsg = msg.toUtf8();
db.setType(Query::PUSH, TABLE_DMESG); switch (type)
db.addColumn(COLUMN_LOGENTRY, msg); {
db.exec(); case QtDebugMsg: case QtInfoMsg: case QtWarningMsg:
{
fprintf(stdout, "inf: %s\n", logMsg.constData());
#ifdef Q_OS_WINDOWS
format.chop(2);
windowsLog(format, "inf: " + utf8);
#else
syslog(LOG_INFO, "inf: %s", logMsg.constData());
#endif
break;
}
case QtCriticalMsg: case QtFatalMsg:
{
fprintf(stderr, "err: %s\n", logMsg.constData());
#ifdef Q_OS_WINDOWS
format.chop(2);
windowsLog(format, "err: " + utf8);
#else
syslog(LOG_ERR, "err: %s", logMsg.constData());
#endif
break;
}
}
} }
} }
@ -74,12 +134,16 @@ void showHelp()
txtOut << " -ls_sql_drvs : list all available SQL drivers that the host currently supports." << Qt::endl; txtOut << " -ls_sql_drvs : list all available SQL drivers that the host currently supports." << Qt::endl;
txtOut << " -load_ssl : re-load the host SSL certificate without stopping the host instance." << Qt::endl; txtOut << " -load_ssl : re-load the host SSL certificate without stopping the host instance." << Qt::endl;
txtOut << " -elevate : elevate any user account to rank 1." << Qt::endl; txtOut << " -elevate : elevate any user account to rank 1." << Qt::endl;
txtOut << " -res_pw : reset a user account password with a randomized one time password." << Qt::endl;
txtOut << " -add_admin : create a rank 1 account with a randomized one time password." << Qt::endl << Qt::endl; txtOut << " -add_admin : create a rank 1 account with a randomized one time password." << Qt::endl << Qt::endl;
txtOut << "Internal module | -public_cmds, -user_cmds, -exempt_cmds, -run_cmd |:" << Qt::endl << Qt::endl; txtOut << "Internal module | -public_cmds, -user_cmds, -exempt_cmds, -run_cmd |:" << Qt::endl << Qt::endl;
txtOut << " -pipe : the named pipe used to establish a data connection with the session." << Qt::endl; txtOut << " -pipe : the named pipe used to establish a data connection with the session." << Qt::endl;
txtOut << " -mem_ses : the shared memory key for the session." << Qt::endl; txtOut << " -mem_ses : the shared memory key for the session." << Qt::endl;
txtOut << " -mem_host : the shared memory key for the host main process." << Qt::endl << Qt::endl; txtOut << " -mem_host : the shared memory key for the host main process." << Qt::endl << Qt::endl;
txtOut << "Details:" << Qt::endl << Qt::endl; txtOut << "Details:" << Qt::endl << Qt::endl;
txtOut << "res_pw - this argument takes a single string representing a user name to reset the password. the host" << Qt::endl;
txtOut << " will set a randomized password and display it on the CLI." << Qt::endl << Qt::endl;
txtOut << " example: -res_pw somebody" << Qt::endl << Qt::endl;
txtOut << "add_admin - this argument takes a single string representing a user name to create a rank 1 account with." << Qt::endl; txtOut << "add_admin - this argument takes a single string representing a user name to create a rank 1 account with." << Qt::endl;
txtOut << " the host will set a randomized password for it and display it on the CLI. this user will be" << Qt::endl; txtOut << " the host will set a randomized password for it and display it on the CLI. this user will be" << Qt::endl;
txtOut << " required to change the password upon logging in." << Qt::endl; txtOut << " required to change the password upon logging in." << Qt::endl;
@ -91,14 +155,6 @@ void showHelp()
txtOut << " executable will auto close if it fails to connect to the pipe and/or shared memory segments" << Qt::endl << Qt::endl; txtOut << " executable will auto close if it fails to connect to the pipe and/or shared memory segments" << Qt::endl << Qt::endl;
} }
void soeDueToDbErr(int *retCode, const QString *errMsg)
{
*retCode = 1;
QTextStream(stderr) << "" << Qt::endl << "err: Stop error." << Qt::endl;
QTextStream(stderr) << " what happened: " << Qt::endl << *errMsg << Qt::endl << Qt::endl;
}
int shellToHost(const QStringList &args, bool holdErrs, QCoreApplication &app) int shellToHost(const QStringList &args, bool holdErrs, QCoreApplication &app)
{ {
auto ret = 0; auto ret = 0;
@ -130,10 +186,6 @@ int main(int argc, char *argv[])
QCoreApplication::setApplicationName(APP_NAME); QCoreApplication::setApplicationName(APP_NAME);
QCoreApplication::setApplicationVersion(APP_VER); QCoreApplication::setApplicationVersion(APP_VER);
QString err;
qInstallMessageHandler(msgHandler);
// args.append("-ls_sql_drvs"); // debug // args.append("-ls_sql_drvs"); // debug
if (args.contains("-run_cmd", Qt::CaseInsensitive) || if (args.contains("-run_cmd", Qt::CaseInsensitive) ||
@ -141,7 +193,7 @@ int main(int argc, char *argv[])
args.contains("-exempt_cmds", Qt::CaseInsensitive) || args.contains("-exempt_cmds", Qt::CaseInsensitive) ||
args.contains("-user_cmds", Qt::CaseInsensitive)) args.contains("-user_cmds", Qt::CaseInsensitive))
{ {
if (setupDb(&err)) if (setupDb())
{ {
auto *mod = new Module(&app); auto *mod = new Module(&app);
@ -152,7 +204,7 @@ int main(int argc, char *argv[])
} }
else else
{ {
soeDueToDbErr(&ret, &err); ret = 1;
} }
} }
else if (args.contains("-help", Qt::CaseInsensitive) || args.size() == 1) else if (args.contains("-help", Qt::CaseInsensitive) || args.size() == 1)
@ -163,7 +215,7 @@ int main(int argc, char *argv[])
{ {
QTextStream(stdout) << "" << Qt::endl; QTextStream(stdout) << "" << Qt::endl;
for (auto driver : QSqlDatabase::drivers()) for (const auto &driver : QSqlDatabase::drivers())
{ {
QTextStream(stdout) << driver << Qt::endl; QTextStream(stdout) << driver << Qt::endl;
} }
@ -181,91 +233,131 @@ int main(int argc, char *argv[])
args.contains("-status", Qt::CaseInsensitive) || args.contains("-status", Qt::CaseInsensitive) ||
args.contains("-load_ssl", Qt::CaseInsensitive)) args.contains("-load_ssl", Qt::CaseInsensitive))
{ {
qInstallMessageHandler(msgHandler);
ret = shellToHost(args, false, app); ret = shellToHost(args, false, app);
} }
else if (setupDb(&err)) else
{ {
if (args.contains("-host", Qt::CaseInsensitive)) qInstallMessageHandler(msgHandler);
{
auto *serv = new TCPServer(&app);
if (serv->start()) if (setupDb())
{
ret = QCoreApplication::exec();
}
}
else if (args.contains("-host_trig", Qt::CaseInsensitive))
{ {
QProcess::startDetached(QCoreApplication::applicationFilePath(), QStringList() << "-host"); if (args.contains("-host", Qt::CaseInsensitive))
} {
else if (args.contains("-elevate", Qt::CaseInsensitive)) auto *serv = new TCPServer(&app);
{
ret = 1;
QByteArray uId; if (serv->start())
{
ret = QCoreApplication::exec();
}
}
else if (args.contains("-host_trig", Qt::CaseInsensitive))
{
QProcess::startDetached(QCoreApplication::applicationFilePath(), QStringList() << "-host");
}
else if (args.contains("-elevate", Qt::CaseInsensitive))
{
ret = 1;
if (args.size() <= 2) QByteArray uId;
{
QTextStream(stderr) << "err: A user name was not given." << Qt::endl; if (args.size() <= 2)
{
QTextStream(stderr) << "err: A user name was not given." << Qt::endl;
}
else if (!validUserName(args[2]))
{
QTextStream(stderr) << "err: Invalid user name." << Qt::endl;
}
else if (!userExists(args[2], &uId))
{
QTextStream(stderr) << "err: The user name does not exists." << Qt::endl;
}
else
{
Query db;
db.setType(Query::UPDATE, TABLE_USERS);
db.addColumn(COLUMN_HOST_RANK, 1);
db.addCondition(COLUMN_USER_ID, uId);
if (db.exec())
{
ret = 0;
}
}
} }
else if (!validUserName(args[2])) else if (args.contains("-add_admin", Qt::CaseInsensitive))
{ {
QTextStream(stderr) << "err: Invalid user name." << Qt::endl; ret = 1;
if (args.size() <= 2)
{
QTextStream(stderr) << "err: A user name was not given." << Qt::endl;
}
else if (!validUserName(args[2]))
{
QTextStream(stderr) << "err: Invalid user name." << Qt::endl;
}
else if (userExists(args[2]))
{
QTextStream(stderr) << "err: The user name already exists." << Qt::endl;
}
else
{
auto randPw = genPw();
if (createUser(args[2], args[2] + "@change_me.null", "", randPw, 1, true))
{
QTextStream(stdout) << "password: " << randPw << Qt::endl;
ret = 0;
}
}
} }
else if (!userExists(args[2], &uId)) else if (args.contains("-res_pw", Qt::CaseInsensitive))
{ {
QTextStream(stderr) << "err: The user name does not exists." << Qt::endl; ret = 1;
QByteArray uId;
if (args.size() <= 2)
{
QTextStream(stderr) << "err: A user name was not given." << Qt::endl;
}
else if (!validUserName(args[2]))
{
QTextStream(stderr) << "err: Invalid user name." << Qt::endl;
}
else if (!userExists(args[2], &uId))
{
QTextStream(stderr) << "err: The user name does not exists." << Qt::endl;
}
else
{
auto randPw = genPw();
if (updatePassword(uId, randPw, TABLE_USERS, true))
{
QTextStream(stdout) << "password: " << randPw << Qt::endl;
ret = 0;
}
}
} }
else else
{ {
Query db; showHelp();
db.setType(Query::UPDATE, TABLE_USERS);
db.addColumn(COLUMN_HOST_RANK, 1);
db.addCondition(COLUMN_USER_ID, uId);
db.exec();
ret = 0;
}
}
else if (args.contains("-add_admin", Qt::CaseInsensitive))
{
ret = 1;
if (args.size() <= 2)
{
QTextStream(stderr) << "err: A user name was not given." << Qt::endl;
}
else if (!validUserName(args[2]))
{
QTextStream(stderr) << "err: Invalid user name." << Qt::endl;
}
else if (userExists(args[2]))
{
QTextStream(stderr) << "err: The user name already exists." << Qt::endl;
}
else
{
auto randPw = genPw();
createUser(args[2], "", "", randPw, 1, true);
QTextStream(stdout) << "password: " << randPw << Qt::endl;
ret = 0;
} }
} }
else else
{ {
showHelp(); ret = 1;
} }
}
else
{
soeDueToDbErr(&ret, &err);
}
cleanupDbConnection(); cleanupDbConnection();
}
return ret; return ret;
} }

View File

@ -361,11 +361,11 @@ bool MemShare::createSharedMem(const QByteArray &sesId, const QString &hostKey)
if (!sharedMem->create(len)) if (!sharedMem->create(len))
{ {
qDebug() << "err: Failed to create a shared memory master block. reason: " + sharedMem->errorString(); qCritical() << "Failed to create a shared memory master block. reason: " + sharedMem->errorString();
} }
else if (!hostSharedMem->attach()) else if (!hostSharedMem->attach())
{ {
qDebug() << "err: Failed to attach to the host shared memory master block. reason: " + hostSharedMem->errorString(); qCritical() << "Failed to attach to the host shared memory master block. reason: " + hostSharedMem->errorString();
} }
else else
{ {
@ -387,11 +387,11 @@ bool MemShare::attachSharedMem(const QString &sKey, const QString &hKey)
if (!sharedMem->attach()) if (!sharedMem->attach())
{ {
qDebug() << "err: Failed to attach to the session shared memory master block. reason: " + sharedMem->errorString(); qCritical() << "Failed to attach to the session shared memory master block. reason: " + sharedMem->errorString();
} }
else if (!hostSharedMem->attach()) else if (!hostSharedMem->attach())
{ {
qDebug() << "err: Failed to attach to the host shared memory master block. reason: " + hostSharedMem->errorString(); qCritical() << "Failed to attach to the host shared memory master block. reason: " + hostSharedMem->errorString();
} }
else else
{ {

View File

@ -54,7 +54,6 @@ QStringList Module::userCmdList()
ret << FileInfo::cmdName(); ret << FileInfo::cmdName();
ret << MakePath::cmdName(); ret << MakePath::cmdName();
ret << ChangeDir::cmdName(); ret << ChangeDir::cmdName();
ret << ListDBG::cmdName();
ret << ToPeer::cmdName(); ret << ToPeer::cmdName();
ret << LsP2P::cmdName(); ret << LsP2P::cmdName();
ret << P2POpen::cmdName(); ret << P2POpen::cmdName();
@ -134,7 +133,7 @@ QStringList Module::rankExemptList()
bool Module::runCmd(const QString &name) bool Module::runCmd(const QString &name)
{ {
bool ret = true; auto ret = true;
if (userCmdList().contains(name, Qt::CaseInsensitive)) if (userCmdList().contains(name, Qt::CaseInsensitive))
{ {
@ -178,7 +177,6 @@ bool Module::runCmd(const QString &name)
else if (noCaseMatch(name, FileInfo::cmdName())) new FileInfo(this); else if (noCaseMatch(name, FileInfo::cmdName())) new FileInfo(this);
else if (noCaseMatch(name, MakePath::cmdName())) new MakePath(this); else if (noCaseMatch(name, MakePath::cmdName())) new MakePath(this);
else if (noCaseMatch(name, ChangeDir::cmdName())) new ChangeDir(this); else if (noCaseMatch(name, ChangeDir::cmdName())) new ChangeDir(this);
else if (noCaseMatch(name, ListDBG::cmdName())) new ListDBG(this);
else if (noCaseMatch(name, ToPeer::cmdName())) new ToPeer(this); else if (noCaseMatch(name, ToPeer::cmdName())) new ToPeer(this);
else if (noCaseMatch(name, LsP2P::cmdName())) new LsP2P(this); else if (noCaseMatch(name, LsP2P::cmdName())) new LsP2P(this);
else if (noCaseMatch(name, P2POpen::cmdName())) new P2POpen(this); else if (noCaseMatch(name, P2POpen::cmdName())) new P2POpen(this);
@ -209,14 +207,14 @@ bool Module::runCmd(const QString &name)
else if (noCaseMatch(name, Tree::cmdName())) new Tree(this); else if (noCaseMatch(name, Tree::cmdName())) new Tree(this);
else else
{ {
qDebug() << "Module err: the loader claims command name '" << name << "' exists but no command object was actually matched/built."; qCritical() << "Internal module - the module claim command name '" << name << "' exists but no command object was actually matched/built.";
ret = false; ret = false;
} }
} }
else else
{ {
qDebug() << "Module err: command name '" << name << "' not found."; qCritical() << "Internal module - command name '" << name << "' not found.";
ret = false; ret = false;
} }
@ -231,7 +229,7 @@ void Module::listCmds(const QStringList &list)
bool Module::start(const QStringList &args) bool Module::start(const QStringList &args)
{ {
bool ret = true; auto ret = true;
if (args.contains("-run_cmd")) if (args.contains("-run_cmd"))
{ {

View File

@ -83,7 +83,7 @@ void Session::connectToPeer(const QSharedPointer<SessionCarrier> &peer)
{ {
if (peer->sessionObj == nullptr) if (peer->sessionObj == nullptr)
{ {
qDebug() << "Session::connectToPeer() the peer session object is null."; qCritical() << "Session::connectToPeer() the peer session object is null.";
} }
else if ((peer->sessionObj != this) && (flags & SESSION_RDY)) else if ((peer->sessionObj != this) && (flags & SESSION_RDY))
{ {

View File

@ -101,7 +101,7 @@ QString parseMd(const QString &cmdName, int offset)
if (!file.open(QFile::ReadOnly)) if (!file.open(QFile::ReadOnly))
{ {
qDebug() << "err: internal command: " << cmdName << " does not have a document file."; qDebug() << "Internal command: " << cmdName << " does not have a document file.";
} }
else else
{ {

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
systemctl -q stop $app_target systemctl -q stop $app_target
systemctl -q disable $app_target systemctl -q disable $app_target
rm -v /etc/systemd/system/$app_target.service rm -v /lib/systemd/system/$app_target.service
rm -v /usr/bin/$app_target rm -v /usr/bin/$app_target
rm -rv $install_dir rm -rv $install_dir
deluser $app_target deluser $app_target