Compare commits

..

No commits in common. "fabc82a0a21a2c6f8ce4bc92532c22a9d12ddf1e" and "50cd13804541ebab1cacf83b8942a7d444010f95" have entirely different histories.

48 changed files with 2032 additions and 1099 deletions

View File

@ -71,6 +71,7 @@ SOURCES += src/main.cpp \
src/commands/mods.cpp \
src/commands/info.cpp \
src/commands/cast.cpp \
src/commands/admin.cpp \
src/commands/auth.cpp \
src/commands/acct_recovery.cpp \
src/commands/table_viewer.cpp \
@ -96,6 +97,7 @@ HEADERS += \
src/commands/mods.h \
src/commands/info.h \
src/commands/cast.h \
src/commands/admin.h \
src/commands/auth.h \
src/commands/acct_recovery.h \
src/commands/table_viewer.h \

View File

@ -38,13 +38,13 @@ def get_qt_from_cli():
return ""
def get_info_header():
def get_db_header():
current_dir = os.path.dirname(__file__)
if current_dir == "":
return "src" + os.sep + "common.h"
return "src" + os.sep + "db.h"
else:
return current_dir + os.sep + "src" + os.sep + "common.h"
return current_dir + os.sep + "src" + os.sep + "db.h"
def get_nearest_subdir(path, sub_name):
dir_list = os.listdir(path)
@ -112,23 +112,10 @@ def verbose_copy(src, dst):
if os.path.exists(dst) and os.path.isdir(dst):
shutil.rmtree(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:
print("wrn: " + src + " does not exists. skipping.")
shutil.copyfile(src, dst)
def linux_build_app_dir(app_ver, app_name, app_target, qt_bin):
if not os.path.exists("app_dir/linux/sqldrivers"):
@ -140,7 +127,6 @@ 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/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("build/linux/" + app_target, "app_dir/linux/" + app_target)
shutil.copyfile("build/linux/" + app_target, "/tmp/" + app_target)
@ -214,7 +200,7 @@ def complete(app_ver, app_target):
print("You can now run the install.py script to install onto this machine or create an installer.")
def main():
with open(get_info_header()) as file:
with open(get_db_header()) as file:
text = file.read()
app_target = get_app_target(text)
@ -240,18 +226,9 @@ def main():
else:
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"])
if result.returncode == 0:
result = subprocess.run([maker])
if result.returncode == 0:

View File

@ -9,6 +9,7 @@
<file>docs/intern_commands/add_sub_ch.md</file>
<file>docs/intern_commands/auth.md</file>
<file>docs/intern_commands/cast.md</file>
<file>docs/intern_commands/close_host.md</file>
<file>docs/intern_commands/close_sub_ch.md</file>
<file>docs/intern_commands/decline_ch.md</file>
<file>docs/intern_commands/find_ch.md</file>
@ -23,6 +24,7 @@
<file>docs/intern_commands/fs_move.md</file>
<file>docs/intern_commands/fs_tree.md</file>
<file>docs/intern_commands/fs_upload.md</file>
<file>docs/intern_commands/host_config.md</file>
<file>docs/intern_commands/host_info.md</file>
<file>docs/intern_commands/invite_to_ch.md</file>
<file>docs/intern_commands/is_email_verified.md</file>
@ -31,6 +33,7 @@
<file>docs/intern_commands/ls_auth_log.md</file>
<file>docs/intern_commands/ls_ch_members.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_open_chs.md</file>
<file>docs/intern_commands/ls_p2p.md</file>
@ -44,6 +47,7 @@
<file>docs/intern_commands/p2p_open.md</file>
<file>docs/intern_commands/p2p_request.md</file>
<file>docs/intern_commands/ping_peers.md</file>
<file>docs/intern_commands/preview_email.md</file>
<file>docs/intern_commands/recover_acct.md</file>
<file>docs/intern_commands/remove_ch_member.md</file>
<file>docs/intern_commands/rename_ch.md</file>
@ -51,6 +55,7 @@
<file>docs/intern_commands/request_new_pw.md</file>
<file>docs/intern_commands/request_new_user_name.md</file>
<file>docs/intern_commands/request_pw_reset.md</file>
<file>docs/intern_commands/restart_host.md</file>
<file>docs/intern_commands/rm_acct.md</file>
<file>docs/intern_commands/rm_ch.md</file>
<file>docs/intern_commands/rm_mod.md</file>
@ -59,6 +64,7 @@
<file>docs/intern_commands/rm_sub_ch.md</file>
<file>docs/intern_commands/set_active_flag.md</file>
<file>docs/intern_commands/set_disp_name.md</file>
<file>docs/intern_commands/set_email_template.md</file>
<file>docs/intern_commands/set_email.md</file>
<file>docs/intern_commands/set_member_level.md</file>
<file>docs/intern_commands/set_pw.md</file>

View File

@ -1,6 +1,6 @@
# MRCI #
(Modular Remote Command Interpreter) is a command interpreter primarily designed to provide any type of remote service to connected clients. As the name implies, it is expandable via 3rd party modules by adding additional commands that remote clients can run on the host. It has a fully featured user account management system with access control to certain commands for certain users.
(Modular Remote Command Interpreter) is a command interpreter primarily designed to provide any type of remote service to connected clients. As the name implies, it is expandable via 3rd party modules by adding addtional commands that remote clients can run on the host. It has a fully feasured user account management system with access control to certain commands for certain users. All persistent data is handled by a SQLite database and all remote connections are handled via TCP and encrypted in SSL/TLS.
### Usage ###
@ -12,18 +12,18 @@ Usage: mrci <argument>
-help : display usage information about this application.
-stop : stop the current host instance if one is currently running.
-about : display versioning/warranty information about this application.
-addr : set the listening address and port for TCP clients.
-status : display status information about the host instance if it is currently running.
-reset_root : reset the root account password to the default password.
-host : start a new host instance. (this blocks)
-host_trig : start a new host instance. (this does not block)
-default_pw : show the default password.
-public_cmds : run the internal module to list it's public commands. for internal use only.
-exempt_cmds : run the internal module to list it's rank exempt commands. for internal use only.
-user_cmds : run the internal module to list it's user commands. for internal use only.
-run_cmd : run an internal module command. for internal use only.
-ls_sql_drvs : list all available SQL drivers that the host currently supports.
-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.
Internal module | -public_cmds, -user_cmds, -exempt_cmds, -run_cmd |:
@ -33,55 +33,47 @@ Internal module | -public_cmds, -user_cmds, -exempt_cmds, -run_cmd |:
Details:
res_pw - this argument takes a single string representing a user name to reset the password. the host
will set a randomized password and display it on the CLI.
example: -res_pw somebody
add_admin - this argument takes a single string representing a user name to create a rank 1 account with.
the host will set a randomized password for it and display it on the CLI. this user will be
required to change the password upon logging in.
example: -add_admin somebody
elevate - this argument takes a single string representing a user name to an account to promote to rank 1.
example: -elevate somebody
addr - this argument takes a {ip_address:port} string. it will return an error if not formatted correctly
examples: 10.102.9.2:35516 or 0.0.0.0:35516.
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 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.
The host can only 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.
Any one user account registered with the host can be given root privileges which basically gives this user unrestricted access to anything in the host for administrative purposes. When a host instance is created for the first time, it will create a new user account called 'root' with a randomized default password. To find out what the default password is, run -default_pw. When logging in for the fist time, the host will require you to change the user name and password before continuing.
### More Than Just a Command Interpreter ###
Typical use for a MRCI host is to run commands on a remote host that clients ask it to run, very similar to what you see in remote terminal emulators. It however does have a few features typically not seen in terminals:
Typical use for a MRCI host is to run commands on a remote host that clients ask it to run, very similar to what you see in remote terminal emulators. It however does have a few feasures typically not seen in terminals:
* 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.
* Fully featured user account management system.
* Fully feasured user account management system.
* Built in permissions and command access management.
* Host limits management (max concurrent users, max failed password attempts, etc...).
* Account recovery emailing in case of forgotten passwords. **
* Access to various logs.
* Acesss to various logs.
* Built in host file management (copy, move, delete, etc...).
* Built in file upload/download support.
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 the option 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 options to change the email client to any other application that has a command line interface.
### Documentation ###
* [1.1 The Protocol](docs/protocol.md)
* [2.1 Modules](docs/modules.md)
* [3.1 Type IDs](docs/type_ids.md)
* [4.1 Host Features](docs/host_features.md)
* [5.1 Async Commands](docs/async.md)
* [6.1 Shared Memory](docs/shared_data.md)
* [7.1 Internal Commands](docs/intern_commands.md)
* [1.1 The Protocol](protocol.md)
* [2.1 Modules](modules.md)
* [3.1 Type IDs](type_ids.md)
* [4.1 Host Features](host_features.md)
* [5.1 Async Commands](async.md)
* [6.1 Shared Memory](shared_data.md)
* [7.1 Internal Commands](intern_commands.md)
### Build Setup ###
@ -109,7 +101,7 @@ while running the install script, it will ask you to input 1 of 3 options:
***local machine*** - This option will install the built application onto the local machine without creating an installer.
***create installer*** - This option creates an installer that can be distributed to other machines for installation. The resulting installer is just a regular .py script file that the target machine can run if it has Python3 installed. Only Python3 needs to be installed and an internet connection is not required.
***create installer*** - This option creates an installer that can be distributed to other machines to installation. The resulting installer is just a regular .py script file that the target machine can run if it has Python3 insalled. Only Python3 needs to be installed and an internet connection is not required.
***exit*** - Cancel the installation.

View File

@ -32,12 +32,15 @@ enum AsyncCommands : quint16
{
ASYNC_RDY = 1, // client | retricted
ASYNC_SYS_MSG = 2, // client | retricted
ASYNC_EXIT = 3, // internal | private
ASYNC_CAST = 4, // client | public
ASYNC_MAXSES = 5, // internal | private
ASYNC_LOGOUT = 6, // internal | private
ASYNC_USER_DELETED = 7, // client | public
ASYNC_DISP_RENAMED = 8, // internal | public
ASYNC_USER_RANK_CHANGED = 9, // internal | public
ASYNC_CMD_RANKS_CHANGED = 10, // internal | public
ASYNC_RESTART = 11, // internal | private
ASYNC_ENABLE_MOD = 12, // internal | public
ASYNC_DISABLE_MOD = 13, // internal | public
ASYNC_END_SESSION = 14, // internal | private
@ -81,7 +84,10 @@ enum AsyncCommands : quint16
This command signals to the client that your current session is now ready to start running commands. This is sent to the client after successfully setting up the tcp connection ([protocol](protocol.md)). It can carry [TEXT](type_ids.md) data that can be displayed directly to the user if needed.
```ASYNC_SYS_MSG (2)```
This command carry [TEXT](type_ids.md) or [ERR](type_ids.md) data that are system messages that can be directly displayed to the user of needed. It is also used to carry MY_INFO data when local user account information is changed.
This command carry [TEXT](type_ids.md) or [ERR](type_ids.md) data that are system messages that can be directly displayed to the user of needed. It is also used to carry [HOST_CERT](type_ids.md) data during the tcp connection setup and MY_INFO when local user account information has changed.
```ASYNC_EXIT (3)```
This is an internal async command that doesn't carry any data. It is used to send a ```closeServer()``` signal to the TCPServer object in the main process. This will cause it stop listing for clients, close all sessions and then close the main process.
```ASYNC_CAST (4)```
This is an internal only command that carries a 54byte open sub-channels list described in section 5.3 and an embedded frame that can then be sent to clients that have any of the matching open sub-channels. It drops that sub-channel list before arriving at the client so it will apppear like a regular [mrci frame](protocol.md) of any data type.
@ -90,6 +96,9 @@ from_module: [54bytes(sub_channel_list)][1byte(type_id)][rest-of-bytes(pay_load)
to_client: [type_id][cmd_id(4)][branch_id(0)][size_of_payload][payload]
```
```ASYNC_MAXSES (5)```
Internal only async command can used by modules to send a 4byte unsigned 32bit int to the session object to change the maximum amount the concurrent sessions for the TCPServer object.
```ASYNC_LOGOUT (6)```
This internal only async command doesn't carry any data. This can be used by modules to tell the session object to logout the current user.
@ -97,7 +106,7 @@ This internal only async command doesn't carry any data. This can be used by mod
This command carries a 32byte user id hash of the user account that was delete from the host database. All sessions that are currently logged into this account will force logout.
```ASYNC_DISP_RENAMED (8)```
This command carries a combination of the 32byte user id hash and the 64byte new display name (UTF-8, padded with 0x00) of the user account that changed it's display name. This will trigger all sessions that are currently logged into this account to send an updated [MY_INFO](type_ids.md) frame via ASYNC_SYS_MSG to the clients.
This command carries a combination of the 32byte user id hash and the 64byte new display name (UTF-16LE, padded with 0x00) of the user account that changed it's display name. This will trigger all sessions that are currently logged into this account to send an updated [MY_INFO](type_ids.md) frame via ASYNC_SYS_MSG to the clients.
```
from_module: [32bytes(user_id)][64bytes(new_disp_name)]
to_client: [type_id(9)][cmd_id(2)][branch_id(0)][size_of_payload][payload(MY_INFO)]
@ -113,6 +122,9 @@ to_client: [type_id(9)][cmd_id(2)][branch_id(0)][size_of_payload][payload(MY_I
```ASYNC_CMD_RANKS_CHANGED (10)```
This internal only command doesn't carry any data, it just triggers all sessions to re-load runable commands.
```ASYNC_RESTART (11)```
This internal only async commmand doesn't carry any data. It is used to send a ```resServer()``` signal to the TCPServer object in the main process. This will cause it stop listing for clients, close all sessions, reload the host settings and start listening for clients again.
```ASYNC_ENABLE_MOD (12)```
This internal only async commmand that carry a [TEXT](type_ids.md) path to a module executable. All session objects that receive this will then attempt to load the module.
@ -170,7 +182,7 @@ to_client: [type_id(26)][cmd_id(22)][branch_id(0)][size_of_payload][payload(CH_I
```
```ASYNC_RENAME_CH (23)```
This async command carries a combination of the channel id and a null terminated UTF-8 string containing the new name of the channel that has been renamed. This command desn't use any of the standard frame formats so it sends a [BYTES](type_ids.md) frame to the client.
This async command carries a combination of the channel id and a 16bit null terminated UTF-16LE string containing the new name of the channel that has been renamed. This command desn't use any of the standard frame formats so it sends a [BYTES](type_ids.md) frame to the client.
```
to_client: [type_id(14)][cmd_id(28)][branch_id(0)][size_of_payload][payload(channel_id + new_channel_name)]
```
@ -182,7 +194,7 @@ format: [8bytes(64bit_ch_id)][1byte(8bit_sub_ch_id)]
```
```ASYNC_NEW_SUB_CH (25)```
This async command carries a comination of the channel id, sub-channel id, access level and a null terminated UTF-8 string containing the sub-channel name when a new sub-channel is created. All sessions that are logged in as a member of the channel forwards the data to the clients as a [BYTES](type_ids.md) frame.
This async command carries a comination of the channel id, sub-channel id, access level and a 16bit null terminated UTF-16LE string containing the sub-channel name when a new sub-channel is created. All sessions that are logged in as a member of the channel forwards the data to the clients as a [BYTES](type_ids.md) frame.
```
to_client: [type_id(14)][cmd_id(25)][branch_id(0)][8bytes(64bit_ch_id)][1byte(8bit_sub_ch_id)]
[1byte(8bit_access_level)][16bit_null_term_sub-channel_name]
@ -195,7 +207,7 @@ to_client: [type_id(14)][cmd_id(26)][branch_id(0)][8bytes(64bit_ch_id)][1byte(8b
```
```ASYNC_RENAME_SUB_CH (27)```
This async command carries a combination of the channel id, sub-channel id, access level and a null terminated UTF-8 string containing the new sub-channel name. All sessions that are logged in as a member of the channel forwards the data to the clients as a [BYTES](type_ids.md) frame.
This async command carries a combination of the channel id, sub-channel id, access level and a 16bit null terminated UTF-16LE string containing the new sub-channel name. All sessions that are logged in as a member of the channel forwards the data to the clients as a [BYTES](type_ids.md) frame.
```
to_client: [type_id(14)][cmd_id(27)][branch_id(0)][8bytes(64bit_ch_id)][1byte(8bit_sub_ch_id)]
[16bit_null_term_sub-channel_name]
@ -281,7 +293,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.
```ASYNC_DEBUG_TEXT (44)```
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.
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.
```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.
@ -291,7 +303,7 @@ This async command doesn't carry any data. Any module command that sends it tell
### 5.3 Open Sub-Channel List ###
An open sub-channel list is a binary data structure that string togeather up to 6 sub-channel combinations that indicate which channel id and sub id combinations are currently open. Each sub-channel are 9bytes long and the list itself maintians a fixed length of 54bytes so it is padded with 0x00 chars to maintain the fixed length (this padding can appear anywhere in 9byte increments within the list). Each sub-channel is formatted like this:
An open sub-channel list is a binary data structure that string togeather up to 6 channel-sub combinations that indicate which channel id and sub id combinations are currently open. Each sub-channel are 9bytes long and the list itself maintians a fixed length of 54bytes so it is padded with 0x00 chars to maintain the fixed length (this padding can appear anywhere in 9byte increments within the list). Each sub-channel is formatted like this:
```
bytes[0-7] - 64bit LE unsigned int (channel id)

View File

@ -4,9 +4,9 @@ Other than transporting data to be processed by modules, the host have a few oth
### 4.2 Host Ranks ###
Each user registered in the host must be assigned a host rank. The lower it's numeric value, the higher the level of access the user has in the host with 0 being invalid. The host defines a default initial host rank for all new users in the host config file located at /etc/mrci/conf.json if running on a linux based os or %PROGRAMDATA%\mrci\conf.json if running on windows.
Each user registered in the host must be assigned a host rank. The lower it's numeric value, the higher the level of access the user has in the host with 0 being invalid. When initialized, the host defines a ```root``` user with a host rank of 1; giving it the highest level of access possible in the host. The host also defines a default initial host rank of 2 for all new accounts that are created in the host. This can be re-configured at any time using the [host_config](intern_commands.md) internal command.
Some internal commands have the ability to change the user account information of other accounts. The host will in general not allow users of a lower level of access to any user information of higher access level. For example: a user of host rank ```1``` can force change another user's email address if that user's rank is ```2``` or higher but the rank ```2``` user can't do the same in reverse.
Some internal commands have the ability to change the user account information of other accounts. The host will in general does not allow users of a lower level of access to any user information of higher access level. For example: a user of host rank ```1``` can force change another user's email address if that user's rank is ```2``` or higher but the rank ```2``` user can't do the same in reverse.
Host ranks can also be assigned to the commands themselves via the command names of specific modules. By doing this, the host can be configured to allow users of certain ranks or higher access to running certain commands. For example: if a command named ```this_cmd``` is assigned a host rank of ```6```, all users with a host rank value of ```6``` or lower will have access to running this command. All commands that don't have an assigned rank will be assumed a rank of ```1``` but all commands that define itself as rank exempt can bypass this and allow the user to run it regardless of the user's host rank. Exemptions would also disregard the assigned rank of the command.
@ -71,171 +71,6 @@ public-level(5):
All sub-channels can be configured with it's own "lowest level of access" level that can make it so only certain channel members can open it. For example, the channel owner or admin can set a sub-channel's minimum level to 4 to make it so only channel regular members and above can open the sub-channel or it can be set to 5 to make it so anybody can open/close the sub-channel, affectively making it a public sub-channel.
There can only be one channel owner so if the owner decides to change the privilege of any other member in the channel to owner, the current owner will automatically step down to level 2 (admin). Also note that privilege level 5 is reserved for users that are not a member of the channel and cannot be assigned to any user that are currently members of the channel.
There can only be one channel owner so if the owner decides change the privilege of any other member in the channel to owner, the current owner will automatically step down to level 2 (admin). Also note that privilege level 5 is reserved for users that are not a member of the channel and cannot be assigned to any user that are currently members of the channel.
Sub-channels can also be assigned what is called *read-only* flags. These flags are attached to the sub-channel id and privilege level. It is decoupled from all changes that could occur with the sub-channel so this means the sub-channel can get renamed or even deleted but the read-only flag would still remain. What a read-only flag actual does is make it so certain users of the matching level can listen for broadcast data but cannot send out broadcast data to the sub-channel. So a read-only flag for example can be added to a sub-channel id for privilege 5 to make it so public users can listen to the sub-channel but cannot send out anything to it.
### 4.4 Host Config File ###
This application stores important global settings in a single json formatted file located at /etc/mrci/conf.json if running on a Linux based OS or %Programdata%\mrci\conf.json if running on windows. Here is a description of all the settings that are stored in that file and what are considered valid vaules.
```
all_channels_active_update : bool
This option tells the host if all sub-channels should be considered
active or not. otherwise, the active flag can be toggled on/off at the
individual sub-channels. active sub-channels send/receive PEER_INFO or
PEER_STAT frames to all peer clients connected to the sub-channel so
they can be made aware of each other's status and public information.
without the active flag, no such frames are transferred.
auto_lock_limit : int
The autolock threshold is an integer value that determines how many
failed login attempts can be made before the user account is locked
by the host.
db_driver : string
The host can support different types of SQL databases depending on
what QT database drivers are installed in the system. these drivers
are usually found in /opt/mrci/sqldrivers if running on a Linux
based OS or %programfiles%\MRCI\sqldrivers if running on Windows.
you can also run mrci -ls_sql_drvs to get a list of available
drivers. the default is QSQLITE.
db_host_name : string
This value contains the network, internet address or file location
of the database.
db_password : string
This value contains the authentication password to the database if
password protected.
db_user_name : string
This value contains the authentication username to the database if
password protected.
email_verify_subject : string
The host will use this string as the email subject when sending an
email verification email. an email verification basically sends
a numeric code to the target user's email address and awaits input
of the code by the user. if the entered code is correct, that
verifies that the user is the owner of the email address.
email_verify_template : string
Path to a any text file formatted in UTF8 unicode in the local host
file system. the text contained in this file will be used in the
actual email body when sending a email verification email to the
target user's email address. the message body must contain all of
the keywords in section 4.5.
enable_email_verify : bool
This enables/disables automated email confirmations. this tells the
host if it is allowed to load the verify_email command for any user,
regardless of rank.
enable_public_reg : bool
Public registration basically allows un-logged in clients to run the
add_acct command. doing this allows un-registered users to become
registered users without the need to contact an admin.
enable_pw_reset : bool
This enables automated password resets via email so users can reset
their account passwords without the need to contact an admin. this
basically tells the host if it is allowed to load the
request_pw_reset and recover_acct commands or not.
initial_rank : int
The initial host rank is the rank all new user accounts are
registered with when created. the host rank itself is an integer
value that determine what commands each user can or cannot run.
see section 4.2 for more info on host ranks.
listening_addr : string
This is the local address that the host listen on for clients.
the default is 0.0.0.0 which means the host will listen on any
local address connected to the host.
listening_port : int
This is the port that the host will listen on for clients.
mail_client_cmd : string
This is the command line template that the host will use when
sending emails. just like the email templates, this command
also require certain keywords to be present to successfully
construct a working command line. see section 4.6 for these
keywords.
max_sessions : int
Max sessions is an integar value that determines how many
simultaneous clients the host will be allowed to run at once.
max_sub_channels : int
This option sets the maximum amount of sub-channels each channel
can have. this must range between 1 and 255.
reset_pw_mail_subject : string
The host will use this string as the email subject when sending a
password reset email. the email body will contain a temp password
that the user will need to enter when running the recover_acct
command and in turn be able to change the account password.
reset_pw_mail_template : string
Path to a any text file formatted in UTF8 unicode in the local host
file system. the text contained in this file will be used in the
actual email body when sending a password reset email to the target
user's email address. the message body must contain all of the
keywords in section 4.5.
tls_cert_chain : string
Path to the SSL/TLS cert file used for secure TCP connections. more
than one cert file can be defined to form a complete chain by colon
seperating multiple file paths.
tls_priv_key : string
Path to the SSL/TLS private key used for secure TCP connections.
```
### 4.5 Email Template Keywords ###
The host will replace the following keywords in the email template with actual values/text when contructing the email.
```
%user_name% - the registered username of the target.
%date% - the date and time stamp of the email being sent.
%otp% - standing for "one time password" this keyword is
the temporary password or verification code that
is sent to the target's email.
```
### 4.6 Email Command Line Template Keywords ###
The host will replace the following keywords in the command line template with actual values/text before running it.
```
%message_body% - the fully built message body text from an email
template.
%subject% - the email subject text.
%target_email% - target email address that the email will be
sent to.
```

View File

@ -22,6 +22,8 @@ The host is extendable via 3rd party modules but the host itself is an internal
* [ch_owner_override](intern_commands/ch_owner_override.md) - set/unset the channel owner override flag for your current session.
* [close_host](intern_commands/close_host.md) - close the host instance.
* [close_sub_ch](intern_commands/close_sub_ch.md) - close a sub-channel for your current session.
* [decline_ch](intern_commands/decline_ch.md) - decline an invite to a channel.
@ -50,6 +52,8 @@ The host is extendable via 3rd party modules but the host itself is an internal
* [fs_upload](intern_commands/fs_upload.md) - upload a single file to the host.
* [host_config](intern_commands/host_config.md) - view/change various host settings.
* [host_info](intern_commands/host_info.md) - display system information about the host.
* [invite_to_ch](intern_commands/invite_to_ch.md) - invite a host user to join a channel.
@ -66,6 +70,8 @@ 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_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_open_chs](intern_commands/ls_open_chs.md) - list all of the sub-channels that are currently open.
@ -92,6 +98,8 @@ The host is extendable via 3rd party modules but the host itself is an internal
* [ping_peers](intern_commands/ping_peers.md) - ping all peer sessions with any matching sub-channels to return information about themselves to you.
* [preview_email](intern_commands/preview_email.md) - preview the confirmation or password reset emails with dummy values.
* [recover_acct](intern_commands/recover_acct.md) - login to a user account using a temporary password.
* [remove_ch_member](intern_commands/remove_ch_member.md) - remove a user as a member of a channel you currently a member of or cancel an invite.
@ -106,6 +114,8 @@ The host is extendable via 3rd party modules but the host itself is an internal
* [request_pw_reset](intern_commands/request_pw_reset.md) - request a password reset for a user account.
* [restart_host](intern_commands/restart_host.md) - re-start the host instance.
* [rm_acct](intern_commands/rm_acct.md) - delete a user account from the host database.
* [rm_ch](intern_commands/rm_ch.md) - permanently remove a channel and all of it's sub-shannels from the host.
@ -124,6 +134,8 @@ The host is extendable via 3rd party modules but the host itself is an internal
* [set_email](intern_commands/set_email.md) - set the user account email address.
* [set_email_template](intern_commands/set_email_template.md) - set the email template used by the host to send emails for user account resets and confirmations.
* [set_member_level](intern_commands/set_member_level.md) - set the user privilege levels of a channel member.
* [set_pw](intern_commands/set_pw.md) - update your account password.

View File

@ -0,0 +1,11 @@
### Summary ###
close the host instance.
### IO ###
```[none]/[text]```
### Description ###
this ends all sessions and closes the host main instance. be very careful with this command since it will shutdown the mrci host application entirely so all clients will not be able to connect until it is started again.

View File

@ -0,0 +1,11 @@
### Summary ###
view/change various host settings.
### IO ###
```[none]/[text]```
### Description ###
view/edit host settings. this command uses a menu system, follow the on screen intructions embedded into the menu.

View File

@ -0,0 +1,16 @@
### 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

@ -0,0 +1,11 @@
### Summary ###
preview the confirmation or password reset emails with dummy values.
### IO ###
```[-reset_email] or [-confirm_email]/[text]```
### Description ###
preview the reset password email with the -reset_email argument or preview the email confirmation with -confirm_email. this prints the subject first and then the message body with the keywords substituted the dummy values/text. this command will output the preview as configured so if the messages do contain html, it will output the html as is. it will be up to the client to parse the html or not.

View File

@ -0,0 +1,11 @@
### Summary ###
re-start the host instance.
### IO ###
```[none]/[text]```
### Description ###
this will restart the host instance. it will stop listening for new clients, close all current sessions, reload the database and then start listening for clients again. the host behaviour might also change depending on what is new in the database or if the path has changed.

View File

@ -0,0 +1,24 @@
### Summary ###
set the email template used by the host to send emails for user account resets and confirmations.
### IO ###
```[[-reset_template] or [-confirm_template] {-subject {(text)}} {[-body {(text)}] or [-client_file (text) -len (int)]}]/[text]```
### Description ###
this updates the email template used by the host to send emails to the users that request password resets and or email confirmations. the presents of -reset_template updates the password reset email or the presents of -confirm_template updates the confirmation email.
-subject is exactly as the name implies, it tells the host what subject to use when sending the email.
-body sets the email message body directly from the command line or -client_file loads the body from a text file. if uploading a text file, use the -len parameter to enter the file size in bytes if your client does not auto fill that.
the message body must contain the following keywords to be acceptable:
%user_name%
%date%
%temp_pw% (if -reset_template)
%confirmation_code% (if -confirm_template)
the host will substitute these keywords with actual values/text when contructing the email. note: if sending a text file, the host assumes it is encoded in UTF-16LE. pass an empty -subject and/or a empty -body to reset the values to host defaults.

View File

@ -27,14 +27,14 @@ A full description of the type id's can be found in the [Type_IDs.md](type_ids.m
### 2.3 Module Command Line Options ###
```
The host will call the module with just one of these parameters:
The host will call the module with just one of these options:
-public_cmds : send a NEW_CMD frame for all public commands the module can run.
-exempt_cmds : send a NEW_CMD frame all rank exempt commands the module can run.
-user_cmds : send a NEW_CMD frame for all user rank enforced commands the module can run.
-run_cmd {command_name} : run a module command based on the command name.
The host will include all 3 of these parameters with the above option:
The host will include all 3 of these options with the above option:
-pipe {pipe_name/path} : the named pipe used to establish a data connection with the session.
-mem_ses {key_name} : the shared memory key for the session object.
@ -45,16 +45,12 @@ notes:
* When the session calls the module in list mode (-public_cmds, -exempt_cmds or -user_cmds), it will only respond to frame type ids: [NEW_CMD](type_ids.md) or [ERR](type_ids.md) from the module; all other data types are ignored. Modules called in run mode (-run_cmd) on the other hand will open up all frame type ids.
* 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 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 options.
* 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 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.
* The session will call all modules with the -public_cmds when created for the first time or when the user logout so 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 options so the command names should not overlap when these options are active.
* 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 module.
* 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

@ -1,6 +1,6 @@
### 1.1 The Protocol ###
The main goal of this application is to transport data from remote TCP clients to the [Modules](modules.md) defined in the host. How the data is processed and/or returned back to the client depends entirely on the module itself. The data format that the host understands for data transport is referred to as MRCI frames described below in section 1.2.
The main goal of this application is to transport data from remote TCP clients to the [Modules](modules.md) defined in the host. How the data is processed and/or returned back to the client depends entirely on the type of data being transported or the module itself. The data format that the host understands for data transport is referred to as MRCI frames described below in section 1.2.
Before any MRCI frames can be transported, both the host and client need basic information about each other. This is done by having the client send a fixed length client header when it successfully connects to the host and the host will reply with it's own fixed length host header. Descriptions of these headers can be found in sections 1.4 and 1.5.
@ -20,7 +20,7 @@ notes:
* A full description of the type id's can be found in the [Type_IDs.md](type_ids.md) document.
* Modules call commands via a command name but the host will assign a unique command id to all command names so clients can call them using a simple 2 byte integer instead of full text. The command ids can change as the modules change so it is recommended for clients to not depend on consistant command ids but depend on the [ASYNC_ADD_CMD](async.md) and [ASYNC_RM_CMD](async.md) async commands instead.
* Modules call commands via a command name but the host will assign a unique command id to all command names so clients can call them using a simple 2 byte integer instead of full text. The command ids can change as the modules change so it is recommended for clients to not depend on consistant command ids but depend on the [ASYNC_ADD_CMD](async.md) and [ASYNC_RM_CMD](async.md) async commands.
* The branch id is an id that can be assigned by the client itself to run muliple instances of the same command. Commands sent by a certain branch id will result in data sent back to the client from the module with that same branch id.
@ -48,7 +48,7 @@ TCP_Rev - this indicate any changes to the MRCI protocol that interface the
Mod_Rev - this indicate any changes to the IPC protocol that interface the
host with the modules via named pipes. any changes to the IPC
frames or major changes to async commands will cause this rev to
frames, NEW_CMD/CMD_ID type ids, etc. will cause this rev to
increment.
```

View File

@ -155,12 +155,9 @@ def local_install(app_target, app_name):
if not os.path.exists("/var/opt/" + app_target):
os.makedirs("/var/opt/" + app_target)
if not os.path.exists("/etc/" + app_target):
os.makedirs("/etc/" + 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/" + app_target + ".service", "/lib/systemd/system/" + app_target + ".service", 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)
verbose_copy("app_dir/linux/" + app_target, install_dir + "/" + app_target)
verbose_copy("app_dir/linux/lib", install_dir + "/lib")
@ -170,9 +167,8 @@ def local_install(app_target, app_name):
subprocess.run(["useradd", "-r", app_target])
subprocess.run(["chmod", "-R", "755", install_dir])
subprocess.run(["chmod", "644", "/lib/systemd/system/" + app_target + ".service"])
subprocess.run(["chmod", "755", "/etc/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, "/etc/" + app_target])
subprocess.run(["systemctl", "start", app_target])
subprocess.run(["systemctl", "enable", app_target])
@ -251,7 +247,7 @@ def make_install(app_ver, app_name):
print("adding file: " + file)
zip_file.write(file)
sub_copy_file(__file__, path, "main(is_sfx=False)", "main(is_sfx=True)\n\n\n", 11000)
sub_copy_file(__file__, path, "main(is_sfx=False)", "main(is_sfx=True)\n\n\n", 7700)
with open(path, "a") as dst_file, open("app_dir.zip", "rb") as src_file:
print("Packing the compressed app_dir into the sfx script file --")

View File

@ -79,7 +79,6 @@ CmdObject::CmdObject(QObject *parent) : MemShare(parent)
ipcWorker = new IPCWorker(pipe, nullptr);
keepAliveTimer = new QTimer(this);
progTimer = new QTimer(this);
dProc = new QProcess(this);
progCurrent = 0;
progMax = 0;
@ -121,11 +120,6 @@ void CmdObject::term()
flags = 0;
retCode = ABORTED;
if (dProc->state() == QProcess::Running)
{
dProc->kill();
}
onTerminate();
postProc();
}
@ -138,30 +132,6 @@ void CmdObject::kill()
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)
{
if (typeId == TERM_CMD)

View File

@ -18,7 +18,6 @@
// <http://www.gnu.org/licenses/>.
#include "common.h"
#include "db.h"
class IPCWorker : public QObject
{
@ -67,7 +66,6 @@ protected:
QTimer *keepAliveTimer;
QTimer *progTimer;
IPCWorker *ipcWorker;
QProcess *dProc;
quint32 flags;
quint16 retCode;
qint64 progCurrent;
@ -82,7 +80,6 @@ protected:
void startProgPulse();
void stopProgPulse();
void postProc();
bool runDetachedProc(const QStringList &args);
QString libName();
virtual void procIn(const QByteArray &, quint8) {}

View File

@ -47,18 +47,9 @@ ModProcess::ModProcess(const QString &app, const QString &memSes, const QString
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()
{
logErrMsgs(toCmdId32(ASYNC_SYS_MSG, 0));
emit dataToClient(toCmdId32(ASYNC_SYS_MSG, 0), readAllStandardError(), ERR);
}
void ModProcess::rdFromStdOut()
@ -81,8 +72,7 @@ quint16 ModProcess::genCmdId()
QString ModProcess::makeCmdUnique(const QString &name)
{
QString strNum;
auto names = cmdUniqueNames->values();
QStringList names = cmdUniqueNames->values();
for (int i = 1; names.contains(name + strNum); ++i)
{
@ -190,7 +180,7 @@ void ModProcess::onDataFromProc(quint8 typeId, const QByteArray &data)
}
else if (typeId == ERR)
{
qCritical() << "Module: " << program() << " - " << QString::fromUtf8(data);
qDebug() << QString::fromUtf8(data);
}
}
@ -277,6 +267,7 @@ void ModProcess::setSessionParams(QHash<quint16, QString> *uniqueNames,
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();
deleteLater();
@ -286,8 +277,7 @@ void ModProcess::err(QProcess::ProcessError error)
{
if (error == QProcess::FailedToStart)
{
qCritical() << "Module process: " << program() << " failed to start. reason: " << errorString();
qCritical() << "Args in use: " << arguments().join(' ');
qDebug() << "err: Module process: " << program() << " failed to start. reason: " << errorString();
onFailToStart();
}
@ -449,6 +439,7 @@ void CmdProcess::onReady()
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 cmdProcFinished(cmdId);
@ -462,9 +453,7 @@ void CmdProcess::onFinished(int exitCode, QProcess::ExitStatus exitStatus)
if (!cmdIdle)
{
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, "err: The command has stopped unexpectedly or it has failed to send an IDLE frame before exiting.\n", ERR);
emit dataToClient(cmdId, wrInt(CRASH, 16), IDLE);
}
@ -474,16 +463,16 @@ void CmdProcess::onFinished(int exitCode, QProcess::ExitStatus exitStatus)
deleteLater();
}
void CmdProcess::rdFromStdErr()
{
emit dataToClient(cmdId, readAllStandardError(), ERR);
}
void CmdProcess::rdFromStdOut()
{
emit dataToClient(cmdId, readAllStandardOutput(), TEXT);
}
void CmdProcess::rdFromStdErr()
{
logErrMsgs(cmdId);
}
void CmdProcess::dataFromSession(quint32 id, const QByteArray &data, quint8 dType)
{
if (id == cmdId)
@ -519,6 +508,13 @@ bool CmdProcess::validAsync(quint16 async, const QByteArray &data, QTextStream &
ret = false; errMsg << "expected data containing the user id and display name to be " << (BLKSIZE_USER_ID + BLKSIZE_DISP_NAME) << " bytes long.";
}
}
else if (async == ASYNC_MAXSES)
{
if (data.size() != BLKSIZE_HOST_LOAD)
{
ret = false; errMsg << "the 32bit max session int is not " << BLKSIZE_HOST_LOAD << " bytes long.";
}
}
else if (async == ASYNC_USER_RANK_CHANGED)
{
if (data.size() != (BLKSIZE_USER_ID + BLKSIZE_HOST_RANK))
@ -628,8 +624,9 @@ bool CmdProcess::validAsync(quint16 async, const QByteArray &data, QTextStream &
void CmdProcess::asyncDirector(quint16 id, const QByteArray &payload)
{
if ((id == ASYNC_KEEP_ALIVE) || (id == ASYNC_DEBUG_TEXT) || (id == ASYNC_LOGOUT) || (id == ASYNC_SET_DIR) ||
(id == ASYNC_END_SESSION) || (id == ASYNC_USER_LOGIN) || (id == ASYNC_OPEN_SUBCH) || (id == ASYNC_CLOSE_SUBCH))
if ((id == ASYNC_EXIT) || (id == ASYNC_MAXSES) || (id == ASYNC_LOGOUT) || (id == ASYNC_RESTART) ||
(id == ASYNC_END_SESSION) || (id == ASYNC_USER_LOGIN) || (id == ASYNC_OPEN_SUBCH) || (id == ASYNC_CLOSE_SUBCH) ||
(id == ASYNC_KEEP_ALIVE) || (id == ASYNC_SET_DIR) || (id == ASYNC_DEBUG_TEXT))
{
emit privIPC(id, payload);
}
@ -684,7 +681,7 @@ void CmdProcess::onDataFromProc(quint8 typeId, const QByteArray &data)
}
else
{
qCritical() << "async id: " << async << " from command id/name: " << toCmdId16(cmdId) << "/" << cmdName << " blocked. reason: " << errMsg;
qDebug() << "async id: " << async << " from command id: " << toCmdId16(cmdId) << " blocked. reason: " << errMsg;
}
}
}

View File

@ -18,7 +18,6 @@
// <http://www.gnu.org/licenses/>.
#include "common.h"
#include "db.h"
class ModProcess : public QProcess
{
@ -57,7 +56,6 @@ protected:
virtual void onDataFromProc(quint8 typeId, const QByteArray &data);
void cleanupPipe();
void logErrMsgs(quint32 id);
void wrIpcFrame(quint8 typeId, const QByteArray &data);
bool startProc(const QStringList &args);
bool isCmdLoaded(const QString &name);
@ -126,8 +124,8 @@ private:
private slots:
void rdFromStdOut();
void rdFromStdErr();
void rdFromStdOut();
public slots:

View File

@ -17,14 +17,18 @@
// <http://www.gnu.org/licenses/>.
RecoverAcct::RecoverAcct(QObject *parent) : CmdObject(parent) {}
IsEmailVerified::IsEmailVerified(QObject *parent) : CmdObject(parent) {}
ResetPwRequest::ResetPwRequest(QObject *parent) : CmdObject(parent) {}
VerifyEmail::VerifyEmail(QObject *parent) : CmdObject(parent) {}
IsEmailVerified::IsEmailVerified(QObject *parent) : CmdObject(parent) {}
SetEmailTemplate::SetEmailTemplate(QObject *parent) : CmdObject(parent) {}
PreviewEmail::PreviewEmail(QObject *parent) : CmdObject(parent) {}
QString RecoverAcct::cmdName() {return "recover_acct";}
QString ResetPwRequest::cmdName() {return "request_pw_reset";}
QString VerifyEmail::cmdName() {return "verify_email";}
QString IsEmailVerified::cmdName() {return "is_email_verified";}
QString SetEmailTemplate::cmdName() {return "set_email_template";}
QString PreviewEmail::cmdName() {return "preview_email";}
void delRecoverPw(const QByteArray &uId)
{
@ -55,7 +59,7 @@ bool expired(const QByteArray &uId)
auto expiry = db.getData(COLUMN_TIME).toDateTime().addSecs(3600); // pw datetime + 1hour;
if (expiry > QDateTime::currentDateTimeUtc())
if (expiry > QDateTime::currentDateTime().toUTC())
{
ret = false;
}
@ -71,7 +75,6 @@ void RecoverAcct::addToThreshold()
{
Query db(this);
// log recovery attempt failure
db.setType(Query::PUSH, TABLE_AUTH_LOG);
db.addColumn(COLUMN_USER_ID, uId);
db.addColumn(COLUMN_IPADDR, rdStringFromBlock(clientIp, BLKSIZE_CLIENT_IP));
@ -81,10 +84,12 @@ void RecoverAcct::addToThreshold()
db.addColumn(COLUMN_ACCEPTED, false);
db.exec();
auto confObj = confObject();
auto maxAttempts = confObj[CONF_AUTO_LOCK_LIM].toInt();
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_LOCK_LIMIT);
db.exec();
auto maxAttempts = db.getData(COLUMN_LOCK_LIMIT).toUInt();
// pull how many failed recovery attempts were made on this account
db.setType(Query::PULL, TABLE_AUTH_LOG);
db.addColumn(COLUMN_IPADDR);
db.addCondition(COLUMN_USER_ID, uId);
@ -93,26 +98,15 @@ void RecoverAcct::addToThreshold()
db.addCondition(COLUMN_ACCEPTED, false);
db.exec();
if (db.rows() > maxAttempts)
if (static_cast<quint32>(db.rows()) > maxAttempts)
{
delRecoverPw(uId);
// 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
{
errTxt("err: Access denied.\n\n");
errTxt("err: Access denied.\n");
privTxt("Enter the temporary password (leave blank to cancel): ");
}
}
@ -131,42 +125,18 @@ void RecoverAcct::procIn(const QByteArray &binIn, quint8 dType)
{
retCode = ABORTED;
flags &= ~MORE_INPUT;
mainTxt("\n");
}
else if (!acceptablePw(pw, uId, &errMsg))
{
errTxt(errMsg + "\n\n");
errTxt(errMsg + "\n");
privTxt("Enter a new password (leave blank to cancel): ");
}
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);
delRecoverPw(uId);
flags &= ~MORE_INPUT;
mainTxt("\n");
}
}
else
@ -175,12 +145,9 @@ void RecoverAcct::procIn(const QByteArray &binIn, quint8 dType)
{
retCode = ABORTED;
flags &= ~MORE_INPUT;
mainTxt("\n");
}
else if (!validPassword(pw))
{
errTxt("err: Invalid password.\n");
addToThreshold();
}
else if (!auth(uId, pw, TABLE_PW_RECOVERY))
@ -239,16 +206,11 @@ void ResetPwRequest::procIn(const QByteArray &binIn, uchar dType)
{
if (dType == TEXT)
{
auto confObj = confObject();
auto cmdLine = confObj[CONF_MAIL_CLIENT_CMD].toString();
auto subject = confObj[CONF_PW_RES_EMAIL_SUBJECT].toString();
auto msgFile = confObj[CONF_PW_RES_EMAIL_TEMP].toString();
auto args = parseArgs(binIn, 2);
auto email = getParam("-email", args);
auto name = getParam("-user", args);
QByteArray uId;
QString body;
if (!email.isEmpty() && validEmailAddr(email))
{
@ -265,39 +227,51 @@ void ResetPwRequest::procIn(const QByteArray &binIn, uchar dType)
{
errTxt("err: No such user.\n");
}
else if (getEmailParams(cmdLine, msgFile, &body))
else
{
retCode = EXECUTION_FAIL;
retCode = NO_ERRORS;
auto pw = genPw();
auto date = QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd HH:mm:ss (t)");
auto dbrdy = false;
if (recoverPWExists(uId))
{
dbrdy = updatePassword(uId, pw, TABLE_PW_RECOVERY);
updatePassword(uId, pw, TABLE_PW_RECOVERY);
}
else
{
dbrdy = createTempPw(uId, pw);
createTempPw(uId, pw);
}
if (dbrdy)
{
Query db(this);
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_TEMP_PW_SUBJECT);
db.addColumn(COLUMN_TEMP_PW_MSG);
db.addColumn(COLUMN_MAILERBIN);
db.addColumn(COLUMN_MAIL_SEND);
db.exec();
auto date = QDateTime::currentDateTimeUtc().toString("YYYY-MM-DD HH:MM:SS");
auto subject = db.getData(COLUMN_TEMP_PW_SUBJECT).toString();
auto body = db.getData(COLUMN_TEMP_PW_MSG).toString();
auto app = db.getData(COLUMN_MAILERBIN).toString();
auto cmdLine = db.getData(COLUMN_MAIL_SEND).toString();
body.replace(DATE_SUB, date);
body.replace(USERNAME_SUB, name);
body.replace(OTP_SUB, pw);
body.replace(TEMP_PW_SUB, pw);
cmdLine.replace(TARGET_EMAIL_SUB, email);
cmdLine.replace(SUBJECT_SUB, "'" + escapeChars(subject, '\\', '\'') + "'");
cmdLine.replace(MSG_SUB, "'" + escapeChars(body, '\\', '\'') + "'");
if (runDetachedProc(parseArgs(cmdLine.toUtf8(), -1)))
if (QProcess::startDetached(expandEnvVariables(app), 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 1hour.\n");
}
else
{
errTxt("err: The host email system has reported an internal error, try again later.\n");
}
}
}
@ -311,8 +285,6 @@ void VerifyEmail::procIn(const QByteArray &binIn, quint8 dType)
if (txt.isEmpty())
{
mainTxt("\n");
retCode = ABORTED;
flags &= ~MORE_INPUT;
}
@ -327,14 +299,11 @@ void VerifyEmail::procIn(const QByteArray &binIn, quint8 dType)
async(ASYNC_RW_MY_INFO, uId);
retCode = NO_ERRORS;
flags &= ~MORE_INPUT;
mainTxt("\n");
}
else
{
errTxt("err: The code you entered does not match.\n\n");
errTxt("err: The code you entered does not match.\n");
privTxt("Please try again: ");
}
}
@ -342,40 +311,50 @@ void VerifyEmail::procIn(const QByteArray &binIn, quint8 dType)
{
uId = rdFromBlock(userId, BLKSIZE_USER_ID);
auto confObj = confObject();
auto cmdLine = confObj[CONF_MAIL_CLIENT_CMD].toString();
auto subject = confObj[CONF_EVERIFY_SUBJECT].toString();
auto msgFile = confObj[CONF_EVERIFY_TEMP].toString();
auto email = getEmailForUser(uId);
QString body;
QString err;
if (email.isEmpty())
{
retCode = INVALID_PARAMS;
if (getEmailParams(cmdLine, msgFile, &body))
errTxt("err: Your account currently has no email address, please update it.\n");
}
else
{
flags |= MORE_INPUT;
code = QString::number(QRandomGenerator::global()->bounded(100000, 999999));
Query db(this);
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_CONFIRM_SUBJECT);
db.addColumn(COLUMN_CONFIRM_MSG);
db.addColumn(COLUMN_MAILERBIN);
db.addColumn(COLUMN_MAIL_SEND);
db.exec();
auto uName = rdStringFromBlock(userName, BLKSIZE_USER_NAME);
auto date = QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd HH:mm:ss (t)");
auto date = QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd HH:mm:ss");
auto subject = db.getData(COLUMN_CONFIRM_SUBJECT).toString();
auto body = db.getData(COLUMN_CONFIRM_MSG).toString();
auto app = db.getData(COLUMN_MAILERBIN).toString();
auto cmdLine = db.getData(COLUMN_MAIL_SEND).toString();
body.replace(DATE_SUB, date);
body.replace(USERNAME_SUB, uName);
body.replace(OTP_SUB, code);
body.replace(CONFIRM_CODE_SUB, code);
cmdLine.replace(TARGET_EMAIL_SUB, email);
cmdLine.replace(SUBJECT_SUB, "'" + escapeChars(subject, '\\', '\'') + "'");
cmdLine.replace(MSG_SUB, "'" + escapeChars(body, '\\', '\'') + "'");
if (runDetachedProc(parseArgs(cmdLine.toUtf8(), -1)))
if (QProcess::startDetached(expandEnvVariables(app), 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: ");
}
else
{
retCode = EXECUTION_FAIL;
errTxt("err: The host email system has reported an internal error, try again later.\n");
}
}
}
@ -399,3 +378,252 @@ void IsEmailVerified::procIn(const QByteArray &binIn, quint8 dType)
mainTxt(boolStr(db.getData(COLUMN_EMAIL_VERIFIED).toBool()) + "\n");
}
}
void SetEmailTemplate::procIn(const QByteArray &binIn, quint8 dType)
{
if ((flags & MORE_INPUT) && (dType == GEN_FILE))
{
bodyText.append(QString::fromUtf8(binIn));
dataSent += binIn.size();
mainTxt(QString::number(dataSent) + "/" + len + "\n");
if (dataSent >= len.toInt())
{
mainTxt("\nUpload complete.\n");
proc();
}
}
else if ((dType == GEN_FILE) || (dType == TEXT))
{
auto args = parseArgs(binIn, 9);
dataSent = 0;
textFromFile = argExists("-client_file", args);
subject = getParam("-subject", args);
bodyText = getParam("-body", args);
len = getParam("-len", args);
if (argExists("-reset_template", args))
{
eType = PW_RESET;
}
else if (argExists("-confirm_template", args))
{
eType = CONFIRM_EMAIL;
}
else
{
eType = NONE;
}
retCode = INVALID_PARAMS;
if (eType == NONE)
{
errTxt("err: Which template do you want to change? -reset_template or -confirm_template not found.\n");
}
else if (textFromFile && !isInt(len))
{
errTxt("err: '" + len + "' given in -len is not a valid integer.\n");
}
else if (textFromFile && (len.toInt() <= 0))
{
errTxt("err: The text file size cannot be 0 or less than 0.\n");
}
else if (textFromFile && (len.toInt() > 20000))
{
errTxt("err: The text file size is too large. it cannot exceed 20,000 bytes or 10,000 chars.\n");
}
else
{
retCode = NO_ERRORS;
if (argExists("-subject", args) && subject.isEmpty())
{
if (eType == CONFIRM_EMAIL) subject = DEFAULT_CONFIRM_SUBJECT;
else subject = DEFAULT_TEMP_PW_SUBJECT;
}
if (argExists("-body", args) && bodyText.isEmpty())
{
if (eType == CONFIRM_EMAIL) bodyText = TXT_ConfirmCodeTemplate;
else bodyText = TXT_TempPwTemplate;
}
if (textFromFile)
{
mainTxt("Input hooked...awaiting text file data.\n\n");
bodyText.clear();
flags |= MORE_INPUT;
emit procOut(QByteArray(), GEN_FILE);
emit procOut(QByteArray(), GEN_FILE);
}
else
{
proc();
}
}
}
}
void SetEmailTemplate::proc()
{
if (bodyText.isEmpty() && subject.isEmpty())
{
retCode = INVALID_PARAMS;
errTxt("err: The email body and subject text are empty, nothing will be changed.\n");
}
else
{
Query db(this);
db.setType(Query::UPDATE, TABLE_SERV_SETTINGS);
QString codeSub;
QString bodyColumn;
QString subjectColumn;
auto execQuery = false;
if (eType == PW_RESET)
{
codeSub = TEMP_PW_SUB;
bodyColumn = COLUMN_TEMP_PW_MSG;
subjectColumn = COLUMN_TEMP_PW_SUBJECT;
}
else
{
codeSub = CONFIRM_CODE_SUB;
bodyColumn = COLUMN_CONFIRM_MSG;
subjectColumn = COLUMN_CONFIRM_SUBJECT;
}
if (!bodyText.isEmpty())
{
retCode = INVALID_PARAMS;
if (!bodyText.contains(DATE_SUB, Qt::CaseInsensitive))
{
errTxt("err: The email body does not contain: " + QString(DATE_SUB) + "\n");
}
else if (!bodyText.contains(USERNAME_SUB, Qt::CaseInsensitive))
{
errTxt("err: The email body does not contain: " + QString(USERNAME_SUB) + "\n");
}
else if (!bodyText.contains(codeSub, Qt::CaseInsensitive))
{
errTxt("err: The email body does not contain: " + codeSub + "\n");
}
else if (bodyText.size() > 10000)
{
errTxt("err: The email body is too large. it cannot exceed 10,000 chars.\n");
}
else
{
mainTxt("Email body updated.\n");
db.addColumn(bodyColumn, bodyText);
execQuery = true;
retCode = NO_ERRORS;
}
}
if (!subject.isEmpty())
{
retCode = INVALID_PARAMS;
if (subject.size() > 120)
{
errTxt("err: The subject is too large. it cannot exceed 120 chars.\n");
}
else
{
mainTxt("Email subject updated.\n");
db.addColumn(subjectColumn, subject);
execQuery = true;
retCode = NO_ERRORS;
}
}
if (execQuery) db.exec();
}
flags &= ~MORE_INPUT;
}
void PreviewEmail::procIn(const QByteArray &binIn, quint8 dType)
{
if (dType == TEXT)
{
auto args = parseArgs(binIn, 4);
auto eType = NONE;
if (argExists("-reset_email", args)) eType = PW_RESET;
else if (argExists("-confirm_email", args)) eType = CONFIRM_EMAIL;
if (eType == NONE)
{
retCode = INVALID_PARAMS;
errTxt("err: which template do you want to preview? -reset_email or -confirm_email not found.\n");
}
else
{
QString codeSub;
QString code;
QString bodyColumn;
QString subjectColumn;
auto date = QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd HH:mm:ss");
if (eType == PW_RESET)
{
codeSub = TEMP_PW_SUB;
code = genPw();
bodyColumn = COLUMN_TEMP_PW_MSG;
subjectColumn = COLUMN_TEMP_PW_SUBJECT;
}
else
{
codeSub = CONFIRM_CODE_SUB;
code = QString::number(QRandomGenerator::global()->bounded(100000, 999999));
bodyColumn = COLUMN_CONFIRM_MSG;
subjectColumn = COLUMN_CONFIRM_SUBJECT;
}
Query db(this);
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(bodyColumn);
db.addColumn(subjectColumn);
db.exec();
auto uName = rdStringFromBlock(userName, BLKSIZE_USER_NAME);
auto subject = db.getData(subjectColumn).toString();
auto body = db.getData(bodyColumn).toString();
body.replace(DATE_SUB, date);
body.replace(USERNAME_SUB, uName);
body.replace(codeSub, code);
QString txt;
QTextStream txtOut(&txt);
txtOut << "-----Subject-------" << Qt::endl << Qt::endl;
txtOut << subject << Qt::endl << Qt::endl;
txtOut << "-----Body----------" << Qt::endl << Qt::endl;
mainTxt(txt);
bigTxt(body);
}
}
}

View File

@ -1,4 +1,4 @@
#ifndef ACCT_RECOVERY_H
#ifndef ACCT_RECOVERY_H
#define ACCT_RECOVERY_H
// This file is part of MRCI.
@ -20,6 +20,13 @@
#include "../common.h"
#include "../cmd_object.h"
enum TemplateType
{
PW_RESET,
CONFIRM_EMAIL,
NONE
};
bool expired(const QByteArray &uId);
void delRecoverPw(const QByteArray &uId);
@ -93,4 +100,45 @@ public:
explicit IsEmailVerified(QObject *parent = nullptr);
};
//------------------
class SetEmailTemplate : public CmdObject
{
Q_OBJECT
private:
QString bodyText;
QString subject;
QString len;
int dataSent;
bool textFromFile;
TemplateType eType;
void proc();
public:
static QString cmdName();
void procIn(const QByteArray &binIn, quint8 dType);
explicit SetEmailTemplate(QObject *parent = nullptr);
};
//-----------------
class PreviewEmail : public CmdObject
{
Q_OBJECT
public:
static QString cmdName();
void procIn(const QByteArray &binIn, quint8 dType);
explicit PreviewEmail(QObject *parent = nullptr);
};
#endif // ACCT_RECOVERY_H

510
src/commands/admin.cpp Normal file
View File

@ -0,0 +1,510 @@
#include "admin.h"
// This file is part of MRCI.
// MRCI is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// MRCI is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with MRCI under the LICENSE.md file. If not, see
// <http://www.gnu.org/licenses/>.
CloseHost::CloseHost(QObject *parent) : CmdObject(parent) {}
RestartHost::RestartHost(QObject *parent) : CmdObject(parent) {}
ServSettings::ServSettings(QObject *parent) : CmdObject(parent) {}
QString CloseHost::cmdName() {return "close_host";}
QString RestartHost::cmdName() {return "restart_host";}
QString ServSettings::cmdName() {return "host_config";}
void CloseHost::procIn(const QByteArray &binIn, quint8 dType)
{
if (dType == TEXT)
{
if (flags & MORE_INPUT)
{
auto input = QString::fromUtf8(binIn);
if (input == "CLOSE")
{
flags &= ~MORE_INPUT;
async(ASYNC_EXIT);
}
else if (input.isEmpty())
{
retCode = ABORTED;
flags &= ~MORE_INPUT;
}
else
{
errTxt("err: Invalid response. you need to type 'CLOSE' exactly as shown without the quotes.\n");
promptTxt("Enter 'CLOSE' to proceed or leave blank to cancel: ");
}
}
else
{
flags |= MORE_INPUT;
promptTxt("You are about to shutdown the host instance, type: 'CLOSE' to proceed or leave blank to cancel: ");
}
}
}
void RestartHost::procIn(const QByteArray &binIn, quint8 dType)
{
if (dType == TEXT)
{
if (flags & MORE_INPUT)
{
auto input = QString::fromUtf8(binIn);
if (input == "RESTART")
{
flags &= ~MORE_INPUT;
async(ASYNC_RESTART);
}
else if (input.isEmpty())
{
retCode = ABORTED;
flags &= ~MORE_INPUT;
}
else if (!input.isEmpty())
{
errTxt("err: Invalid response. you need to type 'RESTART' exactly as shown without the quotes.\n");
promptTxt("Enter 'RESTART' to proceed or leave blank to cancel: ");
}
}
else
{
flags |= MORE_INPUT;
promptTxt("You are about to re-start the host instance, type: 'RESTART' to proceed or leave blank to cancel: ");
}
}
}
void ServSettings::printSettings()
{
Query db(this);
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_PUB_USERS);
db.addColumn(COLUMN_LOCK_LIMIT);
db.addColumn(COLUMN_MAXSESSIONS);
db.addColumn(COLUMN_INITRANK);
db.addColumn(COLUMN_MAILERBIN);
db.addColumn(COLUMN_MAIL_SEND);
db.addColumn(COLUMN_ENABLE_CONFIRM);
db.addColumn(COLUMN_ENABLE_PW_RESET);
db.addColumn(COLUMN_ACTIVE_UPDATE);
db.addColumn(COLUMN_MAX_SUB_CH);
db.exec();
auto pubBool = boolStr(db.getData(COLUMN_PUB_USERS).toBool());
auto resBool = boolStr(db.getData(COLUMN_ENABLE_PW_RESET).toBool());
auto conBool = boolStr(db.getData(COLUMN_ENABLE_CONFIRM).toBool());
auto actBool = boolStr(db.getData(COLUMN_ACTIVE_UPDATE).toBool());
QString txt;
QTextStream txtOut(&txt);
txtOut << "All Sub-Channels Active Update: " << actBool << Qt::endl;
txtOut << "Public Registration: " << pubBool << Qt::endl;
txtOut << "Automated Password Resets: " << resBool << Qt::endl;
txtOut << "Automated Email Verify: " << conBool << Qt::endl;
txtOut << "Maximum Sessions: " << db.getData(COLUMN_MAXSESSIONS).toUInt() << Qt::endl;
txtOut << "Autolock Threshold: " << db.getData(COLUMN_LOCK_LIMIT).toUInt() << Qt::endl;
txtOut << "Maximum Sub-Channels: " << db.getData(COLUMN_MAX_SUB_CH).toUInt() << Qt::endl;
txtOut << "Initial Host Rank: " << db.getData(COLUMN_INITRANK).toUInt() << Qt::endl;
txtOut << "Root User: " << getUserName(rootUserId()) << Qt::endl;
txtOut << "Working Path: " << QDir::currentPath() << Qt::endl;
txtOut << "Mailer Executable: " << db.getData(COLUMN_MAILERBIN).toString() << Qt::endl;
txtOut << "Mailer Command: " << db.getData(COLUMN_MAIL_SEND).toString() << Qt::endl << Qt::endl;
printDatabaseInfo(txtOut);
mainTxt(txt);
}
void ServSettings::printOptions()
{
if (level == 0)
{
QString txt;
QTextStream txtOut(&txt);
txtOut << "[01] Autolock Threshold [02] Max Sessions" << Qt::endl;
txtOut << "[03] Public Registration [04] Initial Rank" << Qt::endl;
txtOut << "[05] Mailer Exe [06] Mailer Command" << Qt::endl;
txtOut << "[07] Password Resets [08] Email Verify" << Qt::endl;
txtOut << "[09] Active Update [10] Max Sub-Channels" << Qt::endl;
txtOut << "[11] Set Root User [00] Exit" << Qt::endl << Qt::endl;
txtOut << "Select an option: ";
level = 1;
promptTxt(txt);
}
}
void ServSettings::returnToStart()
{
select = 0;
level = 0;
mainTxt("\n");
printSettings();
printOptions();
}
void ServSettings::procIn(const QByteArray &binIn, quint8 dType)
{
if (dType == TEXT)
{
if (flags & MORE_INPUT)
{
if (level == 1)
{
QString txt;
QTextStream txtOut(&txt);
auto ok = false;
select = QString::fromUtf8(binIn).toInt(&ok);
if ((select == 1) && ok)
{
txtOut << "" << Qt::endl;
txtOut << "The autolock threshold is an integer value that determines how many" << Qt::endl;
txtOut << "failed login attempts can be made before the user account is locked" << Qt::endl;
txtOut << "by the host." << Qt::endl << Qt::endl;
txtOut << "Enter a new value (leave blank to cancel): ";
level = 2;
}
else if ((select == 2) && ok)
{
txtOut << "" << Qt::endl;
txtOut << "Max sessions is an integar value that determines how many simultaneous" << Qt::endl;
txtOut << "clients the host will be allowed to run at once." << Qt::endl << Qt::endl;
txtOut << "Enter a new value (leave blank to cancel): ";
level = 2;
}
else if ((select == 3) && ok)
{
txtOut << "" << Qt::endl;
txtOut << "Public registration basically allows un-logged in clients to run the" << Qt::endl;
txtOut << "add_acct command. doing this allows un-registered users to become" << Qt::endl;
txtOut << "registered users without the need to contact an admin." << Qt::endl << Qt::endl;
txtOut << "[0] Disable" << Qt::endl;
txtOut << "[1] Enable" << Qt::endl << Qt::endl;
txtOut << "Select an option (leave blank to cancel): ";
level = 2;
}
else if ((select == 4) && ok)
{
txtOut << "" << Qt::endl;
txtOut << "The initial host rank is the rank all new user accounts are registered" << Qt::endl;
txtOut << "with when created. the host rank itself is an integer value that" << Qt::endl;
txtOut << "determine what commands each user can or cannot run." << Qt::endl << Qt::endl;
txtOut << "Enter a new value (leave blank to cancel): ";
level = 2;
}
else if ((select == 5) && ok)
{
txtOut << "" << Qt::endl;
txtOut << "This is the path to the command line email client executable" << Qt::endl;
txtOut << "that the host can utilize to send emails to registered users." << Qt::endl << Qt::endl;
txtOut << "note: the host assumes the email application already has a" << Qt::endl;
txtOut << " configured sender email address/server." << Qt::endl << Qt::endl;
txtOut << "Enter a new path (leave blank to cancel): ";
level = 2;
}
else if ((select == 6) && ok)
{
txtOut << "" << Qt::endl;
txtOut << "This is the command line that will be used with the email client" << Qt::endl;
txtOut << "executable to send emails to registered users. it must contain the" << Qt::endl;
txtOut << "keywords " << SUBJECT_SUB << ", " << TARGET_EMAIL_SUB << " and " << MSG_SUB << " to be" << Qt::endl;
txtOut << "acceptable. the host will substitute these keywords for actual" << Qt::endl;
txtOut << "parameters when calling the email client." << Qt::endl << Qt::endl;
txtOut << "Enter a new command line (leave blank to cancel): ";
level = 2;
}
else if ((select == 7) && ok)
{
txtOut << "" << Qt::endl;
txtOut << "This enables automated password resets via email so users can" << Qt::endl;
txtOut << "reset their account passwords without the need to contact an" << Qt::endl;
txtOut << "admin. this basically tells the host if it is allowed to load" << Qt::endl;
txtOut << "the request_pw_reset and recover_acct commands or not." << Qt::endl << Qt::endl;
txtOut << "[0] Disable" << Qt::endl;
txtOut << "[1] Enable" << Qt::endl << Qt::endl;
txtOut << "Select an option (leave blank to cancel): ";
level = 2;
}
else if ((select == 8) && ok)
{
txtOut << "" << Qt::endl;
txtOut << "This enables/disables automated email confirmations. this" << Qt::endl;
txtOut << "tells the host if it is allowed to load the verify_email " << Qt::endl;
txtOut << "command for any user, regardless of rank." << Qt::endl << Qt::endl;
txtOut << "[0] Disable" << Qt::endl;
txtOut << "[1] Enable" << Qt::endl << Qt::endl;
txtOut << "Select an option (leave blank to cancel): ";
level = 2;
}
else if ((select == 9) && ok)
{
txtOut << "" << Qt::endl;
txtOut << "This option tells the host if all sub-channels should be considered" << Qt::endl;
txtOut << "active or not. otherwise, the active flag can be toggled on/off at the" << Qt::endl;
txtOut << "sub-channel level. active sub-channels send/receive PEER_INFO or" << Qt::endl;
txtOut << "PEER_STAT frames with each other so all peers connected to the" << Qt::endl;
txtOut << "sub-channel can be made aware of each other's public information." << Qt::endl;
txtOut << "without the active flag, no such frames are transffered." << Qt::endl << Qt::endl;
txtOut << "[0] Disable" << Qt::endl;
txtOut << "[1] Enable" << Qt::endl << Qt::endl;
txtOut << "Select an option (leave blank to cancel): ";
level = 2;
}
else if ((select == 10) && ok)
{
txtOut << "" << Qt::endl;
txtOut << "This option sets the maximum amount of sub-channels each channel can" << Qt::endl;
txtOut << "have. the hard maximum is 256 and the minimum is 1." << Qt::endl << Qt::endl;
txtOut << "Enter a new value (leave blank to cancel): ";
level = 2;
}
else if ((select == 11) && ok)
{
txtOut << "" << Qt::endl;
txtOut << "Set the root user of the host by the given user name. the root user" << Qt::endl;
txtOut << "is an unrestricted user that can do anything on the host. this user" << Qt::endl;
txtOut << "however is unable to change rank (1) and cannot get deleted. only" << Qt::endl;
txtOut << "the current root user can use this option to appoint an existing" << Qt::endl;
txtOut << "user as the new root." << Qt::endl << Qt::endl;
if (rdFromBlock(userId, BLKSIZE_USER_ID) != rootUserId())
{
txtOut << "Enter a new user name (leave blank to cancel): ";
}
else
{
txtOut << "You are not the current root user so this option is blocked." << Qt::endl;
txtOut << "Press enter to return to the main menu.";
}
level = 2;
}
else if ((select == 0) && ok)
{
flags &= ~MORE_INPUT;
}
else
{
txtOut << "" << Qt::endl << "Select an option: ";
}
promptTxt(txt);
}
else if (level == 2)
{
auto value = QString::fromUtf8(binIn);
if (value.isEmpty())
{
mainTxt("\n");
returnToStart();
}
else
{
if ((select == 1) || (select == 2) || (select == 4))
{
bool ok;
quint32 num = value.toUInt(&ok, 10);
if (!ok)
{
errTxt("err: Invalid 32bit unsigned integer. valid range: 1-4294967295.\n");
promptTxt("Enter a new value (leave blank to cancel): ");
}
else if (num == 0)
{
errTxt("err: This value cannot be 0, valid range: 1-4294967295.\n");
promptTxt("Enter a new value (leave blank to cancel): ");
}
else
{
Query db(this);
db.setType(Query::UPDATE, TABLE_SERV_SETTINGS);
if (select == 1) db.addColumn(COLUMN_LOCK_LIMIT, num);
else if (select == 2) db.addColumn(COLUMN_MAXSESSIONS, num);
else db.addColumn(COLUMN_INITRANK, num);
db.exec();
if (select == 2)
{
async(ASYNC_MAXSES, wrInt(num, BLKSIZE_HOST_LOAD * 8));
}
returnToStart();
}
}
else if ((select == 3) || (select == 7) || (select == 8) || (select == 9))
{
if (!isBool(value))
{
errTxt("err: Invalid boolean value. must be 0 (false) or 1 (true).\n");
promptTxt("Select an option (leave blank to cancel): ");
}
else
{
QString column;
if (select == 3) column = COLUMN_PUB_USERS;
else if (select == 7) column = COLUMN_ENABLE_PW_RESET;
else if (select == 8) column = COLUMN_ENABLE_CONFIRM;
else column = COLUMN_ACTIVE_UPDATE;
Query db(this);
db.setType(Query::UPDATE, TABLE_SERV_SETTINGS);
db.addColumn(column, static_cast<bool>(value.toUInt()));
db.exec();
returnToStart();
}
}
else if (select == 5)
{
if (!QFile::exists(expandEnvVariables(value)))
{
errTxt("err: The given file: '" + value + "' does not exists.\n");
promptTxt("Enter a new path (leave blank to cancel): ");
}
else
{
Query db(this);
db.setType(Query::UPDATE, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_MAILERBIN, value);
db.exec();
returnToStart();
}
}
else if (select == 6)
{
if (!value.contains(SUBJECT_SUB, Qt::CaseInsensitive))
{
errTxt("err: The '" + QString(SUBJECT_SUB) + "' keyword is missing.\n");
promptTxt("Enter a new command line (leave blank to cancel): ");
}
else if (!value.contains(TARGET_EMAIL_SUB, Qt::CaseInsensitive))
{
errTxt("err: The '" + QString(TARGET_EMAIL_SUB) + "' keyword is missing.\n");
promptTxt("Enter a new command line (leave blank to cancel): ");
}
else if (!value.contains(MSG_SUB, Qt::CaseInsensitive))
{
errTxt("err: The '" + QString(MSG_SUB) + "' keyword is missing.\n");
promptTxt("Enter a new command line (leave blank to cancel): ");
}
else
{
Query db(this);
db.setType(Query::UPDATE, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_MAIL_SEND, value);
db.exec();
returnToStart();
}
}
else if (select == 10)
{
if (!isInt(value))
{
errTxt("err: '" + value + "' is not a valid integer.\n");
promptTxt("Enter a new value (leave blank to cancel): ");
}
else if ((value.toInt() < 1) || (value.toInt() > 256))
{
errTxt("err: A valid maximum sub-channels value ranges between 1-256.\n");
promptTxt("Enter a new value (leave blank to cancel): ");
}
else
{
Query db(this);
db.setType(Query::UPDATE, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_MAX_SUB_CH, value.toInt());
db.exec();
returnToStart();
}
}
else if (select == 11)
{
QByteArray uId;
if (rdFromBlock(userId, BLKSIZE_USER_ID) != rootUserId())
{
returnToStart();
}
else if (!validUserName(value))
{
errTxt("err: Invalid user name. it must be 2-24 chars long and contain no spaces.\n");
promptTxt("Enter a new user name (leave blank to cancel): ");
}
else if (!userExists(value, &uId))
{
errTxt("err: The requested user name does not exists.\n");
promptTxt("Enter a new user name (leave blank to cancel): ");
}
else
{
Query db(this);
db.setType(Query::UPDATE, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_ROOT_USER, value);
db.exec();
returnToStart();
}
}
}
}
}
else
{
select = 0;
level = 0;
flags |= MORE_INPUT;
printSettings();
printOptions();
}
}
}

75
src/commands/admin.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef ADMIN_CMDS_H
#define ADMIN_CMDS_H
// This file is part of MRCI.
// MRCI is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// MRCI is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with MRCI under the LICENSE.md file. If not, see
// <http://www.gnu.org/licenses/>.
#include "../common.h"
#include "../cmd_object.h"
class CloseHost : public CmdObject
{
Q_OBJECT
public:
static QString cmdName();
void procIn(const QByteArray &binIn, quint8 dType);
explicit CloseHost(QObject *parent = nullptr);
};
//--------------------------------------
class RestartHost : public CmdObject
{
Q_OBJECT
public:
static QString cmdName();
void procIn(const QByteArray &binIn, quint8 dType);
explicit RestartHost(QObject *parent = nullptr);
};
//--------------------------------------
class ServSettings : public CmdObject
{
Q_OBJECT
private:
int level;
int select;
void printOptions();
void printSettings();
void returnToStart();
public:
static QString cmdName();
void procIn(const QByteArray &binIn, quint8 dType);
explicit ServSettings(QObject *parent = nullptr);
};
#endif // ADMIN_CMDS_H

View File

@ -38,7 +38,6 @@ void Auth::addToThreshold()
{
Query db(this);
// log the failed login attempt
db.setType(Query::PUSH, TABLE_AUTH_LOG);
db.addColumn(COLUMN_USER_ID, uId);
db.addColumn(COLUMN_IPADDR, ip);
@ -48,9 +47,12 @@ void Auth::addToThreshold()
db.addColumn(COLUMN_ACCEPTED, false);
db.exec();
auto maxAttempts = confObject()[CONF_AUTO_LOCK_LIM].toInt();
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_LOCK_LIMIT);
db.exec();
auto maxAttempts = db.getData(COLUMN_LOCK_LIMIT).toUInt();
// pull all login attempts on the account
db.setType(Query::PULL, TABLE_AUTH_LOG);
db.addColumn(COLUMN_IPADDR);
db.addCondition(COLUMN_USER_ID, uId);
@ -59,26 +61,14 @@ void Auth::addToThreshold()
db.addCondition(COLUMN_ACCEPTED, false);
db.exec();
if (db.rows() > maxAttempts)
if (static_cast<quint32>(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.addColumn(COLUMN_LOCKED, true);
db.addCondition(COLUMN_USER_ID, uId);
db.exec();
flags &= ~MORE_INPUT;
retCode = INVALID_PARAMS;
errTxt("err: Maximum login attempts exceeded, the account is now locked.\n");
}
else
{
@ -91,15 +81,19 @@ void Auth::confirmAuth()
{
Query db(this);
// 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);
if (rootUserId() == uId)
{
db.addCondition(COLUMN_IPADDR, ip);
}
db.exec();
// log the login attempt as accepted
db.setType(Query::PUSH, TABLE_AUTH_LOG);
db.addColumn(COLUMN_USER_ID, uId);
db.addColumn(COLUMN_IPADDR, ip);
@ -173,6 +167,11 @@ void Auth::procIn(const QByteArray &binIn, quint8 dType)
flags &= ~MORE_INPUT;
retCode = ABORTED;
}
else if (noCaseMatch(DEFAULT_ROOT_USER, text))
{
errTxt("err: '" + QString(DEFAULT_ROOT_USER) + "' is a reserved keyword. invalid for use as a user name.\n");
promptTxt("Enter a new user name (leave blank to cancel): ");
}
else if (validEmailAddr(text))
{
errTxt("err: Invaild user name. it looks like an email address.\n");
@ -221,7 +220,6 @@ void Auth::procIn(const QByteArray &binIn, quint8 dType)
}
else if (!validPassword(text))
{
errTxt("err: Invalid password.\n");
addToThreshold();
}
else if (!auth(uId, text, TABLE_USERS))

View File

@ -486,7 +486,7 @@ void SetActiveState::procIn(const QByteArray &binIn, quint8 dType)
db.addCondition(COLUMN_SUB_CH_ID, subId);
db.exec();
if (confObject()[CONF_ALL_CH_UPDATE].toBool())
if (globalActiveFlag())
{
mainTxt("warning: The host currently have the global active update flag set so setting this flag at the sub-channel level does nothing.\n");
}
@ -541,9 +541,7 @@ void CreateSubCh::procIn(const QByteArray &binIn, quint8 dType)
}
else if (!genSubId(chId, &subId))
{
auto max = confObject()[CONF_MAX_SUBS].toInt();
errTxt("err: This channel has reached the maximum amount sub-channels it can have (" + QString::number(max) + ").\n");
errTxt("err: This channel has reached the maximum amount sub-channels it can have (" + QString::number(maxSubChannels()) + ").\n");
}
else
{

View File

@ -26,6 +26,13 @@ IPHist::IPHist(QObject *parent) : TableViewer(parent)
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)
{
list = cmdList;
@ -36,6 +43,7 @@ MyInfo::MyInfo(QObject *parent) : CmdObject(parent) {}
QString HostInfo::cmdName() {return "host_info";}
QString IPHist::cmdName() {return "ls_act_log";}
QString ListDBG::cmdName() {return "ls_dbg";}
QString MyInfo::cmdName() {return "my_info";}
QString ListCommands::shortText(const QString &cmdName)
@ -67,6 +75,10 @@ void ListCommands::onIPCConnected()
{
genType = QByteArray(1, GEN_UPLOAD);
}
else if (cmdName == SetEmailTemplate::cmdName())
{
genType = QByteArray(1, GEN_UPLOAD);
}
QByteArray frame;
@ -88,16 +100,21 @@ void HostInfo::procIn(const QByteArray &binIn, quint8 dType)
if (dType == TEXT)
{
Query db(this);
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_IPADDR);
db.addColumn(COLUMN_PORT);
db.addColumn(COLUMN_MAXSESSIONS);
db.exec();
QString txt;
QTextStream txtOut(&txt);
hostSharedMem->lock();
auto confObj = confObject();
auto sesCount = rd32BitFromBlock(hostLoad);
auto maxSes = confObj[CONF_MAX_SESSIONS].toInt();
auto addr = confObj[CONF_LISTEN_ADDR].toString();
auto port = confObj[CONF_LISTEN_PORT].toInt();
quint32 sesCount = rd32BitFromBlock(hostLoad);
quint32 maxSes = db.getData(COLUMN_MAXSESSIONS).toUInt();
hostSharedMem->unlock();
@ -106,8 +123,8 @@ void HostInfo::procIn(const QByteArray &binIn, quint8 dType)
txtOut << "Host Name: " << QSysInfo::machineHostName() << Qt::endl;
txtOut << "Host OS: " << QSysInfo::prettyProductName() << Qt::endl;
txtOut << "Load: " << sesCount << "/" << maxSes << Qt::endl;
txtOut << "Listening Addr: " << addr << Qt::endl;
txtOut << "Listening Port: " << port << Qt::endl;
txtOut << "Listening Addr: " << db.getData(COLUMN_IPADDR).toString() << Qt::endl;
txtOut << "Listening Port: " << db.getData(COLUMN_PORT).toUInt() << Qt::endl;
mainTxt(txt);
}

View File

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

View File

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

View File

@ -148,7 +148,7 @@ void CreateUser::procIn(const QByteArray &binIn, quint8 dType)
errTxt(errMsg + "\n");
privTxt("Enter a new password (leave blank to cancel): ");
}
else if (!createUser(newName, email, dispName, password, confObject()[CONF_INIT_RANK].toInt()))
else if (!createUser(newName, email, dispName, password))
{
retCode = INVALID_PARAMS;
@ -181,6 +181,10 @@ void CreateUser::procIn(const QByteArray &binIn, quint8 dType)
{
errTxt("err: Invalid username. it must be 2-24 chars long and contain no spaces.\n");
}
else if (noCaseMatch(DEFAULT_ROOT_USER, newName))
{
errTxt("err: '" + QString(DEFAULT_ROOT_USER) + "' is a reserved keyword. invalid for use as a username.\n");
}
else if (validEmailAddr(newName))
{
errTxt("err: Invaild username. it looks like an email address.\n");
@ -273,6 +277,10 @@ void RemoveUser::procIn(const QByteArray &binIn, quint8 dType)
{
errTxt("err: The requested user name does not exists.\n");
}
else if (rootUserId() == uId)
{
errTxt("err: Unable to delete root user: '" + uName + "'\n");
}
else if (isChOwner(uId))
{
errTxt("err: The requested user name is the owner of one or more channels. assign new owners for these channels before attempting to delete this account.\n");
@ -338,7 +346,11 @@ void ChangeUserRank::procIn(const QByteArray &binIn, quint8 dType)
{
errTxt("err: The requested user account does not exists.\n");
}
else if (!canModifyUser(uId, rd32BitFromBlock(hostRank), uId == rdFromBlock(userId, BLKSIZE_USER_ID)))
else if (rootUserId() == uId)
{
errTxt("err: You are not allowed to change the rank of root user: '" + uName + "'\n");
}
else if (!canModifyUser(uId, rd32BitFromBlock(hostRank), false))
{
errTxt("err: The target user out ranks you or is equal to your own rank. access denied.\n");
}
@ -411,6 +423,10 @@ void ChangeUsername::procIn(const QByteArray &binIn, quint8 dType)
{
errTxt("err: Invalid username. it must be 2-24 chars long and contain no spaces.\n");
}
else if (noCaseMatch(DEFAULT_ROOT_USER, newName))
{
errTxt("err: '" + QString(DEFAULT_ROOT_USER) + "' is a reserved keyword. invalid for use as a username.\n");
}
else if (validEmailAddr(newName))
{
errTxt("err: Invaild username. it looks like an email address.\n");

View File

@ -16,178 +16,14 @@
// along with MRCI under the LICENSE.md file. If not, see
// <http://www.gnu.org/licenses/>.
#include "db.h"
QString getLocalFilePath(const QString &fileName, bool var)
QString sslCertChain()
{
#ifdef Q_OS_WINDOWS
auto path = QDir::homePath() + "\\AppData\\" + APP_TARGET + "\\" + fileName;
#else
auto path = QDir::homePath() + "/." + APP_TARGET + "/" + fileName;
#endif
if (!QFile::exists(path))
{
#ifdef Q_OS_WINDOWS
path = expandEnvVariables("%PROGRAMDATA%\\") + APP_TARGET + "\\" + fileName;
#else
if (var)
{
path = QString("/var/opt/") + APP_TARGET + "/" + fileName;
}
else
{
path = QString("/etc/") + APP_TARGET + "/" + fileName;
return expandEnvVariables(qEnvironmentVariable(ENV_PUB_KEY, DEFAULT_PUB_KEY_NAME));
}
#endif
}
mkPath(QFileInfo(path).path());
return path;
}
QJsonObject confObject()
QString sslPrivKey()
{
QJsonObject obj;
QFile file(getLocalFilePath(CONF_FILENAME));
if (file.exists())
{
if (file.open(QFile::ReadOnly))
{
obj = QJsonDocument::fromJson(file.readAll()).object();
}
else if (file.remove())
{
obj = confObject();
}
}
else
{
obj.insert(CONF_LISTEN_ADDR, DEFAULT_LISTEN_ADDRESS);
obj.insert(CONF_LISTEN_PORT, DEFAULT_LISTEN_PORT);
obj.insert(CONF_AUTO_LOCK_LIM, DEFAULT_LOCK_LIMIT);
obj.insert(CONF_MAX_SESSIONS, DEFAULT_MAXSESSIONS);
obj.insert(CONF_MAX_SUBS, DEFAULT_MAX_SUBS);
obj.insert(CONF_INIT_RANK, DEFAULT_INIT_RANK);
obj.insert(CONF_ENABLE_PUB_REG, false);
obj.insert(CONF_ENABLE_EVERIFY, true);
obj.insert(CONF_ENABLE_PWRES, true);
obj.insert(CONF_ALL_CH_UPDATE, false);
obj.insert(CONF_DB_DRIVER, DEFAULT_DB_DRIVER);
obj.insert(CONF_DB_ADDR, getLocalFilePath(DEFAULT_DB_FILENAME, true));
obj.insert(CONF_DB_UNAME, QSysInfo::machineHostName());
obj.insert(CONF_DB_PW, QString(QSysInfo::machineUniqueId().toHex()));
obj.insert(CONF_MAIL_CLIENT_CMD, DEFAULT_MAIL_SEND);
obj.insert(CONF_CERT_CHAIN, getLocalFilePath(DEFAULT_CERT_FILENAME));
obj.insert(CONF_PRIV_KEY, getLocalFilePath(DEFAULT_PRIV_FILENAME));
obj.insert(CONF_PW_RES_EMAIL_SUBJECT, DEFAULT_TEMP_PW_SUBJECT);
obj.insert(CONF_EVERIFY_SUBJECT, DEFAULT_CONFIRM_SUBJECT);
obj.insert(CONF_PW_RES_EMAIL_TEMP, getLocalFilePath(DEFAULT_RES_PW_FILENAME));
obj.insert(CONF_EVERIFY_TEMP, getLocalFilePath(DEFAULT_EVERIFY_FILENAME));
wrDefaultMailTemplates(obj);
if (file.open(QFile::WriteOnly | QFile::Truncate))
{
file.write(QJsonDocument(obj).toJson());
}
}
file.close();
return obj;
}
void wrDefaultMailTemplates(const QJsonObject &obj)
{
QFile file(obj[CONF_PW_RES_EMAIL_TEMP].toString());
if (file.open(QFile::WriteOnly | QFile::Truncate))
{
file.write(TXT_TempPwTemplate);
}
file.close();
file.setFileName(obj[CONF_EVERIFY_TEMP].toString());
if (file.open(QFile::WriteOnly | QFile::Truncate))
{
file.write(TXT_ConfirmCodeTemplate);
}
file.close();
}
void updateConf(const QJsonObject &obj)
{
QFile file(getLocalFilePath(CONF_FILENAME));
if (file.open(QFile::WriteOnly | QFile::Truncate))
{
file.write(QJsonDocument(obj).toJson());
}
file.close();
}
void updateConf(const char *key, const QJsonValue &value)
{
auto obj = confObject();
obj[key] = value;
updateConf(obj);
}
bool getEmailParams(const QString &mailCmd, const QString &bodyFile, QString *bodyText)
{
auto ret = false;
bodyText->clear();
QFile file(bodyFile);
if (!file.open(QFile::ReadOnly))
{
qCritical() << "Could not open the email message template file '" << bodyFile << "' reason: " << file.errorString();
}
else
{
auto body = QString(file.readAll());
if ((!body.contains(DATE_SUB, Qt::CaseInsensitive)) ||
(!body.contains(USERNAME_SUB, Qt::CaseInsensitive)) ||
(!body.contains(OTP_SUB, Qt::CaseInsensitive)))
{
qCritical() << "Email message template '" << bodyFile << "' is missing one or more of the following key words: " << DATE_SUB << ", " << USERNAME_SUB << ", " << OTP_SUB;
}
else if ((mailCmd.contains(SUBJECT_SUB, Qt::CaseInsensitive)) ||
(mailCmd.contains(MSG_SUB, Qt::CaseInsensitive)) ||
(mailCmd.contains(TARGET_EMAIL_SUB, Qt::CaseInsensitive)))
{
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;
}
else
{
ret = true;
}
}
file.close();
return ret;
return expandEnvVariables(qEnvironmentVariable(ENV_PRIV_KEY, DEFAULT_PRIV_KEY_NAME));
}
QByteArray rdFileContents(const QString &path, QTextStream &msg)
@ -233,16 +69,9 @@ QString boolStr(bool state)
QString genSerialNumber()
{
Serial::threadIndex++;
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);
return QString::number(QDateTime::currentDateTime().toMSecsSinceEpoch()) + "-" + QString::number(Serial::serialIndex);
}
void serializeThread(QThread *thr)
@ -695,7 +524,7 @@ bool fullMatchChs(const char *openChs, const char *comp)
void containsActiveCh(const char *subChs, char *actBlock)
{
if (confObject()[CONF_ALL_CH_UPDATE].toBool())
if (globalActiveFlag())
{
wr8BitToBlock(1, actBlock);
}
@ -729,11 +558,34 @@ void containsActiveCh(const char *subChs, char *actBlock)
void printDatabaseInfo(QTextStream &txt)
{
auto confObj = confObject();
auto json = getDbSettings();
auto driver = json["driver"].toString();
txt << "Database Parameters --" << Qt::endl << Qt::endl;
txt << "Driver: " << confObj[CONF_DB_DRIVER].toString() << Qt::endl;
txt << "Host: " << confObj[CONF_DB_ADDR].toString() << Qt::endl << Qt::endl;
txt << "Driver: " << driver << Qt::endl;
if (driver == "QSQLITE")
{
txt << "File: " << sqlDataPath() << Qt::endl;
}
else
{
txt << "Host: " << json["host_name"].toString() << Qt::endl;
txt << "User: " << json["user_name"].toString() << Qt::endl;
}
txt << Qt::endl;
}
QString defaultPw()
{
Query db;
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_DEFAULT_PASS);
db.exec();
return db.getData(COLUMN_DEFAULT_PASS).toString();
}
bool channelExists(const QString &chName, quint64 *chId)
@ -785,6 +637,17 @@ bool inviteExists(const QByteArray &uId, quint64 chId)
return db.rows();
}
bool globalActiveFlag()
{
Query db;
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_ACTIVE_UPDATE);
db.exec();
return db.getData(COLUMN_ACTIVE_UPDATE).toBool();
}
bool genSubId(quint64 chId, quint8 *newId)
{
bool ret = false;
@ -796,7 +659,7 @@ bool genSubId(quint64 chId, quint8 *newId)
db.addCondition(COLUMN_CHANNEL_ID, chId);
db.exec();
if (db.rows() < confObject()[CONF_MAX_SUBS].toInt())
if (db.rows() < maxSubChannels())
{
QList<quint8> subList;
@ -828,6 +691,17 @@ bool isChOwner(const QByteArray &uId)
return db.rows();
}
int maxSubChannels()
{
Query db;
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_MAX_SUB_CH);
db.exec();
return db.getData(COLUMN_MAX_SUB_CH).toInt();
}
int channelAccessLevel(const QByteArray &uId, const char *override, quint64 chId)
{
if (rd8BitFromBlock(override) == 1)
@ -1054,7 +928,7 @@ QString getParam(const QString &key, const QStringList &args)
QString ret;
int pos = args.indexOf(QRegularExpression(key, QRegularExpression::CaseInsensitiveOption));
int pos = args.indexOf(QRegExp(key, Qt::CaseInsensitive));
if (pos != -1)
{
@ -1122,6 +996,8 @@ ShellIPC::ShellIPC(const QStringList &args, bool supressErr, QObject *parent) :
bool ShellIPC::connectToHost()
{
auto pipeInfo = QFileInfo(HOST_CONTROL_PIPE);
connectToServer(HOST_CONTROL_PIPE);
if (!waitForConnected() && !holdErrs)
@ -1153,6 +1029,4 @@ void ShellIPC::dataIn()
emit closeInstance();
}
quint64 Serial::threadIndex = 0;
quint16 Serial::msgIndex = 0;
bool Serial::msgDetails = true;
quint64 Serial::serialIndex = 0;

View File

@ -26,15 +26,17 @@
#include <QRandomGenerator>
#include <QProcess>
#include <QHash>
#include <QRegularExpression>
#include <QRegExp>
#include <QStringList>
#include <QDateTime>
#include <QHostAddress>
#include <QCoreApplication>
#include <QTextCodec>
#include <QFileInfo>
#include <QDir>
#include <QSysInfo>
#include <QFileInfoList>
#include <QTemporaryFile>
#include <QChar>
#include <QtMath>
#include <QStorageInfo>
@ -61,157 +63,35 @@
#include <QTcpSocket>
#include <QMessageLogContext>
#include <QtGlobal>
#include <QJsonObject>
#include <QJsonDocument>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QVariant>
#include <QList>
#include <QSqlError>
#include <QDir>
#include <QFile>
#include <QCryptographicHash>
#include <QDateTime>
#include <QLibrary>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include "db.h"
#include "shell.h"
#include "mem_share.h"
#define APP_NAME "MRCI"
#define APP_VER "5.1.2.1"
#define APP_TARGET "mrci"
#define SERVER_HEADER_TAG "MRCI"
#define HOST_CONTROL_PIPE "MRCI_HOST_CONTROL"
#define FRAME_HEADER_SIZE 8
#define MAX_FRAME_BITS 24
#define LOCAL_BUFFSIZE 16777215
#define CLIENT_HEADER_LEN 292
#define MAX_LS_ENTRIES 50
#define MAX_LOG_SIZE 100000000
#define SUBJECT_SUB "%subject%"
#define MSG_SUB "%message_body%"
#define TARGET_EMAIL_SUB "%target_email%"
#define OTP_SUB "%otp%"
#define USERNAME_SUB "%user_name%"
#define DATE_SUB "%date%"
#ifdef Q_OS_WIN
#define DEFAULT_MAIL_SEND "%COMSPEC% echo %message_body% | mutt -s %subject% %target_email%"
#else
#define DEFAULT_MAIL_SEND "/bin/sh -c \"echo %message_body% | mutt -s %subject% %target_email%\""
#endif
#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_RES_PW_FILENAME "res_pw_template.txt"
#define DEFAULT_EVERIFY_FILENAME "email_verify_template.txt"
#define DEFAULT_CONFIRM_SUBJECT "Email Verification"
#define DEFAULT_TEMP_PW_SUBJECT "Password Reset"
#define DEFAULT_LISTEN_ADDRESS "0.0.0.0"
#define DEFAULT_LISTEN_PORT 35516
#define DEFAULT_LOCK_LIMIT 20
#define DEFAULT_MAXSESSIONS 100
#define DEFAULT_MAX_SUBS 50
#define DEFAULT_INIT_RANK 2
#define CONF_FILENAME "conf.json"
#define CONF_LISTEN_ADDR "listening_addr"
#define CONF_LISTEN_PORT "listening_port"
#define CONF_AUTO_LOCK_LIM "auto_lock_limit"
#define CONF_MAX_SESSIONS "max_sessions"
#define CONF_MAX_SUBS "max_sub_channels"
#define CONF_INIT_RANK "initial_rank"
#define CONF_ENABLE_PUB_REG "enable_public_reg"
#define CONF_ENABLE_EVERIFY "enable_email_verify"
#define CONF_ENABLE_PWRES "enable_pw_reset"
#define CONF_ALL_CH_UPDATE "all_channels_active_update"
#define CONF_DB_DRIVER "db_driver"
#define CONF_DB_ADDR "db_host_name"
#define CONF_DB_UNAME "db_user_name"
#define CONF_DB_PW "db_password"
#define CONF_MAIL_CLIENT_CMD "mail_client_cmd"
#define CONF_CERT_CHAIN "tls_cert_chain"
#define CONF_PRIV_KEY "tls_priv_key"
#define CONF_PW_RES_EMAIL_SUBJECT "reset_pw_mail_subject"
#define CONF_EVERIFY_SUBJECT "email_verify_subject"
#define CONF_PW_RES_EMAIL_TEMP "reset_pw_mail_template"
#define CONF_EVERIFY_TEMP "email_verify_template"
#define TABLE_IPHIST "ip_history"
#define TABLE_USERS "users"
#define TABLE_CMD_RANKS "command_ranks"
#define TABLE_AUTH_LOG "auth_log"
#define TABLE_PW_RECOVERY "pw_recovery"
#define TABLE_CHANNELS "channels"
#define TABLE_CH_MEMBERS "channel_members"
#define TABLE_SUB_CHANNELS "sub_channels"
#define TABLE_RDONLY_CAST "read_only_flags"
#define TABLE_MODULES "modules"
#define COLUMN_IPADDR "ip_address"
#define COLUMN_LOGENTRY "log_entry"
#define COLUMN_SESSION_ID "session_id"
#define COLUMN_TIME "time_stamp"
#define COLUMN_USERNAME "user_name"
#define COLUMN_HOST_RANK "host_rank"
#define COLUMN_COMMAND "command_name"
#define COLUMN_MOD_MAIN "module_executable"
#define COLUMN_SALT "salt"
#define COLUMN_HASH "hash"
#define COLUMN_EMAIL "email_address"
#define COLUMN_NEED_PASS "new_password_req"
#define COLUMN_NEED_NAME "new_name_req"
#define COLUMN_EMAIL_VERIFIED "email_verified"
#define COLUMN_LOCKED "locked"
#define COLUMN_AUTH_ATTEMPT "auth_attempt"
#define COLUMN_RECOVER_ATTEMPT "recover_attempt"
#define COLUMN_COUNT "count_to_threshold"
#define COLUMN_ACCEPTED "accepted"
#define COLUMN_DISPLAY_NAME "display_name"
#define COLUMN_USER_ID "user_id"
#define COLUMN_CHANNEL_NAME "channel_name"
#define COLUMN_CHANNEL_ID "channel_id"
#define COLUMN_ACTIVE_UPDATE "active_updates"
#define COLUMN_SUB_CH_NAME "sub_channel_name"
#define COLUMN_SUB_CH_ID "sub_channel_id"
#define COLUMN_PENDING_INVITE "pending_invite"
#define COLUMN_LOWEST_LEVEL "lowest_access_level"
#define COLUMN_ACCESS_LEVEL "access_level"
#define COLUMN_APP_NAME "client_app"
#define TXT_TempPwTemplate "\
A password reset was requested for your account: %user_name%\n\
Your recovery password is as follows:\n\n\
%otp%\n\n\
PLEASE IGNORE THIS EMAIL IF YOU MADE NO SUCH REQUEST.\n\n\
Date requested: %date%."
#define TXT_ConfirmCodeTemplate "\
Please confirm your email address for account: %user_name%\n\
Your confirmation code is as follows:\n\n\
%otp%\n\n\
Date requested: %date%."
#define SERVER_HEADER_TAG "MRCI"
#define HOST_CONTROL_PIPE "MRCI_HOST_CONTROL"
enum AsyncCommands : quint16
{
ASYNC_RDY = 1, // client | retricted
ASYNC_SYS_MSG = 2, // client | retricted
ASYNC_EXIT = 3, // internal | private
ASYNC_CAST = 4, // client | public
ASYNC_MAXSES = 5, // internal | private
ASYNC_LOGOUT = 6, // internal | private
ASYNC_USER_DELETED = 7, // client | public
ASYNC_DISP_RENAMED = 8, // internal | public
ASYNC_USER_RANK_CHANGED = 9, // internal | public
ASYNC_CMD_RANKS_CHANGED = 10, // internal | public
ASYNC_RESTART = 11, // internal | private
ASYNC_ENABLE_MOD = 12, // internal | public
ASYNC_DISABLE_MOD = 13, // internal | public
ASYNC_END_SESSION = 14, // internal | private
@ -341,7 +221,6 @@ enum ChannelMemberLevel : quint8
};
class Session;
class Query;
QByteArray toFixedTEXT(const QString &txt, int len);
QByteArray nullTermTEXT(const QString &txt);
@ -353,10 +232,6 @@ void serializeThread(QThread *thr);
void mkPath(const QString &path);
void listDir(QList<QPair<QString,QString> > &list, const QString &srcPath, const QString &dstPath);
void containsActiveCh(const char *subChs, char *actBlock);
void updateConf(const char *key, const QJsonValue &value);
void updateConf(const QJsonObject &obj);
void wrDefaultMailTemplates(const QJsonObject &obj);
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 QString &uName, const QString &dispName, const QString &email, QString *errMsg);
bool containsNewLine(const QString &str);
@ -386,10 +261,12 @@ bool noCaseMatch(const QString &strA, const QString &strB);
bool argExists(const QString &key, const QStringList &args);
bool matchAnyCh(const char *chsA, const char *chsB);
bool fullMatchChs(const char *openChs, const char *comp);
bool globalActiveFlag();
bool genSubId(quint64 chId, quint8 *newId);
bool isChOwner(const QByteArray &uId);
int channelAccessLevel(const QByteArray &uId, quint64 chId);
int channelAccessLevel(const QByteArray &uId, const char *override, quint64 chId);
int maxSubChannels();
QString getUserNameForEmail(const QString &email);
QString getEmailForUser(const QByteArray &uId);
QString getDispName(const QByteArray &uId);
@ -398,10 +275,10 @@ QString boolStr(bool state);
QString getParam(const QString &key, const QStringList &args);
QString escapeChars(const QString &str, const QChar &escapeChr, const QChar &chr);
QString genSerialNumber();
QString genMsgNumber();
QString getLocalFilePath(const QString &fileName, bool var = false);
QString defaultPw();
QString sslCertChain();
QString sslPrivKey();
QStringList parseArgs(const QByteArray &data, int maxArgs, int *pos = nullptr);
QJsonObject confObject();
//---------------------------
@ -467,9 +344,7 @@ class Serial
public:
static quint64 threadIndex;
static quint16 msgIndex;
static bool msgDetails;
static quint64 serialIndex;
};
#endif // COMMON_H

View File

@ -27,23 +27,29 @@ QString columnType(const QString &column)
if ((column == COLUMN_IPADDR) || (column == COLUMN_LOGENTRY) || (column == COLUMN_USERNAME) ||
(column == COLUMN_CHANNEL_NAME) || (column == COLUMN_EMAIL) || (column == COLUMN_SUB_CH_NAME) ||
(column == COLUMN_COMMAND) || (column == COLUMN_DISPLAY_NAME))
(column == COLUMN_COMMAND) || (column == COLUMN_CLIENT_VER) || (column == COLUMN_DISPLAY_NAME))
{
ret = "TEXT COLLATE NOCASE";
}
else if ((column == COLUMN_COUNT) || (column == COLUMN_ACCEPTED) || (column == COLUMN_LOCKED) ||
(column == COLUMN_NEED_PASS) || (column == COLUMN_NEED_NAME) || (column == COLUMN_PENDING_INVITE) ||
(column == COLUMN_AUTH_ATTEMPT) || (column == COLUMN_EMAIL_VERIFIED) || (column == COLUMN_ACTIVE_UPDATE) ||
(column == COLUMN_RECOVER_ATTEMPT))
(column == COLUMN_NEED_PASS) || (column == COLUMN_NEED_NAME) || (column == COLUMN_PUB_USERS) ||
(column == COLUMN_AUTH_ATTEMPT) || (column == COLUMN_EMAIL_VERIFIED) || (column == COLUMN_ENABLE_PW_RESET) ||
(column == COLUMN_ENABLE_CONFIRM) || (column == COLUMN_RECOVER_ATTEMPT) || (column == COLUMN_ACTIVE_UPDATE) ||
(column == COLUMN_PENDING_INVITE))
{
ret = "BOOL";
}
else if (column == COLUMN_MOD_MAIN)
else if ((column == COLUMN_TEMP_PW_MSG) || (column == COLUMN_ZIPBIN) || (column == COLUMN_CONFIRM_SUBJECT) ||
(column == COLUMN_ZIPCOMPRESS) || (column == COLUMN_ZIPEXTRACT) || (column == COLUMN_TEMP_PW_SUBJECT) ||
(column == COLUMN_MAILERBIN) || (column == COLUMN_MAIL_SEND) || (column == COLUMN_CONFIRM_MSG) ||
(column == COLUMN_MOD_MAIN))
{
ret = "TEXT";
}
else if ((column == COLUMN_CHANNEL_ID) || (column == COLUMN_LOWEST_LEVEL) || (column == COLUMN_SUB_CH_ID) ||
(column == COLUMN_HOST_RANK) || (column == COLUMN_ACCESS_LEVEL))
else if ((column == COLUMN_LOCK_LIMIT) || (column == COLUMN_PORT) || (column == COLUMN_BAN_LIMIT) ||
(column == COLUMN_HOST_RANK) || (column == COLUMN_MAXSESSIONS) || (column == COLUMN_LOWEST_LEVEL) ||
(column == COLUMN_ACCESS_LEVEL) || (column == COLUMN_CHANNEL_ID) || (column == COLUMN_SUB_CH_ID) ||
(column == COLUMN_MAX_SUB_CH) || (column == COLUMN_INITRANK))
{
ret = "INTEGER";
}
@ -70,6 +76,11 @@ QByteArray genUniqueHash()
return hasher.result();
}
QString sqlDataPath()
{
return expandEnvVariables(qEnvironmentVariable(ENV_DB_PATH, DEFAULT_DB_FILE));
}
QList<int> genSequence(int min, int max, int len)
{
QList<int> ret;
@ -178,6 +189,17 @@ QString genPw()
return ret;
}
quint32 initHostRank()
{
Query db;
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_INITRANK);
db.exec();
return db.getData(COLUMN_INITRANK).toUInt();
}
QByteArray getSalt(const QByteArray &uId, const QString &table)
{
Query db;
@ -190,20 +212,77 @@ QByteArray getSalt(const QByteArray &uId, const QString &table)
return db.getData(COLUMN_SALT).toByteArray();
}
bool testDbWritable()
QByteArray rootUserId()
{
Query db;
db.setType(Query::CREATE_TABLE, "test");
db.addColumn("test");
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_ROOT_USER);
db.exec();
db.setType(Query::DEL_TABLE, "test");
auto id = db.getData(COLUMN_ROOT_USER).toByteArray();
return db.exec();
if (id.isEmpty())
{
db.setType(Query::PULL, TABLE_USERS);
db.addColumn(COLUMN_USER_ID);
db.addCondition(COLUMN_USERNAME, DEFAULT_ROOT_USER);
db.exec();
id = db.getData(COLUMN_USER_ID).toByteArray();
}
bool createUser(const QString &userName, const QString &email, const QString &dispName, const QString &password, int rank, bool requireNewPass)
return id;
}
QJsonObject getDbSettings(bool defaults)
{
QJsonObject ret;
QFile file(DEFAULT_DB_JSON_FILE);
if (file.exists() && !defaults)
{
if (file.open(QFile::ReadOnly))
{
ret = QJsonDocument::fromJson(file.readAll()).object();
}
else
{
ret = getDbSettings(true);
}
}
else
{
ret.insert("driver", "QSQLITE");
ret.insert("host_name", "localhost");
ret.insert("user_name", QSysInfo::machineHostName());
ret.insert("password", QString(QSysInfo::machineUniqueId().toHex()));
if (file.open(QFile::WriteOnly | QFile::Truncate))
{
file.write(QJsonDocument(ret).toJson());
}
}
file.close();
return ret;
}
void saveDbSettings(const QJsonObject &obj)
{
QFile file(DEFAULT_DB_JSON_FILE);
if (file.open(QFile::WriteOnly | QFile::Truncate))
{
file.write(QJsonDocument(obj).toJson());
}
file.close();
}
bool createUser(const QString &userName, const QString &email, const QString &dispName, const QString &password)
{
auto ret = false;
auto newUId = genUniqueHash();
@ -213,7 +292,7 @@ bool createUser(const QString &userName, const QString &email, const QString &di
db.setType(Query::PUSH, TABLE_USERS);
db.addColumn(COLUMN_USERNAME, userName);
db.addColumn(COLUMN_EMAIL, email);
db.addColumn(COLUMN_HOST_RANK, rank);
db.addColumn(COLUMN_HOST_RANK, initHostRank());
db.addColumn(COLUMN_DISPLAY_NAME, dispName);
db.addColumn(COLUMN_EMAIL_VERIFIED, false);
db.addColumn(COLUMN_NEED_PASS, false);
@ -224,7 +303,7 @@ bool createUser(const QString &userName, const QString &email, const QString &di
if (db.exec())
{
ret = updatePassword(newUId, password, TABLE_USERS, requireNewPass);
ret = updatePassword(newUId, password, TABLE_USERS);
}
return ret;
@ -283,7 +362,7 @@ bool auth(const QByteArray &uId, const QString &password, const QString &table)
QCryptographicHash hasher(QCryptographicHash::Keccak_512);
hasher.addData(password.toUtf8() + salt);
hasher.addData(QTextCodec::codecForName("UTF-16LE")->fromUnicode(password) + salt);
db.setType(Query::PULL, table);
db.addColumn(COLUMN_HASH);
@ -322,43 +401,53 @@ Query::Query(QObject *parent) : QObject(parent)
if (!QSqlDatabase::contains(getConnectionName()))
{
auto confObj = confObject();
auto driver = confObj[CONF_DB_DRIVER].toString();
auto settings = getDbSettings();
auto driver = settings["driver"].toString();
auto db = QSqlDatabase::addDatabase(driver, getConnectionName());
db.setConnectOptions("ISC_DPB_LC_CTYPE=UTF8");
if (driver == "QSQLITE")
{
db.setDatabaseName(confObj[CONF_DB_ADDR].toString());
db.setDatabaseName(sqlDataPath());
}
else
{
db.setDatabaseName(APP_NAME);
db.setUserName(confObj[CONF_DB_UNAME].toString());
db.setHostName(confObj[CONF_DB_ADDR].toString());
db.setPassword(confObj[CONF_DB_PW].toString());
db.setUserName(settings["user_name"].toString());
db.setHostName(settings["host_name"].toString());
db.setPassword(settings["password"].toString());
}
if (db.open())
{
enableForeignKeys(true);
setTextEncoding("UTF8");
if (!testDbWritable())
{
queryOk = false;
qCritical("%s", "Write access to the database is denied.");
}
}
else
{
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()
@ -424,6 +513,7 @@ void Query::setType(QueryType qType, const QString &tbl)
limit.clear();
columnList.clear();
bindValues.clear();
lastErr.clear();
directBind.clear();
whereBinds.clear();
columnsAsPassed.clear();
@ -472,11 +562,6 @@ void Query::setType(QueryType qType, const QString &tbl)
break;
}
case DEL_TABLE:
txt << "DROP TABLE " << tbl;
break;
}
}
@ -820,6 +905,7 @@ bool Query::exec()
}
queryOk = query.exec();
lastErr = query.lastError().driverText().trimmed();
rowsAffected = query.numRowsAffected();
if (queryOk && query.isSelect())
@ -840,24 +926,6 @@ bool Query::exec()
{
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;

157
src/db.h
View File

@ -17,7 +17,150 @@
// along with MRCI under the LICENSE.md file. If not, see
// <http://www.gnu.org/licenses/>.
#include "common.h"
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QVariant>
#include <QList>
#include <QSqlError>
#include <QDir>
#include <QCoreApplication>
#include <QFile>
#include <QTextCodec>
#include <QThread>
#include <QCryptographicHash>
#include <QTextStream>
#include <QStringList>
#include <QProcess>
#include <QDateTime>
#include <QRandomGenerator>
#include <QJsonObject>
#include <QJsonDocument>
#include "shell.h"
#define APP_NAME "MRCI"
#define APP_VER "4.1.2.0"
#define APP_TARGET "mrci"
#ifdef Q_OS_WIN
#define DEFAULT_MAILBIN "%COMSPEC%"
#define DEFAULT_MAIL_SEND "echo %message_body% | mutt -s %subject% %target_email%"
#define DEFAULT_WORK_DIR "%PROGRAMDATA%\\mrci"
#else
#define DEFAULT_MAILBIN "/bin/sh"
#define DEFAULT_MAIL_SEND "-c \"echo %message_body% | mutt -s %subject% %target_email%\""
#define DEFAULT_WORK_DIR "/var/opt/mrci"
#endif
#define ENV_DB_PATH "MRCI_DB_PATH"
#define ENV_WORK_DIR "MRCI_WORK_DIR"
#define ENV_PRIV_KEY "MRCI_PRIV_KEY"
#define ENV_PUB_KEY "MRCI_PUB_KEY"
#define SUBJECT_SUB "%subject%"
#define MSG_SUB "%message_body%"
#define TARGET_EMAIL_SUB "%target_email%"
#define CONFIRM_CODE_SUB "%confirmation_code%"
#define TEMP_PW_SUB "%temp_pw%"
#define USERNAME_SUB "%user_name%"
#define DATE_SUB "%date%"
#define DEFAULT_PUB_KEY_NAME "cert.pem"
#define DEFAULT_PRIV_KEY_NAME "priv.pem"
#define DEFAULT_DB_FILE "data.db"
#define DEFAULT_DB_JSON_FILE "db_settings.json"
#define DEFAULT_ROOT_USER "root"
#define DEFAULT_CONFIRM_SUBJECT "Email Verification"
#define DEFAULT_TEMP_PW_SUBJECT "Password Reset"
#define DEFAULT_LISTEN_ADDRESS "0.0.0.0"
#define DEFAULT_LISTEN_PORT 35516
#define DEFAULT_BAN_LIMIT 30
#define DEFAULT_LOCK_LIMIT 20
#define DEFAULT_MAXSESSIONS 100
#define DEFAULT_MAX_SUBS 50
#define DEFAULT_INIT_RANK 2
#define MAX_LS_ENTRIES 50
#define MAX_CMDS_PER_MOD 256
#define TXT_TempPwTemplate "\
A password reset was requested for account: %user_name%\n\
Your recovery password is as follows:\n\n\
%temp_pw%\n\n\
PLEASE IGNORE THIS EMAIL IF YOU MADE NO SUCH REQUEST.\n\n\
Date requested: %date%."
#define TXT_ConfirmCodeTemplate "\
Please confirm your email address for account: %user_name%\n\
Your confirmation code is as follows:\n\n\
%confirmation_code%\n\n\
Date requested: %date%."
#define TABLE_IPHIST "ip_history"
#define TABLE_USERS "users"
#define TABLE_SERV_SETTINGS "host_settings"
#define TABLE_CMD_RANKS "command_ranks"
#define TABLE_AUTH_LOG "auth_log"
#define TABLE_PW_RECOVERY "pw_recovery"
#define TABLE_DMESG "host_debug_messages"
#define TABLE_CHANNELS "channels"
#define TABLE_CH_MEMBERS "channel_members"
#define TABLE_SUB_CHANNELS "sub_channels"
#define TABLE_RDONLY_CAST "read_only_flags"
#define TABLE_MODULES "modules"
#define COLUMN_IPADDR "ip_address"
#define COLUMN_LOGENTRY "log_entry"
#define COLUMN_CLIENT_VER "client_version"
#define COLUMN_SESSION_ID "session_id"
#define COLUMN_TIME "time_stamp"
#define COLUMN_USERNAME "user_name"
#define COLUMN_PORT "port"
#define COLUMN_BAN_LIMIT "auto_ban_threshold"
#define COLUMN_LOCK_LIMIT "auto_lock_threshold"
#define COLUMN_MAXSESSIONS "max_sessions"
#define COLUMN_HOST_RANK "host_rank"
#define COLUMN_COMMAND "command_name"
#define COLUMN_MOD_MAIN "module_executable"
#define COLUMN_INITRANK "initial_host_rank"
#define COLUMN_PUB_USERS "allow_public_registrations"
#define COLUMN_SALT "salt"
#define COLUMN_HASH "hash"
#define COLUMN_EMAIL "email_address"
#define COLUMN_NEED_PASS "new_password_req"
#define COLUMN_NEED_NAME "new_name_req"
#define COLUMN_EMAIL_VERIFIED "email_verified"
#define COLUMN_LOCKED "locked"
#define COLUMN_ZIPBIN "archiver_executable"
#define COLUMN_ZIPEXTRACT "extract_command"
#define COLUMN_ZIPCOMPRESS "compress_command"
#define COLUMN_AUTH_ATTEMPT "auth_attempt"
#define COLUMN_RECOVER_ATTEMPT "recover_attempt"
#define COLUMN_COUNT "count_to_threshold"
#define COLUMN_ACCEPTED "accepted"
#define COLUMN_MAILERBIN "mailer_executable"
#define COLUMN_MAIL_SEND "mailer_send_command"
#define COLUMN_TEMP_PW_MSG "temp_pw_email_template"
#define COLUMN_CONFIRM_MSG "verify_email_template"
#define COLUMN_CONFIRM_SUBJECT "verify_email_subject"
#define COLUMN_TEMP_PW_SUBJECT "temp_pw_email_subject"
#define COLUMN_ENABLE_PW_RESET "enable_pw_reset"
#define COLUMN_ENABLE_CONFIRM "eable_email_verify"
#define COLUMN_DISPLAY_NAME "display_name"
#define COLUMN_USER_ID "user_id"
#define COLUMN_CHANNEL_NAME "channel_name"
#define COLUMN_CHANNEL_ID "channel_id"
#define COLUMN_ACTIVE_UPDATE "active_updates"
#define COLUMN_SUB_CH_NAME "sub_channel_name"
#define COLUMN_SUB_CH_ID "sub_channel_id"
#define COLUMN_PENDING_INVITE "pending_invite"
#define COLUMN_LOWEST_LEVEL "lowest_access_level"
#define COLUMN_ACCESS_LEVEL "access_level"
#define COLUMN_MAX_SUB_CH "max_sub_channels"
#define COLUMN_APP_NAME "client_app"
#define COLUMN_DEFAULT_PASS "default_password"
#define COLUMN_ROOT_USER "root_user"
QString genPw();
QList<int> genSequence(int min, int max, int len);
@ -25,14 +168,17 @@ QChar genLetter();
QChar genNum();
QChar genSpecialChar();
int inRange(int pos, int min, int max);
QString sqlDataPath();
QString columnType(const QString &column);
quint32 initHostRank();
QByteArray getSalt(const QByteArray &uId, const QString &table);
QByteArray genUniqueHash();
bool createUser(const QString &userName, const QString &email, const QString &dispName, const QString &password, int rank, bool requireNewPass = false);
QByteArray rootUserId();
QJsonObject getDbSettings(bool defaults = false);
bool createUser(const QString &userName, const QString &email, const QString &dispName, const QString &password);
bool createTempPw(const QByteArray &uId, const QString &password);
bool updatePassword(const QByteArray &uId, const QString &password, const QString &table, bool requireNewPass = false);
bool auth(const QByteArray &uId, const QString &password, const QString &table);
bool testDbWritable();
void cleanupDbConnection();
void saveDbSettings(const QJsonObject &obj);
void moveCharLeft(int pos, QString &str);
@ -52,8 +198,7 @@ public:
DEL,
INNER_JOIN_PULL,
CREATE_TABLE,
ALTER_TABLE,
DEL_TABLE
ALTER_TABLE
};
enum Condition
@ -105,6 +250,7 @@ public:
QStringList tables();
QStringList columnsInTable(const QString &tbl);
QVariant getData(const QString &column, int row = 0);
QString errDetail();
QList<QList<QVariant> > &allData();
private:
@ -114,6 +260,7 @@ private:
bool queryOk;
int rowsAffected;
QString table;
QString lastErr;
QString limit;
QString qStr;
QString wStr;

View File

@ -16,15 +16,23 @@
// along with MRCI under the LICENSE.md file. If not, see
// <http://www.gnu.org/licenses/>.
bool setupDb()
bool setupDb(QString *errMsg)
{
auto ret = true;
errMsg->clear();
Query query(QThread::currentThread());
Query defaults(QThread::currentThread());
QString randPw;
if (query.inErrorstate())
{
ret = false;
errMsg->append("database open failure: \n");
errMsg->append(query.errDetail());
}
if (ret)
@ -69,6 +77,15 @@ bool setupDb()
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)
{
query.setType(Query::CREATE_TABLE, TABLE_USERS);
@ -90,6 +107,37 @@ bool setupDb()
query.addUnique(COLUMN_USER_ID);
ret = query.exec();
if (query.createExecuted())
{
auto uId = genUniqueHash();
query.setType(Query::PUSH, TABLE_USERS);
query.addColumn(COLUMN_USERNAME, DEFAULT_ROOT_USER);
query.addColumn(COLUMN_HOST_RANK, 1);
query.addColumn(COLUMN_NEED_PASS, true);
query.addColumn(COLUMN_NEED_NAME, true);
query.addColumn(COLUMN_LOCKED, false);
query.addColumn(COLUMN_EMAIL_VERIFIED, false);
query.addColumn(COLUMN_USER_ID, uId);
query.addRandBlob(COLUMN_SALT, 128);
ret = query.exec();
if (ret)
{
randPw = genPw();
ret = updatePassword(uId, randPw, TABLE_USERS, true);
}
}
else
{
query.setType(Query::UPDATE, TABLE_USERS);
query.addColumn(COLUMN_NEED_NAME, true);
query.addCondition(COLUMN_USERNAME, DEFAULT_ROOT_USER);
ret = query.exec();
}
}
if (ret)
@ -151,8 +199,138 @@ bool setupDb()
query.addColumn(COLUMN_SUB_CH_ID);
query.addColumn(COLUMN_ACCESS_LEVEL);
query.addForeign(COLUMN_CHANNEL_ID, TABLE_CHANNELS, COLUMN_CHANNEL_ID, Query::CASCADE, Query::CASCADE);
}
if (ret)
{
query.setType(Query::CREATE_TABLE, TABLE_SERV_SETTINGS);
query.addColumn(COLUMN_IPADDR);
query.addColumn(COLUMN_PORT);
query.addColumn(COLUMN_BAN_LIMIT);
query.addColumn(COLUMN_LOCK_LIMIT);
query.addColumn(COLUMN_MAXSESSIONS);
query.addColumn(COLUMN_PUB_USERS);
query.addColumn(COLUMN_ZIPBIN);
query.addColumn(COLUMN_ZIPCOMPRESS);
query.addColumn(COLUMN_ZIPEXTRACT);
query.addColumn(COLUMN_INITRANK);
query.addColumn(COLUMN_MAILERBIN);
query.addColumn(COLUMN_MAIL_SEND);
query.addColumn(COLUMN_CONFIRM_SUBJECT);
query.addColumn(COLUMN_TEMP_PW_SUBJECT);
query.addColumn(COLUMN_CONFIRM_MSG);
query.addColumn(COLUMN_TEMP_PW_MSG);
query.addColumn(COLUMN_ENABLE_CONFIRM);
query.addColumn(COLUMN_ENABLE_PW_RESET);
query.addColumn(COLUMN_ACTIVE_UPDATE);
query.addColumn(COLUMN_MAX_SUB_CH);
query.addColumn(COLUMN_DEFAULT_PASS);
query.addColumn(COLUMN_ROOT_USER);
ret = query.exec();
if (randPw.isEmpty())
{
randPw = genPw();
}
auto rootUId = rootUserId();
if (query.createExecuted())
{
query.setType(Query::PUSH, TABLE_SERV_SETTINGS);
query.addColumn(COLUMN_IPADDR, DEFAULT_LISTEN_ADDRESS);
query.addColumn(COLUMN_PORT, DEFAULT_LISTEN_PORT);
query.addColumn(COLUMN_BAN_LIMIT, DEFAULT_BAN_LIMIT);
query.addColumn(COLUMN_LOCK_LIMIT, DEFAULT_LOCK_LIMIT);
query.addColumn(COLUMN_MAXSESSIONS, DEFAULT_MAXSESSIONS);
query.addColumn(COLUMN_PUB_USERS, false);
query.addColumn(COLUMN_INITRANK, DEFAULT_INIT_RANK);
query.addColumn(COLUMN_MAILERBIN, DEFAULT_MAILBIN);
query.addColumn(COLUMN_MAIL_SEND, DEFAULT_MAIL_SEND);
query.addColumn(COLUMN_CONFIRM_SUBJECT, DEFAULT_CONFIRM_SUBJECT);
query.addColumn(COLUMN_TEMP_PW_SUBJECT, DEFAULT_TEMP_PW_SUBJECT);
query.addColumn(COLUMN_CONFIRM_MSG, TXT_ConfirmCodeTemplate);
query.addColumn(COLUMN_TEMP_PW_MSG, TXT_TempPwTemplate);
query.addColumn(COLUMN_ENABLE_CONFIRM, true);
query.addColumn(COLUMN_ENABLE_PW_RESET, true);
query.addColumn(COLUMN_ACTIVE_UPDATE, true);
query.addColumn(COLUMN_MAX_SUB_CH, DEFAULT_MAX_SUBS);
query.addColumn(COLUMN_DEFAULT_PASS, randPw);
query.addColumn(COLUMN_ROOT_USER, rootUId);
ret = query.exec();
}
else
{
query.setType(Query::PULL, TABLE_SERV_SETTINGS);
query.addColumn(COLUMN_IPADDR);
query.addColumn(COLUMN_PORT);
query.addColumn(COLUMN_BAN_LIMIT);
query.addColumn(COLUMN_LOCK_LIMIT);
query.addColumn(COLUMN_MAXSESSIONS);
query.addColumn(COLUMN_PUB_USERS);
query.addColumn(COLUMN_ZIPBIN);
query.addColumn(COLUMN_ZIPCOMPRESS);
query.addColumn(COLUMN_ZIPEXTRACT);
query.addColumn(COLUMN_INITRANK);
query.addColumn(COLUMN_MAILERBIN);
query.addColumn(COLUMN_MAIL_SEND);
query.addColumn(COLUMN_CONFIRM_SUBJECT);
query.addColumn(COLUMN_TEMP_PW_SUBJECT);
query.addColumn(COLUMN_CONFIRM_MSG);
query.addColumn(COLUMN_TEMP_PW_MSG);
query.addColumn(COLUMN_ENABLE_CONFIRM);
query.addColumn(COLUMN_ENABLE_PW_RESET);
query.addColumn(COLUMN_ACTIVE_UPDATE);
query.addColumn(COLUMN_MAX_SUB_CH);
query.addColumn(COLUMN_DEFAULT_PASS);
query.addColumn(COLUMN_ROOT_USER);
ret = query.exec();
if (ret)
{
defaults.setType(Query::UPDATE, TABLE_SERV_SETTINGS);
if (query.getData(COLUMN_IPADDR).isNull()) defaults.addColumn(COLUMN_IPADDR, DEFAULT_LISTEN_ADDRESS);
if (query.getData(COLUMN_PORT).isNull()) defaults.addColumn(COLUMN_PORT, DEFAULT_LISTEN_PORT);
if (query.getData(COLUMN_BAN_LIMIT).isNull()) defaults.addColumn(COLUMN_BAN_LIMIT, DEFAULT_BAN_LIMIT);
if (query.getData(COLUMN_LOCK_LIMIT).isNull()) defaults.addColumn(COLUMN_LOCK_LIMIT, DEFAULT_LOCK_LIMIT);
if (query.getData(COLUMN_MAXSESSIONS).isNull()) defaults.addColumn(COLUMN_MAXSESSIONS, DEFAULT_MAXSESSIONS);
if (query.getData(COLUMN_PUB_USERS).isNull()) defaults.addColumn(COLUMN_PUB_USERS, false);
if (query.getData(COLUMN_INITRANK).isNull()) defaults.addColumn(COLUMN_INITRANK, DEFAULT_INIT_RANK);
if (query.getData(COLUMN_MAILERBIN).isNull()) defaults.addColumn(COLUMN_MAILERBIN, DEFAULT_MAILBIN);
if (query.getData(COLUMN_MAIL_SEND).isNull()) defaults.addColumn(COLUMN_MAIL_SEND, DEFAULT_MAIL_SEND);
if (query.getData(COLUMN_CONFIRM_SUBJECT).isNull()) defaults.addColumn(COLUMN_CONFIRM_SUBJECT, DEFAULT_CONFIRM_SUBJECT);
if (query.getData(COLUMN_TEMP_PW_SUBJECT).isNull()) defaults.addColumn(COLUMN_TEMP_PW_SUBJECT, DEFAULT_TEMP_PW_SUBJECT);
if (query.getData(COLUMN_CONFIRM_MSG).isNull()) defaults.addColumn(COLUMN_CONFIRM_MSG, TXT_ConfirmCodeTemplate);
if (query.getData(COLUMN_TEMP_PW_MSG).isNull()) defaults.addColumn(COLUMN_TEMP_PW_MSG, TXT_TempPwTemplate);
if (query.getData(COLUMN_ENABLE_CONFIRM).isNull()) defaults.addColumn(COLUMN_ENABLE_CONFIRM, true);
if (query.getData(COLUMN_ENABLE_PW_RESET).isNull()) defaults.addColumn(COLUMN_ENABLE_PW_RESET, true);
if (query.getData(COLUMN_ACTIVE_UPDATE).isNull()) defaults.addColumn(COLUMN_ACTIVE_UPDATE, true);
if (query.getData(COLUMN_MAX_SUB_CH).isNull()) defaults.addColumn(COLUMN_MAX_SUB_CH, DEFAULT_MAX_SUBS);
if (query.getData(COLUMN_DEFAULT_PASS).isNull()) defaults.addColumn(COLUMN_DEFAULT_PASS, randPw);
if (query.getData(COLUMN_ROOT_USER).isNull()) defaults.addColumn(COLUMN_ROOT_USER, rootUId);
if (defaults.columns())
{
ret = defaults.exec();
}
}
}
}
if (query.inErrorstate())
{
errMsg->append("main setup: \n");
errMsg->append(query.errDetail());
}
if (defaults.inErrorstate())
{
errMsg->append("setup of default parameters: \n");
errMsg->append(defaults.errDetail());
}
return ret;

View File

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

View File

@ -37,80 +37,20 @@ extern "C"
// a "no OPENSSL_Applink" error.
#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
void msgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
Q_UNUSED(type)
Q_UNUSED(context)
if (!msg.contains("QSslSocket: cannot resolve"))
{
auto logMsg = msg.toUtf8();
Query db;
switch (type)
{
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;
}
}
db.setType(Query::PUSH, TABLE_DMESG);
db.addColumn(COLUMN_LOGENTRY, msg);
db.exec();
}
}
@ -124,37 +64,38 @@ void showHelp()
txtOut << " -help : display usage information about this application." << Qt::endl;
txtOut << " -stop : stop the current host instance if one is currently running." << Qt::endl;
txtOut << " -about : display versioning/warranty information about this application." << Qt::endl;
txtOut << " -addr : set the listening address and port for TCP clients." << Qt::endl;
txtOut << " -status : display status information about the host instance if it is currently running." << Qt::endl;
txtOut << " -reset_root : reset the root account password to the default password." << Qt::endl;
txtOut << " -host : start a new host instance. (this blocks)" << Qt::endl;
txtOut << " -host_trig : start a new host instance. (this does not block)" << Qt::endl;
txtOut << " -default_pw : show the default password." << Qt::endl;
txtOut << " -public_cmds : run the internal module to list it's public commands. for internal use only." << Qt::endl;
txtOut << " -exempt_cmds : run the internal module to list it's rank exempt commands. for internal use only." << Qt::endl;
txtOut << " -user_cmds : run the internal module to list it's user commands. for internal use only." << Qt::endl;
txtOut << " -run_cmd : run an internal module command. for internal use only." << 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 << " -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 << " -load_ssl : re-load the host SSL certificate without stopping the host instance." << 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 << " -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 << "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 << " 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 << " example: -add_admin somebody" << Qt::endl << Qt::endl;
txtOut << "elevate - this argument takes a single string representing a user name to an account to promote to rank 1." << Qt::endl;
txtOut << " example: -elevate somebody" << Qt::endl << Qt::endl;
txtOut << "addr - this argument takes a {ip_address:port} string. it will return an error if not formatted correctly" << Qt::endl;
txtOut << " examples: 10.102.9.2:35516 or 0.0.0.0:35516." << Qt::endl << Qt::endl;
txtOut << "run_cmd - this argument is used by the host itself along with the internal module arguments to run the" << Qt::endl;
txtOut << " internal command names passed by it. this is not ment to be run directly by human input. the" << 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)
{
auto ret = 0;
@ -180,12 +121,22 @@ int main(int argc, char *argv[])
serializeThread(app.thread());
auto workDir = expandEnvVariables(qEnvironmentVariable(ENV_WORK_DIR, DEFAULT_WORK_DIR));
auto args = QCoreApplication::arguments();
auto ret = 0;
QDir dir(workDir);
if (!dir.exists()) dir.mkpath(workDir);
QDir::setCurrent(workDir);
QCoreApplication::setApplicationName(APP_NAME);
QCoreApplication::setApplicationVersion(APP_VER);
QString err;
qInstallMessageHandler(msgHandler);
// args.append("-ls_sql_drvs"); // debug
if (args.contains("-run_cmd", Qt::CaseInsensitive) ||
@ -193,7 +144,14 @@ int main(int argc, char *argv[])
args.contains("-exempt_cmds", Qt::CaseInsensitive) ||
args.contains("-user_cmds", Qt::CaseInsensitive))
{
if (setupDb())
// security note: it is critical that the above internal arguments are checked
// first. external clients have the ability to pass additional
// args and those args come through here. it can be a security
// threat if an external arg is a powerful arg like "reset_root"
// and it ends up getting processed unintentionally by this
// function.
if (setupDb(&err))
{
auto *mod = new Module(&app);
@ -204,7 +162,7 @@ int main(int argc, char *argv[])
}
else
{
ret = 1;
soeDueToDbErr(&ret, &err);
}
}
else if (args.contains("-help", Qt::CaseInsensitive) || args.size() == 1)
@ -215,7 +173,7 @@ int main(int argc, char *argv[])
{
QTextStream(stdout) << "" << Qt::endl;
for (const auto &driver : QSqlDatabase::drivers())
for (auto driver : QSqlDatabase::drivers())
{
QTextStream(stdout) << driver << Qt::endl;
}
@ -233,15 +191,9 @@ int main(int argc, char *argv[])
args.contains("-status", Qt::CaseInsensitive) ||
args.contains("-load_ssl", Qt::CaseInsensitive))
{
qInstallMessageHandler(msgHandler);
ret = shellToHost(args, false, app);
}
else
{
qInstallMessageHandler(msgHandler);
if (setupDb())
else if (setupDb(&err))
{
if (args.contains("-host", Qt::CaseInsensitive))
{
@ -256,108 +208,72 @@ int main(int argc, char *argv[])
{
QProcess::startDetached(QCoreApplication::applicationFilePath(), QStringList() << "-host");
}
else if (args.contains("-elevate", Qt::CaseInsensitive))
else if (args.contains("-addr", Qt::CaseInsensitive))
{
ret = 1;
auto params = getParam("-addr", args);
auto addr = params.split(':');
QByteArray uId;
ret = 128;
if (args.size() <= 2)
if (addr.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;
QTextStream(stderr) << "" << Qt::endl << "err: Address string parsing error, number of params found: " << addr.size() << Qt::endl;
}
else
{
Query db;
bool pOk;
auto port = addr[1].toUShort(&pOk);
if (!pOk)
{
QTextStream(stderr) << "" << Qt::endl << "err: Invalid port." << Qt::endl;
}
else if (port == 0)
{
QTextStream(stderr) << "" << Qt::endl << "err: The port cannot be 0." << Qt::endl;
}
else if (QHostAddress(addr[0]).isNull())
{
QTextStream(stderr) << "" << Qt::endl << "err: Invalid ip address." << Qt::endl;
}
else
{
Query db(&app);
db.setType(Query::UPDATE, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_IPADDR, addr[0]);
db.addColumn(COLUMN_PORT, port);
db.exec();
ret = shellToHost(args, true, app);
}
}
}
else if (args.contains("-reset_root", Qt::CaseInsensitive))
{
auto uId = rootUserId();
Query db(&app);
db.setType(Query::UPDATE, TABLE_USERS);
db.addColumn(COLUMN_HOST_RANK, 1);
db.addColumn(COLUMN_LOCKED, false);
db.addCondition(COLUMN_USER_ID, uId);
db.exec();
if (db.exec())
{
ret = 0;
updatePassword(uId, defaultPw(), TABLE_USERS, true);
}
}
}
else if (args.contains("-add_admin", Qt::CaseInsensitive))
else if (args.contains("-default_pw", 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();
if (createUser(args[2], args[2] + "@change_me.null", "", randPw, 1, true))
{
QTextStream(stdout) << "password: " << randPw << Qt::endl;
ret = 0;
}
}
}
else if (args.contains("-res_pw", Qt::CaseInsensitive))
{
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;
}
QTextStream(stdout) << "" << Qt::endl << " Root User : " << getUserName(rootUserId()) << Qt::endl;
QTextStream(stdout) << " Default Password: " << defaultPw() << Qt::endl << Qt::endl;
}
}
else
{
showHelp();
}
}
else
{
ret = 1;
soeDueToDbErr(&ret, &err);
}
cleanupDbConnection();
}
return ret;
}

View File

@ -222,8 +222,8 @@ bool genDefaultSSLFiles(const QString &outsideAddr, QTextStream &msg)
auto ret = genRSAKey(cert, msg);
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);
if (ret) ret = writePrivateKey(DEFAULT_PRIV_KEY_NAME, cert, msg);
if (ret) ret = writeX509(DEFAULT_PUB_KEY_NAME, cert, msg);
cert->cleanup();
cert->deleteLater();

View File

@ -32,7 +32,7 @@
#include <QHostAddress>
#include <QSslKey>
#include "common.h"
#include "db.h"
class Cert : public QObject
{

View File

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

View File

@ -16,12 +16,36 @@
// along with MRCI under the LICENSE.md file. If not, see
// <http://www.gnu.org/licenses/>.
Module::Module(QObject *parent) : QObject(parent) {}
Module::Module(QObject *parent) : QObject(parent)
{
pubReg = false;
emailConfirmation = false;
passwrdResets = false;
loadSettings();
}
void Module::loadSettings()
{
Query db(this);
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_PUB_USERS);
db.addColumn(COLUMN_ENABLE_CONFIRM);
db.addColumn(COLUMN_ENABLE_PW_RESET);
db.exec();
pubReg = db.getData(COLUMN_PUB_USERS).toBool();
emailConfirmation = db.getData(COLUMN_ENABLE_CONFIRM).toBool();
passwrdResets = db.getData(COLUMN_ENABLE_PW_RESET).toBool();
}
QStringList Module::userCmdList()
{
QStringList ret;
ret << CloseHost::cmdName();
ret << RestartHost::cmdName();
ret << Cast::cmdName();
ret << OpenSubChannel::cmdName();
ret << CloseSubChannel::cmdName();
@ -39,12 +63,15 @@ QStringList Module::userCmdList()
ret << LsCmdRanks::cmdName();
ret << RemoveCmdRank::cmdName();
ret << AssignCmdRank::cmdName();
ret << ServSettings::cmdName();
ret << LockUser::cmdName();
ret << NameChangeRequest::cmdName();
ret << PasswordChangeRequest::cmdName();
ret << OverWriteEmail::cmdName();
ret << RemoveUser::cmdName();
ret << ChangeUserRank::cmdName();
ret << SetEmailTemplate::cmdName();
ret << PreviewEmail::cmdName();
ret << DownloadFile::cmdName();
ret << UploadFile::cmdName();
ret << Delete::cmdName();
@ -54,6 +81,7 @@ QStringList Module::userCmdList()
ret << FileInfo::cmdName();
ret << MakePath::cmdName();
ret << ChangeDir::cmdName();
ret << ListDBG::cmdName();
ret << ToPeer::cmdName();
ret << LsP2P::cmdName();
ret << P2POpen::cmdName();
@ -93,14 +121,12 @@ QStringList Module::pubCmdList()
ret << Auth::cmdName();
ret << MyInfo::cmdName();
auto confObj = confObject();
if (confObj[CONF_ENABLE_PUB_REG].toBool())
if (pubReg)
{
ret << CreateUser::cmdName();
}
if (confObj[CONF_ENABLE_PWRES].toBool())
if (passwrdResets)
{
ret << ResetPwRequest::cmdName();
ret << RecoverAcct::cmdName();
@ -121,9 +147,7 @@ QStringList Module::rankExemptList()
ret << ChangeEmail::cmdName();
ret << IsEmailVerified::cmdName();
auto confObj = confObject();
if (confObj[CONF_ENABLE_EVERIFY].toBool())
if (emailConfirmation)
{
ret << VerifyEmail::cmdName();
}
@ -133,11 +157,13 @@ QStringList Module::rankExemptList()
bool Module::runCmd(const QString &name)
{
auto ret = true;
bool ret = true;
if (userCmdList().contains(name, Qt::CaseInsensitive))
{
if (noCaseMatch(name, Auth::cmdName())) new Auth(this);
if (noCaseMatch(name, CloseHost::cmdName())) new CloseHost(this);
else if (noCaseMatch(name, RestartHost::cmdName())) new RestartHost(this);
else if (noCaseMatch(name, Auth::cmdName())) new Auth(this);
else if (noCaseMatch(name, Cast::cmdName())) new Cast(this);
else if (noCaseMatch(name, OpenSubChannel::cmdName())) new OpenSubChannel(this);
else if (noCaseMatch(name, CloseSubChannel::cmdName())) new CloseSubChannel(this);
@ -156,6 +182,7 @@ bool Module::runCmd(const QString &name)
else if (noCaseMatch(name, LsCmdRanks::cmdName())) new LsCmdRanks(this);
else if (noCaseMatch(name, RemoveCmdRank::cmdName())) new RemoveCmdRank(this);
else if (noCaseMatch(name, AssignCmdRank::cmdName())) new AssignCmdRank(this);
else if (noCaseMatch(name, ServSettings::cmdName())) new ServSettings(this);
else if (noCaseMatch(name, LockUser::cmdName())) new LockUser(this);
else if (noCaseMatch(name, NameChangeRequest::cmdName())) new NameChangeRequest(this);
else if (noCaseMatch(name, PasswordChangeRequest::cmdName())) new PasswordChangeRequest(this);
@ -167,6 +194,8 @@ bool Module::runCmd(const QString &name)
else if (noCaseMatch(name, RemoveUser::cmdName())) new RemoveUser(this);
else if (noCaseMatch(name, ChangeUserRank::cmdName())) new ChangeUserRank(this);
else if (noCaseMatch(name, IsEmailVerified::cmdName())) new IsEmailVerified(this);
else if (noCaseMatch(name, SetEmailTemplate::cmdName())) new SetEmailTemplate(this);
else if (noCaseMatch(name, PreviewEmail::cmdName())) new PreviewEmail(this);
else if (noCaseMatch(name, MyInfo::cmdName())) new MyInfo(this);
else if (noCaseMatch(name, DownloadFile::cmdName())) new DownloadFile(this);
else if (noCaseMatch(name, UploadFile::cmdName())) new UploadFile(this);
@ -177,6 +206,7 @@ bool Module::runCmd(const QString &name)
else if (noCaseMatch(name, FileInfo::cmdName())) new FileInfo(this);
else if (noCaseMatch(name, MakePath::cmdName())) new MakePath(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, LsP2P::cmdName())) new LsP2P(this);
else if (noCaseMatch(name, P2POpen::cmdName())) new P2POpen(this);
@ -207,14 +237,14 @@ bool Module::runCmd(const QString &name)
else if (noCaseMatch(name, Tree::cmdName())) new Tree(this);
else
{
qCritical() << "Internal module - the module claim command name '" << name << "' exists but no command object was actually matched/built.";
qDebug() << "Module err: the loader claims command name '" << name << "' exists but no command object was actually matched/built.";
ret = false;
}
}
else
{
qCritical() << "Internal module - command name '" << name << "' not found.";
qDebug() << "Module err: command name '" << name << "' not found.";
ret = false;
}
@ -229,7 +259,7 @@ void Module::listCmds(const QStringList &list)
bool Module::start(const QStringList &args)
{
auto ret = true;
bool ret = true;
if (args.contains("-run_cmd"))
{

View File

@ -19,6 +19,7 @@
#include "common.h"
#include "cmd_object.h"
#include "commands/admin.h"
#include "commands/cast.h"
#include "commands/info.h"
#include "commands/mods.h"
@ -36,6 +37,10 @@ class Module : public QObject
private:
bool pubReg;
bool emailConfirmation;
bool passwrdResets;
bool runCmd(const QString &name);
void listCmds(const QStringList &list);
void loadSettings();

View File

@ -83,7 +83,7 @@ void Session::connectToPeer(const QSharedPointer<SessionCarrier> &peer)
{
if (peer->sessionObj == nullptr)
{
qCritical() << "Session::connectToPeer() the peer session object is null.";
qDebug() << "Session::connectToPeer() the peer session object is null.";
}
else if ((peer->sessionObj != this) && (flags & SESSION_RDY))
{
@ -594,6 +594,18 @@ void Session::privAsyncDataIn(quint16 cmdId, const QByteArray &data)
{
logout("", true);
}
else if (cmdId == ASYNC_EXIT)
{
emit closeServer();
}
else if (cmdId == ASYNC_RESTART)
{
emit resServer();
}
else if (cmdId == ASYNC_MAXSES)
{
emit setMaxSessions(static_cast<quint32>(rdInt(data)));
}
else if (cmdId == ASYNC_PING_PEERS)
{
castPingForPeers();

View File

@ -120,7 +120,10 @@ signals:
void killCmd32(quint32 cmdId);
void asyncToPeers(quint16 cmdId, const QByteArray data);
void connectPeers(QSharedPointer<SessionCarrier> peer);
void setMaxSessions(quint32 value);
void ended();
void closeServer();
void resServer();
void killMods();
};

View File

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

View File

@ -95,10 +95,19 @@ bool TCPServer::start()
close();
cleanupDbConnection();
Query db(this);
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_PORT);
db.addColumn(COLUMN_IPADDR);
db.addColumn(COLUMN_MAXSESSIONS);
db.exec();
maxSessions = db.getData(COLUMN_MAXSESSIONS).toUInt();
auto ret = false;
auto conf = confObject();
auto addr = conf[CONF_LISTEN_ADDR].toString();
auto port = conf[CONF_LISTEN_PORT].toInt();
auto addr = db.getData(COLUMN_IPADDR).toString();
auto port = static_cast<quint16>(db.getData(COLUMN_PORT).toUInt());
if (!createPipe())
{
@ -140,6 +149,10 @@ void TCPServer::sessionEnded()
{
closeServer();
}
else if ((count == 0) && (flags & RES_ON_EMPTY))
{
resServer();
}
else if (!(flags & (CLOSE_ON_EMPTY | RES_ON_EMPTY)) && !servOverloaded())
{
resumeAccepting();
@ -168,12 +181,28 @@ void TCPServer::closeServer()
}
}
void TCPServer::resServer()
{
if (rd32BitFromBlock(hostLoad) == 0)
{
controlPipe->close();
start();
}
else
{
flags |= RES_ON_EMPTY;
flags &= ~CLOSE_ON_EMPTY;
emit endAllSessions();
}
}
bool TCPServer::servOverloaded()
{
hostSharedMem->lock();
auto confObj = confObject();
auto ret = rd32BitFromBlock(hostLoad) >= static_cast<quint32>(confObj[CONF_MAX_SESSIONS].toInt());
bool ret = rd32BitFromBlock(hostLoad) >= maxSessions;
hostSharedMem->unlock();
@ -219,16 +248,22 @@ void TCPServer::procPipeIn()
QString text;
QTextStream txtOut(&text);
Query db(this);
db.setType(Query::PULL, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_IPADDR);
db.addColumn(COLUMN_PORT);
db.exec();
hostSharedMem->lock();
auto confObj = confObject();
txtOut << "" << Qt::endl;
txtOut << "Host Load: " << rd32BitFromBlock(hostLoad) << "/" << confObj[CONF_MAX_SESSIONS].toInt() << Qt::endl;
txtOut << "Host Load: " << rd32BitFromBlock(hostLoad) << "/" << maxSessions << Qt::endl;
txtOut << "Address: " << serverAddress().toString() << Qt::endl;
txtOut << "Port: " << serverPort() << Qt::endl;
txtOut << "SSL Chain: " << confObj[CONF_CERT_CHAIN].toString() << Qt::endl;
txtOut << "SSL Private: " << confObj[CONF_PRIV_KEY].toString() << Qt::endl << Qt::endl;
txtOut << "Working Path: " << QDir::currentPath() << Qt::endl;
txtOut << "SSL Chain: " << sslCertChain() << Qt::endl;
txtOut << "SSL Private: " << sslPrivKey() << Qt::endl << Qt::endl;
printDatabaseInfo(txtOut);
@ -237,6 +272,17 @@ void TCPServer::procPipeIn()
}
}
void TCPServer::setMaxSessions(quint32 value)
{
Query db(this);
db.setType(Query::UPDATE, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_MAXSESSIONS, value);
db.exec();
maxSessions = value;
}
void TCPServer::incomingConnection(qintptr socketDescriptor)
{
auto *soc = new QSslSocket(nullptr);
@ -270,6 +316,9 @@ void TCPServer::incomingConnection(qintptr socketDescriptor)
connect(ses, &Session::ended, this, &TCPServer::sessionEnded);
connect(ses, &Session::ended, thr, &QThread::quit);
connect(ses, &Session::connectPeers, this, &TCPServer::connectPeers);
connect(ses, &Session::closeServer, this, &TCPServer::closeServer);
connect(ses, &Session::resServer, this, &TCPServer::resServer);
connect(ses, &Session::setMaxSessions, this, &TCPServer::setMaxSessions);
connect(this, &TCPServer::connectPeers, ses, &Session::connectToPeer);
connect(this, &TCPServer::endAllSessions, ses, &Session::endSession);
@ -358,14 +407,12 @@ QString TCPServer::loadSSLData(bool onReload)
QString txtMsg;
QTextStream stream(&txtMsg);
auto localObj = confObject();
auto privPath = localObj[CONF_PRIV_KEY].toString();
auto pubPath = localObj[CONF_CERT_CHAIN].toString();
auto chain = pubPath.split(":");
auto chain = sslCertChain().split(":");
auto priv = sslPrivKey();
auto allCertsExists = true;
auto privKeyExists = QFile::exists(privPath);
auto privKeyExists = QFile::exists(priv);
stream << "Private key: " << privPath << Qt::endl;
stream << "Private key: " << priv << Qt::endl;
if (!privKeyExists)
{
@ -386,19 +433,16 @@ QString TCPServer::loadSSLData(bool onReload)
if (chain.isEmpty())
{
stream << "No cert files are defined in the conf file." << Qt::endl;
stream << "No cert files are defined in the env." << Qt::endl;
allCertsExists = false;
}
stream << Qt::endl;
auto defaultPriv = getLocalFilePath(DEFAULT_PRIV_FILENAME);
auto defaultCert = getLocalFilePath(DEFAULT_CERT_FILENAME);
if (allCertsExists && privKeyExists)
{
if (onReload && (privPath == defaultPriv) && (pubPath == defaultCert))
if (onReload && (priv == DEFAULT_PRIV_KEY_NAME) && (sslCertChain() == DEFAULT_PUB_KEY_NAME))
{
stream << "Re-generating self-signed cert." << Qt::endl;
@ -408,18 +452,18 @@ QString TCPServer::loadSSLData(bool onReload)
}
}
applyPrivKey(privPath, stream);
applyPrivKey(priv, stream);
applyCerts(chain, stream);
}
else if ((privPath == defaultPriv) && (pubPath == defaultCert))
else if ((priv == DEFAULT_PRIV_KEY_NAME) && (sslCertChain() == DEFAULT_PUB_KEY_NAME))
{
stream << "Generating self-signed cert." << Qt::endl;
if (genDefaultSSLFiles(wanIP, stream))
{
stream << Qt::endl << "The default self-signed cert files were generated successfully." << Qt::endl << Qt::endl;
stream << Qt::endl << "The default self-signed cert files are generated successfully." << Qt::endl << Qt::endl;
applyPrivKey(privPath, stream);
applyPrivKey(priv, stream);
applyCerts(chain, stream);
}
}

View File

@ -39,6 +39,7 @@ private:
QSslKey sslKey;
QString hostKey;
QString wanIP;
quint32 maxSessions;
quint32 flags;
QString loadSSLData(bool onReload);
@ -54,10 +55,12 @@ private slots:
void newPipeConnection();
void closedPipeConnection();
void sessionEnded();
void setMaxSessions(quint32 value);
void replyFromIpify(QNetworkReply *reply);
public slots:
void resServer();
void closeServer();
public:

View File

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