commit beb59ec2ab4bba995a1a94ee5b8490f66328b1e7 Author: Maurice O'Neal Date: Fri Sep 6 23:43:07 2019 -0400 Initial commit for the MRCI project. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15361cf --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# C++ objects and libs +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.dll +*.dylib + +# Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc +Makefile* +*build-* + +# Qt unit tests +target_wrapper.* + +# QtCreator +*.autosave + +# QtCreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCreator CMake +CMakeLists.txt.user* + +# QtCreator 4.8< compilation database +compile_commands.json + +# QtCreator local machine specific files for imported projects +*creator.user* diff --git a/MRCI.pro b/MRCI.pro new file mode 100644 index 0000000..6c52ded --- /dev/null +++ b/MRCI.pro @@ -0,0 +1,101 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2017-03-19 +# +# 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 +# . +# +#------------------------------------------------- + +QT -= gui +QT += network +QT += sql + +CONFIG += console +CONFIG -= app_bundle + +TARGET = mrci + +win32 { + + LIBS += -llibeay32 -lssleay32 + +} else { + + LIBS += -lcrypto -lssl + +} + +SOURCES += src/main.cpp \ + src/commands/channels.cpp \ + src/commands/cmd_ranks.cpp \ + src/commands/p2p.cpp \ + src/session.cpp \ + src/db.cpp \ + src/make_cert.cpp \ + src/int_loader.cpp \ + src/tcp_server.cpp \ + src/unix_signal.cpp \ + src/cmd_executor.cpp \ + src/common.cpp \ + src/shell.cpp \ + src/db_setup.cpp \ + src/commands/users.cpp \ + src/commands/mods.cpp \ + src/commands/info.cpp \ + src/commands/groups.cpp \ + src/commands/cast.cpp \ + src/commands/bans.cpp \ + src/commands/admin.cpp \ + src/commands/auth.cpp \ + src/commands/command.cpp \ + src/commands/cmd_state.cpp \ + src/commands/acct_recovery.cpp \ + src/commands/table_viewer.cpp \ + src/commands/fs.cpp \ + src/commands/certs.cpp + +HEADERS += \ + src/commands/channels.h \ + src/commands/cmd_ranks.h \ + src/commands/p2p.h \ + src/session.h \ + src/db.h \ + src/make_cert.h \ + src/int_loader.h \ + src/tcp_server.h \ + src/unix_signal.h \ + src/cmd_executor.h \ + src/common.h \ + src/shell.h \ + src/db_setup.h \ + src/commands/users.h \ + src/commands/mods.h \ + src/commands/info.h \ + src/commands/groups.h \ + src/commands/cast.h \ + src/commands/bans.h \ + src/commands/admin.h \ + src/commands/auth.h \ + src/commands/command.h \ + src/commands/cmd_state.h \ + src/commands/acct_recovery.h \ + src/commands/table_viewer.h \ + src/commands/fs.h \ + src/commands/certs.h + +RESOURCES += \ + cmd_docs.qrc diff --git a/cmd_docs.qrc b/cmd_docs.qrc new file mode 100644 index 0000000..9225231 --- /dev/null +++ b/cmd_docs.qrc @@ -0,0 +1,93 @@ + + + docs/intern_commands/accept_ch.md + docs/intern_commands/add_acct.md + docs/intern_commands/add_ban.md + docs/intern_commands/add_cert.md + docs/intern_commands/add_ch.md + docs/intern_commands/add_group.md + docs/intern_commands/add_mod.md + docs/intern_commands/add_ranked_cmd.md + docs/intern_commands/add_rdonly_flag.md + docs/intern_commands/add_sub_ch.md + docs/intern_commands/auth.md + docs/intern_commands/cast.md + docs/intern_commands/cert_info.md + docs/intern_commands/close_host.md + docs/intern_commands/close_sub_ch.md + docs/intern_commands/cmd_info.md + docs/intern_commands/decline_ch.md + docs/intern_commands/find_ch.md + docs/intern_commands/force_set_email.md + docs/intern_commands/fs_cd.md + docs/intern_commands/fs_copy.md + docs/intern_commands/fs_delete.md + docs/intern_commands/fs_download.md + docs/intern_commands/fs_info.md + docs/intern_commands/fs_list.md + docs/intern_commands/fs_mkpath.md + docs/intern_commands/fs_move.md + docs/intern_commands/fs_upload.md + docs/intern_commands/host_config.md + docs/intern_commands/host_info.md + docs/intern_commands/invite_to_ch.md + docs/intern_commands/is_email_verified.md + docs/intern_commands/lock_acct.md + docs/intern_commands/ls_act_log.md + docs/intern_commands/ls_auth_log.md + docs/intern_commands/ls_bans.md + docs/intern_commands/ls_certs.md + docs/intern_commands/ls_ch_members.md + docs/intern_commands/ls_chs.md + docs/intern_commands/ls_cmds.md + docs/intern_commands/ls_dbg.md + docs/intern_commands/ls_groups.md + docs/intern_commands/ls_mods.md + docs/intern_commands/ls_open_chs.md + docs/intern_commands/ls_p2p.md + docs/intern_commands/ls_ranked_cmds.md + docs/intern_commands/ls_rdonly_flags.md + docs/intern_commands/ls_sub_chs.md + docs/intern_commands/ls_users.md + docs/intern_commands/my_info.md + docs/intern_commands/open_sub_ch.md + docs/intern_commands/p2p_close.md + docs/intern_commands/p2p_open.md + docs/intern_commands/p2p_request.md + docs/intern_commands/pause.md + docs/intern_commands/ping_peers.md + docs/intern_commands/preview_email.md + docs/intern_commands/recover_acct.md + docs/intern_commands/remove_ch_member.md + docs/intern_commands/rename_ch.md + docs/intern_commands/rename_sub_ch.md + docs/intern_commands/request_new_pw.md + docs/intern_commands/request_new_user_name.md + docs/intern_commands/request_pw_reset.md + docs/intern_commands/restart_host.md + docs/intern_commands/resume.md + docs/intern_commands/rm_acct.md + docs/intern_commands/rm_ban.md + docs/intern_commands/rm_cert.md + docs/intern_commands/rm_ch.md + docs/intern_commands/rm_mod.md + docs/intern_commands/rm_ranked_cmd.md + docs/intern_commands/rm_rdonly_flag.md + docs/intern_commands/rm_sub_ch.md + docs/intern_commands/set_active_flag.md + docs/intern_commands/set_disp_name.md + docs/intern_commands/set_email_template.md + docs/intern_commands/set_email.md + docs/intern_commands/set_group_rank.md + docs/intern_commands/set_group.md + docs/intern_commands/set_member_level.md + docs/intern_commands/set_pw.md + docs/intern_commands/set_sub_ch_level.md + docs/intern_commands/set_user_name.md + docs/intern_commands/term.md + docs/intern_commands/to_peer.md + docs/intern_commands/trans_group.md + docs/intern_commands/verify_email.md + docs/intern_commands/ch_owner_override.md + + diff --git a/docs/Async.md b/docs/Async.md new file mode 100644 index 0000000..03f6dce --- /dev/null +++ b/docs/Async.md @@ -0,0 +1,184 @@ +### 6.1 Async Commands ### + +An async command is a virtual command that the host can use to send data to the client at any time while connected to the host. As the name implies, the occurance of a client receiving data from an async command is not always the result of running a regular command in the current session. This can occur for example when information in your account is changed by another client connected to the host; your client would not know about this change until an async command is sent notify it of the change. These commands cannot be called directly by a client or even a module command object. + +Async commands are not only used send data to the client but also used internally within the host to help session objects operating in different processes to communicate with each other. Some async commands in fact are considered internal only because the client should never see any data come from them at anytime. + +These are considered "virtual" commands because there is no defined command objects attached to them. Instead, async commands are best identified by command id values 1-255. Here is a list of currently "defined" async commands: + +``` +#define ASYNC_RDY 1 +#define ASYNC_SYS_MSG 2 +#define ASYNC_EXE_CRASH 3 +#define ASYNC_EXIT 4 // internal only +#define ASYNC_CAST 5 // internal only +#define ASYNC_MAXSES 6 // internal only +#define ASYNC_LOGOUT 7 // internal only +#define ASYNC_USER_DELETED 8 // internal only +#define ASYNC_GROUP_RENAMED 9 // internal only +#define ASYNC_DISP_RENAMED 10 // internal only +#define ASYNC_GRP_TRANS 11 // internal only +#define ASYNC_USER_GROUP_CHANGED 12 // internal only +#define ASYNC_CMD_RANKS_CHANGED 13 // internal only +#define ASYNC_RESTART 14 // internal only +#define ASYNC_ENABLE_MOD 15 // internal only +#define ASYNC_DISABLE_MOD 16 // internal only +#define ASYNC_GROUP_UPDATED 17 // internal only +#define ASYNC_END_SESSION 18 // internal only +#define ASYNC_USER_LOGIN 19 // internal only +#define ASYNC_RESTORE_AUTH 20 // internal only +#define ASYNC_TO_PEER 21 +#define ASYNC_LIMITED_CAST 22 +#define ASYNC_RW_MY_INFO 23 // internal only +#define ASYNC_P2P 24 +#define ASYNC_CLOSE_P2P 25 // internal only +#define ASYNC_NEW_CH_MEMBER 26 +#define ASYNC_DEL_CH 27 +#define ASYNC_RENAME_CH 28 +#define ASYNC_CH_ACT_FLAG 29 +#define ASYNC_NEW_SUB_CH 30 +#define ASYNC_RM_SUB_CH 31 +#define ASYNC_RENAME_SUB_CH 32 +#define ASYNC_INVITED_TO_CH 33 +#define ASYNC_RM_CH_MEMBER 34 +#define ASYNC_INVITE_ACCEPTED 35 +#define ASYNC_MEM_LEVEL_CHANGED 36 +#define ASYNC_SUB_CH_LEVEL_CHG 37 +#define ASYNC_ADD_RDONLY 38 +#define ASYNC_RM_RDONLY 39 +#define ASYNC_ADD_CMD 40 +#define ASYNC_RM_CMD 41 +#define ASYNC_USER_RENAMED 42 +#define ASYNC_PUBLIC_AUTH 43 // internal only +``` + +### 6.2 Async Data ### + +```ASYNC_RDY (1)``` +This command signals to the client that your current session is now ready to start running commands. This is usually sent after successfully setting up the tcp connection ([protocol](Protocol.md)) or after successfully recovering from a session crash. It can carry ```TEXT``` data that can be displayed directly to the user if needed. + +```ASYNC_SYS_MSG (2)``` +This command carry ```TEXT``` or ```ERR``` data that are system messages that can be directly displayed to the user of needed. It is also used to carry ```HOST_CERT``` data during the tcp connection setup and ```MY_INFO``` when local user account information has changed. + +```ASYNC_EXE_CRASH (3)``` +This is used to send ```ERR``` messages to the client if your session crashes or fails to setup an IPC connection for any reason. + +```ASYNC_EXIT (4)``` +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 (5)``` +This is an internal only command that carries a 54byte open sub-channels list ```wrAbleChIds``` described in section [3.4](Command_Objects.md) and an embedded mrci 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 of any data type. + +```ASYNC_MAXSES (6)``` +Internal only async command that is used by internal commands to send a ```BYTES``` frame to the main process to update the maximum amount the concurrent sessions for the ```TCPServer``` object. The data itself is actually a 32bit unsigned int. + +```ASYNC_LOGOUT (7)``` +This internal only async command doesn't carry any data. This is just used to notify the main process ```Session``` object that the user has logged out and should not attempt to restore authentication in case of a session crash. This doesn't actually do the logout. + +```ASYNC_USER_DELETED (8)``` +This internal only async command carry ```TEXT``` data that is the user name of the user account that was deleted from the host database. All ```Session``` objects that get this command must read and match this to the user name that is currently logged in for that object. If the user name matches, the ```Session``` object must logout since the user account no longer exists. + +```ASYNC_GROUP_RENAMED (9)``` +Internal only async command that carry ```TEXT``` command line arguments to notify all ```Session``` objects that the a host group name has changed. Example: ```-src "old_group_name" -dst "new_group_name"```. All ```Session``` objects that have matching current group names to ```old_group_name``` must update that group name to ```new_group_name``` and also send a ```ASYNC_SYS_MSG``` containing an updated ```MY_INFO```. + +```ASYNC_DISP_RENAMED (10)``` +Internal only async command that carry ```TEXT``` command line arguments to notify all ```Session``` objects that a user has changed the display name. Example: ```-name "new_display_name" -user "user_name"```. All ```Session``` objects that have matching current user names to ```user_name``` must update the display name to ```new_display_name``` and also send a ```ASYNC_SYS_MSG``` containing an updated ```MY_INFO```. + +```ASYNC_GRP_TRANS (11)``` +Internal only async command that carry the same ```TEXT``` command line arguments as ```ASYNC_GROUP_RENAMED``` to notify all ```Session``` objects that the all users currently in the group given in ```-src``` must be updated to the group given in ```-dst```. This triggers a ```ASYNC_SYS_MSG``` to send an updated ```MY_INFO``` and the ```Session``` object will load all the commands that the user now have access to and remove any commands that lost access due to the group change. + +```ASYNC_USER_GROUP_CHANGED (12)``` +This is an internal only command carry ```TEXT``` command line arguments to notify all ```Session``` objects that a user's group was changed to another group. example: ```-user "user_name" -group "new_group"```. All ```Session``` objects the have the matching user name need to update it's group to ```new_group```, send a ```ASYNC_SYS_MSG``` with an updated ```MY_INFO``` and load all the commands that the user now have access to and remove any commands that lost access due to the group change. + +```ASYNC_CMD_RANKS_CHANGED (13)``` +This internal only async commmand doesn't carry any data. Instead, it notifies all ```Session``` objects that assigned command ranks have changed. This will cause all of the ```Session``` objects to re-check the currently loaded commands and determine if it needs to remove any commands that the user no longer have access to and add any commands that the user may have gained access to. + +```ASYNC_RESTART (14)``` +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 (15)``` +This internal only async commmand carry ```TEXT``` path to the main library file of a module. All ```Session``` objects will then load the module. + +```ASYNC_DISABLE_MOD (16)``` +This internal only async commmand carry ```TEXT``` path to the main library file of a module. All ```Session``` objects will then delete all commands associated with the with this module and then unload it. + +```ASYNC_GROUP_UPDATED (17)``` +This is an internal only command that carry ```TEXT``` command line arguments to notify all ```Session``` objects that a group's host rank has changed. Example: ```-name "group_name" -rank 2```. All ```Session``` object that have matching group names to ```group_name``` will need to update the host rank to ```2```. When the session's host rank to adjusted this way, the session will need to re-check the currently loaded commands and determine if it needs to remove any commands that the user no longer have access to and add any commands that the user may have gained access to. + +```ASYNC_END_SESSION (18)``` +This internal only async commmand doesn't carry any data. It is used to notify the main process that the ```CmdExecutor``` object is requesting to close the session. The main process ```Session``` object will then signal the slave process to close and close the tcp session. + +```ASYNC_USER_LOGIN (19)``` +This is an internal only command that carry ```PRIV_IPC``` data containing the 256bit Keccak hash user id of the user that has successfully logged in. This is used by the ```CmdExecutor``` object to notify the main process ```Session``` object of the current user associated with the current session. The main process ```Session``` object will use this information to restore the user authorization in case of a session crash. + +```ASYNC_RESTORE_AUTH (20)``` +This is an internal only command that carry ```PRIV_IPC``` data containing the 256bit Keccak hash user id of a user that has successfully logged in with the current session. It is used by the ```Session``` object in the main process to tell the ```CmdExecutor``` object in the slave process to authorize the user without a password. This is only used when attempting to restore the session from a crash and a user was logged in at the time. + +```ASYNC_TO_PEER (21)``` +This is an async command that carry an embedded mrci frame directly to/from peer sessions without any restrictions. It is prepended with the 224bit sha3 hash of the target session id; however, it drops that session id before arriving at the client so it will apppear as a regular mrci frame of any data type. Users should not be given direct access to this for security reasons. + +```ASYNC_LIMITED_CAST (22)``` +This operate exactly like ```ASYNC_CAST``` except only sessions with active update sub-channels will respond to it. + +```ASYNC_RW_MY_INFO (23)``` +This internal only async command carry ```TEXT``` data of the user name to tell all ```Session``` objects that have the matching user name to send an update ```MY_INFO``` to the client. This is useful for when a host admin force updates user information of other lesser privileged users to make their clients aware of the changes. + +```ASYNC_P2P (24)``` +This async command carry an embedded mrci frame directly to/from peer sessions following the p2p negotiation process as described in [Type_IDs](Type_IDs.md), section 4.2 at the P2P specific data types. It prepends the 224bit sha3 hash of the destination session id and source session id; however, it drops the destination id and source id for just the P2P specific data types before arriving at the client. For all other data types (if a p2p connection is estabished), the source id is prepend-moved to the payload of the mrci frame before arriving at the client. + +```ASYNC_CLOSE_P2P (25)``` +This internal only async command carry a 224bit sha3 hash session id of a session that is about to close. This notifies all ```CmdExecutor``` objects that have matching hashes in ```p2pAccepted``` and ```p2pPending``` (section [3.4](Command_Objects.md)) to remove them now. It also triggers a ```ASYNC_P2P``` to send a ```P2P_CLOSE``` for the session id in question so the clients can also be made aware of this. + +```ASYNC_NEW_CH_MEMBER (26)``` +```TEXT``` command line arguments when a new channel is created and the user that created it is added as the channel owner. Example: ```-user "user_name" -ch_name "new_channel" -ch_id 334 -level 1```. + +```ASYNC_DEL_CH (27)``` +```TEXT``` command line arguments when a channel is deleted. Example: ```-ch_name "channel_name" -ch_id 426```. The host will automatically close all sub-channels related to this channel for all sessions that have them open. + +```ASYNC_RENAME_CH (28)``` +```TEXT``` command line arguments when a channel is renamed. Example: ```-ch_name "old_name" -new_name "new_name"```. + +```ASYNC_CH_ACT_FLAG (29)``` +```TEXT``` command line arguments when sub-channel's active update flag has been updated. Example: ```-ch_name "channel_name" -sub_name "sub_channel" -state 1```. (```-state``` 1 is true or ```-state``` 0 is false). + +```ASYNC_NEW_SUB_CH (30)``` +```TEXT``` command line arguments when a new sub-channel is created. Example: ```-ch_name "channel_name" -sub_name "new_sub_channel" -ch_id 987 -sub_id 5 -level 2```. + +```ASYNC_RM_SUB_CH (31)``` +```TEXT``` command line arguments when a sub-channel is deleted. Example: ```-ch_name "channel_name" -sub_name "sub_channel" -ch_id 987 -sub_id 5```. The host will automatically close this sub-channel for sessions that currently have it open. + +```ASYNC_RENAME_SUB_CH (32)``` +```TEXT``` command line arguments when a sub-channel is renamed. Example: ```-ch_name "channel_name" -sub_name "sub_channel" -new_name "new_sub_name"```. + +```ASYNC_INVITED_TO_CH (33)``` +```TEXT``` command line arguments when a new user is invited to join a channel. Example: ```-ch_name "channel_name" -user "user_name"```. + +```ASYNC_RM_CH_MEMBER (34)``` +```TEXT``` command line arguments when a user is kicked from a channel, uninvited or has left the channel. Example: ```-ch_name "channel_name" -user "user_name" -ch_id 746```. + +```ASYNC_INVITE_ACCEPTED (35)``` +```TEXT``` command line arguments when a user that was previously invite to join the channel has accepted the invite and should now be considered full member of the channel starting off at level ```REGULAR```. Example: ```-ch_name "channel_name" -user "user_name"```. + +```ASYNC_MEM_LEVEL_CHANGED (36)``` +```TEXT``` command line arguments when a channel member's privilege level is changed. Example: ```-ch_name "channel_name" -user "user_name" -ch_id 774 -level 2```. The host automatically closes all sub-channels related to the channel for the affected user. It will be will up to the client to re-open the sub-channel(s) if the user still have access to it/them. + +```ASYNC_SUB_CH_LEVEL_CHG (37)``` +```TEXT``` command line arguments when a sub-channel's lowest level of access is changed. Example: ```-ch_name "channel_name" -sub_name "sub_channel" -level 3 -ch_id 645 -sub_id 5```. The host will automatically close this sub-channel for sessions that currently have it open. It will be up to the client to reopen it if the current user still have access to it. + +```ASYNC_ADD_RDONLY (38)``` +```TEXT``` command line arguments when a read only flag is added to a sub-channel's access level. Example: ```-ch_name "channel_name" -sub_id 5 -level 4```. The host will automatically close this sub-channel for sessions that currently have it open. It will be up to the client to reopen it if the current user still have access to it. + +```ASYNC_RM_RDONLY (39)``` +```TEXT``` command line arguments when a read only flag is removed from a sub-channel's access level. Example: ```-ch_name "channel_name" -sub_id 5 -level 4```. The host will automatically close this sub-channel for sessions that currently have it open. It will be up to the client to reopen it if the current user still have access to it. + +```ASYNC_ADD_CMD (40)``` +This async command carry ```NEW_CMD``` when the session's ```CmdExecutor``` loads a new command object and wants to notify the client of it. + +```ASYNC_RM_CMD (41)``` +This async command carry ```CMD_ID``` when the session's ```CmdExecutor``` deletes a command object and wants to notify the client of it. + +```ASYNC_USER_RENAMED (42)``` +```TEXT``` command line arguments when a user changes it's user name. Example: ```-old "old_user_name" -new_name "new_user_name"```. + +```ASYNC_PUBLIC_AUTH (43)``` +This internal only async commmand doesn't carry any data. It just tells the ```CmdExecutor``` to load or reload commands without an authorized user so only public commands will be available until the client authorizes into a user account. This is used by the host when starting a session for the first time or if restoring the session after a crash and no user was logged in at the time. \ No newline at end of file diff --git a/docs/Command_Loaders.md b/docs/Command_Loaders.md new file mode 100644 index 0000000..50413cf --- /dev/null +++ b/docs/Command_Loaders.md @@ -0,0 +1,57 @@ +### 2.1 Command Loaders ### + +Every command object ([Command_Objects](Command_Objects.md)) defined in the host must internally define a unique command name. Command loaders have the very simple job of creating these objects via the requested command name. The host use command names to determine if the current session is allowed to load them or not and command loaders are used to relate those command names with the command objects themselves. + +When the host ```CmdExecutor``` determines that it can indeed load the commands based on the command names, it will then assign command ids to all command objects that were successfully built from the command loader. Each command loader can have a total of 256 command objects and internally defined commands start at command id: 256 - 512 and module defined commands start at command id: 513 and up. + +### 2.2 CommandLoader Class ### + +The ```CommandLoader``` class itself defines a few virtual functions that make the command name to command object relationship possible: + +```ExternCommand *cmdObj(QString cmdName)``` +This function is used to create the ```ExternCommand``` object associated with the command name passed by the ```QString``` parameter and return a pointer to it. If the command name is invalid, doesn't exists or fails to contruct the object for some reason, return ```nullptr```. The command object can have a parent or orphaned, just make sure it never gets deleted at any time; the host will handle that externally. It is safe to use the command loader itself as the parent. + +```QStringList cmdList()``` +This function needs to return a ```QStringList``` of all of command names that this loader can actually load when the ```cmdObj()``` function is called. the host uses this list to enforce user access filtering of the command objects using the built in permission id system. + +```QStringList pubCmdList()``` +This funtion needs to work the same way as ```cmdList()``` except the loader will use this function to name any commands that can be accessed by un-logged in clients. aka, public commands. Note: the commands listed here must also be listed in ```cmdList()``` or else the commands will not get loaded at all. + +```QStringList rankExemptList()``` +The loader can use this function to return a ```QStringList``` of all of command names that need to be exempt from host ranking system (section [5.2](Host_Features.md)). Commands listed here will be allowed to load/run regardless of what host rank the current user is. + +Here's a few notes to consider when using this class: + +* Never self delete or explicitly delete the command loader or any of the command objects it sucessfully creates at any time. The host ```CmdExecutor``` object will handle the life cycle of these objects externally. +* Command ids are assigned in alphabetical order of the command names returned by ```cmdList()```. The command ids will remain constant as long as the command names also remain constant. All clients are recommended to rely on the ASYNC_ADD_CMD and ASYNC_RM_CMD async commands to keep track of the command ids and names (section [6.2](Async.md)). + +### 2.3 Modules ### + +External commands are added to the host through modules based on low level [QT plugins](https://doc.qt.io/qt-5/plugins-howto.html). Each module must define a ```ModCommandLoader``` class in it's main library file and the file itself must be named 'main' with a library file extension that the host platform supports (main.so, main.dll, etc..). Modules are installed using the *add_mod* internal command that supports extracting the module's library files from an archive file (.zip, .tar, etc...) or just a single library file (.so, .dll, .a, etc...). + +In the case of an archive file, it extracts all of the files from the the archive file while preserving the directory tree so you can bundle additional files that your module depends on but as mentioned before, a library file named 'main' must be present on the base directory. + +A template and an example of a module can be found in the 'modules/Tester' directory of the source code of this project. It provides the command.cpp and command.h files that contain the ```ModCommandLoader```, ```CommandLoader``` and ```ExternCommand``` classes that are needed to create a module. Also feel free to copy the command.cpp and command.h files from 'src/commands' if you prefer. + +### 2.4 The Import Rev ### + +The import rev is a single digit versioning system for external modules that help the host determine if it is compatible with the module it is attempting to load or not. Bumps to this rev is usually triggered by significant changes to the ```CommandLoader``` or ```ModCommandLoader``` classes. Compatibility negotiation is a two way communication between the host ```CmdExecutor``` and the module itself using the virtual functions described in section 2.5. + +### 2.5 ModCommandLoader Class ### + +This class uses ```CommandLoader``` as a base and adds more virtual functions more suitable for loading command objects from an external library file: + +```bool hostRevOk(quint64 rev)``` +When the host calls this function, it will pass the import rev that it supports in the ```quint64``` parameter. Use this function to return if this rev is acceptable or not. The host will give up loading the module if the rev is not acceptable. + +```quint64 rev()``` +Use this function to return the import rev that this module supports. The host will decide if it is acceptable or not. + +```QString lastError()``` +The host will call this function if a command object fails to load or if ```hostRevOk()``` returns false so it can log the error message returned by it to the host database. + +```void modPath(QString path)``` +The host will call this function after successfully negotiating the import rev. The ```QString``` parameter passed into this will have the absolute path to the module's install directory. You can use this path to load additional files that came bundled in the archive file. + +```void aboutToDelete()``` +The host will call this function before calling ```deleteLater()```. All command objects at this point should already be deleted, use this opportunity to free any resources related to the loader itself. Unload any additional lib files that the loader may have used. \ No newline at end of file diff --git a/docs/Command_Objects.md b/docs/Command_Objects.md new file mode 100644 index 0000000..b33b3e2 --- /dev/null +++ b/docs/Command_Objects.md @@ -0,0 +1,193 @@ +### 3.1 Command Objects ### + +```ExternCommand``` objects are QT/C++ classes used by the host to execute all logic related to the specific commands called by the client. It basically defines several QT signals and virtual functions that the host's internal ```CmdExecutor``` object uses to process input data from the client and return data to the client or to other peers connected to the host. Each command callable by the client defines a seperate ```ExternCommand``` object and it's life cycle is managed externally by the host's ```CmdExecutor``` object. + +### 3.2 Defined Variables in ExternCommand ### + +Aside from the virtual functions and QT signals, the ```ExternCommand``` object also defines a few public variables that can be of interest: + +```cmdId``` +This is a ```quint16``` command id that the command object was assigned to when it was built. Changing it at any time does nothing upstream but it is reset to it's proper value every time the ```procBin()``` function is called. the MRCI protocol use assigned command ids to call certain command objects currently loaded into the ```CmdExecutor``` object. It is unique to each command object and makes it possible for the client to call a command with only 2 bytes of data instead of a variable number of bytes for a command name. This variable exist to make it possible for the internal logic of the command object to be made aware of it's assigned command id. + +```internCommands``` +This is a ```QHash``` containing a list of pointers to internal command objects built to be used from within this command object. It is filled by the ```internRequest()``` function (section 3.3). Internal commands differ from external commands like this by having direct access to the host database and less restrictive access to various host functions. This variable is useful if you want access to such functions without the chance of causing a session crash or database corruption. + +```errSent``` +This is a ```bool``` that simply indicates if the command has sent error message to the client using the ```errTxt()``` non-virtual function. This is useful if you want to determine of the last run of the command was successful or not but only if the internal logic used the aforementioned function to send the error. The ```CmdExecutor``` does reset it to false before calling the ```procBin()``` function. + +```inLoopMode``` +This is a ```bool``` that indicates if the command object is currently in loop mode. This basically retains what was passed into the ```enableLoop()``` signal when it is called. more info on this can be found in section 3.5. + +```inMoreInputMode``` +This is a ```bool``` that indicates if the command object is currently in the more input state. It retains what was passed into the ```enableMoreInput()``` signal when it is called. see section 3.5 for more info. + +```QString QObject::objectName()``` +This is not a variable but it is a function available to the ```ExternCommand``` object from the QT API. The ```CmdExecutor``` assigns the unique command name to this property that was assigned to this command when it was built. Just like ```cmdId``` this makes the internal logic aware of it's assigned command name. Changing it using ```QObject::setObjectName()``` does nothing upstream but also unlike ```cmdId``` it is not reset upon calling ```procBin()```. + +### 3.3 ExternCommand Virtual Functions ### + +```void procBin(SharedObjs,QByteArray,uchar)``` +This function is called by the host to process input data from the client passed in the form of a ```QByteArray```. Look at this as the main execution function of the command that can be called more than once if the loop state is enabled. The ```SharedObjs``` parameter is passed by the ```CmdExecutor``` to share information about the session with the command object as described in section 3.4 of this document. It is passed as a const because changing any of the objects within ```SharedObjs``` in an unexpected way may cause undesired behaviour. The third ```uchar``` parameter indicate what type of data is in the ```QByteArray```. Details on type ids can be found in the [Type_IDs.md](Type_IDs.md) document. + +```bool handlesGenfile()``` +Command objects should return true on this function if your command object handles the ```GEN_FILE``` data type in anyway. The host will use this to tell the client which commands support this data type which ones don't. The reason for this is because the ```GEN_FILE``` data type require structured 2 way communication in a way between the host and client (see [Type_IDs.md](Type_IDs.md)). This function is called only once shortly after the command is contructed. + +```QString shortText()``` +Command objects must return a short summary on what this command actually does. The host will use this to send help text to the client. How this text is displayed to the user depends entirely on the client. + +```QString ioText()``` +Just like shortText(), this is a help text function that describes what input/output data to expected to/from the command object. It follows a specific text format: + +``` + [] Brackets are used to indicate a block of data (text,binary,etc..). + Example: [text] + + / Seperates input and output data blocks to/from the command. + Example: [input_text]/[output_text] + + - Indicates a argument. + Example: [-arg]/[text] + + () Brackets indicate argument parameters/values. + Example: [-arg (text)]/[text] + + {} Brackets indicate optional arguments or parameters. + Example: [-arg {(text)} {-optional}]/[text] + + 'or' Indicates optional input data blocks and/or possible output blocks. + Example: [-arg1 (text)] or [-arg2 (text)]/[output_text1] or [output_text2] +``` + +```QString longText()``` +This is a help text function that is used by the host to send full detailed information about the command and it's usage. It's recommended to be as thorough as possible to help users understand proper usage of your command. The host will send it as ```BIG_TEXT``` so it will be up to the client if it should word-wrap the text when displaying to the user. + +```QString libText()``` +Command objects can use this function to return the library/module name and version that the command object belongs to. This can also contain extra information that clients can use to further identify the command object; however, the text is limited to 64 chars or less. Any text returned by this function that is larger than 64 chars will be truncated. + +```QStringList internRequest()``` +After the command is successfully built, this function is called by the ```CmdExecutor``` to fulfil any request to build internal command objects that can be used from within the object that was built. the ```QStringList``` returned by this function just need to list any of the known internal command names and the ```CmdExecutor``` will build them and add them to the ```internCommands``` ```QHash``` defined inside the ```ExternCommand``` object (section 3.2). Here's a few notes to consider if you want to build internal commands within your own command. + +* Your command object is assigned the parent of the internal commands. +* The internal commands built into internCommands have full access to the internal host batabase and functions that normal ExternCommands would otherwise not have access to. +* The output from the internal commands go directly to the client, peers or other objects. Nothing is passed through the parent object by default. +* Clients do not have direct access to these specific internal commands. Instead, they can only be called by running the ```procBin()``` function directly from within the parent object. +* The internal commands inherit the parent command object's command id/name so output from these internal commands will appear as if it came from the parent object. This also includes the ```enableLoop()``` and ```enableMoreInput()``` signals (section 3.5). +* It will be up to the parent object to detect and execute the loop mode for these internal commands since ```CmdExecutor::exeCmd()``` will not call these objects directly. +* Terminating the parent object also terminates all of the internal commands within it. +* It is safe to delete the internals commands but be sure to remove the deleted commands from the ```internCommands``` ```QHash``` or it will must likely cause the session to crash. You normally should never have to do this anyway, the ```CmdExecutor``` will safely delete these internal commands when the parent is about to get deleted. + +```void term()``` +This function is called by the host when the command needs to terminate what it is currently doing. Use this opportunity to reset and clean up any reasources so the command object can be ready to be called again in a reset state. + +```void aboutToDelete()``` +The host will call this function before calling the command object's ```deleteLater()``` function. Use this opportunity to clean up anything the host may not be aware of in preparation for deletion. Avoid self deleting any ```ExternCommand``` object at any time, doing so will must certainly cause the session to crash. + +```bool errState();``` +Return true or false on this function to indicate if the command object is currently in an error state. The default logic simply returns ```errSent``` (section 3.2). + +### 3.4 Shared Objects ### + +The ```SharedObjs``` class is used by the host's ```Session``` and ```CmdExecutor``` objects to share data with all command objects. It contains pointers to various objects related to the user's current session and command states. It is passed into the command via the ```ExternCommand::procBin()``` function as a read only const. Below are all of the pointers defined within it: + +```sessionId``` +This is a ```QByteArray``` containing the 224bit (28byte) sha3 hash id unique to the current session only. this does change upon reconnecting to the host. + +```p2pAccepted``` +This is a ```QList``` object that list all peer session ids that the user has accepted to be allowed to send/receive data when using the ```toPeer()``` signal. + +```p2pPending``` +This is a ```QList``` object that list all peer session ids that sent a ```P2P_REQUEST``` to/from the session and are awaiting a ```P2P_OPEN```. When a ```P2P_OPEN``` is received, the session id sent by it is removed from this list and then added to ```p2pAccepted```. + +```chIds``` +This is a ```QByteArray``` object that string togeather up to 6 channel-sub combinations that indicate which channel id and sub id combinations are currently open. Each channel-sub are 9bytes long and chIds itself maintians a fixed length of 54bytes. It is padded with 0x00 chars to maintain the fixed length (this padding can appear anywhere in 9byte increments within chIds). Channel-sub is formatted like this: + +``` + bytes[0-7] - 64bit LE unsigned int (channel id) + bytes[8] - 8bit LE unsigned int (sub id) + + note: channel id 0 is not a valid id and the sub id cannot + be valid without a valid channel id. +``` + +```wrAbleChIds``` +This is a ```QByteArray``` object formatted exactly like ```chIds```. It however list all channel-subs from ```chIds``` that are 'writable.' This is what is actually used when casting data to peers while ```chIds``` is used when receiving data from peers. This cannot list any channel-subs that are not also listed in ```chIds```. + +```chList``` +This is a ```QList``` of channel names that the currently logged user is a member of. Unlike ```chIds```, this list doesn't care if the channel is open or not. + +```activeUpdate``` +This is a ```bool``` object that indicate if the above ```chIds``` contains an open active update channel. When a session is active updating it will respond to peer pings, ```PEER_INFO``` and ```PEER_STAT``` frames related to just the active update sub-channels. + +```chOwnerOverride``` +This is a ```bool``` object that indicates if the user currently have the channel owner override flag active or not. The owner override flag allows the user to do anything a channel owner could do in a channel without actually being the owner of the channel. This is usually reserved for host administrators. + +```sessionAddr``` +This is a ```QString``` representation of the IPv4 or IPv6 address of the TCP client. + +```userName``` +This is a ```QString``` of the current user that is logged in on the current session. This is empty if no user is currently logged in. + +```groupName``` +This is a ```QString``` of the group that the above ```userName``` is a member of. it is also blank if no user is currently logged in. + +```displayName``` +This is a ```QString``` of the user's desired display name. Clients are encouraged put more emphasis on this if displaying the user to the public. + +```appName``` +This is a ```QString``` of the client's application name. It can also contain the application's version depending on the application itself. This is updated upon connecting to the host and normally does not change while the session is active. + +```clientMajor``` +This is a ```ushort``` version major of the mrci protocol that the client is using. + +```clientMinor``` +This is a ```ushort``` version minor of the mrci protocol that the client is using. + +```clientPatch``` +This is a ```ushort``` version patch of the mrci protocol that the client is using. + +```userId``` +This is a ```QByteArray``` containing the 256bit (32byte) Keccak hash unique to the current user. This is empty if no user is currently logged in. This remains constant even when the user name, or any other user information changes. + +```moreInputCmds``` +This is a ```QList``` of command ids that are currently in the more input state. Command objects in this state are simply requesting more input data from the client and should not be considered finished. + +```activeLoopCmds``` +This is a ```QList``` of command ids that are currently in the loop state. Command objects in this state will cause the ```CmdExecutor``` to call ```ExternCommand::procBin()``` in a loop until it is removed from this list. + +```pausedCmds``` +This is a ```QList``` containing a list of command ids that are blocked from looping. The commands listed here must also be listed in activeLoopCmds. + +```hostRank``` +This is a ```uint``` that indicates the currently logged in user's host rank. The host rank determines the level of access to the host commands the user currently have. what those commands could be depends on how the host is configured. The lower the rank value, the higher the privilege level with 1 being the highest level. 0 is not considered a valid rank and it will initialized as that if no user is currently logged in. + +```cmdNames``` +This is a ```QHash``` with command id ```quint16``` keys that relate to ```QString``` command names that are currently loaded for the session. + +### 3.5 ExternCommand Signals ### + +```dataToClient(QByteArray data, uchar typeId)``` +This signal sends the data to ```CmdExecutor``` passed as a ```QByteArray``` with a ```uchar``` indicating what [TypeID](Type_IDs.md) is stored in that ```QByteArray```. The ```CmdExecutor``` object inspects the type id and stops external commands from sending the following data types: ```PRIV_IPC```, ```PUB_IPC```, ```NEW_CMD```, ```PEER_STAT```, ```MY_INFO```, ```PEER_INFO```, ```HOST_CERT```, ```IDLE```, ```PING_PEERS```, ```P2P_REQUEST```, ```P2P_OPEN```, ```P2P_CLOSE```. If none of these type ids are caught, the data is then sent to the client as a mrci frame. + +```castToPeers(QByteArray data, uchar typeId)``` +This is used to broadcast data to all clients that have any matching sub-channels open. It takes the same parameters and have the same retrictions as ```dataToClient()```. + +```toPeer(QByteArray sessionId, QByteArray data, uchar typeId)``` +This signal is used to send data directly to a peer client without the need to open any sub-channels. Instead, the data is routed via the target peer's session id, given in the 1st ```QByteArray``` parameter as 224bit (28byte) sha3 hash. The next 2 parameters are the same as ```dataToClient()```, however only the following data types are allowed until a connection is negotiated with the peer: ```P2P_REQUEST```, ```P2P_OPEN``` or ```P2P_CLOSE```. More info on this can be found in [Type_IDs.md](Type_IDs.md), section 4.2. Except the P2P specific data types, the same retrictions for ```dataToClient()``` also apply here. + +```closeChById(quint64 channelId, uchar subId), closeChByName(QString channelName, QString subName), openChById(quint64 channelId, uchar subId), openChByName(QString channelName, QString subName)``` +These signals open/close sub-channels via the channel and sub-channel names or ids. The ```CmdExecutor``` does error checking on these so errors are returned to the client if the channel or sub-channel does not exists, is invalid or if the user doesn't have access to it. + +```enableLoop(bool state)``` +This takes a ```bool``` value that enables or disables the command object's loop state. If the loop state is enabled, the ```procBin()``` function will be called by the ```CmdExecutor``` within it's own loop until the loop state is disabled. 3rd party module devs are highly recommended to use this rather than implementing their own long term loops. By letting the ```CmdExecutor``` handle the looping structure it would be possible for it terminate/interrupt the command more gracefully from the outside. + +```enableMoreInput(bool state)``` +This takes a ```bool``` value that enables or disables the command object's more input state. This state tells the ```CmdExecutor``` that the command is requesting more data from the client and should hold off sending an ```IDLE``` frame to the client because the command is not yet finished. if both loop and more input states are disabled, an ```IDLE``` frame is sent to indicate to the client that the command is finished. + +```closeSession()``` +This closes the session from the host side. The TCP connection also closes. + +```cmdFinished()``` +This signal disables both loop and more input states. It also immediately sends the IDLE frame to the client to indicate that the command is finished. Normally, you should not have to emit this signal directly because the ```CmdExecutor``` will automatically send the ```IDLE``` frame if it detects that both the loop and more inputs states are disabled after calling ```procBin()```. This signal is only useful if you need to indicate that the command is finished outside of the ```procBin()``` function. + +```logout()``` +This clears parameters in ```SharedObjs``` related to the currently logged in user and puts the session in a reset state with no user currently logged in. Unlike ```closeSession()```, this does not close the TCP connection. \ No newline at end of file diff --git a/docs/Host_Features.md b/docs/Host_Features.md new file mode 100644 index 0000000..689c217 --- /dev/null +++ b/docs/Host_Features.md @@ -0,0 +1,80 @@ +### 5.1 Host Features ### + +Other than transporting data to be processed by command objects, the host have a few other built in features such as data broadcasting to/from clients connected to the host, a multi-process architecture and full user account management. The following concepts needed to be created to facilitate these specific features: + +### 5.2 Host Groups And Ranks ### + +Host groups are used to manage the access level of all users. Each user registered in the host must be assigned a group and each group must be assigned a host rank. The rank is numeric integer that indicates the value of the group's rank. The lower it's numeric value, the higher the level of access the users in that group have in the host with 0 being invalid. By default, the host defines the ```root``` user into a group also called ```root``` and that group has a host rank of 1; giving it the highest level of access possible in the host. + +The host also defines a default ```users``` group assigned a rank of 2 and that group is also assigned as the ```initial group``` for all new accounts that are created in the host. This can be re-configured at any time using the ```host_settings``` 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``` can't do the same in reverse. + +Host ranks can also be assigned to the commands themselves via the command names. The ```CmdExecutor``` object uses this to determine if it's allowed to load the command object or not. 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 loading/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 load/run it regardless of the user's host rank. This would also disregard the assigned rank of the command. + +### 5.3 Channels And Sub-channels ### + +Channels are used to manage sub-channels and sub-channels are used to determine which clients can send/receive broadcast data. When a client broadcast data to a open sub-channel, the clients that want to receive that data will also need to have the matching sub-channel open. Channels and sub-channels can be identified by a string name or integer id. When a channel is created, the user only needs to provide a unique channel name because the host will auto generate a unique channel id attached to that name. This id is a 64bit unsigned integer and it remains constant for as long as the channel exists, even when the name changes. The channel however can't broadcast any data until a sub-channel is created with the user providing a sub-channel name unique to the channel and the host will auto generate a sub-channel id also unique to the channel. The sub-id is a 8bit unsigned integer and it also remains constant for as long as the sub-channel exists. When brocasting data, a combination of the channel and sub-channel ids are used in the ```SharedObjs``` class (```chIds``` variable) as described in section [3.4](Command_Objects.md) to route the data. + +Access to opening sub-channels are managed in a way similar to the host groups and ranks. All channels have a list of host user accounts that are members of the channel, each having managed levels of access to what they can do as a member of the channel. Host users are added via invitation and the users themselves have the option to accept or decline the invitation to join the channel. Like the host rank, access levels to the channels are a numeric integer value where the lower it's value is the higher level of access the channel member will have but unlike the host rank, the access levels are not user defined. Instead, all levels are host defined and what each level can do is also host defined. + +``` +enum ChannelMemberLevel +{ + OWNER = 1, + ADMIN = 2, + OFFICER = 3, + REGULAR = 4, + PUBLIC = 5 +}; +``` + +Below is a description of what each of these levels can do within the channel: + +owner-level(1): + +1. delete or rename the channel. +2. delete, create or rename sub-channels within the channel. +3. invite new users to the channel or cancel invites. +4. remove any member of the channel except your self. +5. set sub-channels' level of access. +6. add/remove read only flags to/from sub-channels. +7. update the privilege level of any member in the channel. +8. open/close sub-channels. + +admin-level(2): + +1. delete, create or rename sub-channels within the channel. +2. invite new users to the channel or cancel invites. +3. remove any member of channel except the owner and other admins. +4. set sub-channels' level of access. +5. add/remove read only flags to/from sub-channels. +6. update the privilege level of members up to your own level but not above. +7. open/close sub-channels. + +officer-level(3): + +1. invite new users to the channel or cancel invites. +2. can only remove regular members of the channel. +3. update the privilege level of members up to your own level but not above. +4. open/close sub-channels. + +regular-level(4): + +1. open/close sub-channels. + +public-level(5): + +1. open/close public sub-channels. + +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 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. + +### 5.4 Multi-Process Architecture ### + +The host uses what's called a multi-process architecture which basically means each session operate in seperated processes and threads. This makes it possible for a session to crash due to a faultly command without taking down the entire host. In fact, the main process can detect this crash and attempt to restore the session automatically, up to a limited amount of attempts just so it doesn't infinite loop on crash-restore attempts. + +In the code, this is done mainly in the ```Session``` class. When a client connects, a version of this class is created to operate in the main process and in it's own thread. It will then create another instance of the host and that new instance will create another ```Session``` class to operate in slave mode. The slave ```Session``` class will then start communicating with the main process ```Session``` via named pipes (```QLocalSocket```) using the same mrci frame protocol as with the client (section [1.1](Protocol.md)). The class that handles running the commands is the ```CmdExecutor``` object and that lives in the slave process where it is safe to crash without causing the host to go down. \ No newline at end of file diff --git a/docs/Internal_Commands.md b/docs/Internal_Commands.md new file mode 100644 index 0000000..a91828a --- /dev/null +++ b/docs/Internal_Commands.md @@ -0,0 +1,182 @@ +### 7.1 Internal Commands ### + +The host is extendable via 3rd party modules but the host itself have it's own internal module that load commands with direct access to the host database and several internal power functions that external commands would otherwise not have direct access to. + +* [auth](intern_commands/auth.md) - login to the host using a registered user account name or email address. + +* [ls_cmds](intern_commands/ls_cmds.md) - list all available commands for your current session. + +* [my_info](intern_commands/my_info.md) - display information about your current session and your account. + +* [recover_acct](intern_commands/recover_acct.md) - reset a user account password. + +* [request_pw_reset](intern_commands/request_pw_reset.md) - request a password reset for a user account. + +* [accept_ch](intern_commands/accept_ch.md) - accept an invite to a channel to become a regular member of it. + +* [add_acct](intern_commands/add_acct.md) - create a new host user account. + +* [add_ban](intern_commands/add_ban.md) - add an ip address to the host ban list. + +* [add_cert](intern_commands/add_cert.md) - install a new SSL/TLS cert into the host from a local cert and private key file. + +* [add_ch](intern_commands/add_ch.md) - create a new channel. + +* [add_group](intern_commands/add_group.md) - create a new host group. + +* [add_mod](intern_commands/add_mod.md) - upload a new module to install into the host. + +* [add_ranked_cmd](intern_commands/add_ranked_cmd.md) - assign a rank to a command object name. + +* [add_rdonly_flag](intern_commands/add_rdonly_flag.md) - add a read only flag to a certain sub-channel and privilege level. + +* [add_sub_ch](intern_commands/add_sub_ch.md) - create a new sub-channel within a channel. + +* [cast](intern_commands/cast.md) - broadcast text/data to all sessions listening to any matching sub-channels. + +* [cert_info](intern_commands/cert_info.md) - display detailed information about an installed SSL/TLS certificate. + +* [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. + +* [cmd_info](intern_commands/cmd_info.md) - display detailed information about a command. + +* [decline_ch](intern_commands/decline_ch.md) - decline an invite to a channel. + +* [find_ch](intern_commands/find_ch.md) - search for channels within the host based on the channel name or channel id. + +* [force_set_email](intern_commands/force_set_email.md) - overwrite/change the email address of another user's account. + +* [fs_cd](intern_commands/fs_cd.md) - display or change the current directory for the current session. + +* [fs_copy](intern_commands/fs_copy.md) - copy a file system object (file,directory,symlink) from one location to another. + +* [fs_delete](intern_commands/fs_delete.md) - attempt to delete a file system object (file,directory,symlink) in the host. + +* [fs_download](intern_commands/fs_download.md) - download a single file from the host. + +* [fs_info](intern_commands/fs_info.md) - get detailed information about a file system object (file,directory,symlink) in the host. + +* [fs_list](intern_commands/fs_list.md) - list all files or sub-directories in a directory. + +* [fs_mkpath](intern_commands/fs_mkpath.md) - attempt to create a directory and all sub-directories of a given path. + +* [fs_move](intern_commands/fs_move.md) - move/rename a file system object (file,directory,symlink) from one location to another. + +* [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. + +* [is_email_verified](intern_commands/is_email_verified.md) - check if your email address is verified. + +* [lock_acct](intern_commands/lock_acct.md) - lock user account. + +* [ls_act_log](intern_commands/ls_act_log.md) - display or manage the client activity log. + +* [ls_auth_log](intern_commands/ls_auth_log.md) - display or manage the host authorization activity log. + +* [ls_bans](intern_commands/ls_bans.md) - display or manage the host ip address ban table. + +* [ls_certs](intern_commands/ls_certs.md) - display a list of all SSL/TLS certificates installed in the host database. + +* [ls_ch_members](intern_commands/ls_ch_members.md) - list all members in a channel. + +* [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 debug messages from the host instance and all session instances. + +* [ls_groups](intern_commands/ls_groups.md) - list all groups currently registered in the host. + +* [ls_mods](intern_commands/ls_mods.md) - list all available modules currently installed in the host. + +* [ls_open_chs](intern_commands/ls_open_chs.md) - list all of the sub-channels that are currently open. + +* [ls_p2p](intern_commands/ls_p2p.md) - list all p2p connections and pending p2p request you currently have. + +* [ls_ranked_cmds](intern_commands/ls_ranked_cmds.md) - list all command names with assigned host ranks. + +* [ls_rdonly_flags](intern_commands/ls_rdonly_flags.md) - list all read only flags currently present for a channel. + +* [ls_sub_chs](intern_commands/ls_sub_chs.md) - list all sub-channels within a channel you currently a member of. + +* [ls_users](intern_commands/ls_users.md) - list all users currently registered in the host database. + +* [open_sub_ch](intern_commands/open_sub_ch.md) - open a sub-channel to send/receive broadcasted data to/from other peers. + +* [p2p_close](intern_commands/p2p_close.md) - close the p2p connection with the client given in this command or decline a p2p request. + +* [p2p_open](intern_commands/p2p_open.md) - accept the p2p request you may have received from another client connected to the host. + +* [p2p_request](intern_commands/p2p_request.md) - send out a p2p request to the client session id given in this command. + +* [pause](intern_commands/pause.md) - pause the current task that the command is running. + +* [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. + +* [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. + +* [rename_ch](intern_commands/rename_ch.md) - rename a channel. + +* [rename_sub_ch](intern_commands/rename_sub_ch.md) - rename a sub-channel within a channel. + +* [request_new_pw](intern_commands/request_new_pw.md) - enable/disable a password change request for a user on next login. + +* [request_new_user_name](intern_commands/request_new_user_name.md) - enable/disable a user name change request for a user on next login. + +* [restart_host](intern_commands/restart_host.md) - re-start the host instance. + +* [resume](intern_commands/resume.md) - resumes the current task that the command is running. + +* [rm_acct](intern_commands/rm_acct.md) - delete a user account from the host database. + +* [rm_ban](intern_commands/rm_ban.md) - remove an ip address from the ban list. + +* [rm_cert](intern_commands/rm_cert.md) - remove the SSL/TLS cert associated with the given common name. + +* [rm_ch](intern_commands/rm_ch.md) - permanently remove a channel and all of it's sub-shannels from the host. + +* [rm_mod](intern_commands/rm_mod.md) - uninstall a module from the host. + +* [rm_ranked_cmd](intern_commands/rm_ranked_cmd.md) - remove a rank from a command object name. + +* [rm_rdonly_flag](intern_commands/rm_rdonly_flag.md) - remove a read only flag from a certain sub-channel privilege level combination. + +* [rm_sub_ch](intern_commands/rm_sub_ch.md) - remove a sub-channel within a channel. + +* [set_active_flag](intern_commands/set_active_flag.md) - set or unset the active update flag of a sub-channel. + +* [set_disp_name](intern_commands/set_disp_name.md) - change your account display name. + +* [set_email](intern_commands/set_email.md) - change 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_group](intern_commands/set_group.md) - change a user account's group. + +* [set_group_rank](intern_commands/set_group_rank.md) - set the host rank of a group. + +* [set_member_level](intern_commands/set_member_level.md) - set the user privilege levels of a channel member. (lower the value, the higher the privilege) + +* [set_pw](intern_commands/set_pw.md) - change your account password. + +* [set_sub_ch_level](intern_commands/set_sub_ch_level.md) - set the lowest privilege level that members need to be in order to open a certain sub-channel. + +* [set_user_name](intern_commands/set_user_name.md) - change your account user name. + +* [term](intern_commands/term.md) - terminate the current task that the command is running. + +* [to_peer](intern_commands/to_peer.md) - send/receive any data directly with a client connected to the host that has accepted your p2p request or the peer's p2p request. + +* [trans_group](intern_commands/trans_group.md) - transfer all user accounts from one group to another. + +* [verify_email](intern_commands/verify_email.md) - verify your email address by sending a confirmation code to it. + diff --git a/docs/LICENSE.md b/docs/LICENSE.md new file mode 100644 index 0000000..f718b3e --- /dev/null +++ b/docs/LICENSE.md @@ -0,0 +1,621 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/docs/Protocol.md b/docs/Protocol.md new file mode 100644 index 0000000..1104bef --- /dev/null +++ b/docs/Protocol.md @@ -0,0 +1,80 @@ +### 1.1 The Protocol ### + +The main goal of this application is to transport data from remote TCP clients to the [Command_Objects](Command_Objects.md) defined in the host. How the data processed and/or returned back to the client depends entirely on the type of data being transported or the command object itself. The format that the host understands for data transport is referred to as MRCI frames as described below in section 1.2. + +Before any MRCI frames can be transported, the host need to be made aware of the version of the host that the client supports and the client needs to be made aware of the host current version. 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. + +### 1.2 MRCI Frames ### + +MRCI frames are a data structure that allow the TCP clients to communicate with commands objects and various other objects defined in the MRCI host. + +``` +Format: + +[type_id][cmd_id][data_len][payload] + +type_id - 1byte - 8bit little endian integer type id of the payload. +cmd_id - 2bytes - 16bit little endian integer command object id. +data_len - 3bytes - 24bit little endian integer size of the payload. +payload - variable - the actual data to be processed. +``` + +A full description of the type id's can be found in the [Type_IDs.md](Type_IDs.md) document. + +### 1.3 Versioning System ### + +The host uses the typical 3 number versioning system: Major.Minor.Patch + +* **Major** - this indicates any major changes to the code of the application that renders versions of different majors incompatible with each other. +* **Minor** - this indicates only minor changes to the code that may require a few conditional blocks to maintain compatibility. +* **Patch** - this indicates changes that won't require any behaviour changes at all to maintain compatibility. + +Any increments to the major resets the minor and patch to 0. Any 3rd party client applications connecting to a MRCI host need to be aware of this versioning but does not need to adopt it as it's own version number. + +### 1.4 Client Header ### + +``` +Format: + +[tag][major][minor][patch][appName][coName] + +tag - 4bytes - 0x4D, 0x52, 0x43, 0x49 (MRCI) +major - 2bytes - 16bit little endian unsigned int +minor - 2bytes - 16bit little endian unsigned int +patch - 2bytes - 16bit little endian unsigned int +appName - 128bytes - UTF16LE string (padded with spaces) +coName - 272bytes - UTF16LE string (padded with spaces) +``` + +notes: + +* The **tag** is just a fixed ascii string "MRCI" that indicates to the host that the client is indeed attempting to use the MRCI protocol. + +* The **appName** is the name of the client application that is connected to the host. It can also contain the client's app version if needed because it doesn't follow any particular standard. This string is accessable to all command objects so the commands themselves can be made aware of what app the user is currently using. + +* The **coName** is the common name of a SSL certificate that is currently installed in the host. Depending on how the host is configured, it can contain more than one installed SSL cert so coName can be used by clients as a way to request which one of the SSL certs to use during the SSL handshake. + +### 1.5 Host Header ### + +``` +Format: + +[reply][major][minor][patch][sesId] + +reply - 1byte - 8bit little endian unsigned int +major - 2bytes - 16bit little endian unsigned int +minor - 2bytes - 16bit little endian unsigned int +patch - 2bytes - 16bit little endian unsigned int +sesId - 28bytes - 224bit sha3 hash +``` + +notes: + +* **reply** is a numeric value that the host returns in it's header to communicate to the client the result of it's evaluation of the client's header. + + * reply = 1, means the client version is acceptable and it does not need to take any further action. + * reply = 2, means the client version is acceptable but the host will now send it's Pem formatted SSL cert data in a ```HOST_CERT``` mrci frame just after sending it's header. After receiving the cert, the client will then need to send a STARTTLS signal using this cert. + * reply = 3, means the client version is not supported by the host and the session will end shortly. + * reply = 4, means the host was unable to load the SSL cert associated with the common name sent by the client. The session will auto close at this point. + +* **sesId** is the session id. It is a unique 224bit sha3 hash generated against the current date and time of session creation (down to the msec) and the machine id. This can be used by the host and client to uniquely identify the current session or past sessions. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..f438468 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,90 @@ +# MRCI # + +(Modular Remote Command Interpreter) is a command interpreter primarily designed to provide remote service to connected clients, whether text based or any kind of data. 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 user groups. All persistent data is handled by a SQLite database and all remote connections are handled via TCP and encrypted in SSL/TLS. + +### Usage ### + +``` +Usage: mrci + + + + -help : display usage information about this application. + -start : start a new host instance in the background. + -stop : stop the current host instance if one is currently running. + -about : display versioning/warranty information about this application. + -addr {ip_address:port} : 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 default. + -executor : this starts a command executor instance. this is normally for internal use only. + -host : this starts a blocking host instance. this is normally for internal use only. +``` + +Other than that, 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 127.0.0.1 and port 35516. Just like any linux OS, the host will have an administrative user called root along with a group also called root. This account can be used to modify anything on the host without restriction. The default password set to (R00tPa$$w0rd) when logging in for the first time as root, the host will require you to change the password before continuing. + +### More Than Just a Command Interpreter ### + +Typical use for a MRCI host is to run commands that clients ask it to run, very similar to what you see in terminal emulators. It however does have a few feasures typically not seen in local 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 feasured builtin user account management. +* 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. ** +* 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 options to change the email client to any other application that has a command line interface. + +### Documentation ### + +[1.1 The Protocol](Protocol.md) +[2.1 Command Loaders](Command_Loaders.md) +[3.1 Command Objects](Command_Objects.md) +[4.1 Type IDs](Type_IDs.md) +[5.1 Host Features](Host_Features.md) +[6.1 Async Commands](Async.md) +[7.1 Internal Commands](Internal_Commands.md) + +### Development Setup ### + +Linux Required Packages: +``` +qtbase5-dev +libssl-dev +gcc +make +makeself +``` + +### Build From Source (Linux) ### + +Linux_build.sh is a custom script designed to build this project from the source code using qmake, make and makeself. You can pass 2 optional arguments: + +1. The path to the QT bin folder in case you want to compile with a QT install not defined in PATH. +2. Path of the output makeself file (usually has a .run extension). If not given, the outfile will be named mrci-1.0.0.run in the source code folder. + +Build: +``` +cd /path/to/source/code +sh ./linux_build.sh +``` +Install: +``` +chmod +x ./mrci-1.0.0.run +./mrci-1.0.0.run +``` + +The makeself installer not only installs the application but also installs it as a service if the target linux system supports systemd. + +Start/Stop the service: +``` +sudo systemctl start mrci@$USER +sudo systemctl stop mrci@$USER +``` diff --git a/docs/Type_IDs.md b/docs/Type_IDs.md new file mode 100644 index 0000000..548598c --- /dev/null +++ b/docs/Type_IDs.md @@ -0,0 +1,221 @@ +### 4.1 Type IDs ### + +All mrci frames transferred throughout this application are usually passed into various functions using a ```uchar``` numeric value to indicate the type of data being passed with the ```QByteArray``` that are also usually passed into the functions. The type id enum values are as follows: + +``` +enum TypeID +{ + GEN_FILE = 30, + TEXT = 31, + ERR = 32, + PRIV_TEXT = 33, + IDLE = 34, + HOST_CERT = 35, + FILE_INFO = 36, + PEER_INFO = 37, + MY_INFO = 38, + PEER_STAT = 39, + P2P_REQUEST = 40, + P2P_CLOSE = 41, + P2P_OPEN = 42, + BYTES = 43, + SESSION_ID = 44, + NEW_CMD = 45, + CMD_ID = 46, + BIG_TEXT = 47 +}; +``` + +### 4.2 Type Descriptions ### + +```TEXT``` +This is text that can be displayed directly to the user or pass arguments for the command object to process. + +format: ```[UTF-16LE_string] (no BOM)``` + +```GEN_FILE``` +This is a file transfer type id that can be used to transfer any type of file type (music, photos, documents, etc...). It operates in its own protocol of sorts. The 1st GEN_FILE frame received by the host or client is TEXT parameters similar to what you see in terminal command lines with at least one of the arguments listed below. The next set of GEN_FILE frames received by the host or client is then the binary data that needs to be written to an open file or streamed until the limit defined in -len is meet. + +The host or the client can be set as the sender or receiver of the GEN_FILE binary data. Which ever is designated as the receiver by the TEXT parameters need to send an empty GEN_FILE frame to start the process. An example if this can be found in section 4.3. + +arguments: + +* **-len (int)** | this is the integer value of the file size or amount of bytes to read/write. + +* **-offset (int)** | this integer position tells where in the source or destination file to start reading/writing. + +* **-client_file** (string) | this is the file path to the source/destination file in the client's file system. + +* **-remote_file** (string) | this is the file path to the source/destination file in the host file system. + +* **-single_step** | the presents of this argument tells both the client and host to operate in single step mode. single step mode causes the receiver of the binary data whether host or client to send an empty GEN_FILE frame after successfully receiving the data. this then tells the sender to send the next GEN_FILE frame containing binary data for the file and the cycle continues until len is meet. if this argument is not found, the sender can simply send all GEN_FILE data without waiting for an empty GEN_FILE from the receiver. + +* **-to_host** | this argument should only come from host and it will define the client as the sender and the host as the receiver. + +* **-from_host** | opposite affect to *-to_host*. it defines the host as the sender and the client as the receiver. + +* **-truncate** | this indicates to whoever is the receiver to truncate the file being written to. + +* **-force** | in some cases, the receiver might need to overwrite the target file. the presents of this argument tells it to overwrite without asking the user. the host should never send this argument and the client should ignore it if it is received from the host. + +```ERR``` +This type id is similar to TEXT except it indicates that this is an error message that can be displayed directly to the user if needed. + +```PRIV_TEXT``` +This id can be treated exactly like TEXT except this should tell the client to hide or do not echo the next TEXT data that the host is expecting, like a password or other sensitive text data. + +```BIG_TEXT``` +Also formatted exactly like TEXT but this indicates to the client that this is a large body of text that is recommended to be word wrapped when displaying to the user. It can contain line breaks so clients are also recommended to honor those line breaks. + +```IDLE``` +This doesn't carry any actual data, instead this indicates that the command id that sent it has finished it's task. Commands objects are not allowed to send this directly, the ```CmdExecutor``` will handle it. + +```HOST_CERT``` +Just as the name implies, this data type is used by the host to send the host SSL certificate while setting up an SSL connection. + +```FILE_INFO``` +This is a data structure that carries information about a file system object (file,dir,link). + +``` + format: + 1. bytes[0] - flags (8bit little endian uint) + 2. bytes[1-8] - creation time in msec since Epoch UTC (64bit little endian uint) + 3. bytes[9-16] - modification time in msec since Epoch UTC (64bit little endian uint) + 4. bytes[17-24] - file size (64bit little endian uint) + 5. bytes[25-variable] - file name (UTF16-LE string, 16bit terminated) + 6. bytes[variable] - symmlink target if it is a symmlink (UTF16-LE string, 16bit terminated) + + notes: + 1. 16bit terminated UTF-16LE strings are basically + terminated by 2 bytes of 0x00. + 2. the symmlink target is empty if not a symmlink but + the terminator should still be present. + + flags: + 1. bit 0 - true if the object is a file + 2. bit 1 - true if the object is a directory + 3. bit 2 - true if the object is a symmlink + 4. bit 3 - true if the current user have read permissions + 5. bit 4 - true if the current user have write permissions + 6. bit 5 - true if the current user have execute permissions + 7. bit 6 - true if the object exist in the file system. if symmlink, + this determines if the symm target exists or not. +``` + +```PEER_INFO``` +This carry some user account and session information about a peer client connected to the host. + +``` + format: + 1. bytes[0-27] 28bytes - session id (224bit hash) + 2. bytes[28-59] 32bytes - user id (256bit hash) + 3. bytes[60-107] 48bytes - user name (TEXT - padded with empty spaces) + 4. bytes[108-235] 128bytes - app name (TEXT - padded with empty spaces) + 5. bytes[236-299] 64bytes - disp name (TEXT - padded with empty spaces) + + notes: + 1. the session id is unique to the peer's session connection only. it + can change upon reconnection. + 2. the user id is unique to the peer's user account. is stays constant + even when the user name changes and across all clients logged into + the same account. + 3. the display name is the preffered display name of the peer. clients + are encouraged to use this rather than the user name when displaying + peer info to the user. if empty, it's ok to just fall back to the user + name. +``` + +```MY_INFO``` +This contains all of the information found in ```PEER_INFO``` for the local session but also includes the following: + +``` + format: + 1. bytes[300-427] 128bytes - email (TEXT - padded with empty spaces) + 2. bytes[428-451] 24bytes - group name (TEXT - padded with empty spaces) + 3. bytes[452] 1byte - is email confirmed? (0x00 false, 0x01 true) +``` + +```NEW_CMD``` +This contains information about a new command that was added to the current session. + +``` + format: + 1. bytes[0-1] 2bytes - 16bit LE unsigned int (command id) + 2. bytes[2] 1byte - bool (0x01 or 0x00) (handles gen file) + 3. bytes[3-130] 128bytes - command name (TEXT - padded with empty spaces) + 4. bytes[131-258] 128bytes - library name (TEXT - padded with empty spaces) + + notes: + 1. the handles gen file flag is a single byte 0x01 to indicate true and + 0x00 to indicate false. clients need to be aware of which command + handles the GEN_FILE mini protocol because it requires user input at + both ends (host and client). + 2. the library name is what ever is returned by the command object's + ExternCommand::libText() function. this can contain the module name + and/or extra informaion the client can use to identify the command. +``` + +```CMD_ID``` +This type id carries a 16bit unsigned LE int representing a command id. + +format: ```2bytes - 16bit LE unsigned int (command id)``` + +```PEER_STAT``` +This contain status information of a peer client when the peer changes sub-channels or disconnects from the host. + +``` + format: + 1. bytes[0-27] 28bytes - session id (224bit hash) + 2. bytes[28-81] 54bytes - channel-sub ids + 3. bytes[82] 1byte - is disconnected? (0x00 false, 0x01 true) + + notes: + 1. if (is disconnected) is set true (0x01) the session id will no longer + be valid for that peer client so you should not make anymore attempts + to send data to it. + 2. channel-sub ids is a string of 9byte channel-sub id combinations at + a fixed length of 54bytes (padded with 0x00). this indicates what + channels-subs the peer currently have open if the peer's channel ids + no longer match with your session, it can be considered inactive or + disconnected since you will no longer send/receive data with this peer. +``` + +```SESSION_ID``` +This is a fixed length 28byte(224bit) sha3 hash of a client's session id connected to the host. This is unique to just the client's tcp connection with the host. This can change upon re-connection. + +format: ```28bytes - session id (224bit sha3 hash)``` + +```P2P_REQUEST``` +This is formatted extactly like ```PEER_INFO``` except this is allowed to be sent directly to the target peer without restriction when using the ```ExternCommand::toPeer()``` signal ([Command_Objects.md](Command_Objects.md), section 2.5). It will be up to the target peer to respond with a ```P2P_OPEN``` for the host to then unrestrict ```toPeer()``` so it will then be able to send/received other TypeIDs with this peer until ```P2P_CLOSE``` is sent/received. ```P2P_CLOSE``` can also be sent to decline the request. When sending this TypeID, the ```CmdExecutor``` will ignore the data passed into ```toPeer()``` from the command object and substitute with it's own generated ```P2P_REQUEST``` to prevent fraudulent request from getting sent out to all sessions; because of this, it is ok and preferable to send an empty ```P2P_REQUEST``` with the ```toPeer()``` signal. + +```P2P_OPEN``` +This contains a 28byte session id hash of the peer session that you or the peer will allow direct communication with when using the ```ExternCommand::toPeer()``` signal. + +format: ```28bytes - session id (224bit sha3 hash)``` + +```P2P_CLOSE``` +This contains a 28byte session id hash of the peer session that you or the peer want to close direct communication with when using the ```ExternCommand::toPeer()``` signal. + +format: ```28bytes - session id (224bit sha3 hash)``` + +```BYTES``` +This contains arbitrary binary data of any format that is not specialized for any internal objects in the host. + +### 4.3 GEN_FILE Example ### + +Setup: + +* The host has a command called *upload_file* with a command id of *768* and handles the ```GEN_FILE``` data type. +* The client has a file called */home/foo/bar.mp3* and wants to upload it to the host file */home/host/music/bar.mp3* and the client knows the file size is 512bytes. + +To upload the file, the client calls command id *768* with the following text arguments (must still be sent as a GEN_FILE): +```-client_file "/home/foo/bar.mp3" -remote_file "/home/host/music/bar.mp3" -len 512``` + +The host will then return the following the text arguments to the client (also sent as a GEN_FILE): +```-to_host``` + +This argument from the host designates it as the receiver so it will be up to the host to send an empty ```GEN_FILE``` to indicate to the client that it was ready to start receiving binary data from the client to write to */home/host/music/bar.mp3*. If that file already exists, the host will need to ask the user to overwrite or not. + +If the host indicates that it's ready for the upload, the client can then simply read 512 bytes from */home/foo/bar.mp3* and send the read bytes to the host command id *768* as a ```GEN_FILE```. + +The host will then write the bytes received from the client to */home/host/music/bar.mp3* and then auto terminate the command since 512 bytes has been meet. \ No newline at end of file diff --git a/docs/intern_commands/accept_ch.md b/docs/intern_commands/accept_ch.md new file mode 100644 index 0000000..0821873 --- /dev/null +++ b/docs/intern_commands/accept_ch.md @@ -0,0 +1,11 @@ +### Summary ### + +accept an invite to a channel to become a regular member of it. + +### IO ### + +```[-ch_name (text)]/[text]``` + +### Description ### + +accept an invite to the join the channel given in -ch_name. this will fail and do nothing if you do not currently have an existing invite to the channel. once accepted, you will be added to the channel's member list initiated as a regular-level(4). \ No newline at end of file diff --git a/docs/intern_commands/add_acct.md b/docs/intern_commands/add_acct.md new file mode 100644 index 0000000..da3a659 --- /dev/null +++ b/docs/intern_commands/add_acct.md @@ -0,0 +1,11 @@ +### Summary ### + +create a new host user account. + +### IO ### + +```[-name (text) -email (text) {-disp (text)}]/[text]``` + +### Description ### + +this creates a new user account with the user name given in -name and an email address used for account recovery in -email. the command will fail if the user name already exists. you can pass the optional -disp to set the display name for the new user account. the display name can be used by clients to present the user account to other clients without showing the true user name or make it easier for users to identify each other since the display name is not restricted by uniqueness. the display name can be anything; it's only restricted to 32 chars or less. \ No newline at end of file diff --git a/docs/intern_commands/add_ban.md b/docs/intern_commands/add_ban.md new file mode 100644 index 0000000..59fb524 --- /dev/null +++ b/docs/intern_commands/add_ban.md @@ -0,0 +1,11 @@ +### Summary ### + +add an ip address to the host ban list. + +### IO ### + +```[-ip (text)]/[text]``` + +### Description ### + +add an ip address or its integer representative given in -ip to the host ban list that will prevent any client from connecting to the host with that ip address. \ No newline at end of file diff --git a/docs/intern_commands/add_cert.md b/docs/intern_commands/add_cert.md new file mode 100644 index 0000000..66f8864 --- /dev/null +++ b/docs/intern_commands/add_cert.md @@ -0,0 +1,11 @@ +### Summary ### + +install a new SSL/TLS cert into the host from a local cert and private key file. + +### IO ### + +```[-name (text) -cert (path) -priv (path) {-force}]/[text]``` + +### Description ### + +add an SSL/TLS certificate to a common name given in -name. the common name is used by clients to request certain certificate installed in the host. certificates usually come in seperate cert file and private key file pairs. -cert is the file path to the cert file and -priv is the path to the private key. the files must be formatted in Pem or Der (extension doesn't matter). once installed, the file pairs no longer need to exists for the host to use them. you can the pass the optional -force option to replace a common name if it already exists without asking a confirmation question. \ No newline at end of file diff --git a/docs/intern_commands/add_ch.md b/docs/intern_commands/add_ch.md new file mode 100644 index 0000000..52a68c9 --- /dev/null +++ b/docs/intern_commands/add_ch.md @@ -0,0 +1,11 @@ +### Summary ### + +create a new channel. + +### IO ### + +```[-ch_name (text)]/[text]``` + +### Description ### + +create a new channel with a unique name given in -ch_name. this command will automatically add the currently logged in user as a member and set it as the channel owner. being a channel owner prevents you from being able to delete your account at a later time. you will need to name a new owner from the channel member list or delete the entire channel to allow account deletion. the host will automatically assign a unique channel id for the new channel. channels are used for broadcast peer to peer data transfers within the host and the base channel created by this command is used to manage who has access to such data. \ No newline at end of file diff --git a/docs/intern_commands/add_group.md b/docs/intern_commands/add_group.md new file mode 100644 index 0000000..cc6bfe3 --- /dev/null +++ b/docs/intern_commands/add_group.md @@ -0,0 +1,11 @@ +### Summary ### + +create a new host group. + +### IO ### + +```[-name (text)]/[text]``` + +### Description ### + +this will create a new host group with the unique group name given in -name. all new groups are initialized at rank 2. this can be reconfigured at any time using various group editing commands. host groups are used to manage the access level of all users. each user registered in the host must be assigned a group and each group must be assigned a host rank. the rank is numeric integer that indicates the value of the group's rank. The lower it's numeric value, the higher the level of access the users in that group have in the host with 0 being invalid. \ No newline at end of file diff --git a/docs/intern_commands/add_mod.md b/docs/intern_commands/add_mod.md new file mode 100644 index 0000000..0c04d42 --- /dev/null +++ b/docs/intern_commands/add_mod.md @@ -0,0 +1,15 @@ +### Summary ### + +upload a new module to install into the host. + +### IO ### + +```[-client_file (path) -name (text) -len (int)]/[text]``` + +### Description ### + +upload a new module to the host using an archive file (.zip, .tar, .7z, etc...) or a loadable library file (.so, .dll, .a, etc...) using the GEN_FILE data type id. the module name is given in -name while GEN_FILE specific arguments like -len determine the size of the file being sent and -client_file determine the file location on the client's file system. note: the host will use -client_file argument to read the file's suffix and determine if it is an archive or library file. + +once successfully unloaded; if it is an archive file, all of it's contents are extracted to the module installation directory. the archive file must contain a loadable library file called 'main' with any of the host platform compatible library suffixes like (.so, .dll, .a, etc...). if a library file is uploaded, it is simply copied over to the module installation directory as 'main.' + +if successfully installed, all sessions are notified to load the new module. even after a successful install, the module can still fail to load. check the host debug log for failure details if that is the case. \ No newline at end of file diff --git a/docs/intern_commands/add_ranked_cmd.md b/docs/intern_commands/add_ranked_cmd.md new file mode 100644 index 0000000..10800a1 --- /dev/null +++ b/docs/intern_commands/add_ranked_cmd.md @@ -0,0 +1,11 @@ +### Summary ### + +assign a rank to a command object name. + +### IO ### + +```[-command (text) -rank (int)]/[text]``` + +### Description ### + +this will assign the command rank in -rank to the command name given in -command. assigning ranks to commands give groups that have that rank or higher access to running the command. any commands that don't have an assigned rank is assumed a rank of 1. the lower the numeric value of the rank, the higher the rank (0 is invalid). some commands can claim immunity from host ranking which basically means the command is allowed to load/run regardless of the user's host rank. for internal commands, any commands that edit the user's own information like it's name, display name, password, etc including the login command and various session state commands have immunity. \ No newline at end of file diff --git a/docs/intern_commands/add_rdonly_flag.md b/docs/intern_commands/add_rdonly_flag.md new file mode 100644 index 0000000..7a88760 --- /dev/null +++ b/docs/intern_commands/add_rdonly_flag.md @@ -0,0 +1,11 @@ +### Summary ### + +add a read only flag to a certain sub-channel and privilege level. + +### IO ### + +```[-ch_name (text) -sub_id (int) -level (int)]/[text]``` + +### Description ### + +this adds a read only flag to a sub-channel for users at a certain privilage level given in -level. valid privilage levels range 1-5 representing owner-level(1), admin-level(2), officer-level(3), regular-level(4) and public-level(5). the presents of a read only flag bassically tells the host that users connected to this sub-channel at this level can only receive data from this sub-channel and cannot cast data to the sub-channel. this is useful if you want to setup a channel that allow certain user(s) to broadcast data while everybody else can only listen for the data. the channel name given in -ch_name must already exists but the sub-channel id given in -sub_id doesn't need to exists (valid range 0-255) but what ever sub-channel name that takes the sub-id specified here gets the read only flag. only the channel owner-level(1) and admin-level(2) can add read only flags to sub-channels. also note that this command will cause all sessions that currently have the sub-channel open to close it. it will be up to the clients to re-open the sub-channel. \ No newline at end of file diff --git a/docs/intern_commands/add_sub_ch.md b/docs/intern_commands/add_sub_ch.md new file mode 100644 index 0000000..c568afd --- /dev/null +++ b/docs/intern_commands/add_sub_ch.md @@ -0,0 +1,11 @@ +### Summary ### + +create a new sub-channel within a channel. + +### IO ### + +```[-ch_name (text) -sub_name (text)]/[text]``` + +### Description ### + +create a new sub-channel given in -sub_name for the channel given in -ch_name. only the channel owner-level(1) and admin-level(2) is allowed to do this. sub-channels are used to determine which clients can send/receive broadcast data. when a client broadcast data to a open sub-channel, the clients that want to receive that data will also need to have the matching sub-channel open. \ No newline at end of file diff --git a/docs/intern_commands/auth.md b/docs/intern_commands/auth.md new file mode 100644 index 0000000..b47c212 --- /dev/null +++ b/docs/intern_commands/auth.md @@ -0,0 +1,11 @@ +### Summary ### + +login to the host using a registered user account name or email address. + +### IO ### + +```[-user (text)] or [-email (text)]/[text]``` + +### Description ### + +login into a user account name given in -user or email address given in -email. this command will ask for the password during execution to complete the authentication. \ No newline at end of file diff --git a/docs/intern_commands/cast.md b/docs/intern_commands/cast.md new file mode 100644 index 0000000..69e9ef6 --- /dev/null +++ b/docs/intern_commands/cast.md @@ -0,0 +1,11 @@ +### Summary ### + +broadcast text/data to all sessions listening to any matching sub-channels. + +### IO ### + +```[any]/[any]``` + +### Description ### + +this sends data passed into it to all peers connected to any of your matching opened sub-channels. this is useful for broadcasting data session-to-session. nothing gets re-sent to the session that initiated the cast. \ No newline at end of file diff --git a/docs/intern_commands/cert_info.md b/docs/intern_commands/cert_info.md new file mode 100644 index 0000000..7820d1c --- /dev/null +++ b/docs/intern_commands/cert_info.md @@ -0,0 +1,11 @@ +### Summary ### + +display detailed information about an installed SSL/TLS certificate. + +### IO ### + +```[-name (text)]/[text]``` + +### Description ### + +display more detailed information about the SSL/TLS certificate given in -name. -name is the common name clients can use to request a certain certificate installed in the host. \ No newline at end of file diff --git a/docs/intern_commands/ch_owner_override.md b/docs/intern_commands/ch_owner_override.md new file mode 100644 index 0000000..9e21adc --- /dev/null +++ b/docs/intern_commands/ch_owner_override.md @@ -0,0 +1,11 @@ +### Summary ### + +set/unset the channel owner override flag for your current session. + +### IO ### + +```[-state (1 or 0)]/[text]``` + +### Description ### + +This sets/unsets the channel owner override flag for your current session according to the bool value passed into -state. the owner override flag basically tells the host that the current session can do anything a channel owner can do to any channel in the host and doesn't even need to be a member. this is a simple but very powerful command, it is best reserved for host administrators only. \ No newline at end of file diff --git a/docs/intern_commands/close_host.md b/docs/intern_commands/close_host.md new file mode 100644 index 0000000..2f6842a --- /dev/null +++ b/docs/intern_commands/close_host.md @@ -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 entirely so all clients will not be able to connect until it is started again. \ No newline at end of file diff --git a/docs/intern_commands/close_sub_ch.md b/docs/intern_commands/close_sub_ch.md new file mode 100644 index 0000000..3e2c792 --- /dev/null +++ b/docs/intern_commands/close_sub_ch.md @@ -0,0 +1,11 @@ +### Summary ### + +close a sub-channel for your current session. + +### IO ### + +```[-ch_name (text) -sub_name (text)]/[text]``` + +### Description ### + +this command closes a sub-channel on your current session so you can stop sending or receiving broadcast data to/from the specific sub-channel name given in -sub_name for the base channel given in -ch_name. \ No newline at end of file diff --git a/docs/intern_commands/cmd_info.md b/docs/intern_commands/cmd_info.md new file mode 100644 index 0000000..3f868f1 --- /dev/null +++ b/docs/intern_commands/cmd_info.md @@ -0,0 +1,11 @@ +### Summary ### + +display detailed information about a command. + +### IO ### + +```[-cmd_name (text)] or [-cmd_id (int)]/[text]``` + +### Description ### + +display more detailed information about the command name given in -cmd_name or the command id given in -cmd_id. \ No newline at end of file diff --git a/docs/intern_commands/decline_ch.md b/docs/intern_commands/decline_ch.md new file mode 100644 index 0000000..6e8fcd1 --- /dev/null +++ b/docs/intern_commands/decline_ch.md @@ -0,0 +1,11 @@ +### Summary ### + +decline an invite to a channel. + +### IO ### + +```[-ch_name (text)]/[text]``` + +### Description ### + +decline or cancel an invite to the channel given in -ch_name. this will fail and do nothing if you do not currently have an existing invite to the channel. the channel owner-level(1), admin-level(2) or officer(3) can also cancel this invite at any time. \ No newline at end of file diff --git a/docs/intern_commands/find_ch.md b/docs/intern_commands/find_ch.md new file mode 100644 index 0000000..9640108 --- /dev/null +++ b/docs/intern_commands/find_ch.md @@ -0,0 +1,11 @@ +### Summary ### + +search for channels within the host based on the channel name or channel id. + +### IO ### + +```[-name (text)] or [-id (int)]/[text]``` + +### Description ### + +search the host database for channels with any matching key words given in -name or any channel id given in -id. \ No newline at end of file diff --git a/docs/intern_commands/force_set_email.md b/docs/intern_commands/force_set_email.md new file mode 100644 index 0000000..aecee82 --- /dev/null +++ b/docs/intern_commands/force_set_email.md @@ -0,0 +1,11 @@ +### Summary ### + +overwrite/change the email address of another user's account. + +### IO ### + +```[-user (text) -new_email (text)]/[text]``` + +### Description ### + +change the email address for the user account name given in -user to the email adress given in -new_email. \ No newline at end of file diff --git a/docs/intern_commands/fs_cd.md b/docs/intern_commands/fs_cd.md new file mode 100644 index 0000000..7737c8f --- /dev/null +++ b/docs/intern_commands/fs_cd.md @@ -0,0 +1,11 @@ +### Summary ### + +display or change the current directory for the current session. + +### IO ### + +```[{-path (text)}]/[text]``` + +### Description ### + +change the current directory of your current session to the directory specified in -path or display it if -path is not specified. note: the current directory is not shared among any of the peer sessions. \ No newline at end of file diff --git a/docs/intern_commands/fs_copy.md b/docs/intern_commands/fs_copy.md new file mode 100644 index 0000000..c399bbc --- /dev/null +++ b/docs/intern_commands/fs_copy.md @@ -0,0 +1,11 @@ +### Summary ### + +copy a file system object (file,directory,symlink) from one location to another. + +### IO ### + +```[-src (text) -dst (text) {-force}]/[text]``` + +### Description ### + +copy the file system object (file/directory/symlink) given in -src to the destination given in -dst. this command will ask a confirmation question of the the destination object already exists; pass -force to bypass this. this will also do a full recursive copy if copying a directory. \ No newline at end of file diff --git a/docs/intern_commands/fs_delete.md b/docs/intern_commands/fs_delete.md new file mode 100644 index 0000000..16a0201 --- /dev/null +++ b/docs/intern_commands/fs_delete.md @@ -0,0 +1,11 @@ +### Summary ### + +attempt to delete a file system object (file,directory,symlink) in the host. + +### IO ### + +```[-path (text) {-force}]/[text]``` + +### Description ### + +attempt to delete the file system object (file/directory/symlink) given in -path. pass -force to bypass the command's confirmation question. note: this will do a recursive delete if deleting a directory. \ No newline at end of file diff --git a/docs/intern_commands/fs_download.md b/docs/intern_commands/fs_download.md new file mode 100644 index 0000000..ff13a53 --- /dev/null +++ b/docs/intern_commands/fs_download.md @@ -0,0 +1,17 @@ +### Summary ### + +download a single file from the host. + +### IO ### + +```[-remote_file (text) {-client_file (text)} {-len (int)} {-offset (int)} {-single_step} {-force} {-truncate}]/[GEN_FILE]``` + +### Description ### + +this command sends GEN_FILE data of the file given in -remote_file. depending on the client, you might need to enter the destination file in -client_file. + +-offset is the position in the file to start reading and it defaults to 0 if not given. +-len is the amount of data to read from the file and the host will auto fill it to the file size if not given. the host also auto fill to the file size if it larger than the actual file size. +-single_step enables GEN_FILE's single step mode if the client/host desires it. +-force bypasses any overwrite confirmation question if the client does such a thing. +-truncate tells the client if it should truncate the destination file. the host does nothing with this. it's entirely up to the client to support it. \ No newline at end of file diff --git a/docs/intern_commands/fs_info.md b/docs/intern_commands/fs_info.md new file mode 100644 index 0000000..bf67314 --- /dev/null +++ b/docs/intern_commands/fs_info.md @@ -0,0 +1,11 @@ +### Summary ### + +get detailed information about a file system object (file,directory,symlink) in the host. + +### IO ### + +```[-path (text) {-info_frame}]/[text] or [FILE_INFO]``` + +### Description ### + +display more information about the file system object (file,directory,symlink) specified in -path. by default, it returns human readable text but the -info_frame option causes the command to return a FILE_INFO frame instead for easier machine parsing. \ No newline at end of file diff --git a/docs/intern_commands/fs_list.md b/docs/intern_commands/fs_list.md new file mode 100644 index 0000000..11f1d29 --- /dev/null +++ b/docs/intern_commands/fs_list.md @@ -0,0 +1,11 @@ +### Summary ### + +list all files or sub-directories in a directory. + +### IO ### + +```[{-path (text)} {-info_frame}]/[text] or [FILE_INFO]``` + +### Description ### + +this list all files in the current directory or the directory specified in -path. this command normally returns human readable text for each file or sub-directory that is listed but you can pass -info_frame to make the command return FILE_INFO frames for each file/sub-directory instead. note: if displaying as text, all directory names are displayed with a '/' at the end. \ No newline at end of file diff --git a/docs/intern_commands/fs_mkpath.md b/docs/intern_commands/fs_mkpath.md new file mode 100644 index 0000000..ec2978a --- /dev/null +++ b/docs/intern_commands/fs_mkpath.md @@ -0,0 +1,11 @@ +### Summary ### + +attempt to create a directory and all sub-directories of a given path. + +### IO ### + +```[-path (text)]/[text]``` + +### Description ### + +this attempts to create the directory and all sub-directories needed to make the path given in -path. this does nothing if the path already exists. \ No newline at end of file diff --git a/docs/intern_commands/fs_move.md b/docs/intern_commands/fs_move.md new file mode 100644 index 0000000..10fb57b --- /dev/null +++ b/docs/intern_commands/fs_move.md @@ -0,0 +1,11 @@ +### Summary ### + +move/rename a file system object (file,directory,symlink) from one location to another. + +### IO ### + +```[-src (text) -dst (text) {-force}]/[text]``` + +### Description ### + +move/rename the file system object (file/directory/symlink) given in -src to the destination given in -dst. this command will ask a confirmation question of the the destination file already exists; pass -force to bypass this. \ No newline at end of file diff --git a/docs/intern_commands/fs_upload.md b/docs/intern_commands/fs_upload.md new file mode 100644 index 0000000..9b12e76 --- /dev/null +++ b/docs/intern_commands/fs_upload.md @@ -0,0 +1,16 @@ +### Summary ### + +upload a single file to the host. + +### IO ### + +```[-remote_file (text) -len (int) {-client_file (text)} {-offset (int)} {-single_step} {-force} {-truncate}]/[GEN_FILE]``` + +### Description ### + +attempt to upload the file given in -client_file to the destination file path in the host given in -remote_file. depending on the client, you might not need to enter -len. if supported, the client will auto fill -len with what ever value is needed. + +-offset is the position in the file to start writing and it defaults to 0 if not given. +-single_step enables GEN_FILE's single step mode if the client/host desires it. +-force bypasses the overwrite confirmation question if the destination file already exists. +-truncate tells the host if it should truncate the destination file. \ No newline at end of file diff --git a/docs/intern_commands/host_config.md b/docs/intern_commands/host_config.md new file mode 100644 index 0000000..380ae55 --- /dev/null +++ b/docs/intern_commands/host_config.md @@ -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. \ No newline at end of file diff --git a/docs/intern_commands/host_info.md b/docs/intern_commands/host_info.md new file mode 100644 index 0000000..b456354 --- /dev/null +++ b/docs/intern_commands/host_info.md @@ -0,0 +1,11 @@ +### Summary ### + +display system information about the host. + +### IO ### + +```[none]/[text]``` + +### Description ### + +this will display all relevant information about the host. \ No newline at end of file diff --git a/docs/intern_commands/invite_to_ch.md b/docs/intern_commands/invite_to_ch.md new file mode 100644 index 0000000..73dcad9 --- /dev/null +++ b/docs/intern_commands/invite_to_ch.md @@ -0,0 +1,11 @@ +### Summary ### + +invite a host user to join a channel. + +### IO ### + +```[-ch_name (text) -user (text)]/[text]``` + +### Description ### + +invite a new user via the user name given in -user to join the channel given in -ch_name. you must be a member of the channel at any of the following levels for the this to work: owner-level(1), admin-level(2) or officer(3). all new members to any channel are initiated at regular-level(4). \ No newline at end of file diff --git a/docs/intern_commands/is_email_verified.md b/docs/intern_commands/is_email_verified.md new file mode 100644 index 0000000..75d6964 --- /dev/null +++ b/docs/intern_commands/is_email_verified.md @@ -0,0 +1,11 @@ +### Summary ### + +check if your email address is verified. + +### IO ### + +```[none]/[text]``` + +### Description ### + +return a true or false TEXT that indicate if your currently logged in account has a verified email address. \ No newline at end of file diff --git a/docs/intern_commands/lock_acct.md b/docs/intern_commands/lock_acct.md new file mode 100644 index 0000000..f8af8d9 --- /dev/null +++ b/docs/intern_commands/lock_acct.md @@ -0,0 +1,11 @@ +### Summary ### + +lock user account. + +### IO ### + +```[-user (text) -state (0 or 1)]/[text]``` + +### Description ### + +this sets the locked state of a user account name given in -user. locked user accounts are unable to login until the lock is released. -state is a boolean value where 1 is true and 0 is false. \ No newline at end of file diff --git a/docs/intern_commands/ls_act_log.md b/docs/intern_commands/ls_act_log.md new file mode 100644 index 0000000..142fc5a --- /dev/null +++ b/docs/intern_commands/ls_act_log.md @@ -0,0 +1,19 @@ +### Summary ### + +display or manage the client activity log. + +### 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 +-ip_address +-session_id +-client_version +-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. \ No newline at end of file diff --git a/docs/intern_commands/ls_auth_log.md b/docs/intern_commands/ls_auth_log.md new file mode 100644 index 0000000..08b19d4 --- /dev/null +++ b/docs/intern_commands/ls_auth_log.md @@ -0,0 +1,25 @@ +### Summary ### + +display or manage the host authorization activity log. + +### 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 +-ip_address +-user_name +-auth_attempt +-recover_attempt +-count_to_threshold +-accepted + +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. + +the host use entries in this table to enforce maximum failed login thresholds using a combination of the values found in the -count_to_threshold, -user_name, -count_to_threshold and -accepted columns. so for example, if the host counts a maximum of 50 entries for a certain -user_name with -count_to_threshold = 1 and -accepted = 0 and the host maximum amount of failed attempts is set to 50 then the host will then auto lock the user account to protect it. + +the -accepted column is a 1 or 0 to indicate if the user successfully logged in or not and the -count_to_threshold column is also 1 or 0 to determine if this particular entry should be counted toward the threshold or not. \ No newline at end of file diff --git a/docs/intern_commands/ls_bans.md b/docs/intern_commands/ls_bans.md new file mode 100644 index 0000000..92856f3 --- /dev/null +++ b/docs/intern_commands/ls_bans.md @@ -0,0 +1,16 @@ +### Summary ### + +display or manage the host ip address ban table. + +### 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 +-ip_address + +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. keep in mind, the host use this table to enforce the ban-by-ip option. deleting entries in this table will un-ban the ip's affected by the deletion. \ No newline at end of file diff --git a/docs/intern_commands/ls_certs.md b/docs/intern_commands/ls_certs.md new file mode 100644 index 0000000..4b4af64 --- /dev/null +++ b/docs/intern_commands/ls_certs.md @@ -0,0 +1,11 @@ +### Summary ### + +display a list of all SSL/TLS certificates installed in the host database. + +### IO ### + +```[{search_terms}]/[text]``` + +### Description ### + +by default, all entries in the table are displayed in 50 entries per page. you can pass the column name as -common_name (text) to refine your search to specific entries. \ No newline at end of file diff --git a/docs/intern_commands/ls_ch_members.md b/docs/intern_commands/ls_ch_members.md new file mode 100644 index 0000000..887e7ee --- /dev/null +++ b/docs/intern_commands/ls_ch_members.md @@ -0,0 +1,11 @@ +### Summary ### + +list all members in a channel. + +### IO ### + +```[-ch_name (text) {-find (text)}]/[text]``` + +### Description ### + +list all members currently in the channel given in -ch_name. you must be a member of the channel (any level) for this to work. this command will display 50 entries per page but you can pass the optional -find argument with a text search term to find members via the user name. \ No newline at end of file diff --git a/docs/intern_commands/ls_chs.md b/docs/intern_commands/ls_chs.md new file mode 100644 index 0000000..a45c2ca --- /dev/null +++ b/docs/intern_commands/ls_chs.md @@ -0,0 +1,11 @@ +### Summary ### + +list all channels you are currently a member of and all pending invites. + +### IO ### + +```[none]/[text]``` + +### Description ### + +this will list all channels that you are currently a member of including all pending invitations to join other channels. this command will display a maximum of 50 entries per page. \ No newline at end of file diff --git a/docs/intern_commands/ls_cmds.md b/docs/intern_commands/ls_cmds.md new file mode 100644 index 0000000..21acee7 --- /dev/null +++ b/docs/intern_commands/ls_cmds.md @@ -0,0 +1,11 @@ +### Summary ### + +list all available commands for your current session. + +### IO ### + +```[{-find (text)}]/[text]``` + +### Description ### + +display all available commands in your current session in a table format. you can pass an optional -find text to narrow down the size of the list or find specific commands via the command name. \ No newline at end of file diff --git a/docs/intern_commands/ls_dbg.md b/docs/intern_commands/ls_dbg.md new file mode 100644 index 0000000..ab69b4c --- /dev/null +++ b/docs/intern_commands/ls_dbg.md @@ -0,0 +1,16 @@ +### Summary ### + +display debug messages from the host instance and all session instances. + +### 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. \ No newline at end of file diff --git a/docs/intern_commands/ls_groups.md b/docs/intern_commands/ls_groups.md new file mode 100644 index 0000000..9f6f85c --- /dev/null +++ b/docs/intern_commands/ls_groups.md @@ -0,0 +1,14 @@ +### Summary ### + +list all groups currently registered in the host. + +### IO ### + +```[{search_terms}]/[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: + +-group_name +-host_rank \ No newline at end of file diff --git a/docs/intern_commands/ls_mods.md b/docs/intern_commands/ls_mods.md new file mode 100644 index 0000000..f61e9b4 --- /dev/null +++ b/docs/intern_commands/ls_mods.md @@ -0,0 +1,15 @@ +### Summary ### + +list all available modules currently installed in the host. + +### IO ### + +```[none]/[text]``` + +### Description ### + +display a list of all modules currently installed in the host. any mods marked with a '1' on the locked column will prevent new sessions from attempting to load it. the presents of a lock can mean 3 things: + +1. the module is queued for deletion. +2. installation is in process and the module is not yet ready for loading. +3. a session has crashed while attempting to load the module. \ No newline at end of file diff --git a/docs/intern_commands/ls_open_chs.md b/docs/intern_commands/ls_open_chs.md new file mode 100644 index 0000000..50377d1 --- /dev/null +++ b/docs/intern_commands/ls_open_chs.md @@ -0,0 +1,11 @@ +### Summary ### + +list all of the sub-channels that are currently open. + +### IO ### + +```[none]/[text]``` + +### Description ### + +this command will list all open sub-channels you are currently listening or broadcasting to. \ No newline at end of file diff --git a/docs/intern_commands/ls_p2p.md b/docs/intern_commands/ls_p2p.md new file mode 100644 index 0000000..2740543 --- /dev/null +++ b/docs/intern_commands/ls_p2p.md @@ -0,0 +1,11 @@ +### Summary ### + +list all p2p connections and pending p2p request you currently have. + +### IO ### + +```[none]/[text]``` + +### Description ### + +this will list all client session id hashes as hex text that you currently have active connections with or any clients you currently have pending P2P_REQUEST with. \ No newline at end of file diff --git a/docs/intern_commands/ls_ranked_cmds.md b/docs/intern_commands/ls_ranked_cmds.md new file mode 100644 index 0000000..13542a7 --- /dev/null +++ b/docs/intern_commands/ls_ranked_cmds.md @@ -0,0 +1,14 @@ +### Summary ### + +list all command names with assigned host ranks. + +### IO ### + +```[{search_terms}]/[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: + +-command_obj_name +-host_rank diff --git a/docs/intern_commands/ls_rdonly_flags.md b/docs/intern_commands/ls_rdonly_flags.md new file mode 100644 index 0000000..ab06369 --- /dev/null +++ b/docs/intern_commands/ls_rdonly_flags.md @@ -0,0 +1,11 @@ +### Summary ### + +list all read only flags currently present for a channel. + +### IO ### + +```[-ch_name (text)]/[text]``` + +### Description ### + +list all sub-channels with read only flags currently present for the channel name given in -ch_name. non-members of the channel can use this command to list all public sub-channels with read only flags while members of the channel can list all sub-channels with read only flags regardless of access level. \ No newline at end of file diff --git a/docs/intern_commands/ls_sub_chs.md b/docs/intern_commands/ls_sub_chs.md new file mode 100644 index 0000000..75e0e85 --- /dev/null +++ b/docs/intern_commands/ls_sub_chs.md @@ -0,0 +1,11 @@ +### Summary ### + +list all sub-channels within a channel you currently a member of. + +### IO ### + +```[-ch_name (text)]/[text]``` + +### Description ### + +this will list all sub-channels that currently exists in the channel given in -ch_name. non-members can only list sub-channels accessible to public users while channel members can list all sub-channels regurdless of privilege level. \ No newline at end of file diff --git a/docs/intern_commands/ls_users.md b/docs/intern_commands/ls_users.md new file mode 100644 index 0000000..468cec5 --- /dev/null +++ b/docs/intern_commands/ls_users.md @@ -0,0 +1,16 @@ +### Summary ### + +list all users currently registered in the host database. + +### IO ### + +```[{search_terms}]/[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: + +-DateAdded +-UserName +-GroupName +-UserID diff --git a/docs/intern_commands/my_info.md b/docs/intern_commands/my_info.md new file mode 100644 index 0000000..aa4fcf1 --- /dev/null +++ b/docs/intern_commands/my_info.md @@ -0,0 +1,11 @@ +### Summary ### + +display information about your current session and your account. + +### IO ### + +```[none]/[text]``` + +### Description ### + +this displays all available information about the current session and the currently active user account if logged in. \ No newline at end of file diff --git a/docs/intern_commands/open_sub_ch.md b/docs/intern_commands/open_sub_ch.md new file mode 100644 index 0000000..5ee2290 --- /dev/null +++ b/docs/intern_commands/open_sub_ch.md @@ -0,0 +1,11 @@ +### Summary ### + +open a sub-channel to send/receive broadcasted data to/from other peers. + +### IO ### + +```[-ch_name (text) -sub_name (text)]/[text]``` + +### Description ### + +use this command to open the channel and sub-channel name given in -ch for the main channel name and -sub for the sub-channel name. only peers with matching channel-sub combinations can send/receive data with each other when using the cast command. whether peers send/receive auto/pinged PEER_INFO or PEER_STAT frames depends if the active update flag is set on the channel itself or the host. \ No newline at end of file diff --git a/docs/intern_commands/p2p_close.md b/docs/intern_commands/p2p_close.md new file mode 100644 index 0000000..5e95ea9 --- /dev/null +++ b/docs/intern_commands/p2p_close.md @@ -0,0 +1,11 @@ +### Summary ### + +close the p2p connection with the client given in this command or decline a p2p request. + +### IO ### + +```[SESSION_ID]/[none]``` + +### Description ### + +send out a P2P_CLOSE to the client's 28byte (224bit) session id given to this command. this can be used to decline a P2P_REQUEST or close the p2p connection you may have the peer. this command fails if you have no such connection or no pending P2P_REQUEST with the peer. \ No newline at end of file diff --git a/docs/intern_commands/p2p_open.md b/docs/intern_commands/p2p_open.md new file mode 100644 index 0000000..cf0aaac --- /dev/null +++ b/docs/intern_commands/p2p_open.md @@ -0,0 +1,11 @@ +### Summary ### + +accept the p2p request you may have received from another client connected to the host. + +### IO ### + +```[SESSION_ID]/[none]``` + +### Description ### + +send out a P2P_OPEN to the client's 28byte (224bit) session id given to this command. use this in response to a P2P_REQUEST to accept it. this command fails if you did not receive a P2P_REQUEST from the peer client. \ No newline at end of file diff --git a/docs/intern_commands/p2p_request.md b/docs/intern_commands/p2p_request.md new file mode 100644 index 0000000..9c26adb --- /dev/null +++ b/docs/intern_commands/p2p_request.md @@ -0,0 +1,11 @@ +### Summary ### + +send out a p2p request to the client session id given in this command. + +### IO ### + +```[SESSION_ID]/[P2P_CLOSE] or [P2P_OPEN]``` + +### Description ### + +send out a P2P_REQUEST to another client connected to the host. a P2P_OPEN frame will be returned to you from the target session if the request is accepted. you will then be allowed to send any data to/from this peer. a P2P_CLOSE is returned if the request is declined. this command simply takes a 28byte (224bit) hash of the peer client's session id. \ No newline at end of file diff --git a/docs/intern_commands/pause.md b/docs/intern_commands/pause.md new file mode 100644 index 0000000..b707175 --- /dev/null +++ b/docs/intern_commands/pause.md @@ -0,0 +1,11 @@ +### Summary ### + +pause the current task that the command is running. + +### IO ### + +```[{CMD_ID}]/[text]``` + +### Description ### + +this pauses the command given by a CMD_ID frame sent into this command. an error is returned if the requested command is not currently in the looping state. if the frame is empty, this command pauses all commands currently in the looping state. \ No newline at end of file diff --git a/docs/intern_commands/ping_peers.md b/docs/intern_commands/ping_peers.md new file mode 100644 index 0000000..e7c9437 --- /dev/null +++ b/docs/intern_commands/ping_peers.md @@ -0,0 +1,11 @@ +### Summary ### + +ping all peer sessions with any matching sub-channels to return information about themselves to you. + +### IO ### + +```[none]/[PEER_INFO]``` + +### Description ### + +this will ping all active channels you currently have open. all peer sessions that have any matching open active channels will return a PEER_INFO frame to you. \ No newline at end of file diff --git a/docs/intern_commands/preview_email.md b/docs/intern_commands/preview_email.md new file mode 100644 index 0000000..b6b0c91 --- /dev/null +++ b/docs/intern_commands/preview_email.md @@ -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. \ No newline at end of file diff --git a/docs/intern_commands/recover_acct.md b/docs/intern_commands/recover_acct.md new file mode 100644 index 0000000..6fbabe7 --- /dev/null +++ b/docs/intern_commands/recover_acct.md @@ -0,0 +1,11 @@ +### Summary ### + +reset a user account password. + +### IO ### + +```[-email (text)] or [-user (text)]/[text]``` + +### Description ### + +reset the password for the user account given in -email or -user. during execution, this command will ask for the temporary password that was sent to that email address when request_pw_reset was called. \ No newline at end of file diff --git a/docs/intern_commands/remove_ch_member.md b/docs/intern_commands/remove_ch_member.md new file mode 100644 index 0000000..38041b7 --- /dev/null +++ b/docs/intern_commands/remove_ch_member.md @@ -0,0 +1,11 @@ +### Summary ### + +remove a user as a member of a channel you currently a member of or cancel an invite. + +### IO ### + +```[-ch_name (text) -user (text)]/[text]``` + +### Description ### + +remove a user given in -user from the channel member list for the channel given in -ch_name. normally, only the channel owner-level(1), admin-level(2) of officer(3) can do this but it is unrestricted if removing your self as a member except the channel owner. another restriction is if trying to remove members with higher or equal member privileges than your self so officers can't remove admins, other officers or the owner and admins can't remove other admins or the owner. the owner can't be removed from the channel at anytime. this command can also be used to cancel an invite for a user. \ No newline at end of file diff --git a/docs/intern_commands/rename_ch.md b/docs/intern_commands/rename_ch.md new file mode 100644 index 0000000..df897af --- /dev/null +++ b/docs/intern_commands/rename_ch.md @@ -0,0 +1,11 @@ +### Summary ### + +rename a channel. + +### IO ### + +```[-ch_name (text) -new_name (text)]/[text]``` + +### Description ### + +rename the channel given in -ch_name to the name given in -new_name. valid channel names cannot contain spaces and must be between 4-32 chars long. only the channel owner-level(1) is allowed to do this. \ No newline at end of file diff --git a/docs/intern_commands/rename_sub_ch.md b/docs/intern_commands/rename_sub_ch.md new file mode 100644 index 0000000..6cbfe56 --- /dev/null +++ b/docs/intern_commands/rename_sub_ch.md @@ -0,0 +1,11 @@ +### Summary ### + +rename a sub-channel within a channel. + +### IO ### + +```[-ch_name (text) -sub_name (text) -new_name (text)]/[text]``` + +### Description ### + +rename the sub-channel given in -sub_name for the channel given in -ch_name to the new name given in -new_name. the same rules for channel names also apply to sub-channel names. only the channel owner-level(1) and admin-level(2) is allowed to do this. \ No newline at end of file diff --git a/docs/intern_commands/request_new_pw.md b/docs/intern_commands/request_new_pw.md new file mode 100644 index 0000000..5e7813f --- /dev/null +++ b/docs/intern_commands/request_new_pw.md @@ -0,0 +1,11 @@ +### Summary ### + +enable/disable a password change request for a user on next login. + +### IO ### + +```[-user (text) -req (0 or 1)]/[text]``` + +### Description ### + +this will set or unset the request for the user given in -user to change the password on next login. pass 0 on -req to disable the request or pass 1 to enable the request. the request is automatically disabled when the user successfully changes the password. \ No newline at end of file diff --git a/docs/intern_commands/request_new_user_name.md b/docs/intern_commands/request_new_user_name.md new file mode 100644 index 0000000..722c858 --- /dev/null +++ b/docs/intern_commands/request_new_user_name.md @@ -0,0 +1,11 @@ +### Summary ### + +enable/disable a user name change request for a user on next login. + +### IO ### + +```[-user (text) -req (0 or 1)]/[text]``` + +### Description ### + +this will set or unset the request for the user given in -user to change the user name on next login. pass 0 on -req to disable the request or pass 1 to enable the request. the request is automatically disabled when the user successfully changes the user name. \ No newline at end of file diff --git a/docs/intern_commands/request_pw_reset.md b/docs/intern_commands/request_pw_reset.md new file mode 100644 index 0000000..3fd89d7 --- /dev/null +++ b/docs/intern_commands/request_pw_reset.md @@ -0,0 +1,11 @@ +### Summary ### + +request a password reset for a user account. + +### IO ### + +```[-email (text)] or [-user (text)]/[text]``` + +### Description ### + +this will send a temporary password for the user account associated with the email adress given in -email or the username given in -user. this password can then be used with recover_account to reset the password. \ No newline at end of file diff --git a/docs/intern_commands/restart_host.md b/docs/intern_commands/restart_host.md new file mode 100644 index 0000000..d2cbe27 --- /dev/null +++ b/docs/intern_commands/restart_host.md @@ -0,0 +1,11 @@ +### Summary ### + +re-start the host instance. + +### IO ### + +```[none]/[text]``` + +### Description ### + +this will restart the host instance, along with a database reload. be very careful with this command. it ends all current sessions and since it also reloads the database, host behaviour might also change depending on what is in the new database if the path was changed. \ No newline at end of file diff --git a/docs/intern_commands/resume.md b/docs/intern_commands/resume.md new file mode 100644 index 0000000..a9c81b1 --- /dev/null +++ b/docs/intern_commands/resume.md @@ -0,0 +1,11 @@ +### Summary ### + +resumes the current task that the command is running. + +### IO ### + +```[{CMD_ID}]/[text]``` + +### Description ### + +this resumes the looping task for the command given by a CMD_ID frame sent into this command. a failure is returned if the command is not currently in a paused state. if the frame is empty, all currently paused commands are resumed. \ No newline at end of file diff --git a/docs/intern_commands/rm_acct.md b/docs/intern_commands/rm_acct.md new file mode 100644 index 0000000..89b0286 --- /dev/null +++ b/docs/intern_commands/rm_acct.md @@ -0,0 +1,11 @@ +### Summary ### + +delete a user account from the host database. + +### IO ### + +```[-name (text) {-force}]/[text]``` + +### Description ### + +delete the user account given in -name. this will also automatically kill all sessions currently using this user account. be very careful with this command since it's changes cannot be undone. you can use the -force option to bypass the confirmation question. \ No newline at end of file diff --git a/docs/intern_commands/rm_ban.md b/docs/intern_commands/rm_ban.md new file mode 100644 index 0000000..e533c78 --- /dev/null +++ b/docs/intern_commands/rm_ban.md @@ -0,0 +1,11 @@ +### Summary ### + +remove an ip address from the ban list. + +### IO ### + +```[-ip (text)]/[text]``` + +### Description ### + +this removes an ip address from the host ban list. nothing happens if the ip does not exists in the list. \ No newline at end of file diff --git a/docs/intern_commands/rm_cert.md b/docs/intern_commands/rm_cert.md new file mode 100644 index 0000000..d7e994f --- /dev/null +++ b/docs/intern_commands/rm_cert.md @@ -0,0 +1,11 @@ +### Summary ### + +remove the SSL/TLS cert associated with the given common name. + +### IO ### + +```[-name (text) {-force}]/[text]``` + +### Description ### + +remove the SSL/TLS certificate and private key for the common name given in -name. you can the pass the optional -force option to remove the cert without asking a confirmation question. \ No newline at end of file diff --git a/docs/intern_commands/rm_ch.md b/docs/intern_commands/rm_ch.md new file mode 100644 index 0000000..cbd22e1 --- /dev/null +++ b/docs/intern_commands/rm_ch.md @@ -0,0 +1,11 @@ +### Summary ### + +permanently remove a channel and all of it's sub-shannels from the host. + +### IO ### + +```[-ch_name (text)]/[text]``` + +### Description ### + +this will permanently remove the channel given in -ch_name and all sub-channels. only the channel owner-level(1) is allowed to do this. \ No newline at end of file diff --git a/docs/intern_commands/rm_mod.md b/docs/intern_commands/rm_mod.md new file mode 100644 index 0000000..d5cfe97 --- /dev/null +++ b/docs/intern_commands/rm_mod.md @@ -0,0 +1,11 @@ +### Summary ### + +uninstall a module from the host. + +### IO ### + +```[-name (text)]/[text]``` + +### Description ### + +this tells all sessions in the host to unload the module given in -name and all associated commands. it will also permanently delete all files associated with the module from the host. file deletion is not done instantly; instead, a specific amount of time is allotted to allow all sessions to unload the mod/clean up before it's files are deleted. also note that all associated commands are forced to terminate when the module is unloaded. \ No newline at end of file diff --git a/docs/intern_commands/rm_ranked_cmd.md b/docs/intern_commands/rm_ranked_cmd.md new file mode 100644 index 0000000..bf94213 --- /dev/null +++ b/docs/intern_commands/rm_ranked_cmd.md @@ -0,0 +1,11 @@ +### Summary ### + +remove a rank from a command object name. + +### IO ### + +```[-command (text)]/[text]``` + +### Description ### + +this will remove the command rank from the command object given in -command. any command object without an assigned perm id is assumed a rank of 1 but some exceptions apply for commands that need to be allowed for all users regardless of rank. \ No newline at end of file diff --git a/docs/intern_commands/rm_rdonly_flag.md b/docs/intern_commands/rm_rdonly_flag.md new file mode 100644 index 0000000..0984db1 --- /dev/null +++ b/docs/intern_commands/rm_rdonly_flag.md @@ -0,0 +1,11 @@ +### Summary ### + +remove a read only flag from a certain sub-channel privilege level combination. + +### IO ### + +```[-ch_name (text) -sub_id (int) -level (int)]/[text]``` + +### Description ### + +remove a read only flag the sub-channel given in -sub_id for connected users at the level given in -level. the channel given in -ch_name must already exists. with, the read only flag gone users connected to the sub-channel at the specified level would be able to cast data to the sub-channel once again. also note that this command will cause all sessions that currently have the sub-channel open to close the sub-channel. it will be up to the clients to re-open the sub-channel. \ No newline at end of file diff --git a/docs/intern_commands/rm_sub_ch.md b/docs/intern_commands/rm_sub_ch.md new file mode 100644 index 0000000..6cec3d2 --- /dev/null +++ b/docs/intern_commands/rm_sub_ch.md @@ -0,0 +1,11 @@ +### Summary ### + +remove a sub-channel within a channel. + +### IO ### + +```[-ch_name (text) -sub_name (text)]/[text]``` + +### Description ### + +remove a sub-channel given in -sub_name for the channel given in -ch_name. only the channel owner-level(1) and admin-level(2) is allowed to do this. only the channel owner-level(1) and admin-level(2) is allowed to do this. \ No newline at end of file diff --git a/docs/intern_commands/set_active_flag.md b/docs/intern_commands/set_active_flag.md new file mode 100644 index 0000000..911190f --- /dev/null +++ b/docs/intern_commands/set_active_flag.md @@ -0,0 +1,11 @@ +### Summary ### + +set or unset the active update flag of a sub-channel. + +### IO ### + +```[-ch_name (text) -sub_name (text) -state (1 or 0)]/[text]``` + +### Description ### + +this sets the active update flag on the channel given in -ch_name true or false based on the value given in -state. the active update flag allow the sessions to send PEER_INFO or PEER_STAT frames to the clients when a peer connected to the channel changes information like it's user name, group name, display name, disconnect etc...this flag is ignored if the host have the global active update flag set. if that's the case, all channels will active update. the channel owner-level(1) and admin-level(2) is allowed to do this. \ No newline at end of file diff --git a/docs/intern_commands/set_disp_name.md b/docs/intern_commands/set_disp_name.md new file mode 100644 index 0000000..d5f5d3c --- /dev/null +++ b/docs/intern_commands/set_disp_name.md @@ -0,0 +1,11 @@ +### Summary ### + +change your account display name. + +### IO ### + +```[-name (text)]/[text]``` + +### Description ### + +this changes the display name on your own account to the name given in -name. the display name is used by some clients to present your account to other clients instead of showing your real user name. it is not restricted by uniqueness and can be any text that is less than or equal to 32 chars. \ No newline at end of file diff --git a/docs/intern_commands/set_email.md b/docs/intern_commands/set_email.md new file mode 100644 index 0000000..e0305ff --- /dev/null +++ b/docs/intern_commands/set_email.md @@ -0,0 +1,11 @@ +### Summary ### + +change the user account email address. + +### IO ### + +```[-new_email (text)]/[text]``` + +### Description ### + +change the email address on your own account to the email adress given in -new_email. \ No newline at end of file diff --git a/docs/intern_commands/set_email_template.md b/docs/intern_commands/set_email_template.md new file mode 100644 index 0000000..0d1528c --- /dev/null +++ b/docs/intern_commands/set_email_template.md @@ -0,0 +1,22 @@ +### 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. if the body or subject contains single quotes ' ' escape them with a \. \ No newline at end of file diff --git a/docs/intern_commands/set_group.md b/docs/intern_commands/set_group.md new file mode 100644 index 0000000..de2080d --- /dev/null +++ b/docs/intern_commands/set_group.md @@ -0,0 +1,11 @@ +### Summary ### + +change a user account's group. + +### IO ### + +```[-user (text) -group (text)]/[text]``` + +### Description ### + +this changes the group of the user account given in -user to the target group given in -group. \ No newline at end of file diff --git a/docs/intern_commands/set_group_rank.md b/docs/intern_commands/set_group_rank.md new file mode 100644 index 0000000..f4f074b --- /dev/null +++ b/docs/intern_commands/set_group_rank.md @@ -0,0 +1,11 @@ +### Summary ### + +set the host rank of a group. + +### IO ### + +```[-name (text) -rank (int)]/[text]``` + +### Description ### + +set the host rank for the group name given in -name to the rank given in -rank. the host rank is used throughout this application to determine how much access to the host commands each user attached to the group has. the lower the numeric value of the host rank, the higher the level of access to the host the group has (1 being the highest level of access). you cannot change the group of a group that has a higher rank than your own group. \ No newline at end of file diff --git a/docs/intern_commands/set_member_level.md b/docs/intern_commands/set_member_level.md new file mode 100644 index 0000000..5e41b40 --- /dev/null +++ b/docs/intern_commands/set_member_level.md @@ -0,0 +1,41 @@ +### Summary ### + +set the user privilege levels of a channel member. (lower the value, the higher the privilege) + +### IO ### + +```[-ch_name (text) -user (text) -level (int)]/[text]``` + +### Description ### + +set the privilege level of the channel member given in -user for the channel given in -ch_name to the level given in -level. a valid privilege level is an integer between 1-4 representing owner-level(1), admin-level(2), officer-level(3) and regular-level(4). the channel owner-level(1) is reserved for just 1 member in the channel and only that member can assign another member in the channel that privilege level. when the owner privilege is assigned to another member, the current owner will step down to a admin. this command is restricted based on your current level so you cannot assign a level higher that you own. below is a description of what each of these levels can do within the channel: + +owner-level(1): +1. delete or rename the channel. +2. delete, create or rename sub-channels within the channel. +3. invite new users to the channel or cancel invites. +4. remove any member of the channel except your self. +5. set sub-channels' level of access. +6. add/remove read only flags to/from sub-channels. +7. update the privilege level of any member in the channel. +8. open/close sub-channels for casting. + +admin-level(2): +1. delete, create or rename sub-channels within the channel. +2. invite new users to the channel or cancel invites. +3. remove any member of channel except the owner and other admins. +4. set sub-channels' level of access. +5. add/remove read only flags to/from sub-channels. +6. update the privilege level of members up to your own level but not above. +7. open/close sub-channels for casting. + +officer-level(3): +1. invite new users to the channel or cancel invites. +2. can only remove regular members of the channel. +3. update the privilege level of members up to your own level but not above. +4. open/close sub-channels for casting. + +regular-level(4): +1. open/close sub-channels for casting. + +note: this command causes all sessions that are loggined in as this user to close all related sub-channels. it will be up to the client to re-open the sub-channels if the user still have access to them. \ No newline at end of file diff --git a/docs/intern_commands/set_pw.md b/docs/intern_commands/set_pw.md new file mode 100644 index 0000000..a801ae2 --- /dev/null +++ b/docs/intern_commands/set_pw.md @@ -0,0 +1,11 @@ +### Summary ### + +change your account password. + +### IO ### + +```[password]/[text]``` + +### Description ### + +this changes the password on your own account. this command will instantly hook the input and await a new password to be entered. \ No newline at end of file diff --git a/docs/intern_commands/set_sub_ch_level.md b/docs/intern_commands/set_sub_ch_level.md new file mode 100644 index 0000000..0314002 --- /dev/null +++ b/docs/intern_commands/set_sub_ch_level.md @@ -0,0 +1,11 @@ +### Summary ### + +set the lowest privilege level that members need to be in order to open a certain sub-channel. + +### IO ### + +```[-ch_name (text) -sub_name (text) -level (int)]/[text]``` + +### Description ### + +this command makes it possible so set minimum privilege levels to open the sub-channel given in -sub_name to the level given in -level for the channel given in -ch_name. a valid level is an integer between 1-5 representing owner-level(1), admin-level(2), officer-level(3), regular-level(4) and public-level(5). for example, you could set a sub-channel's minimum level to 4 to make it so only channel regular members and above can open/close the sub-channel or you can set it to 5 to make it so anybody can open/close the sub-channel, affectively making it a public sub-channel. only the channel owner or admin(s) are allowed to do this. also note that this command will cause all sessions that currently have the sub-channel open to close the sub-channel. it will be up to the client to re-open the sub-channel if it's user account still have access to it. \ No newline at end of file diff --git a/docs/intern_commands/set_user_name.md b/docs/intern_commands/set_user_name.md new file mode 100644 index 0000000..57984b1 --- /dev/null +++ b/docs/intern_commands/set_user_name.md @@ -0,0 +1,11 @@ +### Summary ### + +change your account user name. + +### IO ### + +```[-new_name (text)]/[text]``` + +### Description ### + +this changes the user name on your own account to the new user name given in -new_name. \ No newline at end of file diff --git a/docs/intern_commands/term.md b/docs/intern_commands/term.md new file mode 100644 index 0000000..9eba129 --- /dev/null +++ b/docs/intern_commands/term.md @@ -0,0 +1,11 @@ +### Summary ### + +terminate the current task that the command is running. + +### IO ### + +```[{CMD_ID}]/[text]``` + +### Description ### + +this terminates the command given by a CMD_ID frame sent into this command. if the frame is empty, this command terminates all commands. this command doesn't care if the target command object itself is in looping and/or more input states. \ No newline at end of file diff --git a/docs/intern_commands/to_peer.md b/docs/intern_commands/to_peer.md new file mode 100644 index 0000000..f0ecbad --- /dev/null +++ b/docs/intern_commands/to_peer.md @@ -0,0 +1,11 @@ +### Summary ### + +send/receive any data directly with a client connected to the host that has accepted your p2p request or the peer's p2p request. + +### IO ### + +```[(SESSION_ID)any]/[none]``` + +### Description ### + +send any type of data directly to a peer client connected to the host that has accepted your p2p request. you must prepend the data sent into this command with the 224bit hash the peer's session id. an error is called out if the peer session has not accepted your p2p request. \ No newline at end of file diff --git a/docs/intern_commands/trans_group.md b/docs/intern_commands/trans_group.md new file mode 100644 index 0000000..406b266 --- /dev/null +++ b/docs/intern_commands/trans_group.md @@ -0,0 +1,11 @@ +### Summary ### + +transfer all user accounts from one group to another. + +### IO ### + +```[-src (text) -dst (text)]/[text]``` + +### Description ### + +transfer all user accounts currently attached to the group given in -src to the group given in -dst. you must have a higher rank than both groups for this to work. \ No newline at end of file diff --git a/docs/intern_commands/verify_email.md b/docs/intern_commands/verify_email.md new file mode 100644 index 0000000..572bcc8 --- /dev/null +++ b/docs/intern_commands/verify_email.md @@ -0,0 +1,11 @@ +### Summary ### + +verify your email address by sending a confirmation code to it. + +### IO ### + +```[text]/[text]``` + +### Description ### + +send a confirmation code to the email address of your currently logged in account and await input of that confirmation code. note: the code automatically invalidates if the command is terminated without entering the confirmation code. \ No newline at end of file diff --git a/linux_build.sh b/linux_build.sh new file mode 100644 index 0000000..573de8c --- /dev/null +++ b/linux_build.sh @@ -0,0 +1,125 @@ +#!/bin/sh + +qt_dir="$1" +installer_file="$2" + +src_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +bin_name="mrci" +app_version="1.0.0" +app_name="MRCI" +install_dir="/opt/$bin_name" +bin_dir="/usr/bin" +tmp_dir="$HOME/.cache/mrci_build" +user="$USER" + +if [ "$qt_dir" != "" ]; then + + PATH=$qt_dir:$PATH + +fi + +if [ "$installer_file" = "" ]; then + + installer_file="$src_dir/$bin_name-$app_version.run" + +fi + +if [ -d "$tmp_dir" ]; then + + rm -rfv $tmp_dir + +fi + +mkdir -vp $tmp_dir +cp -rv $src_dir/. $tmp_dir + +if [ $? -eq 0 ]; then + + cd $tmp_dir + + qmake -config release + + if [ $? -eq 0 ]; then + + make + + if [ $? -eq 0 ]; then + + mkdir -v ./build/ + mkdir -v ./build/sqldrivers + mkdir -v ./build/lib + ldd ./$bin_name | grep "=> /" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./build/lib + mv -v ./$bin_name ./build/$bin_name + cp -v $qt_dir/../plugins/sqldrivers/libqsqlite.so ./build/sqldrivers + + startup_script="./build/$bin_name.sh" + setup_script="./build/setup.sh" + uninstall_script="./build/uninstall.sh" + service_file="./build/$bin_name.service" + + echo "#!/bin/sh" > $startup_script + echo "export LD_LIBRARY_PATH=$install_dir/lib" >> $startup_script + echo "$install_dir/$bin_name \$1 \$2 \$3 \$4 \$5 \$6 \$7 \$8 \$9 \$10" >> $startup_script + + echo "#!/bin/sh" > $setup_script + echo "if [ -f \"$install_dir/uninstall.sh\" ]; then" >> $setup_script + echo " sh $install_dir/uninstall.sh" >> $setup_script + echo "fi" >> $setup_script + echo "if [ ! -d \"$install_dir\" ]; then" >> $setup_script + echo " sudo mkdir -p $install_dir" >> $setup_script + echo "fi" >> $setup_script + echo "sudo cp -rfv ./lib $install_dir" >> $setup_script + echo "sudo cp -rfv ./sqldrivers $install_dir" >> $setup_script + echo "sudo cp -fv ./$bin_name $install_dir" >> $setup_script + echo "sudo cp -fv ./$bin_name.sh $install_dir" >> $setup_script + echo "sudo cp -fv ./uninstall.sh $install_dir" >> $setup_script + echo "sudo cp -fv ./$bin_name.service /etc/systemd/system/$bin_name@$USER.service" >> $setup_script + echo "sudo chmod 755 $install_dir/$bin_name" >> $setup_script + echo "sudo chmod 755 $install_dir/$bin_name.sh" >> $setup_script + echo "sudo chmod 755 $install_dir/uninstall.sh" >> $setup_script + echo "sudo chmod 755 $install_dir" >> $setup_script + echo "sudo chmod -R 755 $install_dir/lib" >> $setup_script + echo "sudo chmod -R 755 $install_dir/sqldrivers" >> $setup_script + echo "sudo chmod 755 /etc/systemd/system/$bin_name@$USER.service" >> $setup_script + echo "sudo ln -sf $install_dir/$bin_name.sh $bin_dir/$bin_name" >> $setup_script + echo "cd $install_dir/lib" >> $setup_script + echo "ldd $install_dir/$bin_name | grep \"=> /\" | awk '{if (\$3 != \"not found\") print \$1}' | xargs -I '{}' sudo rm -f '{}'" >> $setup_script + echo "sudo systemctl start $bin_name@$USER" >> $setup_script + echo "sudo systemctl enable $bin_name@$USER" >> $setup_script + echo "echo \"\nInstallation finished. If you ever need to uninstall this application, run this command:\n\"" >> $setup_script + echo "echo \" sh $install_dir/uninstall.sh\n\"" >> $setup_script + + echo "[Unit]" > $service_file + echo "Description=$app_name host" >> $service_file + echo "After=network.target" >> $service_file + echo "" >> $service_file + echo "[Service]" >> $service_file + echo "Type=simple" >> $service_file + echo "User=%i" >> $service_file + echo "ExecStart=/usr/bin/env $bin_name -host" >> $service_file + echo "" >> $service_file + echo "[Install]" >> $service_file + echo "WantedBy=multi-user.target" >> $service_file + + echo "#!/bin/sh" > $uninstall_script + echo "sudo systemctl -q stop $bin_name@$USER" >> $uninstall_script + echo "sudo systemctl -q disable $bin_name@$USER" >> $uninstall_script + echo "sudo rm -v /etc/systemd/system/$bin_name@$USER.service" >> $uninstall_script + echo "sudo rm -v $bin_dir/$bin_name" >> $uninstall_script + echo "sudo rm -rv $install_dir" >> $uninstall_script + + chmod +x $setup_script + + makeself ./build $installer_file "$app_name Installation" ./setup.sh + + fi + + fi + +fi + +if [ -d "$tmp_dir" ]; then + + rm -rf $tmp_dir + +fi diff --git a/modules/Tester/Tester.pro b/modules/Tester/Tester.pro new file mode 100644 index 0000000..cdcfa17 --- /dev/null +++ b/modules/Tester/Tester.pro @@ -0,0 +1,34 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2017-06-26T15:36:12 +# +# This file is part of MCI_Host. +# +# MCI_Host 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. +# +# MCI_Host 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 MCI_Host under the GPL.txt file. If not, see +# . +# +#------------------------------------------------- + +QT -= gui + +TARGET = ModTester +TEMPLATE = lib + +SOURCES += \ + command.cpp \ + main.cpp + +HEADERS += \ + command.h \ + main.h diff --git a/modules/Tester/command.cpp b/modules/Tester/command.cpp new file mode 100644 index 0000000..aec6ebc --- /dev/null +++ b/modules/Tester/command.cpp @@ -0,0 +1,70 @@ +#include "command.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 +// . + +SharedObjs::SharedObjs(QObject *parent) : QObject(parent) +{ + p2pAccepted = nullptr; + p2pPending = nullptr; + chIds = nullptr; + wrAbleChIds = nullptr; + chList = nullptr; + activeUpdate = nullptr; + chOwnerOverride = nullptr; + sessionAddr = nullptr; + userName = nullptr; + groupName = nullptr; + displayName = nullptr; + appName = nullptr; + clientMajor = nullptr; + clientMinor = nullptr; + clientPatch = nullptr; + sessionId = nullptr; + userId = nullptr; + moreInputCmds = nullptr; + activeLoopCmds = nullptr; + pausedCmds = nullptr; + hostRank = nullptr; + cmdNames = nullptr; +} + +bool ExternCommand::errState() +{ + return errSent; +} + +void ExternCommand::mainTxt(const QString &txt) +{ + emit dataToClient(QTextCodec::codecForName(TXT_CODEC)->fromUnicode(txt).mid(2), TEXT); +} + +void ExternCommand::errTxt(const QString &txt) +{ + errSent = true; + + emit dataToClient(QTextCodec::codecForName(TXT_CODEC)->fromUnicode(txt).mid(2), ERR); +} + +void ExternCommand::privTxt(const QString &txt) +{ + emit dataToClient(QTextCodec::codecForName(TXT_CODEC)->fromUnicode(txt).mid(2), PRIV_TEXT); +} + +void ExternCommand::bigTxt(const QString &txt) +{ + emit dataToClient(QTextCodec::codecForName(TXT_CODEC)->fromUnicode(txt).mid(2), BIG_TEXT); +} diff --git a/modules/Tester/command.h b/modules/Tester/command.h new file mode 100644 index 0000000..b8a93ca --- /dev/null +++ b/modules/Tester/command.h @@ -0,0 +1,182 @@ +#ifndef EXTERN_COMMAND_H +#define EXTERN_COMMAND_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 +// . + +#define TXT_CODEC "UTF-16LE" +#define TXT_CODEC_BITS 16 +#define MOD_LOADER_IID "MCRI.host.module" + +#include +#include +#include +#include +#include + +enum TypeID +{ + GEN_FILE = 30, + TEXT = 31, + ERR = 32, + PRIV_TEXT = 33, + IDLE = 34, + HOST_CERT = 35, + FILE_INFO = 36, + PEER_INFO = 37, + MY_INFO = 38, + PEER_STAT = 39, + P2P_REQUEST = 40, + P2P_CLOSE = 41, + P2P_OPEN = 42, + BYTES = 43, + SESSION_ID = 44, + NEW_CMD = 45, + CMD_ID = 46, + BIG_TEXT = 47 +}; + +enum ChannelMemberLevel +{ + OWNER = 1, + ADMIN = 2, + OFFICER = 3, + REGULAR = 4, + PUBLIC = 5 +}; + +class ExternCommand; + +class SharedObjs : public QObject +{ + Q_OBJECT + +public: + + const QHash *cmdNames; + const QList *chList; + const QList *p2pAccepted; + const QList *p2pPending; + const QList *moreInputCmds; + const QList *activeLoopCmds; + const QList *pausedCmds; + const QString *sessionAddr; + const QString *userName; + const QString *groupName; + const QString *displayName; + const QString *appName; + const ushort *clientMajor; + const ushort *clientMinor; + const ushort *clientPatch; + const QByteArray *chIds; + const QByteArray *wrAbleChIds; + const QByteArray *sessionId; + const QByteArray *userId; + const bool *activeUpdate; + const bool *chOwnerOverride; + const uint *hostRank; + + explicit SharedObjs(QObject *parent = nullptr); +}; + +class ExternCommand : public QObject +{ + Q_OBJECT + +protected: + + void mainTxt(const QString &txt); + void errTxt(const QString &txt); + void privTxt(const QString &txt); + void bigTxt(const QString &txt); + +public: + + explicit ExternCommand(QObject *parent = nullptr) : QObject(parent) {} + + virtual ~ExternCommand() {} + + virtual void procBin(const SharedObjs *, const QByteArray &, uchar) {} + virtual void aboutToDelete() {} + virtual void term() {} + virtual bool handlesGenfile() {return false;} + virtual bool errState(); + virtual QString shortText() {return "";} + virtual QString ioText() {return "";} + virtual QString longText() {return "";} + virtual QString libText() {return "";} + virtual QStringList internRequest() {return QStringList();} + + QHash internCommands; + quint16 cmdId; + bool errSent; + bool inLoopMode; + bool inMoreInputMode; + +signals: + + void dataToClient(const QByteArray &data, uchar typeId = TEXT); + void castToPeers(const QByteArray &data, uchar typeId = TEXT); + void toPeer(const QByteArray &dst, const QByteArray &data, uchar typeId = TEXT); + void closeChByName(const QString &ch, const QString &sub); + void closeChById(quint64 id, uchar subId); + void openChByName(const QString &ch, const QString &sub); + void openChById(quint64 id, uchar subId); + void enableLoop(bool state); + void enableMoreInput(bool state); + void closeSession(); + void cmdFinished(); + void logout(); +}; + +class CommandLoader : public QObject +{ + Q_OBJECT + +public: + + explicit CommandLoader(QObject *parent = nullptr) : QObject(parent) {} + + virtual ~CommandLoader() {} + + virtual QStringList pubCmdList() {return QStringList();} + virtual QStringList cmdList() {return QStringList();} + virtual QStringList rankExemptList() {return QStringList();} + virtual ExternCommand *cmdObj(const QString &) {return nullptr;} +}; + +class ModCommandLoader : public CommandLoader +{ + Q_OBJECT + +public: + + explicit ModCommandLoader(QObject *parent = nullptr) : CommandLoader(parent) {} + + virtual ~ModCommandLoader() {} + + virtual void modPath(const QString &) {} + virtual void aboutToDelete() {} + virtual bool hostRevOk(quint64) {return false;} + virtual QString lastError() {return "";} + virtual quint64 rev() {return 0;} +}; + +QT_BEGIN_NAMESPACE +Q_DECLARE_INTERFACE(ModCommandLoader, MOD_LOADER_IID) +QT_END_NAMESPACE + +#endif // EXTERN_COMMAND_H diff --git a/modules/Tester/main.cpp b/modules/Tester/main.cpp new file mode 100644 index 0000000..5a91ca7 --- /dev/null +++ b/modules/Tester/main.cpp @@ -0,0 +1,220 @@ +#include "main.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 +// . + +QString libName() +{ + return QString(LIB_NAME) + "_" + QString(LIB_VERSION); +} + +Loader::Loader(QObject *parent) : ModCommandLoader(parent) +{ +} + +bool Loader::hostRevOk(quint64 minRev) +{ + return minRev >= IMPORT_REV; +} + +quint64 Loader::rev() +{ + return IMPORT_REV; +} + +QStringList Loader::cmdList() +{ + return QStringList() << "test_text" << "test_input" << "test_loop" << "test_inherit"; +} + +ExternCommand *Loader::cmdObj(const QString &name) +{ + ExternCommand *ret = nullptr; + + if (name == "test_text") ret = new ModText(this); + else if (name == "test_input") ret = new ModInput(this); + else if (name == "test_loop") ret = new ModLoop(this); + else if (name == "test_inherit") ret = new ModInherit(this); + + return ret; +} + +ModText::ModText(QObject *parent) : ExternCommand(parent) {} + +QString ModText::shortText() {return "test module text output.";} +QString ModText::ioText() {return "[none]/[text]";} +QString ModText::longText() {return "this test the module interface text output. input data is ignored.";} +QString ModText::libText() {return libName();} + +ModInput::ModInput(QObject *parent) : ExternCommand(parent) {} + +QString ModInput::shortText() {return "test module input hook.";} +QString ModInput::ioText() {return "[text]/[text]";} +QString ModInput::longText() {return "this command will ask you to enter Yes or No and will not release until a valid response is entered. this demonstrates how to impliment a confirmation question using the more input mode.";} +QString ModInput::libText() {return libName();} + +ModLoop::ModLoop(QObject *parent) : ExternCommand(parent) {index = 0;} + +QString ModLoop::shortText() {return "test module looping command.";} +QString ModLoop::ioText() {return "[none]/[text]";} +QString ModLoop::longText() {return "this command will display 'loop' along with the loop number 10 times to demonstrate looping.";} +QString ModLoop::libText() {return libName();} + +ModInherit::ModInherit(QObject *parent) : ExternCommand(parent) {} + +QString ModInherit::shortText() {return "module internal command inheritance test.";} +QString ModInherit::ioText() {return "[text]/[text]";} +QString ModInherit::longText() {return "this command will run the output of the known internal command 'my_info' to demonstrate internal command inheritance.";} +QString ModInherit::libText() {return libName();} + +void ModText::procBin(const SharedObjs *sharedObjs, const QByteArray &data, uchar dType) +{ + Q_UNUSED(data) + Q_UNUSED(dType) + Q_UNUSED(sharedObjs) + + // mainTxt() is convenience function that sends a TEXT frame to the CmdExecutor + // object to be processed by the host. it's basically a short hand for the emit + // call: + + // emit dataToClient(toTEXT("some text\n"), TEXT); + + // errTxt() does the same thing but indicates to the client that it is a error + // message so it can be displayed in a different color, size, logged etc.. + // depending on the client. + + // emit dataToClient(toTEXT("some error\n"), ERR); + + // privTxt() this also does that same thing except it indicates to the client + // that the command is asking for private data like a password, SSN, pin number + // etc.. clients that get this indicator should not echo or display the next + // data frame to be sent back to the host. + + // emit dataToClient(toTEXT("enter your password: "), PRIV); + + mainTxt("Main text out from module: " + QString(LIB_NAME) + "\n"); + errTxt("Error text out from module: " + QString(LIB_NAME) + "\n"); +} + +void ModInput::procBin(const SharedObjs *sharedObjs, const QByteArray &data, uchar dType) +{ + Q_UNUSED(sharedObjs) + + if (dType == TEXT) + { + QString text = QTextCodec::codecForName(TXT_CODEC)->toUnicode(data); + + if (inMoreInputMode) + { + if (text.isEmpty()) + { + errTxt("err: You entered nothing.\n"); + } + else + { + if ((text.toLower() == "yes") || (text.toLower() == "no")) + { + // setting moreInput false tells the host that the command + // is no longer asking for more input from the client so the + // host will consider the command finished at this point. + + emit enableMoreInput(false); + + mainTxt("You entered: '" + text + "' Good bye.\n"); + } + else + { + errTxt("err: Invalid response.\n"); + } + } + } + else + { + // setting moreInput true tells the host that the command is not + // finished and is awaiting more input from the client. you don't + // need to reimplement term() if all it takes for the command to + // finish its task is to set moreInput and/or loop false; the + // host will do that externally. + + emit enableMoreInput(true); + + mainTxt("Please enter Yes or No: "); + } + } +} + +void ModLoop::term() +{ + // this function is called by the host to terminate the command if + // termination is requested and only if the more input or loop modes + // are active. + + // this command has a variable called 'index' that the host is not aware + // of and does not have access to it so in this case, index needs reset + // to 0 when the command is requested to terminate. + + index = 0; +} + +void ModLoop::procBin(const SharedObjs *sharedObjs, const QByteArray &data, uchar dType) +{ + Q_UNUSED(data) + Q_UNUSED(dType) + Q_UNUSED(sharedObjs) + + // the host will constantly call this function for as long as loop + // mode remains true or if requested to terminate. keep in mind that + // in all subsequent calls after the initial call, the input data will + // always be empty and dType will always default to TEXT. + + if (inLoopMode) + { + mainTxt("Loop: " + QString::number(index++) + "\n"); + + if (index == 10) + { + emit enableLoop(false); + + index = 0; + } + } + else + { + emit enableLoop(true); + } +} + +QStringList ModInherit::internRequest() +{ + return QStringList() << "my_info"; +} + +void ModInherit::procBin(const SharedObjs *sharedObjs, const QByteArray &data, uchar dType) +{ + Q_UNUSED(data) + Q_UNUSED(dType) + + if (!internCommands.contains("my_info")) + { + errTxt("err: This command object did not successfully inherit 'my_info' unable to continue.\n"); + } + else + { + mainTxt("If inherited correctly, the output of 'my_info' should show below:\n\n"); + + internCommands["my_info"]->procBin(sharedObjs, QByteArray(), TEXT); + } +} diff --git a/modules/Tester/main.h b/modules/Tester/main.h new file mode 100644 index 0000000..962631b --- /dev/null +++ b/modules/Tester/main.h @@ -0,0 +1,131 @@ +#ifndef MOD_TESTER_H +#define MOD_TESTER_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 +// . + +#include +#include +#include + +#include "command.h" + +#define IMPORT_REV 1 + +// the import revision is a module compatibility version number +// used by the host to determine if it can successfully load and +// run this library or not. as of right now, the host supports rev1 +// and up. + +#define LIB_VERSION "1.0.0" +#define LIB_NAME "MRCITestMod" + +// the versioning system for the library itself can be completely +// different from the host import revision. + +QString libName(); + +class Loader : public ModCommandLoader +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "MRCI.host.module") + Q_INTERFACES(ModCommandLoader) + +public: + + bool hostRevOk(quint64 minRev); + quint64 rev(); + ExternCommand *cmdObj(const QString &name); + QStringList cmdList(); + + explicit Loader(QObject *parent = nullptr); +}; + +//----------------- + +class ModText : public ExternCommand +{ + Q_OBJECT + +public: + + explicit ModText(QObject *parent = nullptr); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &data, uchar dType); + QString shortText(); + QString ioText(); + QString longText(); + QString libText(); +}; + +//-------------------- + +class ModInput : public ExternCommand +{ + Q_OBJECT + +public: + + explicit ModInput(QObject *parent = nullptr); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &data, uchar dType); + QString shortText(); + QString ioText(); + QString longText(); + QString libText(); +}; + +//----------------------- + +class ModLoop : public ExternCommand +{ + Q_OBJECT + +private: + + int index; + +public: + + explicit ModLoop(QObject *parent = nullptr); + + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &data, uchar dType); + QString shortText(); + QString ioText(); + QString longText(); + QString libText(); +}; + +//-------------------------- + +class ModInherit : public ExternCommand +{ + Q_OBJECT + +public: + + explicit ModInherit(QObject *parent = nullptr); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &data, uchar dType); + QString shortText(); + QString ioText(); + QString longText(); + QString libText(); + QStringList internRequest(); +}; + +#endif // MOD_TESTER_H diff --git a/modules/Tester/tester.pro b/modules/Tester/tester.pro new file mode 100644 index 0000000..cdcfa17 --- /dev/null +++ b/modules/Tester/tester.pro @@ -0,0 +1,34 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2017-06-26T15:36:12 +# +# This file is part of MCI_Host. +# +# MCI_Host 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. +# +# MCI_Host 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 MCI_Host under the GPL.txt file. If not, see +# . +# +#------------------------------------------------- + +QT -= gui + +TARGET = ModTester +TEMPLATE = lib + +SOURCES += \ + command.cpp \ + main.cpp + +HEADERS += \ + command.h \ + main.h diff --git a/src/cmd_executor.cpp b/src/cmd_executor.cpp new file mode 100644 index 0000000..a69c04a --- /dev/null +++ b/src/cmd_executor.cpp @@ -0,0 +1,827 @@ +#include "cmd_executor.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 +// . + +CmdExecutor::CmdExecutor(RWSharedObjs *rwShare, SharedObjs *rdOnlyShare, QSharedMemory *debugInfo, QObject *parent) : QObject(parent) +{ + rwShare->commands = &commands; + rwShare->activeLoopCmds = &activeLoopCmds; + rwShare->pausedCmds = &pausedCmds; + rwShare->moreInputCmds = &moreInputCmds; + rwShare->cmdNames = &cmdNames; + + rdOnlyShare->activeLoopCmds = &activeLoopCmds; + rdOnlyShare->pausedCmds = &pausedCmds; + rdOnlyShare->moreInputCmds = &moreInputCmds; + rdOnlyShare->cmdNames = &cmdNames; + + loopIndex = 0; + exeDebugInfo = debugInfo; + rdSharedObjs = rdOnlyShare; + rwSharedObjs = rwShare; + internalCmds = new InternalCommandLoader(rwShare, this); + + connect(this, &CmdExecutor::loop, this, &CmdExecutor::exeCmd); +} + +void CmdExecutor::wrCrashDebugInfo(const QString &msg) +{ + if (exeDebugInfo->isAttached()) + { + exeDebugInfo->lock(); + + QByteArray data = toTEXT(msg).leftJustified(EXE_DEBUG_INFO_SIZE, static_cast(0), true); + + memcpy(exeDebugInfo->data(), data.data(), EXE_DEBUG_INFO_SIZE); + + exeDebugInfo->unlock(); + } +} + +void CmdExecutor::connectExternCmd(ExternCommand *cmd, quint16 cmdId, const QString &cmdName) +{ + wrCrashDebugInfo(" exe func: connectExternCmd()\n cmd id: " + QString::number(cmdId) + "\n cmd name: " + cmdName); + + auto *cmdOutput = new CommandOutput(cmd); + + connect(cmdOutput, &CommandOutput::dataOut, this, &CmdExecutor::externDataToIPC); + + connectCommon(cmd, cmdOutput, cmdId, cmdName); +} + +void CmdExecutor::connectInternCmd(InternCommand *cmd, quint16 cmdId, const QString &cmdName) +{ + wrCrashDebugInfo(" exe func: connectInternCmd()\n cmd id: " + QString::number(cmdId) + "\n cmd name: " + cmdName); + + auto *cmdOutput = new CommandOutput(cmd); + + connect(cmdOutput, &CommandOutput::dataOut, this, &CmdExecutor::internDataToIPC); + + connect(cmd, &InternCommand::backendDataOut, this, &CmdExecutor::backendFromCmd); + connect(cmd, &InternCommand::authOk, this, &CmdExecutor::authOk); + connect(cmd, &InternCommand::termAllCommands, this, &CmdExecutor::termAllCommands); + connect(cmd, &InternCommand::termCommandId, this, &CmdExecutor::termCommandId); + connect(cmd, &InternCommand::loadMod, this, &CmdExecutor::loadModFile); + connect(cmd, &InternCommand::unloadMod, this, &CmdExecutor::unloadModFile); + connect(cmd, &InternCommand::reloadCommands, this, &CmdExecutor::buildCommands); + + connectCommon(cmd, cmdOutput, cmdId, cmdName); +} + +void CmdExecutor::connectCommon(ExternCommand *cmd, CommandOutput *cmdOutput, quint16 cmdId, const QString &cmdName) +{ + wrCrashDebugInfo(" exe func: connectCommon()\n cmd id: " + QString::number(cmdId) + "\n cmd name: " + cmdName); + + connect(cmdOutput, &CommandOutput::closeChById, this, &CmdExecutor::closeChById); + connect(cmdOutput, &CommandOutput::openChById, this, &CmdExecutor::openChById); + connect(cmdOutput, &CommandOutput::closeChByName, this, &CmdExecutor::closeChByName); + connect(cmdOutput, &CommandOutput::openChByName, this, &CmdExecutor::openChByName); + connect(cmdOutput, &CommandOutput::cmdFinished, this, &CmdExecutor::commandFinished); + connect(cmdOutput, &CommandOutput::enableLoop, this, &CmdExecutor::enableLoop); + connect(cmdOutput, &CommandOutput::enableMoreInput, this, &CmdExecutor::enableMoreInput); + + connect(cmd, &ExternCommand::closeChById, cmdOutput, &CommandOutput::closeChIdFromCmdObj); + connect(cmd, &ExternCommand::openChById, cmdOutput, &CommandOutput::openChIdFromCmdObj); + connect(cmd, &ExternCommand::closeChByName, cmdOutput, &CommandOutput::closeChNameFromCmdObj); + connect(cmd, &ExternCommand::openChByName, cmdOutput, &CommandOutput::openChNameFromCmdObj); + connect(cmd, &ExternCommand::cmdFinished, cmdOutput, &CommandOutput::finished); + connect(cmd, &ExternCommand::enableLoop, cmdOutput, &CommandOutput::enableLoopFromCmdObj); + connect(cmd, &ExternCommand::enableMoreInput, cmdOutput, &CommandOutput::enableMoreInputFromCmdObj); + connect(cmd, &ExternCommand::dataToClient, cmdOutput, &CommandOutput::dataFromCmdObj); + + connect(cmd, &ExternCommand::castToPeers, this, &CmdExecutor::castToPeers); + connect(cmd, &ExternCommand::toPeer, this, &CmdExecutor::toPeer); + connect(cmd, &ExternCommand::closeSession, this, &CmdExecutor::endSession); + connect(cmd, &ExternCommand::logout, this, &CmdExecutor::logout); + + cmd->cmdId = cmdId; + + cmd->setObjectName(cmdName); + cmdOutput->setCmdId(cmdId); +} + +void CmdExecutor::enableLoop(quint16 cmdId, bool state) +{ + if (state) + { + uniqueAdd(cmdId, activeLoopCmds); + } + else + { + activeLoopCmds.removeAll(cmdId); + } +} + +void CmdExecutor::enableMoreInput(quint16 cmdId, bool state) +{ + if (state) + { + uniqueAdd(cmdId, moreInputCmds); + } + else + { + moreInputCmds.removeAll(cmdId); + } +} + +void CmdExecutor::preExe(ExternCommand *cmdObj, quint16 cmdId) +{ + cmdObj->cmdId = cmdId; + cmdObj->errSent = false; +} + +void CmdExecutor::preExe(const QList &cmdObjs, quint16 cmdId) +{ + for (auto cmdObj : cmdObjs) + { + preExe(cmdObj, cmdId); + } +} + +void CmdExecutor::exeCmd(quint16 cmdId, const QByteArray &data, uchar typeId) +{ + wrCrashDebugInfo(" exe func: exeCmd()\n cmd id: " + QString::number(cmdId) + "\n type id: " + QString::number(typeId)); + + if (!commands.contains(cmdId)) + { + emit dataToSession(cmdId, toTEXT("err: The requested command id: '" + QString::number(cmdId) + "' does not exists.\n"), ERR); + emit dataToSession(cmdId, QByteArray(), IDLE); + } + else if (!pausedCmds.contains(cmdId)) + { + ExternCommand *cmdObj = commands[cmdId]; + + preExe(cmdObj, cmdId); + preExe(cmdObj->internCommands.values(), cmdId); + + cmdObj->procBin(rdSharedObjs, data, typeId); + + if (!activeLoopCmds.contains(cmdId) && !moreInputCmds.contains(cmdId)) + { + emit cmdObj->cmdFinished(); + } + + nextLoopCmd(); + } +} + +void CmdExecutor::nextLoopCmd() +{ + if (!activeLoopCmds.isEmpty()) + { + if (loopIndex == activeLoopCmds.size()) + { + loopIndex = 0; + } + + wrCrashDebugInfo(" exe func: nextLoopCmd()\n loop index: " + QString::number(loopIndex)); + + emit loop(activeLoopCmds[loopIndex++], QByteArray(), TEXT); + } +} + +void CmdExecutor::termCommandObj(quint16 cmdId, ExternCommand *cmd, bool del) +{ + wrCrashDebugInfo(" exe func: termCommandObj()\n cmd id: " + QString::number(cmdId)); + + if (moreInputCmds.contains(cmdId) || activeLoopCmds.contains(cmdId)) + { + cmd->term(); + + cmd->inLoopMode = false; + cmd->inMoreInputMode = false; + + for (auto internObj : cmd->internCommands.values()) + { + if (internObj->inLoopMode || internObj->inMoreInputMode) + { + internObj->term(); + + internObj->inLoopMode = false; + internObj->inMoreInputMode = false; + } + } + + emit dataToSession(cmdId, QByteArray(), IDLE); + } + + moreInputCmds.removeAll(cmdId); + activeLoopCmds.removeAll(cmdId); + pausedCmds.removeAll(cmdId); + + if (del) + { + wrCrashDebugInfo(" exe func: termCommandObj()\n cmd id: " + QString::number(cmdId) + "\n note: calling the command object's aboutToDelete()"); + + for (auto internObj : cmd->internCommands.values()) + { + internObj->aboutToDelete(); + internObj->deleteLater(); + } + + cmd->aboutToDelete(); + cmd->deleteLater(); + + commands.remove(cmdId); + cmdNames.remove(cmdId); + + emit dataToSession(ASYNC_RM_CMD, wrInt(cmdId, 16), CMD_ID); + } +} + +void CmdExecutor::termCommandId(quint16 cmdId) +{ + if (commands.contains(cmdId)) + { + termCommandObj(cmdId, commands[cmdId]); + } +} + +void CmdExecutor::termCommandsInList(const QList &cmds, bool del) +{ + for (auto&& cmdId : cmds) + { + termCommandObj(cmdId, commands[cmdId], del); + } +} + +void CmdExecutor::termAllCommands() +{ + termCommandsInList(moreInputCmds, false); + termCommandsInList(activeLoopCmds, false); + termCommandsInList(pausedCmds, false); +} + +void CmdExecutor::close() +{ + clearCommands(); + cleanupDbConnection(); + + emit okToDelete(); +} + +void CmdExecutor::clearCommands() +{ + termCommandsInList(commands.keys(), true); + + QList loaders = mods.keys(); + + for (auto* loader : loaders) + { + ModCommandLoader *cmdLoader = qobject_cast(loader->instance()); + + if (cmdLoader) + { + wrCrashDebugInfo(" exe func: clearCommands()\n mod file: " + loader->fileName() + "\n note: calling the modules's aboutToDelete()"); + + cmdLoader->aboutToDelete(); + } + + loader->unload(); + loader->deleteLater(); + } + + mods.clear(); +} + +void CmdExecutor::commandFinished(quint16 cmdId) +{ + emit dataToSession(cmdId, QByteArray(), IDLE); + + moreInputCmds.removeAll(cmdId); + activeLoopCmds.removeAll(cmdId); + pausedCmds.removeAll(cmdId); +} + +QString CmdExecutor::makeCmdUnique(const QString &name) +{ + QString strNum; + QStringList names = cmdNames.values(); + + for (int j = 1; names.contains(QString(name + strNum).toLower()); ++j) + { + strNum = "_" + QString::number(j); + } + + return QString(name + strNum).toLower(); +} + +void CmdExecutor::procInternRequest(ExternCommand *cmd, quint16 cmdId, const QString &cmdName) +{ + wrCrashDebugInfo(" exe func: procInternRequest()\n cmd id: " + QString::number(cmdId)); + + QStringList internCmdNames = internalCmds->cmdList(); + + for (auto&& reqCmdName : cmd->internRequest()) + { + if (internCmdNames.contains(reqCmdName, Qt::CaseInsensitive)) + { + InternCommand *cmdObj = internalCmds->cmdObj(reqCmdName); + + if (cmdObj != nullptr) + { + connectInternCmd(cmdObj, cmdId, cmdName); + + cmdObj->setParent(cmd); + cmd->internCommands.insert(reqCmdName, cmdObj); + } + } + } +} + +quint16 CmdExecutor::getModIdOffs(const QString &path) +{ + Query db(this); + + db.setType(Query::PULL, TABLE_MODULES); + db.addColumn(COLUMN_CMD_ID_OFFS); + db.addCondition(COLUMN_MOD_MAIN, path); + db.exec(); + + return static_cast(db.getData(COLUMN_CMD_ID_OFFS).toUInt()); +} + +void CmdExecutor::loadModFile(const QString &path) +{ + loadModLib(path, getModIdOffs(path)); +} + +void CmdExecutor::loadModLib(const QString &path, quint16 idOffs) +{ + if (!isModFileLoaded(path) && QFile::exists(path)) + { + bool modOk = false; + auto *pluginLoader = new QPluginLoader(path); + QObject *cmdLoaderObj = pluginLoader->instance(); + ModCommandLoader *cmdLoader = qobject_cast(cmdLoaderObj); + QString modPath = QFileInfo(path).path(); + + if (idOffs == 0) + { + qDebug() << "CmdExecutor::loadModLib() err: failed to get a valid command id offset for the module."; + } + else if (!pluginLoader->isLoaded()) + { + qDebug() << "CmdExecutor::loadModLib() err: failed to load mod lib file: " << path << " reason: " << pluginLoader->errorString(); + } + else if (!cmdLoaderObj) + { + qDebug() << "CmdExecutor::loadModLib() err: failed to load mod lib file: " << path << " reason: the root component object could not be instantiated."; + } + else if (!cmdLoader) + { + qDebug() << "CmdExecutor::loadModLib() err: failed to load mod lib file: " << path << " reason: the ModCommandLoader object could not be instantiated."; + } + else if (cmdLoader->rev() < IMPORT_REV) + { + qDebug() << "CmdExecutor::loadModLib() err: failed to load mod lib file: " << path << " reason: module import rev " << cmdLoader->rev() << " not compatible with host rev " << IMPORT_REV << "."; + } + else if (!cmdLoader->hostRevOk(IMPORT_REV)) + { + qDebug() << "CmdExecutor::loadModLib() err: failed to load mod lib file: " << path << " the module rejected the host import rev. reason: " << cmdLoader->lastError() << "."; + } + else + { + wrCrashDebugInfo(" exe func: loadModLib()\n path: " + path); + + modOk = true; + + cmdLoader->modPath(modPath); + + loadCmds(cmdLoader, idOffs, pluginLoader, QFileInfo(modPath).fileName()); + } + + if (!modOk) + { + pluginLoader->unload(); + pluginLoader->deleteLater(); + } + } +} + +void CmdExecutor::unloadModFile(const QString &path) +{ + if (isModFileLoaded(path)) + { + QList modLoaders = mods.keys(); + + for (auto* loader : modLoaders) + { + if (rmFileSuffix(loader->fileName()) == path) + { + QList cmdList = mods.value(loader); + + termCommandsInList(cmdList, true); + + ModCommandLoader *cmdLoader = qobject_cast(loader->instance()); + + if (cmdLoader != nullptr) + { + wrCrashDebugInfo(" exe func: unloadModFile()\n mod file: " + loader->fileName() + "\n note: calling the modules's aboutToDelete()"); + + cmdLoader->aboutToDelete(); + } + + loader->unload(); + loader->deleteLater(); + + mods.remove(loader); + + break; + } + } + } +} + +bool CmdExecutor::isModFileLoaded(const QString &path) +{ + bool ret = false; + + QList loaders = mods.keys(); + + for (auto* loader : loaders) + { + if (rmFileSuffix(loader->fileName()) == path) + { + ret = true; + + break; + } + } + + return ret; +} + +void CmdExecutor::loadMods() +{ + Query db(this); + + db.setType(Query::PULL, TABLE_MODULES); + db.addColumn(COLUMN_MOD_MAIN); + db.addColumn(COLUMN_CMD_ID_OFFS); + db.addCondition(COLUMN_LOCKED, false); + db.exec(); + + for (int i = 0; db.rows(); ++i) + { + loadModLib(db.getData(COLUMN_MOD_MAIN, i).toString(), static_cast(db.getData(COLUMN_CMD_ID_OFFS).toUInt())); + } +} + +bool CmdExecutor::allowCmdLoad(const QString &cmdName, const QString &modName, const QStringList &exemptList) +{ + bool ret = false; + + if (exemptList.contains(cmdName, Qt::CaseInsensitive)) + { + ret = true; + } + else + { + Query db(this); + + db.setType(Query::PULL, TABLE_CMD_RANKS); + db.addColumn(COLUMN_HOST_RANK); + db.addCondition(COLUMN_COMMAND, cmdName); + db.addCondition(COLUMN_MOD_NAME, modName); + db.exec(); + + if (db.rows()) + { + uint cmdRank = db.getData(COLUMN_HOST_RANK).toUInt(); + + if (cmdRank >= *rdSharedObjs->hostRank) + { + ret = true; + } + } + else if (*rdSharedObjs->hostRank == 1) + { + ret = true; + } + } + + return ret; +} + +void CmdExecutor::loadInternCmd(CommandLoader *loader, const QString &cmdName, const QString &uniqueName, quint16 id) +{ + auto *cmdObj = reinterpret_cast(loader->cmdObj(cmdName)); + + if (cmdObj != nullptr) + { + connectInternCmd(cmdObj, id, uniqueName); + procInternRequest(cmdObj, id, uniqueName); + + commands.insert(id, cmdObj); + cmdNames.insert(id, uniqueName); + + emit dataToSession(ASYNC_ADD_CMD, toNEW_CMD(id, uniqueName, cmdObj), NEW_CMD); + } +} + +void CmdExecutor::loadExternCmd(CommandLoader *loader, const QString &cmdName, const QString &uniqueName, quint16 id) +{ + ExternCommand *cmdObj = loader->cmdObj(cmdName); + + if (cmdObj != nullptr) + { + connectExternCmd(cmdObj, id, uniqueName); + procInternRequest(cmdObj, id, uniqueName); + + commands.insert(id, cmdObj); + cmdNames.insert(id, uniqueName); + + emit dataToSession(ASYNC_ADD_CMD, toNEW_CMD(id, uniqueName, cmdObj), NEW_CMD); + } +} + +void CmdExecutor::addToModList(quint16 cmdId, QPluginLoader *pluginLoader) +{ + if (mods.contains(pluginLoader)) + { + mods[pluginLoader].append(cmdId); + } + else + { + mods.insert(pluginLoader, QList() << cmdId); + } +} + +void CmdExecutor::loadCmd(CommandLoader *loader, quint16 cmdId, QPluginLoader *pluginLoader, const QString &cmdName, const QString &uniqueCmdName) +{ + wrCrashDebugInfo(" exe func: loadCmd()\n cmd id: " + QString::number(cmdId) + "\n cmd name: " + cmdName); + + if (pluginLoader == nullptr) + { + loadInternCmd(loader, cmdName, uniqueCmdName, cmdId); + } + else + { + loadExternCmd(loader, cmdName, uniqueCmdName, cmdId); + addToModList(cmdId, pluginLoader); + } +} + +void CmdExecutor::loadCmds(CommandLoader *loader, quint16 idOffs, QPluginLoader *pluginLoader, const QString &modName) +{ + QStringList list = loader->cmdList(); + QStringList pub = loader->pubCmdList(); + QStringList exempt = loader->rankExemptList(); + + list.sort(Qt::CaseInsensitive); + + for (quint16 id = 0; (id < list.size()) && (id < MAX_CMDS_PER_MOD); ++id) + { + QString unique = makeCmdUnique(list[id]); + quint16 cmdId = idOffs + id; + + if (validCommandName(unique)) + { + if (commands.contains(cmdId)) + { + if (!allowCmdLoad(list[id], modName, exempt)) + { + termCommandObj(cmdId, commands[cmdId], true); + } + } + else + { + if (rdSharedObjs->userName->isEmpty()) + { + if (pub.contains(list[id], Qt::CaseInsensitive)) + { + loadCmd(loader, cmdId, pluginLoader, list[id], unique); + } + } + else if (allowCmdLoad(list[id], modName, exempt)) + { + loadCmd(loader, cmdId, pluginLoader, list[id], unique); + } + } + } + else + { + qDebug() << "CmdExecutor::getCmdNames() err: command object name '" << unique << "' is not valid."; + } + } +} + +void CmdExecutor::buildCommands() +{ + loadCmds(internalCmds, MAX_CMDS_PER_MOD, nullptr, INTERN_MOD_NAME); + loadMods(); +} + +bool CmdExecutor::externBlockedTypeId(uchar typeId) +{ + // the internal host objects will handle sending the following TypeIDs to the clients + // and peers. any attempt to do so via ExternCommand object will be blocked since these + // data types can cause some behaviour issues if sent at an unexpected time. + + return (typeId == PRIV_IPC) || (typeId == PUB_IPC) || (typeId == PING_PEERS) || + (typeId == PEER_STAT) || (typeId == MY_INFO) || (typeId == PEER_INFO) || + (typeId == HOST_CERT) || (typeId == IDLE) || (typeId == NEW_CMD); +} + +bool CmdExecutor::p2pBlockedTypeId(uchar typeId) +{ + // this is used to block P2P specific typeIDs. only toPeers() or internDataToIPC() + // should be allowed to send these frame types, all others use this to block them. + + return (typeId == P2P_REQUEST) || (typeId == P2P_OPEN) || (typeId == P2P_CLOSE); +} + +void CmdExecutor::externDataToIPC(quint16 cmdId, const QByteArray &data, uchar typeId) +{ + if (!externBlockedTypeId(typeId) && !p2pBlockedTypeId(typeId)) + { + emit dataToSession(cmdId, data, typeId); + } +} + +void CmdExecutor::internDataToIPC(quint16 cmdId, const QByteArray &data, uchar typeId) +{ + if ((typeId != IDLE) && (typeId != NEW_CMD)) + { + emit dataToSession(cmdId, data, typeId); + } +} + +void CmdExecutor::castToPeers(const QByteArray &data, uchar typeId) +{ + if (!externBlockedTypeId(typeId) && !p2pBlockedTypeId(typeId)) + { + QByteArray castHeader = *rdSharedObjs->wrAbleChIds + wrInt(typeId, 8); + + emit dataToSession(ASYNC_CAST, castHeader + data, PUB_IPC); + } +} + +void CmdExecutor::toPeer(const QByteArray &dst, const QByteArray &data, uchar typeId) +{ + if (!externBlockedTypeId(typeId) && (dst.size() == 28)) + { + QByteArray p2pHeader = dst + *rdSharedObjs->sessionId + wrInt(typeId, 8); + + if (typeId == P2P_REQUEST) + { + if (!rdSharedObjs->p2pPending->contains(dst)) + { + rwSharedObjs->p2pPending->append(dst); + + emit dataToSession(ASYNC_P2P, p2pHeader + toPEER_INFO(rdSharedObjs), PUB_IPC); + } + } + else + { + if ((typeId == P2P_CLOSE) || (typeId == P2P_OPEN)) + { + if (rdSharedObjs->p2pPending->contains(dst) || rdSharedObjs->p2pAccepted->contains(dst)) + { + if (typeId == P2P_CLOSE) + { + rwSharedObjs->p2pPending->removeAll(dst); + rwSharedObjs->p2pAccepted->removeAll(dst); + + emit dataToSession(ASYNC_P2P, p2pHeader + dst, PUB_IPC); + } + else if (!rdSharedObjs->p2pAccepted->contains(dst)) + { + rwSharedObjs->p2pPending->removeAll(dst); + rwSharedObjs->p2pAccepted->append(dst); + + emit dataToSession(ASYNC_P2P, p2pHeader + dst, PUB_IPC); + } + } + } + else + { + emit dataToSession(ASYNC_P2P, p2pHeader + data, PUB_IPC); + } + } + } +} + +void CmdExecutor::backendFromCmd(quint16 cmdId, const QByteArray &data, uchar typeId) +{ + if ((typeId == PUB_IPC) || (typeId == PRIV_IPC) || (typeId == PUB_IPC_WITH_FEEDBACK)) + { + emit dataToSession(cmdId, data, typeId); + } +} + +void CmdExecutor::openOrCloseChByName(quint16 cmdId, const QString &ch, const QString &sub, bool open) +{ + if (!validChName(ch)) + { + emit dataToSession(cmdId, toTEXT("err: '" + ch + "' is not a valid channel name.\n"), ERR); + } + else if (!validChName(sub)) + { + emit dataToSession(cmdId, toTEXT("err: '" + sub + "' is not a valid sub channel name.\n"), ERR); + } + else if (!channelSubExists(ch, sub)) + { + emit dataToSession(cmdId, toTEXT("err: Sub-channel: '" + sub + "' does not exists.\n"), ERR); + } + else + { + Query db(this); + + db.setType(Query::PULL, TABLE_SUB_CHANNELS); + db.addColumn(COLUMN_CHANNEL_ID); + db.addColumn(COLUMN_SUB_CH_ID); + db.addCondition(COLUMN_SUB_CH_NAME, sub); + db.addCondition(COLUMN_CHANNEL_NAME, ch); + db.exec(); + + if (open) + { + openChById(cmdId, db.getData(COLUMN_CHANNEL_ID).toULongLong(), static_cast(db.getData(COLUMN_SUB_CH_ID).toUInt())); + } + else + { + closeChById(cmdId, db.getData(COLUMN_CHANNEL_ID).toULongLong(), static_cast(db.getData(COLUMN_SUB_CH_ID).toUInt())); + } + } +} + +void CmdExecutor::openChByName(quint16 cmdId, const QString &ch, const QString &sub) +{ + openOrCloseChByName(cmdId, ch, sub, true); +} + +void CmdExecutor::closeChByName(quint16 cmdId, const QString &ch, const QString &sub) +{ + openOrCloseChByName(cmdId, ch, sub, false); +} + +void CmdExecutor::openChById(quint16 cmdId, quint64 chId, uchar subId) +{ + QByteArray id = wrInt(chId, 64) + wrInt(subId, 8); + + if (chId == 0) + { + emit dataToSession(cmdId, toTEXT("err: '0' is not a valid channel id. it must an unsigned integer between 1-18446744073709551615.\n"), ERR); + } + else if (countChs(*rdSharedObjs->chIds) == 6) + { + emit dataToSession(cmdId, toTEXT("err: The maximum amount of open sub-channels reached (6).\n"), ERR); + } + else if (containsChId(id, *rdSharedObjs->chIds)) + { + emit dataToSession(cmdId, toTEXT("err: The requested sub-channel is already open.\n"), ERR); + } + else if (!channelSubExists(chId, subId)) + { + emit dataToSession(cmdId, toTEXT("err: The requested sub-channel does not exists.\n"), ERR); + } + else if (channelAccessLevel(rdSharedObjs, chId) > lowestAcessLevel(chId, subId)) + { + emit dataToSession(cmdId, toTEXT("err: Access denied.\n"), ERR); + } + else + { + wrOpenCh(rwSharedObjs, id); + } +} + +void CmdExecutor::closeChById(quint16 cmdId, quint64 chId, uchar subId) +{ + QByteArray id = wrInt(chId, 64) + wrInt(subId, 8); + + if (chId == 0) + { + emit dataToSession(cmdId, toTEXT("err: '0' is not a valid channel id. it must an integer between 1-18446744073709551615.\n"), ERR); + } + else if (!containsChId(id, *rdSharedObjs->chIds)) + { + emit dataToSession(cmdId, toTEXT("err: The requested sub-channel is not open.\n"), ERR); + } + else + { + QByteArray peerStat; + + wrCloseCh(rwSharedObjs, id, peerStat); + + if (!peerStat.isEmpty()) + { + emit dataToSession(ASYNC_LIMITED_CAST, peerStat, PUB_IPC); + } + } +} diff --git a/src/cmd_executor.h b/src/cmd_executor.h new file mode 100644 index 0000000..fc4eb7f --- /dev/null +++ b/src/cmd_executor.h @@ -0,0 +1,106 @@ +#ifndef CMD_EXECUTOR_H +#define CMD_EXECUTOR_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 +// . + +#include "common.h" +#include "int_loader.h" + +class CmdExecutor : public QObject +{ + Q_OBJECT + +private: + + SharedObjs *rdSharedObjs; + RWSharedObjs *rwSharedObjs; + InternalCommandLoader *internalCmds; + QSharedMemory *exeDebugInfo; + QList moreInputCmds; + QList activeLoopCmds; + QList pausedCmds; + QHash cmdNames; + QHash commands; + QHash > mods; + int loopIndex; + + void clearCommands(); + void loadMods(); + void nextLoopCmd(); + void preExe(ExternCommand *cmdObj, quint16 cmdId); + void preExe(const QList &cmdObjs, quint16 cmdId); + void procInternRequest(ExternCommand *cmd, quint16 cmdId, const QString &cmdName); + void connectCommon(ExternCommand *cmd, CommandOutput *cmdOutput, quint16 cmdId, const QString &cmdName); + void connectInternCmd(InternCommand *cmd, quint16 cmdId, const QString &cmdName); + void connectExternCmd(ExternCommand *cmd, quint16 cmdId, const QString &cmdName); + void openOrCloseChByName(quint16 cmdId, const QString &ch, const QString &sub, bool open); + void loadCmd(CommandLoader *loader, quint16 cmdId, QPluginLoader *pluginLoader, const QString &cmdName, const QString &uniqueCmdName); + void loadCmds(CommandLoader *loader, quint16 idOffs, QPluginLoader *pluginLoader, const QString &modName); + void loadInternCmd(CommandLoader *loader, const QString &cmdName, const QString &uniqueName, quint16 id); + void loadExternCmd(CommandLoader *loader, const QString &cmdName, const QString &uniqueName, quint16 id); + void addToModList(quint16 cmdId, QPluginLoader *pluginLoader); + bool isModFileLoaded(const QString &path); + bool externBlockedTypeId(uchar typeId); + bool p2pBlockedTypeId(uchar typeId); + bool allowCmdLoad(const QString &cmdName, const QString &modName, const QStringList &exemptList); + QString makeCmdUnique(const QString &name); + quint16 getModIdOffs(const QString &path); + +private slots: + + void termAllCommands(); + void enableMoreInput(quint16 cmdId, bool state); + void enableLoop(quint16 cmdId, bool state); + void openChById(quint16 cmdId, quint64 chId, uchar subId); + void openChByName(quint16 cmdId, const QString &ch, const QString &sub); + void closeChById(quint16 cmdId, quint64 chId, uchar subId); + void closeChByName(quint16 cmdId, const QString &ch, const QString &sub); + void termCommandId(quint16 cmdId); + void termCommandObj(quint16 cmdId, ExternCommand *cmd, bool del = false); + void termCommandsInList(const QList &cmds, bool del = false); + void commandFinished(quint16 cmdId); + void externDataToIPC(quint16 cmdId, const QByteArray &data, uchar typeId); + void internDataToIPC(quint16 cmdId, const QByteArray &data, uchar typeId); + void castToPeers(const QByteArray &data, uchar typeId); + void toPeer(const QByteArray &dst, const QByteArray &data, uchar typeId); + +public slots: + + void close(); + void buildCommands(); + void wrCrashDebugInfo(const QString &msg); + void unloadModFile(const QString &path); + void loadModFile(const QString &path); + void loadModLib(const QString &path, quint16 idOffs); + void exeCmd(quint16 cmdId, const QByteArray &data, uchar typeId); + void backendFromCmd(quint16 cmdId, const QByteArray &data, uchar typeId); + +signals: + + void endSession(); + void logout(); + void authOk(); + void okToDelete(); + void dataToSession(quint16 cmdId, const QByteArray &data, uchar typeId); + void loop(quint16 cmdId, const QByteArray &data, uchar typeId); + +public: + + explicit CmdExecutor(RWSharedObjs *rwShare, SharedObjs *rdOnlyShare, QSharedMemory *debugInfo, QObject *parent = nullptr); +}; + +#endif // CMD_EXECUTOR_H diff --git a/src/commands/acct_recovery.cpp b/src/commands/acct_recovery.cpp new file mode 100644 index 0000000..2e9ea67 --- /dev/null +++ b/src/commands/acct_recovery.cpp @@ -0,0 +1,586 @@ +#include "acct_recovery.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 +// . + +RecoverAcct::RecoverAcct(QObject *parent) : InternCommand(parent) {inputOk = false;} +ResetPwRequest::ResetPwRequest(QObject *parent) : InternCommand(parent) {} +VerifyEmail::VerifyEmail(QObject *parent) : InternCommand(parent) {} +IsEmailVerified::IsEmailVerified(QObject *parent) : InternCommand(parent) {} +SetEmailTemplate::SetEmailTemplate(QObject *parent) : InternCommand(parent) {} +PreviewEmail::PreviewEmail(QObject *parent) : InternCommand(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 RecoverAcct::term() +{ + emit enableMoreInput(false); + + inputOk = false; +} + +void RecoverAcct::delRecoverPw() +{ + Query db(this); + + db.setType(Query::DEL, TABLE_PW_RECOVERY); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + db.setType(Query::UPDATE, TABLE_AUTH_LOG); + db.addColumn(COLUMN_COUNT, false); + db.addCondition(COLUMN_COUNT, true); + db.addCondition(COLUMN_RECOVER_ATTEMPT, true); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + term(); +} + +void RecoverAcct::addToThreshold(const SharedObjs *sharedObjs) +{ + Query db(this); + + db.setType(Query::PUSH, TABLE_AUTH_LOG); + db.addColumn(COLUMN_USERNAME, uName); + db.addColumn(COLUMN_IPADDR, *sharedObjs->sessionAddr); + db.addColumn(COLUMN_AUTH_ATTEMPT, false); + db.addColumn(COLUMN_RECOVER_ATTEMPT, true); + db.addColumn(COLUMN_COUNT, true); + db.addColumn(COLUMN_ACCEPTED, false); + db.exec(); + + db.setType(Query::PULL, TABLE_SERV_SETTINGS); + db.addColumn(COLUMN_LOCK_LIMIT); + db.exec(); + + uint maxAttempts = db.getData(COLUMN_LOCK_LIMIT).toUInt(); + + db.setType(Query::PULL, TABLE_AUTH_LOG); + db.addColumn(COLUMN_IPADDR); + db.addCondition(COLUMN_USERNAME, uName); + db.addCondition(COLUMN_RECOVER_ATTEMPT, true); + db.addCondition(COLUMN_COUNT, true); + db.addCondition(COLUMN_ACCEPTED, false); + db.exec(); + + if (static_cast(db.rows()) > maxAttempts) + { + delRecoverPw(); + term(); + } + else + { + errTxt("err: Access denied.\n"); + privTxt("Enter the temporary password: "); + } +} + +void RecoverAcct::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (moreInputEnabled() && (dType == TEXT)) + { + QString pw = fromTEXT(binIn); + + if (inputOk) + { + if (!validPassword(pw)) + { + errTxt("err: Invalid password. it must be 8-200 chars long containing numbers, mixed case letters and special chars.\n"); + privTxt("Enter a new password: "); + } + else + { + updatePassword(uName, pw, TABLE_USERS); + delRecoverPw(); + term(); + } + } + else + { + if (pw.isEmpty()) + { + term(); + } + else if (!validPassword(pw)) + { + addToThreshold(sharedObjs); + } + else if (!auth(uName, pw, TABLE_PW_RECOVERY)) + { + addToThreshold(sharedObjs); + } + else + { + privTxt("Enter a new password: "); + + inputOk = true; + } + } + } + else if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString email = getParam("-email", args); + QString name = getParam("-user", args); + + if (!email.isEmpty() && validEmailAddr(email)) name = getUserNameForEmail(email); + + if (name.isEmpty() || !validUserName(name)) + { + errTxt("err: The -user or -email argument is empty, not found or invalid.\n"); + } + else if (!userExists(name)) + { + errTxt("err: No such user.\n"); + } + else if (!recoverPWExists(name)) + { + errTxt("err: This account does not have a recovery password.\n"); + } + else + { + privTxt("Enter the temporary password (leave blank to cancel): "); + + uName = name; + + emit enableMoreInput(true); + } + } +} + +void ResetPwRequest::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString email = getParam("-email", args); + QString name = getParam("-user", args); + + if (!email.isEmpty() && validEmailAddr(email)) name = getUserNameForEmail(email); + + if (name.isEmpty() || !validUserName(name)) + { + errTxt("err: The -user or -email argument is empty, not found or invalid.\n"); + } + else if (!userExists(name)) + { + errTxt("err: No such user.\n"); + } + else + { + email = getEmailForUser(name); + + QString pw = genPw(); + QString date = QDateTime::currentDateTimeUtc().toString("YYYY-MM-DD HH:MM:SS"); + + if (recoverPWExists(name)) + { + updatePassword(name, pw, TABLE_PW_RECOVERY); + } + else + { + createTempPw(name, email, pw); + } + + 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(); + + QString subject = db.getData(COLUMN_TEMP_PW_SUBJECT).toString(); + QString body = db.getData(COLUMN_TEMP_PW_MSG).toString(); + QString app = db.getData(COLUMN_MAILERBIN).toString(); + QString cmdLine = db.getData(COLUMN_MAIL_SEND).toString(); + + body.replace(DATE_SUB, date); + body.replace(USERNAME_SUB, name); + body.replace(TEMP_PW_SUB, pw); + + cmdLine.replace(TARGET_EMAIL_SUB, email); + cmdLine.replace(SUBJECT_SUB, "'" + escapeChars(subject, '\\', '\'') + "'"); + cmdLine.replace(MSG_SUB, "'" + escapeChars(body, '\\', '\'') + "'"); + + QProcess::startDetached(expandEnvVariables(app), parseArgs(toTEXT(cmdLine), -1)); + + mainTxt("A temporary password was sent to the email address associated with the account.\n"); + } + } +} + +void VerifyEmail::term() +{ + emit enableMoreInput(false); + + code.clear(); +} + +void VerifyEmail::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (moreInputEnabled() && (dType == TEXT)) + { + QString txt = fromTEXT(binIn); + + if (txt == code) + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_USERS); + db.addColumn(COLUMN_EMAIL_VERIFIED, true); + db.addCondition(COLUMN_USERNAME, *sharedObjs->userName); + db.exec(); + + emit backendDataOut(ASYNC_RW_MY_INFO, toTEXT(*sharedObjs->userName), PUB_IPC_WITH_FEEDBACK); + + term(); + } + else if (txt.isEmpty()) + { + term(); + } + else + { + errTxt("err: The code you entered does not match.\n"); + privTxt("Please try again: "); + } + } + else if (dType == TEXT) + { + QString email = getEmailForUser(*sharedObjs->userName); + + if (email.isEmpty()) + { + errTxt("err: Your account currently has no email address, please update it.\n"); + } + else + { + QString date = QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd HH:mm:ss"); + + 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(); + + QString subject = db.getData(COLUMN_CONFIRM_SUBJECT).toString(); + QString body = db.getData(COLUMN_CONFIRM_MSG).toString(); + QString app = db.getData(COLUMN_MAILERBIN).toString(); + QString cmdLine = db.getData(COLUMN_MAIL_SEND).toString(); + + body.replace(DATE_SUB, date); + body.replace(USERNAME_SUB, *sharedObjs->userName); + body.replace(CONFIRM_CODE_SUB, code); + + cmdLine.replace(TARGET_EMAIL_SUB, email); + cmdLine.replace(SUBJECT_SUB, "'" + escapeChars(subject, '\\', '\'') + "'"); + cmdLine.replace(MSG_SUB, "'" + escapeChars(body, '\\', '\'') + "'"); + + QProcess::startDetached(expandEnvVariables(app), parseArgs(toTEXT(cmdLine), -1)); + + privTxt("A confirmation code was sent to your email address: " + email + "\n\n" + "Please enter that code now or leave blank to cancel: "); + + emit enableMoreInput(true); + } + } +} + +void IsEmailVerified::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(binIn); + + if (dType == TEXT) + { + Query db(this); + + db.setType(Query::PULL, TABLE_USERS); + db.addColumn(COLUMN_EMAIL_VERIFIED); + db.addCondition(COLUMN_USERNAME, *sharedObjs->userName); + db.exec(); + + mainTxt(boolStr(db.getData(COLUMN_EMAIL_VERIFIED).toBool()) + "\n"); + } +} + +bool SetEmailTemplate::handlesGenfile() +{ + return true; +} + +void SetEmailTemplate::term() +{ + emit enableMoreInput(false); + + textFromFile = false; + dataSent = 0; + + subject.clear(); + bodyText.clear(); + len.clear(); +} + +void SetEmailTemplate::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (moreInputEnabled() && (dType == GEN_FILE)) + { + bodyText.append(fromTEXT(binIn)); + + dataSent += binIn.size(); + + mainTxt(QString::number(dataSent) + "/" + len + "\n"); + + if (dataSent >= len.toInt()) + { + emit enableMoreInput(false); + + mainTxt("\nUpload complete.\n"); + proc(); + } + } + else if (dType == GEN_FILE) + { + QStringList args = parseArgs(binIn, 9); + + 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; + } + + if (eType == NONE) + { + errTxt("err: Which template do you want to change? -reset_template or -confirm_template not found.\n"); + term(); + } + else if (textFromFile && !isInt(len)) + { + errTxt("err: '" + len + "' given in -len is not a valid integer.\n"); + term(); + } + else if (textFromFile && (len.toInt() <= 0)) + { + errTxt("err: The text file size cannot be 0 or less than 0.\n"); + term(); + } + 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"); + term(); + } + else + { + 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(); + + emit enableMoreInput(true); + emit dataToClient(toTEXT("-to_host"), GEN_FILE); + emit dataToClient(QByteArray(), GEN_FILE); + } + else + { + proc(); + } + } + } +} + +void SetEmailTemplate::proc() +{ + if (bodyText.isEmpty() && subject.isEmpty()) + { + 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; + bool 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()) + { + 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; + } + } + + if (!subject.isEmpty()) + { + 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; + } + } + + if (execQuery) db.exec(); + } + + term(); +} + +void PreviewEmail::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + TemplateType eType = NONE; + + if (argExists("-reset_email", args)) eType = PW_RESET; + else if (argExists("-confirm_email", args)) eType = CONFIRM_EMAIL; + + if (eType == NONE) + { + 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; + QString 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(); + + QString subject = db.getData(subjectColumn).toString(); + QString body = db.getData(bodyColumn).toString(); + + body.replace(DATE_SUB, date); + body.replace(USERNAME_SUB, *sharedObjs->userName); + body.replace(codeSub, code); + + QString txt; + QTextStream txtOut(&txt); + + txtOut << "-----Subject-------" << endl << endl; + txtOut << subject << endl << endl; + txtOut << "-----Body----------" << endl << endl; + + mainTxt(txt); + bigTxt(body); + } + } +} diff --git a/src/commands/acct_recovery.h b/src/commands/acct_recovery.h new file mode 100644 index 0000000..2aa0194 --- /dev/null +++ b/src/commands/acct_recovery.h @@ -0,0 +1,144 @@ +#ifndef ACCT_RECOVERY_H +#define ACCT_RECOVERY_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 +// . + +#include "../common.h" + +enum TemplateType +{ + PW_RESET, + CONFIRM_EMAIL, + NONE +}; + +class RecoverAcct : public InternCommand +{ + Q_OBJECT + +private: + + QString uName; + bool inputOk; + + void delRecoverPw(); + void addToThreshold(const SharedObjs *sharedObjs); + +public: + + static QString cmdName(); + + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit RecoverAcct(QObject *parent = nullptr); +}; + +//--------------- + +class ResetPwRequest : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit ResetPwRequest(QObject *parent = nullptr); +}; + +//---------------- + +class VerifyEmail : public InternCommand +{ + Q_OBJECT + +private: + + QString code; + +public: + + static QString cmdName(); + + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit VerifyEmail(QObject *parent = nullptr); +}; + +//---------------- + +class IsEmailVerified : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit IsEmailVerified(QObject *parent = nullptr); +}; + +//------------------ + +class SetEmailTemplate : public InternCommand +{ + Q_OBJECT + +private: + + QString bodyText; + QString subject; + QString len; + int dataSent; + bool textFromFile; + TemplateType eType; + + void proc(); + +public: + + static QString cmdName(); + + bool handlesGenfile(); + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit SetEmailTemplate(QObject *parent = nullptr); +}; + +//----------------- + +class PreviewEmail : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit PreviewEmail(QObject *parent = nullptr); +}; + +#endif // ACCT_RECOVERY_H diff --git a/src/commands/admin.cpp b/src/commands/admin.cpp new file mode 100644 index 0000000..58a55e6 --- /dev/null +++ b/src/commands/admin.cpp @@ -0,0 +1,581 @@ +#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 +// . + +CloseHost::CloseHost(QObject *parent) : InternCommand(parent) {} +RestartHost::RestartHost(QObject *parent) : InternCommand(parent) {} +ServSettings::ServSettings(QObject *parent) : InternCommand(parent) {} + +QString CloseHost::cmdName() {return "close_host";} +QString RestartHost::cmdName() {return "restart_host";} +QString ServSettings::cmdName() {return "host_config";} + +void CloseHost::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + if (moreInputEnabled()) + { + QString input = fromTEXT(binIn); + + if (input == "CLOSE") + { + emit enableMoreInput(false); + emit backendDataOut(ASYNC_EXIT, QByteArray(), PRIV_IPC); + } + else if (input.isEmpty()) + { + emit enableMoreInput(false); + } + else + { + errTxt("err: Invalid response. you need to type 'CLOSE' exactly as shown without the quotes.\n"); + mainTxt("Enter 'CLOSE' to proceed or leave blank to cancel: "); + } + } + else + { + emit enableMoreInput(true); + + mainTxt("You are about to shutdown the host instance, type: 'CLOSE' to proceed or leave blank to cancel: "); + } + } +} + +void RestartHost::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + if (moreInputEnabled()) + { + QString input = fromTEXT(binIn); + + if (input == "RESTART") + { + emit enableMoreInput(false); + emit backendDataOut(ASYNC_RESTART, QByteArray(), PRIV_IPC); + } + else if (input.isEmpty()) + { + emit enableMoreInput(false); + } + else if (!input.isEmpty()) + { + errTxt("err: Invalid response. you need to type 'RESTART' exactly as shown without the quotes.\n"); + mainTxt("Enter 'RESTART' to proceed or leave blank to cancel: "); + } + } + else + { + emit enableMoreInput(true); + + mainTxt("You are about to re-start the host instance, type: 'RESTART' to proceed or leave blank to cancel: "); + } + } +} + +void ServSettings::term() +{ + emit enableMoreInput(false); + + level = 0; + select = 0; +} + +void ServSettings::printSettings() +{ + Query db(this); + + db.setType(Query::PULL, TABLE_SERV_SETTINGS); + db.addColumn(COLUMN_PUB_USERS); + db.addColumn(COLUMN_BAN_LIMIT); + db.addColumn(COLUMN_LOCK_LIMIT); + db.addColumn(COLUMN_MAXSESSIONS); + db.addColumn(COLUMN_ZIPBIN); + db.addColumn(COLUMN_ZIPCOMPRESS); + db.addColumn(COLUMN_ZIPEXTRACT); + db.addColumn(COLUMN_INITGROUP); + 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(); + + QString pubBool = boolStr(db.getData(COLUMN_PUB_USERS).toBool()); + QString resBool = boolStr(db.getData(COLUMN_ENABLE_PW_RESET).toBool()); + QString conBool = boolStr(db.getData(COLUMN_ENABLE_CONFIRM).toBool()); + QString actBool = boolStr(db.getData(COLUMN_ACTIVE_UPDATE).toBool()); + + QString txt; + QTextStream txtOut(&txt); + + txtOut << "All Sub-Channels Active Update: " << actBool << endl; + txtOut << "Public Registration: " << pubBool << endl; + txtOut << "Automated Password Resets: " << resBool << endl; + txtOut << "Automated Email Verify: " << conBool << endl; + txtOut << "Maximum Sessions: " << db.getData(COLUMN_MAXSESSIONS).toUInt() << endl; + txtOut << "Autoban Threshold: " << db.getData(COLUMN_BAN_LIMIT).toUInt() << endl; + txtOut << "Autolock Threshold: " << db.getData(COLUMN_LOCK_LIMIT).toUInt() << endl; + txtOut << "Maximum Sub-Channels: " << db.getData(COLUMN_MAX_SUB_CH).toUInt() << endl; + txtOut << "Initial Group: " << db.getData(COLUMN_INITGROUP).toString() << endl; + txtOut << "Database Path: " << sqlDataPath() << endl; + txtOut << "Modules Install Path: " << modDataPath() << endl; + txtOut << "Archiver Executable: " << db.getData(COLUMN_ZIPBIN).toString() << endl; + txtOut << "Archiver Compress Command: " << db.getData(COLUMN_ZIPCOMPRESS).toString() << endl; + txtOut << "Archiver Extract Command: " << db.getData(COLUMN_ZIPEXTRACT).toString() << endl; + txtOut << "Mailer Executable: " << db.getData(COLUMN_MAILERBIN).toString() << endl; + txtOut << "Mailer Command: " << db.getData(COLUMN_MAIL_SEND).toString() << endl << endl; + + mainTxt(txt); +} + +void ServSettings::printOptions() +{ + if (level == 0) + { + QString txt; + QTextStream txtOut(&txt); + + txtOut << "[01] Autoban Threshold [02] Autolock Threshold" << endl; + txtOut << "[03] Max Sessions [04] Public Registration" << endl; + txtOut << "[05] Initial Group [06] Archiver Exe" << endl; + txtOut << "[07] Compress Command [08] Extract Command" << endl; + txtOut << "[09] Mailer Exe [10] Mailer Command" << endl; + txtOut << "[11] Password Resets [12] Email Verify" << endl; + txtOut << "[13] Active Update [14] Max Sub-Channels" << endl; + txtOut << "[00] Exit" << endl << endl; + txtOut << "Select an option: "; + + level = 1; + + mainTxt(txt); + } +} + +void ServSettings::returnToStart() +{ + select = 0; + level = 0; + + mainTxt("\n"); + printSettings(); + printOptions(); +} + +void ServSettings::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + if (moreInputEnabled()) + { + if (level == 1) + { + QString txt; + QTextStream txtOut(&txt); + + bool ok = false; + + select = fromTEXT(binIn).toInt(&ok); + + if ((select == 1) && ok) + { + txtOut << "" << endl; + txtOut << "The autoban threshold is an integar value that determines how many" << endl; + txtOut << "failed login attempts can be made to the " << ROOT_USER << " user before the" << endl; + txtOut << "offending ip address is blocked by the host." << endl << endl; + txtOut << "Enter a new value (leave blank to cancel): "; + + level = 2; + } + else if ((select == 2) && ok) + { + txtOut << "" << endl; + txtOut << "The autolock threshold is an integar value that determines how many" << endl; + txtOut << "failed login attempts can be made before the user account is locked" << endl; + txtOut << "by the host." << endl << endl; + txtOut << "note: the " << ROOT_USER << " user never gets locked. instead, the offenders are blocked" << endl; + txtOut << " by ip address according to the autoban threshold." << endl << endl; + txtOut << "Enter a new value (leave blank to cancel): "; + + level = 2; + } + else if ((select == 3) && ok) + { + txtOut << "" << endl; + txtOut << "Max sessions is an integar value that determines how many simultaneous" << endl; + txtOut << "clients the host will be allowed to run at once." << endl << endl; + txtOut << "Enter a new value (leave blank to cancel): "; + + level = 2; + } + else if ((select == 4) && ok) + { + txtOut << "" << endl; + txtOut << "Public registration basically allows un-logged in clients to run the" << endl; + txtOut << "new_user command. doing this allows un-registered users to become" << endl; + txtOut << "registered users without the need to contact an admin." << endl << endl; + txtOut << "[0] Disable" << endl; + txtOut << "[1] Enable" << endl << endl; + txtOut << "Select an option (leave blank to cancel): "; + + level = 2; + } + else if ((select == 5) && ok) + { + txtOut << "" << endl; + txtOut << "The initial group is the group any new user rergistered using the" << endl; + txtOut << "new_user command is attached to. the group must already exists and" << endl; + txtOut << "will not be allowed to get deleted as long as it is the initial" << endl; + txtOut << "group." << endl << endl; + txtOut << "Enter a new group (leave blank to cancel): "; + + level = 2; + } + else if ((select == 6) && ok) + { + txtOut << "" << endl; + txtOut << "This is the path to zip archiver's executable file that the" << endl; + txtOut << "host can call when it needs to extract or create archive files" << endl; + txtOut << "like .zip, .tar, etc.." << endl << endl; + txtOut << "Enter a new path (leave blank to cancel): "; + + level = 2; + } + else if ((select == 7) && ok) + { + txtOut << "" << endl; + txtOut << "This is the command line the host will use when calling the archiver" << endl; + txtOut << "to create a zip file. it must contain the keywords " << OUTPUT_DIR_SUB << endl; + txtOut << "and " << INPUT_DIR_SUB << ". the host will substitute these keywords for" << endl; + txtOut << "for the actual input/output directories when calling the command." << endl << endl; + txtOut << "Enter a new command line (leave blank to cancel): "; + + level = 2; + } + else if ((select == 8) && ok) + { + txtOut << "" << endl; + txtOut << "This is the command line the host will use when calling the archiver" << endl; + txtOut << "to extract a zip file. it must contain the keywords " << OUTPUT_DIR_SUB << endl; + txtOut << "and " << INPUT_DIR_SUB << ". the host will substitute these keywords for" << endl; + txtOut << "for the actual input/output directories when calling the command." << endl << endl; + txtOut << "Enter a new command line (leave blank to cancel): "; + + level = 2; + } + else if ((select == 9) && ok) + { + txtOut << "" << endl; + txtOut << "This is the path to the command line email client executable" << endl; + txtOut << "that the host can utilize to send emails to registered users." << endl << endl; + txtOut << "note: the host assumes the email application already has a" << endl; + txtOut << " configured sender email address/server." << endl << endl; + txtOut << "Enter a new path (leave blank to cancel): "; + + level = 2; + } + else if ((select == 10) && ok) + { + txtOut << "" << endl; + txtOut << "This is the command line that will be used with the email client" << endl; + txtOut << "executable to send emails to registered users. it must contain the" << endl; + txtOut << "keywords " << SUBJECT_SUB << ", " << TARGET_EMAIL_SUB << " and " << MSG_SUB << " to be" << endl; + txtOut << "acceptable. the host will substitute these keywords for actual" << endl; + txtOut << "parameters when calling the email client." << endl << endl; + txtOut << "Enter a new command line (leave blank to cancel): "; + + level = 2; + } + else if ((select == 11) && ok) + { + txtOut << "" << endl; + txtOut << "This enables automated password resets via email so users can" << endl; + txtOut << "reset their account passwords without the need to contact an" << endl; + txtOut << "admin. this basically tells the host if it is allowed to load" << endl; + txtOut << "the request_pw_reset and recover_account commands or not." << endl << endl; + txtOut << "[0] Disable" << endl; + txtOut << "[1] Enable" << endl << endl; + txtOut << "Select an option (leave blank to cancel): "; + + level = 2; + } + else if ((select == 12) && ok) + { + txtOut << "" << endl; + txtOut << "This enables automated email confirmations. this tells the" << endl; + txtOut << "host if it is allowed to load the confirm_email command." << endl << endl; + txtOut << "[0] Disable" << endl; + txtOut << "[1] Enable" << endl << endl; + txtOut << "Select an option (leave blank to cancel): "; + + level = 2; + } + else if ((select == 13) && ok) + { + txtOut << "" << endl; + txtOut << "This option tells the host if all sub-channels should be considered" << endl; + txtOut << "active or not. otherwise, the active flag can be toggled on/off at the" << endl; + txtOut << "sub-channel level. active sub-channels send/receive PEER_INFO or" << endl; + txtOut << "PEER_STAT frames with each other so all peers connected to the" << endl; + txtOut << "sub-channel can be made aware of each other's public information." << endl; + txtOut << "without the active flag, no such frames are transffered." << endl << endl; + txtOut << "[0] Disable" << endl; + txtOut << "[1] Enable" << endl << endl; + txtOut << "Select an option (leave blank to cancel): "; + + level = 2; + } + else if ((select == 14) && ok) + { + txtOut << "" << endl; + txtOut << "This option sets the maximum amount of sub-channels each channel can" << endl; + txtOut << "have. the hard maximum is 256 and the minimum is 1." << endl << endl; + txtOut << "Enter a new value (leave blank to cancel): "; + + level = 2; + } + else if ((select == 0) && ok) + { + term(); + } + else + { + txtOut << "" << endl << "Select an option: "; + } + + mainTxt(txt); + } + else if (level == 2) + { + QString value = fromTEXT(binIn); + + if (value.isEmpty()) + { + mainTxt("\n"); + returnToStart(); + } + else + { + if ((select == 1) || (select == 2) || (select == 3)) + { + bool ok; + uint num = value.toUInt(&ok, 10); + + if (!ok) + { + errTxt("err: Invalid 32bit unsigned integer. valid range: 1-4294967295.\n"); + mainTxt("Enter a new value (leave blank to cancel): "); + } + else if (num == 0) + { + errTxt("err: This value cannot be 0, valid range: 1-4294967295.\n"); + mainTxt("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_BAN_LIMIT, num); + else if (select == 2) db.addColumn(COLUMN_LOCK_LIMIT, num); + else db.addColumn(COLUMN_MAXSESSIONS, num); + + db.exec(); + + if (select == 5) + { + emit backendDataOut(ASYNC_MAXSES, wrInt(num, 32), PRIV_IPC); + } + + returnToStart(); + } + } + else if ((select == 4) || (select == 11) || (select == 12) || (select == 13)) + { + if (!isBool(value)) + { + errTxt("err: Invalid boolean value. must be 0 (false) or 1 (true).\n"); + mainTxt("Select an option (leave blank to cancel): "); + } + else + { + QString column; + + if (select == 4) column = COLUMN_PUB_USERS; + else if (select == 11) column = COLUMN_ENABLE_PW_RESET; + else if (select == 12) column = COLUMN_ENABLE_CONFIRM; + else column = COLUMN_ACTIVE_UPDATE; + + Query db(this); + + db.setType(Query::UPDATE, TABLE_SERV_SETTINGS); + db.addColumn(column, static_cast(value.toUInt())); + db.exec(); + + returnToStart(); + } + } + else if (select == 5) + { + if (!validGroupName(value)) + { + errTxt("err: Invalid group name.\n"); + mainTxt("Enter a new group (leave blank to cancel): "); + } + else if (!groupExists(value)) + { + errTxt("err: '" + value + "' does not exists.\n"); + mainTxt("Enter a new group (leave blank to cancel): "); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_SERV_SETTINGS); + db.addColumn(COLUMN_INITGROUP, value); + db.exec(); + + returnToStart(); + } + } + else if ((select == 6) || (select == 9)) + { + if (!QFile::exists(expandEnvVariables(value))) + { + errTxt("err: The given file: '" + value + "' does not exists.\n"); + mainTxt("Enter a new path (leave blank to cancel): "); + } + else + { + Query db(this); + + QString column; + + if (select == 6) column = COLUMN_ZIPBIN; + else column = COLUMN_MAILERBIN; + + db.setType(Query::UPDATE, TABLE_SERV_SETTINGS); + db.addColumn(column, value); + db.exec(); + + returnToStart(); + } + } + else if ((select == 7) || (select == 8)) + { + if (!value.contains(INPUT_DIR_SUB, Qt::CaseInsensitive)) + { + errTxt("err: The '" + QString(INPUT_DIR_SUB) + "' keyword is missing.\n"); + mainTxt("Enter a new command line (leave blank to cancel): "); + } + else if (!value.contains(OUTPUT_DIR_SUB, Qt::CaseInsensitive)) + { + errTxt("err: The '" + QString(OUTPUT_DIR_SUB) + "' keyword is missing.\n"); + mainTxt("Enter a new command line (leave blank to cancel): "); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_SERV_SETTINGS); + + if (select == 7) + { + db.addColumn(COLUMN_ZIPCOMPRESS, value); + } + else + { + db.addColumn(COLUMN_ZIPEXTRACT, value); + } + + db.exec(); + + returnToStart(); + } + } + else if (select == 10) + { + if (!value.contains(SUBJECT_SUB, Qt::CaseInsensitive)) + { + errTxt("err: The '" + QString(SUBJECT_SUB) + "' keyword is missing.\n"); + mainTxt("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"); + mainTxt("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"); + mainTxt("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 == 14) + { + if (!isInt(value)) + { + errTxt("err: '" + value + "' is not a valid integer.\n"); + mainTxt("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"); + mainTxt("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 + { + emit enableMoreInput(true); + + printSettings(); + printOptions(); + } + } +} diff --git a/src/commands/admin.h b/src/commands/admin.h new file mode 100644 index 0000000..30be75a --- /dev/null +++ b/src/commands/admin.h @@ -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 +// . + +#include "../common.h" + +class CloseHost : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit CloseHost(QObject *parent = nullptr); +}; + +//-------------------------------------- + +class RestartHost : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit RestartHost(QObject *parent = nullptr); +}; + +//-------------------------------------- + +class ServSettings : public InternCommand +{ + Q_OBJECT + +private: + + int level; + int select; + + void printOptions(); + void printSettings(); + void returnToStart(); + +public: + + static QString cmdName(); + + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit ServSettings(QObject *parent = nullptr); +}; + +#endif // ADMIN_CMDS_H diff --git a/src/commands/auth.cpp b/src/commands/auth.cpp new file mode 100644 index 0000000..5f78ee4 --- /dev/null +++ b/src/commands/auth.cpp @@ -0,0 +1,319 @@ +#include "auth.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 +// . + +Auth::Auth(QObject *parent) : InternCommand(parent) {} + +AuthLog::AuthLog(QObject *parent) : TableViewer(parent) +{ + setParams(TABLE_AUTH_LOG, QStringList() << COLUMN_TIME + << COLUMN_IPADDR + << COLUMN_USERNAME + << COLUMN_AUTH_ATTEMPT + << COLUMN_RECOVER_ATTEMPT + << COLUMN_COUNT + << COLUMN_ACCEPTED, true); +} + +QString Auth::cmdName() {return "auth";} +QString AuthLog::cmdName() {return "ls_auth_log";} + +void Auth::term() +{ + emit enableMoreInput(false); + + newPassword = false; + newUserName = false; + loginOk = false; + + uName.clear(); +} + +void Auth::addToThreshold(const SharedObjs *sharedObjs) +{ + Query db(this); + + db.setType(Query::PUSH, TABLE_AUTH_LOG); + db.addColumn(COLUMN_USERNAME, uName); + db.addColumn(COLUMN_IPADDR, *sharedObjs->sessionAddr); + db.addColumn(COLUMN_AUTH_ATTEMPT, true); + db.addColumn(COLUMN_RECOVER_ATTEMPT, false); + db.addColumn(COLUMN_COUNT, true); + db.addColumn(COLUMN_ACCEPTED, false); + db.exec(); + + db.setType(Query::PULL, TABLE_SERV_SETTINGS); + + uint maxAttempts = 0; + bool isRoot = false; + + if (noCaseMatch(ROOT_USER, uName)) + { + isRoot = true; + + db.addColumn(COLUMN_BAN_LIMIT); + db.exec(); + + maxAttempts = db.getData(COLUMN_BAN_LIMIT).toUInt(); + } + else + { + db.addColumn(COLUMN_LOCK_LIMIT); + db.exec(); + + maxAttempts = db.getData(COLUMN_LOCK_LIMIT).toUInt(); + } + + db.setType(Query::PULL, TABLE_AUTH_LOG); + db.addColumn(COLUMN_IPADDR); + db.addCondition(COLUMN_USERNAME, uName); + db.addCondition(COLUMN_AUTH_ATTEMPT, true); + db.addCondition(COLUMN_COUNT, true); + db.addCondition(COLUMN_ACCEPTED, false); + + if (isRoot) db.addCondition(COLUMN_IPADDR, *sharedObjs->sessionAddr); + + db.exec(); + + if (static_cast(db.rows()) > maxAttempts) + { + if (isRoot) + { + if (!QHostAddress(*sharedObjs->sessionAddr).isLoopback()) + { + db.setType(Query::PUSH, TABLE_IPBANS); + db.addColumn(COLUMN_IPADDR, *sharedObjs->sessionAddr); + db.exec(); + + emit closeSession(); + } + } + else + { + db.setType(Query::UPDATE, TABLE_USERS); + db.addColumn(COLUMN_LOCKED, true); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + } + + term(); + } + else + { + errTxt("err: Access denied.\n\n"); + privTxt("Enter password (leave blank to cancel): "); + } +} + +void Auth::confirmAuth(const SharedObjs *sharedObjs) +{ + *rwSharedObjs->userName = uName; + *rwSharedObjs->displayName = dName; + *rwSharedObjs->userId = uId; + *rwSharedObjs->groupName = getUserGroup(uName); + *rwSharedObjs->hostRank = getRankForGroup(*sharedObjs->groupName); + + Query db(this); + + db.setType(Query::UPDATE, TABLE_AUTH_LOG); + db.addColumn(COLUMN_COUNT, false); + db.addCondition(COLUMN_COUNT, true); + db.addCondition(COLUMN_USERNAME, uName); + db.addCondition(COLUMN_AUTH_ATTEMPT, true); + + if (noCaseMatch(ROOT_USER, uName)) + { + db.addCondition(COLUMN_IPADDR, *sharedObjs->sessionAddr); + } + + db.exec(); + + db.setType(Query::PUSH, TABLE_AUTH_LOG); + db.addColumn(COLUMN_USERNAME, uName); + db.addColumn(COLUMN_IPADDR, *sharedObjs->sessionAddr); + db.addColumn(COLUMN_COUNT, false); + db.addColumn(COLUMN_ACCEPTED, true); + db.addColumn(COLUMN_AUTH_ATTEMPT, true); + db.addColumn(COLUMN_RECOVER_ATTEMPT, false); + db.exec(); + + mainTxt("Access granted.\n"); + + emit authOk(); +} + +void Auth::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + if (moreInputEnabled()) + { + QString text = fromTEXT(binIn); + + if (loginOk) + { + if (newPassword) + { + if (text.isEmpty()) + { + mainTxt("\n"); + term(); + } + else if (!validPassword(text)) + { + errTxt("err: Invalid password. it must be 8-200 chars long containing numbers, mixed case letters and special chars.\n\n"); + privTxt("Enter a new password (leave blank to cancel): "); + } + else + { + updatePassword(uName, text, TABLE_USERS); + + Query db(this); + + db.setType(Query::UPDATE, TABLE_USERS); + db.addColumn(COLUMN_NEED_PASS, false); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + newPassword = false; + + if (newUserName) + { + mainTxt("Enter a new user name: "); + } + else + { + confirmAuth(sharedObjs); + term(); + } + } + } + else if (newUserName) + { + if (text.isEmpty()) + { + mainTxt("\n"); + term(); + } + else if (!validUserName(text)) + { + errTxt("err: Invalid username. it must be 2-24 chars long and contain no spaces.\n\n"); + mainTxt("Enter a new user name (leave blank to cancel): "); + } + else if (!userExists(text)) + { + errTxt("err: The requested User name already exists.\n\n"); + mainTxt("Enter a new user name (leave blank to cancel): "); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_USERS); + db.addColumn(COLUMN_NEED_NAME, false); + db.addColumn(COLUMN_USERNAME, text); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + emit backendDataOut(ASYNC_USER_RENAMED, toTEXT("-old '" + escapeChars(uName, '\\', '\'') + "' -new '" + escapeChars(text, '\\', '\'') + "'"), PUB_IPC); + + uName = text; + newUserName = false; + + confirmAuth(sharedObjs); + term(); + } + } + } + else if (text.isEmpty()) + { + mainTxt("\n"); + term(); + } + else if (!validPassword(text)) + { + addToThreshold(sharedObjs); + } + else if (!auth(uName, text, TABLE_USERS)) + { + addToThreshold(sharedObjs); + } + else + { + loginOk = true; + + if (newPassword) + { + privTxt("Enter a new password (leave blank to cancel): "); + } + else if (newUserName) + { + mainTxt("Enter a new user name (leave blank to cancel): "); + } + else + { + confirmAuth(sharedObjs); + term(); + } + } + } + else + { + QStringList args = parseArgs(binIn, 2); + QString email = getParam("-email", args); + QString name = getParam("-user", args); + + if (!email.isEmpty() && validEmailAddr(email)) name = getUserNameForEmail(email); + + if (name.isEmpty() || !validUserName(name)) + { + errTxt("err: The -user or -email argument is empty, not found or invalid.\n"); + } + else if (!userExists(name)) + { + errTxt("err: No such user.\n"); + } + else if (isLocked(name)) + { + errTxt("err: The requested user account is locked.\n"); + } + else + { + emit enableMoreInput(true); + + uName = name; + + Query db(this); + + db.setType(Query::PULL, TABLE_USERS); + db.addColumn(COLUMN_DISPLAY_NAME); + db.addColumn(COLUMN_NEED_NAME); + db.addColumn(COLUMN_NEED_PASS); + db.addColumn(COLUMN_USER_ID); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + newPassword = db.getData(COLUMN_NEED_PASS).toBool(); + newUserName = db.getData(COLUMN_NEED_NAME).toBool(); + dName = db.getData(COLUMN_DISPLAY_NAME).toString(); + uId = db.getData(COLUMN_USER_ID).toByteArray(); + + privTxt("Enter password (leave blank to cancel): "); + } + } + } +} diff --git a/src/commands/auth.h b/src/commands/auth.h new file mode 100644 index 0000000..9920d93 --- /dev/null +++ b/src/commands/auth.h @@ -0,0 +1,62 @@ +#ifndef AUTH_H +#define AUTH_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 +// . + +#include "../common.h" +#include "table_viewer.h" + +class Auth : public InternCommand +{ + Q_OBJECT + +private: + + QByteArray uId; + QString uName; + QString dName; + bool loginOk; + bool newPassword; + bool newUserName; + + void confirmAuth(const SharedObjs *sharedObjs); + void addToThreshold(const SharedObjs *sharedObjs); + +public: + + static QString cmdName(); + + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit Auth(QObject *parent = nullptr); +}; + +//------------------------ + +class AuthLog : public TableViewer +{ + Q_OBJECT + +public: + + static QString cmdName(); + + explicit AuthLog(QObject *parent = nullptr); +}; + +#endif // AUTH_H diff --git a/src/commands/bans.cpp b/src/commands/bans.cpp new file mode 100644 index 0000000..3253580 --- /dev/null +++ b/src/commands/bans.cpp @@ -0,0 +1,89 @@ +#include "bans.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 +// . + +ListBans::ListBans(QObject *parent) : TableViewer(parent) +{ + setParams(TABLE_IPBANS, QStringList() << COLUMN_TIME << COLUMN_IPADDR, true); +} + +BanIP::BanIP(QObject *parent) : InternCommand(parent) {} +UnBanIP::UnBanIP(QObject *parent) : InternCommand(parent) {} + +QString ListBans::cmdName() {return "ls_bans";} +QString BanIP::cmdName() {return "add_ban";} +QString UnBanIP::cmdName() {return "rm_ban";} + +void BanIP::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString ip = getParam("-ip", args); + + if (ip.isEmpty()) + { + errTxt("err: The ip address argument (-ip) was not found or is empty.\n"); + } + else if (!QHostAddress().setAddress(ip)) + { + errTxt("err: '" + ip + "' is not a valid ip address.\n"); + } + else + { + QHostAddress addr(ip); + + Query db(this); + + db.setType(Query::PUSH, TABLE_IPBANS); + db.addColumn(COLUMN_IPADDR, addr.toString()); + db.exec(); + } + } +} + +void UnBanIP::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString ip = getParam("-ip", args); + + if (ip.isEmpty()) + { + errTxt("err: The ip address argument (-ip) was not found or is empty.\n"); + } + else if (!QHostAddress().setAddress(ip)) + { + errTxt("err: '" + ip + "' is not a valid ip address.\n"); + } + else + { + QHostAddress addr(ip); + + Query db; + + db.setType(Query::DEL, TABLE_IPBANS); + db.addCondition(COLUMN_IPADDR, addr.toString()); + db.exec(); + } + } +} diff --git a/src/commands/bans.h b/src/commands/bans.h new file mode 100644 index 0000000..069fbbb --- /dev/null +++ b/src/commands/bans.h @@ -0,0 +1,64 @@ +#ifndef BAN_CMDS_H +#define BAN_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 +// . + +#include "../common.h" +#include "table_viewer.h" + +class ListBans : public TableViewer +{ + Q_OBJECT + +public: + + static QString cmdName(); + + explicit ListBans(QObject *parent = nullptr); +}; + +//--------------------------------- + +class BanIP : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit BanIP(QObject *parent = nullptr); +}; + +//-------------------------------- + +class UnBanIP : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit UnBanIP(QObject *parent = nullptr); +}; + +#endif // BAN_CMDS_H diff --git a/src/commands/cast.cpp b/src/commands/cast.cpp new file mode 100644 index 0000000..8edcda9 --- /dev/null +++ b/src/commands/cast.cpp @@ -0,0 +1,355 @@ +#include "cast.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 +// . + +Cast::Cast(QObject *parent) : InternCommand(parent) {} +OpenSubChannel::OpenSubChannel(QObject *parent) : InternCommand(parent) {} +CloseSubChannel::CloseSubChannel(QObject *parent) : InternCommand(parent) {} +LsOpenChannels::LsOpenChannels(QObject *parent) : InternCommand(parent) {} +PingPeers::PingPeers(QObject *parent) : InternCommand(parent) {} +AddRDOnlyFlag::AddRDOnlyFlag(QObject *parent) : InternCommand(parent) {} +RemoveRDOnlyFlag::RemoveRDOnlyFlag(QObject *parent) : InternCommand(parent) {} + +ListRDonlyFlags::ListRDonlyFlags(QObject *parent) : TableViewer(parent) +{ + setParams(TABLE_RDONLY_CAST, QStringList() << COLUMN_SUB_CH_ID << COLUMN_ACCESS_LEVEL << COLUMN_CHANNEL_NAME, false); +} + +QString Cast::cmdName() {return "cast";} +QString OpenSubChannel::cmdName() {return "open_sub_ch";} +QString CloseSubChannel::cmdName() {return "close_sub_ch";} +QString LsOpenChannels::cmdName() {return "ls_open_chs";} +QString PingPeers::cmdName() {return "ping_peers";} +QString AddRDOnlyFlag::cmdName() {return "add_rdonly_flag";} +QString RemoveRDOnlyFlag::cmdName() {return "rm_rdonly_flag";} +QString ListRDonlyFlags::cmdName() {return "ls_rdonly_flags";} + +void Cast::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + emit castToPeers(binIn, dType); +} + +void OpenSubChannel::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString ch = getParam("-ch_name", args); + QString sub = getParam("-sub_name", args); + + if (ch.isEmpty()) + { + errTxt("err: Channel name (-ch_name) argument not found or is empty.\n"); + } + else if (sub.isEmpty()) + { + errTxt("err: Sub-Channel name (-sub_name) argument not found or is empty.\n"); + } + else + { + emit openChByName(ch, sub); + } + } +} + +void CloseSubChannel::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString ch = getParam("-ch_name", args); + QString sub = getParam("-sub_name", args); + + if (ch.isEmpty()) + { + errTxt("err: Channel name (-ch_name) argument not found or is empty.\n"); + } + else if (sub.isEmpty()) + { + errTxt("err: Sub-Channel name (-sub_name) argument not found or is empty.\n"); + } + else + { + emit closeChByName(ch, sub); + } + } +} + +void LsOpenChannels::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(binIn); + + if (dType == TEXT) + { + Query db; + QList tableData; + QStringList separators; + QList justLens; + + for (int i = 0; i < 5; ++i) + { + justLens.append(14); + separators.append("-------"); + } + + tableData.append(QStringList() << COLUMN_CHANNEL_NAME << COLUMN_SUB_CH_NAME << COLUMN_CHANNEL_ID << COLUMN_SUB_CH_ID << "read_only"); + tableData.append(separators); + + for (int i = 0; i < sharedObjs->chIds->size(); i += 9) + { + quint64 chId = rdInt(QByteArray::fromRawData(sharedObjs->chIds->data() + i, 8)); + quint64 subId = rdInt(QByteArray::fromRawData(sharedObjs->chIds->data() + (i + 8), 1)); + + if (chId) + { + QStringList columnData; + + db.setType(Query::PULL, TABLE_SUB_CHANNELS); + db.addColumn(COLUMN_SUB_CH_NAME); + db.addColumn(COLUMN_CHANNEL_NAME); + db.addCondition(COLUMN_CHANNEL_ID, chId); + db.addCondition(COLUMN_SUB_CH_ID, subId); + db.exec(); + + QByteArray subCh = QByteArray::fromRawData(sharedObjs->chIds->data() + i, 9); + QString chName = db.getData(COLUMN_CHANNEL_NAME).toString(); + QString subName = db.getData(COLUMN_SUB_CH_NAME).toString(); + QString rdOnly; + + if (chPos(subCh, *sharedObjs->wrAbleChIds) != -1) + { + rdOnly = "1"; + } + else + { + rdOnly = "0"; + } + + columnData.append(chName); + columnData.append(subName); + columnData.append(QString::number(chId)); + columnData.append(QString::number(subId)); + columnData.append(rdOnly); + + for (int k = 0; k < justLens.size(); ++k) + { + if (justLens[k] < columnData[k].size()) justLens[k] = columnData[k].size(); + } + + tableData.append(columnData); + } + } + + mainTxt("\n"); + + for (auto&& row : tableData) + { + for (int i = 0; i < row.size(); ++i) + { + mainTxt(row[i].leftJustified(justLens[i] + 2, ' ')); + } + + mainTxt("\n"); + } + } +} + +void PingPeers::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(binIn); + + if (dType == TEXT) + { + if (!(*sharedObjs->activeUpdate)) + { + errTxt("err: You don't currently have any active update sub-channels open. sending a ping request is pointless because peers won't be able to respond.\n"); + } + else + { + QByteArray castHeader = *sharedObjs->chIds + wrInt(PING_PEERS, 8); + QByteArray data = toPEER_INFO(sharedObjs); + + emit backendDataOut(ASYNC_LIMITED_CAST, castHeader + data, PUB_IPC); + } + } +} + +void AddRDOnlyFlag::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 6); + QString chName = getParam("-ch_name", args); + QString subId = getParam("-sub_id", args); + QString level = getParam("-level", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (subId.isEmpty()) + { + errTxt("err: The sub-channel id (-sub_id) was not found or is empty.\n"); + } + else if (level.isEmpty()) + { + errTxt("err: The privilage level (-level) argument was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (!validSubId(subId)) + { + errTxt("err: Invalid sub-channel id. valid range (0-255).\n"); + } + else if (!validLevel(level, true)) + { + errTxt("err: Invalid privilage level. valid range (1-5).\n"); + } + else if (!channelExists(chName)) + { + errTxt("err: Channel name '" + chName + "' does not exists.\n"); + } + else if (channelAccessLevel(sharedObjs, chName) > ADMIN) + { + errTxt("err: Access denied.\n"); + } + else if (rdOnlyFlagExists(chName, static_cast(subId.toInt()), level.toInt())) + { + errTxt("err: A read only flag for sub-id: " + QString::number(subId.toInt()) + " level: " + QString::number(level.toInt()) + " already exists.\n"); + } + else + { + Query db(this); + + db.setType(Query::PUSH, TABLE_RDONLY_CAST); + db.addColumn(COLUMN_CHANNEL_NAME, chName); + db.addColumn(COLUMN_SUB_CH_ID, subId.toInt()); + db.addColumn(COLUMN_ACCESS_LEVEL, level.toInt()); + db.exec(); + + emit backendDataOut(ASYNC_ADD_RDONLY, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void RemoveRDOnlyFlag::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 6); + QString chName = getParam("-ch_name", args); + QString subId = getParam("-sub_id", args); + QString level = getParam("-level", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (subId.isEmpty()) + { + errTxt("err: The sub-channel id (-sub_id) was not found or is empty.\n"); + } + else if (level.isEmpty()) + { + errTxt("err: The privilage level (-level) argument was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (!validSubId(subId)) + { + errTxt("err: Invalid sub-channel id. valid range (0-255).\n"); + } + else if (!validLevel(level, true)) + { + errTxt("err: Invalid privilage level. valid range (1-5).\n"); + } + else if (!channelExists(chName)) + { + errTxt("err: Channel name '" + chName + "' does not exists.\n"); + } + else if (channelAccessLevel(sharedObjs, chName) > ADMIN) + { + errTxt("err: Access denied.\n"); + } + else if (!rdOnlyFlagExists(chName, static_cast(subId.toInt()), level.toInt())) + { + errTxt("err: A read only flag for sub-id: " + QString::number(subId.toInt()) + " level: " + QString::number(level.toInt()) + " does not exists.\n"); + } + else + { + Query db(this); + + db.setType(Query::DEL, TABLE_RDONLY_CAST); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.addCondition(COLUMN_SUB_CH_ID, subId.toInt()); + db.addCondition(COLUMN_ACCESS_LEVEL, level.toInt()); + db.exec(); + + emit backendDataOut(ASYNC_RM_RDONLY, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void ListRDonlyFlags::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + if (moreInputEnabled()) + { + TableViewer::procBin(sharedObjs, binIn, dType); + } + else + { + QStringList args = parseArgs(binIn, 2); + QString chName = getParam("-ch_name", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (!channelExists(chName)) + { + errTxt("err: Channel name '" + chName + "' does not exists.\n"); + } + else + { + if (channelAccessLevel(sharedObjs, chName) > REGULAR) + { + TableViewer::procBin(sharedObjs, toTEXT("-" + QString(COLUMN_CHANNEL_NAME) + " " + chName + " -" + QString(COLUMN_LOWEST_LEVEL) + " " + QString::number(PUBLIC)), dType); + } + else + { + TableViewer::procBin(sharedObjs, toTEXT("-" + QString(COLUMN_CHANNEL_NAME) + " " + chName), dType); + } + } + } + } +} diff --git a/src/commands/cast.h b/src/commands/cast.h new file mode 100644 index 0000000..4f579fc --- /dev/null +++ b/src/commands/cast.h @@ -0,0 +1,141 @@ +#ifndef CAST_CMDS_H +#define CAST_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 +// . + +#include "../common.h" +#include "table_viewer.h" + +class Cast : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit Cast(QObject *parent = nullptr); +}; + +//---------------------------------- + +class OpenSubChannel : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit OpenSubChannel(QObject *parent = nullptr); +}; + +//----------------------------------- + +class CloseSubChannel : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit CloseSubChannel(QObject *parent = nullptr); +}; + +//---------------------------------- + +class LsOpenChannels : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit LsOpenChannels(QObject *parent = nullptr); +}; + +//---------------------------------- + +class PingPeers : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit PingPeers(QObject *parent = nullptr); +}; + +//--------------------------------- + +class AddRDOnlyFlag : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit AddRDOnlyFlag(QObject *parent = nullptr); +}; + +//------------------------------- + +class RemoveRDOnlyFlag : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit RemoveRDOnlyFlag(QObject *parent = nullptr); +}; + +//------------------------------ + +class ListRDonlyFlags : public TableViewer +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit ListRDonlyFlags(QObject *parent = nullptr); +}; + +#endif // CAST_CMDS_H diff --git a/src/commands/certs.cpp b/src/commands/certs.cpp new file mode 100644 index 0000000..4751891 --- /dev/null +++ b/src/commands/certs.cpp @@ -0,0 +1,277 @@ +#include "certs.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 +// . + +ListCerts::ListCerts(QObject *parent) : TableViewer(parent) +{ + setParams(TABLE_CERT_DATA, QStringList() << COLUMN_COMMON_NAME, false); +} + +CertInfo::CertInfo(QObject *parent) : InternCommand(parent) {} +AddCert::AddCert(QObject *parent) : InternCommand(parent) {} +RemoveCert::RemoveCert(QObject *parent) : InternCommand(parent) {} + +QString ListCerts::cmdName() {return "ls_certs";} +QString CertInfo::cmdName() {return "cert_info";} +QString AddCert::cmdName() {return "add_cert";} +QString RemoveCert::cmdName() {return "rm_cert";} + +void CertInfo::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString coName = getParam("-name", args); + + if (coName.isEmpty()) + { + errTxt("err: The common name argument (-name) was not found or is empty.\n"); + } + else if (!validCommonName(coName)) + { + errTxt("err: Invalid common name. it must be less than 200 chars long and contain no spaces.\n"); + } + else if (!certExists(coName)) + { + errTxt("err: A cert with common name: '" + coName + "' does not exists.\n"); + } + else + { + QString txt; + QTextStream txtOut(&txt); + + Query db(this); + + db.setType(Query::PULL, TABLE_CERT_DATA); + db.addColumn(COLUMN_CERT); + db.addCondition(COLUMN_COMMON_NAME, coName); + db.exec(); + + QSslCertificate cert = toSSLCert(db.getData(COLUMN_CERT).toByteArray()); + + txtOut << "Self Signed: " << boolStr(cert.isSelfSigned()) << endl; + txtOut << "Black Listed: " << boolStr(cert.isBlacklisted()) << endl; + txtOut << "Effective Date: " << cert.effectiveDate().toString("MM/dd/yy") << endl; + txtOut << "Expiry Date: " << cert.expiryDate().toString("MM/dd/yy") << endl; + + mainTxt(txt); + } + } +} + +void AddCert::term() +{ + coName.clear(); + certBa.clear(); + privBa.clear(); + + emit enableMoreInput(false); +} + +void AddCert::run() +{ + Query db(this); + + db.setType(qType, TABLE_CERT_DATA); + db.addColumn(COLUMN_COMMON_NAME, coName); + db.addColumn(COLUMN_CERT, certBa); + db.addColumn(COLUMN_PRIV_KEY, privBa); + db.exec(); + + term(); +} + +void AddCert::ask() +{ + emit enableMoreInput(true); + + mainTxt("Common name: '" + coName + "' already exists. do you want to replace it? (y/n): "); +} + +void AddCert::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if ((dType == TEXT) && moreInputEnabled()) + { + QString ans = fromTEXT(binIn); + + if (noCaseMatch("n", ans)) + { + term(); + } + else if (noCaseMatch("y", ans)) + { + run(); + } + else + { + ask(); + } + } + else if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 7); + QString cert = getParam("-cert", args); + QString priv = getParam("-priv", args); + bool force = argExists("-force", args); + + coName = getParam("-name", args); + + QFile certFile(cert, this); + QFile privFile(priv, this); + + if (coName.isEmpty()) + { + errTxt("err: The common name (-name) argument was not found or empty.\n"); + } + else if (cert.isEmpty()) + { + errTxt("err: The cert file path (-cert) argument was not found or empty.\n"); + } + else if (priv.isEmpty()) + { + errTxt("err: The priv key file path (-priv) argument was not found or empty.\n"); + } + else if (!certFile.exists()) + { + errTxt("err: The given cert file: '" + cert + "' does not exists or is not a file.\n"); + } + else if (!privFile.exists()) + { + errTxt("err: The given priv key file: '" + priv + "' does not exists or is not a file.\n"); + } + else if (!validCommonName(coName)) + { + errTxt("err: The common name must be less than or equal to 200 chars long and contain no spaces.\n"); + } + else if (!certFile.open(QFile::ReadOnly)) + { + errTxt("err: Unable to open the cert file: '" + cert + "' for reading. reason: " + certFile.errorString() + "\n"); + } + else if (!privFile.open(QFile::ReadOnly)) + { + errTxt("err: Unable to open the priv key: '" + priv + "' for reading. reason: " + privFile.errorString() + "\n"); + } + else if (toSSLCert(&certFile).isNull()) + { + errTxt("err: The given cert file is not compatible.\n"); + } + else if (toSSLKey(&privFile).isNull()) + { + errTxt("err: The given private key is not compatible.\n"); + } + else + { + certBa = certFile.readAll(); + privBa = privFile.readAll(); + + if (certExists(coName)) + { + qType = Query::UPDATE; + + if (force) run(); + else ask(); + } + else + { + qType = Query::PUSH; + + run(); + } + } + + certFile.close(); + privFile.close(); + } +} + +void RemoveCert::run() +{ + Query db(this); + + db.setType(Query::DEL, TABLE_CERT_DATA); + db.addCondition(COLUMN_COMMON_NAME, coName); + db.exec(); + + term(); +} + +void RemoveCert::term() +{ + emit enableMoreInput(false); + + coName.clear(); +} + +void RemoveCert::ask() +{ + emit enableMoreInput(true); + + mainTxt("Are you sure you want to remove the cert for common name: " + coName + "? (y/n): "); +} + +void RemoveCert::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if ((dType == TEXT) && moreInputEnabled()) + { + QString ans = fromTEXT(binIn); + + if (noCaseMatch("n", ans)) + { + term(); + } + else if (noCaseMatch("y", ans)) + { + run(); + } + else + { + ask(); + } + } + else if (dType == TEXT) + { + QStringList args = parseArgs(binIn, -1); + QString name = getParam("-name", args); + bool force = argExists("-force", args); + + if (name.isEmpty()) + { + errTxt("err: Common name (-name) argument not found or is empty.\n"); + } + else if (!validCommonName(name)) + { + errTxt("err: The common name must be lass than or equal to 200 chars long and contain no spaces.\n"); + } + else if (!certExists(name)) + { + errTxt("err: The given common name '" + name + "' does not exists.\n"); + } + else + { + coName = name; + + if (force) run(); + else ask(); + } + } +} diff --git a/src/commands/certs.h b/src/commands/certs.h new file mode 100644 index 0000000..c703361 --- /dev/null +++ b/src/commands/certs.h @@ -0,0 +1,99 @@ +#ifndef CERTS_H +#define CERTS_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 +// . + +#include "../common.h" +#include "../make_cert.h" +#include "table_viewer.h" + +class ListCerts : public TableViewer +{ + Q_OBJECT + +public: + + static QString cmdName(); + + explicit ListCerts(QObject *parent = nullptr); +}; + +//-------------------------- + +class CertInfo : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit CertInfo(QObject *parent = nullptr); +}; + +//---------------------------- + +class AddCert : public InternCommand +{ + Q_OBJECT + +private: + + QString coName; + QByteArray certBa; + QByteArray privBa; + Query::QueryType qType; + + void run(); + void ask(); + +public: + + static QString cmdName(); + + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit AddCert(QObject *parent = nullptr); +}; + +//----------------------------- + +class RemoveCert : public InternCommand +{ + Q_OBJECT + +private: + + QString coName; + + void run(); + void ask(); + +public: + + static QString cmdName(); + + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit RemoveCert(QObject *parent = nullptr); +}; + +#endif // CERTS_H diff --git a/src/commands/channels.cpp b/src/commands/channels.cpp new file mode 100644 index 0000000..83f93d1 --- /dev/null +++ b/src/commands/channels.cpp @@ -0,0 +1,959 @@ +#include "channels.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 +// . + +CreateChannel::CreateChannel(QObject *parent) : InternCommand(parent) {} +RemoveChannel::RemoveChannel(QObject *parent) : InternCommand(parent) {} +RenameChannel::RenameChannel(QObject *parent) : InternCommand(parent) {} +SetActiveState::SetActiveState(QObject *parent) : InternCommand(parent) {} +CreateSubCh::CreateSubCh(QObject *parent) : InternCommand(parent) {} +RemoveSubCh::RemoveSubCh(QObject *parent) : InternCommand(parent) {} +RenameSubCh::RenameSubCh(QObject *parent) : InternCommand(parent) {} +InviteToCh::InviteToCh(QObject *parent) : InternCommand(parent) {} +DeclineChInvite::DeclineChInvite(QObject *parent) : InternCommand(parent) {} +AcceptChInvite::AcceptChInvite(QObject *parent) : InternCommand(parent) {} +RemoveChMember::RemoveChMember(QObject *parent) : InternCommand(parent) {} +SetMemberLevel::SetMemberLevel(QObject *parent) : InternCommand(parent) {} +SetSubAcessLevel::SetSubAcessLevel(QObject *parent) : InternCommand(parent) {} +OwnerOverride::OwnerOverride(QObject *parent) : InternCommand(parent) {} + +ListChannels::ListChannels(QObject *parent) : TableViewer(parent) +{ + setParams(TABLE_CH_MEMBERS, QStringList() << COLUMN_CHANNEL_ID << COLUMN_CHANNEL_NAME << COLUMN_PENDING_INVITE << COLUMN_ACCESS_LEVEL << COLUMN_USERNAME, false); +} + +ListSubCh::ListSubCh(QObject *parent) : TableViewer(parent) +{ + setParams(TABLE_SUB_CHANNELS, QStringList() << COLUMN_CHANNEL_NAME << COLUMN_CHANNEL_ID << COLUMN_SUB_CH_ID << COLUMN_SUB_CH_NAME << COLUMN_LOWEST_LEVEL << COLUMN_ACTIVE_UPDATE, false); +} + +SearchChannels::SearchChannels(QObject *parent) : TableViewer(parent) +{ + setParams(TABLE_CHANNELS, QStringList() << COLUMN_CHANNEL_ID << COLUMN_CHANNEL_NAME, false); +} + +ListMembers::ListMembers(QObject *parent) : TableViewer(parent) +{ + setParams(TABLE_CH_MEMBERS, QStringList() << COLUMN_CHANNEL_ID << COLUMN_CHANNEL_NAME << COLUMN_PENDING_INVITE << COLUMN_ACCESS_LEVEL << COLUMN_USERNAME, false); +} + +QString CreateChannel::cmdName() {return "add_ch";} +QString RemoveChannel::cmdName() {return "rm_ch";} +QString RenameChannel::cmdName() {return "rename_ch";} +QString SetActiveState::cmdName() {return "set_active_flag";} +QString CreateSubCh::cmdName() {return "add_sub_ch";} +QString RemoveSubCh::cmdName() {return "rm_sub_ch";} +QString RenameSubCh::cmdName() {return "rename_sub_ch";} +QString ListChannels::cmdName() {return "ls_chs";} +QString ListSubCh::cmdName() {return "ls_sub_chs";} +QString SearchChannels::cmdName() {return "find_ch";} +QString InviteToCh::cmdName() {return "invite_to_ch";} +QString DeclineChInvite::cmdName() {return "decline_ch";} +QString AcceptChInvite::cmdName() {return "accept_ch";} +QString RemoveChMember::cmdName() {return "remove_ch_member";} +QString SetMemberLevel::cmdName() {return "set_member_level";} +QString SetSubAcessLevel::cmdName() {return "set_sub_ch_level";} +QString ListMembers::cmdName() {return "ls_ch_members";} +QString OwnerOverride::cmdName() {return "ch_owner_override";} + +void ListChannels::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(binIn); + + if (moreInputEnabled()) + { + TableViewer::procBin(sharedObjs, binIn, dType); + } + else + { + TableViewer::procBin(sharedObjs, toTEXT("-" + QString(COLUMN_USERNAME) + " " + *sharedObjs->userName), dType); + } +} + +void ListSubCh::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString chName = getParam("-ch_name", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (!channelExists(chName)) + { + errTxt("err: Channel name '" + chName + "' does not exists.\n"); + } + else + { + if (channelAccessLevel(sharedObjs, getChId(chName)) > REGULAR) + { + TableViewer::procBin(sharedObjs, toTEXT("-" + QString(COLUMN_CHANNEL_NAME) + " " + chName + " -" + QString(COLUMN_LOWEST_LEVEL) + " " + QString::number(PUBLIC)), dType); + } + else + { + TableViewer::procBin(sharedObjs, toTEXT("-" + QString(COLUMN_CHANNEL_NAME) + " " + chName), dType); + } + } + } +} + +void SearchChannels::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + if (moreInputEnabled()) + { + TableViewer::procBin(sharedObjs, binIn, dType); + } + else + { + QStringList args = parseArgs(binIn, 4); + QString name = getParam("-name", args); + QString chId = getParam("-id", args); + + if (!name.isEmpty() && !validChName(name)) + { + errTxt("err: '" + name + "' is not a valid channel name.\n"); + } + else if (!chId.isEmpty() && !isInt(chId)) + { + errTxt("err: '" + chId + "' is not a valid channel id.\n"); + } + else if (!name.isEmpty()) + { + TableViewer::procBin(sharedObjs, toTEXT("-" + QString(COLUMN_CHANNEL_NAME) + " " + name), dType); + } + else if (!chId.isEmpty()) + { + TableViewer::procBin(sharedObjs, toTEXT("-" + QString(COLUMN_CHANNEL_ID) + " " + chId), dType); + } + else + { + TableViewer::procBin(sharedObjs, QByteArray(), dType); + } + } + } +} + +void ListMembers::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + if (moreInputEnabled()) + { + TableViewer::procBin(sharedObjs, binIn, dType); + } + else + { + QStringList args = parseArgs(binIn, 2); + QString chName = getParam("-ch_name", args); + QString userFind = getParam("-find", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (channelAccessLevel(sharedObjs, chName) > REGULAR) + { + errTxt("err: You are not currently a member of the channel: '" + chName + "'\n"); + } + else + { + QByteArray argsBa = toTEXT("-" + QString(COLUMN_CHANNEL_NAME) + " " + chName); + + if (!userFind.isEmpty()) + { + argsBa.append(" " + toTEXT("-" + QString(COLUMN_USERNAME) + " " + userFind)); + } + + TableViewer::procBin(sharedObjs, argsBa, dType); + } + } + } +} + +void CreateChannel::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString chName = getParam("-ch_name", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name. it must be between 4-32 chars long and contain no spaces.\n"); + } + else if (channelExists(chName)) + { + errTxt("err: Channel name '" + chName + "' already exists.\n"); + } + else + { + Query db(this); + + db.setType(Query::PUSH, TABLE_CHANNELS); + db.addColumn(COLUMN_CHANNEL_NAME, chName); + db.exec(); + + quint64 chId = getChId(chName); + + db.setType(Query::PUSH, TABLE_CH_MEMBERS); + db.addColumn(COLUMN_CHANNEL_ID, chId); + db.addColumn(COLUMN_USERNAME, *sharedObjs->userName); + db.addColumn(COLUMN_CHANNEL_NAME, chName); + db.addColumn(COLUMN_ACCESS_LEVEL, OWNER); + db.addColumn(COLUMN_PENDING_INVITE, false); + db.exec(); + + args.append("-user"); + args.append("'" + escapeChars(*sharedObjs->userName, '\\', '\'') + "'"); + args.append("-level"); + args.append(QString::number(OWNER)); + args.append("-ch_id"); + args.append(QString::number(chId)); + + emit backendDataOut(ASYNC_NEW_CH_MEMBER, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void RemoveChannel::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString chName = getParam("-ch_name", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (!channelExists(chName)) + { + errTxt("err: Channel name '" + chName + "' does not exists.\n"); + } + else if (channelAccessLevel(sharedObjs, chName) != OWNER) + { + errTxt("err: Only the channel owner can delete it.\n"); + } + else + { + quint64 id = getChId(chName); + + Query db(this); + + db.setType(Query::DEL, TABLE_CHANNELS); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.exec(); + + args.append("-ch_id"); + args.append(QString::number(id)); + + emit backendDataOut(ASYNC_DEL_CH, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void RenameChannel::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString chName = getParam("-ch_name", args); + QString newName = getParam("-new_name", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (newName.isEmpty()) + { + errTxt("err: The new channel name (-new_name) was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (!validChName(newName)) + { + errTxt("err: Invalid new channel name. it must be between 4-32 chars long and contain no spaces.\n"); + } + else if (!channelExists(chName)) + { + errTxt("err: Channel name '" + chName + "' does not exists.\n"); + } + else if (channelAccessLevel(sharedObjs, chName) != OWNER) + { + errTxt("err: Only the channel owner can rename it.\n"); + } + else if (channelExists(newName)) + { + errTxt("err: Channel name '" + newName + "' already exists.\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_CHANNELS); + db.addColumn(COLUMN_CHANNEL_NAME, newName); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.exec(); + + emit backendDataOut(ASYNC_RENAME_CH, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void SetActiveState::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 6); + QString chName = getParam("-ch_name", args); + QString subName = getParam("-sub_name", args); + QString state = getParam("-state", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (subName.isEmpty()) + { + errTxt("err: The sub-channel name (-sub_name) was not found or is empty.\n"); + } + else if (state.isEmpty()) + { + errTxt("err: The active flag state (-state) argument was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (!validChName(subName)) + { + errTxt("err: Invalid sub-channel name.\n"); + } + else if (!isBool(state)) + { + errTxt("err: '" + state + "' is not a valid boolean value. it must be 0 or 1.\n"); + } + else if (!channelExists(chName)) + { + errTxt("err: Channel name '" + chName + "' does not exists.\n"); + } + else if (channelAccessLevel(sharedObjs, chName) > ADMIN) + { + errTxt("err: Access denied.\n"); + } + else if (!channelSubExists(chName, subName)) + { + errTxt("err: Sub-channel name '" + chName + "' does not exists.\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_SUB_CHANNELS); + db.addColumn(COLUMN_ACTIVE_UPDATE, static_cast(state.toInt())); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.addCondition(COLUMN_SUB_CH_NAME, subName); + db.exec(); + + 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"); + } + else + { + emit backendDataOut(ASYNC_CH_ACT_FLAG, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } + } +} + +void CreateSubCh::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString chName = getParam("-ch_name", args); + QString subName = getParam("-sub_name", args); + int subId = 0; + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (subName.isEmpty()) + { + errTxt("err: The sub-channel name (-sub_name) was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (!validChName(subName)) + { + errTxt("err: Invalid sub-channel name. it must be between 4-32 chars long and contain no spaces.\n"); + } + else if (!channelExists(chName)) + { + errTxt("err: Channel name '" + chName + "' does not exists.\n"); + } + else if (channelAccessLevel(sharedObjs, chName) > ADMIN) + { + errTxt("err: Access denied.\n"); + } + else if (channelSubExists(chName, subName)) + { + errTxt("err: Sub-channel name '" + subName + "' already exists.\n"); + } + else if (!genSubId(chName, &subId)) + { + errTxt("err: This channel has reached the maximum amount sub-channels it can have (" + QString::number(maxSubChannels()) + ").\n"); + } + else + { + quint64 chId = getChId(chName); + + Query db(this); + + db.setType(Query::PUSH, TABLE_SUB_CHANNELS); + db.addColumn(COLUMN_SUB_CH_NAME, subName); + db.addColumn(COLUMN_SUB_CH_ID, subId); + db.addColumn(COLUMN_LOWEST_LEVEL, REGULAR); + db.addColumn(COLUMN_CHANNEL_NAME, chName); + db.addColumn(COLUMN_CHANNEL_ID, chId); + db.addColumn(COLUMN_ACTIVE_UPDATE, false); + db.exec(); + + args.append("-ch_id"); + args.append(QString::number(chId)); + args.append("-level"); + args.append(QString::number(REGULAR)); + args.append("-sub_id"); + args.append(QString::number(subId)); + + emit backendDataOut(ASYNC_NEW_SUB_CH, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void RemoveSubCh::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString chName = getParam("-ch_name", args); + QString subName = getParam("-sub_name", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (subName.isEmpty()) + { + errTxt("err: The sub-channel name (-sub_name) was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (!validChName(subName)) + { + errTxt("err: Invalid sub-channel name.\n"); + } + else if (channelAccessLevel(sharedObjs, chName) > ADMIN) + { + errTxt("err: Access denied.\n"); + } + else if (!channelSubExists(chName, subName)) + { + errTxt("err: Sub-channel name '" + subName + "' does not exists.\n"); + } + else + { + quint64 chId = getChId(chName); + uchar subId = getSubId(chName, subName); + + Query db(this); + + db.setType(Query::DEL, TABLE_SUB_CHANNELS); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.addCondition(COLUMN_SUB_CH_NAME, subName); + db.exec(); + + args.append("-ch_id"); + args.append(QString::number(chId)); + args.append("-sub_id"); + args.append(QString::number(subId)); + + emit backendDataOut(ASYNC_RM_SUB_CH, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void RenameSubCh::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 6); + QString chName = getParam("-ch_name", args); + QString subName = getParam("-sub_name", args); + QString newName = getParam("-new_name", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (subName.isEmpty()) + { + errTxt("err: The sub-channel name (-sub_name) argument was not found or is empty.\n"); + } + else if (newName.isEmpty()) + { + errTxt("err: The new sub-channel name (-new_name) argument was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (!validChName(subName)) + { + errTxt("err: Invalid sub-channel name.\n"); + } + else if (!validChName(newName)) + { + errTxt("err: Invalid new sub-channel name. it must be between 4-32 chars long and contain no spaces.\n"); + } + else if (channelAccessLevel(sharedObjs, chName) > ADMIN) + { + errTxt("err: Access denied.\n"); + } + else if (!channelSubExists(chName, subName)) + { + errTxt("err: Sub-channel name '" + subName + "' does not exists.\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_SUB_CHANNELS); + db.addColumn(COLUMN_SUB_CH_NAME, newName); + db.addCondition(COLUMN_SUB_CH_NAME, subName); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.exec(); + + emit backendDataOut(ASYNC_RENAME_SUB_CH, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void InviteToCh::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString chName = getParam("-ch_name", args); + QString uName = getParam("-user", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (uName.isEmpty()) + { + errTxt("err: The user name (-user) argument was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (!validUserName(uName)) + { + errTxt("err: Invalid user name.\n"); + } + else if (!channelExists(chName)) + { + errTxt("err: Channel name '" + chName + "' does not exists.\n"); + } + else if (!userExists(uName)) + { + errTxt("err: User name '" + uName + "' does not exists.\n"); + } + else if (channelAccessLevel(sharedObjs, chName) > OFFICER) + { + errTxt("err: Access denied.\n"); + } + else if (inviteExists(uName, chName)) + { + errTxt("err: User name '" + uName + "' already has an invitation to channel '" + chName + ".'\n"); + } + else if (channelAccessLevel(uName, chName) < PUBLIC) + { + errTxt("err: User name '" + uName + "' is already a member of the requested channel '" + chName + ".'\n"); + } + else + { + Query db(this); + + db.setType(Query::PUSH, TABLE_CH_MEMBERS); + db.addColumn(COLUMN_USERNAME, uName); + db.addColumn(COLUMN_CHANNEL_NAME, chName); + db.addColumn(COLUMN_CHANNEL_ID, getChId(chName)); + db.addColumn(COLUMN_PENDING_INVITE, true); + db.addColumn(COLUMN_ACCESS_LEVEL, REGULAR); + db.exec(); + + emit backendDataOut(ASYNC_INVITED_TO_CH, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void DeclineChInvite::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString chName = getParam("-ch_name", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (!channelExists(chName)) + { + errTxt("err: Channel name '" + chName + "' does not exists.\n"); + } + else if (!inviteExists(*sharedObjs->userName, chName)) + { + errTxt("err: You don't currently have an invitation to channel '" + chName + ".'\n"); + } + else + { + Query db(this); + + db.setType(Query::DEL, TABLE_CH_MEMBERS); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.addCondition(COLUMN_USERNAME, *sharedObjs->userName); + db.exec(); + + args.append("-ch_id"); + args.append(QString::number(getChId(chName))); + args.append("-user"); + args.append("'" + escapeChars(*sharedObjs->userName, '\\', '\'') + "'"); + + emit backendDataOut(ASYNC_RM_CH_MEMBER, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void AcceptChInvite::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString chName = getParam("-ch_name", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (!channelExists(chName)) + { + errTxt("err: Channel name '" + chName + "' does not exists.\n"); + } + else if (!inviteExists(*sharedObjs->userName, chName)) + { + errTxt("err: You don't currently have an invitation to channel '" + chName + ".'\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_CH_MEMBERS); + db.addColumn(COLUMN_PENDING_INVITE, false); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.addCondition(COLUMN_USERNAME, *sharedObjs->userName); + db.exec(); + + args.append("-user"); + args.append("'" + escapeChars(*sharedObjs->userName, '\\', '\'') + "'"); + + emit backendDataOut(ASYNC_INVITE_ACCEPTED, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void RemoveChMember::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString chName = getParam("-ch_name", args); + QString uName = getParam("-user", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (uName.isEmpty()) + { + errTxt("err: The user name (-user) argument was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (!validUserName(uName)) + { + errTxt("err: Invalid user name.\n"); + } + else if (!channelExists(chName)) + { + errTxt("err: Channel name '" + chName + "' does not exists.\n"); + } + else if (!userExists(uName)) + { + errTxt("err: User name '" + uName + "' does not exists.\n"); + } + else if (!allowMemberDel(sharedObjs, uName, chName)) + { + errTxt("err: Access denied.\n"); + } + else + { + quint64 id = getChId(chName); + + Query db(this); + + db.setType(Query::DEL, TABLE_CH_MEMBERS); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + args.append("-ch_id"); + args.append(QString::number(id)); + + emit backendDataOut(ASYNC_RM_CH_MEMBER, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void SetMemberLevel::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 6); + QString chName = getParam("-ch_name", args); + QString uName = getParam("-user", args); + QString level = getParam("-level", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (uName.isEmpty()) + { + errTxt("err: The user name (-user) argument was not found or is empty.\n"); + } + else if (level.isEmpty()) + { + errTxt("err: The privilege level (-level) argument was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (!validUserName(uName)) + { + errTxt("err: Invalid user name.\n"); + } + else if (!validLevel(level, false)) + { + errTxt("err: Invalid privilege level. it must be an integer between 1-4.\n"); + } + else if (!channelExists(chName)) + { + errTxt("err: Channel name '" + chName + "' does not exists.\n"); + } + else if (!userExists(uName)) + { + errTxt("err: User name '" + uName + "' does not exists.\n"); + } + else if (!allowLevelChange(sharedObjs, level.toInt(), chName)) + { + errTxt("err: Access denied.\n"); + } + else if (channelAccessLevel(uName, chName) < PUBLIC) + { + errTxt("err: The target user '" + uName + "' is not a member of the channel.\n"); + } + else + { + quint64 id = getChId(chName); + + Query db(this); + + db.setType(Query::PULL, TABLE_CH_MEMBERS); + db.addColumn(COLUMN_USERNAME); + db.addCondition(COLUMN_ACCESS_LEVEL, OWNER); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.exec(); + + QString owner = db.getData(COLUMN_USERNAME).toString(); + + db.setType(Query::UPDATE, TABLE_CH_MEMBERS); + db.addColumn(COLUMN_ACCESS_LEVEL, level.toInt()); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + args.append("-ch_id"); + args.append(QString::number(id)); + + emit backendDataOut(ASYNC_MEM_LEVEL_CHANGED, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + + if (level.toInt() == OWNER) + { + db.setType(Query::UPDATE, TABLE_CH_MEMBERS); + db.addColumn(COLUMN_ACCESS_LEVEL, ADMIN); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.addCondition(COLUMN_USERNAME, owner); + db.exec(); + + args.clear(); + args.append("-user"); + args.append("'" + escapeChars(owner, '\\', '\'') + "'"); + args.append("-ch_name"); + args.append("'" + escapeChars(chName, '\\', '\'') + "'"); + args.append("-ch_id"); + args.append(QString::number(id)); + args.append("-level"); + args.append(QString::number(ADMIN)); + + emit backendDataOut(ASYNC_MEM_LEVEL_CHANGED, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } + } +} + +void SetSubAcessLevel::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 6); + QString chName = getParam("-ch_name", args); + QString subName = getParam("-sub_name", args); + QString level = getParam("-level", args); + + if (chName.isEmpty()) + { + errTxt("err: The channel name (-ch_name) argument was not found or is empty.\n"); + } + else if (subName.isEmpty()) + { + errTxt("err: The sub-channel name (-sub_name) argument was not found or is empty.\n"); + } + else if (level.isEmpty()) + { + errTxt("err: The privilege level (-level) argument was not found or is empty.\n"); + } + else if (!validChName(chName)) + { + errTxt("err: Invalid channel name.\n"); + } + else if (!validChName(subName)) + { + errTxt("err: Invalid sub-channel name.\n"); + } + else if (!validLevel(level, true)) + { + errTxt("err: Invalid privilege level. it must be an integer between 1-5.\n"); + } + else if (channelAccessLevel(sharedObjs, chName) > ADMIN) + { + errTxt("err: Access denied.\n"); + } + else if (!channelSubExists(chName, subName)) + { + errTxt("err: Sub-channel name '" + subName + "' does not exists.\n"); + } + else + { + quint64 chId = getChId(chName); + uchar subId = getSubId(chName, subName); + + Query db(this); + + db.setType(Query::UPDATE, TABLE_SUB_CHANNELS); + db.addColumn(COLUMN_LOWEST_LEVEL, level.toInt()); + db.addCondition(COLUMN_SUB_CH_NAME, subName); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.exec(); + + args.append("-ch_id"); + args.append(QString::number(chId)); + args.append("-sub_id"); + args.append(QString::number(subId)); + + emit backendDataOut(ASYNC_SUB_CH_LEVEL_CHG, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void OwnerOverride::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString state = getParam("-state", args); + + if (state.isEmpty()) + { + errTxt("err: The flag state (-state) argument was not found or is empty.\n"); + } + else if (!isBool(state)) + { + errTxt("err: Invalid bool value for -state, it must be 1 or 0.\n"); + } + else + { + *rwSharedObjs->chOwnerOverride = state.toInt(); + } + } +} diff --git a/src/commands/channels.h b/src/commands/channels.h new file mode 100644 index 0000000..e77b7e5 --- /dev/null +++ b/src/commands/channels.h @@ -0,0 +1,291 @@ +#ifndef CHANNELS_H +#define CHANNELS_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 +// . + +#include "../common.h" +#include "table_viewer.h" + +class CreateChannel : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit CreateChannel(QObject *parent = nullptr); +}; + +//----------------------- + +class RemoveChannel : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit RemoveChannel(QObject *parent = nullptr); +}; + +//---------------------- + +class RenameChannel : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit RenameChannel(QObject *parent = nullptr); +}; + +//------------------------ + +class SetActiveState : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit SetActiveState(QObject *parent = nullptr); +}; + +//------------------------- + +class CreateSubCh : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit CreateSubCh(QObject *parent = nullptr); +}; + +//------------------------- + +class RemoveSubCh : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit RemoveSubCh(QObject *parent = nullptr); +}; + +//-------------------------- + +class RenameSubCh : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit RenameSubCh(QObject *parent = nullptr); +}; + +//------------------------ + +class ListSubCh : public TableViewer +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit ListSubCh(QObject *parent = nullptr); +}; + +//-------------------------- + +class ListChannels : public TableViewer +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit ListChannels(QObject *parent = nullptr); +}; + +//---------------------------- + +class SearchChannels : public TableViewer +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit SearchChannels(QObject *parent = nullptr); +}; + +//------------------------------ + +class InviteToCh : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit InviteToCh(QObject *parent = nullptr); +}; + +//------------------------------- + +class DeclineChInvite : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit DeclineChInvite(QObject *parent = nullptr); +}; + +//------------------------------- + +class AcceptChInvite : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit AcceptChInvite(QObject *parent = nullptr); +}; + +//------------------------------ + +class RemoveChMember : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit RemoveChMember(QObject *parent = nullptr); +}; + +//------------------------------ + +class SetMemberLevel : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit SetMemberLevel(QObject *parent = nullptr); +}; + +//------------------------------ + +class SetSubAcessLevel : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit SetSubAcessLevel(QObject *parent = nullptr); +}; + +//----------------------------- + +class ListMembers : public TableViewer +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit ListMembers(QObject *parent = nullptr); +}; + +//----------------------------- + +class OwnerOverride : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit OwnerOverride(QObject *parent = nullptr); +}; + +#endif // CHANNELS_H diff --git a/src/commands/cmd_ranks.cpp b/src/commands/cmd_ranks.cpp new file mode 100644 index 0000000..1d86b72 --- /dev/null +++ b/src/commands/cmd_ranks.cpp @@ -0,0 +1,107 @@ +#include "cmd_ranks.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 +// . + +LsCmdRanks::LsCmdRanks(QObject *parent) : TableViewer(parent) +{ + setParams(TABLE_CMD_RANKS, QStringList() << COLUMN_COMMAND << COLUMN_HOST_RANK, false); +} + +AssignCmdRank::AssignCmdRank(QObject *parent) : InternCommand(parent) {} +RemoveCmdRank::RemoveCmdRank(QObject *parent) : InternCommand(parent) {} + +QString LsCmdRanks::cmdName() {return "ls_ranked_cmds";} +QString AssignCmdRank::cmdName() {return "add_ranked_cmd";} +QString RemoveCmdRank::cmdName() {return "rm_ranked_cmd";} + +void AssignCmdRank::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString cmdName = getParam("-command", args); + QString rank = getParam("-rank", args); + + if (cmdName.isEmpty()) + { + errTxt("err: The command name (-command) argument was not found or is empty.\n"); + } + else if (rank.isEmpty()) + { + errTxt("err: The rank (-rank) argument was not found or is empty.\n"); + } + else if (!isInt(rank)) + { + errTxt("err: The given rank is not a valid unsigned integer.\n"); + } + else if (!validCommandName(cmdName)) + { + errTxt("err: Invalid command name. it must be 1-64 chars long and can only contain letters, numbers, '_' or '?'.\n"); + } + else if (commandHasRank(cmdName)) + { + errTxt("err: The given command name already has an assigned rank.\n"); + } + else + { + Query db(this); + + db.setType(Query::PUSH, TABLE_CMD_RANKS); + db.addColumn(COLUMN_COMMAND, cmdName); + db.addColumn(COLUMN_HOST_RANK, rank.toUInt()); + db.exec(); + + emit backendDataOut(ASYNC_CMD_RANKS_CHANGED, QByteArray(), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void RemoveCmdRank::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString cmdName = getParam("-command", args); + + if (cmdName.isEmpty()) + { + errTxt("err: The command name (-command) argument was not found or is empty.\n"); + } + else if (!validCommandName(cmdName)) + { + errTxt("err: Invalid command name.\n"); + } + else if (!commandHasRank(cmdName)) + { + errTxt("err: The given command name does not have an assigned rank.\n"); + } + else + { + Query db(this); + + db.setType(Query::DEL, TABLE_CMD_RANKS); + db.addCondition(COLUMN_COMMAND, cmdName); + db.exec(); + + emit backendDataOut(ASYNC_CMD_RANKS_CHANGED, QByteArray(), PUB_IPC_WITH_FEEDBACK); + } + } +} diff --git a/src/commands/cmd_ranks.h b/src/commands/cmd_ranks.h new file mode 100644 index 0000000..a2b5b50 --- /dev/null +++ b/src/commands/cmd_ranks.h @@ -0,0 +1,64 @@ +#ifndef CMD_RANKS_H +#define CMD_RANKS_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 +// . + +#include "../common.h" +#include "table_viewer.h" + +class LsCmdRanks : public TableViewer +{ + Q_OBJECT + +public: + + static QString cmdName(); + + explicit LsCmdRanks(QObject *parent = nullptr); +}; + +//------------------------ + +class AssignCmdRank : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit AssignCmdRank(QObject *parent = nullptr); +}; + +//------------------------ + +class RemoveCmdRank : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit RemoveCmdRank(QObject *parent = nullptr); +}; + +#endif // CMD_RANKS_H diff --git a/src/commands/cmd_state.cpp b/src/commands/cmd_state.cpp new file mode 100644 index 0000000..2917fec --- /dev/null +++ b/src/commands/cmd_state.cpp @@ -0,0 +1,105 @@ +#include "cmd_state.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 +// . + +Term::Term(QObject *parent) : InternCommand(parent) {} +Pause::Pause(QObject *parent) : InternCommand(parent) {} +Resume::Resume(QObject *parent) : InternCommand(parent) {} + +QString Term::cmdName() {return "term";} +QString Pause::cmdName() {return "pause";} +QString Resume::cmdName() {return "resume";} + +void Term::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == CMD_ID) + { + if (binIn.isEmpty()) + { + emit termAllCommands(); + } + else + { + auto cmdId = static_cast(rdInt(binIn)); + + if (!sharedObjs->cmdNames->contains(cmdId)) + { + errTxt("err: No such command id: '" + QString::number(cmdId) + "'\n"); + } + else + { + emit termCommandId(cmdId); + } + } + } +} + +void Pause::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == CMD_ID) + { + if (binIn.isEmpty()) + { + *rwSharedObjs->pausedCmds = *rwSharedObjs->activeLoopCmds; + } + else + { + auto cmdId = static_cast(rdInt(binIn)); + + if (!sharedObjs->cmdNames->contains(cmdId)) + { + errTxt("err: No such command id: '" + QString::number(cmdId) + "'\n"); + } + else if (!sharedObjs->activeLoopCmds->contains(cmdId)) + { + errTxt("err: The command is not currently in a loop state.\n"); + } + else + { + uniqueAdd(cmdId, *rwSharedObjs->pausedCmds); + } + } + } +} + +void Resume::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == CMD_ID) + { + if (binIn.isEmpty()) + { + rwSharedObjs->pausedCmds->clear(); + } + else + { + auto cmdId = static_cast(rdInt(binIn)); + + if (!sharedObjs->cmdNames->contains(cmdId)) + { + errTxt("err: No such command id: '" + QString::number(cmdId) + "'\n"); + } + else if (!sharedObjs->pausedCmds->contains(cmdId)) + { + errTxt("err: The command is not currently in a paused state.\n"); + } + else + { + rwSharedObjs->pausedCmds->removeAll(cmdId); + } + } + } +} diff --git a/src/commands/cmd_state.h b/src/commands/cmd_state.h new file mode 100644 index 0000000..cf79b2b --- /dev/null +++ b/src/commands/cmd_state.h @@ -0,0 +1,65 @@ +#ifndef CMD_STATE_H +#define CMD_STATE_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 +// . + +#include "../common.h" + +class Term : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit Term(QObject *parent = nullptr); +}; + +//----------------------- + +class Pause : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit Pause(QObject *parent = nullptr); +}; + +//---------------------- + +class Resume : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit Resume(QObject *parent = nullptr); +}; + +#endif // CMD_STATE_H diff --git a/src/commands/command.cpp b/src/commands/command.cpp new file mode 100644 index 0000000..aec6ebc --- /dev/null +++ b/src/commands/command.cpp @@ -0,0 +1,70 @@ +#include "command.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 +// . + +SharedObjs::SharedObjs(QObject *parent) : QObject(parent) +{ + p2pAccepted = nullptr; + p2pPending = nullptr; + chIds = nullptr; + wrAbleChIds = nullptr; + chList = nullptr; + activeUpdate = nullptr; + chOwnerOverride = nullptr; + sessionAddr = nullptr; + userName = nullptr; + groupName = nullptr; + displayName = nullptr; + appName = nullptr; + clientMajor = nullptr; + clientMinor = nullptr; + clientPatch = nullptr; + sessionId = nullptr; + userId = nullptr; + moreInputCmds = nullptr; + activeLoopCmds = nullptr; + pausedCmds = nullptr; + hostRank = nullptr; + cmdNames = nullptr; +} + +bool ExternCommand::errState() +{ + return errSent; +} + +void ExternCommand::mainTxt(const QString &txt) +{ + emit dataToClient(QTextCodec::codecForName(TXT_CODEC)->fromUnicode(txt).mid(2), TEXT); +} + +void ExternCommand::errTxt(const QString &txt) +{ + errSent = true; + + emit dataToClient(QTextCodec::codecForName(TXT_CODEC)->fromUnicode(txt).mid(2), ERR); +} + +void ExternCommand::privTxt(const QString &txt) +{ + emit dataToClient(QTextCodec::codecForName(TXT_CODEC)->fromUnicode(txt).mid(2), PRIV_TEXT); +} + +void ExternCommand::bigTxt(const QString &txt) +{ + emit dataToClient(QTextCodec::codecForName(TXT_CODEC)->fromUnicode(txt).mid(2), BIG_TEXT); +} diff --git a/src/commands/command.h b/src/commands/command.h new file mode 100644 index 0000000..b8a93ca --- /dev/null +++ b/src/commands/command.h @@ -0,0 +1,182 @@ +#ifndef EXTERN_COMMAND_H +#define EXTERN_COMMAND_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 +// . + +#define TXT_CODEC "UTF-16LE" +#define TXT_CODEC_BITS 16 +#define MOD_LOADER_IID "MCRI.host.module" + +#include +#include +#include +#include +#include + +enum TypeID +{ + GEN_FILE = 30, + TEXT = 31, + ERR = 32, + PRIV_TEXT = 33, + IDLE = 34, + HOST_CERT = 35, + FILE_INFO = 36, + PEER_INFO = 37, + MY_INFO = 38, + PEER_STAT = 39, + P2P_REQUEST = 40, + P2P_CLOSE = 41, + P2P_OPEN = 42, + BYTES = 43, + SESSION_ID = 44, + NEW_CMD = 45, + CMD_ID = 46, + BIG_TEXT = 47 +}; + +enum ChannelMemberLevel +{ + OWNER = 1, + ADMIN = 2, + OFFICER = 3, + REGULAR = 4, + PUBLIC = 5 +}; + +class ExternCommand; + +class SharedObjs : public QObject +{ + Q_OBJECT + +public: + + const QHash *cmdNames; + const QList *chList; + const QList *p2pAccepted; + const QList *p2pPending; + const QList *moreInputCmds; + const QList *activeLoopCmds; + const QList *pausedCmds; + const QString *sessionAddr; + const QString *userName; + const QString *groupName; + const QString *displayName; + const QString *appName; + const ushort *clientMajor; + const ushort *clientMinor; + const ushort *clientPatch; + const QByteArray *chIds; + const QByteArray *wrAbleChIds; + const QByteArray *sessionId; + const QByteArray *userId; + const bool *activeUpdate; + const bool *chOwnerOverride; + const uint *hostRank; + + explicit SharedObjs(QObject *parent = nullptr); +}; + +class ExternCommand : public QObject +{ + Q_OBJECT + +protected: + + void mainTxt(const QString &txt); + void errTxt(const QString &txt); + void privTxt(const QString &txt); + void bigTxt(const QString &txt); + +public: + + explicit ExternCommand(QObject *parent = nullptr) : QObject(parent) {} + + virtual ~ExternCommand() {} + + virtual void procBin(const SharedObjs *, const QByteArray &, uchar) {} + virtual void aboutToDelete() {} + virtual void term() {} + virtual bool handlesGenfile() {return false;} + virtual bool errState(); + virtual QString shortText() {return "";} + virtual QString ioText() {return "";} + virtual QString longText() {return "";} + virtual QString libText() {return "";} + virtual QStringList internRequest() {return QStringList();} + + QHash internCommands; + quint16 cmdId; + bool errSent; + bool inLoopMode; + bool inMoreInputMode; + +signals: + + void dataToClient(const QByteArray &data, uchar typeId = TEXT); + void castToPeers(const QByteArray &data, uchar typeId = TEXT); + void toPeer(const QByteArray &dst, const QByteArray &data, uchar typeId = TEXT); + void closeChByName(const QString &ch, const QString &sub); + void closeChById(quint64 id, uchar subId); + void openChByName(const QString &ch, const QString &sub); + void openChById(quint64 id, uchar subId); + void enableLoop(bool state); + void enableMoreInput(bool state); + void closeSession(); + void cmdFinished(); + void logout(); +}; + +class CommandLoader : public QObject +{ + Q_OBJECT + +public: + + explicit CommandLoader(QObject *parent = nullptr) : QObject(parent) {} + + virtual ~CommandLoader() {} + + virtual QStringList pubCmdList() {return QStringList();} + virtual QStringList cmdList() {return QStringList();} + virtual QStringList rankExemptList() {return QStringList();} + virtual ExternCommand *cmdObj(const QString &) {return nullptr;} +}; + +class ModCommandLoader : public CommandLoader +{ + Q_OBJECT + +public: + + explicit ModCommandLoader(QObject *parent = nullptr) : CommandLoader(parent) {} + + virtual ~ModCommandLoader() {} + + virtual void modPath(const QString &) {} + virtual void aboutToDelete() {} + virtual bool hostRevOk(quint64) {return false;} + virtual QString lastError() {return "";} + virtual quint64 rev() {return 0;} +}; + +QT_BEGIN_NAMESPACE +Q_DECLARE_INTERFACE(ModCommandLoader, MOD_LOADER_IID) +QT_END_NAMESPACE + +#endif // EXTERN_COMMAND_H diff --git a/src/commands/fs.cpp b/src/commands/fs.cpp new file mode 100644 index 0000000..9220865 --- /dev/null +++ b/src/commands/fs.cpp @@ -0,0 +1,812 @@ +#include "fs.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 +// . + +DownloadFile::DownloadFile(QObject *parent) : InternCommand(parent) {file = new QFile(this);} +UploadFile::UploadFile(QObject *parent) : InternCommand(parent) {file = new QFile(this);} +Delete::Delete(QObject *parent) : InternCommand(parent) {} +Copy::Copy(QObject *parent) : InternCommand(parent) {src = new QFile(this); dst = new QFile(this);} +Move::Move(QObject *parent) : Copy(parent) {} +MakePath::MakePath(QObject *parent) : InternCommand(parent) {} +ListFiles::ListFiles(QObject *parent) : InternCommand(parent) {} +FileInfo::FileInfo(QObject *parent) : InternCommand(parent) {} +ChangeDir::ChangeDir(QObject *parent) : InternCommand(parent) {} + +QString DownloadFile::cmdName() {return "fs_download";} +QString UploadFile::cmdName() {return "fs_upload";} +QString Delete::cmdName() {return "fs_delete";} +QString Copy::cmdName() {return "fs_copy";} +QString Move::cmdName() {return "fs_move";} +QString MakePath::cmdName() {return "fs_mkpath";} +QString ListFiles::cmdName() {return "fs_list";} +QString FileInfo::cmdName() {return "fs_info";} +QString ChangeDir::cmdName() {return "fs_cd";} + +bool DownloadFile::handlesGenfile() +{ + return true; +} + +void DownloadFile::term() +{ + file->close(); + + buffSize = static_cast(qPow(2, MAX_FRAME_BITS) - 1); + ssMode = false; + dataSent = 0; + len = 0; + + emit enableLoop(false); + emit enableMoreInput(false); +} + +void DownloadFile::sendChunk() +{ + if (buffSize > len) buffSize = len; + + QByteArray data = file->read(buffSize); + + dataSent += data.size(); + + emit dataToClient(data, GEN_FILE); + + mainTxt(QString::number(dataSent) + "/" + QString::number(len) + "\n"); + + if ((dataSent >= len) || file->atEnd()) + { + term(); + } +} + +void DownloadFile::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if ((dType == GEN_FILE) && (moreInputEnabled() || loopEnabled())) + { + sendChunk(); + } + else if (dType == GEN_FILE) + { + QStringList args = parseArgs(binIn, 11); + QString path = getParam("-remote_file", args); + QString offStr = getParam("-offset", args); + QString lenStr = getParam("-len", args); + + file->setFileName(path); + + if (path.isEmpty()) + { + errTxt("err: -remote_file not found or is empty.\n"); + } + else if (!file->exists()) + { + errTxt("err: File not found.\n"); + } + else if (!offStr.isEmpty() && !isInt(offStr)) + { + errTxt("err: Offset '" + offStr + "' is not a valid integer.\n"); + } + else if (!isInt(lenStr)) + { + errTxt("err: Len '" + lenStr + "' is not valid integer.\n"); + } + else if (!QFileInfo(path).isFile()) + { + errTxt("err: The remote file is not a file at all.\n"); + } + else if (!file->open(QFile::ReadOnly)) + { + errTxt("err: Unable to open remote file for reading. reason: " + file->errorString() + "\n"); + } + else + { + QString genfileRet = "-from_host"; + + ssMode = argExists("-single_step", args); + len = lenStr.toLongLong(); + + if ((len == 0) || (len > file->size())) + { + genfileRet.append(" -len " + QString::number(len)); + + len = file->size(); + } + + file->seek(offStr.toLongLong()); + + emit enableMoreInput(true); + emit enableLoop(!ssMode); + emit dataToClient(toTEXT(genfileRet), GEN_FILE); + } + } +} + +bool UploadFile::handlesGenfile() +{ + return true; +} + +void UploadFile::term() +{ + file->close(); + + force = false; + confirm = false; + ssMode = false; + dataReceived = 0; + len = 0; + mode = nullptr; + + emit enableLoop(false); + emit enableMoreInput(false); +} + +void UploadFile::wrToFile(const QByteArray &data) +{ + dataReceived += data.size(); + + file->write(data); + + mainTxt(QString::number(dataReceived) + "/" + QString::number(len) + "\n"); + + if (dataReceived >= len) + { + term(); + } + else if (ssMode) + { + emit dataToClient(QByteArray(), GEN_FILE); + } +} + +void UploadFile::ask() +{ + confirm = true; + + mainTxt("'" + file->fileName() + "' already exists, do you want to overwrite? (y/n): "); +} + +void UploadFile::run() +{ + if (file->open(mode)) + { + emit dataToClient(QByteArray(), GEN_FILE); + } + else + { + errTxt("err: Unable to open the remote file for writing. reason: " + file->errorString() + "\n"); + term(); + } +} + +void UploadFile::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (((dType == GEN_FILE) || (dType == TEXT)) && confirm) + { + QString ans = fromTEXT(binIn); + + if (noCaseMatch("y", ans)) + { + confirm = false; + + run(); + } + else if (noCaseMatch("n", ans)) + { + term(); + } + else + { + ask(); + } + } + else if ((dType == GEN_FILE) && moreInputEnabled()) + { + wrToFile(binIn); + } + else if (dType == GEN_FILE) + { + QStringList args = parseArgs(binIn, 11); + QString lenStr = getParam("-len", args); + QString offStr = getParam("-offset", args); + QString dst = getParam("-remote_file", args); + bool exists = QFileInfo(dst).exists(); + + file->setFileName(dst); + + if (argExists("-truncate", args)) mode = QFile::ReadWrite | QFile::Truncate; + else mode = QFile::ReadWrite; + + if (dst.isEmpty()) + { + errTxt("err: The remote file path argument (-remote_file) was not found or is empty.\n"); + } + else if (lenStr.isEmpty()) + { + errTxt("err: The data len argument (-len) was not found or is empty.\n"); + } + else if (!offStr.isEmpty() && !isInt(offStr)) + { + errTxt("err: Offset '" + offStr + "' is not a valid integer.\n"); + } + else if (!isInt(lenStr)) + { + errTxt("err: Len '" + lenStr + "' is not valid integer.\n"); + } + else + { + ssMode = argExists("-single_step", args); + force = argExists("-force", args); + len = lenStr.toLongLong(); + + file->seek(offStr.toLongLong()); + + emit enableMoreInput(true); + emit dataToClient(toTEXT("-to_host"), GEN_FILE); + + if (exists && !force) ask(); + else run(); + } + } +} + +void Delete::term() +{ + emit enableMoreInput(false); + + path.clear(); +} + +void Delete::ask() +{ + emit enableMoreInput(true); + + mainTxt("Are you sure you want to delete the object? (y/n): "); +} + +void Delete::run() +{ + bool ok; + + if (QFileInfo(path).isFile() || QFileInfo(path).isSymLink()) + { + ok = QFile::remove(path); + } + else + { + ok = QDir(path).removeRecursively(); + } + + if (!ok) errTxt("err: Could not delete '" + path + "' for an unknown reason.\n"); +} + +void Delete::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (moreInputEnabled() && (dType == TEXT)) + { + QString ans = fromTEXT(binIn); + + if (noCaseMatch("y", ans)) + { + emit enableMoreInput(false); + + run(); + } + else if (noCaseMatch("n", ans)) + { + term(); + } + else + { + ask(); + } + } + else if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 3); + + path = getParam("-path", args); + + if (path.isEmpty()) + { + errTxt("err: The object path argument (-path) not found or is empty.\n"); + } + else if (!QFileInfo(path).exists()) + { + errTxt("err: Object not found.\n"); + } + else if (!QFileInfo(path).isWritable()) + { + errTxt("err: Permission denied.\n"); + } + else + { + if (argExists("-force", args)) run(); + else ask(); + } + } +} + +void Copy::term() +{ + fromQueue = false; + procedAFile = false; + yToAll = false; + nToAll = false; + + src->close(); + dst->close(); + + queue.clear(); + srcPath.clear(); + dstPath.clear(); + oriSrcPath.clear(); + + emit enableLoop(false); + emit enableMoreInput(false); +} + +void Copy::ask() +{ + emit enableMoreInput(true); + + QString opts; + + if (fromQueue) opts = "(y/n/y-all/n-all): "; + else opts = "(y/n): "; + + mainTxt("'" + dstPath + "' already exists, do you want to overwrite? " + opts); +} + +bool Copy::matchingVolumeMatters() +{ + return false; +} + +bool Copy::permissionsOk(bool dstExists) +{ + bool ret = true; + + if (!QFileInfo(srcPath).isReadable()) + { + errTxt("err: Read permission to '" + srcPath + "' is denied.\n"); + + ret = false; + } + else if (dstExists && !QFileInfo(dstPath).isWritable()) + { + errTxt("err: Write permission to '" + dstPath + "' is denied\n"); + + ret = false; + } + + return ret; +} + +void Copy::run() +{ + mkPathForFile(dstPath); + + if (matchingVolumeMatters() && matchedVolume(srcPath, dstPath)) + { + runOnMatchingVolume(); + } + else if (QFileInfo(srcPath).isSymLink()) + { + if (procedAFile) mainTxt("\n"); + + mainTxt("mklink: '" + srcPath + "' --> '" + dstPath + "'\n"); + + if (QFile::link(QFileInfo(srcPath).symLinkTarget(), dstPath)) + { + procedAFile = true; + + postProcFile(); + } + else + { + errTxt("err: Unable to re-create the source symlink at the destination path. writing to the path is not possible/denied.\n"); + term(); + } + } + else if (QFileInfo(srcPath).isDir()) + { + mkPath(dstPath); + listDir(queue, srcPath, dstPath); + + emit enableLoop(true); + } + else + { + if (!dst->open(QFile::WriteOnly | QFile::Truncate)) + { + errTxt("err: Unable to open the destination file '" + dstPath + "' for writing. reason: " + dst->errorString() + "\n"); + term(); + } + else if (!src->open(QFile::ReadOnly)) + { + errTxt("err: Unable to open the source file '" + srcPath + "' for reading. reason: " + src->errorString() + "\n"); + term(); + } + else + { + if (procedAFile) mainTxt("\n"); + + mainTxt("'" + srcPath + "' --> '" + dstPath + "'\n\n"); + + emit enableLoop(true); + } + } +} + +void Copy::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (loopEnabled()) + { + if (src->isOpen() || dst->isOpen()) + { + dst->write(src->read(LOCAL_BUFFSIZE)); + + mainTxt(QString::number(src->pos()) + "/" + QString::number(src->size()) + "\n"); + + if (src->atEnd()) + { + procedAFile = true; + + src->close(); + dst->close(); + + postProcFile(); + } + } + else if (!queue.isEmpty()) + { + QPair srcToDst = queue.takeFirst(); + + srcPath = srcToDst.first; + dstPath = srcToDst.second; + fromQueue = true; + + src->setFileName(srcPath); + dst->setFileName(dstPath); + + bool exists = QFileInfo(dstPath).exists(); + + if (exists && !yToAll && !nToAll) + { + enableLoop(false); + + ask(); + } + else if (!nToAll || !exists) + { + run(); + } + } + else + { + preFinish(); + term(); + } + } + else if ((dType == TEXT) && moreInputEnabled()) + { + QString ans = fromTEXT(binIn); + + if (noCaseMatch("y", ans)) + { + emit enableLoop(fromQueue); + emit enableMoreInput(false); + + run(); + } + else if (noCaseMatch("n", ans)) + { + emit enableLoop(fromQueue); + emit enableMoreInput(false); + + if (!loopEnabled()) term(); + } + else if (fromQueue) + { + if (noCaseMatch("y-all", ans)) + { + emit enableLoop(fromQueue); + emit enableMoreInput(false); + + yToAll = true; + + run(); + } + else if (noCaseMatch("n-all", ans)) + { + emit enableLoop(fromQueue); + emit enableMoreInput(false); + + nToAll = true; + } + else + { + ask(); + } + } + else + { + ask(); + } + } + else if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 5); + bool force = argExists("-force", args); + + srcPath = getParam("-src", args); + dstPath = getParam("-dst", args); + oriSrcPath = srcPath; + + bool dstExists = QFileInfo(dstPath).exists(); + + src->setFileName(srcPath); + dst->setFileName(dstPath); + + if (srcPath.isEmpty()) + { + errTxt("err: The source file (-src) argument was not found or is empty.\n"); + } + else if (dstPath.isEmpty()) + { + errTxt("err: The destination file (-dst) argument was not found or is empty.\n"); + } + else if (!QFileInfo(srcPath).exists()) + { + errTxt("err: The source file does not exists.\n"); + } + else if (dstExists && !matchedFsObjTypes(srcPath, dstPath)) + { + errTxt("err: The existing destination object type does not match the source object type.\n"); + } + else if (permissionsOk(dstExists)) + { + if (dstExists && force) run(); + else if (dstExists) ask(); + else run(); + } + } +} + +bool Move::matchingVolumeMatters() +{ + return true; +} + +void Move::runOnMatchingVolume() +{ + QFileInfo dstInfo(dstPath); + + if (dstInfo.exists()) + { + if (dstInfo.isDir()) QDir(dstPath).removeRecursively(); + else QFile::remove(dstPath); + } + + if (!QFile::rename(srcPath, dstPath)) + { + errTxt("err: Unable to do move operation. it's likely the command failed to remove the existing destination object or writing to the path is not possible/denied.\n"); + term(); + } +} + +void Move::postProcFile() +{ + QFile::remove(srcPath); +} + +void Move::preFinish() +{ + if (QFileInfo(oriSrcPath).isDir()) + { + QDir(oriSrcPath).removeRecursively(); + } +} + +bool Move::permissionsOk(bool dstExists) +{ + bool ret = true; + + if (!QFileInfo(srcPath).isReadable() || !QFileInfo(srcPath).isWritable()) + { + errTxt("err: Read/Write permission(s) to '" + srcPath + "' is denied.\n"); + + ret = false; + } + else if (dstExists && !QFileInfo(dstPath).isWritable()) + { + errTxt("err: Write permission to '" + dstPath + "' is denied\n"); + + ret = false; + } + + return ret; +} + +void MakePath::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString path = getParam("-path", args); + + if (path.isEmpty()) + { + errTxt("err: The path argument (-path) was not found or is empty.\n"); + } + else + { + mkPath(path); + } + } +} + +void ListFiles::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 3); + QString path = getParam("-path", args); + bool infoFrame = argExists("-info_frame", args); + + if (path.isEmpty()) + { + path = QDir::currentPath(); + } + + QFileInfo pathInfo(path); + + if (!pathInfo.exists()) + { + errTxt("err: '" + path + "' does not exists.\n"); + } + else if (!pathInfo.isDir()) + { + errTxt("err: '" + path + "' is not a directory.\n"); + } + else if (!pathInfo.isReadable()) + { + errTxt("err: Cannot read '" + path + "' permission denied.\n"); + } + else + { + QDir dir(path); + + dir.setFilter(QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot); + dir.setSorting(QDir::DirsFirst | QDir::Name); + + QFileInfoList list = dir.entryInfoList(); + + for (auto&& info : list) + { + if (infoFrame) + { + emit dataToClient(toFILE_INFO(info), FILE_INFO); + } + else if (info.isDir()) + { + mainTxt(info.fileName() + "/" + "\n"); + } + else + { + mainTxt(info.fileName() + "\n"); + } + } + } + } +} + +void FileInfo::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 3); + QString path = getParam("-path", args); + bool infoFrame = argExists("-info_frame", args); + + QFileInfo info(path); + + if (path.isEmpty()) + { + errTxt("err: The path argument (-path) was not found or is empty.\n"); + } + else if (!info.exists()) + { + errTxt("err: Object not found.\n"); + } + else + { + if (infoFrame) + { + emit dataToClient(toFILE_INFO(info), FILE_INFO); + } + else + { + QString txt; + QTextStream txtOut(&txt); + QStorageInfo storInfo(path); + + txtOut << "is_file: " << boolStr(info.isFile()) << endl; + txtOut << "is_dir: " << boolStr(info.isDir()) << endl; + txtOut << "is_symlnk: " << boolStr(info.isSymLink()) << endl << endl; + + txtOut << "can_read: " << boolStr(info.isReadable()) << endl; + txtOut << "can_write: " << boolStr(info.isWritable()) << endl; + txtOut << "can_execute: " << boolStr(info.isExecutable()) << endl << endl; + + txtOut << "bytes: " << QString::number(info.size()) << endl << endl; + + txtOut << "device: " << storInfo.device() << endl << endl; + + txtOut << "time_created: " << info.birthTime().toString("MM/dd/yyyy hh:mm:ss AP t") << endl; + txtOut << "last_modified: " << info.lastModified().toString("MM/dd/yyyy hh:mm:ss AP t") << endl; + txtOut << "last_accessed: " << info.lastRead().toString("MM/dd/yyyy hh:mm:ss AP t") << endl; + + mainTxt(txt); + } + } + } +} + +void ChangeDir::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 3); + QString path = getParam("-path", args); + + if (path.isEmpty()) + { + mainTxt(QDir::currentPath() + "\n"); + } + else if (!QFileInfo(path).exists()) + { + errTxt("err: '" + path + "' does not exists.\n"); + } + else if (!QFileInfo(path).isDir()) + { + errTxt("err: '" + path + "' is not a directory.\n"); + } + else + { + QDir::setCurrent(path); + + mainTxt(QDir::currentPath() + "\n"); + } + } +} diff --git a/src/commands/fs.h b/src/commands/fs.h new file mode 100644 index 0000000..a29b2ab --- /dev/null +++ b/src/commands/fs.h @@ -0,0 +1,220 @@ +#ifndef FS_H +#define FS_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 +// . + +#include "../common.h" + +class DownloadFile : public InternCommand +{ + Q_OBJECT + +private: + + QFile *file; + qint64 buffSize; + qint64 len; + qint64 dataSent; + bool ssMode; + + void sendChunk(); + +public: + + static QString cmdName(); + + bool handlesGenfile(); + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit DownloadFile(QObject *parent = nullptr); +}; + +//----------------------- + +class UploadFile : public InternCommand +{ + Q_OBJECT + +private: + + QFile::OpenMode mode; + QFile *file; + qint64 len; + qint64 dataReceived; + bool ssMode; + bool confirm; + bool force; + + void wrToFile(const QByteArray &data); + void run(); + void ask(); + +public: + + static QString cmdName(); + + bool handlesGenfile(); + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit UploadFile(QObject *parent = nullptr); +}; + +//----------------------- + +class Delete : public InternCommand +{ + Q_OBJECT + +private: + + void ask(); + void run(); + + QString path; + +public: + + static QString cmdName(); + + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit Delete(QObject *parent = nullptr); +}; + +//------------------------ + +class Copy : public InternCommand +{ + Q_OBJECT + +protected: + + void ask(); + void run(); + + QFile *src; + QFile *dst; + bool procedAFile; + bool fromQueue; + bool yToAll; + bool nToAll; + QString dstPath; + QString srcPath; + QString oriSrcPath; + QList > queue; + + virtual bool matchingVolumeMatters(); + virtual bool permissionsOk(bool dstExists); + virtual void runOnMatchingVolume() {} + virtual void postProcFile() {} + virtual void preFinish() {} + +public: + + static QString cmdName(); + + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit Copy(QObject *parent = nullptr); +}; + +//------------------------ + +class Move : public Copy +{ + Q_OBJECT + +private: + + bool matchingVolumeMatters(); + bool permissionsOk(bool dstExists); + void runOnMatchingVolume(); + void postProcFile(); + void preFinish(); + +public: + + static QString cmdName(); + + explicit Move(QObject *parent = nullptr); +}; + +//----------------------- + +class MakePath : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit MakePath(QObject *parent = nullptr); +}; + +//----------------------- + +class ListFiles : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit ListFiles(QObject *parent = nullptr); +}; + +//----------------------- + +class FileInfo : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit FileInfo(QObject *parent = nullptr); +}; + +//----------------------- + +class ChangeDir : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit ChangeDir(QObject *parent = nullptr); +}; + +#endif // FS_H diff --git a/src/commands/groups.cpp b/src/commands/groups.cpp new file mode 100644 index 0000000..098682a --- /dev/null +++ b/src/commands/groups.cpp @@ -0,0 +1,286 @@ +#include "groups.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 +// . + +ListGroups::ListGroups(QObject *parent) : TableViewer(parent) +{ + setParams(TABLE_GROUPS, QStringList() << COLUMN_GRNAME << COLUMN_HOST_RANK, false); +} + +CreateGroup::CreateGroup(QObject *parent) : InternCommand(parent) {} +RemoveGroup::RemoveGroup(QObject *parent) : InternCommand(parent) {} +TransGroup::TransGroup(QObject *parent) : InternCommand(parent) {} +SetGroupRank::SetGroupRank(QObject *parent) : InternCommand(parent) {} +RenameGroup::RenameGroup(QObject *parent) : InternCommand(parent) {} + +QString ListGroups::cmdName() {return "ls_groups";} +QString CreateGroup::cmdName() {return "add_group";} +QString RemoveGroup::cmdName() {return "rm_group";} +QString TransGroup::cmdName() {return "trans_group";} +QString SetGroupRank::cmdName() {return "set_group_rank";} +QString RenameGroup::cmdName() {return "rename_group";} + +void CreateGroup::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString grName = getParam("-name", args); + + if (grName.isEmpty()) + { + errTxt("err: The group name argument (-name) was not found or is empty.\n"); + } + else if (!validGroupName(grName)) + { + errTxt("err: The group name must be 1-12 chars long and contain no spaces.\n"); + } + else if (groupExists(grName)) + { + errTxt("err: The requested group already exists.\n"); + } + else + { + Query db(this); + + db.setType(Query::PUSH, TABLE_GROUPS); + db.addColumn(COLUMN_GRNAME, grName); + db.addColumn(COLUMN_HOST_RANK, 2); + db.exec(); + } + } +} + +void RemoveGroup::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString grName = getParam("-name", args); + + if (grName.isEmpty()) + { + errTxt("err: The group name (-name) argument was not found or is empty.\n"); + } + else if (noCaseMatch(ROOT_USER, grName)) + { + errTxt("err: '" + QString(ROOT_USER) + "' is a protected group, you cannot delete it.\n"); + } + else if (!validGroupName(grName)) + { + errTxt("err: Invalid group name.\n"); + } + else if (!groupExists(grName)) + { + errTxt("err: No such group found in the database.\n"); + } + else if (noCaseMatch(grName, initGroup())) + { + errTxt("err: '" + grName + "' is the initial group for new users, unable to delete it.\n"); + } + else if (!checkRank(*sharedObjs->groupName, grName)) + { + errTxt("err: The target group out ranks or is equal to your own group. access denied.\n"); + } + else + { + Query db(this); + + db.setType(Query::DEL, TABLE_GROUPS); + db.addCondition(COLUMN_GRNAME, grName); + + if (!db.exec()) + { + errTxt("err: Unable to delete group. some user accounts might still be attached to it.\n"); + } + } + } +} + +void TransGroup::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString src = getParam("-src", args); + QString dst = getParam("-dst", args); + + if (src.isEmpty()) + { + errTxt("err: The source group (-src) argument was not found or is empty.\n"); + } + else if (dst.isEmpty()) + { + errTxt("err: The destination group (-dst) argument was not found or is empty.\n"); + } + else if (noCaseMatch(src, ROOT_USER) || noCaseMatch(dst, ROOT_USER)) + { + errTxt("err: Unable to transfer to or from protected group '" + QString(ROOT_USER) + "'\n"); + } + else if (!validGroupName(src)) + { + errTxt("err: Invalid source group name.\n"); + } + else if (!validGroupName(dst)) + { + errTxt("err: Invalid destination group name.\n"); + } + else if (!groupExists(src)) + { + errTxt("err: '" + src + "' does not exists.\n"); + } + else if (!groupExists(dst)) + { + errTxt("err: '" + dst + "' does not exists.\n"); + } + else if (!checkRank(*sharedObjs->groupName, src)) + { + errTxt("err: The source group out ranks your own group. access denied.\n"); + } + else if (!checkRank(*sharedObjs->groupName, dst)) + { + errTxt("err: The destination group out ranks or is equal to your own group. access denied.\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_USERS); + db.addColumn(COLUMN_GRNAME, dst); + db.addCondition(COLUMN_GRNAME, src); + db.exec(); + + emit backendDataOut(ASYNC_GRP_TRANS, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void SetGroupRank::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString grName = getParam("-name", args); + QString rank = getParam("-rank", args); + + if (grName.isEmpty()) + { + errTxt("err: The group name (-name) argument was not found or is empty.\n"); + } + else if (noCaseMatch(ROOT_USER, grName)) + { + errTxt("err: '" + QString(ROOT_USER) + "' is a protected group, you cannot change it.\n"); + } + else if (!validGroupName(grName)) + { + errTxt("err: Invalid group name.\n"); + } + else if (!isInt(rank)) + { + errTxt("err: Invalid rank.\n"); + } + else if (rank.toUInt() < *sharedObjs->hostRank) + { + errTxt("err: you cannot set a rank higher than your own.\n"); + } + else if (!groupExists(grName)) + { + errTxt("err: Group name '" + grName + "' does not exists.\n"); + } + else if (!checkRank(*sharedObjs->groupName, grName)) + { + errTxt("err: The target group out ranks or is equal to your own group. access denied.\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_GROUPS); + db.addColumn(COLUMN_HOST_RANK, rank.toUInt()); + db.addCondition(COLUMN_GRNAME, grName); + db.exec(); + + emit backendDataOut(ASYNC_GROUP_UPDATED, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void RenameGroup::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString name = getParam("-name", args); + QString newName = getParam("-new_name", args); + + if (name.isEmpty()) + { + errTxt("err: The group name (-name) argument was not found or is empty.\n"); + } + else if (newName.isEmpty()) + { + errTxt("err: The new name (-new_name) argument was not found or is empty.\n"); + } + else if (noCaseMatch(name, ROOT_USER)) + { + errTxt("err: Cannot rename protected group '" + QString(ROOT_USER) + "'\n"); + } + else if (noCaseMatch(newName, ROOT_USER)) + { + errTxt("err: Cannot use '" + QString(ROOT_USER) + "' for a new name. it is reserved.\n"); + } + else if (!validGroupName(name)) + { + errTxt("err: Invalid group name.\n"); + } + else if (!validGroupName(newName)) + { + errTxt("err: Invalid new group name. the group name must be between 1-12 chars long and contain no spaces.\n"); + } + else if (!groupExists(name)) + { + errTxt("err: Group name '" + name + "' does not exists.\n"); + } + else if (groupExists(newName)) + { + errTxt("err: Group name '" + newName + "' already exists.\n"); + } + else if (!checkRank(*sharedObjs->groupName, name)) + { + errTxt("err: The target group out ranks or is equal to your own group. access denied.\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_GROUPS); + db.addColumn(COLUMN_GRNAME, newName); + db.addCondition(COLUMN_GRNAME, name); + db.exec(); + + args.clear(); + args.append("-src"); + args.append("'" + escapeChars(name, '\\', '\'') + "'"); + args.append("-dst"); + args.append("'" + escapeChars(newName, '\\', '\'') + "'"); + + emit backendDataOut(ASYNC_GROUP_RENAMED, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} diff --git a/src/commands/groups.h b/src/commands/groups.h new file mode 100644 index 0000000..ca6a3b3 --- /dev/null +++ b/src/commands/groups.h @@ -0,0 +1,109 @@ +#ifndef GROUP_CMDS_H +#define GROUP_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 +// . + +#include "../common.h" +#include "table_viewer.h" + +class ListGroups : public TableViewer +{ + Q_OBJECT + +public: + + static QString cmdName(); + + explicit ListGroups(QObject *parent = nullptr); +}; + +//-------------------------------------- + +class CreateGroup : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit CreateGroup(QObject *parent = nullptr); +}; + +//-------------------------------------- + +class RemoveGroup : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit RemoveGroup(QObject *parent = nullptr); +}; + +//-------------------------------------- + +class TransGroup : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit TransGroup(QObject *parent = nullptr); +}; + +//------------------------------------- + +class SetGroupRank : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit SetGroupRank(QObject *parent = nullptr); +}; + +//------------------------------------ + +class RenameGroup : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit RenameGroup(QObject *parent = nullptr); +}; + +#endif // GROUP_CMDS_H diff --git a/src/commands/info.cpp b/src/commands/info.cpp new file mode 100644 index 0000000..71001af --- /dev/null +++ b/src/commands/info.cpp @@ -0,0 +1,224 @@ +#include "info.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 +// . + +IPHist::IPHist(QObject *parent) : TableViewer(parent) +{ + setParams(TABLE_IPHIST, QStringList() << COLUMN_TIME << COLUMN_IPADDR << COLUMN_SESSION_ID << COLUMN_CLIENT_VER << COLUMN_LOGENTRY, true); +} + +ListDBG::ListDBG(QObject *parent) : TableViewer(parent) +{ + setParams(TABLE_DMESG, QStringList() << COLUMN_TIME << COLUMN_LOGENTRY, true); +} + +ListCommands::ListCommands(QObject *parent) : InternCommand(parent) {} +HostInfo::HostInfo(QObject *parent) : InternCommand(parent) {} +MyInfo::MyInfo(QObject *parent) : InternCommand(parent) {} +CmdInfo::CmdInfo(QObject *parent) : InternCommand(parent) {} + +QString ListCommands::cmdName() {return "ls_cmds";} +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 CmdInfo::cmdName() {return "cmd_info";} + +void ListCommands::procBin(const SharedObjs *sharedObjs, const QByteArray &data, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QString find = getParam("-find", parseArgs(data, 2)); + QList cmdIds = sharedObjs->cmdNames->keys(); + QList tableData; + QStringList separators; + QList justLens; + + for (int i = 0; i < 3; ++i) + { + justLens.append(12); + separators.append("-------"); + } + + tableData.append(QStringList() << "command_id" << "command_name" << "summary"); + tableData.append(separators); + + for (auto&& cmdId: cmdIds) + { + QStringList rowData; + + rowData.append(QString::number(cmdId)); + rowData.append(sharedObjs->cmdNames->value(cmdId)); + rowData.append(rwSharedObjs->commands->value(cmdId)->shortText()); + + if (find.isEmpty() || rowData.contains(find, Qt::CaseInsensitive)) + { + for (int k = 0; k < justLens.size(); ++k) + { + if (justLens[k] < rowData[k].size()) justLens[k] = rowData[k].size(); + } + + tableData.append(rowData); + } + } + + mainTxt("\n"); + + for (auto&& row : tableData) + { + for (int i = 0; i < row.size(); ++i) + { + mainTxt(row[i].leftJustified(justLens[i] + 2, ' ')); + } + + mainTxt("\n"); + } + } +} + +void HostInfo::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(binIn); + Q_UNUSED(sharedObjs); + + 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); + + txtOut << "Application: " << QCoreApplication::applicationName() << " v" << QCoreApplication::applicationVersion() << " " << QSysInfo::WordSize << "Bit" << endl; + txtOut << "Import Rev: " << IMPORT_REV << endl; + txtOut << "Host Name: " << QSysInfo::machineHostName() << endl; + txtOut << "Host OS: " << QSysInfo::prettyProductName() << endl; + txtOut << "Load: " << rdSessionLoad() << "/" << db.getData(COLUMN_MAXSESSIONS).toUInt() << endl; + txtOut << "Listening Addr: " << db.getData(COLUMN_IPADDR).toString() << endl; + txtOut << "Listening Port: " << db.getData(COLUMN_PORT).toUInt() << endl; + + mainTxt(txt); + } +} + +void MyInfo::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(binIn); + + if (dType == TEXT) + { + QString txt; + QTextStream txtOut(&txt); + + txtOut << "Session id: " << sharedObjs->sessionId->toHex() << endl; + txtOut << "IP Address: " << *sharedObjs->sessionAddr << endl; + txtOut << "Logged-in: " << boolStr(!sharedObjs->userName->isEmpty()) << endl; + txtOut << "App Name: " << *sharedObjs->appName << endl; + + if (!sharedObjs->userName->isEmpty()) + { + Query db(this); + + db.setType(Query::PULL, TABLE_USERS); + db.addColumn(COLUMN_EMAIL); + db.addColumn(COLUMN_TIME); + db.addColumn(COLUMN_EMAIL_VERIFIED); + db.addCondition(COLUMN_USERNAME, *sharedObjs->userName); + db.exec(); + + txtOut << "User Name: " << *sharedObjs->userName << endl; + txtOut << "Group Name: " << *sharedObjs->groupName << endl; + txtOut << "Display Name: " << *sharedObjs->displayName << endl; + txtOut << "User ID: " << sharedObjs->userId->toHex() << endl; + txtOut << "Email: " << db.getData(COLUMN_EMAIL).toString() << endl; + txtOut << "Register Date: " << db.getData(COLUMN_TIME).toString() << endl; + txtOut << "Email Verified: " << boolStr(db.getData(COLUMN_EMAIL_VERIFIED).toBool()) << endl; + txtOut << "Owner Override: " << boolStr(*sharedObjs->chOwnerOverride) << endl; + txtOut << "Host Rank: " << *sharedObjs->hostRank << endl; + } + + mainTxt(txt); + } +} + +void CmdInfo::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString name = getParam("-cmd_name", args); + QString cmdId = getParam("-cmd_id", args); + + if (name.isEmpty() && cmdId.isEmpty()) + { + errTxt("err: Command name (-cmd_name) of command id (-cmd_id) parameter not found or is empty.\n"); + } + else if (!name.isEmpty() && !validCommandName(name)) + { + errTxt("err: Command name '" + name + "' is not valid.\n"); + } + else if (!cmdId.isEmpty() && !isInt(cmdId)) + { + errTxt("err: Command id '" + cmdId + "' is not a valid integer.\n"); + } + else if (!name.isEmpty() && !sharedObjs->cmdNames->values().contains(name.toLower())) + { + errTxt("err: No such command name '" + name + "'\n"); + } + else if (!cmdId.isEmpty() && !sharedObjs->cmdNames->contains(cmdId.toUShort())) + { + errTxt("err: No such command id '" + cmdId + "'\n"); + } + else + { + if (!name.isEmpty()) + { + cmdId = QString::number(sharedObjs->cmdNames->key(name)); + } + + if (!cmdId.isEmpty()) + { + name = sharedObjs->cmdNames->value(cmdId.toUShort()); + } + + QString txt; + QTextStream txtOut(&txt); + + ExternCommand *cmdObj = rwSharedObjs->commands->value(cmdId.toUShort()); + + txtOut << "Command name: " << name << endl; + txtOut << "Command id: " << cmdId << endl; + txtOut << "Gen file: " << boolStr(cmdObj->handlesGenfile()) << endl << endl; + + txtOut << "IO:" << endl << endl; + txtOut << cmdObj->ioText() << endl << endl; + txtOut << "Summary:" << endl << endl; + txtOut << cmdObj->shortText() << endl << endl; + txtOut << "Details:" << endl << endl; + + mainTxt(txt); + bigTxt(cmdObj->longText()); + } + } +} diff --git a/src/commands/info.h b/src/commands/info.h new file mode 100644 index 0000000..16d9a1d --- /dev/null +++ b/src/commands/info.h @@ -0,0 +1,107 @@ +#ifndef INFO_H +#define INFO_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 +// . + +#include "../common.h" +#include "table_viewer.h" + +class ListCommands : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &data, uchar dType); + + explicit ListCommands(QObject *parent = nullptr); +}; + +//-------------------------------------- + +class HostInfo : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit HostInfo(QObject *parent = nullptr); +}; + +//------------------------------------ + +class IPHist : public TableViewer +{ + Q_OBJECT + +public: + + static QString cmdName(); + + explicit IPHist(QObject *parent = nullptr); +}; + +//------------------------------------ + +class MyInfo : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit MyInfo(QObject *parent = nullptr); +}; + +//----------------------------------- + +class ListDBG : public TableViewer +{ + Q_OBJECT + +public: + + static QString cmdName(); + + explicit ListDBG(QObject *parent = nullptr); +}; + +//---------------------------------- + +class CmdInfo : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit CmdInfo(QObject *parent = nullptr); +}; + +#endif // INFO_H diff --git a/src/commands/mods.cpp b/src/commands/mods.cpp new file mode 100644 index 0000000..c2f463f --- /dev/null +++ b/src/commands/mods.cpp @@ -0,0 +1,357 @@ +#include "mods.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 +// . + +ListMods::ListMods(QObject *parent) : TableViewer(parent) +{ + setParams(TABLE_MODULES, QStringList() << COLUMN_MOD_NAME << COLUMN_MOD_MAIN << COLUMN_LOCKED << COLUMN_CMD_ID_OFFS, false); +} + +UploadMod::UploadMod(QObject *parent) : InternCommand(parent) +{ + dSize = 0; + fileBuff = new QTemporaryFile(this); + proc = new QProcess(this); + + proc->setProcessChannelMode(QProcess::SeparateChannels); + + connect(proc, SIGNAL(finished(int)), this, SLOT(procFinished(int))); + connect(proc, SIGNAL(readyReadStandardOutput()), this, SLOT(rdTextFromProc())); + connect(proc, SIGNAL(readyReadStandardError()), this, SLOT(rdErrFromProc())); +} + +DelMod::DelMod(QObject *parent) : InternCommand(parent) {} + +QString ListMods::cmdName() {return "ls_mods";} +QString DelMod::cmdName() {return "rm_mod";} +QString UploadMod::cmdName() {return "add_mod";} + +QString rmFileSuffix(const QString &filePath) +{ + QString suffix = "." + QFileInfo(filePath).suffix(); + + return filePath.left(filePath.size() - suffix.size()); +} + +bool validFileOnlyName(const QString &fileName) +{ + QString forbidden = "/\\:*?\"<>|"; + bool ret = true; + + if (fileName.contains("..")) + { + ret = false; + } + else + { + for (auto&& chr : forbidden) + { + if (fileName.contains(chr, Qt::CaseInsensitive)) + { + ret = false; + + break; + } + } + } + + return ret; +} + +bool UploadMod::handlesGenfile() +{ + return true; +} + +bool UploadMod::libExists(const QString &path) +{ +#ifdef Q_OS_WIN + + return QFileInfo::exists(path + ".dll") || + QFileInfo::exists(path + ".DLL"); +#endif + +#ifdef Q_OS_UNIX + + return QFileInfo::exists(path + ".so"); + +#endif + +#ifdef Q_OS_AIX + + return QFileInfo::exists(path + ".a"); + +#endif + +#ifdef Q_OS_MAC + + return QFileInfo::exists(path + ".so") || + QFileInfo::exists(path + ".dylib") || + QFileInfo::exists(path + ".bundle"); +#endif +} + +void UploadMod::clearOnfailure() +{ + QDir(modPath).removeRecursively(); + + Query db(this); + + db.setType(Query::DEL, TABLE_MODULES); + db.addCondition(COLUMN_MOD_NAME, modName); + db.exec(); +} + +void UploadMod::procFinished(int exStatus) +{ + if (exStatus != 0) + { + errTxt("\nerr: The file operation stopped on error.\n"); + clearOnfailure(); + } + else if (!libExists(modPath + "/main")) + { + errTxt("\nerr: The module's main library file does not exists.\n"); + clearOnfailure(); + } + else + { + modPath = modPath + "/main"; + + Query db(this); + + db.setType(Query::UPDATE, TABLE_MODULES); + db.addColumn(COLUMN_LOCKED, false); + db.addCondition(COLUMN_MOD_NAME, modName); + db.exec(); + + emit backendDataOut(ASYNC_ENABLE_MOD, toTEXT(modPath), PUB_IPC_WITH_FEEDBACK); + + mainTxt("\nFinished..."); + } + + term(); +} + +void UploadMod::term() +{ + if (proc->state() == QProcess::Running) + { + proc->blockSignals(true); + proc->kill(); + } + + fileBuff->close(); + + modPath.clear(); + modName.clear(); + + dSize = 0; + + emit enableMoreInput(false); +} + +void UploadMod::rdErrFromProc() +{ + errTxt(proc->readAllStandardError()); +} + +void UploadMod::rdTextFromProc() +{ + mainTxt(proc->readAllStandardOutput()); +} + +void UploadMod::procStartError(QProcess::ProcessError err) +{ + if (err == QProcess::FailedToStart) + { + errTxt("err: Could not start the host archiver. reason: " + proc->errorString() + "\n"); + clearOnfailure(); + term(); + } +} + +void UploadMod::setup() +{ + if (QLibrary::isLibrary(clientFile)) + { + QString suffix = QFileInfo(clientFile).completeSuffix(); + QString dst = modPath + "/main." + suffix; + + mainTxt("copy file: " + fileBuff->fileName() + " --> " + dst + "/n"); + + if (QFile::copy(fileBuff->fileName(), dst)) + { + procFinished(0); + } + else + { + procFinished(1); + } + } + else + { + Query db(this); + + db.setType(Query::PULL, TABLE_SERV_SETTINGS); + db.addColumn(COLUMN_ZIPBIN); + db.addColumn(COLUMN_ZIPEXTRACT); + db.exec(); + + QString exePath = db.getData(COLUMN_ZIPBIN).toString(); + QString cmdLine = db.getData(COLUMN_ZIPEXTRACT).toString(); + + cmdLine.replace(INPUT_DIR_SUB, "'" + fileBuff->fileName() + "'"); + cmdLine.replace(OUTPUT_DIR_SUB, "'" + modPath + "'"); + + proc->blockSignals(false); + proc->setProgram(expandEnvVariables(exePath)); + proc->setArguments(parseArgs(toTEXT(cmdLine), -1)); + proc->start(); + } +} + +void UploadMod::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (moreInputEnabled() && (dType == GEN_FILE) && (proc->state() == QProcess::NotRunning)) + { + if (fileBuff->write(binIn.data(), binIn.size()) == -1) + { + errTxt("err: Temp file write failure, unable to continue.\n"); + clearOnfailure(); + term(); + } + else + { + mainTxt(QString::number(fileBuff->pos()) + "/" + QString::number(dSize) + "\n"); + + if (fileBuff->size() >= dSize) + { + mainTxt("\nUpload complete...setting up.\n\n"); + setup(); + } + } + } + else if (dType == GEN_FILE) + { + QStringList args = parseArgs(binIn, -1); + QString name = getParam("-name", args); + QString len = getParam("-len", args); + bool sOk = false; + + clientFile = getParam("-client_file", args); + dSize = len.toLongLong(&sOk, 10); + modPath = modDataPath() + "/" + name; + modName = name; + + if (name.isEmpty()) + { + errTxt("err: Module name (-name) argument not found or is empty.\n"); + } + else if (len.isEmpty()) + { + errTxt("err: Data length (-len) argument not found or is empty.\n"); + } + if (maxedInstalledMods()) + { + errTxt("err: Host maximum amount of installed modules has been reached.\n"); + } + else if (!sOk) + { + errTxt("err: The given data length is not a valid integer.\n"); + } + else if (name.size() > 64) + { + errTxt("err: The module name cannot be larger than 64 chars long.\n"); + } + else if (!validFileOnlyName(name)) + { + errTxt("err: Module names cannot contain the following chars: /\\:*?\"<>| or the updir sequence: '..'\n"); + } + else if (modExists(name)) + { + errTxt("err: A module of the same name already exists.\n"); + } + else if (!fileBuff->open()) + { + errTxt("err: Could not open a new temp file for reading/writing.\n"); + } + else + { + Query db(this); + + db.setType(Query::PULL, TABLE_MODULES); + db.addColumn(COLUMN_MOD_NAME); + db.exec(); + + quint16 idOffs = static_cast((db.rows() + 2) * MAX_CMDS_PER_MOD) + 1; + + db.setType(Query::PUSH, TABLE_MODULES); + db.addColumn(COLUMN_MOD_NAME, name); + db.addColumn(COLUMN_MOD_MAIN, modPath + "/main"); + db.addColumn(COLUMN_CMD_ID_OFFS, idOffs); + db.addColumn(COLUMN_LOCKED, true); + db.exec(); + + mainTxt("Input hooked...uploading data.\n\n"); + + emit enableMoreInput(true); + emit dataToClient(toTEXT("-to_host"), GEN_FILE); + emit dataToClient(QByteArray(), GEN_FILE); + } + } +} + +void DelMod::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString name = getParam("-name", args); + + if (name.isEmpty()) + { + errTxt("err: Module name argument (-name) was not found or is empty.\n"); + } + else if (!validFileOnlyName(name)) + { + errTxt("err: Module names cannot contain the following chars: /\\:*?\"<>| or the updir sequence: '..'\n"); + } + else if (!modExists(name)) + { + errTxt("err: No such module found: '" + name + "'\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_MODULES); + db.addColumn(COLUMN_LOCKED, true); + db.addCondition(COLUMN_MOD_NAME, name); + db.exec(); + + QByteArray modPath = toTEXT(modDataPath() + "/" + name + "/main"); + + emit backendDataOut(ASYNC_DISABLE_MOD, modPath, PRIV_IPC); + emit backendDataOut(ASYNC_DISABLE_MOD, modPath, PUB_IPC_WITH_FEEDBACK); + } + } +} diff --git a/src/commands/mods.h b/src/commands/mods.h new file mode 100644 index 0000000..34bab23 --- /dev/null +++ b/src/commands/mods.h @@ -0,0 +1,89 @@ +#ifndef MODS_H +#define MODS_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 +// . + +#include "../common.h" +#include "table_viewer.h" + +QString rmFileSuffix(const QString &filePath); +bool validFileOnlyName(const QString &fileName); + +class UploadMod : public InternCommand +{ + Q_OBJECT + +private: + + QProcess *proc; + QString clientFile; + QString modName; + QString modPath; + QTemporaryFile *fileBuff; + qint64 dSize; + + void clearOnfailure(); + void setup(); + bool libExists(const QString &path); + +private slots: + + void rdTextFromProc(); + void rdErrFromProc(); + void procFinished(int exStatus); + void procStartError(QProcess::ProcessError err); + +public: + + static QString cmdName(); + + bool handlesGenfile(); + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit UploadMod(QObject *parent = nullptr); +}; + +//------------------------------- + +class DelMod : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit DelMod(QObject *parent = nullptr); +}; + +//------------------------------- + +class ListMods : public TableViewer +{ + Q_OBJECT + +public: + + static QString cmdName(); + + explicit ListMods(QObject *parent = nullptr); +}; + +#endif // MODS_H diff --git a/src/commands/p2p.cpp b/src/commands/p2p.cpp new file mode 100644 index 0000000..d68e642 --- /dev/null +++ b/src/commands/p2p.cpp @@ -0,0 +1,163 @@ +#include "p2p.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 +// . + +ToPeer::ToPeer(QObject *parent) : InternCommand(parent) {} +P2PRequest::P2PRequest(QObject *parent) : InternCommand(parent) {} +P2POpen::P2POpen(QObject *parent) : InternCommand(parent) {} +P2PClose::P2PClose(QObject *parent) : InternCommand(parent) {} +LsP2P::LsP2P(QObject *parent) : InternCommand(parent) {} + +QString ToPeer::cmdName() {return "to_peer";} +QString P2PRequest::cmdName() {return "p2p_request";} +QString P2POpen::cmdName() {return "p2p_open";} +QString P2PClose::cmdName() {return "p2p_close";} +QString LsP2P::cmdName() {return "ls_p2p";} + +void ToPeer::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + QByteArray peerId = binIn.left(28); + + if (!sharedObjs->p2pAccepted->contains(peerId)) + { + errTxt("err: You don't current have an open p2p connection with the requested peer."); + } + else + { + emit toPeer(peerId, binIn.mid(28), dType); + } +} + +void P2PRequest::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == SESSION_ID) + { + if (binIn.size() != 28) + { + errTxt("err: The given client session id does not equal 28 bytes."); + } + else if (sharedObjs->p2pAccepted->contains(binIn)) + { + errTxt("err: You already have an open p2p connection with the requested peer."); + } + else if (sharedObjs->p2pPending->contains(binIn)) + { + errTxt("err: There is already a pending p2p request for this peer."); + } + else + { + emit toPeer(binIn, QByteArray(), P2P_REQUEST); + } + } +} + +void P2POpen::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == SESSION_ID) + { + if (binIn.size() != 28) + { + errTxt("err: The given client session id does not equal 28 bytes."); + } + else if (sharedObjs->p2pAccepted->contains(binIn)) + { + errTxt("err: You already have an open p2p connection with the requested peer."); + } + else if (!sharedObjs->p2pPending->contains(binIn)) + { + errTxt("err: There is no pending p2p request for the given peer."); + } + else + { + emit toPeer(binIn, QByteArray(), P2P_OPEN); + } + } +} + +void P2PClose::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == SESSION_ID) + { + if (binIn.size() != 28) + { + errTxt("err: The given client session id does not equal 28 bytes."); + } + else if (!sharedObjs->p2pAccepted->contains(binIn) && !sharedObjs->p2pPending->contains(binIn)) + { + errTxt("err: There is no pending p2p request or p2p connection with the given peer."); + } + else + { + emit toPeer(binIn, QByteArray(), P2P_CLOSE); + } + } +} + +void LsP2P::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(binIn); + + if (dType == TEXT) + { + QList peerIds = *sharedObjs->p2pAccepted + *sharedObjs->p2pPending; + QList tableData; + QStringList separators; + QList justLens; + + for (int i = 0; i < 2; ++i) + { + justLens.append(7); + separators.append("-------"); + } + + tableData.append(QStringList() << "peer_id" << "pending"); + tableData.append(separators); + + for (auto&& peerId: peerIds) + { + QString pending = "0"; + QStringList columnData; + + if (sharedObjs->p2pPending->contains(peerId)) + { + pending = "1"; + } + + columnData.append(peerId.toHex()); + columnData.append(pending); + + for (int k = 0; k < justLens.size(); ++k) + { + if (justLens[k] < columnData[k].size()) justLens[k] = columnData[k].size(); + } + + tableData.append(columnData); + } + + mainTxt("\n"); + + for (auto&& row : tableData) + { + for (int i = 0; i < row.size(); ++i) + { + mainTxt(row[i].leftJustified(justLens[i] + 2, ' ')); + } + + mainTxt("\n"); + } + } +} diff --git a/src/commands/p2p.h b/src/commands/p2p.h new file mode 100644 index 0000000..c36e95e --- /dev/null +++ b/src/commands/p2p.h @@ -0,0 +1,95 @@ +#ifndef P2P_H +#define P2P_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 +// . + +#include "../common.h" + +class ToPeer : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit ToPeer(QObject *parent = nullptr); +}; + +//---------------------------------- + +class P2PRequest : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit P2PRequest(QObject *parent = nullptr); +}; + +//----------------------------------- + +class P2POpen : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit P2POpen(QObject *parent = nullptr); +}; + +//---------------------------------- + +class P2PClose : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit P2PClose(QObject *parent = nullptr); +}; + +//---------------------------------- + +class LsP2P : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit LsP2P(QObject *parent = nullptr); +}; + +#endif // P2P_H diff --git a/src/commands/table_viewer.cpp b/src/commands/table_viewer.cpp new file mode 100644 index 0000000..7654f5d --- /dev/null +++ b/src/commands/table_viewer.cpp @@ -0,0 +1,216 @@ +#include "table_viewer.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 +// . + +TableViewer::TableViewer(QObject *parent) : InternCommand(parent) +{ + delOption = false; + + term(); +} + +void TableViewer::term() +{ + emit enableMoreInput(false); + + del = false; + offset = 0; + + cachedArgs.clear(); + db.setType(Query::PULL, ""); +} + +void TableViewer::setParams(const QString &tbl, const QStringList &colms, bool allowDel) +{ + columns = colms; + table = tbl; + delOption = allowDel; +} + +void TableViewer::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, -1); + bool close = false; + + if (delOption && argExists("-delete", args)) + { + del = true; + } + + if (moreInputEnabled() && !del) + { + if (args.contains("q", Qt::CaseInsensitive)) + { + close = true; + } + else + { + offset += MAX_LS_ENTRIES; + args = cachedArgs; + } + } + + if (close) + { + term(); + } + else if (moreInputEnabled() && del) + { + QString ans = fromTEXT(binIn); + + if (noCaseMatch("y", ans)) + { + db.exec(); + + term(); + } + else if (noCaseMatch("n", ans)) + { + term(); + } + else + { + mainTxt("continue? (y/n): "); + } + } + else + { + if (del) + { + db.setType(Query::DEL, table); + } + else + { + db.setType(Query::PULL, table); + db.setQueryLimit(MAX_LS_ENTRIES, offset); + } + + bool noDelParams = true; + + for (int i = 0; i < columns.size(); ++i) + { + if (!del) db.addColumn(columns[i]); + + QString param = getParam("-" + columns[i], args); + + if (!param.isEmpty()) + { + if (del) + { + if (noDelParams) mainTxt("\n"); + + mainTxt("delete param: " + columns[i] + " = " + param + "\n"); + + noDelParams = false; + } + + db.addCondition(columns[i], param, Query::LIKE); + } + } + + if (del) + { + emit enableMoreInput(true); + + if (noDelParams) mainTxt("\nabout to delete ALL entries in this table.\n"); + + mainTxt("\ncontinue? (y/n): "); + } + else + { + db.exec(); + + QStringList separators; + QList tableData; + QList justLens; + + for (int i = 0; i < columns.size(); ++i) + { + justLens.append(0); + separators.append("-------"); + } + + for (int i = 0; i < db.rows() + 2; ++i) + { + QStringList columnData; + + if (i == 0) + { + columnData = columns; + } + else if (i == 1) + { + columnData = separators; + } + else + { + for (int j = 0; j < columns.size(); ++j) + { + if (columnType(columns[j]) == "BLOB") + { + columnData.append(db.getData(columns[j], i - 2).toByteArray().toHex()); + } + else + { + columnData.append(db.getData(columns[j], i - 2).toString()); + } + } + } + + for (int k = 0; k < justLens.size(); ++k) + { + if (justLens[k] < columnData[k].size()) justLens[k] = columnData[k].size(); + } + + tableData.append(columnData); + } + + mainTxt("\n"); + + for (auto&& row : tableData) + { + for (int i = 0; i < row.size(); ++i) + { + mainTxt(row[i].leftJustified(justLens[i] + 2, ' ')); + } + + mainTxt("\n"); + } + + if (db.rows() == MAX_LS_ENTRIES) + { + if (!moreInputEnabled()) + { + cachedArgs = args; + + emit enableMoreInput(true); + } + + mainTxt("\n[enter or any] more, [q] close: "); + } + else + { + term(); + } + } + } + } +} diff --git a/src/commands/table_viewer.h b/src/commands/table_viewer.h new file mode 100644 index 0000000..3f3692e --- /dev/null +++ b/src/commands/table_viewer.h @@ -0,0 +1,45 @@ +#ifndef TABLE_VIEWER_H +#define TABLE_VIEWER_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 +// . + +#include "../common.h" + +class TableViewer : public InternCommand +{ + Q_OBJECT + +protected: + + Query db; + QString table; + QStringList columns; + QStringList cachedArgs; + bool delOption; + bool del; + uint offset; + +public: + + void term(); + void setParams(const QString &tbl, const QStringList &colms, bool allowDel); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit TableViewer(QObject *parent = nullptr); +}; + +#endif // TABLE_VIEWER_H diff --git a/src/commands/users.cpp b/src/commands/users.cpp new file mode 100644 index 0000000..7a73c8c --- /dev/null +++ b/src/commands/users.cpp @@ -0,0 +1,590 @@ +#include "users.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 +// . + +ListUsers::ListUsers(QObject *parent) : TableViewer(parent) +{ + setParams(TABLE_USERS, QStringList() << COLUMN_TIME << COLUMN_USERNAME << COLUMN_GRNAME << COLUMN_USER_ID, false); +} + +LockUser::LockUser(QObject *parent) : InternCommand(parent) {} +CreateUser::CreateUser(QObject *parent) : InternCommand(parent) {} +RemoveUser::RemoveUser(QObject *parent) : InternCommand(parent) {} +ChangeGroup::ChangeGroup(QObject *parent) : InternCommand(parent) {} +ChangePassword::ChangePassword(QObject *parent) : InternCommand(parent) {} +ChangeDispName::ChangeDispName(QObject *parent) : InternCommand(parent) {} +ChangeUsername::ChangeUsername(QObject *parent) : InternCommand(parent) {} +OverWriteEmail::OverWriteEmail(QObject *parent) : InternCommand(parent) {} +ChangeEmail::ChangeEmail(QObject *parent) : OverWriteEmail(parent) {} +PasswordChangeRequest::PasswordChangeRequest(QObject *parent) : InternCommand(parent) {} +NameChangeRequest::NameChangeRequest(QObject *parent) : InternCommand(parent) {} + +QString ListUsers::cmdName() {return "ls_users";} +QString LockUser::cmdName() {return "lock_acct";} +QString CreateUser::cmdName() {return "add_acct";} +QString RemoveUser::cmdName() {return "rm_acct";} +QString ChangeGroup::cmdName() {return "set_group";} +QString ChangePassword::cmdName() {return "set_pw";} +QString ChangeDispName::cmdName() {return "set_disp_name";} +QString ChangeUsername::cmdName() {return "set_user_name";} +QString OverWriteEmail::cmdName() {return "force_set_email";} +QString ChangeEmail::cmdName() {return "set_email";} +QString PasswordChangeRequest::cmdName() {return "request_new_pw";} +QString NameChangeRequest::cmdName() {return "request_new_user_name";} + +void LockUser::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString uName = getParam("-user", args); + QString state = getParam("-state", args); + + if (uName.isEmpty()) + { + errTxt("err: User name (-user) argument not found or is empty.\n"); + } + else if (state.isEmpty()) + { + errTxt("err: State (-state) argument not found or is empty.\n"); + } + else if (!validUserName(uName)) + { + errTxt("err: Invalid user name.\n"); + } + else if (noCaseMatch(ROOT_USER, uName)) + { + errTxt("err: Unable to lock/unlock protected user: '" + QString(ROOT_USER) + "'\n"); + } + else if (!isBool(state)) + { + errTxt("err: The state bool value (-state) must be a 0 or 1.\n"); + } + else if (!userExists(uName)) + { + errTxt("err: The requested user name does not exists.\n"); + } + else if (!checkRank(*sharedObjs->groupName, getUserGroup(uName))) + { + errTxt("err: The target user account out ranks or is equal to your own rank. access denied.\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_USERS); + db.addColumn(COLUMN_LOCKED, static_cast(state.toInt())); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + } + } +} + +void CreateUser::term() +{ + emit enableMoreInput(false); + + email.clear(); + newName.clear(); +} + +void CreateUser::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + if (moreInputEnabled()) + { + QString password = fromTEXT(binIn); + + if (password.isEmpty()) + { + term(); + } + else if (!validPassword(password)) + { + errTxt("err: Invalid password. it must be 8-200 chars long containing numbers, mixed case letters and special chars.\n\n"); + privTxt("Enter a new password (leave blank to cancel): "); + } + else if (!createUser(newName, email, dispName, password)) + { + errTxt("err: The requested User name already exists.\n"); + term(); + } + else + { + term(); + } + } + else + { + QStringList args = parseArgs(binIn, 4); + + dispName = getParam("-disp", args); + newName = getParam("-name", args); + email = getParam("-email", args); + + if (newName.isEmpty()) + { + errTxt("err: Username (-name) argument not found or is empty.\n"); + } + else if (email.isEmpty()) + { + errTxt("err: Email (-email) argument not found or is empty.\n"); + } + else if (!validUserName(newName)) + { + errTxt("err: Invalid username. it must be 2-24 chars long and contain no spaces.\n"); + } + else if (!validEmailAddr(email)) + { + errTxt("err: Invalid email address. it must contain a '@' symbol along with a vaild host address and user name that contain no spaces. it must also be less than 64 chars long.\n"); + } + else if (!validDispName(dispName)) + { + errTxt("err: The display name is too large or contains a newline char. char limit: 32.\n"); + } + else if (userExists(newName)) + { + errTxt("err: The requested User name already exists.\n"); + } + else if (emailExists(email)) + { + errTxt("err: The requested email address is already in use.\n"); + } + else + { + emit enableMoreInput(true); + + privTxt("Enter a new password (leave blank to cancel): "); + } + } + } +} + +void RemoveUser::term() +{ + emit enableMoreInput(false); + + uName.clear(); +} + +void RemoveUser::rm() +{ + Query db; + + db.setType(Query::DEL, TABLE_USERS); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + emit backendDataOut(ASYNC_USER_DELETED, toTEXT(uName), PUB_IPC_WITH_FEEDBACK); + + term(); +} + +void RemoveUser::ask() +{ + emit enableMoreInput(true); + + mainTxt("Are you sure you want to permanently remove this user account? (y/n): "); +} + +void RemoveUser::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + if (moreInputEnabled()) + { + QString ans = fromTEXT(binIn); + + if (noCaseMatch("y", ans)) + { + rm(); + } + else if (noCaseMatch("n", ans)) + { + term(); + } + else + { + ask(); + } + } + else + { + QStringList args = parseArgs(binIn, 2); + + uName = getParam("-name", args); + + if (uName.isEmpty()) + { + errTxt("err: User name argument (-name) not found or is empty.\n"); + } + else if (noCaseMatch(ROOT_USER, uName)) + { + errTxt("err: Unable to delete protected user: '" + QString(ROOT_USER) + "'\n"); + } + else if (!validUserName(uName)) + { + errTxt("err: Invalid username.\n"); + } + else if (!userExists(uName)) + { + errTxt("err: The requested user name does not exists.\n"); + } + else if (isChOwner(uName)) + { + errTxt("err: The requested user name is the owner of one or more channels.\n"); + } + else if (!checkRank(*sharedObjs->groupName, getUserGroup(uName)) && !noCaseMatch(*sharedObjs->userName, uName)) + { + errTxt("err: The target user account out ranks you, access denied.\n"); + } + else + { + if (argExists("-force", args)) + { + rm(); + } + else + { + ask(); + } + } + } + } +} + +void ChangeGroup::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + + QString uName = getParam("-user", args); + QString gName = getParam("-group", args); + + if (uName.isEmpty()) + { + errTxt("err: User name argument -user not found or is empty.\n"); + } + else if (gName.isEmpty()) + { + errTxt("err: Group name argument -group not found or is empty.\n"); + } + else if (noCaseMatch(ROOT_USER, uName)) + { + errTxt("err: You are not allowed to change the group of protected user: '" + QString(ROOT_USER) + "'\n"); + } + else if (noCaseMatch(ROOT_USER, gName)) + { + errTxt("err: No user created account is allowed to attach to protected group: '" + QString(ROOT_USER) + "'\n"); + } + else if (!validGroupName(gName)) + { + errTxt("err: Invalid group name.\n"); + } + else if (!validUserName(uName)) + { + errTxt("err: Invalid username.\n"); + } + else if (!userExists(uName)) + { + errTxt("err: The requested user account does not exists.\n"); + } + else if (!groupExists(gName)) + { + errTxt("err: The requested group does not exists.\n"); + } + else if (!checkRank(*sharedObjs->groupName, gName, true)) + { + errTxt("err: The target group out ranks your own group. access denied.\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_USERS); + db.addColumn(COLUMN_GRNAME, gName); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + emit backendDataOut(ASYNC_USER_GROUP_CHANGED, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void ChangePassword::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + if (moreInputEnabled()) + { + QString password = fromTEXT(binIn); + + if (password.isEmpty()) + { + emit enableMoreInput(false); + } + else if (!validPassword(password)) + { + errTxt("err: Invalid password. it must be 8-200 chars long containing numbers, mixed case letters and special chars.\n\n"); + privTxt("Enter a new password (leave blank to cancel): "); + } + else + { + emit enableMoreInput(false); + + updatePassword(*sharedObjs->userName, password, TABLE_USERS); + } + } + else + { + emit enableMoreInput(true); + + privTxt("Enter a new password (leave blank to cancel): "); + } + } +} + +void ChangeUsername::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString newName = getParam("-new_name", args); + + if (newName.isEmpty()) + { + errTxt("err: New user name argument (-new_name) not found or is empty.\n"); + } + else if (!validUserName(newName)) + { + errTxt("err: Invalid username. it must be 2-24 chars long and contain no spaces.\n"); + } + else if (userExists(newName)) + { + errTxt("err: The requested user name already exists.\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_USERS); + db.addColumn(COLUMN_USERNAME, newName); + db.addCondition(COLUMN_USERNAME, *sharedObjs->userName); + db.exec(); + + args.append("-old"); + args.append("'" + escapeChars(*sharedObjs->userName, '\\', '\'') + "'"); + + emit backendDataOut(ASYNC_USER_RENAMED, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void ChangeDispName::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString name = getParam("-name", args); + + if (name.isEmpty()) + { + errTxt("err: New display name argument (-name) not found or is empty.\n"); + } + else if (!validDispName(name)) + { + errTxt("err: The display name is too large or contains a newline char. limit: 32 chars.\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_USERS); + db.addColumn(COLUMN_DISPLAY_NAME, name); + db.addCondition(COLUMN_USERNAME, *sharedObjs->userName); + db.exec(); + + args.append("-user"); + args.append("'" + escapeChars(*sharedObjs->userName, '\\', '\'') + "'"); + + emit backendDataOut(ASYNC_DISP_RENAMED, toTEXT(args.join(' ')), PUB_IPC_WITH_FEEDBACK); + } + } +} + +void OverWriteEmail::procArgs(const QString &uName, const QString &newEmail, bool sameRank, const SharedObjs *sharedObjs) +{ + if (newEmail.isEmpty()) + { + errTxt("err: New email address (-new_email) argument was not found or is empty.\n"); + } + else if (uName.isEmpty()) + { + errTxt("err: User name (-user) argument was not found or is empty.\n"); + } + else if (!validUserName(uName)) + { + errTxt("err: Invalid user name.\n"); + } + else if (!validEmailAddr(newEmail)) + { + errTxt("err: Invalid email address.\n"); + } + else if (emailExists(newEmail)) + { + errTxt("err: The requested email address is already in use.\n"); + } + else if (!userExists(uName)) + { + errTxt("err: The requested user account does not exists.\n"); + } + else if (!checkRank(*sharedObjs->groupName, getUserGroup(uName), sameRank)) + { + errTxt("err: The target user account out ranks your own rank. access denied.\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_USERS); + db.addColumn(COLUMN_EMAIL, newEmail); + db.addColumn(COLUMN_EMAIL_VERIFIED, false); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + emit backendDataOut(ASYNC_RW_MY_INFO, toTEXT(uName), PUB_IPC_WITH_FEEDBACK); + } +} + +void OverWriteEmail::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString uName = getParam("-user", args); + QString newEmail = getParam("-new_email", args); + + procArgs(uName, newEmail, false, sharedObjs); + } +} + +void ChangeEmail::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 2); + QString newEmail = getParam("-new_email", args); + + procArgs(*sharedObjs->userName, newEmail, true, sharedObjs); + } +} + +void PasswordChangeRequest::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString uName = getParam("-user", args); + QString req = getParam("-req", args); + + if (uName.isEmpty()) + { + errTxt("err: User name (-user) argument is missing or empty.\n"); + } + else if (req.isEmpty()) + { + errTxt("err: Request bool (-req) argument is missing or empty.\n"); + } + else if (!isBool(req)) + { + errTxt("err: The request bool value (-req) must be a 0 or 1.\n"); + } + else if (!validUserName(uName)) + { + errTxt("err: Invalid user name.\n"); + } + else if (!userExists(uName)) + { + errTxt("err: The requested user account does not exists.\n"); + } + else if (!checkRank(*sharedObjs->groupName, getUserGroup(uName))) + { + errTxt("err: The target user account out ranks or is equal to your own rank. access denied.\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_USERS); + db.addColumn(COLUMN_NEED_PASS, static_cast(req.toInt())); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + } + } +} + +void NameChangeRequest::procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType) +{ + Q_UNUSED(sharedObjs); + + if (dType == TEXT) + { + QStringList args = parseArgs(binIn, 4); + QString uName = getParam("-user", args); + QString req = getParam("-req", args); + + if (uName.isEmpty()) + { + errTxt("err: User name (-user) argument was not found or is empty.\n"); + } + else if (req.isEmpty()) + { + errTxt("err: Request bool (-req) argument was not found or is empty.\n"); + } + else if (!isBool(req)) + { + errTxt("err: The request bool value (-req) must be a 0 or 1.\n"); + } + else if (!validUserName(uName)) + { + errTxt("err: Invalid user name.\n"); + } + else if (!userExists(uName)) + { + errTxt("err: The requested user account does not exists.\n"); + } + else if (!checkRank(*sharedObjs->groupName, getUserGroup(uName))) + { + errTxt("err: The target user account out ranks or is equal to your own rank. access denied.\n"); + } + else + { + Query db(this); + + db.setType(Query::UPDATE, TABLE_USERS); + db.addColumn(COLUMN_NEED_NAME, static_cast(req.toInt())); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + } + } +} diff --git a/src/commands/users.h b/src/commands/users.h new file mode 100644 index 0000000..1b8471a --- /dev/null +++ b/src/commands/users.h @@ -0,0 +1,218 @@ +#ifndef USERS_H +#define USERS_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 +// . + +#include "../common.h" +#include "table_viewer.h" + +class ListUsers : public TableViewer +{ + Q_OBJECT + +public: + + static QString cmdName(); + + explicit ListUsers(QObject *parent = nullptr); +}; + +//------------------------------------ + +class LockUser : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit LockUser(QObject *parent = nullptr); +}; + +//------------------------------------ + +class CreateUser : public InternCommand +{ + Q_OBJECT + +private: + + QString newName; + QString dispName; + QString email; + +public: + + static QString cmdName(); + + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit CreateUser(QObject *parent = nullptr); +}; + +//----------------------------------- + +class RemoveUser : public InternCommand +{ + Q_OBJECT + +private: + + QString uName; + + void rm(); + void ask(); + +public: + + static QString cmdName(); + + void term(); + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit RemoveUser(QObject *parent = nullptr); +}; + +//----------------------------------- + +class ChangeGroup : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit ChangeGroup(QObject *parent = nullptr); +}; + +//--------------------------------- + +class ChangePassword : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit ChangePassword(QObject *parent = nullptr); +}; + +//------------------------------- + +class ChangeUsername : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit ChangeUsername(QObject *parent = nullptr); +}; + +//----------------------------- + +class ChangeDispName : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit ChangeDispName(QObject *parent = nullptr); +}; + +//----------------------------- + +class OverWriteEmail : public InternCommand +{ + Q_OBJECT + +protected: + + void procArgs(const QString &uName, const QString &newEmail, bool sameRank, const SharedObjs *sharedObjs); + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit OverWriteEmail(QObject *parent = nullptr); +}; + +//----------------------------- + +class ChangeEmail : public OverWriteEmail +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit ChangeEmail(QObject *parent = nullptr); +}; + +//----------------------------- + +class PasswordChangeRequest : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit PasswordChangeRequest(QObject *parent = nullptr); +}; + +//---------------------------- + +class NameChangeRequest : public InternCommand +{ + Q_OBJECT + +public: + + static QString cmdName(); + + void procBin(const SharedObjs *sharedObjs, const QByteArray &binIn, uchar dType); + + explicit NameChangeRequest(QObject *parent = nullptr); +}; + +#endif // USERS_H diff --git a/src/common.cpp b/src/common.cpp new file mode 100644 index 0000000..18a50af --- /dev/null +++ b/src/common.cpp @@ -0,0 +1,1788 @@ +#include "common.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 +// . + +QString sessionCountShareKey() +{ + return QString(APP_NAME) + ".SessionCount"; +} + +QString boolStr(bool state) +{ + QString ret; + + if (state) ret = "true"; + else ret = "false"; + + return ret; +} + +uint rdSessionLoad() +{ + uint ret = 0; + + QSharedMemory mem(sessionCountShareKey()); + + if (mem.attach(QSharedMemory::ReadOnly)) + { + mem.lock(); + + memcpy(&ret, mem.data(), 4); + + mem.unlock(); + mem.detach(); + } + + return ret; +} + +void wrSessionLoad(uint value) +{ + QSharedMemory mem(sessionCountShareKey()); + + if (mem.attach(QSharedMemory::ReadWrite)) + { + mem.lock(); + + memcpy(mem.data(), &value, 4); + + mem.unlock(); + mem.detach(); + } +} + +QString genSerialNumber() +{ + return QDateTime::currentDateTime().toString("yyyyMMddHHmmsszzz"); +} + +void serializeThread(QThread *thr) +{ + thr->setObjectName(genSerialNumber()); +} + +QByteArray wrFrame(quint16 cmdId, const QByteArray &data, uchar dType) +{ + QByteArray cmdBa = wrInt(cmdId, 16); + QByteArray typeBa = wrInt(dType, 8); + QByteArray sizeBa = wrInt(data.size(), MAX_FRAME_BITS); + + return typeBa + cmdBa + sizeBa + data; +} + +QByteArray wrInt(quint64 num, int numOfBits) +{ + QByteArray ret(numOfBits / 8, static_cast(0)); + + num = qToLittleEndian(num); + + memcpy(ret.data(), &num, static_cast(ret.size())); + + return ret; +} + +QByteArray wrInt(qint64 num, int numOfBits) +{ + return wrInt(static_cast(num), numOfBits); +} + +QByteArray wrInt(int num, int numOfBits) +{ + return wrInt(static_cast(num), numOfBits); +} + +QByteArray wrInt(uint num, int numOfBits) +{ + return wrInt(static_cast(num), numOfBits); +} + +QByteArray toFILE_INFO(const QFileInfo &info) +{ + // this function converts some information extracted from a QFileInfo object to + // a FILE_INFO frame. + + // format: [1byte(flags)][8bytes(createTime)][8bytes(modTime)][8bytes(fileSize)] + // [TEXT(fileName)][TEXT(symLinkTarget)] + + // note: the TEXT strings are 16bit NULL terminated meaning 2 bytes of 0x00 + // indicate the end of the string. + + // note: the integer data found in flags, modTime, createTime and fileSize + // are formatted in little endian byte order (unsigned). + + char flags = 0; + + if (info.isFile()) flags |= IS_FILE; + if (info.isDir()) flags |= IS_DIR; + if (info.isSymLink()) flags |= IS_SYMLNK; + if (info.isReadable()) flags |= CAN_READ; + if (info.isWritable()) flags |= CAN_WRITE; + if (info.isExecutable()) flags |= CAN_EXE; + if (info.exists()) flags |= EXISTS; + + QByteArray ret; + QByteArray strTerm(2, 0); + + ret.append(flags); + ret.append(wrInt(info.birthTime().toMSecsSinceEpoch(), 64)); + ret.append(wrInt(info.lastModified().toMSecsSinceEpoch(), 64)); + ret.append(wrInt(info.size(), 64)); + ret.append(toTEXT(info.fileName()) + strTerm); + ret.append(toTEXT(info.symLinkTarget() + strTerm)); + + return ret; +} + +QByteArray toFILE_INFO(const QString &path) +{ + return toFILE_INFO(QFileInfo(path)); +} + +QByteArray toPEER_INFO(const SharedObjs *sharedObjs) +{ + return *sharedObjs->sessionId + + *sharedObjs->userId + + fixedToTEXT(*sharedObjs->userName, 24) + + fixedToTEXT(*sharedObjs->appName, 64) + + fixedToTEXT(*sharedObjs->displayName, 32); +} + +QByteArray toNEW_CMD(quint16 cmdId, const QString &cmdName, ExternCommand *cmdObj) +{ + QByteArray idBa = wrInt(cmdId, 16); + QByteArray genBa = wrInt(0, 8); + + if (cmdObj->handlesGenfile()) + { + genBa = wrInt(1, 8); + } + + return idBa + genBa + fixedToTEXT(cmdName, 64) + fixedToTEXT(cmdObj->libText(), 64); +} + +QByteArray toMY_INFO(const SharedObjs *sharedObjs) +{ + Query db; + + db.setType(Query::PULL, TABLE_USERS); + db.addColumn(COLUMN_EMAIL); + db.addColumn(COLUMN_EMAIL_VERIFIED); + db.addCondition(COLUMN_USERNAME, *sharedObjs->userName); + db.exec(); + + QByteArray confirmed; + + if (db.getData(COLUMN_EMAIL_VERIFIED).toBool()) + { + confirmed.append(static_cast(0x01)); + } + else + { + confirmed.append(static_cast(0x00)); + } + + return toPEER_INFO(sharedObjs) + + fixedToTEXT(db.getData(COLUMN_EMAIL).toString(), 64) + + fixedToTEXT(*sharedObjs->groupName, 12) + + confirmed; +} + +QByteArray toPEER_STAT(const QByteArray &sesId, const QByteArray &chIds, bool isDisconnecting) +{ + if (isDisconnecting) + { + return sesId + chIds + QByteArray(1, 0x01); + } + else + { + return sesId + chIds + QByteArray(1, 0x00); + } +} + +QByteArray toTEXT(const QString &txt) +{ + QByteArray ret = QTextCodec::codecForName(TXT_CODEC)->fromUnicode(txt); + + return ret.mid(2); // removes BOM. +} + +QByteArray fixedToTEXT(const QString &txt, int len) +{ + return toTEXT(txt.leftJustified(len, ' ', true)); +} + +QString fromTEXT(const QByteArray &txt) +{ + return QTextCodec::codecForName(TXT_CODEC)->toUnicode(txt); +} + +quint64 rdInt(const QByteArray &bytes) +{ + quint64 ret = 0; + + memcpy(&ret, bytes.data(), static_cast(bytes.size())); + + return qFromLittleEndian(ret); +} + +bool noCaseMatch(const QString &strA, const QString &strB) +{ + return strA.toLower() == strB.toLower(); +} + +bool containsNewLine(const QString &str) +{ + bool ret = false; + + for (auto&& chr : str) + { + if (chr.category() == QChar::Other_Control) + { + ret = true; + } + } + + return ret; +} + +bool validSubId(const QString &num) +{ + bool ret = false; + + if (isInt(num)) + { + ret = (num.toInt() >= 0) && (num.toInt() <= 255); + } + + return ret; +} + +bool validUserName(const QString &uName) +{ + bool ret = false; + + if ((uName.size() >= 2) && (uName.size() <= 24)) + { + ret = !uName.contains(' ') && !containsNewLine(uName); + } + + return ret; +} + +bool validCommonName(const QString &name) +{ + bool ret = false; + + if ((name.size() >= 1) && (name.size() <= 200)) + { + ret = !name.contains(' ') && !containsNewLine(name); + } + + return ret; +} + +bool validEmailAddr(const QString &email) +{ + bool ret = false; + + QStringList spEmail = email.split('@'); + + if ((spEmail.size() == 2) && (email.size() >= 4) && (email.size() <= 64)) + { + if (!email.contains(' ') && !containsNewLine(email)) + { + ret = (spEmail[1].split('.').size() > 1); + } + } + + return ret; +} + +bool validGroupName(const QString &grName) +{ + bool ret = false; + + if ((grName.size() >= 1) && (grName.size() <= 12)) + { + ret = !grName.contains(' ') && !containsNewLine(grName); + } + + return ret; +} + +bool validCommandName(const QString &name) +{ + bool ret = true; + + if ((name.size() >= 1) && (name.size() <= 64) && !name.contains(' ')) + { + for (auto&& chr : name) + { + if (!chr.isNumber() && + !chr.isLetter() && + !(chr == '_') && + !(chr == '?')) + { + ret = false; break; + } + } + } + else + { + ret = false; + } + + return ret; +} + +bool validDispName(const QString &name) +{ + return (name.size() <= 32) && !containsNewLine(name); +} + +bool validChName(const QString &name) +{ + bool ret = false; + + if ((name.size() >= 4) && (name.size() <= 32)) + { + ret = !name.contains(' ') && !containsNewLine(name); + } + + return ret; +} + +bool validLevel(const QString &num, bool includePub) +{ + bool ret = false; + + if (isInt(num)) + { + if (includePub) + { + ret = (num.toInt() >= 1) && (num.toInt() <= PUBLIC); + } + else + { + ret = (num.toInt() >= 1) && (num.toInt() <= REGULAR); + } + } + + return ret; +} + +bool validPassword(const QString &pw) +{ + bool ret = false; + + if ((pw.size() >= 8) && (pw.size() <= 200)) + { + bool letters = false; + bool numbers = false; + bool upper = false; + bool lower = false; + bool special = false; + + for (int i = 0; i < pw.size(); ++i) + { + if (pw[i].isLetter()) {letters = true; break;} + } + + for (int i = 0; (i < pw.size()) && letters; ++i) + { + if (pw[i].isNumber()) {numbers = true; break;} + } + + for (int i = 0; (i < pw.size()) && numbers; ++i) + { + if (pw[i].isUpper()) {upper = true; break;} + } + + for (int i = 0; (i < pw.size()) && upper; ++i) + { + if (pw[i].isLower()) {lower = true; break;} + } + + for (int i = 0; (i < pw.size()) && lower; ++i) + { + if (pw[i].isSymbol() || pw[i].isPunct()) {special = true; break;} + } + + ret = (letters && numbers && upper && lower && special); + } + + return ret; +} + +bool matchedFsObjTypes(const QString &pathA, const QString &pathB) +{ + QFileInfo infoA(pathA); + QFileInfo infoB(pathB); + + bool ret = false; + + if (infoA.isSymLink()) ret = infoB.isSymLink(); + else if (infoA.isFile()) ret = infoB.isFile(); + else ret = infoB.isDir(); + + return ret; +} + +bool matchedVolume(const QString &pathA, const QString &pathB) +{ + QFileInfo infoA(pathA); + QFileInfo infoB(pathB); + + QStorageInfo storA(infoA.absolutePath()); + QStorageInfo storB(infoB.absolutePath()); + + return storA.device() == storB.device(); +} + +void mkPathForFile(const QString &path) +{ + mkPath(QFileInfo(path).absolutePath()); +} + +bool userExists(const QString &uName) +{ + Query db; + + db.setType(Query::PULL, TABLE_USERS); + db.addColumn(COLUMN_USERNAME); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + return db.rows(); +} + +bool recoverPWExists(const QString &uName) +{ + Query db; + + db.setType(Query::PULL, TABLE_PW_RECOVERY); + db.addColumn(COLUMN_USERNAME); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + return db.rows(); +} + +bool emailExists(const QString &email) +{ + Query db; + + db.setType(Query::PULL, TABLE_USERS); + db.addColumn(COLUMN_EMAIL); + db.addCondition(COLUMN_EMAIL, email); + db.exec(); + + return db.rows(); +} + +bool groupExists(const QString &grName) +{ + Query db; + + db.setType(Query::PULL, TABLE_GROUPS); + db.addColumn(COLUMN_GRNAME); + db.addCondition(COLUMN_GRNAME, grName); + db.exec(); + + return db.rows(); +} + +bool modExists(const QString &modName) +{ + Query db; + + db.setType(Query::PULL, TABLE_MODULES); + db.addColumn(COLUMN_MOD_NAME); + db.addCondition(COLUMN_MOD_NAME, modName); + db.exec(); + + return db.rows(); +} + +bool rdOnlyFlagExists(const QString &chName, uchar subId, int level) +{ + Query db; + + db.setType(Query::PULL, TABLE_RDONLY_CAST); + db.addColumn(COLUMN_ACCESS_LEVEL); + db.addCondition(COLUMN_SUB_CH_ID, subId); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.addCondition(COLUMN_ACCESS_LEVEL, level); + db.exec(); + + return db.rows(); +} + +bool isLocked(const QString &uName) +{ + Query db; + + db.setType(Query::PULL, TABLE_USERS); + db.addColumn(COLUMN_LOCKED); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + return db.getData(COLUMN_LOCKED).toBool(); +} + +bool commandHasRank(const QString &cmdName) +{ + Query db; + + db.setType(Query::PULL, TABLE_CMD_RANKS); + db.addColumn(COLUMN_COMMAND); + db.addCondition(COLUMN_COMMAND, cmdName); + db.exec(); + + return db.rows(); +} + +bool checkRank(const QString &myGroup, const QString &targetGroup, bool equalAcceptable) +{ + uint myRank = getRankForGroup(myGroup); + uint targetRank = getRankForGroup(targetGroup); + bool ret = false; + + if (equalAcceptable) + { + ret = (myRank <= targetRank); + } + else + { + ret = (myRank < targetRank); + } + + return ret; +} + +bool maxedInstalledMods() +{ + Query db; + + db.setType(Query::PULL, TABLE_MODULES); + db.addColumn(COLUMN_MOD_NAME); + db.exec(); + + int installed = db.rows(); + double max = (qPow(2, 16) - (MAX_CMDS_PER_MOD * 2)) / MAX_CMDS_PER_MOD; + //max commands - (max async commands + max internal commands) / max commands per mod + + return installed >= max; +} + +bool isBool(const QString &str) +{ + bool ret = false; + int binary = str.toInt(&ret); + + if (ret) + { + ret = (binary == 1) || (binary == 0); + } + + return ret; +} + +bool isInt(const QString &str) +{ + bool ret; + + str.toULongLong(&ret); + + return ret; +} + +bool containsChId(const QByteArray &chId, const QByteArray &chIds) +{ + bool ret = false; + + for (int i = 0; i < chIds.size(); i += 9) + { + QByteArray id = QByteArray::fromRawData(chIds.data() + i, 9); + + if (id == chId) + { + ret = true; + + break; + } + } + + return ret; +} + +bool matchChs(const QByteArray &chsA, const QByteArray &chsB) +{ + bool ret = false; + + for (int i = 0; i < chsA.size(); i += 9) + { + QByteArray id = QByteArray::fromRawData(chsA.data() + i, 9); + + if (containsChId(id, chsB)) + { + ret = true; + + break; + } + } + + return ret; +} + +bool containsActiveCh(const QByteArray &chIds) +{ + bool ret = false; + + Query db; + + for (int i = 0; i < chIds.size(); i += 9) + { + quint64 chId = rdInt(QByteArray::fromRawData(chIds.data() + i, 8)); + quint64 subId = rdInt(QByteArray::fromRawData(chIds.data() + (i + 8), 1)); + + db.setType(Query::PULL, TABLE_SUB_CHANNELS); + db.addColumn(COLUMN_CHANNEL_ID); + db.addCondition(COLUMN_CHANNEL_ID, chId); + db.addCondition(COLUMN_SUB_CH_ID, subId); + db.addCondition(COLUMN_ACTIVE_UPDATE, true); + db.exec(); + + if (db.rows()) + { + ret = true; + + break; + } + } + + return ret; +} + +bool channelExists(quint64 chId) +{ + Query db; + + db.setType(Query::PULL, TABLE_CHANNELS); + db.addColumn(COLUMN_CHANNEL_ID); + db.addCondition(COLUMN_CHANNEL_ID, chId); + db.exec(); + + return db.rows(); +} + +bool channelExists(const QString &chName) +{ + Query db; + + db.setType(Query::PULL, TABLE_CHANNELS); + db.addColumn(COLUMN_CHANNEL_NAME); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.exec(); + + return db.rows(); +} + +bool channelSubExists(quint64 chId, uchar subId) +{ + Query db; + + db.setType(Query::PULL, TABLE_SUB_CHANNELS); + db.addColumn(COLUMN_SUB_CH_ID); + db.addCondition(COLUMN_CHANNEL_ID, chId); + db.addCondition(COLUMN_SUB_CH_ID, subId); + db.exec(); + + return db.rows(); +} + +bool channelSubExists(const QString &ch, const QString &sub) +{ + Query db; + + db.setType(Query::PULL, TABLE_SUB_CHANNELS); + db.addColumn(COLUMN_SUB_CH_NAME); + db.addCondition(COLUMN_CHANNEL_NAME, ch); + db.addCondition(COLUMN_SUB_CH_NAME, sub); + db.exec(); + + return db.rows(); +} + +bool inviteExists(const QString &uName, const QString &chName) +{ + Query db; + + db.setType(Query::PULL, TABLE_CH_MEMBERS); + db.addColumn(COLUMN_CHANNEL_NAME); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.addCondition(COLUMN_USERNAME, uName); + db.addCondition(COLUMN_PENDING_INVITE, true); + db.exec(); + + return db.rows(); +} + +bool memberExists(const QString &uName, const QString &chName) +{ + Query db; + + db.setType(Query::PULL, TABLE_CH_MEMBERS); + db.addColumn(COLUMN_CHANNEL_NAME); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + 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 allowMemberDel(const SharedObjs *sharedObjs, const QString &targetUName, const QString &chName) +{ + bool ret = false; + + if (memberExists(targetUName, chName)) + { + int targetLevel = channelAccessLevel(targetUName, chName); + + if (targetLevel != OWNER) + { + if (noCaseMatch(*sharedObjs->userName, targetUName)) + { + ret = true; + } + else if (channelAccessLevel(sharedObjs, chName) < targetLevel) + { + ret = true; + } + } + } + + return ret; +} + +bool allowLevelChange(const SharedObjs *sharedObjs, int newLevel, const QString &chName) +{ + bool ret = false; + int myLevel = channelAccessLevel(*sharedObjs->userName, chName); + + if ((myLevel == OWNER) && (newLevel == OWNER)) + { + ret = true; + } + else if (newLevel > myLevel) + { + ret = true; + } + + return ret; +} + +bool genSubId(const QString &chName, int *newId) +{ + bool ret = false; + + Query db; + + db.setType(Query::PULL, TABLE_SUB_CHANNELS); + db.addColumn(COLUMN_SUB_CH_ID); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.exec(); + + if (db.rows() < maxSubChannels()) + { + QList subList; + + for (int i = 0; i < db.rows(); ++i) + { + subList.append(db.getData(COLUMN_SUB_CH_ID, i).toInt()); + } + + ret = true; + *newId = 0; + + while (subList.contains(*newId)) *newId += 1; + } + + return ret; +} + +bool isChOwner(const QString &uName) +{ + Query db; + + db.setType(Query::PULL, TABLE_CH_MEMBERS); + db.addColumn(COLUMN_USERNAME); + db.addCondition(COLUMN_USERNAME, uName); + db.addCondition(COLUMN_PENDING_INVITE, false); + db.addCondition(COLUMN_ACCESS_LEVEL, OWNER); + db.exec(); + + return db.rows(); +} + +int channelAccessLevel(const QString &uName, quint64 chId) +{ + Query db; + + db.setType(Query::PULL, TABLE_CH_MEMBERS); + db.addColumn(COLUMN_ACCESS_LEVEL); + db.addCondition(COLUMN_CHANNEL_ID, chId); + db.addCondition(COLUMN_USERNAME, uName); + db.addCondition(COLUMN_PENDING_INVITE, false); + db.exec(); + + if (db.rows()) + { + return db.getData(COLUMN_ACCESS_LEVEL).toInt(); + } + else + { + return PUBLIC; + } +} + +int channelAccessLevel(const QString &uName, const QString &chName) +{ + Query db; + + db.setType(Query::PULL, TABLE_CH_MEMBERS); + db.addColumn(COLUMN_ACCESS_LEVEL); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.addCondition(COLUMN_USERNAME, uName); + db.addCondition(COLUMN_PENDING_INVITE, false); + db.exec(); + + if (db.rows()) + { + return db.getData(COLUMN_ACCESS_LEVEL).toInt(); + } + else + { + return PUBLIC; + } +} + +int channelAccessLevel(const SharedObjs *sharedObjs, const QString &chName) +{ + if (*sharedObjs->chOwnerOverride) + { + return OWNER; + } + else + { + return channelAccessLevel(*sharedObjs->userName, chName); + } +} + +int channelAccessLevel(const SharedObjs *sharedObjs, quint64 chId) +{ + if (*sharedObjs->chOwnerOverride) + { + return OWNER; + } + else + { + return channelAccessLevel(*sharedObjs->userName, chId); + } +} + +int lowestAcessLevel(quint64 chId, uchar subId) +{ + Query db; + + db.setType(Query::PULL, TABLE_SUB_CHANNELS); + db.addColumn(COLUMN_LOWEST_LEVEL); + db.addCondition(COLUMN_CHANNEL_ID, chId); + db.addCondition(COLUMN_SUB_CH_ID, subId); + db.exec(); + + if (db.rows()) + { + return db.getData(COLUMN_LOWEST_LEVEL).toInt(); + } + else + { + return 5000; + } +} + +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(); +} + +quint64 getChId(const QString &chName) +{ + Query db; + + db.setType(Query::PULL, TABLE_CHANNELS); + db.addColumn(COLUMN_CHANNEL_ID); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.exec(); + + return db.getData(COLUMN_CHANNEL_ID).toULongLong(); +} + +uchar getSubId(const QString &chName, const QString &subName) +{ + Query db; + + db.setType(Query::PULL, TABLE_SUB_CHANNELS); + db.addColumn(COLUMN_SUB_CH_ID); + db.addCondition(COLUMN_CHANNEL_NAME, chName); + db.addCondition(COLUMN_SUB_CH_NAME, subName); + db.exec(); + + return static_cast(db.getData(COLUMN_SUB_CH_ID).toUInt()); +} + +int chPos(const QByteArray &id, const QByteArray &chIds) +{ + int ret = -1; + + for (int i = 0; i < chIds.size(); i += 9) + { + QByteArray chInList = QByteArray::fromRawData(chIds.data() + i, 9); + + if (chInList == id) + { + ret = i; + + break; + } + } + + return ret; +} + +int countChs(const QByteArray &chIds) +{ + int ret = 0; + + for (int i = 0; i < chIds.size(); i += 9) + { + quint64 id = rdInt(QByteArray::fromRawData(chIds.data() + i, 8)); + + if (id != 0) + { + ret++; + } + } + + return ret; +} + +int blankChPos(const QByteArray &chIds) +{ + int ret = -1; + + for (int i = 0; i < chIds.size(); i += 9) + { + quint64 id = rdInt(QByteArray::fromRawData(chIds.data() + i, 8)); + + if (id == 0) + { + ret = i; + + break; + } + } + + return ret; +} + +uint getRankForGroup(const QString &grName) +{ + Query db; + + db.setType(Query::PULL, TABLE_GROUPS); + db.addColumn(COLUMN_HOST_RANK); + db.addCondition(COLUMN_GRNAME, grName); + db.exec(); + + return db.getData(COLUMN_HOST_RANK).toUInt(); +} + +void uniqueAdd(quint16 id, QList &list) +{ + if (!list.contains(id)) + { + list.append(id); + } +} + +void listDir(QList > &list, const QString &srcPath, const QString &dstPath) +{ + QDir dir(srcPath); + + dir.setFilter(QDir::AllEntries | QDir::Hidden | QDir::NoDotAndDotDot); + dir.setSorting(QDir::DirsFirst); + + QStringList ls = dir.entryList(); + + for (int i = 0; i < ls.size(); ++i) + { + QPair srcToDst(srcPath + "/" + ls[i], dstPath + "/" + ls[i]); + + list.append(srcToDst); + } +} + +QString getUserGroup(const QString &uName) +{ + Query db; + + db.setType(Query::PULL, TABLE_USERS); + db.addColumn(COLUMN_GRNAME); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + return db.getData(COLUMN_GRNAME).toString(); +} + +QString getUserNameForEmail(const QString &email) +{ + Query db; + + db.setType(Query::PULL, TABLE_USERS); + db.addColumn(COLUMN_USERNAME); + db.addCondition(COLUMN_EMAIL, email); + db.exec(); + + return db.getData(COLUMN_USERNAME).toString(); +} + +QString getEmailForUser(const QString &uName) +{ + Query db; + + db.setType(Query::PULL, TABLE_USERS); + db.addColumn(COLUMN_EMAIL); + db.addCondition(COLUMN_USERNAME, uName); + db.exec(); + + return db.getData(COLUMN_EMAIL).toString(); +} + +QString escapeChars(const QString &str, const QChar &escapeChr, const QChar &chr) +{ + QString ret; + bool escaped = false; + + if (escapeChr == chr) + { + ret = str; + } + else + { + for (auto&& strChr : str) + { + if ((strChr == chr) && !escaped) + { + ret.append(escapeChr); + ret.append(strChr); + } + else + { + escaped = false; + + if (strChr == escapeChr) + { + escaped = true; + } + + ret.append(strChr); + } + } + } + + return ret; +} + +QList genSequence(int min, int max, int len) +{ + QList ret; + + for (int i = 0; i < len; ++i) + { + ret.append(QRandomGenerator::global()->bounded(min, max)); + } + + return ret; +} + +QChar genLetter() +{ + // generate random letter from ascii table decimal value 97-122. + + return QChar(static_cast(QRandomGenerator::global()->bounded(97, 122))); +} + +QChar genNum() +{ + // generate random number from ascii table decimal value 48-57. + + return QChar(static_cast(QRandomGenerator::global()->bounded(48, 57))); +} + +QChar genSpecialChar() +{ + static QString specialChars = "`~!@#$%^&*()-_+=[]{}\\|:;\"'<,>.?/"; + + return specialChars[QRandomGenerator::global()->bounded(0, specialChars.size() - 1)]; +} + +int inRange(int pos, int min, int max) +{ + int ret = pos; + + if (pos < min) ret = min; + if (pos > max) ret = max; + + return ret; +} + +void moveCharLeft(int pos, QString &str) +{ + pos = inRange(pos, 0, str.size() - 1); + + QChar chr = str[pos]; + + str.remove(pos, 1); + + if (pos == 0) str.append(chr); + else str.insert(pos - 1, chr); +} + +void moveCharRight(int pos, QString &str) +{ + pos = inRange(pos, 0, str.size() - 1); + + QChar chr = str[pos]; + + str.remove(pos, 1); + + if (pos == str.size() - 1) str.insert(0, chr); + else str.insert(pos + 1, chr); +} + +QString genPw() +{ + QString ret; + + QList seq = genSequence(2, 5, 4); + + for (int i = 0; i < seq[0]; ++i) + { + ret.append(genLetter()); + } + + for (int i = 0; i < seq[1]; ++i) + { + ret.append(genLetter().toUpper()); + } + + for (int i = 0; i < seq[2]; ++i) + { + ret.append(genNum()); + } + + for (int i = 0; i < seq[3]; ++i) + { + ret.append(genSpecialChar()); + } + + seq = genSequence(0, ret.size() - 1, 10); + + bool toggle = false; + + for (int i : seq) + { + if (toggle) moveCharRight(i, ret); + else moveCharLeft(i, ret); + + toggle = !toggle; + } + + return ret; +} + +QString modDataPath() +{ + QString ret = qEnvironmentVariable(ENV_MOD_PATH, DEFAULT_MOD_PATH); + + if (ret.right(1) == '/') ret.chop(1); + else if (ret.right(1) == '\\') ret.chop(1); + + ret = expandEnvVariables(ret); + + mkPath(ret); + + return ret; +} + +QString pipesPath() +{ + QString ret = qEnvironmentVariable(ENV_PIPE_PATH, DEFAULT_PIPE_PATH); + + if (ret.right(1) == '/') ret.chop(1); + else if (ret.right(1) == '\\') ret.chop(1); + + ret = expandEnvVariables(ret); + + mkPath(ret); + + return ret; +} + +void msgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + Q_UNUSED(type); + Q_UNUSED(context); + + if (!msg.contains("QSslSocket: cannot resolve")) + { + Query db; + + db.setType(Query::PUSH, TABLE_DMESG); + db.addColumn(COLUMN_LOGENTRY, msg); + db.exec(); + } +} + +void mkPath(const QString &path) +{ + if (!QDir().exists(path)) + { + QDir().mkpath(path); + } +} + +void mkFile(const QString &path) +{ + mkPathForFile(path); + + QFile file(path); + + file.open(QFile::WriteOnly); + file.write(QByteArray()); + file.close(); +} + +void wrOpenCh(RWSharedObjs *sharedObjs, const QByteArray &id) +{ + quint64 chId = rdInt(QByteArray::fromRawData(id.data(), 8)); + quint64 subId = rdInt(QByteArray::fromRawData(id.data() + 8, 1)); + int rdPos = blankChPos(*sharedObjs->chIds); + int wrPos = blankChPos(*sharedObjs->wrAbleChIds); + int lvl; + + if (*sharedObjs->chOwnerOverride) + { + lvl = OWNER; + } + else + { + lvl = channelAccessLevel(*sharedObjs->userName, chId); + } + + sharedObjs->chIds->replace(rdPos, 9, id); + + Query db; + + db.setType(Query::PULL, TABLE_RDONLY_CAST); + db.addColumn(COLUMN_CHANNEL_ID); + db.addCondition(COLUMN_CHANNEL_ID, chId); + db.addCondition(COLUMN_SUB_CH_ID, subId); + db.addCondition(COLUMN_ACCESS_LEVEL, lvl); + db.exec(); + + if (db.rows() == 0) + { + sharedObjs->wrAbleChIds->replace(wrPos, 9, id); + } + + if (globalActiveFlag()) + { + *sharedObjs->activeUpdate = true; + } + else + { + *sharedObjs->activeUpdate = containsActiveCh(*sharedObjs->chIds); + } +} + +void wrCloseCh(RWSharedObjs *sharedObjs, const QByteArray &id, QByteArray &peerStat) +{ + int rdPos = chPos(id, *sharedObjs->chIds); + int wrPos = chPos(id, *sharedObjs->wrAbleChIds); + bool needPeerStat = false; + QByteArray oldChIds = *sharedObjs->chIds; + + if (*sharedObjs->activeUpdate) + { + needPeerStat = true; + } + + if (rdPos != -1) + { + sharedObjs->chIds->replace(rdPos, 9, QByteArray(9, static_cast(0))); + + if (wrPos != -1) + { + sharedObjs->wrAbleChIds->replace(wrPos, 9, QByteArray(9, static_cast(0))); + } + + *sharedObjs->activeUpdate = containsActiveCh(*sharedObjs->chIds); + } + + if (needPeerStat && (oldChIds != *sharedObjs->chIds)) + { + QByteArray castHeader = oldChIds + wrInt(PEER_STAT, 8); + QByteArray data = toPEER_STAT(*sharedObjs->sessionId, *sharedObjs->chIds, false); + + peerStat = castHeader + data; + } + else + { + peerStat.clear(); + } +} + +void wrCloseCh(RWSharedObjs *sharedObjs, quint64 chId, QByteArray &peerStat) +{ + QByteArray chBa = wrInt(chId, 64); + QByteArray oldChIds = *sharedObjs->chIds; + bool needPeerStat = false; + + if (*sharedObjs->activeUpdate) + { + needPeerStat = true; + } + + for (int i = 0; i < sharedObjs->chIds->size(); i += 9) + { + if (chBa == QByteArray::fromRawData(sharedObjs->chIds->data() + i, 8)) + { + sharedObjs->chIds->replace(i, 9, QByteArray(9, static_cast(0))); + } + + if (chBa == QByteArray::fromRawData(sharedObjs->wrAbleChIds->data() + i, 8)) + { + sharedObjs->wrAbleChIds->replace(i, 9, QByteArray(9, static_cast(0))); + } + } + + *sharedObjs->activeUpdate = containsActiveCh(*sharedObjs->chIds); + + if (needPeerStat && (oldChIds != *sharedObjs->chIds)) + { + QByteArray castHeader = oldChIds + wrInt(PEER_STAT, 8); + QByteArray data = toPEER_STAT(*sharedObjs->sessionId, *sharedObjs->chIds, false); + + peerStat = castHeader + data; + } + else + { + peerStat.clear(); + } +} + +void wrCloseCh(RWSharedObjs *sharedObjs, const QByteArray &id) +{ + QByteArray unused; + + wrCloseCh(sharedObjs, id, unused); +} + +void wrCloseCh(RWSharedObjs *sharedObjs, quint64 chId) +{ + QByteArray unused; + + wrCloseCh(sharedObjs, chId, unused); +} + +QStringList parseArgs(const QByteArray &data, int maxArgs, int *pos) +{ + QStringList ret; + QString arg; + QString line = fromTEXT(data); + bool inDQuotes = false; + bool inSQuotes = false; + bool escaped = false; + + if (pos != nullptr) *pos = 0; + + for (int i = 0; i < line.size(); ++i) + { + if (pos != nullptr) *pos += (TXT_CODEC_BITS / 8); + + if ((line[i] == '\'') && !inDQuotes && !escaped) + { + // single quote ' + + inSQuotes = !inSQuotes; + } + else if ((line[i] == '\"') && !inSQuotes && !escaped) + { + // double quote " + + inDQuotes = !inDQuotes; + } + else + { + escaped = false; + + if (line[i].isSpace() && !inDQuotes && !inSQuotes) + { + // space + + if (!arg.isEmpty()) + { + ret.append(arg); + arg.clear(); + } + } + else + { + if ((line[i] == '\\') && ((i + 1) < line.size())) + { + if ((line[i + 1] == '\'') || (line[i + 1] == '\"')) + { + escaped = true; + } + else + { + arg.append(line[i]); + } + } + else + { + arg.append(line[i]); + } + } + } + + if ((ret.size() >= maxArgs) && (maxArgs != -1)) + { + break; + } + } + + if (!arg.isEmpty() && !inDQuotes && !inSQuotes) + { + ret.append(arg); + } + + return ret; +} + +QString getParam(const QString &key, const QStringList &args) +{ + // this can be used by command objects to pick out parameters + // from a command line that are pointed by a name identifier + // example: -i /etc/some_file, this function should pick out + // "/etc/some_file" from args if "-i" is passed into key. + + QString ret; + + int pos = args.indexOf(QRegExp(key, Qt::CaseInsensitive)); + + if (pos != -1) + { + // key found. + + if ((pos + 1) <= (args.size() - 1)) + { + // check ahead to make sure pos + 1 will not go out + // of range. + + if (!args[pos + 1].startsWith("-")) + { + // the "-" used throughout this application + // indicates an argument so the above 'if' + // statement will check to make sure it does + // not return another argument as a parameter + // in case a back-to-back "-arg -arg" is + // present. + + ret = args[pos + 1]; + } + } + } + + return ret; +} + +bool argExists(const QString &key, const QStringList &args) +{ + return args.contains(key, Qt::CaseInsensitive); +} + +SessionCarrier::SessionCarrier(Session *session) : QObject() +{ + sessionObj = session; +} + +InternCommand::InternCommand(QObject *parent) : ExternCommand(parent) +{ + rwSharedObjs = nullptr; +} + +void InternCommand::setWritableDataShare(RWSharedObjs *sharedObjs) +{ + rwSharedObjs = sharedObjs; + + term(); +} + +bool InternCommand::loopEnabled() +{ + if (rwSharedObjs == nullptr) + { + return false; + } + else + { + return rwSharedObjs->activeLoopCmds->contains(cmdId); + } +} + +bool InternCommand::moreInputEnabled() +{ + if (rwSharedObjs == nullptr) + { + return false; + } + else + { + return rwSharedObjs->moreInputCmds->contains(cmdId); + } +} + +QString InternCommand::libText() +{ + return INTERN_MOD_NAME; +} + +QString InternCommand::parseMd(int offset) +{ + QFile file(":/docs/intern_commands/" + objectName() + ".md", this); + QByteArray data; + + if (file.open(QFile::ReadOnly)) + { + data = file.readAll(); + } + + file.close(); + + int targetTags = offset * 6; + int pos = -1; + int len = 0; + + for (int i = 0, tags = 0; i < data.size(); ++i) + { + if (data[i] == '#') + { + ++tags; + + if (pos != -1) + { + break; + } + } + else if (tags == targetTags) + { + len++; + + if (pos == -1) + { + pos = i; + } + } + } + + QByteArray ret = data.mid(pos, len).trimmed(); + + if (offset == 2) + { + ret.chop(3); + ret.remove(0, 3); + } + + return ret; +} + +QString InternCommand::shortText() +{ + return parseMd(1); +} + +QString InternCommand::ioText() +{ + return parseMd(2); +} + +QString InternCommand::longText() +{ + return parseMd(3); +} + +CommandOutput::CommandOutput(ExternCommand *parent) : QObject(parent) +{ + // this class is used by CmdExecutor to permanently attach a command id to + // the command object's output data and isolates that id from the object + // itself. this prevents objects from impersonating another object's output + // data. + + cmdObj = parent; +} + +void CommandOutput::setCmdId(quint16 id) +{ + cmdId = id; +} + +void CommandOutput::dataFromCmdObj(const QByteArray &data, uchar typeId) +{ + emit dataOut(cmdId, data, typeId); +} + +void CommandOutput::openChIdFromCmdObj(quint64 id, uchar subId) +{ + emit openChById(cmdId, id, subId); +} + +void CommandOutput::openChNameFromCmdObj(const QString &ch, const QString &sub) +{ + emit openChByName(cmdId, ch, sub); +} + +void CommandOutput::closeChIdFromCmdObj(quint64 id, uchar subId) +{ + emit closeChById(cmdId, id, subId); +} + +void CommandOutput::closeChNameFromCmdObj(const QString &ch, const QString &sub) +{ + emit closeChByName(cmdId, ch, sub); +} + +void CommandOutput::enableLoopFromCmdObj(bool state) +{ + cmdObj->inLoopMode = state; + + emit enableLoop(cmdId, state); +} + +void CommandOutput::enableMoreInputFromCmdObj(bool state) +{ + cmdObj->inMoreInputMode = state; + + emit enableMoreInput(cmdId, state); +} + +void CommandOutput::finished() +{ + cmdObj->inMoreInputMode = false; + cmdObj->inLoopMode = false; + + emit cmdFinished(cmdId); +} + +RWSharedObjs::RWSharedObjs(QObject *parent) : QObject(parent) +{ + commands = nullptr; + cmdNames = nullptr; + chList = nullptr; + chIds = nullptr; + wrAbleChIds = nullptr; + sessionAddr = nullptr; + userName = nullptr; + groupName = nullptr; + displayName = nullptr; + appName = nullptr; + clientMajor = nullptr; + clientMinor = nullptr; + clientPatch = nullptr; + sessionId = nullptr; + moreInputCmds = nullptr; + activeLoopCmds = nullptr; + pausedCmds = nullptr; + p2pAccepted = nullptr; + p2pPending = nullptr; + activeUpdate = nullptr; + chOwnerOverride = nullptr; + hostRank = nullptr; +} + +ShellIPC::ShellIPC(const QStringList &args, QObject *parent) : QLocalSocket(parent) +{ + arguments = args; + pipeName = pipesPath() + "/" + QString(APP_NAME) + ".TCPServer.Control"; + + connect(this, SIGNAL(connected()), this, SLOT(hostConnected())); + connect(this, SIGNAL(disconnected()), this, SIGNAL(closeInstance())); + connect(this, SIGNAL(readyRead()), this, SLOT(dataIn())); +} + +bool ShellIPC::connectToHost() +{ + connectToServer(pipeName); + + if (!waitForConnected()) + { + QTextStream(stdout) << "" << endl << "Host instance not running." << endl << endl; + } + + return state() == QLocalSocket::ConnectedState; +} + +void ShellIPC::hostConnected() +{ + write(toTEXT(arguments.join(' '))); +} + +void ShellIPC::dataIn() +{ + QTextStream(stdout) << fromTEXT(readAll()); + + emit closeInstance(); +} diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..363f68e --- /dev/null +++ b/src/common.h @@ -0,0 +1,429 @@ +#ifndef COMMON_H +#define COMMON_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 +// . + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "db.h" +#include "shell.h" +#include "commands/command.h" + +#define FRAME_HEADER_SIZE 6 +#define MAX_FRAME_BITS 24 +#define IMPORT_REV 1 +#define LOCAL_BUFFSIZE 16777215 +#define CLIENT_INIT_TIME 5000 +#define IPC_PREP_TIME 1000 +#define IPC_CONNECT_DELAY 500 +#define CLIENT_HEADER_LEN 410 +#define SERVER_HEADER_LEN 35 +#define EXE_CRASH_LIMIT 5 +#define EXE_DEBUG_INFO_SIZE 512 +#define SERVER_HEADER_TAG "MRCI" + +#define ASYNC_RDY 1 +#define ASYNC_SYS_MSG 2 +#define ASYNC_EXE_CRASH 3 +#define ASYNC_EXIT 4 // internal only +#define ASYNC_CAST 5 // internal only +#define ASYNC_MAXSES 6 // internal only +#define ASYNC_LOGOUT 7 // internal only +#define ASYNC_USER_DELETED 8 // internal only +#define ASYNC_GROUP_RENAMED 9 // internal only +#define ASYNC_DISP_RENAMED 10 // internal only +#define ASYNC_GRP_TRANS 11 // internal only +#define ASYNC_USER_GROUP_CHANGED 12 // internal only +#define ASYNC_CMD_RANKS_CHANGED 13 // internal only +#define ASYNC_RESTART 14 // internal only +#define ASYNC_ENABLE_MOD 15 // internal only +#define ASYNC_DISABLE_MOD 16 // internal only +#define ASYNC_GROUP_UPDATED 17 // internal only +#define ASYNC_END_SESSION 18 // internal only +#define ASYNC_USER_LOGIN 19 // internal only +#define ASYNC_RESTORE_AUTH 20 // internal only +#define ASYNC_TO_PEER 21 +#define ASYNC_LIMITED_CAST 22 +#define ASYNC_RW_MY_INFO 23 // internal only +#define ASYNC_P2P 24 +#define ASYNC_CLOSE_P2P 25 // internal only +#define ASYNC_NEW_CH_MEMBER 26 +#define ASYNC_DEL_CH 27 +#define ASYNC_RENAME_CH 28 +#define ASYNC_CH_ACT_FLAG 29 +#define ASYNC_NEW_SUB_CH 30 +#define ASYNC_RM_SUB_CH 31 +#define ASYNC_RENAME_SUB_CH 32 +#define ASYNC_INVITED_TO_CH 33 +#define ASYNC_RM_CH_MEMBER 34 +#define ASYNC_INVITE_ACCEPTED 35 +#define ASYNC_MEM_LEVEL_CHANGED 36 +#define ASYNC_SUB_CH_LEVEL_CHG 37 +#define ASYNC_ADD_RDONLY 38 +#define ASYNC_RM_RDONLY 39 +#define ASYNC_ADD_CMD 40 +#define ASYNC_RM_CMD 41 +#define ASYNC_USER_RENAMED 42 +#define ASYNC_PUBLIC_AUTH 43 // internal only + +enum SalveExitCodes +{ + GEN_ERR = 1, + FAILED_TO_OPEN_PIPE = 2, + PIPE_CONNECT_TIMEOUT = 3 +}; + +enum PrivateTypeID +{ + PRIV_IPC = 1, + PUB_IPC = 2, + PUB_IPC_WITH_FEEDBACK = 3, + PING_PEERS = 4 +}; + +enum Flags : uint +{ + IPC_LINK_OK = 1, + IPC_FRAME_RDY = 1 << 1, + TCP_FRAME_RDY = 1 << 2, + SSL_HOLD = 1 << 3, + VER_OK = 1 << 4, + EXPECTED_TERM = 1 << 5, + ACTIVE_PAYLOAD = 1 << 6, + END_SESSION_ON_PAYLOAD_DEL = 1 << 7, + RES_ON_EMPTY = 1 << 8, + CLOSE_ON_EMPTY = 1 << 9, + ACCEPTING = 1 << 10 +}; + +enum FileInfoFlags +{ + IS_FILE = 1, + IS_DIR = 1 << 1, + IS_SYMLNK = 1 << 2, + CAN_READ = 1 << 3, + CAN_WRITE = 1 << 4, + CAN_EXE = 1 << 5, + EXISTS = 1 << 6 +}; + +typedef ExternCommand* (*BuildInternCmd)(QObject*); + +class RWSharedObjs; +class Session; + +QByteArray wrFrame(quint16 cmdId, const QByteArray &data, uchar dType); +QByteArray wrInt(quint64 num, int numOfBits); +QByteArray wrInt(qint64 num, int numOfBits); +QByteArray wrInt(int num, int numOfBits); +QByteArray wrInt(uint num, int numOfBits); +QByteArray toFILE_INFO(const QString &path); +QByteArray toFILE_INFO(const QFileInfo &info); +QByteArray toTEXT(const QString &txt); +QByteArray fixedToTEXT(const QString &txt, int len); +QByteArray toPEER_INFO(const SharedObjs *sharedObjs); +QByteArray toMY_INFO(const SharedObjs *sharedObjs); +QByteArray toPEER_STAT(const QByteArray &sesId, const QByteArray &chIds, bool isDisconnecting); +QByteArray toNEW_CMD(quint16 cmdId, const QString &cmdName, ExternCommand *cmdObj); +quint64 rdInt(const QByteArray &bytes); +quint64 getChId(const QString &chName); +uchar getSubId(const QString &chName, const QString &subName); +uint rdSessionLoad(); +uint getRankForGroup(const QString &grName); +void wrSessionLoad(uint value); +void serializeThread(QThread *thr); +void uniqueAdd(quint16 id, QList &list); +void mkPathForFile(const QString &path); +void msgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); +void mkPath(const QString &path); +void mkFile(const QString &path); +void moveCharLeft(int pos, QString &str); +void moveCharRight(int pos, QString &str); +void listDir(QList > &list, const QString &srcPath, const QString &dstPath); +void wrOpenCh(RWSharedObjs *sharedObjs, const QByteArray &id); +void wrCloseCh(RWSharedObjs *sharedObjs, const QByteArray &id, QByteArray &peerStat); +void wrCloseCh(RWSharedObjs *sharedObjs, quint64 chId, QByteArray &peerStat); +void wrCloseCh(RWSharedObjs *sharedObjs, const QByteArray &id); +void wrCloseCh(RWSharedObjs *sharedObjs, quint64 chId); +bool containsNewLine(const QString &str); +bool validUserName(const QString &uName); +bool validEmailAddr(const QString &email); +bool validPassword(const QString &pw); +bool validGroupName(const QString &grName); +bool validCommandName(const QString &name); +bool validCommonName(const QString &name); +bool validDispName(const QString &name); +bool validChName(const QString &name); +bool validLevel(const QString &num, bool includePub); +bool validSubId(const QString &num); +bool modExists(const QString &modName); +bool userExists(const QString &uName); +bool emailExists(const QString &email); +bool groupExists(const QString &grName); +bool inviteExists(const QString &uName, const QString &chName); +bool memberExists(const QString &uName, const QString &chName); +bool channelExists(quint64 chId); +bool channelExists(const QString &chName); +bool channelSubExists(quint64 chId, uchar subId); +bool channelSubExists(const QString &ch, const QString &sub); +bool recoverPWExists(const QString &uName); +bool rdOnlyFlagExists(const QString &chName, uchar subId, int level); +bool checkRank(const QString &myGroup, const QString &targetGroup, bool equalAcceptable = false); +bool isBool(const QString &str); +bool isInt(const QString &str); +bool isLocked(const QString &uName); +bool commandHasRank(const QString &cmdName); +bool matchedFsObjTypes(const QString &pathA, const QString &pathB); +bool matchedVolume(const QString &pathA, const QString &pathB); +bool noCaseMatch(const QString &strA, const QString &strB); +bool argExists(const QString &key, const QStringList &args); +bool matchChs(const QByteArray &chsA, const QByteArray &chsB); +bool containsChId(const QByteArray &chId, const QByteArray &chIds); +bool containsActiveCh(const QByteArray &chIds); +bool globalActiveFlag(); +bool genSubId(const QString &chName, int *newId); +bool isChOwner(const QString &uName); +bool allowMemberDel(const SharedObjs *sharedObjs, const QString &targetUName, const QString &chName); +bool allowLevelChange(const SharedObjs *sharedObjs, int newLevel, const QString &chName); +bool maxedInstalledMods(); +int channelAccessLevel(const QString &uName, quint64 chId); +int channelAccessLevel(const QString &uName, const QString &chName); +int channelAccessLevel(const SharedObjs *sharedObjs, const QString &chName); +int channelAccessLevel(const SharedObjs *sharedObjs, quint64 chId); +int lowestAcessLevel(quint64 chId, uchar subId); +int chPos(const QByteArray &id, const QByteArray &chIds); +int blankChPos(const QByteArray &chIds); +int countChs(const QByteArray &chIds); +int inRange(int pos, int min, int max); +int maxSubChannels(); +QString fromTEXT(const QByteArray &txt); +QString getUserGroup(const QString &uName); +QString getUserNameForEmail(const QString &email); +QString getEmailForUser(const QString &uName); +QString boolStr(bool state); +QString getParam(const QString &key, const QStringList &args); +QString genPw(); +QString escapeChars(const QString &str, const QChar &escapeChr, const QChar &chr); +QString genSerialNumber(); +QString modDataPath(); +QString pipesPath(); +QString sessionCountShareKey(); +QStringList parseArgs(const QByteArray &data, int maxArgs, int *pos = nullptr); +QList genSequence(int min, int max, int len); +QChar genLetter(); +QChar genNum(); +QChar genSpecialChar(); + +class RWSharedObjs : public QObject +{ + Q_OBJECT + +public: + + QHash *commands; + QHash *cmdNames; + QList *chList; + QList *p2pAccepted; + QList *p2pPending; + QList *moreInputCmds; + QList *activeLoopCmds; + QList *pausedCmds; + QString *sessionAddr; + QString *userName; + QString *groupName; + QString *displayName; + QString *appName; + ushort *clientMajor; + ushort *clientMinor; + ushort *clientPatch; + QByteArray *chIds; + QByteArray *wrAbleChIds; + QByteArray *sessionId; + QByteArray *userId; + bool *activeUpdate; + bool *chOwnerOverride; + uint *hostRank; + + explicit RWSharedObjs(QObject *parent = nullptr); +}; + +//--------------------------- + +class SessionCarrier : public QObject +{ + Q_OBJECT + +public: + + Session *sessionObj; + + explicit SessionCarrier(Session *session); +}; + +//-------------------------- + +class InternCommand : public ExternCommand +{ + Q_OBJECT + +protected: + + RWSharedObjs *rwSharedObjs; + + bool loopEnabled(); + bool moreInputEnabled(); + QString parseMd(int offset); + +public: + + void setWritableDataShare(RWSharedObjs *sharedObjs); + QString libText(); + QString shortText(); + QString ioText(); + QString longText(); + + explicit InternCommand(QObject *parent = nullptr); + +signals: + + void authOk(); + void termAllCommands(); + void castPeerInfo(); + void reloadCommands(); + void loadMod(const QString &path); + void unloadMod(const QString &path); + void termCommandId(quint16 cmdId); + void backendDataOut(quint16 cmdId, const QByteArray &data, uchar typeId); +}; + +//---------------------------- + +class CommandOutput : public QObject +{ + Q_OBJECT + +private: + + quint16 cmdId; + ExternCommand *cmdObj; + +public: + + explicit CommandOutput(ExternCommand *parent); + + void setCmdId(quint16 id); + +public slots: + + void dataFromCmdObj(const QByteArray &data, uchar typeId); + void openChIdFromCmdObj(quint64 id, uchar subId); + void closeChIdFromCmdObj(quint64 id, uchar subId); + void openChNameFromCmdObj(const QString &ch, const QString &sub); + void closeChNameFromCmdObj(const QString &ch, const QString &sub); + void enableLoopFromCmdObj(bool state); + void enableMoreInputFromCmdObj(bool state); + void finished(); + +signals: + + void dataOut(quint16 cmdId, const QByteArray &data, uchar typeId); + void openChById(quint16 cmdId, quint64 id, uchar subId); + void closeChById(quint16 cmdId, quint64 id, uchar subId); + void openChByName(quint16 cmdId, const QString &ch, const QString &sub); + void closeChByName(quint16 cmdId, const QString &ch, const QString &sub); + void cmdFinished(quint16 cmdId); + void enableLoop(quint16 cmdId, bool state); + void enableMoreInput(quint16 cmdId, bool state); +}; + +//---------------------------- + +class ShellIPC : public QLocalSocket +{ + Q_OBJECT + +private: + + QStringList arguments; + QString pipeName; + +private slots: + + void dataIn(); + void hostConnected(); + +public: + + bool connectToHost(); + + explicit ShellIPC(const QStringList &args, QObject *parent = nullptr); + +signals: + + void closeInstance(); +}; + +#endif // COMMON_H diff --git a/src/db.cpp b/src/db.cpp new file mode 100644 index 0000000..35516cb --- /dev/null +++ b/src/db.cpp @@ -0,0 +1,735 @@ +#include "db.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 +// . + +void cleanupDbConnection() +{ + QSqlDatabase::removeDatabase(Query::getConnectionName()); +} + +QString columnType(const QString &column) +{ + QString ret; + + if ((column == COLUMN_IPADDR) || (column == COLUMN_LOGENTRY) || (column == COLUMN_USERNAME) || + (column == COLUMN_GRNAME) || (column == COLUMN_EMAIL) || (column == COLUMN_INITGROUP) || + (column == COLUMN_COMMAND) || (column == COLUMN_CLIENT_VER) || (column == COLUMN_COMMON_NAME) || + (column == COLUMN_DISPLAY_NAME) || (column == COLUMN_CHANNEL_NAME) || (column == COLUMN_SUB_CH_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_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_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_NAME) || (column == COLUMN_MOD_MAIN)) + { + ret = "TEXT"; + } + 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_CMD_ID_OFFS)) + { + ret = "INTEGER"; + } + else if (column == COLUMN_TIME) + { + ret = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL"; + } + else if ((column == COLUMN_HASH) || (column == COLUMN_SALT) || (column == COLUMN_CERT) || + (column == COLUMN_PRIV_KEY) || (column == COLUMN_USER_ID) || (column == COLUMN_SESSION_ID)) + { + ret = "BLOB"; + } + + return ret; +} + +QByteArray genUniqueHash() +{ + QCryptographicHash hasher(QCryptographicHash::Keccak_256); + + hasher.addData(QDateTime::currentDateTime().toString("yyyyMMddHHmmsszzz").toUtf8()); + hasher.addData(QByteArray::number(QRandomGenerator::global()->generate())); + + return hasher.result(); +} + +QString sqlDataPath() +{ + QString ret = qEnvironmentVariable(ENV_DB_PATH, DEFAULT_DB_PATH); + + ret = expandEnvVariables(ret); + + QFileInfo info(ret); + QDir dir(info.path()); + + if (!dir.exists()) dir.mkpath(info.path()); + + return ret; +} + +QString initGroup() +{ + Query db; + + db.setType(Query::PULL, TABLE_SERV_SETTINGS); + db.addColumn(COLUMN_INITGROUP); + db.exec(); + + return db.getData(COLUMN_INITGROUP).toString(); +} + +QByteArray getSalt(const QString &userName, const QString &table) +{ + Query db; + + db.setType(Query::PULL, table); + db.addColumn(COLUMN_SALT); + db.addColumn(COLUMN_USER_ID); + db.addCondition(COLUMN_USERNAME, userName); + db.exec(); + + return db.getData(COLUMN_SALT).toByteArray() + db.getData(COLUMN_USER_ID).toByteArray(); +} + +bool createUser(const QString &userName, const QString &email, const QString &dispName, const QString &password) +{ + bool ret = false; + + Query db; + + db.setType(Query::PUSH, TABLE_USERS); + db.addColumn(COLUMN_USERNAME, userName); + db.addColumn(COLUMN_EMAIL, email); + db.addColumn(COLUMN_GRNAME, initGroup()); + db.addColumn(COLUMN_DISPLAY_NAME, dispName); + db.addColumn(COLUMN_EMAIL_VERIFIED, false); + db.addColumn(COLUMN_NEED_PASS, false); + db.addColumn(COLUMN_NEED_NAME, false); + db.addColumn(COLUMN_USER_ID, genUniqueHash()); + db.addRandBlob(COLUMN_SALT, 128); + + if (db.exec()) + { + ret = updatePassword(userName, password, TABLE_USERS); + } + + return ret; +} + +bool createTempPw(const QString &userName, const QString &email, const QString &password) +{ + bool ret = false; + + Query db; + + db.setType(Query::PUSH, TABLE_PW_RECOVERY); + db.addColumn(COLUMN_USERNAME, userName); + db.addColumn(COLUMN_EMAIL, email); + db.addRandBlob(COLUMN_SALT, 128); + + if (db.exec()) + { + ret = updatePassword(userName, password, TABLE_PW_RECOVERY); + } + + return ret; +} + +bool updatePassword(const QString &userName, const QString &password, const QString &table, bool requireNewPass) +{ + bool ret = false; + QByteArray salt = getSalt(userName, table); + + if (!salt.isEmpty()) + { + Query db; + + QCryptographicHash hasher(QCryptographicHash::Keccak_512); + + hasher.addData(QTextCodec::codecForName("UTF-16LE")->fromUnicode(password) + salt); + + db.setType(Query::UPDATE, table); + db.addColumn(COLUMN_HASH, hasher.result()); + db.addColumn(COLUMN_NEED_PASS, requireNewPass); + db.addCondition(COLUMN_USERNAME, userName); + + ret = db.exec(); + } + + return ret; +} + +bool auth(const QString &userName, const QString &password, const QString &table) +{ + bool ret = false; + QByteArray salt = getSalt(userName, table); + + if (!salt.isEmpty()) + { + Query db; + + QCryptographicHash hasher(QCryptographicHash::Keccak_512); + + hasher.addData(QTextCodec::codecForName("UTF-16LE")->fromUnicode(password) + salt); + + db.setType(Query::PULL, table); + db.addColumn(COLUMN_HASH); + db.addCondition(COLUMN_USERNAME, userName); + db.exec(); + + if (db.rows()) + { + ret = (hasher.result() == db.getData(COLUMN_HASH).toByteArray()); + } + } + + return ret; +} + +Query::Query(QObject *parent) : QObject(parent) +{ + // this class is an SQL database interface that will be used to store + // all persistent data for this application. it works by building + // a query string (qStr + wStr + limit) to be executed by the + // QSqlQuery object in the exec() function. + + // QT's QSqlQuery in a multi-threaded app will only work if the + // thread that originally created the database connection is + // using it so the next set of logic will actually associate the + // the current thread's object name to a database connection name. + + // any QThread used throughout this application will create it's + // own unique object name to make this work. + + createRan = false; + restraintAdded = false; + queryOk = true; + rowsAffected = 0; + type = PULL; + + if (!QSqlDatabase::contains(getConnectionName())) + { + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", getConnectionName()); + + db.setConnectOptions("ISC_DPB_LC_CTYPE=UTF16LE"); + db.setDatabaseName(sqlDataPath()); + + if (db.open()) + { + enableForeignKeys(true); + setTextEncoding("UTF16LE"); + } + else + { + queryOk = false; + lastErr = db.lastError().databaseText().trimmed(); + } + } +} + +QString Query::errDetail() +{ + QString ret; + + QTextStream txtOut(&ret); + + txtOut << " driver error: " << lastErr << endl; + txtOut << " query: " << qStr << endl; + txtOut << " db path: " << sqlDataPath() << endl; + + QFileInfo info = QFileInfo(QFileInfo(sqlDataPath()).path()); + + if (!info.isReadable()) + { + txtOut << " readable: database path doesn't have read permissions." << endl; + } + + if (!info.isWritable()) + { + txtOut << " writable: database path doesn't have write permissions." << endl; + } + + return ret; +} + +bool Query::inErrorstate() +{ + return !queryOk; +} + +QString Query::getConnectionName() +{ + return QThread::currentThread()->objectName(); +} + +QSqlDatabase Query::getDatabase() +{ + return QSqlDatabase::database(getConnectionName()); +} + +void Query::enableForeignKeys(bool state) +{ + QString str; + + if (state) str = "PRAGMA foreign_keys = ON;"; + else str = "PRAGMA foreign_keys = OFF;"; + + QSqlQuery(str, getDatabase()); +} + +void Query::setTextEncoding(const QString &encoding) +{ + QSqlQuery("PRAGMA encoding = " + encoding + ";", getDatabase()); +} + +QStringList Query::tables() +{ + return getDatabase().tables(); +} + +QStringList Query::columnsInTable(const QString &tbl) +{ + QStringList ret; + + QSqlQuery query("PRAGMA table_info(" + tbl + ");", getDatabase()); + + while (query.next()) + { + ret.append(query.value(1).toString()); + } + + return ret; +} + +void Query::setType(QueryType qType, const QString &tbl) +{ + createRan = false; + restraintAdded = false; + rowsAffected = 0; + table = tbl; + type = qType; + + qStr.clear(); + wStr.clear(); + limit.clear(); + columnList.clear(); + bindValues.clear(); + lastErr.clear(); + directBind.clear(); + whereBinds.clear(); + columnsAsPassed.clear(); + data.clear(); + + QTextStream txt(&qStr); + + switch(type) + { + case PUSH: + { + txt << "INSERT INTO " << tbl << " (%columns%) VALUES (%binds%)"; + + break; + } + case PULL: + { + txt << "SELECT %columns% FROM " << tbl; + + break; + } + case UPDATE: + { + txt << "UPDATE " << tbl << " SET %columns%"; + + break; + } + case DEL: + { + txt << "DELETE FROM " << tbl; + + break; + } + case CREATE_TABLE: + { + txt << "CREATE TABLE " << tbl << " (%columns%)"; + + break; + } + case ALTER_TABLE: + { + // alter table for this class is limited to only adding a column. + // renaming tables is simply not needed at this time. + + txt << "ALTER TABLE " << tbl << " ADD %columns%"; + + break; + } + } +} + +void Query::setQueryLimit(uint value, uint offset) +{ + if (type == PULL) + { + limit = " LIMIT " + QString::number(value) + " OFFSET " + QString::number(offset); + } +} + +void Query::increment(const QString &column, double value) +{ + changeValue(column, value, "+"); +} + +void Query::decrement(const QString &column, double value) +{ + changeValue(column, value, "-"); +} + +void Query::changeValue(const QString &column, double value, const QString &sign) +{ + if (type == UPDATE) + { + columnList.append(column + " = " + column + " " + sign + " " + QString::number(value)); + } + + columnsAsPassed.append(column); +} + +void Query::addColumn(const QString &column) +{ + if (((type == PULL) || (type == CREATE_TABLE)) && !columnList.contains(column)) + { + if (type == CREATE_TABLE) columnList.append(column + " " + columnType(column)); + else columnList.append(column); + } + else if (type == ALTER_TABLE) + { + if (!columnList.isEmpty()) columnList.clear(); + + columnList.append(column + " " + columnType(column) + " DEFAULT NULL"); + } + + columnsAsPassed.append(column); +} + +void Query::addColumn(const QString &column, const QVariant &dataIn) +{ + if ((type == PUSH) || (type == UPDATE)) + { + bindValues.append(dataIn); + + if (type == UPDATE) + { + columnList.append(column + " = :" + column); + } + else + { + columnList.append(column); + } + } + + columnsAsPassed.append(column); +} + +void Query::addRandBlob(const QString &column, int len) +{ + if ((type == PUSH) || (type == UPDATE)) + { + QString rand = "randomblob(" + QString::number(len) + ")"; + + directBind.append(columnList.size()); + bindValues.append(rand); + + if (type == UPDATE) + { + columnList.append(column + " = " + rand); + } + else + { + columnList.append(column); + } + } + + columnsAsPassed.append(column); +} + +void Query::addCondition(const QString &column, const QVariant &data, Condition cond) +{ + if ((type == PULL) || (type == UPDATE) || (type == DEL)) + { + QTextStream txt(&wStr); + + if (wStr.isEmpty()) txt << " WHERE "; + else txt << " AND "; + + if (cond == NOT_EQUAL) + { + txt << column << " != :where" << whereBinds.size(); + + whereBinds.append(data); + } + else if (cond == EQUAL) + { + txt << column << " = :where" << whereBinds.size(); + + whereBinds.append(data); + } + else + { + QString escape = "\\"; + QString escapedData = data.toString(); + + escapedData.replace("%", escape + "%"); + escapedData.replace("_", escape + "_"); + escapedData.replace("'", escape + "'"); + + if (cond == LIKE_STARTS_WITH) + { + escapedData.append("%"); + } + else if (cond == LIKE_ENDS_WITH) + { + escapedData.insert(0, "%"); + } + else + { + escapedData.append("%"); + escapedData.insert(0, "%"); + } + + txt << "LIKE('" << escapedData << "', " << column << ", '" << escape << "') > 0"; + } + } +} + +void Query::addComma(QString &str, bool *toggle) +{ + if (!*toggle) + { + str.append(","); + + *toggle = true; + } +} + +void Query::addUnique(const QString &column) +{ + if ((columnsAsPassed.contains(column)) && (type == CREATE_TABLE)) + { + columnList.append("UNIQUE (" + column + ")"); + } +} + +void Query::setPrimary(const QString &column) +{ + if ((columnsAsPassed.contains(column)) && (type == CREATE_TABLE)) + { + columnList.append("PRIMARY KEY (" + column + ")"); + } +} + +void Query::setPrimaryAsc(const QString &column) +{ + if ((columnsAsPassed.contains(column)) && (type == CREATE_TABLE)) + { + columnList.append("PRIMARY KEY (" + column + " ASC)"); + } +} + +void Query::setPrimaryDesc(const QString &column) +{ + if ((columnsAsPassed.contains(column)) && (type == CREATE_TABLE)) + { + columnList.append("PRIMARY KEY (" + column + " DESC)"); + } +} + +void Query::addForeign(const QString &column, const QString &refTable, const QString &refColum, FKAction onDel, FKAction onUpdate) +{ + if ((columnsAsPassed.contains(column)) && ((type == CREATE_TABLE) || (type == ALTER_TABLE))) + { + QString str = "FOREIGN KEY (" + column + ") REFERENCES " + refTable + " (" + refColum + ")"; + + switch (onDel) + { + case NO_ACTION: str.append(" ON DELETE NO ACTION"); break; + case RESTRICT: str.append(" ON DELETE RESTRICT"); break; + case SET_NULL: str.append(" ON DELETE SET NULL"); break; + case SET_DEFAULT: str.append(" ON DELETE SET DEFAULT"); break; + case CASCADE: str.append(" ON DELETE CASCADE"); break; + } + + switch (onUpdate) + { + case NO_ACTION: str.append(" ON UPDATE NO ACTION"); break; + case RESTRICT: str.append(" ON UPDATE RESTRICT"); break; + case SET_NULL: str.append(" ON UPDATE SET NULL"); break; + case SET_DEFAULT: str.append(" ON UPDATE SET DEFAULT"); break; + case CASCADE: str.append(" ON UPDATE CASCADE"); break; + } + + columnList.append(str); + } +} + +void Query::preExec() +{ + QString columnsStr = QStringList(columnList).join(", "); + + qStr.replace("%columns%", columnsStr); + + if (type == PUSH) + { + QString bindsStr; + + for (int i = 0; i < columnList.size(); ++i) + { + if (directBind.contains(i)) + { + bindsStr.append(bindValues[i].toString() + ", "); + } + else + { + bindsStr.append(":" + columnsAsPassed[i] + ", "); + } + } + + bindsStr.chop(2); + qStr.replace("%binds%", bindsStr); + } +} + +bool Query::createRedirect() +{ + bool ret = false; + + if ((type == CREATE_TABLE) && (tables().contains(table))) + { + ret = true; + + QStringList existingColumns = columnsInTable(table); + QStringList newColumns = columnsAsPassed; + + for (int i = 0; (i < newColumns.size()) && queryOk; ++i) + { + if (!existingColumns.contains(newColumns[i])) + { + setType(ALTER_TABLE, table); + addColumn(newColumns[i]); + exec(); + } + } + } + + return ret; +} + +bool Query::createExecuted() +{ + return createRan; +} + +bool Query::exec() +{ + if (!createRedirect()) + { + preExec(); + + QSqlQuery query(getDatabase()); + + query.prepare(qStr + wStr + limit + ";"); + + for (int i = 0; i < bindValues.size(); ++i) + { + if (!directBind.contains(i)) + { + query.bindValue(":" + columnsAsPassed[i], bindValues[i]); + } + } + + for (int i = 0; i < whereBinds.size(); ++i) + { + query.bindValue(":where" + QString::number(i), whereBinds[i]); + } + + queryOk = query.exec(); + lastErr = query.lastError().driverText().trimmed(); + rowsAffected = query.numRowsAffected(); + + if (queryOk && query.isSelect()) + { + while (query.next()) + { + QList row; + + for (int i = 0; i < columnsAsPassed.size(); ++i) + { + row.append(query.value(i)); + } + + data.append(row); + } + } + else if (queryOk && (type == CREATE_TABLE)) + { + createRan = true; + } + } + + return queryOk; +} + +QVariant Query::getData(const QString &column, int row) +{ + QVariant ret; + + if ((row < data.size()) && (row >= 0)) + { + int index = columnsAsPassed.indexOf(column); + + if (index != -1) + { + ret = data[row][index]; + } + } + + return ret; +} + +int Query::rows() +{ + int ret = 0; + + if (type == PULL) ret = data.size(); + else ret = rowsAffected; + + return ret; +} + +int Query::columns() +{ + return columnList.size(); +} diff --git a/src/db.h b/src/db.h new file mode 100644 index 0000000..9850eeb --- /dev/null +++ b/src/db.h @@ -0,0 +1,279 @@ +#ifndef DB_H +#define DB_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 +// . + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shell.h" + +#define APP_NAME "MRCI" +#define APP_VER "1.0.0" +#define APP_TARGET "mrci" + +#ifdef Q_OS_WIN + +#define DEFAULT_ZIPBIN "%ProgramW6432%\\7-Zip\\7z.exe" +#define DEFAULT_MAILBIN "%COMSPEC%" +#define DEFAULT_MAIL_SEND "echo %message_body% | mutt -s %subject% %target_email%" +#define DEFAULT_DB_PATH "%LOCALAPPDATA%\\%EXENAME%\\data.db" +#define DEFAULT_MOD_PATH "%LOCALAPPDATA%\\%EXENAME%\\modules" +#define DEFAULT_PIPE_PATH "%LOCALAPPDATA%\\%EXENAME%\\pipes" + +#else + +#define DEFAULT_ZIPBIN "/usr/bin/7z" +#define DEFAULT_MAILBIN "/bin/sh" +#define DEFAULT_MAIL_SEND "-c \"echo %message_body% | mutt -s %subject% %target_email%\"" +#define DEFAULT_DB_PATH "$HOME/.$EXENAME/data.db" +#define DEFAULT_MOD_PATH "$HOME/.$EXENAME/modules" +#define DEFAULT_PIPE_PATH "$HOME/.$EXENAME/pipes" + +#endif + +#define ENV_DB_PATH "MRCI_DB_PATH" +#define ENV_MOD_PATH "MRCI_MOD_PATH" +#define ENV_PIPE_PATH "MRCI_PIPE_PATH" +#define ENV_EXENAME "EXENAME" +#define ROOT_USER "root" +#define DEFAULT_UGROUP "users" +#define DEFAULT_PASSWRD "R00tPa$$w0rd" +#define OUTPUT_DIR_SUB "%output_dir%" +#define INPUT_DIR_SUB "%input_dir%" +#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 INTERN_MOD_NAME ":internal_mod" +#define DEFAULT_ZIPEXTRACT "x -y %input_dir% -o%output_dir%" +#define DEFAULT_ZIPCOMPRESS "a -y %output_dir% %input_dir%" +#define DEFAULT_CONFIRM_SUBJECT "Email Verification" +#define DEFAULT_TEMP_PW_SUBJECT "Password Reset" +#define DEFAULT_LISTEN_ADDRESS "127.0.0.1" +#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 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_IPBANS "ban_list" +#define TABLE_USERS "users" +#define TABLE_GROUPS "groups" +#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_CERT_DATA "ssl_certs" +#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_GRNAME "group_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_obj_name" +#define COLUMN_MOD_NAME "mod_name" +#define COLUMN_MOD_MAIN "mod_main_lib" +#define COLUMN_CMD_ID_OFFS "command_id_offset" +#define COLUMN_INITGROUP "initial_group" +#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_CERT "ssl_cert" +#define COLUMN_PRIV_KEY "private_key" +#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_COMMON_NAME "common_name" +#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" + +QString sqlDataPath(); +QString columnType(const QString &column); +QString columnDefault(const QString &column, const QString &table); +QString initGroup(); +QByteArray getSalt(const QString &userName, const QString &table); +QByteArray genUniqueHash(); +bool createUser(const QString &userName, const QString &email, const QString &dispName, const QString &password); +bool createTempPw(const QString &userName, const QString &email, const QString &password); +bool updatePassword(const QString &userName, const QString &password, const QString &table, bool requireNewPass = false); +bool auth(const QString &userName, const QString &password, const QString &table); +void cleanupDbConnection(); + +class Query : public QObject +{ + Q_OBJECT + +public: + + enum QueryType + { + UPDATE, + PUSH, + PULL, + DEL, + CREATE_TABLE, + ALTER_TABLE + }; + + enum Condition + { + EQUAL, + NOT_EQUAL, + LIKE, + LIKE_STARTS_WITH, + LIKE_ENDS_WITH + }; + + enum FKAction + { + NO_ACTION, + RESTRICT, + SET_NULL, + SET_DEFAULT, + CASCADE + }; + + static QString getConnectionName(); + static QSqlDatabase getDatabase(); + + explicit Query(QObject *parent = nullptr); + + void addRandBlob(const QString &column, int len); + void addCondition(const QString &column, const QVariant &data, Condition cond = EQUAL); + void addUnique(const QString &column); + void setPrimary(const QString &column); + void setPrimaryAsc(const QString &column); + void setPrimaryDesc(const QString &column); + void setQueryLimit(uint value, uint offset = 0); + void increment(const QString &column, double value); + void decrement(const QString &column, double value); + void addForeign(const QString &column, const QString &refTable, const QString &refColum, FKAction onDel = RESTRICT, FKAction onUpdate = RESTRICT); + void addColumn(const QString &column); + void addColumn(const QString &column, const QVariant &dataIn); + void setType(QueryType qType, const QString &tbl); + void enableForeignKeys(bool state); + void setTextEncoding(const QString &encoding); + int rows(); + int columns(); + bool exec(); + bool createExecuted(); + bool inErrorstate(); + QStringList tables(); + QStringList columnsInTable(const QString &tbl); + QVariant getData(const QString &column, int row = 0); + QString errDetail(); + +private: + + bool createRan; + bool restraintAdded; + bool queryOk; + int rowsAffected; + QString table; + QString lastErr; + QString limit; + QString qStr; + QString wStr; + QueryType type; + QList directBind; + QStringList columnsAsPassed; + QList columnList; + QList bindValues; + QList whereBinds; + QList > data; + + bool createRedirect(); + void postUpdate(); + void preExec(); + void addComma(QString &str, bool *toggle); + void changeValue(const QString &column, double value, const QString &sign); +}; + +#endif // DB_H diff --git a/src/db_setup.cpp b/src/db_setup.cpp new file mode 100644 index 0000000..6cc9d2a --- /dev/null +++ b/src/db_setup.cpp @@ -0,0 +1,389 @@ +#include "db_setup.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 +// . + +bool setupDb(QString *errMsg) +{ + bool ret = true; + + errMsg->clear(); + + Query query(QThread::currentThread()); + Query defaults(QThread::currentThread()); + + if (query.inErrorstate()) + { + ret = false; + + errMsg->append("database open failure: \n"); + errMsg->append(query.errDetail()); + } + + if (ret) + { + query.setType(Query::CREATE_TABLE, TABLE_IPHIST); + query.addColumn(COLUMN_IPADDR); + query.addColumn(COLUMN_TIME); + query.addColumn(COLUMN_CLIENT_VER); + query.addColumn(COLUMN_SESSION_ID); + query.addColumn(COLUMN_LOGENTRY); + + ret = query.exec(); + } + + if (ret) + { + query.setType(Query::CREATE_TABLE, TABLE_MODULES); + query.addColumn(COLUMN_MOD_NAME); + query.addColumn(COLUMN_MOD_MAIN); + query.addColumn(COLUMN_LOCKED); + query.addColumn(COLUMN_CMD_ID_OFFS); + query.setPrimary(COLUMN_MOD_NAME); + query.addUnique(COLUMN_MOD_NAME); + query.addUnique(COLUMN_MOD_MAIN); + + ret = query.exec(); + } + + if (ret) + { + query.setType(Query::CREATE_TABLE, TABLE_IPBANS); + query.addColumn(COLUMN_IPADDR); + query.addColumn(COLUMN_TIME); + query.setPrimary(COLUMN_IPADDR); + query.addUnique(COLUMN_IPADDR); + + ret = query.exec(); + } + + if (ret) + { + query.setType(Query::CREATE_TABLE, TABLE_CMD_RANKS); + query.addColumn(COLUMN_COMMAND); + query.addColumn(COLUMN_HOST_RANK); + query.addColumn(COLUMN_MOD_NAME); + query.setPrimary(COLUMN_COMMAND); + query.addUnique(COLUMN_COMMAND); + + ret = query.exec(); + } + + if (ret) + { + query.setType(Query::CREATE_TABLE, TABLE_CERT_DATA); + query.addColumn(COLUMN_COMMON_NAME); + query.addColumn(COLUMN_CERT); + query.addColumn(COLUMN_PRIV_KEY); + query.setPrimary(COLUMN_COMMON_NAME); + query.addUnique(COLUMN_COMMON_NAME); + + ret = query.exec(); + } + + if (ret) + { + query.setType(Query::CREATE_TABLE, TABLE_CHANNELS); + query.addColumn(COLUMN_CHANNEL_ID); + query.addColumn(COLUMN_CHANNEL_NAME); + query.setPrimaryAsc(COLUMN_CHANNEL_ID); + query.addUnique(COLUMN_CHANNEL_NAME); + + 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_GROUPS); + query.addColumn(COLUMN_GRNAME); + query.addColumn(COLUMN_HOST_RANK); + query.setPrimary(COLUMN_GRNAME); + query.addUnique(COLUMN_GRNAME); + + ret = query.exec(); + + if (query.createExecuted()) + { + query.setType(Query::PUSH, TABLE_GROUPS); + query.addColumn(COLUMN_GRNAME, ROOT_USER); + query.addColumn(COLUMN_HOST_RANK, 1); + + ret = query.exec(); + + if (ret) + { + query.setType(Query::PUSH, TABLE_GROUPS); + query.addColumn(COLUMN_GRNAME, DEFAULT_UGROUP); + query.addColumn(COLUMN_HOST_RANK, 2); + + ret = query.exec(); + } + } + } + + if (ret) + { + query.setType(Query::CREATE_TABLE, TABLE_USERS); + query.addColumn(COLUMN_USERNAME); + query.addColumn(COLUMN_USER_ID); + query.addColumn(COLUMN_GRNAME); + query.addColumn(COLUMN_EMAIL); + query.addColumn(COLUMN_HASH); + query.addColumn(COLUMN_SALT); + query.addColumn(COLUMN_TIME); + query.addColumn(COLUMN_NEED_PASS); + query.addColumn(COLUMN_NEED_NAME); + query.addColumn(COLUMN_LOCKED); + query.addColumn(COLUMN_EMAIL_VERIFIED); + query.addColumn(COLUMN_DISPLAY_NAME); + query.setPrimary(COLUMN_USERNAME); + query.addUnique(COLUMN_USERNAME); + query.addUnique(COLUMN_EMAIL); + query.addUnique(COLUMN_USER_ID); + query.addForeign(COLUMN_GRNAME, TABLE_GROUPS, COLUMN_GRNAME, Query::RESTRICT, Query::CASCADE); + + ret = query.exec(); + + if (query.createExecuted()) + { + query.setType(Query::PUSH, TABLE_USERS); + query.addColumn(COLUMN_USERNAME, ROOT_USER); + query.addColumn(COLUMN_GRNAME, ROOT_USER); + query.addColumn(COLUMN_NEED_PASS, true); + query.addColumn(COLUMN_NEED_NAME, false); + query.addColumn(COLUMN_LOCKED, false); + query.addColumn(COLUMN_EMAIL_VERIFIED, false); + query.addColumn(COLUMN_USER_ID, genUniqueHash()); + query.addRandBlob(COLUMN_SALT, 128); + + ret = query.exec(); + + if (ret) + { + ret = updatePassword(ROOT_USER, DEFAULT_PASSWRD, TABLE_USERS, true); + } + } + } + + if (ret) + { + query.setType(Query::CREATE_TABLE, TABLE_AUTH_LOG); + query.addColumn(COLUMN_IPADDR); + query.addColumn(COLUMN_TIME); + query.addColumn(COLUMN_USERNAME); + query.addColumn(COLUMN_AUTH_ATTEMPT); + query.addColumn(COLUMN_RECOVER_ATTEMPT); + query.addColumn(COLUMN_ACCEPTED); + query.addColumn(COLUMN_COUNT); + query.addForeign(COLUMN_USERNAME, TABLE_USERS, COLUMN_USERNAME, Query::CASCADE, Query::CASCADE); + + ret = query.exec(); + } + + if (ret) + { + query.setType(Query::CREATE_TABLE, TABLE_PW_RECOVERY); + query.addColumn(COLUMN_TIME); + query.addColumn(COLUMN_USERNAME); + query.addColumn(COLUMN_EMAIL); + query.addColumn(COLUMN_HASH); + query.addColumn(COLUMN_SALT); + query.addColumn(COLUMN_USER_ID); + query.addUnique(COLUMN_USERNAME); + query.addUnique(COLUMN_EMAIL); + query.addForeign(COLUMN_USER_ID, TABLE_USERS, COLUMN_USER_ID, Query::CASCADE, Query::RESTRICT); + query.addForeign(COLUMN_USERNAME, TABLE_USERS, COLUMN_USERNAME, Query::CASCADE, Query::CASCADE); + query.addForeign(COLUMN_EMAIL, TABLE_USERS, COLUMN_EMAIL, Query::CASCADE, Query::CASCADE); + + ret = query.exec(); + } + + if (ret) + { + query.setType(Query::CREATE_TABLE, TABLE_CH_MEMBERS); + query.addColumn(COLUMN_CHANNEL_ID); + query.addColumn(COLUMN_CHANNEL_NAME); + query.addColumn(COLUMN_USERNAME); + query.addColumn(COLUMN_PENDING_INVITE); + query.addColumn(COLUMN_ACCESS_LEVEL); + query.addForeign(COLUMN_CHANNEL_ID, TABLE_CHANNELS, COLUMN_CHANNEL_ID, Query::CASCADE, Query::CASCADE); + query.addForeign(COLUMN_CHANNEL_NAME, TABLE_CHANNELS, COLUMN_CHANNEL_NAME, Query::CASCADE, Query::CASCADE); + query.addForeign(COLUMN_USERNAME, TABLE_USERS, COLUMN_USERNAME, Query::CASCADE, Query::CASCADE); + + ret = query.exec(); + } + + if (ret) + { + query.setType(Query::CREATE_TABLE, TABLE_SUB_CHANNELS); + query.addColumn(COLUMN_CHANNEL_ID); + query.addColumn(COLUMN_SUB_CH_NAME); + query.addColumn(COLUMN_SUB_CH_ID); + query.addColumn(COLUMN_CHANNEL_NAME); + query.addColumn(COLUMN_LOWEST_LEVEL); + query.addColumn(COLUMN_ACTIVE_UPDATE); + query.addForeign(COLUMN_CHANNEL_ID, TABLE_CHANNELS, COLUMN_CHANNEL_ID, Query::CASCADE, Query::CASCADE); + query.addForeign(COLUMN_CHANNEL_NAME, TABLE_CHANNELS, COLUMN_CHANNEL_NAME, Query::CASCADE, Query::CASCADE); + + ret = query.exec(); + } + + if (ret) + { + query.setType(Query::CREATE_TABLE, TABLE_RDONLY_CAST); + query.addColumn(COLUMN_CHANNEL_ID); + query.addColumn(COLUMN_CHANNEL_NAME); + 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); + query.addForeign(COLUMN_CHANNEL_NAME, TABLE_CHANNELS, COLUMN_CHANNEL_NAME, 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_INITGROUP); + 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.addForeign(COLUMN_INITGROUP, TABLE_GROUPS, COLUMN_GRNAME, Query::RESTRICT, Query::CASCADE); + + ret = query.exec(); + + 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_ZIPBIN, DEFAULT_ZIPBIN); + query.addColumn(COLUMN_ZIPCOMPRESS, DEFAULT_ZIPCOMPRESS); + query.addColumn(COLUMN_ZIPEXTRACT, DEFAULT_ZIPEXTRACT); + query.addColumn(COLUMN_INITGROUP, DEFAULT_UGROUP); + 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); + + 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_INITGROUP); + 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); + + 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_ZIPBIN).isNull()) defaults.addColumn(COLUMN_ZIPBIN, DEFAULT_ZIPBIN); + if (query.getData(COLUMN_ZIPCOMPRESS).isNull()) defaults.addColumn(COLUMN_ZIPCOMPRESS, DEFAULT_ZIPCOMPRESS); + if (query.getData(COLUMN_ZIPEXTRACT).isNull()) defaults.addColumn(COLUMN_ZIPEXTRACT, DEFAULT_ZIPEXTRACT); + if (query.getData(COLUMN_INITGROUP).isNull()) defaults.addColumn(COLUMN_INITGROUP, DEFAULT_UGROUP); + 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 (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; +} diff --git a/src/db_setup.h b/src/db_setup.h new file mode 100644 index 0000000..5b07351 --- /dev/null +++ b/src/db_setup.h @@ -0,0 +1,24 @@ +#ifndef DB_SETUP_H +#define DB_SETUP_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 +// . + +#include "db.h" + +bool setupDb(QString *errMsg = nullptr); + +#endif // DB_SETUP_H diff --git a/src/int_loader.cpp b/src/int_loader.cpp new file mode 100644 index 0000000..bec5cc4 --- /dev/null +++ b/src/int_loader.cpp @@ -0,0 +1,345 @@ +#include "int_loader.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 +// . + +InternalCommandLoader::InternalCommandLoader(RWSharedObjs *sharedData, QObject *parent) : CommandLoader(parent) +{ + rwShared = sharedData; + pubReg = false; + emailConfirmation = false; + passwrdResets = false; + + // This is used as an automated way to update the Internal_Commands doc for the source code documentation + // keep it commented for release code. + + // makeDocHeader("/path/to/source/docs/Internal_Commands.md"); + + objNames << CloseHost::cmdName(); + objNames << RestartHost::cmdName(); + objNames << Auth::cmdName(); + objNames << ListBans::cmdName(); + objNames << BanIP::cmdName(); + objNames << UnBanIP::cmdName(); + objNames << Cast::cmdName(); + objNames << OpenSubChannel::cmdName(); + objNames << CloseSubChannel::cmdName(); + objNames << LsOpenChannels::cmdName(); + objNames << ListGroups::cmdName(); + objNames << CreateGroup::cmdName(); + objNames << TransGroup::cmdName(); + objNames << ListCommands::cmdName(); + objNames << HostInfo::cmdName(); + objNames << IPHist::cmdName(); + objNames << ListMods::cmdName(); + objNames << DelMod::cmdName(); + objNames << UploadMod::cmdName(); + objNames << ListUsers::cmdName(); + objNames << CreateUser::cmdName(); + objNames << RecoverAcct::cmdName(); + objNames << ResetPwRequest::cmdName(); + objNames << VerifyEmail::cmdName(); + objNames << Resume::cmdName(); + objNames << Pause::cmdName(); + objNames << Term::cmdName(); + objNames << AuthLog::cmdName(); + objNames << LsCmdRanks::cmdName(); + objNames << RemoveCmdRank::cmdName(); + objNames << AssignCmdRank::cmdName(); + objNames << ServSettings::cmdName(); + objNames << LockUser::cmdName(); + objNames << NameChangeRequest::cmdName(); + objNames << PasswordChangeRequest::cmdName(); + objNames << ChangeEmail::cmdName(); + objNames << OverWriteEmail::cmdName(); + objNames << ChangeDispName::cmdName(); + objNames << ChangeUsername::cmdName(); + objNames << ChangePassword::cmdName(); + objNames << RemoveUser::cmdName(); + objNames << ChangeGroup::cmdName(); + objNames << IsEmailVerified::cmdName(); + objNames << SetEmailTemplate::cmdName(); + objNames << PreviewEmail::cmdName(); + objNames << MyInfo::cmdName(); + objNames << DownloadFile::cmdName(); + objNames << UploadFile::cmdName(); + objNames << Delete::cmdName(); + objNames << Copy::cmdName(); + objNames << Move::cmdName(); + objNames << ListFiles::cmdName(); + objNames << FileInfo::cmdName(); + objNames << MakePath::cmdName(); + objNames << ChangeDir::cmdName(); + objNames << ListDBG::cmdName(); + objNames << ListCerts::cmdName(); + objNames << CertInfo::cmdName(); + objNames << AddCert::cmdName(); + objNames << RemoveCert::cmdName(); + objNames << ToPeer::cmdName(); + objNames << LsP2P::cmdName(); + objNames << P2POpen::cmdName(); + objNames << P2PClose::cmdName(); + objNames << P2PRequest::cmdName(); + objNames << PingPeers::cmdName(); + objNames << CreateChannel::cmdName(); + objNames << RemoveChannel::cmdName(); + objNames << RenameChannel::cmdName(); + objNames << SetActiveState::cmdName(); + objNames << CreateSubCh::cmdName(); + objNames << RemoveSubCh::cmdName(); + objNames << RenameSubCh::cmdName(); + objNames << ListChannels::cmdName(); + objNames << ListSubCh::cmdName(); + objNames << SearchChannels::cmdName(); + objNames << InviteToCh::cmdName(); + objNames << DeclineChInvite::cmdName(); + objNames << AcceptChInvite::cmdName(); + objNames << RemoveChMember::cmdName(); + objNames << SetMemberLevel::cmdName(); + objNames << SetSubAcessLevel::cmdName(); + objNames << ListMembers::cmdName(); + objNames << AddRDOnlyFlag::cmdName(); + objNames << RemoveRDOnlyFlag::cmdName(); + objNames << ListRDonlyFlags::cmdName(); + objNames << SetGroupRank::cmdName(); + objNames << CmdInfo::cmdName(); + objNames << OwnerOverride::cmdName(); +} + +void InternalCommandLoader::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(); +} + +bool InternalCommandLoader::exists(const QString &cmdName) +{ + return objNames.contains(cmdName, Qt::CaseInsensitive); +} + +QStringList InternalCommandLoader::cmdList() +{ + loadSettings(); + + if (emailConfirmation && !exists(VerifyEmail::cmdName())) + { + objNames.append(VerifyEmail::cmdName()); + } + else if (!emailConfirmation && exists(VerifyEmail::cmdName())) + { + objNames.removeOne(VerifyEmail::cmdName()); + } + + return objNames; +} + +QStringList InternalCommandLoader::pubCmdList() +{ + loadSettings(); + + QStringList ret; + + ret << ListCommands::cmdName() << Auth::cmdName() << MyInfo::cmdName(); + + if (pubReg) + { + ret << CreateUser::cmdName(); + } + + if (passwrdResets) + { + ret << ResetPwRequest::cmdName() << RecoverAcct::cmdName(); + } + + return ret; +} + +QStringList InternalCommandLoader::rankExemptList() +{ + QStringList ret; + + ret << Auth::cmdName(); + ret << MyInfo::cmdName(); + ret << ChangeDispName::cmdName(); + ret << ChangeUsername::cmdName(); + ret << ChangePassword::cmdName(); + ret << ChangeEmail::cmdName(); + ret << ListCommands::cmdName(); + ret << VerifyEmail::cmdName(); + ret << IsEmailVerified::cmdName(); + ret << Resume::cmdName(); + ret << Pause::cmdName(); + ret << Term::cmdName(); + ret << CmdInfo::cmdName(); + + return ret; +} + +InternCommand *InternalCommandLoader::cmdObj(const QString &name) +{ + InternCommand *ret = nullptr; + + if (objNames.contains(name, Qt::CaseInsensitive)) + { + if (noCaseMatch(name, CloseHost::cmdName())) ret = new CloseHost(this); + else if (noCaseMatch(name, RestartHost::cmdName())) ret = new RestartHost(this); + else if (noCaseMatch(name, Auth::cmdName())) ret = new Auth(this); + else if (noCaseMatch(name, ListBans::cmdName())) ret = new ListBans(this); + else if (noCaseMatch(name, BanIP::cmdName())) ret = new BanIP(this); + else if (noCaseMatch(name, UnBanIP::cmdName())) ret = new UnBanIP(this); + else if (noCaseMatch(name, Cast::cmdName())) ret = new Cast(this); + else if (noCaseMatch(name, OpenSubChannel::cmdName())) ret = new OpenSubChannel(this); + else if (noCaseMatch(name, CloseSubChannel::cmdName())) ret = new CloseSubChannel(this); + else if (noCaseMatch(name, LsOpenChannels::cmdName())) ret = new LsOpenChannels(this); + else if (noCaseMatch(name, ListGroups::cmdName())) ret = new ListGroups(this); + else if (noCaseMatch(name, CreateGroup::cmdName())) ret = new CreateGroup(this); + else if (noCaseMatch(name, TransGroup::cmdName())) ret = new TransGroup(this); + else if (noCaseMatch(name, ListCommands::cmdName())) ret = new ListCommands(this); + else if (noCaseMatch(name, HostInfo::cmdName())) ret = new HostInfo(this); + else if (noCaseMatch(name, IPHist::cmdName())) ret = new IPHist(this); + else if (noCaseMatch(name, ListMods::cmdName())) ret = new ListMods(this); + else if (noCaseMatch(name, DelMod::cmdName())) ret = new DelMod(this); + else if (noCaseMatch(name, UploadMod::cmdName())) ret = new UploadMod(this); + else if (noCaseMatch(name, ListUsers::cmdName())) ret = new ListUsers(this); + else if (noCaseMatch(name, CreateUser::cmdName())) ret = new CreateUser(this); + else if (noCaseMatch(name, RecoverAcct::cmdName())) ret = new RecoverAcct(this); + else if (noCaseMatch(name, ResetPwRequest::cmdName())) ret = new ResetPwRequest(this); + else if (noCaseMatch(name, VerifyEmail::cmdName())) ret = new VerifyEmail(this); + else if (noCaseMatch(name, Resume::cmdName())) ret = new Resume(this); + else if (noCaseMatch(name, Pause::cmdName())) ret = new Pause(this); + else if (noCaseMatch(name, Term::cmdName())) ret = new Term(this); + else if (noCaseMatch(name, AuthLog::cmdName())) ret = new AuthLog(this); + else if (noCaseMatch(name, LsCmdRanks::cmdName())) ret = new LsCmdRanks(this); + else if (noCaseMatch(name, RemoveCmdRank::cmdName())) ret = new RemoveCmdRank(this); + else if (noCaseMatch(name, AssignCmdRank::cmdName())) ret = new AssignCmdRank(this); + else if (noCaseMatch(name, ServSettings::cmdName())) ret = new ServSettings(this); + else if (noCaseMatch(name, LockUser::cmdName())) ret = new LockUser(this); + else if (noCaseMatch(name, NameChangeRequest::cmdName())) ret = new NameChangeRequest(this); + else if (noCaseMatch(name, PasswordChangeRequest::cmdName())) ret = new PasswordChangeRequest(this); + else if (noCaseMatch(name, ChangeEmail::cmdName())) ret = new ChangeEmail(this); + else if (noCaseMatch(name, OverWriteEmail::cmdName())) ret = new OverWriteEmail(this); + else if (noCaseMatch(name, ChangeDispName::cmdName())) ret = new ChangeDispName(this); + else if (noCaseMatch(name, ChangeUsername::cmdName())) ret = new ChangeUsername(this); + else if (noCaseMatch(name, ChangePassword::cmdName())) ret = new ChangePassword(this); + else if (noCaseMatch(name, RemoveUser::cmdName())) ret = new RemoveUser(this); + else if (noCaseMatch(name, ChangeGroup::cmdName())) ret = new ChangeGroup(this); + else if (noCaseMatch(name, IsEmailVerified::cmdName())) ret = new IsEmailVerified(this); + else if (noCaseMatch(name, SetEmailTemplate::cmdName())) ret = new SetEmailTemplate(this); + else if (noCaseMatch(name, PreviewEmail::cmdName())) ret = new PreviewEmail(this); + else if (noCaseMatch(name, MyInfo::cmdName())) ret = new MyInfo(this); + else if (noCaseMatch(name, DownloadFile::cmdName())) ret = new DownloadFile(this); + else if (noCaseMatch(name, UploadFile::cmdName())) ret = new UploadFile(this); + else if (noCaseMatch(name, Delete::cmdName())) ret = new Delete(this); + else if (noCaseMatch(name, Copy::cmdName())) ret = new Copy(this); + else if (noCaseMatch(name, Move::cmdName())) ret = new Move(this); + else if (noCaseMatch(name, ListFiles::cmdName())) ret = new ListFiles(this); + else if (noCaseMatch(name, FileInfo::cmdName())) ret = new FileInfo(this); + else if (noCaseMatch(name, MakePath::cmdName())) ret = new MakePath(this); + else if (noCaseMatch(name, ChangeDir::cmdName())) ret = new ChangeDir(this); + else if (noCaseMatch(name, ListDBG::cmdName())) ret = new ListDBG(this); + else if (noCaseMatch(name, ListCerts::cmdName())) ret = new ListCerts(this); + else if (noCaseMatch(name, CertInfo::cmdName())) ret = new CertInfo(this); + else if (noCaseMatch(name, AddCert::cmdName())) ret = new AddCert(this); + else if (noCaseMatch(name, RemoveCert::cmdName())) ret = new RemoveCert(this); + else if (noCaseMatch(name, ToPeer::cmdName())) ret = new ToPeer(this); + else if (noCaseMatch(name, LsP2P::cmdName())) ret = new LsP2P(this); + else if (noCaseMatch(name, P2POpen::cmdName())) ret = new P2POpen(this); + else if (noCaseMatch(name, P2PClose::cmdName())) ret = new P2PClose(this); + else if (noCaseMatch(name, P2PRequest::cmdName())) ret = new P2PRequest(this); + else if (noCaseMatch(name, PingPeers::cmdName())) ret = new PingPeers(this); + else if (noCaseMatch(name, CreateChannel::cmdName())) ret = new CreateChannel(this); + else if (noCaseMatch(name, RemoveChannel::cmdName())) ret = new RemoveChannel(this); + else if (noCaseMatch(name, RenameChannel::cmdName())) ret = new RenameChannel(this); + else if (noCaseMatch(name, SetActiveState::cmdName())) ret = new SetActiveState(this); + else if (noCaseMatch(name, CreateSubCh::cmdName())) ret = new CreateSubCh(this); + else if (noCaseMatch(name, RemoveSubCh::cmdName())) ret = new RemoveSubCh(this); + else if (noCaseMatch(name, RenameSubCh::cmdName())) ret = new RenameSubCh(this); + else if (noCaseMatch(name, ListChannels::cmdName())) ret = new ListChannels(this); + else if (noCaseMatch(name, ListSubCh::cmdName())) ret = new ListSubCh(this); + else if (noCaseMatch(name, SearchChannels::cmdName())) ret = new SearchChannels(this); + else if (noCaseMatch(name, InviteToCh::cmdName())) ret = new InviteToCh(this); + else if (noCaseMatch(name, DeclineChInvite::cmdName())) ret = new DeclineChInvite(this); + else if (noCaseMatch(name, AcceptChInvite::cmdName())) ret = new AcceptChInvite(this); + else if (noCaseMatch(name, RemoveChMember::cmdName())) ret = new RemoveChMember(this); + else if (noCaseMatch(name, SetMemberLevel::cmdName())) ret = new SetMemberLevel(this); + else if (noCaseMatch(name, SetSubAcessLevel::cmdName())) ret = new SetSubAcessLevel(this); + else if (noCaseMatch(name, ListMembers::cmdName())) ret = new ListMembers(this); + else if (noCaseMatch(name, AddRDOnlyFlag::cmdName())) ret = new AddRDOnlyFlag(this); + else if (noCaseMatch(name, RemoveRDOnlyFlag::cmdName())) ret = new RemoveRDOnlyFlag(this); + else if (noCaseMatch(name, ListRDonlyFlags::cmdName())) ret = new ListRDonlyFlags(this); + else if (noCaseMatch(name, SetGroupRank::cmdName())) ret = new SetGroupRank(this); + else if (noCaseMatch(name, CmdInfo::cmdName())) ret = new CmdInfo(this); + else if (noCaseMatch(name, OwnerOverride::cmdName())) ret = new OwnerOverride(this); + + if (ret == nullptr) + { + qDebug() << "InternalCommandLoader Error: the loader claims command name '" << name << "' exists but no command object was actually matched/built."; + } + else + { + ret->setObjectName(name); + ret->setWritableDataShare(rwShared); + + // This is used as an automated way to update the Internal_Commands doc for the source code documentation + // keep it commented for release code. + + // appendToDoc("/path/to/source/docs/Internal_Commands.md", name, ret); + } + } + else + { + qDebug() << "InternalCommandLoader Error: command name '" << name << "' not found."; + } + + return ret; +} + +void InternalCommandLoader::makeDocHeader(const QString &path) +{ + QFile file(path); + + if (file.open(QFile::WriteOnly | QFile::Text)) + { + file.write("### 7.1 Internal Commands ###\n\n"); + file.write("The host is extendable via 3rd party modules but the host itself have it's own internal module that load "); + file.write("commands with direct access to the host database and several internal power functions that external "); + file.write("commands would otherwise not have direct access to. \n\n"); + } + + file.close(); +} + +void InternalCommandLoader::appendToDoc(const QString &path, const QString &cmdName, InternCommand *obj) +{ + QFile file(path); + QFileInfo info(path); + + if (file.open(QFile::Append | QFile::Text)) + { + file.write("* [" + cmdName.toUtf8() + "](intern_commands/" + cmdName.toUtf8() + ".md) - " + obj->shortText().toUtf8() + "\n\n"); + } + + file.close(); +} diff --git a/src/int_loader.h b/src/int_loader.h new file mode 100644 index 0000000..46d7c64 --- /dev/null +++ b/src/int_loader.h @@ -0,0 +1,72 @@ +#ifndef INT_LOADER_H +#define INT_LOADER_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 +// . + +#include +#include +#include +#include + +#include "db.h" +#include "common.h" +#include "commands/command.h" +#include "commands/admin.h" +#include "commands/bans.h" +#include "commands/cast.h" +#include "commands/groups.h" +#include "commands/info.h" +#include "commands/mods.h" +#include "commands/users.h" +#include "commands/auth.h" +#include "commands/cmd_ranks.h" +#include "commands/cmd_state.h" +#include "commands/acct_recovery.h" +#include "commands/fs.h" +#include "commands/certs.h" +#include "commands/p2p.h" +#include "commands/channels.h" + +class InternalCommandLoader : public CommandLoader +{ + Q_OBJECT + +private: + + QStringList objNames; + bool pubReg; + bool emailConfirmation; + bool passwrdResets; + + void loadSettings(); + void makeDocHeader(const QString &path); + void appendToDoc(const QString &path, const QString &cmdName, InternCommand *obj); + +public: + + RWSharedObjs *rwShared; + + bool exists(const QString &cmdName); + QStringList cmdList(); + QStringList pubCmdList(); + QStringList rankExemptList(); + InternCommand *cmdObj(const QString &name); + + explicit InternalCommandLoader(RWSharedObjs *sharedData, QObject *parent = nullptr); +}; + +#endif // INT_LOADER_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..934e0ed --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,231 @@ +#include +#include +#include +#include +#include + +#include "db.h" +#include "common.h" +#include "tcp_server.h" +#include "unix_signal.h" +#include "cmd_executor.h" +#include "db_setup.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 +// . + +void showHelp() +{ + QTextStream txtOut(stdout); + + txtOut << "" << endl << APP_NAME << " v" << QCoreApplication::applicationVersion() << endl << endl; + txtOut << "Usage: " << APP_TARGET << " " << endl << endl; + txtOut << "" << endl << endl; + txtOut << " -help : display usage information about this application." << endl; + txtOut << " -start : start a new host instance in the background." << endl; + txtOut << " -stop : stop the current host instance if one is currently running." << endl; + txtOut << " -about : display versioning/warranty information about this application." << endl; + txtOut << " -addr {ip_address:port} : set the listening address and port for TCP clients." << endl; + txtOut << " -status : display status information about the host instance if it is currently running." << endl; + txtOut << " -reset_root : reset the root account password to default." << endl; + txtOut << " -executor : this starts a command executor instance. this is normally for internal use only." << endl; + txtOut << " -host : this starts a blocking host instance. this is normally for internal use only." << endl << endl; +} + +void soeDueToDbErr(int *retCode) +{ + *retCode = 1; + + QTextStream(stderr) << "" << endl << "err: Unable to continue due to an unclean or non-existent database structure." << endl; +} + +int shellToHost(const QStringList &args, QCoreApplication &app) +{ + int ret = 0; + auto *ipc = new ShellIPC(args, &app); + + QObject::connect(ipc, SIGNAL(closeInstance()), &app, SLOT(quit())); + + if (ipc->connectToHost()) + { + ret = QCoreApplication::exec(); + } + + return ret; +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + qRegisterMetaType("QAbstractSocket::SocketState"); + qRegisterMetaType >("QSharedPointer"); + qRegisterMetaType >("QSharedPointer"); + + serializeThread(app.thread()); + + QCoreApplication::setApplicationName(APP_NAME); + QCoreApplication::setApplicationVersion(APP_VER); + + qputenv(ENV_EXENAME, APP_TARGET); + + QString err; + QStringList args = QCoreApplication::arguments(); + bool dbFail = false; + int ret = 0; + + if (!setupDb(&err)) + { + QTextStream(stderr) << "" << endl << "err: Database setup error, the host will not be able to start without a solid database structure." << endl; + QTextStream(stderr) << " what happened: " << endl << err << endl; + + dbFail = true; + } + + qInstallMessageHandler(msgHandler); + + if (args.contains("-help", Qt::CaseInsensitive)) + { + showHelp(); + } + else if (args.contains("-about", Qt::CaseInsensitive)) + { + QTextStream(stdout) << "" << endl << APP_NAME << " v" << QCoreApplication::applicationVersion() << endl << endl; + QTextStream(stdout) << "Based on QT " << QT_VERSION_STR << " " << 8 * QT_POINTER_SIZE << "bit" << endl << endl; + QTextStream(stdout) << "The program is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE" << endl; + QTextStream(stdout) << "WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE." << endl << endl; + } + else if (args.contains("-addr", Qt::CaseInsensitive)) + { + QString params = getParam("-addr", args); + QStringList addr = params.split(':'); + + ret = 128; + + if (dbFail) + { + soeDueToDbErr(&ret); + } + else if (addr.size() != 2) + { + QTextStream(stderr) << "" << endl << "err: Address string parsing error, number of params found: " << addr.size() << endl; + } + else + { + bool pOk; + ushort port = addr[1].toUShort(&pOk); + + if (!pOk) + { + QTextStream(stderr) << "" << endl << "err: Invalid port." << endl; + } + else if (port == 0) + { + QTextStream(stderr) << "" << endl << "err: The port cannot be 0." << endl; + } + else if (QHostAddress(addr[0]).isNull()) + { + QTextStream(stderr) << "" << endl << "err: Invalid ip address." << endl; + } + else + { + ret = 0; + + Query db(&app); + + db.setType(Query::UPDATE, TABLE_SERV_SETTINGS); + db.addColumn(COLUMN_IPADDR, addr[0]); + db.addColumn(COLUMN_PORT, port); + db.exec(); + } + } + } + else if (args.contains("-executor", Qt::CaseInsensitive)) + { + if (dbFail) + { + soeDueToDbErr(&ret); + } + else + { + auto *session = new Session(&app); + + session->startAsSlave(args); + + ret = QCoreApplication::exec(); + } + } + else if (args.contains("-host", Qt::CaseInsensitive)) + { + if (dbFail) + { + soeDueToDbErr(&ret); + } + else + { + auto *serv = new TCPServer(&app); + + #ifdef Q_OS_LINUX + + setupUnixSignalHandlers(); + + auto *signalHandler = new UnixSignalHandler(&app); + + QObject::connect(signalHandler, SIGNAL(closeServer()), serv, SLOT(closeServer())); + + #endif + + if (serv->start()) + { + ret = QCoreApplication::exec(); + } + } + } + else if (args.contains("-start", Qt::CaseInsensitive)) + { + if (dbFail) + { + soeDueToDbErr(&ret); + } + else + { + QProcess::startDetached(QCoreApplication::applicationFilePath(), QStringList() << "-host"); + } + } + else if (args.contains("-stop", Qt::CaseInsensitive) || args.contains("-status", Qt::CaseInsensitive)) + { + ret = shellToHost(args, app); + } + else if (args.contains("-reset_root", Qt::CaseInsensitive)) + { + if (dbFail) + { + soeDueToDbErr(&ret); + } + else + { + updatePassword(ROOT_USER, DEFAULT_PASSWRD, TABLE_USERS, true); + } + } + else + { + showHelp(); + } + + cleanupDbConnection(); + + return ret; +} diff --git a/src/make_cert.cpp b/src/make_cert.cpp new file mode 100644 index 0000000..9dcc842 --- /dev/null +++ b/src/make_cert.cpp @@ -0,0 +1,307 @@ +#include "make_cert.h" + +// This file is part of MRCI_Client. + +// MRCI_Client 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_Client 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_Client under the LICENSE.md file. If not, see +// . + +Cert::Cert(QObject *parent) : QObject(parent) +{ + pKey = EVP_PKEY_new(); + x509 = X509_new(); + bne = BN_new(); + rsa = RSA_new(); +} + +void Cert::cleanup() +{ + EVP_PKEY_free(pKey); + X509_free(x509); + BN_free(bne); +} + +QByteArray tempFilePath(const QString &baseName) +{ + return QDir::tempPath().toUtf8() + "/" + baseName.toUtf8() + "_" + QDateTime::currentDateTime().toString("YYYYMMddHHmmsszzz").toUtf8(); +} + +long genSerialViaDateTime() +{ + QDateTime dateTime = QDateTime::currentDateTime(); + QString serialStr = dateTime.toString("YYYYMMddHH"); + + return serialStr.toLong(); +} + +bool genRSAKey(Cert *cert) +{ + bool ret = false; + + if (cert->pKey && cert->bne && cert->rsa) + { + if (BN_set_word(cert->bne, RSA_F4)) + { + if (RSA_generate_key_ex(cert->rsa, 2048, cert->bne, NULL)) + { + if (EVP_PKEY_assign_RSA(cert->pKey, cert->rsa)) + { + ret = true; + } + } + } + } + + return ret; +} + +bool genX509(Cert *cert, const QString &coName) +{ + bool ret = false; + + if (cert->x509 && cert->pKey) + { + ASN1_INTEGER_set(X509_get_serialNumber(cert->x509), genSerialViaDateTime()); + + X509_gmtime_adj(X509_get_notBefore(cert->x509), 0); // now + X509_gmtime_adj(X509_get_notAfter(cert->x509), 31536000L); // 365 days + X509_set_pubkey(cert->x509, cert->pKey); + + // copy the subject name to the issuer name. + + X509_NAME *name = X509_get_subject_name(cert->x509); + QByteArray orgName = QCoreApplication::organizationName().toUtf8(); + + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *) "USA", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *) orgName.data(), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *) coName.toUtf8().data(), -1, -1, 0); + + X509_set_issuer_name(cert->x509, name); + + if (X509_sign(cert->x509, cert->pKey, EVP_sha1())) + { + ret = true; + } + } + + return ret; +} + +bool writePrivateKey(const char *path, Cert* cert) +{ + bool ret = false; + FILE *file = fopen(path, "wb"); + + if (file) + { + ret = PEM_write_PrivateKey(file, cert->pKey, NULL, NULL, 0, NULL, NULL); + } + + fclose(file); + + return ret; +} + +bool writeX509(const char *path, Cert *cert) +{ + bool ret = false; + FILE *file = fopen(path, "wb"); + + if (file) + { + ret = PEM_write_X509(file, cert->x509); + } + + fclose(file); + + return ret; +} + +bool getCertAndKey(const QString &coName, QByteArray &cert, QByteArray &privKey) +{ + bool ret = true; + + Query db; + + db.setType(Query::PULL, TABLE_CERT_DATA); + db.addColumn(COLUMN_CERT); + db.addColumn(COLUMN_PRIV_KEY); + db.addCondition(COLUMN_COMMON_NAME, coName); + db.exec(); + + if (db.rows()) + { + cert = db.getData(COLUMN_CERT).toByteArray(); + privKey = db.getData(COLUMN_PRIV_KEY).toByteArray(); + + QSslCertificate certObj(cert, QSsl::Pem); + + if (certObj.isNull()) + { + genNewSSLData(coName, cert, privKey, true); + + qDebug() << "The cert for CN: " << coName << " was invalid, generated a new self signed cert."; + } + else if (certObj.expiryDate().isValid() && (certObj.expiryDate() < QDateTime::currentDateTime())) + { + genNewSSLData(coName, cert, privKey, true); + + qDebug() << "The cert for CN: " << coName << " was expired, generated a new self signed cert."; + } + } + else if (islocalIP(coName) && !coName.isEmpty()) + { + genNewSSLData(coName, cert, privKey, false); + + qDebug() << "Generated a new self signed cert for CN: " << coName; + } + else + { + ret = false; + } + + return ret; +} + +void genNewSSLData(const QString &coName, QByteArray &cert, QByteArray &privKey, bool exists) +{ + auto *newCert = new Cert(); + QByteArray certPath = tempFilePath(QString(APP_NAME) + "Cert"); + QByteArray privKeyPath = tempFilePath(QString(APP_NAME) + "PrivKey"); + + if (genRSAKey(newCert)) + { + if (genX509(newCert, coName)) + { + if (writePrivateKey(privKeyPath.data(), newCert) && + writeX509(certPath.data(), newCert)) + { + QFile certFile(certPath); + QFile privFile(privKeyPath); + + if (certFile.open(QFile::ReadOnly) && privFile.open(QFile::ReadOnly)) + { + Query db; + + cert = certFile.readAll(); + privKey = privFile.readAll(); + + if (exists) db.setType(Query::UPDATE, TABLE_CERT_DATA); + else db.setType(Query::PUSH, TABLE_CERT_DATA); + + db.addColumn(COLUMN_COMMON_NAME, coName); + db.addColumn(COLUMN_CERT, cert); + db.addColumn(COLUMN_PRIV_KEY, privKey); + db.exec(); + } + + certFile.close(); + privFile.close(); + certFile.remove(); + privFile.remove(); + } + } + } + + newCert->cleanup(); + newCert->deleteLater(); +} + +void genNewSSLData(const QString &coName) +{ + QByteArray cert; + QByteArray priv; + + genNewSSLData(coName, cert, priv, false); +} + +bool islocalIP(const QString &coName) +{ + bool ret = false; + + QList interfaces = QNetworkInterface::allAddresses(); + + for (auto&& addr : interfaces) + { + if ((addr.toString() == coName) && addr.isGlobal()) + { + qDebug() << "Detected local address: " << addr.toString(); + + ret = true; + } + } + + return ret; +} + +bool certExists(const QString &coName) +{ + Query db; + + db.setType(Query::PULL, TABLE_CERT_DATA); + db.addColumn(COLUMN_COMMON_NAME); + db.addCondition(COLUMN_COMMON_NAME, coName); + db.exec(); + + return db.rows(); +} + +QSslKey toSSLKey(const QByteArray &data) +{ + QSslKey ret(data, QSsl::Rsa, QSsl::Pem); + + if (ret.isNull()) ret = QSslKey(data, QSsl::Dsa, QSsl::Pem); + if (ret.isNull()) ret = QSslKey(data, QSsl::Ec, QSsl::Pem); + if (ret.isNull()) ret = QSslKey(data, QSsl::Opaque, QSsl::Pem); + + if (ret.isNull()) ret = QSslKey(data, QSsl::Rsa, QSsl::Der); + if (ret.isNull()) ret = QSslKey(data, QSsl::Dsa, QSsl::Der); + if (ret.isNull()) ret = QSslKey(data, QSsl::Ec, QSsl::Der); + if (ret.isNull()) ret = QSslKey(data, QSsl::Opaque, QSsl::Der); + + return ret; +} + +QSslKey toSSLKey(QIODevice *dev) +{ + QSslKey ret(dev, QSsl::Rsa, QSsl::Pem); + + if (ret.isNull()) ret = QSslKey(dev, QSsl::Dsa, QSsl::Pem); + if (ret.isNull()) ret = QSslKey(dev, QSsl::Ec, QSsl::Pem); + if (ret.isNull()) ret = QSslKey(dev, QSsl::Opaque, QSsl::Pem); + + if (ret.isNull()) ret = QSslKey(dev, QSsl::Rsa, QSsl::Der); + if (ret.isNull()) ret = QSslKey(dev, QSsl::Dsa, QSsl::Der); + if (ret.isNull()) ret = QSslKey(dev, QSsl::Ec, QSsl::Der); + if (ret.isNull()) ret = QSslKey(dev, QSsl::Opaque, QSsl::Der); + + return ret; +} + +QSslCertificate toSSLCert(const QByteArray &data) +{ + QSslCertificate ret(data, QSsl::Pem); + + if (ret.isNull()) ret = QSslCertificate(data, QSsl::Der); + + return ret; +} + +QSslCertificate toSSLCert(QIODevice *dev) +{ + QSslCertificate ret(dev, QSsl::Pem); + + if (ret.isNull()) ret = QSslCertificate(dev, QSsl::Der); + + return ret; +} diff --git a/src/make_cert.h b/src/make_cert.h new file mode 100644 index 0000000..a63a032 --- /dev/null +++ b/src/make_cert.h @@ -0,0 +1,68 @@ +#ifndef MAKE_CERT_H +#define MAKE_CERT_H + +// This file is part of MRCI_Client. + +// MRCI_Client 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_Client 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_Client under the LICENSE.md file. If not, see +// . + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "db.h" + +class Cert : public QObject +{ + Q_OBJECT + +public: + + EVP_PKEY *pKey; + X509 *x509; + BIGNUM *bne; + RSA *rsa; + + void cleanup(); + + explicit Cert(QObject *parent = nullptr); +}; + +QByteArray tempFilePath(const QString &baseName); +QSslKey toSSLKey(const QByteArray &data); +QSslKey toSSLKey(QIODevice *dev); +QSslCertificate toSSLCert(const QByteArray &data); +QSslCertificate toSSLCert(QIODevice *dev); +long genSerialViaDateTime(); +bool genRSAKey(Cert *cert); +bool genX509(Cert *cert, const QString &coName); +bool writePrivateKey(const char *path, Cert *cert); +bool writeX509(const char *path, Cert *cert); +bool islocalIP(const QString &coName); +bool certExists(const QString &coName); +bool getCertAndKey(const QString &coName, QByteArray &cert, QByteArray &privKey); +void genNewSSLData(const QString &coName, QByteArray &cert, QByteArray &privKey, bool exists); +void genNewSSLData(const QString &coName); + +#endif // MAKE_CERT_H diff --git a/src/payload.cpp b/src/payload.cpp new file mode 100644 index 0000000..c3221ef --- /dev/null +++ b/src/payload.cpp @@ -0,0 +1,87 @@ +#include "payload.h" + +// This file is part of MRCI_Host. + +// MRCI_Host 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_Host 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_Host under the LICENSE.md file. If not, see +// . + +QByteArray wrInt(quint64 num, int numOfBits) +{ + QByteArray ret(numOfBits / 8, (char) 0); + + num = qToLittleEndian(num); + + memcpy(ret.data(), &num, ret.size()); + + return ret; +} + +uint rdInt(const QByteArray &bytes) +{ + quint64 ret = 0; + + memcpy(&ret, bytes.data(), bytes.size()); + + return qFromLittleEndian(ret); +} + +QString sessionCountShareKey() +{ + return QString(APP_NAME) + "::SessionCount"; +} + +uint rdSessionLoad() +{ + uint ret = 0; + + QSharedMemory mem(sessionCountShareKey()); + + if (mem.attach(QSharedMemory::ReadOnly)) + { + if (mem.size() >= 4) + { + mem.lock(); + + memcpy(&ret, mem.data(), 4); + + mem.unlock(); + } + + mem.detach(); + } + + return ret; +} + +void wrInt(QSharedMemory *mem, uint value) +{ + if (mem->isAttached() && (mem->size() == 4)) + { + memcpy(mem->data(), &value, 4); + } +} + +QSharedPointer toIPCPointer(const QByteArray &data) +{ + return QSharedPointer(new QByteArray(data)); +} + +QByteArray wrFrame(const QByteArray &data, uchar dType) +{ + return QByteArray(wrInt(dType, 8) + wrInt(data.size(), MAX_BITS) + data); +} + +Payload::Payload(QObject *parent) : QObject(parent) {obj = 0;} + +InternCommand::InternCommand(QObject *parent) : ExternCommand(parent) {} diff --git a/src/payload.h b/src/payload.h new file mode 100644 index 0000000..7d3c58b --- /dev/null +++ b/src/payload.h @@ -0,0 +1,119 @@ +#ifndef PAYLOAD_H +#define PAYLOAD_H + +// This file is part of MRCI_Host. + +// MRCI_Host 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_Host 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_Host under the LICENSE.md file. If not, see +// . + +#include +#include +#include +#include +#include +#include + +#include "db.h" +#include "commands/command.h" + +#define MAX_BITS 24 +#define IPC_CMD_REBIND "bind" +#define IPC_CMD_EXIT "exit" +#define IPC_CMD_CAST "cast" +#define IPC_CMD_MAXSES "max_sessions_changed" +#define IPC_CMD_LOGOUT "force_logout" +#define IPC_CMD_GROUP_RENAMED "group_renamed" +#define IPC_CMD_USER_RENAMED "user_renamed" +#define IPC_CMD_GROUP_TRANS "group_transferred" +#define IPC_CMD_USER_GROUP_CHANGED "user_group_changed" +#define IPC_CMD_PERM_IDS_CHANGED "permission_ids_changed" +#define IPC_CMD_RESTART "restart" +#define IPC_CMD_ENABLE_MOD "enable_mod" +#define IPC_CMD_DISABLE_MOD "disable_mod" +#define IPC_CMD_GROUP_UPDATED "group_params_changed" +#define IPC_CMD_END_SESSION "end_Session" + +enum PrivateTypeID +{ + PRIV_IPC = 1, + PUB_IPC = 2 +}; + +enum ExecutorFlags +{ + IPC_LINK_OK = 1, + DSIZE_RDY = 1 << 1, + DTYPE_RDY = 1 << 2, + ALLOW_PRIV_IPC = 1 << 3, + CMD_LOOP = 1 << 4, + CMD_PAUSED = 1 << 5, + LOGGED_IN = 1 << 6, + REBUILD_ON_IDLE = 1 << 7 +}; + +enum SessionFlags +{ + CRASHED_STATE = 1 << 8, + SSL_NEEDED = 1 << 9, + VER_OK = 1 << 10, + EXPECTED_TERM = 1 << 11, + ACTIVE_PAYLOAD = 1 << 12, + END_SESSION_ON_PAYLOAD_DEL = 1 << 13 +}; + +enum ServerFlags +{ + FIRST_START = 1 << 14, + RES_ON_EMPTY = 1 << 15, + CLOSE_ON_EMPTY = 1 << 16, + ACCEPTING = 1 << 17 +}; + +QSharedPointer toIPCPointer(const QByteArray &data); +QString sessionCountShareKey(); +QByteArray wrFrame(const QByteArray &data, uchar dType); +QByteArray wrInt(quint64 num, int numOfBits); +void wrInt(QSharedMemory *mem, uint value); +uint rdInt(const QByteArray &bytes); +uint rdSessionLoad(); + +class Session; + +class Payload : public QObject +{ + Q_OBJECT + +public: + + Session *obj; + + explicit Payload(QObject *parent = 0); +}; + +class InternCommand : public ExternCommand +{ + Q_OBJECT + +public: + + QTextStream toServ; + + explicit InternCommand(QObject *parent = 0); + +signals: + + void loginSuccess(); +}; + +#endif // PAYLOAD_H diff --git a/src/session.cpp b/src/session.cpp new file mode 100644 index 0000000..477959a --- /dev/null +++ b/src/session.cpp @@ -0,0 +1,1328 @@ +#include "session.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 +// . + +Session::Session(QObject *parent) : QObject(parent) +{ + chIds = QByteArray(54, static_cast(0)); + rwShared = new RWSharedObjs(this); + shared = new SharedObjs(this); + exeDebugInfo = new QSharedMemory(this); + ipcServ = nullptr; + slaveProc = nullptr; + ipcLink = nullptr; + tcpSocket = nullptr; + activeUpdate = false; + chOwnerOverride = false; + exeCrashCount = 0; + clientMajor = 0; + clientMinor = 0; + clientPatch = 0; + ipcFrameCmdId = 0; + ipcFrameSize = 0; + ipcFrameType = 0; + tcpFrameCmdId = 0; + tcpFrameSize = 0; + tcpFrameType = 0; + hostRank = 0; + flags = 0; + + shared->clientMajor = &clientMajor; + shared->clientMinor = &clientMinor; + shared->clientPatch = &clientPatch; + shared->groupName = &groupName; + shared->userName = &userName; + shared->displayName = &displayName; + shared->appName = &appName; + shared->sessionAddr = &peerIp; + shared->sessionId = &sessionId; + shared->userId = &userId; + shared->chIds = &chIds; + shared->wrAbleChIds = &wrAbleChIds; + shared->p2pAccepted = &p2pAccepted; + shared->p2pPending = &p2pPending; + shared->activeUpdate = &activeUpdate; + shared->chList = &chList; + shared->chOwnerOverride = &chOwnerOverride; + shared->hostRank = &hostRank; + + rwShared->clientMajor = &clientMajor; + rwShared->clientMinor = &clientMinor; + rwShared->clientPatch = &clientPatch; + rwShared->groupName = &groupName; + rwShared->userName = &userName; + rwShared->displayName = &displayName; + rwShared->appName = &appName; + rwShared->sessionAddr = &peerIp; + rwShared->sessionId = &sessionId; + rwShared->userId = &userId; + rwShared->chIds = &chIds; + rwShared->wrAbleChIds = &wrAbleChIds; + rwShared->p2pAccepted = &p2pAccepted; + rwShared->p2pPending = &p2pPending; + rwShared->activeUpdate = &activeUpdate; + rwShared->chList = &chList; + rwShared->chOwnerOverride = &chOwnerOverride; + rwShared->hostRank = &hostRank; +} + +void Session::genSessionId() +{ + QByteArray serial = genSerialNumber().toUtf8(); + QByteArray sysId = QSysInfo::machineUniqueId(); + + QCryptographicHash hasher(QCryptographicHash::Sha3_224); + + hasher.addData(serial + sysId); + + sessionId = hasher.result(); +} + +void Session::initAsMain(QSslSocket *tcp) +{ + genSessionId(); + + tcpSocket = tcp; + slaveProc = new QProcess(this); + ipcServ = new QLocalServer(this); + peerIp = tcp->peerAddress().toString(); + pipeName = pipesPath() + "/" + sessionId.toHex(); + + if (QFile::exists(pipeName)) + { + QFile::remove(pipeName); + } + + exeDebugInfo->setKey(sessionId.toHex()); + exeDebugInfo->create(EXE_DEBUG_INFO_SIZE); + + auto buffSize = static_cast(qPow(2, MAX_FRAME_BITS) - 1) + (MAX_FRAME_BITS / 8) + 2; + // max_data_size_per_frame + size_of_size_bytes + size_of_cmd_id + + tcp->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, buffSize); + tcp->setSocketOption(QAbstractSocket::SendBufferSizeSocketOption, buffSize); + + connect(slaveProc, &QProcess::readyReadStandardError, this, &Session::sendStderr); + connect(slaveProc, &QProcess::readyReadStandardOutput, this, &Session::sendStdout); + connect(slaveProc, &QProcess::errorOccurred, this, &Session::exeError); + connect(slaveProc, &QProcess::started, this, &Session::exeStarted); + connect(slaveProc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(exeFinished(int,QProcess::ExitStatus))); + + connect(tcpSocket, &QSslSocket::disconnected, this, &Session::endSession); + connect(tcpSocket, &QSslSocket::readyRead, this, &Session::dataFromClient); + connect(tcpSocket, &QSslSocket::encrypted, this, &Session::run); + + connect(ipcServ, &QLocalServer::newConnection, this, &Session::newIPCLink); +} + +void Session::startAsSlave(const QStringList &args) +{ + sessionId = QByteArray::fromHex(getParam("-session_id", args).toUtf8()); + clientMajor = getParam("-client_major", args).toUShort(); + clientMinor = getParam("-client_minor", args).toUShort(); + clientPatch = getParam("-client_patch", args).toUShort(); + peerIp = getParam("-session_ipaddr", args); + appName = getParam("-app_name", args); + pipeName = pipesPath() + "/" + sessionId.toHex(); + ipcLink = new QLocalSocket(this); + executor = new CmdExecutor(rwShared, shared, exeDebugInfo); + + exeDebugInfo->setKey(sessionId.toHex()); + exeDebugInfo->attach(); + + auto *exeThr = new QThread(); + + connect(exeThr, &QThread::finished, executor, &CmdExecutor::deleteLater); + connect(exeThr, &QThread::finished, exeThr, &QThread::deleteLater); + + connect(executor, &CmdExecutor::okToDelete, this, &Session::closeInstance); + connect(executor, &CmdExecutor::okToDelete, exeThr, &QThread::quit); + connect(executor, &CmdExecutor::logout, this, &Session::logout); + connect(executor, &CmdExecutor::authOk, this, &Session::authOk); + connect(executor, &CmdExecutor::endSession, this, &Session::endSession); + connect(executor, &CmdExecutor::dataToSession, this, &Session::dataToClient); + + connect(this, &Session::closeExe, executor, &CmdExecutor::close); + connect(this, &Session::loadCommands, executor, &CmdExecutor::buildCommands); + connect(this, &Session::dataToCommand, executor, &CmdExecutor::exeCmd); + connect(this, &Session::unloadModFile, executor, &CmdExecutor::unloadModFile); + connect(this, &Session::loadModFile, executor, &CmdExecutor::loadModFile); + + connect(ipcLink, &QLocalSocket::connected, this, &Session::ipcConnected); + connect(ipcLink, &QLocalSocket::disconnected, this, &Session::ipcDisconnected); + connect(ipcLink, &QLocalSocket::readyRead, this, &Session::dataFromIPC); + connect(ipcLink, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(ipcError(QLocalSocket::LocalSocketError))); + + serializeThread(exeThr); + + executor->moveToThread(exeThr); + exeThr->start(); + + QTimer::singleShot(100, this, SLOT(run())); +} + +void Session::modLoadCrashCheck(const QString &crashInfo) +{ + if (crashInfo.contains("loadModLib()")) + { + int pos = crashInfo.indexOf("path: "); + + if (pos != -1) + { + QString path = crashInfo.mid(pos + 7).trimmed(); + + Query db(this); + + db.setType(Query::UPDATE, TABLE_MODULES); + db.addColumn(COLUMN_LOCKED, true); + db.addCondition(COLUMN_MOD_MAIN, path); + db.exec(); + + qDebug() << "Module: '" << path << "' auto locked due to a crash while loading."; + } + } +} + +void Session::rdExeDebug() +{ + if (exeDebugInfo->isAttached()) + { + exeDebugInfo->lock(); + + QByteArray data = QByteArray(static_cast(exeDebugInfo->data()), EXE_DEBUG_INFO_SIZE); + + data.replace(QByteArray(2, static_cast(0)), QByteArray()); + + emit dataToClient(ASYNC_SYS_MSG, toTEXT("\ndebug info:\n"), TEXT); + emit dataToClient(ASYNC_SYS_MSG, data + toTEXT("\n"), TEXT); + + qDebug() << "Debug info generated: " + fromTEXT(data); + + modLoadCrashCheck(fromTEXT(data)); + + exeDebugInfo->unlock(); + } +} + +bool Session::isSlave() +{ + return (tcpSocket == nullptr); +} + +bool Session::isMain() +{ + return (tcpSocket != nullptr); +} + +void Session::payloadDeleted() +{ + if (isSlave()) + { + qDebug() << "Session::payloadDeleted() called while running in slave mode."; + } + else + { + flags &= ~ACTIVE_PAYLOAD; + + if (flags & END_SESSION_ON_PAYLOAD_DEL) + { + endSession(); + } + } +} + +void Session::connectToPeer(const QSharedPointer &peer) +{ + if (isSlave()) + { + qDebug() << "Session::connectToPeer() called while running in slave mode."; + } + else if (peer->sessionObj == nullptr) + { + qDebug() << "Session::connectToPeer() the peer session object is null."; + } + else if ((peer->sessionObj != this) && (flags & IPC_LINK_OK)) + { + connect(peer->sessionObj, &Session::backendToPeers, this, &Session::peersDataIn); + connect(this, &Session::backendToPeers, peer->sessionObj, &Session::peersDataIn); + } +} + +void Session::run() +{ + flags &= ~SSL_HOLD; + + if (isSlave()) + { + ipcLink->connectToServer(pipeName); + + QTimer::singleShot(IPC_PREP_TIME, this, SLOT(newIPCTimeout())); + } + else if (!ipcServ->listen(pipeName)) + { + qDebug() << "Session::run() unable to listen on pipe name: " + pipeName + " reason: " + ipcServ->errorString(); + + dataToClient(ASYNC_SYS_MSG, toTEXT("\nsystem err: Unable to create an IPC pipe for the command executor, ending the session.\n"), ERR); + endSession(); + } + else + { + QStringList args; + + args.append("-executor"); + args.append("-session_id"); + args.append(sessionId.toHex()); + args.append("-client_major"); + args.append(QString::number(clientMajor)); + args.append("-client_minor"); + args.append(QString::number(clientMinor)); + args.append("-client_patch"); + args.append(QString::number(clientPatch)); + args.append("-session_ipaddr"); + args.append(peerIp); + args.append("-app_name"); + args.append(appName); + + slaveProc->start(QCoreApplication::applicationFilePath(), args); + + dataToClient(ASYNC_SYS_MSG, toTEXT("Attempting to start a new command executor.\n"), TEXT); + + QTimer::singleShot(IPC_PREP_TIME, this, SLOT(newIPCTimeout())); + } +} + +void Session::ipcOk() +{ + if (isSlave()) + { + qDebug() << "Session::ipcOk() called while running in slave mode."; + } + else + { + flags |= IPC_LINK_OK; + flags |= ACTIVE_PAYLOAD; + + auto *payload = new SessionCarrier(this); + + connect(ipcLink, &QLocalSocket::readyRead, this, &Session::dataFromIPC); + connect(ipcLink, &QLocalSocket::disconnected, this, &Session::ipcDisconnected); + connect(ipcLink, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(ipcError(QLocalSocket::LocalSocketError))); + + connect(payload, &SessionCarrier::destroyed, this, &Session::payloadDeleted); + + emit connectPeers(QSharedPointer(payload)); + + dataToClient(ASYNC_SYS_MSG, toTEXT("IPC connection successfully established.\n"), TEXT); + + if (!userId.isEmpty()) + { + ipcLink->write(wrFrame(ASYNC_RESTORE_AUTH, userId, PRIV_IPC)); + } + else + { + ipcLink->write(wrFrame(ASYNC_PUBLIC_AUTH, QByteArray(), PRIV_IPC)); + } + } +} + +void Session::newIPCTimeout() +{ + if (isSlave()) + { + if (ipcLink->state() != QLocalSocket::ConnectedState) + { + QCoreApplication::exit(PIPE_CONNECT_TIMEOUT); + } + } + else if (ipcLink == nullptr) + { + dataToClient(ASYNC_SYS_MSG, toTEXT("\nsystem err: Timed out waiting for the command executor to request an IPC link with the session.\n"), ERR); + endSession(); + } +} + +void Session::newIPCLink() +{ + if (isSlave()) + { + qDebug() << "Session::newIPCLink() called while running in slave mode."; + } + else + { + if (ipcLink != nullptr) + { + qDebug() << "Session::newIPCLink() another local socket tried to connect to the session while there is already an active connection. session id: " + sessionId.toHex(); + + ipcServ->nextPendingConnection()->deleteLater(); + } + else + { + ipcLink = ipcServ->nextPendingConnection(); + + QTimer::singleShot(IPC_PREP_TIME, this, SLOT(ipcOk())); + } + } +} + +void Session::exeStarted() +{ + if (isSlave()) + { + qDebug() << "Session::exeStarted() called while running in slave mode."; + } + else + { + dataToClient(ASYNC_SYS_MSG, toTEXT("Command executor successfully started, awaiting an IPC request.\n"), TEXT); + } +} + +void Session::ipcConnected() +{ + if (isSlave()) + { + flags |= IPC_LINK_OK; + } +} + +void Session::ipcDisconnected() +{ + flags &= ~IPC_LINK_OK; + + if (isMain()) + { + ipcLink = nullptr; + } +} + +void Session::exeFinished(int ret, QProcess::ExitStatus status) +{ + if (isSlave()) + { + qDebug() << "Session::exeFinished() called while running in slave mode."; + } + else if (flags & EXPECTED_TERM) + { + endSession(); + } + else if ((status == QProcess::NormalExit) && (ret == FAILED_TO_OPEN_PIPE)) + { + dataToClient(ASYNC_EXE_CRASH, toTEXT("\nsystem err: The command executor could not open a new pipe for listening.\n"), ERR); + endSession(); + } + else if ((status == QProcess::NormalExit) && (ret == PIPE_CONNECT_TIMEOUT)) + { + dataToClient(ASYNC_EXE_CRASH, toTEXT("\nsystem err: The command executor timed out waiting for the session to acknowledge the IPC request.\n"), ERR); + endSession(); + } + else + { + int msecSinceLastCrash = lastExeCrash.msecsTo(QTime::currentTime()); + + if ((msecSinceLastCrash <= 5000) || lastExeCrash.isNull()) + { + // only count executor crashes within 5sec of each other. any longer than that + // can be considered a recoverable session. + + exeCrashCount++; + } + else + { + lastExeCrash = QTime(); + exeCrashCount = 0; + } + + if (exeCrashCount <= EXE_CRASH_LIMIT) + { + ipcServ->close(); + + dataToClient(ASYNC_EXE_CRASH, toTEXT("\nsystem err: The command executor has stopped unexpectedly.\n"), ERR); + rdExeDebug(); + dataToClient(ASYNC_SYS_MSG, toTEXT("\nAttempting to restart the executor...\n\n"), TEXT); + run(); + } + else + { + // if there is the amount of executor crashes defined in EXE_CRASH_LIMIT + // within 5sec of each other then the session can no longer be considered + // recoverable so it will be killed to prevent an infinite loop. + + dataToClient(ASYNC_EXE_CRASH, toTEXT("\nsystem err: The command executor has crashed too many times, ending the session.\n"), ERR); + endSession(); + } + } +} + +void Session::exeError(QProcess::ProcessError err) +{ + if (isSlave()) + { + qDebug() << "Session::exeError() called while running in slave mode."; + } + else if (err == QProcess::FailedToStart) + { + qDebug() << "\nsystem err: Could not start the command executor. reason: " + slaveProc->errorString() + ".\n"; + + dataToClient(ASYNC_SYS_MSG, toTEXT("\nsystem err: Could not start the command executor. details are logged for the host admin.\n"), ERR); + endSession(); + } + else + { + qDebug() << "Session:: exeError() " << slaveProc->errorString(); + } +} + +void Session::ipcError(QLocalSocket::LocalSocketError socketError) +{ + if (socketError != QLocalSocket::PeerClosedError) + { + if (isSlave()) + { + qDebug() << "Session::ipcError() slave mode. socketError: " + QString::number(socketError) + " session id: " + sessionId.toHex(); + } + else + { + qDebug() << "Session::ipcError() main mode. socketError: " + QString::number(socketError) + " session id: " + sessionId.toHex(); + } + } +} + +void Session::addIpAction(const QString &action) +{ + Query db(this); + + db.setType(Query::PUSH, TABLE_IPHIST); + db.addColumn(COLUMN_IPADDR, peerIp); + db.addColumn(COLUMN_LOGENTRY, action); + db.addColumn(COLUMN_SESSION_ID, sessionId); + db.addColumn(COLUMN_CLIENT_VER, QString::number(clientMajor) + "." + + QString::number(clientMinor) + "." + + QString::number(clientPatch)); + db.exec(); +} + +void Session::closeInstance() +{ + if (isMain()) + { + qDebug() << "Session::closeInstance() called while running on the main process."; + } + else + { + QCoreApplication::exit(0); + } +} + +void Session::endSession() +{ + if (isSlave()) + { + ipcLink->write(wrFrame(ASYNC_END_SESSION, QByteArray(), PRIV_IPC)); + } + else + { + if (activeUpdate) + { + QByteArray castHeader = chIds + wrInt(PEER_STAT, 8); + QByteArray data = toPEER_STAT(sessionId, chIds, true); + + emit backendToPeers(ASYNC_LIMITED_CAST, castHeader + data); + } + + if (flags & ACTIVE_PAYLOAD) + { + flags |= END_SESSION_ON_PAYLOAD_DEL; + } + else + { + if (flags & IPC_LINK_OK) + { + flags |= EXPECTED_TERM; + + ipcLink->write(wrFrame(ASYNC_END_SESSION, QByteArray(), PRIV_IPC)); + } + else + { + addIpAction("Session Ended"); + cleanupDbConnection(); + + emit ended(); + } + } + } +} + +void Session::dataFromIPC() +{ + if (flags & IPC_FRAME_RDY) + { + if (ipcLink->bytesAvailable() >= ipcFrameSize) + { + QByteArray data = ipcLink->read(ipcFrameSize); + + if (isSlave()) + { + // data from Session object in main process. + + if ((ipcFrameType == PRIV_IPC) || (ipcFrameType == PUB_IPC)) + { + backendDataIn(ipcFrameCmdId, data); + } + else + { + emit dataToCommand(ipcFrameCmdId, data, ipcFrameType); + } + } + else if (isMain()) + { + // data from Session object in slave process. + + if (ipcFrameType == PRIV_IPC) + { + backendDataIn(ipcFrameCmdId, data); + } + else if ((ipcFrameType == PUB_IPC) || (ipcFrameType == PUB_IPC_WITH_FEEDBACK)) + { + emit backendToPeers(ipcFrameCmdId, data); + } + else + { + dataToClient(ipcFrameCmdId, data, ipcFrameType); + } + } + + flags ^= IPC_FRAME_RDY; + + dataFromIPC(); + } + } + else if (ipcLink->bytesAvailable() >= FRAME_HEADER_SIZE) + { + QByteArray header = ipcLink->read(FRAME_HEADER_SIZE); + + ipcFrameType = static_cast(header[0]); + ipcFrameCmdId = static_cast(rdInt(header.mid(1, 2))); + ipcFrameSize = static_cast(rdInt(header.mid(3))); + flags |= IPC_FRAME_RDY; + + dataFromIPC(); + } +} + +void Session::dataFromClient() +{ + if ((flags & IPC_LINK_OK) && !(flags & SSL_HOLD)) + { + if (flags & TCP_FRAME_RDY) + { + if (tcpSocket->bytesAvailable() >= tcpFrameSize) + { + ipcLink->write(tcpSocket->read(tcpFrameSize)); + + flags ^= TCP_FRAME_RDY; + + dataFromClient(); + } + } + else if (tcpSocket->bytesAvailable() >= FRAME_HEADER_SIZE) + { + QByteArray header = tcpSocket->peek(FRAME_HEADER_SIZE); + + tcpFrameType = static_cast(header[0]); + tcpFrameCmdId = static_cast(rdInt(header.mid(1, 2))); + tcpFrameSize = static_cast(rdInt(header.mid(3)) + FRAME_HEADER_SIZE); + + if ((tcpFrameType == PUB_IPC) || (tcpFrameType == PRIV_IPC) || + (tcpFrameType == PING_PEERS) || (tcpFrameType == PUB_IPC_WITH_FEEDBACK)) + { + // for obvious security reasons, TCP clients should not be allowed + // to send any of the PrivateTypeID data frames. infact, it is logged + // as suspicious behaviour that admins can monitor for. + + addIpAction("Suspicious action: client attempted to send a private TypeID: " + QString::number(tcpFrameType)); + + tcpSocket->readAll(); // kill the frame. + } + else + { + flags |= TCP_FRAME_RDY; + } + + dataFromClient(); + } + } + else if (!(flags & VER_OK)) + { + if (tcpSocket->bytesAvailable() >= CLIENT_HEADER_LEN) + { + if (tcpSocket->read(4) == QByteArray(SERVER_HEADER_TAG)) + { + clientMajor = static_cast(rdInt(tcpSocket->read(2))); + clientMinor = static_cast(rdInt(tcpSocket->read(2))); + clientPatch = static_cast(rdInt(tcpSocket->read(2))); + appName = fromTEXT(tcpSocket->read(128)).trimmed(); + + QString coName = fromTEXT(tcpSocket->read(272)).trimmed(); + QStringList ver = QCoreApplication::applicationVersion().split('.'); + QByteArray servHeader; + + servHeader.append(wrInt(0, 8)); + servHeader.append(wrInt(ver[0].toULongLong(), 16)); + servHeader.append(wrInt(ver[1].toULongLong(), 16)); + servHeader.append(wrInt(ver[2].toULongLong(), 16)); + servHeader.append(sessionId); + + if (clientMajor == 1) + { + flags |= VER_OK; + + addIpAction("Session Active"); + + if (tcpSocket->peerAddress().isLoopback()) + { + // SSL encryption is optional for locally connected clients + // so run() can be called right away instead of starting + // an SSL handshake. + + // reply value 1 means the client version is acceptable + // and the client needs to take no further action, just + // await an IDLE mrci frame from the host to indicate + // that it is ready to take commands. + + servHeader[0] = 1; + + tcpSocket->write(servHeader); + + run(); + } + else + { + flags |= SSL_HOLD; + + QByteArray certBa; + QByteArray privBa; + + if (getCertAndKey(coName, certBa, privBa)) + { + servHeader[0] = 2; + + // reply value 2 means the client version is acceptable + // but the host will now send it's Pem formatted SSL cert + // data in a HOST_CERT mrci frame just after sending it's + // header. + + // the client must use this cert and send a STARTTLS + // signal when ready. + + tcpSocket->setLocalCertificate(toSSLCert(certBa)); + tcpSocket->setPrivateKey(toSSLKey(privBa)); + tcpSocket->write(servHeader); + + dataToClient(ASYNC_SYS_MSG, certBa, HOST_CERT); + + tcpSocket->startServerEncryption(); + } + else + { + servHeader[0] = 4; + + // reply value 4 means the host was unable to load the + // SSL cert associated with the common name sent by the + // client. the session will lock out and auto close at + // this point. + + tcpSocket->write(servHeader); + + endSession(); + } + } + } + else + { + // replay value 3 means the client version is not supported + // by the host and the session will end after sending the + // header. + + servHeader[0] = 3; + + addIpAction("Client Rejected"); + + tcpSocket->write(servHeader); + + endSession(); + } + } + else + { + endSession(); + } + } + } +} + +void Session::dataToClient(quint16 cmdId, const QByteArray &data, uchar typeId) +{ + if (isSlave() && (flags & IPC_LINK_OK)) + { + if (typeId == PUB_IPC_WITH_FEEDBACK) + { + backendDataIn(cmdId, data); + } + + ipcLink->write(wrFrame(cmdId, data, typeId)); + } + else if (isMain()) + { + tcpSocket->write(wrFrame(cmdId, data, typeId)); + } +} + +void Session::peersDataIn(quint16 cmdId, const QByteArray &data) +{ + if (isSlave()) + { + qDebug() << "Session::peersDataIn() called while running in slave mode."; + } + else if (flags & IPC_LINK_OK) + { + ipcLink->write(wrFrame(cmdId, data, PRIV_IPC)); + } +} + +void Session::logout() +{ + if (isMain()) + { + qDebug() << "Session::logout() called while running on the main process."; + } + else + { + userName.clear(); + groupName.clear(); + displayName.clear(); + userId.clear(); + chIds.clear(); + chList.clear(); + + hostRank = 0; + + dataToClient(ASYNC_LOGOUT, QByteArray(), PRIV_IPC); + + emit loadCommands(); + } +} + +void Session::authOk() +{ + if (isMain()) + { + qDebug() << "Session:authOk() called while running on the main process."; + } + else + { + if (!userName.isEmpty()) + { + Query db(this); + + db.setType(Query::PULL, TABLE_CH_MEMBERS); + db.addColumn(COLUMN_CHANNEL_NAME); + db.addCondition(COLUMN_USERNAME, userName); + db.exec(); + + chList.clear(); + + for (int i = 0; i < db.rows(); ++i) + { + chList.append(db.getData(COLUMN_CHANNEL_NAME, i).toString().toLower()); + } + + dataToClient(ASYNC_USER_LOGIN, userId, PRIV_IPC); + sendLocalInfo(); + } + + emit loadCommands(); + } +} + +void Session::castPeerInfo() +{ + if (isMain()) + { + qDebug() << "Session::castPeerInfo() called while running on the main process."; + } + else + { + if (activeUpdate) + { + // format: [54bytes(chIds)][1byte(typeId)][rest-of-bytes(PEER_INFO)] + + QByteArray castHeader = chIds + wrInt(PEER_INFO, 8); + QByteArray data = toPEER_INFO(shared); + + dataToClient(ASYNC_LIMITED_CAST, castHeader + data, PUB_IPC); + } + } +} + +void Session::sendLocalInfo() +{ + if (isMain()) + { + qDebug() << "Session::sendLocalInfo() called while runnning on the main process."; + } + else + { + dataToClient(ASYNC_SYS_MSG, toMY_INFO(shared), MY_INFO); + } +} + +void Session::backendDataIn(quint16 cmdId, const QByteArray &data) +{ + if (flags & IPC_LINK_OK) + { + if (isMain()) + { + if (cmdId == ASYNC_END_SESSION) + { + endSession(); + } + else if (cmdId == ASYNC_USER_LOGIN) + { + userId = data; + } + else if (cmdId == ASYNC_LOGOUT) + { + userId.clear(); + } + else if (cmdId == ASYNC_EXIT) + { + emit closeServer(); + } + else if (cmdId == ASYNC_RESTART) + { + emit resServer(); + } + else if (cmdId == ASYNC_MAXSES) + { + emit setMaxSessions(static_cast(rdInt(data))); + } + else if (cmdId == ASYNC_DISABLE_MOD) + { + emit delayedDirDel(QFileInfo(fromTEXT(data)).absolutePath()); + } + } + else + { + if (cmdId == ASYNC_END_SESSION) + { + p2pAccepted.clear(); + p2pPending.clear(); + + dataToClient(ASYNC_CLOSE_P2P, sessionId, PUB_IPC); + + emit closeExe(); + } + else if (cmdId == ASYNC_USER_DELETED) + { + if (!userName.isEmpty()) + { + if (noCaseMatch(userName, fromTEXT(data))) + { + logout(); + dataToClient(ASYNC_SYS_MSG, toTEXT("\nsystem: your session was forced to logout because your account was deleted.\n"), TEXT); + } + } + } + else if (cmdId == ASYNC_CAST) + { + // format: [54bytes(wrAbleChIds)][1byte(typeId)][rest-of-bytes(payload)] + + if (matchChs(chIds, QByteArray::fromRawData(data.data(), 54))) + { + dataToClient(cmdId, data.mid(55), static_cast(data[54])); + } + } + else if (cmdId == ASYNC_TO_PEER) + { + // format: [28bytes(sessionId)][1byte(typeId)][rest-of-bytes(payload)] + + if (QByteArray::fromRawData(data.data(), 28) == sessionId) + { + dataToClient(cmdId, data.mid(29), static_cast(data[28])); + } + } + else if (cmdId == ASYNC_P2P) + { + // format: [28bytes(dst_sessionId)][28bytes(src_sessionId)][1byte(typeId)][rest-of-bytes(payload)] + + if (QByteArray::fromRawData(data.data(), 28) == sessionId) + { + QByteArray src = data.mid(28, 28); + + if (data[56] == P2P_REQUEST) + { + if (!p2pPending.contains(src) && !p2pAccepted.contains(src)) + { + p2pPending.append(src); + + dataToClient(cmdId, data.mid(57), P2P_REQUEST); + } + } + else if (data[56] == P2P_OPEN) + { + if (p2pPending.contains(src) && !p2pAccepted.contains(src)) + { + p2pPending.removeAll(src); + p2pAccepted.append(src); + + dataToClient(cmdId, data.mid(57), P2P_OPEN); + } + } + else if (data[56] == P2P_CLOSE) + { + if (p2pPending.contains(src)) + { + p2pPending.removeAll(src); + + dataToClient(cmdId, data.mid(57), P2P_CLOSE); + } + else if (p2pAccepted.contains(src)) + { + p2pAccepted.removeAll(src); + + dataToClient(cmdId, data.mid(57), P2P_CLOSE); + } + } + else if (p2pAccepted.contains(src)) + { + dataToClient(cmdId, src + data.mid(57), static_cast(data[56])); + } + } + } + else if (cmdId == ASYNC_CLOSE_P2P) + { + // format: [28bytes(src_sessionId)] + + if (p2pAccepted.contains(data) || p2pPending.contains(data)) + { + p2pAccepted.removeAll(data); + p2pPending.removeAll(data); + + dataToClient(ASYNC_P2P, data, P2P_CLOSE); + } + } + else if (cmdId == ASYNC_LIMITED_CAST) + { + // format: [54bytes(chIds)][1byte(typeId)][rest-of-bytes(payload)] + + if (activeUpdate && matchChs(chIds, QByteArray::fromRawData(data.data(), 54))) + { + if (data[54] == PING_PEERS) + { + // PING_PEERS is formatted exactly like PEER_INFO. it only tells this + // async command to also send PEER_INFO of this session to the session + // that requested the ping using ASYNC_TO_PEER. + + QByteArray peerId = data.mid(55, 28); + QByteArray typeId = wrInt(PEER_INFO, 8); + QByteArray info = toPEER_INFO(shared); + + dataToClient(ASYNC_TO_PEER, peerId + typeId + info, PUB_IPC); + dataToClient(cmdId, data.mid(55), PEER_INFO); + } + else + { + dataToClient(cmdId, data.mid(55), static_cast(data[54])); + } + } + } + else if ((cmdId == ASYNC_GROUP_RENAMED) || (cmdId == ASYNC_GRP_TRANS)) + { + QStringList args = parseArgs(data, -1); + QString name = getParam("-src", args); + + if (noCaseMatch(groupName, name)) + { + groupName = getParam("-dst", args); + + if (cmdId == ASYNC_GRP_TRANS) + { + hostRank = getRankForGroup(groupName); + + emit loadCommands(); + + sendLocalInfo(); + } + } + } + else if (cmdId == ASYNC_RW_MY_INFO) + { + if (noCaseMatch(userName, fromTEXT(data))) + { + sendLocalInfo(); + } + } + else if (cmdId == ASYNC_USER_RENAMED) + { + QStringList args = parseArgs(data, -1); + QString name = getParam("-old", args); + + if (noCaseMatch(userName, name)) + { + userName = getParam("-new_name", args); + + castPeerInfo(); + sendLocalInfo(); + } + } + else if (cmdId == ASYNC_DISP_RENAMED) + { + QStringList args = parseArgs(data, -1); + QString uName = getParam("-user", args); + + if (noCaseMatch(userName, uName)) + { + displayName = getParam("-name", args); + + castPeerInfo(); + sendLocalInfo(); + } + } + else if (cmdId == ASYNC_USER_GROUP_CHANGED) + { + QStringList args = parseArgs(data, -1); + QString uName = getParam("-user", args); + + if (noCaseMatch(userName, uName)) + { + groupName = getParam("-group", args); + hostRank = getRankForGroup(groupName); + + emit loadCommands(); + + sendLocalInfo(); + } + } + else if (cmdId == ASYNC_CMD_RANKS_CHANGED) + { + emit loadCommands(); + } + else if (cmdId == ASYNC_GROUP_UPDATED) + { + QStringList args = parseArgs(data, -1); + QString name = getParam("-name", args); + + if (noCaseMatch(groupName, name)) + { + hostRank = getParam("-rank", args).toUInt(); + + emit loadCommands(); + } + } + else if (cmdId == ASYNC_RESTORE_AUTH) + { + Query db(this); + + db.setType(Query::PULL, TABLE_USERS); + db.addColumn(COLUMN_USERNAME); + db.addColumn(COLUMN_GRNAME); + db.addColumn(COLUMN_DISPLAY_NAME); + db.addCondition(COLUMN_USER_ID, data); + db.exec(); + + userId = data; + userName = db.getData(COLUMN_USERNAME).toString(); + groupName = db.getData(COLUMN_GRNAME).toString(); + displayName = db.getData(COLUMN_DISPLAY_NAME).toString(); + hostRank = getRankForGroup(groupName); + + authOk(); + dataToClient(ASYNC_RDY, toTEXT("\nReady!\n\n"), TEXT); + } + else if (cmdId == ASYNC_PUBLIC_AUTH) + { + userName.clear(); + groupName.clear(); + displayName.clear(); + userId.clear(); + chIds.clear(); + chList.clear(); + + hostRank = 0; + + authOk(); + dataToClient(ASYNC_RDY, toTEXT("\nReady!\n\n"), TEXT); + } + else if (cmdId == ASYNC_ENABLE_MOD) + { + emit loadModFile(fromTEXT(data)); + } + else if (cmdId == ASYNC_DISABLE_MOD) + { + emit unloadModFile(fromTEXT(data)); + } + else if ((cmdId == ASYNC_NEW_CH_MEMBER) || (cmdId == ASYNC_INVITED_TO_CH) || + (cmdId == ASYNC_INVITE_ACCEPTED)) + { + QStringList args = parseArgs(data, -1); + QString user = getParam("-user", args); + QString chName = getParam("-ch_name", args).toLower(); + + if (noCaseMatch(user, userName)) + { + if ((cmdId == ASYNC_NEW_CH_MEMBER) || (cmdId == ASYNC_INVITE_ACCEPTED)) + { + chList.append(chName); + } + + dataToClient(cmdId, data, TEXT); + } + } + else if (cmdId == ASYNC_DEL_CH) + { + QStringList args = parseArgs(data, -1); + QString chName = getParam("-ch_name", args).toLower(); + + if (chList.contains(chName)) + { + chList.removeAll(chName); + + wrCloseCh(rwShared, getParam("-ch_id", args).toULongLong()); + dataToClient(cmdId, data, TEXT); + } + } + else if (cmdId == ASYNC_RM_CH_MEMBER) + { + QStringList args = parseArgs(data, -1); + QString uName = getParam("-user", args); + + if (noCaseMatch(userName, uName)) + { + QString chName = getParam("-ch_name", args).toLower(); + + chList.removeAll(chName); + + QByteArray peerStat; + + wrCloseCh(rwShared, getParam("-ch_id", args).toULongLong(), peerStat); + + if (!peerStat.isEmpty()) + { + dataToClient(ASYNC_LIMITED_CAST, peerStat, PUB_IPC); + } + + dataToClient(cmdId, data, TEXT); + } + } + else if (cmdId == ASYNC_MEM_LEVEL_CHANGED) + { + QStringList args = parseArgs(data, -1); + QString uName = getParam("-user", args); + QString chName = getParam("-ch_name", args).toLower(); + + if (noCaseMatch(userName, uName)) + { + QByteArray peerStat; + + wrCloseCh(rwShared, getParam("-ch_id", args).toULongLong(), peerStat); + + if (!peerStat.isEmpty()) + { + dataToClient(ASYNC_LIMITED_CAST, peerStat, PUB_IPC); + } + + dataToClient(cmdId, data, TEXT); + } + else if (chList.contains(chName)) + { + dataToClient(cmdId, data, TEXT); + } + } + else if (cmdId == ASYNC_RENAME_CH) + { + QStringList args = parseArgs(data, -1); + QString chName = getParam("-ch_name", args).toLower(); + + if (chList.contains(chName)) + { + QString newName = getParam("-new_name", args).toLower(); + + chList.removeAll(chName); + chList.append(newName); + + dataToClient(cmdId, data, TEXT); + } + } + else if (cmdId == ASYNC_CH_ACT_FLAG) + { + QStringList args = parseArgs(data, -1); + QString chName = getParam("-ch_name", args).toLower(); + + if (chList.contains(chName)) + { + activeUpdate = containsActiveCh(chIds); + } + } + else if ((cmdId == ASYNC_NEW_SUB_CH) || (cmdId == ASYNC_RENAME_SUB_CH)) + { + QStringList args = parseArgs(data, -1); + QString chName = getParam("-ch_name", args).toLower(); + + if (chList.contains(chName)) + { + dataToClient(cmdId, data, TEXT); + } + } + else if ((cmdId == ASYNC_RM_SUB_CH) || (cmdId == ASYNC_SUB_CH_LEVEL_CHG) || + (cmdId == ASYNC_RM_RDONLY) || (cmdId == ASYNC_ADD_RDONLY)) + { + QStringList args = parseArgs(data, -1); + QString chName = getParam("-ch_name", args).toLower(); + QString chId = getParam("-ch_id", args); + QString subId = getParam("-sub_id", args); + QByteArray chSub = wrInt(chId.toULongLong(), 64) + wrInt(subId.toUInt(), 8); + + int pos = chPos(chSub, chIds); + + if (pos != -1) + { + wrCloseCh(rwShared, chSub); + dataToClient(cmdId, data, TEXT); + } + else if (chList.contains(chName)) + { + dataToClient(cmdId, data, TEXT); + } + } + } + } +} + +void Session::sendStdout() +{ + if (isSlave()) + { + qDebug() << "CmdExecutor: " << slaveProc->readAllStandardOutput(); + } +} + +void Session::sendStderr() +{ + if (isSlave()) + { + qDebug() << "CmdExecutor Err: " << slaveProc->readAllStandardError(); + } +} diff --git a/src/session.h b/src/session.h new file mode 100644 index 0000000..41f3f96 --- /dev/null +++ b/src/session.h @@ -0,0 +1,129 @@ +#ifndef SOCKET_H +#define SOCKET_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 +// . + +#include "common.h" +#include "int_loader.h" +#include "make_cert.h" +#include "cmd_executor.h" + +class Session : public QObject +{ + Q_OBJECT + +private: + + QList p2pAccepted; + QList p2pPending; + QList chList; + QSharedMemory *exeDebugInfo; + QLocalServer *ipcServ; + QLocalSocket *ipcLink; + QSslSocket *tcpSocket; + QProcess *slaveProc; + RWSharedObjs *rwShared; + SharedObjs *shared; + CmdExecutor *executor; + QTime lastExeCrash; + QString peerIp; + QString pipeName; + QString userName; + QString groupName; + QString displayName; + QString appName; + ushort clientMajor; + ushort clientMinor; + ushort clientPatch; + QByteArray chIds; + QByteArray wrAbleChIds; + QByteArray sessionId; + QByteArray userId; + uint hostRank; + uint flags; + uint ipcFrameSize; + uint tcpFrameSize; + uchar ipcFrameType; + uchar tcpFrameType; + quint16 ipcFrameCmdId; + quint16 tcpFrameCmdId; + bool activeUpdate; + bool chOwnerOverride; + int exeCrashCount; + + void genSessionId(); + void castPeerInfo(); + void sendLocalInfo(); + void rdExeDebug(); + void addIpAction(const QString &action); + void modLoadCrashCheck(const QString &crashInfo); + +private slots: + + void logout(); + void newIPCLink(); + void ipcConnected(); + void ipcDisconnected(); + void dataFromClient(); + void dataFromIPC(); + void payloadDeleted(); + void sendStdout(); + void sendStderr(); + void exeStarted(); + void closeInstance(); + void authOk(); + void ipcOk(); + void newIPCTimeout(); + void exeFinished(int ret, QProcess::ExitStatus status); + void exeError(QProcess::ProcessError err); + void ipcError(QLocalSocket::LocalSocketError socketError); + void dataToClient(quint16 cmdId, const QByteArray &data, uchar typeId); + +public: + + explicit Session(QObject *parent = nullptr); + + void initAsMain(QSslSocket *tcp); + void startAsSlave(const QStringList &args); + bool isSlave(); + bool isMain(); + +public slots: + + void backendDataIn(quint16 cmdId, const QByteArray &data); + void peersDataIn(quint16 cmdId, const QByteArray &data); + void connectToPeer(const QSharedPointer &peer); + void endSession(); + void run(); + +signals: + + void dataToCommand(quint16 cmdId, const QByteArray &data, uchar dType); + void backendToPeers(quint16 cmdId, const QByteArray data); + void connectPeers(QSharedPointer peer); + void setMaxSessions(uint value); + void unloadModFile(const QString &path); + void loadModFile(const QString &path); + void delayedDirDel(const QString &path); + void ended(); + void closeExe(); + void closeServer(); + void resServer(); + void loadCommands(); +}; + +#endif // SOCKET_H diff --git a/src/shell.cpp b/src/shell.cpp new file mode 100644 index 0000000..b48cd53 --- /dev/null +++ b/src/shell.cpp @@ -0,0 +1,95 @@ +#include "shell.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 +// . + +QStringList parseEnvVariables(const QString &txtIn) +{ + QStringList ret; + QString var; + bool varChr = false; + + for (auto&& chr : txtIn) + { + +#ifdef Q_OS_WIN + + if (chr == '%') + { + if (varChr && !var.isEmpty()) + { + ret.append(var); + var.clear(); + } + + varChr = !varChr; + } + + if (varChr && (chr != '%')) + { + var.append(chr); + } + +#else + if (varChr && !chr.isLetterOrNumber()) + { + varChr = false; + + if (!var.isEmpty()) + { + ret.append(var); + var.clear(); + } + } + + if ((chr == '$') && !varChr) + { + varChr = true; + } + else if (varChr) + { + var.append(chr); + } + +#endif + + } + + return ret; +} + +QString expandEnvVariables(const QString &txtIn) +{ + QStringList vars = parseEnvVariables(txtIn); + QString ret = txtIn; + + for (auto&& var : vars) + { + +#ifdef Q_OS_WIN + + ret.replace("%" + var + "%", qEnvironmentVariable(var.toUtf8(), var)); + +#else + + ret.replace("$" + var, qEnvironmentVariable(var.toUtf8(), var)); + +#endif + + } + + return ret; +} diff --git a/src/shell.h b/src/shell.h new file mode 100644 index 0000000..90b5a10 --- /dev/null +++ b/src/shell.h @@ -0,0 +1,25 @@ +#ifndef SHELL_H +#define SHELL_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 +// . + +#include + +QStringList parseEnvVariables(const QString &txtIn); +QString expandEnvVariables(const QString &txtIn); + +#endif // SHELL_H diff --git a/src/tcp_server.cpp b/src/tcp_server.cpp new file mode 100644 index 0000000..25ec37b --- /dev/null +++ b/src/tcp_server.cpp @@ -0,0 +1,338 @@ +#include "tcp_server.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 +// . + +TCPServer::TCPServer(QObject *parent) : QTcpServer(parent) +{ + sessionCounter = new QSharedMemory(sessionCountShareKey(), this); + controlPipe = new QLocalServer(this); + dirDelTimer = new QTimer(this); + controlSocket = nullptr; + flags = 0; + + sessionCounter->create(4); + sessionCounter->attach(); + + connect(controlPipe, &QLocalServer::newConnection, this, &TCPServer::newPipeConnection); + connect(dirDelTimer, &QTimer::timeout, this, &TCPServer::delDir); +} + +void TCPServer::newPipeConnection() +{ + if (controlSocket == nullptr) + { + controlSocket = controlPipe->nextPendingConnection(); + + connect(controlSocket, &QLocalSocket::readyRead, this, &TCPServer::procPipeIn); + connect(controlSocket, &QLocalSocket::disconnected, this, &TCPServer::closedPipeConnection); + } + else + { + controlPipe->nextPendingConnection()->deleteLater(); + } +} + +void TCPServer::closedPipeConnection() +{ + controlSocket->deleteLater(); + + controlSocket = nullptr; +} + + +bool TCPServer::start() +{ + bool ret = false; + + QString contrPath = pipesPath() + "/" + QString(APP_NAME) + ".TCPServer.Control"; + + if (QFile::exists(contrPath)) + { + QFile::remove(contrPath); + } + + if (!controlPipe->listen(contrPath)) + { + QTextStream(stderr) << "" << endl << "err: Unable to open a control pipe." << endl; + QTextStream(stderr) << "err: Reason - " << controlPipe->errorString() << endl; + } + else + { + 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(); + + QString addr = db.getData(COLUMN_IPADDR).toString(); + quint16 port = static_cast(db.getData(COLUMN_PORT).toUInt()); + + if (listen(QHostAddress(addr), port)) + { + ret = true; + + flags |= ACCEPTING; + + syncModPath(); + } + else + { + QTextStream(stderr) << "" << endl << "err: TCP listen failure on address: " << addr << " port: " << port << endl; + QTextStream(stderr) << "err: Reason - " << errorString() << endl; + } + } + + return ret; +} + +void TCPServer::syncModPath() +{ + QDir dir(modDataPath()); + + dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); + + QStringList modNames = dir.entryList(); + + for (auto&& modName : modNames) + { + QString modPath = dir.absolutePath() + "/" + modName; + + if (!QFile::exists(modPath + "/main")) + { + QDir(modPath).removeRecursively(); + } + else if (!modExists(modName)) + { + Query db(this); + + db.setType(Query::PULL, TABLE_MODULES); + db.addColumn(COLUMN_MOD_NAME); + db.exec(); + + quint16 idOffs = static_cast((db.rows() + 2) * MAX_CMDS_PER_MOD); + + db.setType(Query::PUSH, TABLE_MODULES); + db.addColumn(COLUMN_MOD_NAME, modName); + db.addColumn(COLUMN_MOD_MAIN, modPath + "/main"); + db.addColumn(COLUMN_LOCKED, false); + db.addColumn(COLUMN_CMD_ID_OFFS, idOffs); + db.exec(); + } + } +} + +void TCPServer::sessionEnded() +{ + uint count = rdSessionLoad() - 1; + + wrSessionLoad(count); + + if ((count == 0) && (flags & CLOSE_ON_EMPTY)) + { + closeServer(); + } + else if ((count == 0) && (flags & RES_ON_EMPTY)) + { + resServer(); + } + else if (!(flags & (CLOSE_ON_EMPTY | RES_ON_EMPTY)) && !servOverloaded()) + { + resumeAccepting(); + } +} + +void TCPServer::closeServer() +{ + close(); + + if (rdSessionLoad() == 0) + { + cleanupDbConnection(); + + controlPipe->close(); + sessionCounter->detach(); + + QCoreApplication::instance()->quit(); + } + else + { + flags |= CLOSE_ON_EMPTY; + flags &= ~RES_ON_EMPTY; + + emit endAllSessions(); + } +} + +void TCPServer::resServer() +{ + if (rdSessionLoad() == 0) + { + controlPipe->close(); + + start(); + } + else + { + flags |= RES_ON_EMPTY; + flags &= ~CLOSE_ON_EMPTY; + + emit endAllSessions(); + } +} + +bool TCPServer::servOverloaded() +{ + Query db(this); + + db.setType(Query::PULL, TABLE_SERV_SETTINGS); + db.addColumn(COLUMN_MAXSESSIONS); + db.exec(); + + return rdSessionLoad() >= db.getData(COLUMN_MAXSESSIONS).toUInt(); +} + +void TCPServer::procPipeIn() +{ + QStringList args = parseArgs(controlSocket->readAll(), -1); + + if (args.contains("-stop", Qt::CaseInsensitive)) + { + closeServer(); + + controlSocket->write(toTEXT("\n")); + } + else if (args.contains("-status", Qt::CaseInsensitive)) + { + QString text; + QTextStream txtOut(&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(); + + txtOut << "" << endl; + txtOut << "Host Load: " << rdSessionLoad() << "/" << db.getData(COLUMN_MAXSESSIONS).toUInt() << endl; + txtOut << "Active Address: " << serverAddress().toString() << endl; + txtOut << "Active Port: " << serverPort() << endl; + txtOut << "Set Address: " << db.getData(COLUMN_IPADDR).toString() << endl; + txtOut << "Set Port: " << db.getData(COLUMN_PORT).toUInt() << endl; + txtOut << "Database Path: " << sqlDataPath() << endl; + txtOut << "Modules Install Path: " << modDataPath() << endl << endl; + + controlSocket->write(toTEXT(text)); + } +} + +bool TCPServer::inBanList(const QString &ip) +{ + Query db(this); + + db.setType(Query::PULL, TABLE_IPBANS); + db.addColumn(COLUMN_IPADDR); + db.addCondition(COLUMN_IPADDR, ip); + db.exec(); + + return db.rows(); +} + +void TCPServer::delDir() +{ + if (dirsToDel.isEmpty()) + { + dirDelTimer->stop(); + } + else + { + QString path = dirsToDel.takeFirst(); + + Query db(this); + + db.setType(Query::DEL, TABLE_MODULES); + db.addCondition(COLUMN_MOD_MAIN, path, Query::LIKE); + db.exec(); + + QDir(path).removeRecursively(); + } +} + +void TCPServer::delayedDirDel(const QString &path) +{ + dirDelTimer->setSingleShot(false); + dirDelTimer->start(5000); + + dirsToDel.append(path); +} + +void TCPServer::incomingConnection(qintptr socketDescriptor) +{ + auto *soc = new QSslSocket(nullptr); + + soc->setSocketDescriptor(socketDescriptor); + + if (servOverloaded()) + { + soc->deleteLater(); + + pauseAccepting(); + } + else + { + resumeAccepting(); + + if (inBanList(soc->peerAddress().toString())) + { + soc->deleteLater(); + } + else + { + auto *ses = new Session(nullptr); + auto *thr = new QThread(nullptr); + + connect(thr, &QThread::finished, soc, &QSslSocket::deleteLater); + connect(thr, &QThread::finished, ses, &QSslSocket::deleteLater); + connect(thr, &QThread::finished, thr, &QSslSocket::deleteLater); + + connect(ses, &Session::ended, this, &TCPServer::sessionEnded); + connect(ses, &Session::ended, thr, &QThread::quit); + connect(ses, &Session::connectPeers, this, &TCPServer::connectPeers); + connect(ses, &Session::delayedDirDel, this, &TCPServer::delayedDirDel); + connect(ses, &Session::closeServer, this, &TCPServer::closeServer); + connect(ses, &Session::resServer, this, &TCPServer::resServer); + + connect(this, &TCPServer::connectPeers, ses, &Session::connectToPeer); + connect(this, &TCPServer::endAllSessions, ses, &Session::endSession); + + serializeThread(thr); + + ses->initAsMain(soc); + ses->moveToThread(thr); + soc->moveToThread(thr); + thr->start(); + + wrSessionLoad(rdSessionLoad() + 1); + } + } +} diff --git a/src/tcp_server.h b/src/tcp_server.h new file mode 100644 index 0000000..e95b9f7 --- /dev/null +++ b/src/tcp_server.h @@ -0,0 +1,89 @@ +#ifndef TCP_SERVER_H +#define TCP_SERVER_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 +// . + +#include "db.h" +#include "common.h" +#include "session.h" +#include "make_cert.h" +#include "openssl/ssl.h" + +class TCPServer: public QTcpServer +{ + Q_OBJECT + +private: + + QSharedMemory *sessionCounter; + QLocalServer *controlPipe; + QLocalSocket *controlSocket; + QTimer *dirDelTimer; + QStringList dirsToDel; + uint flags; + + bool servOverloaded(); + bool inBanList(const QString &ip); + void syncModPath(); + void incomingConnection(qintptr socketDescriptor); + +private slots: + + void delDir(); + void procPipeIn(); + void newPipeConnection(); + void closedPipeConnection(); + void sessionEnded(); + void delayedDirDel(const QString &path); + +public slots: + + void resServer(); + void closeServer(); + +public: + + explicit TCPServer(QObject *parent = nullptr); + + bool start(); + +signals: + + void connectPeers(QSharedPointer peer); + void endAllSessions(); +}; + +//-------------------------------- + +class IPCServer: public QLocalServer +{ + Q_OBJECT + +private: + + uint flags; + + void incomingConnection(qintptr socketDescriptor); + +public: + + explicit IPCServer(QObject *parent = nullptr); + + bool start(); +}; + +#endif // TCP_SERVER_H diff --git a/src/unix_signal.cpp b/src/unix_signal.cpp new file mode 100644 index 0000000..efbc673 --- /dev/null +++ b/src/unix_signal.cpp @@ -0,0 +1,91 @@ +#include "unix_signal.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 +// . + +void setupUnixSignalHandlers() +{ + struct sigaction hup; + struct sigaction term; + + hup.sa_handler = UnixSignalHandler::hupSignalHandler; + term.sa_handler = UnixSignalHandler::termSignalHandler; + + sigemptyset(&hup.sa_mask); + sigemptyset(&term.sa_mask); + + hup.sa_flags = 0; + hup.sa_flags |= SA_RESTART; + term.sa_flags |= SA_RESTART; + + sigaction(SIGHUP, &hup, 0); + sigaction(SIGTERM, &term, 0); +} + +int *UnixSignalHandler::sighupFd = new int[2]; +int *UnixSignalHandler::sigtermFd = new int[2]; + +UnixSignalHandler::UnixSignalHandler(QObject *parent) : QObject(parent) +{ + socketpair(AF_UNIX, SOCK_STREAM, 0, sighupFd); + socketpair(AF_UNIX, SOCK_STREAM, 0, sigtermFd); + + snHup = new QSocketNotifier(sighupFd[1], QSocketNotifier::Read, this); + snTerm = new QSocketNotifier(sigtermFd[1], QSocketNotifier::Read, this); + + connect(snHup, SIGNAL(activated(int)), this, SLOT(handleSigHup())); + connect(snTerm, SIGNAL(activated(int)), this, SLOT(handleSigTerm())); +} + +void UnixSignalHandler::hupSignalHandler(int) +{ + char chr = 1; + + write(sighupFd[0], &chr, sizeof(chr)); +} + +void UnixSignalHandler::termSignalHandler(int) +{ + char chr = 1; + + write(sigtermFd[0], &chr, sizeof(chr)); +} + +void UnixSignalHandler::handleSigTerm() +{ + snTerm->setEnabled(false); + + char chr; + + read(sigtermFd[1], &chr, sizeof(chr)); + + emit closeServer(); + + snTerm->setEnabled(true); +} + +void UnixSignalHandler::handleSigHup() +{ + snHup->setEnabled(false); + + char chr; + + read(sighupFd[1], &chr, sizeof(chr)); + + emit closeServer(); + + snHup->setEnabled(true); +} diff --git a/src/unix_signal.h b/src/unix_signal.h new file mode 100644 index 0000000..2eb0b6b --- /dev/null +++ b/src/unix_signal.h @@ -0,0 +1,63 @@ +#ifndef UNIX_SIGNAL_H +#define UNIX_SIGNAL_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 +// . + +#include + +#ifdef Q_OS_LINUX + +#include +#include +#include +#include +#include +#include + +void setupUnixSignalHandlers(); + +class UnixSignalHandler : public QObject +{ + Q_OBJECT + +private: + + QSocketNotifier *snHup; + QSocketNotifier *snTerm; + + static int *sighupFd; + static int *sigtermFd; + +public: + + explicit UnixSignalHandler(QObject *parent = 0); + + static void hupSignalHandler(int); + static void termSignalHandler(int); + +public slots: + + void handleSigHup(); + void handleSigTerm(); + +signals: + + void closeServer(); +}; + +#endif // Q_OS_LINUX +#endif // UNIX_SIGNAL_H