Compare commits

..

5 Commits

Author SHA1 Message Date
Maurice ONeal
27b03885df fixed some grammar issues in the README. also moved it back to root
directory for better repository support.

fixed Qt6 support in the build.py file. (missing files)
2022-07-09 19:24:43 -04:00
Maurice ONeal
d268fcb2ee Added support for QT6
Be warned, support for QT6 remain untested. Fixed up build.py
to be forgiving if some lib files are missing. Removed the use
of QRegExp since it has been deprecated.
2021-03-07 17:43:16 -05:00
Maurice ONeal
2d1eef3700 removed linux_build.sh 2020-08-09 17:27:30 -04:00
Maurice ONeal
fb4d0bed87 Added Windows Build/Install Support
linux_build.sh is now replaced by the build.py and install.py Python
scripts. with this comes cross platform build/install support
between Windows and Linux.

as of v4.x.2.x of the MRCI protocol, the TEXT type id is now
formatted in UTF8 instead of UTF16LE unicode. this change also
affected the tcp rev and several async commands so client code had
to be ajusted accordingly.
2020-08-09 17:22:31 -04:00
Maurice ONeal
0c575ed8c9 Updated SSL Handling
- The client will now display all ssl errors that occur during the
  handshake process properly.

- New versions of MRCI host will longer support multiple certs so
  the the common name designator was removed from the client
  header. It is now just 272bytes of padding reserved for future
  expansion.

- Fixed the defined server header len from 35 to 37.

- The client will now respond to only reply 1 or 2 on the host
  header.

- HOST_CERT was removed as a data type and the host will longer
  use it.
2020-04-05 19:07:08 -04:00
26 changed files with 1059 additions and 554 deletions

7
.gitignore vendored
View File

@ -28,6 +28,10 @@ ui_*.h
*.jsc *.jsc
Makefile* Makefile*
*build-* *build-*
/build
/app_dir
/release
/debug
# Qt unit tests # Qt unit tests
target_wrapper.* target_wrapper.*
@ -47,3 +51,6 @@ compile_commands.json
# QtCreator local machine specific files for imported projects # QtCreator local machine specific files for imported projects
*creator.user* *creator.user*
# VSCode
/.vscode

View File

@ -36,9 +36,27 @@ DEFINES += QT_DEPRECATED_WARNINGS
# You can also select to disable deprecated APIs only up to a certain version of Qt. # You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
TARGET = cmdr win32 {
LIBS += -lcrypto -lssl LIBS += -llibcrypto -llibssl
TARGET = cmdr
DESTDIR_TARGET = build\\windows\\cmdr.exe
OBJECTS_DIR = build\\windows
MOC_DIR = build\\windows
RCC_DIR = build\\windows
DESTDIR = build\\windows
} else {
LIBS += -lcrypto -lssl
TARGET = build/linux/cmdr
OBJECTS_DIR = build/linux
MOC_DIR = build/linux
RCC_DIR = build/linux
}
SOURCES += src/main.cpp \ SOURCES += src/main.cpp \
src/cmd_line.cpp \ src/cmd_line.cpp \

123
README.md Normal file
View File

@ -0,0 +1,123 @@
# Cmdr #
Cmdr is a command line terminal emulator client for MRCI host using text input/output. This help administer MRCI host via local or remote TCP connections encrypted with TLS/SSL using the MRCI protocol. It also supports file transfers to/from the client using the GEN_FILE sub-protocol that MRCI understands.
### Usage ###
Cmdr have it's own terminal display so there is no command line switches to pass on start up. Instead, all commands are parsed and processed within it's own terminal interface. Any commands not seen as an internal command for the client itself is passed through to the MRCI host if connected. The client will add a numeric identifier to the end of all host command names that clash with the client commands so there is no chance a client and host command can conflict with each other.
### Versioning System ###
This application uses a 2 number versioning system: [major].[minor]
* Major - this indicates any changes that cause old configuration or library files to be incompatible.
* Minor - this indicates changes to the code that still maintains compatibility with existing config files or libraries.
Any increments to major resets minor to 0.
### The Protocol ###
This application being a MRCI client uses the MRCI protocol to transport data to and from the host using TCP in a binary data format called MRCI frames. In general, local connections with the host are not encrypted but all connections outside of the host must be encrypted using TLS/SSL (including the local network).
Before any MRCI frames can be transported, both the host and client need basic information about each other. This is done by having the client send a fixed length client header when it successfully connects to the host and the host will reply with it's own fixed length host header, described below.
### MRCI Frame Format ###
```
[type_id][cmd_id][branch_id][data_len][payload]
type_id - 1byte - 8bit little endian integer type id of the payload.
cmd_id - 2bytes - 16bit little endian integer command id.
branch_id - 2bytes - 16bit little endian integer branch id.
data_len - 3bytes - 24bit little endian integer size of the payload.
payload - variable - the actual data to be processed.
```
notes:
* A full description of the type id's can be found in the [Type_IDs.md](docs/type_ids.md) document.
* This client call commands by name but the host will assign unique command ids for it's command names and will require the client to use the command id on calling. To track this command id to command name relationship for the host commands, this client will rely on the [ASYNC_ADD_CMD](docs/async.md) and [ASYNC_RM_CMD](docs/async.md) async commands.
* The branch id is an id that can be used to run multiple instances of the same command on the host. Commands sent by a certain branch id will result in data sent back with that same branch id. For now, this client does not do branching; instead all commands sent to the host using branch id 0 only.
### Client Header (This Application) ###
```
[tag][appName][mod_instructions][padding]
tag - 4bytes - 0x4D, 0x52, 0x43, 0x49 (MRCI)
appName - 32bytes - UTF8 string (padded with 0x00)
modInst - 128bytes - UTF8 string (padded with 0x00)
padding - 128bytes - string of (0x00)
```
notes:
* **tag** is just a fixed ascii string "MRCI" that indicates to the host that the client is indeed attempting to use the MRCI protocol.
* **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 modules so the commands themselves can be made aware of what app the user is currently using.
* **modInst** is an additional set of command lines that can be passed onto to all module processes when they are initialized. This can be used by certain clients that want to instruct certain modules that might be installed in the host to do certain actions during initialization. This remains constant for as long as the session is active and cannot be changed at any point.
### Host Header ###
```
Format:
[reply][major][minor][tcp_rev][mod_rev][sesId]
reply - 1byte - 8bit little endian unsigned int
major - 2bytes - 16bit little endian unsigned int
minor - 2bytes - 16bit little endian unsigned int
tcp_rev - 2bytes - 16bit little endian unsigned int
mod_rev - 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 if SSL need to imitated or not.
* reply = 1, means SSL is not required so the client doesn't need to take any further action.
* reply = 2, means SSL is required to continue so the client needs to send a STARTLS signal.
* **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 of the host. This can be used by the host or client to uniquely identify the current session or past sessions.
### Async Commands ###
Async commands are 'virtual commands' that this application can encounter at any time while connected to the host. More information about this can be found in the [Async.md](docs/async.md) document. This application does act on some of the data carried by the async commands but not all of them.
### Build Setup ###
For Linux you need the following packages to successfully build/install:
```
qtbase5-dev
libssl-dev
libgl1-mesa-dev
libxcb1-dev
gcc
make
python3
```
For Windows support you need to have the following applications installed:
```
OpenSSL
Qt5.12 or newer
Python3
```
### Build ###
To build this project from source you just need to run the build.py and then the install.py python scripts. While running the build the script, it will try to find the Qt API installed in your machine according to the PATH env variable. If not found, it will ask you to input where it can find the Qt bin folder where the qmake executable exists or you can bypass all of this by passing the -qt_dir option on it's command line.
while running the install script, it will ask you to input 1 of 3 options:
***local machine*** - This option will install the built application onto the local machine without creating an installer.
***create installer*** - This option creates an installer that can be distributed to other machines to installation. The resulting installer is just a regular .py script file that the target machine can run if it has Python3 installed. Only Python3 needs to be installed and an internet connection is not required.
***exit*** - Cancel the installation.
-local or -installer can be passed as command line options for install.py to explicitly select one of the above options without pausing for user input.

300
build.py Normal file
View File

@ -0,0 +1,300 @@
#!/usr/bin/python3
import os
import re
import subprocess
import shutil
import platform
import sys
def get_app_target(text):
return re.search(r'(APP_TARGET) +(\"(.*?)\")', text).group(3)
def get_app_ver(text):
return re.search(r'(APP_VERSION) +(\"(.*?)\")', text).group(3)
def get_app_name(text):
return re.search(r'(APP_NAME) +(\"(.*?)\")', text).group(3)
def get_qt_path():
try:
return str(subprocess.check_output(["qtpaths", "--binaries-dir"]), 'utf-8').strip()
except:
print("A direct call to 'qtpaths' has failed so automatic retrieval of the QT bin folder is not possible.")
return input("Please enter the QT bin path (leave blank to cancel the build): ")
def get_qt_from_cli():
for arg in sys.argv:
if arg == "-qt_dir":
index = sys.argv.index(arg)
try:
return sys.argv[index + 1]
except:
return ""
return ""
def get_ver_header():
current_dir = os.path.dirname(__file__)
if current_dir == "":
return "src" + os.sep + "common.h"
else:
return current_dir + os.sep + "src" + os.sep + "common.h"
def get_nearest_subdir(path, sub_name):
dir_list = os.listdir(path)
ret = ""
for entry in dir_list:
if sub_name in entry:
ret = entry
break
return ret
def get_maker(qt_path):
ret = ""
if platform.system() == "Linux":
ret = "make"
elif platform.system() == "Windows":
path = os.path.abspath(qt_path + "\\..")
name = os.path.basename(path)
if "mingw" in name:
tools_path = os.path.abspath(qt_path + "\\..\\..\\..\\Tools")
mingw_ver = name[5:7]
mingw_tool_subdir = get_nearest_subdir(tools_path, "mingw" + mingw_ver)
mingw_tool_path = tools_path + "\\" + mingw_tool_subdir + "\\bin"
if not os.environ['PATH'].endswith(";"):
os.environ['PATH'] = os.environ['PATH'] + ";"
os.environ['PATH'] = os.environ['PATH'] + mingw_tool_path
ret = "mingw32-make"
elif "msvc" in name:
print("Warning: this script will assume you already ran the VsDevCmd.bat or vsvars32.bat script files")
print(" for Microsoft Visual Studio. Either way, a call to 'nmake' should be recognizable as ")
print(" a shell command otherwise this script will fail.\n")
ans = input("If that is the case enter 'y' to continue or any other key to cancel the build: ")
if ans == 'y' or ans == 'Y':
ret = "nmake"
else:
exit()
else:
print("The system platform is unknown. Output from platform.system() = " + platform.system())
return ret
def cd():
current_dir = os.path.dirname(__file__)
if current_dir != "":
os.chdir(current_dir)
def verbose_copy(src, dst):
print("cpy: " + src + " --> " + dst)
if os.path.isdir(src):
if os.path.exists(dst) and os.path.isdir(dst):
shutil.rmtree(dst)
try:
# ignore errors thrown by shutil.copytree()
# it's likely not actually failing to copy
# the directory but still throws errors if
# it fails to apply the same file stats as
# the source. this type of errors can be
# ignored.
shutil.copytree(src, dst)
except:
pass
elif os.path.exists(src):
shutil.copyfile(src, dst)
else:
print("wrn: " + src + " does not exists. skipping.")
def linux_build_app_dir(app_ver, app_name, app_target, qt_bin):
if not os.path.exists("app_dir/linux/platforms"):
os.makedirs("app_dir/linux/platforms")
if not os.path.exists("app_dir/linux/xcbglintegrations"):
os.makedirs("app_dir/linux/xcbglintegrations")
if not os.path.exists("app_dir/linux/lib"):
os.makedirs("app_dir/linux/lib")
if not os.path.exists("app_dir/icons"):
os.makedirs("app_dir/icons")
verbose_copy(qt_bin + "/../plugins/platforms/libqxcb.so", "app_dir/linux/platforms/libqxcb.so")
verbose_copy(qt_bin + "/../plugins/xcbglintegrations/libqxcb-egl-integration.so", "app_dir/linux/xcbglintegrations/libqxcb-egl-integration.so")
verbose_copy(qt_bin + "/../plugins/xcbglintegrations/libqxcb-glx-integration.so", "app_dir/linux/xcbglintegrations/libqxcb-glx-integration.so")
verbose_copy("build/linux/" + app_target, "app_dir/linux/" + app_target)
verbose_copy("icons", "app_dir/icons")
shutil.copyfile("build/linux/" + app_target, "/tmp/" + app_target)
# copying the executable file from the build folder to
# temp bypasses any -noexe retrictions a linux file
# system may have. there is a chance temp is also
# restricted in this way but that kind of config is
# rare. ldd will not run correctly with -noexe
# enabled.
lines = str(subprocess.check_output(["ldd", "/tmp/" + app_target]), 'utf-8').split("\n")
os.remove("/tmp/" + app_target)
for line in lines:
if " => " in line:
if ("libQt" in line) or ("libicu" in line) or ("libssl" in line) or ("libcrypto" in line) or ("libGL.so" in line) or ("libpcre16.so" in line) or ("libpcre.so" in line):
if " (0x0" in line:
start_index = line.index("> ") + 2
end_index = line.index(" (0x0")
src_file = line[start_index:end_index]
file_name = os.path.basename(src_file)
verbose_copy(src_file, "app_dir/linux/lib/" + file_name)
if "/usr/lib/x86_64-linux-gnu/qt5/bin" == qt_bin:
verbose_copy(qt_bin + "/../../libQt5DBus.so.5", "app_dir/linux/lib/libQt5DBus.so.5")
verbose_copy(qt_bin + "/../../libQt5XcbQpa.so.5", "app_dir/linux/lib/libQt5XcbQpa.so.5")
verbose_copy(qt_bin + "/../../libQt6DBus.so.6", "app_dir/linux/lib/libQt6DBus.so.6")
verbose_copy(qt_bin + "/../../libQt6XcbQpa.so.6", "app_dir/linux/lib/libQt6XcbQpa.so.6")
else:
verbose_copy(qt_bin + "/../lib/libQt5DBus.so.5", "app_dir/linux/lib/libQt5DBus.so.5")
verbose_copy(qt_bin + "/../lib/libQt5XcbQpa.so.5", "app_dir/linux/lib/libQt5XcbQpa.so.5")
verbose_copy(qt_bin + "/../lib/libQt6DBus.so.6", "app_dir/linux/lib/libQt6DBus.so.6")
verbose_copy(qt_bin + "/../lib/libQt6XcbQpa.so.6", "app_dir/linux/lib/libQt6XcbQpa.so.6")
verbose_copy(qt_bin + "/../lib/libQt6OpenGL.so.6", "app_dir/linux/lib/libQt6OpenGL.so.6")
verbose_copy("templates/linux_run_script.sh", "app_dir/linux/" + app_target + ".sh")
verbose_copy("templates/linux_uninstall.sh", "app_dir/linux/uninstall.sh")
verbose_copy("templates/linux_icon.desktop", "app_dir/linux/" + app_target + ".desktop")
complete(app_ver, app_target)
def windows_build_app_dir(app_ver, app_name, app_target, qt_bin):
if os.path.exists("release"):
os.removedirs("release")
if os.path.exists("debug"):
os.removedirs("debug")
if not os.path.exists("app_dir\\windows"):
os.makedirs("app_dir\\windows")
if not os.path.exists("app_dir\\icons"):
os.makedirs("app_dir\\icons")
verbose_copy("icons", "app_dir\\icons")
verbose_copy("build\\windows\\" + app_target + ".exe", "app_dir\\windows\\" + app_target + ".exe")
verbose_copy("templates\\windows_uninstall.bat", "app_dir\\windows\\uninstall.bat")
verbose_copy("templates\\win_icon.vbs", "app_dir\\windows\\icon.vbs")
os.chdir("app_dir\\windows\\")
result = subprocess.run([qt_bin + "\\" + "windeployqt", app_target + ".exe"])
cd()
if result.returncode == 0:
complete(app_ver, app_target)
def complete(app_ver, app_target):
if os.path.exists("Makefile"):
os.remove("Makefile")
if os.path.exists("Makefile.Debug"):
os.remove("Makefile.Debug")
if os.path.exists("Makefile.Release"):
os.remove("Makefile.Release")
if os.path.exists("object_script." + app_target + ".Debug"):
os.remove("object_script." + app_target + ".Debug")
if os.path.exists("object_script." + app_target + ".Release"):
os.remove("object_script." + app_target + ".Release")
print("Build complete for version: " + app_ver)
print("You can now run the install.py script to install onto this machine or create an installer.")
def main():
with open(get_ver_header()) as file:
text = file.read()
app_target = get_app_target(text)
app_ver = get_app_ver(text)
app_name = get_app_name(text)
qt_bin = get_qt_from_cli()
if qt_bin == "":
qt_bin = get_qt_path()
maker = get_maker(qt_bin)
if qt_bin != "":
print("app_target = " + app_target)
print("app_version = " + app_ver)
print("app_name = " + app_name)
print("qt_bin = " + qt_bin)
print("maker = " + maker + "\n")
if maker == "":
print("Could not find a valid maker/compiler on this platform, unable to continue.")
else:
cd()
if platform.system() == "Linux":
if os.path.exists("build/linux"):
shutil.rmtree("build/linux")
elif platform.system() == "Windows":
if os.path.exists("build/windows"):
shutil.rmtree("build/windows")
result = subprocess.run([qt_bin + os.sep + "qmake", "-config", "release"])
if result.returncode == 0:
result = subprocess.run([maker])
if result.returncode == 0:
if not os.path.exists("app_dir"):
os.makedirs("app_dir")
with open("app_dir" + os.sep + "info.txt", "w") as info_file:
info_file.write(app_target + "\n")
info_file.write(app_ver + "\n")
info_file.write(app_name + "\n")
if platform.system() == "Linux":
linux_build_app_dir(app_ver, app_name, app_target, qt_bin)
elif platform.system() == "Windows":
windows_build_app_dir(app_ver, app_name, app_target, qt_bin)
else:
print("The platform you are running in is not compatible with the app_dir build out procedure.")
print(" output from platform.system() = " + platform.system())
if __name__ == "__main__":
main()

View File

@ -1,120 +0,0 @@
# Cmdr #
Cmdr is a command line terminal emulator client for MRCI host using text input/output. This help administer MRCI host via local or remote TCP connections encrypted with TLS/SSL using the MRCI protocol. It also supports file transfers to/from the client using the GEN_FILE sub-protocol that MRCI understands.
### Usage ###
Cmdr have it's own terminal display so there is no command line switches to pass on start up. Instead, all commands are parsed and processed within it's own terminal interface. Any commands not seen as an internal command for the client itself is passed through to the MRCI host if connected. The client will add a numeric identifer to the end of all host command names that clash with the client commands so there is no chance a client and host command can conflict with each other.
### Versioning System ###
This application uses a 2 number versioning system: [major].[minor]
* Major - this indicates any changes that cause old configuration or library files to be incompatible.
* Minor - this indicates changes to the code that still maintains compatibility with existing config files or libraries.
Any increments to major resets minor to 0.
### The Protocol ###
This application being a MRCI client uses the MRCI protocol to transport data to and from the host using TCP in a binary data format called MRCI frames. In general, local connections with the host are not encrypted but all connections outside of the host must be encrypted using TLS/SSL (including the local network).
Before any MRCI frames can be transported, both the host and client need basic information about each other. This is done by having the client send a fixed length client header when it successfully connects to the host and the host will reply with it's own fixed length host header, described below.
### MRCI Frame Format ###
```
[type_id][cmd_id][branch_id][data_len][payload]
type_id - 1byte - 8bit little endian integer type id of the payload.
cmd_id - 2bytes - 16bit little endian integer command id.
branch_id - 2bytes - 16bit little endian integer branch id.
data_len - 3bytes - 24bit little endian integer size of the payload.
payload - variable - the actual data to be processed.
```
notes:
* A full description of the type id's can be found in the [Type_IDs.md](type_ids.md) document.
* This client call commands by name but the host will assign unique command ids for it's command names and will require the client to use the command id on calling. To track this command id to command name relationship for the host commands, this client will rely on the [ASYNC_ADD_CMD](async.md) and [ASYNC_RM_CMD](async.md) async commands.
* The branch id is an id that can be used to run muliple instances of the same command on the host. Commands sent by a certain branch id will result in data sent back with that same branch id. For now, this client does not do branching; instead all commands sent to the host using branch id 0 only.
### Client Header (This Application) ###
```
[tag][appName][coName]
tag - 4bytes - 0x4D, 0x52, 0x43, 0x49 (MRCI)
appName - 134bytes - UTF16LE string (padded with 0x00)
coName - 272bytes - UTF16LE string (padded with 0x00)
```
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.
* 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. If the client doesn't know which cert to request, it is good practice to use the address that was used to connect to the host.
### Host Header ###
```
Format:
[reply][major][minor][tcp_rev][mod_rev][sesId]
reply - 1byte - 8bit little endian unsigned int
major - 2bytes - 16bit little endian unsigned int
minor - 2bytes - 16bit little endian unsigned int
tcp_rev - 2bytes - 16bit little endian unsigned int
mod_rev - 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 is acceptable and it does not need to take any further action.
* reply = 2, means the client 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 = 4, means the host was unable to find the SSL cert associated with the common name sent by the client. The session will auto close at this point.
* **major**, **minor**, **tcp_rev**, **mod_rev** these 4 numeric values are the host version number that uses a 4 number versioning system. This can be used by the client to setup backwards compatibility or determine if it supports the host at all. If not supported, the client can simply disconnect from the host and display an error to the user.
* **sesId** is the session id. It is a unique 224bit sha3 hash that can be used by the host and client to uniquely identify the current session or past sessions.
### Async Commands ###
Async commands are 'virtual commands' that this application can encounter at any time while connected to the host. More information about this can be found in the [Async.md](Async.md) document. This application does act on some of the data carried by the async commands but not all of them.
### 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 cmdr-x.x.run in the source code folder.
Build:
```
cd /path/to/source/code
sh ./linux_build.sh
```
Install:
```
chmod +x ./cmdr-x.x.run
./cmdr-x.x.run
```

View File

@ -220,7 +220,7 @@ to_client: [type_id(25)][cmd_id(28)][branch_id(0)][size_of_payload][payload(CH_M
``` ```
```ASYNC_RM_CH_MEMBER (29)``` ```ASYNC_RM_CH_MEMBER (29)```
This async command carries a combination of the channel id and the 32byte user id of the channel member that has left or kicked from the channel member list. All sessions that are logged in as a member of the channel forwards the data to the clients as a [BYTES](type_ids.md) frame. This async command carries a combination of the channel id and the 32byte user id of the channel member that has left or kicked from the channel member list. All sessions that are logged in as a member of the channel forwards the data to the clients as a [BYTES](type_ids.md) frame. This async aommand also sent when a user declines an invite to a channel.
``` ```
from_module: [8bytes(chId)][32bytes(userId)] from_module: [8bytes(chId)][32bytes(userId)]
to_client: [type_id(14)][cmd_id(29)][branch_id(0)][size_of_payload][payload(64bit_ch_id + 256bit_user_id)] to_client: [type_id(14)][cmd_id(29)][branch_id(0)][size_of_payload][payload(64bit_ch_id + 256bit_user_id)]

View File

@ -10,7 +10,6 @@ enum TypeID : quint8
ERR = 3, ERR = 3,
PRIV_TEXT = 4, PRIV_TEXT = 4,
IDLE = 5, IDLE = 5,
HOST_CERT = 6,
FILE_INFO = 7, FILE_INFO = 7,
PEER_INFO = 8, PEER_INFO = 8,
MY_INFO = 9, MY_INFO = 9,
@ -43,7 +42,7 @@ enum TypeID : quint8
```TEXT``` ```TEXT```
This is text that can be displayed directly to the user, passed as command line arguments to be processed or used to carry text data within other data types. This is text that can be displayed directly to the user, passed as command line arguments to be processed or used to carry text data within other data types.
format: ```[UTF-16LE_string] (no BOM)``` format: ```[UTF-8_string] (no BOM)```
```GEN_FILE``` ```GEN_FILE```
This is a file transfer type id that can be used to transfer any 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. This is a file transfer type id that can be used to transfer any 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.
@ -78,7 +77,7 @@ This id can be treated exactly like TEXT except this tells the client that the c
This is similar to PRIV_TEXT expect it is not asking for private information. It is simply prompting for non-sensitive information from the user. This is similar to PRIV_TEXT expect it is not asking for private information. It is simply prompting for non-sensitive information from the user.
```BIG_TEXT``` ```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. 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 recommended to honor those line breaks.
```IDLE``` ```IDLE```
All commands started during the session returns this type id when it has finished it's task. It carries a 16bit unsigned integer indicating the result of the task that the command was running. All commands started during the session returns this type id when it has finished it's task. It carries a 16bit unsigned integer indicating the result of the task that the command was running.
@ -98,13 +97,13 @@ enum RetCode : quint16
notes: notes:
1. the custom return code can be additional data added to the end of the 16bit 1. the custom return code can be additional data added to the end of the 16bit
integer that can carry additional data about the result of the task. it can integer that can carry additional data about the result of the task. it can
be any format that the module itself decides it can should be. nothing is be any format that the module itself decides it to be. nothing is stopping
stopping modules from defining return codes beyond the value of 7 but it is modules from defining return codes beyond the value of 7 but it is advised
advised not to because this enum might be expanded in the future. not to because this enum might be expanded in the future.
``` ```
```TERM_CMD``` ```TERM_CMD```
This type id doesn't carry any actual data. It is used to tell the host to stop/terminate the command id and branch id that was used to send it. It does not actually terminate the module's process within the host, it only simply tells it to stop what it is currently doing. This will also terminate any commands in a prompt/more input state. This type id doesn't carry any actual data. It is used to tell the host to stop/terminate the command id and branch id that was used to send it. It does not actually terminate the module's process within the host, it will only tell it to stop what it is currently doing. This will also terminate any commands in a prompt state.
```KILL_CMD``` ```KILL_CMD```
This works similarly to TERM_CMD except it will also terminate the module process. The module process will have 3 seconds to shutdown gracefully before it is force killed by the host session. This works similarly to TERM_CMD except it will also terminate the module process. The module process will have 3 seconds to shutdown gracefully before it is force killed by the host session.
@ -115,9 +114,6 @@ This type id doesn't carry any actual data, instead can be used to tell the host
```RESUME_CMD``` ```RESUME_CMD```
This is the other half of YIELD_CMD that tells the host to resume the command that was running. This is the other half of YIELD_CMD that tells the host to resume the command that was running.
```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.
```HOST_VER``` ```HOST_VER```
This data structure carries 4 numeric values that represent the host version as described in section [1.3](protocol.md). This data structure carries 4 numeric values that represent the host version as described in section [1.3](protocol.md).
@ -147,12 +143,12 @@ This is a data structure that carries information about a file system object (fi
2. bytes[1-8] - creation time in msec since Epoch UTC (64bit 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) 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) 4. bytes[17-24] - file size (64bit little endian uint)
5. bytes[25-n] - file name (UTF16-LE string, 16bit terminated) 5. bytes[25-n] - file name (UTF8 string, NULL terminated)
6. bytes[n-n] - symmlink target if it is a symmlink (UTF16-LE string, 16bit terminated) 6. bytes[n-n] - symmlink target if it is a symmlink (UTF8 string, NULL terminated)
notes: notes:
1. 16bit terminated UTF-16LE strings are basically 1. NULL terminated UTF-8 strings are basically tailed
terminated by 2 bytes of 0x00. by a single 0x00 byte to indicate the end-of-string.
2. the symmlink target is empty if not a symmlink but 2. the symmlink target is empty if not a symmlink but
the terminator should still be present. the terminator should still be present.
@ -173,11 +169,11 @@ This carry some user account and session information about a peer client connect
``` ```
format: format:
1. bytes[0-27] 28bytes - session id (224bit hash) 1. bytes[0-27] 28bytes - session id (224bit hash)
2. bytes[28-59] 32bytes - user id (256bit hash) 2. bytes[28-59] 32bytes - user id (256bit hash)
3. bytes[60-107] 48bytes - user name (TEXT - padded with 0x00) 3. bytes[60-83] 24bytes - user name (TEXT - padded with 0x00)
4. bytes[108-235] 128bytes - app name (TEXT - padded with 0x00) 4. bytes[84-115] 32bytes - app name (TEXT - padded with 0x00)
5. bytes[236-299] 64bytes - disp name (TEXT - padded with 0x00) 5. bytes[116-139] 24bytes - disp name (TEXT - padded with 0x00)
notes: notes:
1. the session id is unique to the peer's session connection only. it 1. the session id is unique to the peer's session connection only. it
@ -187,23 +183,23 @@ This carry some user account and session information about a peer client connect
even when the user name changes and across all clients logged into even when the user name changes and across all clients logged into
the same account. the same account.
3. the display name is the preffered display name of the peer. clients 3. the display name is the preferred display name of the peer. clients
are encouraged to use this rather than the user name when displaying 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 peer info to the end user. if empty, then it is ok to fall back to
name. displaying the user name.
``` ```
```PING_PEERS``` ```PING_PEERS```
This is formatted extactly as PEER_INFO except it is used by the ASYNC_LIMITED_CAST [async](async.md) command to tell all peer sessions that receive it to send a PEER_INFO frame about the local session to their own clients and return PEER_INFO frames about themselves to the local session. This is formatted extactly as PEER_INFO except it is used by the ASYNC_LIMITED_CAST [async](async.md) command to tell all peer sessions that receive it to forward the same frame to their own clients and return PEER_INFO frames about themselves to the session that sent the PING_PEERS.
```MY_INFO``` ```MY_INFO```
This contains all of the information found in ```PEER_INFO``` for the local session but also includes the following: This contains all of the information found in ```PEER_INFO``` for the local session but also includes the following:
``` ```
format: format:
1. bytes[300-427] 128bytes - email (TEXT - padded with 0x00) 1. bytes[140-203] 64bytes - email (TEXT - padded with 0x00)
2. bytes[428-431] 4bytes - host rank (32bit unsigned int) 2. bytes[204-207] 4bytes - host rank (32bit unsigned int)
3. bytes[432] 1byte - is email confirmed? (0x00 false, 0x01 true) 3. bytes[208] 1byte - is email confirmed? (0x00 false, 0x01 true)
``` ```
```NEW_CMD``` ```NEW_CMD```
@ -211,13 +207,13 @@ This contains information about a new command that was added to the current sess
``` ```
format: format:
1. bytes[0-1] 2bytes - 16bit LE unsigned int (command id) 1. bytes[0-1] 2bytes - 16bit LE unsigned int (command id)
2. bytes[2] 1byte - 8bit LE unsigned int (genfile type) 2. bytes[2] 1byte - 8bit LE unsigned int (genfile type)
3. bytes[3-130] 128bytes - command name (TEXT - padded with 0x00) 3. bytes[3-66] 64bytes - command name (TEXT - padded with 0x00)
4. bytes[131-258] 128bytes - library name (TEXT - padded with 0x00) 4. bytes[67-130] 64bytes - library name (TEXT - padded with 0x00)
5. bytes[259-n] variable - short text (16bit null terminated) 5. bytes[131-n] variable - short text (null terminated)
6. bytes[n-n] variable - io text (16bit null terminated) 6. bytes[n-n] variable - io text (null terminated)
7. bytes[n-n] variable - long text (16bit null terminated) 7. bytes[n-n] variable - long text (null terminated)
notes: notes:
1. the genfile type is numerical value of 2, 3 or 0. a value of 2 1. the genfile type is numerical value of 2, 3 or 0. a value of 2
@ -292,13 +288,13 @@ This contains public information about a channel member.
2. bytes[8-39] 32bytes - user id (256bit hash) 2. bytes[8-39] 32bytes - user id (256bit hash)
3. bytes[40] 1byte - is invite? (0x00=false, 0x01=true) 3. bytes[40] 1byte - is invite? (0x00=false, 0x01=true)
4. bytes[41] 1byte - member's channel privilege level (8bit unsigned int) 4. bytes[41] 1byte - member's channel privilege level (8bit unsigned int)
5. bytes[42-n] variable - user name (TEXT - 16bit null terminated) 5. bytes[42-n] variable - user name (TEXT - null terminated)
6. bytes[n-n] variable - display name (TEXT - 16bit null terminated) 6. bytes[n-n] variable - display name (TEXT - null terminated)
7. bytes[n-n] variable - channel name (TEXT - 16bit null terminated) 7. bytes[n-n] variable - channel name (TEXT - null terminated)
notes: notes:
1. a 16bit null terminated TEXT formatted string ended with 2 bytes of 1. a null terminated TEXT formatted string ends with a 0x00 to
(0x00) to indicate the end of the string data. indicate the end of the string.
2. the member's privilege level can be any of the values discribed in 2. the member's privilege level can be any of the values discribed in
section [4.3](host_features.md). section [4.3](host_features.md).

BIN
icons/win_icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

372
install.py Normal file
View File

@ -0,0 +1,372 @@
#!/usr/bin/python3
import os
import subprocess
import shutil
import platform
import sys
import zipfile
import binascii
import tempfile
def cd():
current_dir = os.path.dirname(__file__)
if current_dir != "":
os.chdir(current_dir)
def get_default_install_dir(app_target, app_name):
if platform.system() == "Linux":
return "/opt/" + app_target
else:
return os.environ['ProgramFiles'] + "\\" + app_name
def get_default_installer_path(app_ver, app_name):
return os.path.expanduser("~") + os.sep + app_name + "-" + app_ver + ".py"
def get_install_dir(app_target, app_name):
path = get_default_install_dir(app_target, app_name)
print("The default install directory is: " + path)
while(True):
ans = input("Do you want to change it? (y/n): ")
if ans == "y" or ans == "Y":
path = input("Enter a new install directory (leave blank to go back to the default): ")
path = os.path.normpath(path)
break
elif ans == "n" or ans == "N":
break
if path == "":
return get_default_install_dir(app_target, app_name)
else:
return path
def get_installer_path(app_ver, app_name):
path = get_default_installer_path(app_ver, app_name)
print("The built .py installer will placed here: " + path)
while(True):
ans = input("Do you want to change the path? (y/n): ")
if ans == "y" or ans == "Y":
path = input("Enter a new path (leave blank to go back to the default): ")
path = os.path.normpath(path)
break
elif ans == "n" or ans == "N":
break
if path == "":
return get_default_installer_path(app_ver, app_name)
else:
return path
def make_install_dir(path):
try:
if not os.path.exists(path):
os.makedirs(path)
return True
except:
print("Failed to create the install directory, please make sure you are runnning this script with admin rights.")
return False
def replace_bin(binary, old_bin, new_bin, offs):
while(True):
try:
index = binary.index(old_bin, offs)
binary = binary[:index] + new_bin + binary[index + len(old_bin):]
except ValueError:
break
return binary
def bin_sub_copy_file(src, dst, old_bin, new_bin, offs):
binary = bytearray()
with open(src, "rb") as rd_file:
binary = rd_file.read()
binary = replace_bin(binary, old_bin, new_bin, offs)
with open(dst, "wb") as wr_file:
wr_file.write(binary)
def text_sub_copy_file(src, dst, old_text, new_text, offs):
bin_sub_copy_file(src, dst, old_text.encode("utf-8"), new_text.encode("utf-8"), offs)
def text_template_deploy(src, dst, install_dir, app_name, app_target):
print("dep: " + dst)
text_sub_copy_file(src, dst, "$install_dir", install_dir, 0)
text_sub_copy_file(dst, dst, "$app_name", app_name, 0)
text_sub_copy_file(dst, dst, "$app_target", app_target, 0)
def verbose_copy(src, dst):
print("cpy: " + src + " --> " + dst)
if os.path.isdir(src):
files = os.listdir(src)
if not os.path.exists(dst):
os.makedirs(dst)
for file in files:
tree_src = src + os.path.sep + file
tree_dst = dst + os.path.sep + file
if os.path.isdir(tree_src):
if not os.path.exists(tree_dst):
os.makedirs(tree_dst)
verbose_copy(tree_src, tree_dst)
else:
shutil.copyfile(src, dst)
def verbose_create_symmlink(src, dst):
print("lnk: " + src + " --> " + dst)
if os.path.exists(dst):
os.remove(dst)
os.symlink(src, dst)
def local_install(app_target, app_name):
if platform.system() == "Linux":
if not os.path.exists("app_dir/linux"):
print("An app_dir for the Linux platform could not be found.")
else:
install_dir = get_install_dir(app_target, app_name)
if os.path.exists(install_dir + "/uninstall.sh"):
subprocess.run([install_dir + "/uninstall.sh"])
if make_install_dir(install_dir):
if not os.path.exists("/var/opt/" + app_target):
os.makedirs("/var/opt/" + app_target)
text_template_deploy("app_dir/linux/" + app_target + ".sh", install_dir + "/" + app_target + ".sh", install_dir, app_name, app_target)
text_template_deploy("app_dir/linux/uninstall.sh", install_dir + "/uninstall.sh", install_dir, app_name, app_target)
text_template_deploy("app_dir/linux/" + app_target + ".desktop", "/usr/share/applications/" + app_target + ".desktop", install_dir, app_name, app_target)
for image_res in ["8x8", "16x16", "22x22", "24x24", "32x32", "36x36", "42x42", "48x48", "64x64", "192x192", "256x256", "512x512"]:
src = "app_dir/icons/" + image_res + ".png"
dst = "/usr/share/icons/hicolor/" + image_res
if os.path.exists(dst):
verbose_copy(src, dst + "/apps/" + app_target + ".png")
subprocess.run(["chmod", "644", dst + "/apps/" + app_target + ".png"])
verbose_copy("app_dir/icons/scalable.svg", "/usr/share/icons/hicolor/scalable/apps/" + app_target + ".svg")
verbose_copy("app_dir/linux/" + app_target, install_dir + "/" + app_target)
verbose_copy("app_dir/linux/lib", install_dir + "/lib")
verbose_copy("app_dir/linux/platforms", install_dir + "/platforms")
verbose_copy("app_dir/linux/xcbglintegrations", install_dir + "/xcbglintegrations")
verbose_create_symmlink(install_dir + "/" + app_target + ".sh", "/usr/bin/" + app_target)
subprocess.run(["chmod", "644", "/usr/share/icons/hicolor/scalable/apps/" + app_target + ".svg"])
subprocess.run(["chmod", "755", install_dir + "/" + app_target + ".sh"])
subprocess.run(["chmod", "755", install_dir + "/" + app_target])
subprocess.run(["chmod", "755", install_dir + "/uninstall.sh"])
print("Installation finished. If you ever need to uninstall this application, run this command with root rights:")
print(" sh " + install_dir + "/uninstall.sh\n")
elif platform.system() == "Windows":
if not os.path.exists("app_dir\\windows"):
print("An app_dir for the Windows platform could not be found.")
else:
install_dir = get_install_dir(app_target, app_name)
if os.path.exists(install_dir + "\\uninstall.bat"):
subprocess.run([install_dir + "\\uninstall.bat"])
if os.path.exists(install_dir):
# this block is here to make sure the install_dir is deleted
# if/when the uninstall.bat fails to do so. in my test machine,
# the .bat script will delete install_dir if run directly but
# not when called through subprocess.run() for some reason.
shutil.rmtree(install_dir)
if make_install_dir(install_dir):
verbose_copy("app_dir\\windows", install_dir)
verbose_copy("app_dir\\icons\\win_icon.ico", install_dir + "\\icon.ico")
text_template_deploy("app_dir\\windows\\uninstall.bat", install_dir + "\\uninstall.bat", install_dir, app_name, app_target)
text_template_deploy("app_dir\\windows\\icon.vbs", install_dir + "\\icon.vbs", install_dir, app_name, app_target)
text_sub_copy_file(install_dir + "\\icon.vbs", install_dir + "\\icon.vbs", "%SystemDrive%", os.environ['SystemDrive'], 0)
os.system("\"" + install_dir + "\\icon.vbs\"")
print("Installation finished. If you ever need to uninstall this application, run this batch file with admin rights:")
print(" " + install_dir + "\\uninstall.bat\n")
else:
print("The platform you are running in is not compatible.")
print(" output from platform.system() = " + platform.system())
def dir_tree(path):
ret = []
if os.path.isdir(path):
for entry in os.listdir(path):
full_path = os.path.join(path, entry)
if os.path.isdir(full_path):
for sub_dir_file in dir_tree(full_path):
ret.append(sub_dir_file)
else:
ret.append(full_path)
return ret
def to_hex(data):
return str(binascii.hexlify(data))[2:-1]
def from_hex(text_line):
return binascii.unhexlify(text_line)
def make_install(app_ver, app_name):
path = get_installer_path(app_ver, app_name)
with zipfile.ZipFile("app_dir.zip", "w", compression=zipfile.ZIP_DEFLATED) as zip_file:
print("Compressing app_dir --")
for file in dir_tree("app_dir"):
print("adding file: " + file)
zip_file.write(file)
text_sub_copy_file(__file__, path, "main(is_sfx=False)", "main(is_sfx=True)\n\n\n", 7700)
with open(path, "a") as dst_file, open("app_dir.zip", "rb") as src_file:
print("Packing the compressed app_dir into the sfx script file --")
dst_file.write("# APP_DIR\n")
stat = os.stat("app_dir.zip")
while(True):
buffer = src_file.read(4000000)
if len(buffer) != 0:
dst_file.write("# " + to_hex(buffer) + "\n")
print(str(src_file.tell()) + "/" + str(stat.st_size))
if len(buffer) < 4000000:
break
os.remove("app_dir.zip")
print("Finished.")
def sfx():
abs_sfx_path = os.path.abspath(__file__)
mark_found = False
os.chdir(tempfile.gettempdir())
with open(abs_sfx_path) as packed_file, open("app_dir.zip", "wb") as zip_file:
stat = os.stat(abs_sfx_path)
print("Unpacking the app_dir compressed file from the sfx script.")
while(True):
line = packed_file.readline()
if not line:
break
elif mark_found:
zip_file.write(from_hex(line[2:-1]))
print(str(packed_file.tell()) + "/" + str(stat.st_size))
else:
if line == "# APP_DIR\n":
mark_found = True
print("Done.")
if not mark_found:
print("The app_dir mark was not found, unable to continue.")
else:
with zipfile.ZipFile("app_dir.zip", "r", compression=zipfile.ZIP_DEFLATED) as zip_file:
print("De-compressing app_dir --")
zip_file.extractall()
print("Preparing for installation.")
os.remove("app_dir.zip")
with open("app_dir" + os.sep + "info.txt") as info_file:
info = info_file.read().split("\n")
local_install(info[0], info[2])
shutil.rmtree("app_dir")
def main(is_sfx):
cd()
app_target = ""
app_ver = ""
app_name = ""
if not is_sfx:
with open("app_dir" + os.sep + "info.txt") as info_file:
info = info_file.read().split("\n")
app_target = info[0]
app_ver = info[1]
app_name = info[2]
if is_sfx:
sfx()
elif "-local" in sys.argv:
local_install(app_target, app_name)
elif "-installer" in sys.argv:
make_install(app_ver, app_name)
else:
print("Do you want to install onto this machine or create an installer?")
print("[1] local machine")
print("[2] create installer")
print("[3] exit")
while(True):
opt = input("select an option: ")
if opt == "1":
local_install(app_target, app_name)
break
elif opt == "2":
make_install(app_ver, app_name)
break
elif opt == "3":
break
if __name__ == "__main__":
main(is_sfx=False)

View File

@ -1,220 +0,0 @@
#!/bin/sh
qt_dir="$1"
installer_file="$2"
src_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
bin_name="cmdr"
app_version="3.0"
app_name="Cmdr"
install_dir="/opt/$bin_name"
bin_dir="/usr/bin"
tmp_dir="$HOME/.cache/cmdr_build"
if [ ! -d "$qt_dir" ]; then
echo "a valid path to Qt was not provided, falling back to the default: /usr/lib/x86_64-linux-gnu/qt5/bin"
qt_dir="/usr/lib/x86_64-linux-gnu/qt5/bin"
else
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 -rf $tmp_dir
fi
if [ $? -eq 0 -a -d "$qt_dir" ]; then
mkdir -vp $tmp_dir
cp -r $src_dir/. $tmp_dir
cd $tmp_dir
qmake -config release
if [ $? -eq 0 ]; then
make
if [ $? -eq 0 ]; then
mkdir -v ./build
mkdir -v ./build/lib
mkdir -v ./build/platforms
ldd ./$bin_name | grep "libQt" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./build/lib
ldd ./$bin_name | grep "libicu" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./build/lib
ldd ./$bin_name | grep "libssl" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./build/lib
ldd ./$bin_name | grep "libcrypto" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./build/lib
ldd ./$bin_name | grep "libGL.so" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./build/lib
ldd ./$bin_name | grep "libpcre16.so" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./build/lib
ldd ./$bin_name | grep "libpcre.so" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./build/lib
mv -v ./$bin_name ./build/$bin_name
cp -v $qt_dir/../plugins/platforms/libqxcb.so ./build/platforms/libqxcb.so
cp -rfv ./icons ./build
cp -rfv $qt_dir/../plugins/xcbglintegrations ./build
if [ "$qt_dir" = "/usr/lib/x86_64-linux-gnu/qt5/bin" ]; then
cp -fv $qt_dir/../../libQt5DBus.so.5 ./build/lib/libQt5DBus.so.5
cp -fv $qt_dir/../../libQt5XcbQpa.so.5 ./build/lib/libQt5XcbQpa.so.5
else
cp -fv $qt_dir/../lib/libQt5DBus.so.5 ./build/lib/libQt5DBus.so.5
cp -fv $qt_dir/../lib/libQt5XcbQpa.so.5 ./build/lib/libQt5XcbQpa.so.5
fi
startup_script="./build/$bin_name.sh"
setup_script="./build/setup.sh"
uninstall_script="./build/uninstall.sh"
desktop_file="./build/$bin_name.desktop"
echo "#!/bin/sh" > $startup_script
echo "export QTDIR=$install_dir" >> $startup_script
echo "export QT_PLUGIN_PATH=$install_dir" >> $startup_script
echo "export LD_LIBRARY_PATH=\"$install_dir/lib:\$LD_LIBRARY_PATH\"" >> $startup_script
echo "$install_dir/$bin_name" >> $startup_script
echo "[Desktop Entry]" > $desktop_file
echo "Type=Application" >> $desktop_file
echo "Exec=$bin_dir/$bin_name" >> $desktop_file
echo "Name=$app_name" >> $desktop_file
echo "GenericName=Terminal emulator for MRCI host." >> $desktop_file
echo "Icon=$bin_name" >> $desktop_file
echo "StartupWMClass=$bin_name" >> $desktop_file
echo "Terminal=false" >> $desktop_file
echo "Categories=Network;Terminal;MRCI;" >> $desktop_file
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 -rv ./lib $install_dir" >> $setup_script
echo "sudo cp -rv ./platforms $install_dir" >> $setup_script
echo "sudo cp -rv ./xcbglintegrations $install_dir" >> $setup_script
echo "sudo cp -v ./$bin_name $install_dir" >> $setup_script
echo "sudo cp -v ./$bin_name.sh $install_dir" >> $setup_script
echo "sudo cp -v ./uninstall.sh $install_dir" >> $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/platforms" >> $setup_script
echo "sudo chmod -R 755 $install_dir/xcbglintegrations" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/8x8/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/16x16/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/22x22/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/24x24/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/32x32/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/36x36/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/42x42/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/48x48/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/64x64/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/72x72/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/96x96/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/128x128/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/192x192/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/256x256/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/512x512/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/icons/hicolor/scalable/apps" >> $setup_script
echo "sudo mkdir -p /usr/share/applications" >> $setup_script
echo "sudo cp -v ./icons/8x8.png /usr/share/icons/hicolor/8x8/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/16x16.png /usr/share/icons/hicolor/16x16/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/22x22.png /usr/share/icons/hicolor/22x22/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/24x24.png /usr/share/icons/hicolor/24x24/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/32x32.png /usr/share/icons/hicolor/32x32/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/36x36.png /usr/share/icons/hicolor/36x36/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/42x42.png /usr/share/icons/hicolor/42x42/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/48x48.png /usr/share/icons/hicolor/48x48/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/64x64.png /usr/share/icons/hicolor/64x64/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/72x72.png /usr/share/icons/hicolor/72x72/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/96x96.png /usr/share/icons/hicolor/96x96/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/128x128.png /usr/share/icons/hicolor/128x128/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/192x192.png /usr/share/icons/hicolor/192x192/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/256x256.png /usr/share/icons/hicolor/256x256/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/512x512.png /usr/share/icons/hicolor/512x512/apps/$bin_name.png" >> $setup_script
echo "sudo cp -v ./icons/scalable.svg /usr/share/icons/hicolor/scalable/apps/$bin_name.svg" >> $setup_script
echo "sudo cp -v ./$bin_name.desktop /usr/share/applications/$bin_name.desktop" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/8x8/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/16x16/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/22x22/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/24x24/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/32x32/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/36x36/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/42x42/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/48x48/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/64x64/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/72x72/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/96x96/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/128x128/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/192x192/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/256x256/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/512x512/apps/$bin_name.png" >> $setup_script
echo "sudo chmod 644 /usr/share/icons/hicolor/scalable/apps/$bin_name.svg" >> $setup_script
echo "sudo chmod 644 /usr/share/applications/$bin_name.desktop" >> $setup_script
echo "sudo ln -sfv $install_dir/$bin_name.sh $bin_dir/$bin_name" >> $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 "#!/bin/sh" > $uninstall_script
echo "sudo rm -v $bin_dir/$bin_name" >> $uninstall_script
echo "sudo rm -rv $install_dir" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/8x8/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/16x16/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/22x22/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/24x24/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/32x32/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/36x36/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/42x42/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/48x48/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/64x64/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/72x72/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/96x96/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/128x128/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/192x192/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/256x256/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/512x512/apps/$bin_name.png" >> $uninstall_script
echo "sudo rm -v /usr/share/icons/hicolor/scalable/apps/$bin_name.svg" >> $uninstall_script
echo "sudo rm -v /usr/share/applications/$bin_name.desktop" >> $uninstall_script
echo "echo Finished." >> $uninstall_script
chmod +x $setup_script
makeself ./build $installer_file "$app_name Installation" ./setup.sh
fi
fi
else
echo "err: A valid Qt dir could not be found."
fi
if [ -d "$tmp_dir" ]; then
rm -rf $tmp_dir
fi

View File

@ -164,7 +164,7 @@ void CmdLine::toHost(const QString &cmdName, const QString &args)
{ {
if (flags & HOST_HOOK) if (flags & HOST_HOOK)
{ {
emit dataToHookedHost(toTEXT(args), TEXT); emit dataToHookedHost(args.toUtf8(), TEXT);
} }
else if (!cmdName.isEmpty()) else if (!cmdName.isEmpty())
{ {
@ -178,11 +178,11 @@ void CmdLine::toHost(const QString &cmdName, const QString &args)
{ {
emit setHostCmdId(cmdId); emit setHostCmdId(cmdId);
emit setGenfileType(Shared::genfileTypes->value(cmdId)); emit setGenfileType(Shared::genfileTypes->value(cmdId));
emit dataToGenFile(toTEXT(args)); emit dataToGenFile(args.toUtf8());
} }
else else
{ {
emit dataToHost(cmdId, toTEXT(args), TEXT); emit dataToHost(cmdId, args.toUtf8(), TEXT);
} }
} }
} }
@ -225,7 +225,7 @@ void CmdLine::procCmdLine(const QString &line)
if (flags & GEN_HOOK) if (flags & GEN_HOOK)
{ {
emit dataToGenFile(toTEXT(argsLine)); emit dataToGenFile(argsLine.toUtf8());
} }
else if (Shared::clientCmds->contains(cmdName)) else if (Shared::clientCmds->contains(cmdName))
{ {

View File

@ -111,12 +111,12 @@ void SaveBookmark::dataIn(const QString &argsLine)
{ {
if (activeHook()) if (activeHook())
{ {
if (QRegExp("y", Qt::CaseInsensitive).exactMatch(argsLine)) if ("y" == argsLine.toLower())
{ {
run(baseName, baseArgs); run(baseName, baseArgs);
term(); term();
} }
else if (QRegExp("n", Qt::CaseInsensitive).exactMatch(argsLine)) else if ("n" == argsLine.toLower())
{ {
term(); term();
} }

View File

@ -130,10 +130,6 @@ void Connect::dataIn(const QString &argsLine)
{ {
cacheTxt(ERR, "err: Host address is empty.\n"); cacheTxt(ERR, "err: Host address is empty.\n");
} }
else if (QHostAddress(*Shared::hostAddress).isNull())
{
cacheTxt(ERR, "err: '" + *Shared::hostAddress + "' is not a valid address.\n");
}
else else
{ {
emit connectToHost(); emit connectToHost();

View File

@ -18,13 +18,27 @@
HostDoc::HostDoc(const QByteArray &import, QObject *parent) : Command(parent) HostDoc::HostDoc(const QByteArray &import, QObject *parent) : Command(parent)
{ {
// ```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 - 8bit LE unsigned int (genfile type)
// 3. bytes[3-66] 64bytes - command name (TEXT - padded with 0x00)
// 4. bytes[67-130] 64bytes - library name (TEXT - padded with 0x00)
// 5. bytes[131-n] variable - short text (null terminated)
// 6. bytes[n-n] variable - io text (null terminated)
// 7. bytes[n-n] variable - long text (null terminated)
valid = false; valid = false;
if (import.size() >= 259) if (import.size() >= 131) // within safe import size
{ {
cmdId = static_cast<quint16>(rdInt(import.mid(0, 2))); cmdId = static_cast<quint16>(rdInt(import.mid(0, 2))); // 1.
auto cmdName = QString(import.mid(3, 64)).toLower(); // 3.
QString cmdName = fromTEXT(import.mid(3, 128)).toLower();
QString num; QString num;
for (int i = 1; Shared::clientCmds->contains(cmdName + num); ++i) for (int i = 1; Shared::clientCmds->contains(cmdName + num); ++i)
@ -34,18 +48,18 @@ HostDoc::HostDoc(const QByteArray &import, QObject *parent) : Command(parent)
setObjectName(cmdName + num); setObjectName(cmdName + num);
if ((import[2] == GEN_UPLOAD) || (import[2] == GEN_DOWNLOAD)) if ((import[2] == GEN_UPLOAD) || (import[2] == GEN_DOWNLOAD)) // 2.
{ {
Shared::genfileCmds->insert(cmdId, objectName()); Shared::genfileCmds->insert(cmdId, objectName());
Shared::genfileTypes->insert(cmdId, static_cast<quint8>(import[2])); Shared::genfileTypes->insert(cmdId, static_cast<quint8>(import[2]));
} }
quint32 offs = 259; quint32 offs = 131; // 5.
libTxt = fromTEXT(import.mid(131, 128)); libTxt = QString(import.mid(67, 64)); // 4.
shortTxt = readNullTermText(import, &offs); shortTxt = readNullTermText(import, &offs);
ioTxt = readNullTermText(import, &offs); ioTxt = readNullTermText(import, &offs); // 6.
longTxt = readNullTermText(import, &offs); longTxt = readNullTermText(import, &offs); // 7.
valid = true; valid = true;
Shared::hostCmds->insert(cmdId, objectName()); Shared::hostCmds->insert(cmdId, objectName());
@ -55,29 +69,23 @@ HostDoc::HostDoc(const QByteArray &import, QObject *parent) : Command(parent)
QString HostDoc::readNullTermText(const QByteArray &data, quint32 *offs) QString HostDoc::readNullTermText(const QByteArray &data, quint32 *offs)
{ {
static const QByteArray null16(2, 0x00);
QString ret; QString ret;
quint32 len = static_cast<quint32>(data.size());
for (; *offs < len; *offs += 2) auto len = static_cast<quint32>(data.size());
for (; *offs < len; *offs += 1)
{ {
if ((*offs + 2) < len) if (data[*offs] == 0x00)
{ {
QByteArray chr = QByteArray::fromRawData(data.data() + *offs, 2); break;
}
if (chr == null16) else
{ {
break; ret.append(data[*offs]);
}
else
{
ret.append(fromTEXT(chr));
}
} }
} }
*offs += 2; *offs += 1;
return ret; return ret;
} }

View File

@ -45,9 +45,9 @@ void About::dispInfo(Command *cmdObj)
QTextStream txtOut(&txt); QTextStream txtOut(&txt);
wordWrap("i/o: ", txtOut, cmdObj->ioText(), Shared::mainWidget); wordWrap("i/o: ", txtOut, cmdObj->ioText(), Shared::mainWidget);
txtOut << "" << endl; txtOut << "" << Qt::endl;
wordWrap("library: ", txtOut, cmdObj->libText(), Shared::mainWidget); wordWrap("library: ", txtOut, cmdObj->libText(), Shared::mainWidget);
txtOut << "" << endl; txtOut << "" << Qt::endl;
wordWrap("usage: ", txtOut, cmdObj->longText(), Shared::mainWidget); wordWrap("usage: ", txtOut, cmdObj->longText(), Shared::mainWidget);
cacheTxt(TEXT, txt); cacheTxt(TEXT, txt);
@ -115,12 +115,12 @@ void About::dataIn(const QString &argsLine)
QString txt; QString txt;
QTextStream txtOut(&txt); QTextStream txtOut(&txt);
txtOut << libText() << endl << endl; txtOut << libText() << Qt::endl << Qt::endl;
txtOut << "Based on QT " << QT_VERSION_STR << " " << 8 * QT_POINTER_SIZE << "bit" << endl << endl; txtOut << "Based on QT " << QT_VERSION_STR << " " << 8 * QT_POINTER_SIZE << "bit" << Qt::endl << Qt::endl;
txtOut << "The program is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE" << endl; txtOut << "The program is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE" << Qt::endl;
txtOut << "WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE." << endl << endl; txtOut << "WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE." << Qt::endl << Qt::endl;
txtOut << "run: 'ls_cmds' to see all available commands." << endl << endl; txtOut << "run: 'ls_cmds' to see all available commands." << Qt::endl << Qt::endl;
txtOut << "for more detailed information about a command run: 'about <command>'" << endl << endl; txtOut << "for more detailed information about a command run: 'about <command>'" << Qt::endl << Qt::endl;
cacheTxt(TEXT, txt); cacheTxt(TEXT, txt);
} }
@ -145,8 +145,8 @@ void ListCmds::ls(QHash<QString, Command *> *cmdObjs, QTextStream &txtOut, const
cmdNames.sort(Qt::CaseInsensitive); cmdNames.sort(Qt::CaseInsensitive);
txtOut << endl; txtOut << Qt::endl;
txtOut << title << endl << endl; txtOut << title << Qt::endl << Qt::endl;
for (int i = 0; i < cmdNames.size(); ++i) for (int i = 0; i < cmdNames.size(); ++i)
{ {

View File

@ -34,20 +34,20 @@ void Status::dataIn(const QString &argsLine)
QString txt; QString txt;
QTextStream txtOut(&txt); QTextStream txtOut(&txt);
txtOut << "--Local data" << endl << endl; txtOut << "--Local data" << Qt::endl << Qt::endl;
txtOut << " Client version : " << QCoreApplication::applicationVersion() << endl; txtOut << " Client version : " << QCoreApplication::applicationVersion() << Qt::endl;
txtOut << " Connected? : " << boolText(*Shared::connectedToHost) << endl; txtOut << " Connected? : " << boolText(*Shared::connectedToHost) << Qt::endl;
txtOut << " Address : " << *Shared::hostAddress << endl; txtOut << " Address : " << *Shared::hostAddress << Qt::endl;
txtOut << " Port : " << *Shared::hostPort << endl; txtOut << " Port : " << *Shared::hostPort << Qt::endl;
if (*Shared::connectedToHost) if (*Shared::connectedToHost)
{ {
txtOut << "" << endl; txtOut << "" << Qt::endl;
txtOut << "--Session data" << endl << endl; txtOut << "--Session data" << Qt::endl << Qt::endl;
txtOut << " Client address : " << Shared::socket->localAddress().toString() << endl; txtOut << " Client address : " << Shared::socket->localAddress().toString() << Qt::endl;
txtOut << " Host address : " << Shared::socket->peerAddress().toString() << endl; txtOut << " Host address : " << Shared::socket->peerAddress().toString() << Qt::endl;
txtOut << " Session id : " << Shared::sessionId->toHex() << endl; txtOut << " Session id : " << Shared::sessionId->toHex() << Qt::endl;
txtOut << " Host version : " << verText() << endl; txtOut << " Host version : " << verText() << Qt::endl;
txtOut << " GEN_FILE commands : "; txtOut << " GEN_FILE commands : ";
QStringList genCmds = Shared::genfileCmds->values(); QStringList genCmds = Shared::genfileCmds->values();
@ -56,13 +56,13 @@ void Status::dataIn(const QString &argsLine)
{ {
genCmds.sort(Qt::CaseInsensitive); genCmds.sort(Qt::CaseInsensitive);
txtOut << "" << endl; txtOut << "" << Qt::endl;
} }
for (int i = 0; i < genCmds.size(); ++i) for (int i = 0; i < genCmds.size(); ++i)
{ {
if (i != 0) txtOut << " " << genCmds[i] << endl; if (i != 0) txtOut << " " << genCmds[i] << Qt::endl;
else txtOut << genCmds[i] << endl; else txtOut << genCmds[i] << Qt::endl;
} }
} }

View File

@ -138,25 +138,9 @@ QByteArray wrFrame(quint16 cmdId, const QByteArray &data, uchar dType)
return typeBa + cmdBa + branBa + sizeBa + data; return typeBa + cmdBa + branBa + sizeBa + data;
} }
QByteArray toTEXT(const QString &txt) QByteArray toFixedTEXT(const QString &txt, int len)
{ {
QByteArray ret = QTextCodec::codecForName(TXT_CODEC)->fromUnicode(txt); return txt.toUtf8().leftJustified(len, 0, true);
return ret.mid(2);
}
QByteArray fixedToTEXT(const QString &txt, int len)
{
return toTEXT(txt).leftJustified(len, 0, true);
}
QString fromTEXT(const QByteArray &txt)
{
QByteArray ba = txt;
ba.replace(QByteArray(2, 0x00), QByteArray());
return QTextCodec::codecForName(TXT_CODEC)->toUnicode(ba);
} }
QByteArray wrInt(quint64 num, int numOfBits) QByteArray wrInt(quint64 num, int numOfBits)
@ -211,10 +195,11 @@ QStringList parseArgs(const QByteArray &data, int maxArgs)
{ {
QStringList ret; QStringList ret;
QString arg; QString arg;
QString line = fromTEXT(data);
bool inDQuotes = false; auto line = QString(data);
bool inSQuotes = false; auto inDQuotes = false;
bool escaped = false; auto inSQuotes = false;
auto escaped = false;
for (int i = 0; i < line.size(); ++i) for (int i = 0; i < line.size(); ++i)
{ {
@ -280,7 +265,7 @@ QStringList parseArgs(const QByteArray &data, int maxArgs)
QStringList parseArgs(const QString &line) QStringList parseArgs(const QString &line)
{ {
return parseArgs(toTEXT(line), -1); return parseArgs(line.toUtf8(), -1);
} }
void wordWrap(const QString &label, QTextStream &txtOut, const QString &txtIn, QWidget *measureWid) void wordWrap(const QString &label, QTextStream &txtOut, const QString &txtIn, QWidget *measureWid)
@ -313,8 +298,8 @@ void wordWrap(const QString &label, QTextStream &txtOut, const QString &txtIn, Q
line.chop(1); line.chop(1);
if (labelWr) txtOut << label << line << endl; if (labelWr) txtOut << label << line << Qt::endl;
else txtOut << indent << line << endl; else txtOut << indent << line << Qt::endl;
labelWr = false; labelWr = false;
} }
@ -325,8 +310,8 @@ void wordWrap(const QString &label, QTextStream &txtOut, const QString &txtIn, Q
// the case, the line is added to the return and then // the case, the line is added to the return and then
// cleared. // cleared.
if (labelWr) txtOut << label << line << endl; if (labelWr) txtOut << label << line << Qt::endl;
else txtOut << indent << line << endl; else txtOut << indent << line << Qt::endl;
line.clear(); line.clear();
@ -365,7 +350,7 @@ QString getParam(const QString &key, const QStringList &args)
QString ret; QString ret;
int pos = args.indexOf(QRegExp(key, Qt::CaseInsensitive)); int pos = args.indexOf(QRegularExpression(key, QRegularExpression::CaseInsensitiveOption));
if (pos != -1) if (pos != -1)
{ {

View File

@ -50,7 +50,6 @@
#include <QTextBlockFormat> #include <QTextBlockFormat>
#include <QComboBox> #include <QComboBox>
#include <QDebug> #include <QDebug>
#include <QTextCodec>
#include <QCoreApplication> #include <QCoreApplication>
#include <QStringList> #include <QStringList>
#include <QHostAddress> #include <QHostAddress>
@ -59,8 +58,7 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QSslSocket> #include <QSslSocket>
#include <QFile> #include <QFile>
#include <QFile> #include <QRegularExpression>
#include <QRegExp>
#include <QFileInfo> #include <QFileInfo>
#include <QMutex> #include <QMutex>
#include <QProgressBar> #include <QProgressBar>
@ -71,12 +69,11 @@
#define DEFAULT_MAX_LINES 1000 #define DEFAULT_MAX_LINES 1000
#define RDBUFF 16777215 #define RDBUFF 16777215
#define DEFAULT_PORT 35516 #define DEFAULT_PORT 35516
#define TXT_CODEC "UTF-16LE"
#define BOOKMARK_FOLDER "bookmarks" #define BOOKMARK_FOLDER "bookmarks"
#define CONFIG_FILENAME "config_v3.json" #define CONFIG_FILENAME "config_v3.json"
#define APP_NAME "Cmdr" #define APP_NAME "Cmdr"
#define APP_TARGET "cmdr" #define APP_TARGET "cmdr"
#define APP_VERSION "3.2" #define APP_VERSION "3.5"
enum TypeID : quint8 enum TypeID : quint8
{ {
@ -85,7 +82,6 @@ enum TypeID : quint8
ERR = 3, ERR = 3,
PRIV_TEXT = 4, PRIV_TEXT = 4,
IDLE = 5, IDLE = 5,
HOST_CERT = 6,
FILE_INFO = 7, FILE_INFO = 7,
PEER_INFO = 8, PEER_INFO = 8,
MY_INFO = 9, MY_INFO = 9,
@ -169,11 +165,9 @@ void wordWrap(const QString &label, QTextStream &txtOut, const QString &t
bool argExists(const QString &key, const QStringList &args); bool argExists(const QString &key, const QStringList &args);
QByteArray wrInt(quint64 num, int numOfBits); QByteArray wrInt(quint64 num, int numOfBits);
QByteArray wrFrame(quint16 cmdId, const QByteArray &data, uchar dType); QByteArray wrFrame(quint16 cmdId, const QByteArray &data, uchar dType);
QByteArray toTEXT(const QString &txt); QByteArray toFixedTEXT(const QString &txt, int len);
QByteArray fixedToTEXT(const QString &txt, int len);
QStringList parseArgs(const QByteArray &data, int maxArgs); QStringList parseArgs(const QByteArray &data, int maxArgs);
QStringList parseArgs(const QString &line); QStringList parseArgs(const QString &line);
QString fromTEXT(const QByteArray &txt);
QString appDataDir(); QString appDataDir();
QString getParam(const QString &key, const QStringList &args); QString getParam(const QString &key, const QStringList &args);
QString extractCmdName(const QByteArray &data); QString extractCmdName(const QByteArray &data);

View File

@ -259,8 +259,8 @@ void Genfile::setGenfileType(quint8 typeId)
QByteArray Genfile::autoFill(const QByteArray &data) QByteArray Genfile::autoFill(const QByteArray &data)
{ {
QStringList args = parseArgs(data, -1); auto args = parseArgs(data, -1);
int ind = args.indexOf(QRegExp("-len", Qt::CaseInsensitive)); auto ind = args.indexOf(QRegularExpression("-len", QRegularExpression::CaseInsensitiveOption));
if (ind == -1) if (ind == -1)
{ {
@ -269,7 +269,7 @@ QByteArray Genfile::autoFill(const QByteArray &data)
args.append("-len"); args.append("-len");
args.append(len); args.append(len);
return toTEXT(args.join(' ')); return args.join(' ').toUtf8();
} }
else else
{ {
@ -372,9 +372,9 @@ void Genfile::dataIn(const QByteArray &data)
} }
else if (flags & CONFIRM_NEEDED) else if (flags & CONFIRM_NEEDED)
{ {
QString ans = fromTEXT(data); auto ans = QString(data);
if (QRegExp("y", Qt::CaseInsensitive).exactMatch(ans)) if ("y" == ans.toLower())
{ {
emit unsetUserIO(GEN_HOOK); emit unsetUserIO(GEN_HOOK);
@ -382,7 +382,7 @@ void Genfile::dataIn(const QByteArray &data)
setupForWriting(); setupForWriting();
} }
else if (QRegExp("n", Qt::CaseInsensitive).exactMatch(ans)) else if ("n" == ans.toLower())
{ {
finished(); finished();
} }

View File

@ -36,6 +36,7 @@ Session::Session(QObject *parent) : QSslSocket(nullptr)
connect(this, &Session::loopDataIn, this, &Session::dataIn); connect(this, &Session::loopDataIn, this, &Session::dataIn);
connect(this, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(sockerr(QAbstractSocket::SocketError))); connect(this, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(sockerr(QAbstractSocket::SocketError)));
connect(this, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrs(QList<QSslError>)));
progResetDelay->setInterval(200); progResetDelay->setInterval(200);
progResetDelay->setSingleShot(true); progResetDelay->setSingleShot(true);
@ -82,7 +83,14 @@ void Session::sockerr(QAbstractSocket::SocketError err)
} }
else else
{ {
cacheTxt(ERR, "\nerr: " + errorString() + "\n"); if (sslHandshakeErrors().isEmpty())
{
// a non-empty sslErrors() can assume the errors were already displayed
// by the sslErrs() slot so this conditional block is here to prevent
// the same error from getting displayed twice.
cacheTxt(ERR, "\nerr: " + errorString() + "\n");
}
if (state() == QAbstractSocket::UnconnectedState) if (state() == QAbstractSocket::UnconnectedState)
{ {
@ -91,18 +99,41 @@ void Session::sockerr(QAbstractSocket::SocketError err)
} }
} }
void Session::sslErrs(const QList<QSslError> &errors)
{
cacheTxt(ERR, "\n");
auto canIgnore = true;
for (auto err : errors)
{
if ((err.error() == QSslError::SelfSignedCertificate) ||
(err.error() == QSslError::SelfSignedCertificateInChain))
{
cacheTxt(TEXT, "WARNING: the host cert is self signed.\n\n");
}
else
{
canIgnore = false;
cacheTxt(ERR, "err: " + err.errorString() + "\n");
}
}
if (canIgnore)
{
ignoreSslErrors();
}
}
void Session::isConnected() void Session::isConnected()
{ {
// client header format: [4bytes(tag)][134bytes(appName)][272bytes(coName)] // client header format: [4bytes(tag)][32bytes(appName)][128bytes(modInst)][128bytes(padding)]
// tag = 0x4D, 0x52, 0x43, 0x49 (MRCI) // tag = 0x4D, 0x52, 0x43, 0x49 (MRCI)
// appName = UTF16LE string (padded with 0x00) // appName = UTF8 string (padded with 0x00)
// coName = UTF16LE string (padded with 0x00) // modInst = UTF8 string (padded with 0x00)
// padding = 128 bytes of (0x00)
// for MRCI host, if the client doesn't need to request a special SSL
// cert from the host, it is good practice to fallback to using the
// address used to connect to the host as the common name (coName).
// in this case, it is used all of the time.
cacheTxt(TEXT, "Connected.\n"); cacheTxt(TEXT, "Connected.\n");
@ -111,18 +142,10 @@ void Session::isConnected()
QByteArray header; QByteArray header;
header.append(SERVER_HEADER_TAG); header.append(SERVER_HEADER_TAG);
header.append(fixedToTEXT(appName, 134)); header.append(toFixedTEXT(appName, 32));
header.append(fixedToTEXT(*Shared::hostAddress, 272)); header.append(QByteArray(256, 0));
if (header.size() == CLIENT_HEADER_LEN) write(header);
{
write(header);
}
else
{
cacheTxt(ERR, "\nerr: client bug! - header len not equal to " + QString::number(CLIENT_HEADER_LEN) + "\n");
disconnectFromHost();
}
} }
void Session::handShakeDone() void Session::handShakeDone()
@ -132,13 +155,13 @@ void Session::handShakeDone()
QString txt; QString txt;
QTextStream txtOut(&txt); QTextStream txtOut(&txt);
txtOut << "SSL Handshake sucessful." << endl txtOut << "SSL Handshake sucessful." << Qt::endl
<< "cipher details:" << endl << "cipher details:" << Qt::endl
<< " #cipher - " << cipher.name() << endl << " #cipher - " << cipher.name() << Qt::endl
<< " #protocol - " << cipher.protocolString() << endl << " #protocol - " << cipher.protocolString() << Qt::endl
<< " #bit_depth - " << cipher.usedBits() << endl << " #bit_depth - " << cipher.usedBits() << Qt::endl
<< " #key_exchange - " << cipher.keyExchangeMethod() << endl << " #key_exchange - " << cipher.keyExchangeMethod() << Qt::endl
<< " #authentication - " << cipher.authenticationMethod() << endl; << " #authentication - " << cipher.authenticationMethod() << Qt::endl;
cacheTxt(TEXT, txt); cacheTxt(TEXT, txt);
} }
@ -246,43 +269,24 @@ void Session::procAsync(const QByteArray &data)
{ {
if (dType == TEXT) if (dType == TEXT)
{ {
cacheTxt(dType, "\ncast_text: " + fromTEXT(data) + "\n"); cacheTxt(dType, "\ncast_text: " + QString(data) + "\n");
} }
else if (dType == BIG_TEXT) else if (dType == BIG_TEXT)
{ {
QString txt; QString txt;
QTextStream stream(&txt); QTextStream stream(&txt);
wordWrap("cast_text: ", stream, fromTEXT(data), Shared::mainWidget); wordWrap("cast_text: ", stream, QString(data), Shared::mainWidget);
cacheTxt(TEXT, "\n"); cacheTxt(TEXT, "\n");
cacheTxt(TEXT, txt); cacheTxt(TEXT, txt);
} }
} }
else if (cmdId == ASYNC_RDY) else if (cmdId == ASYNC_RDY)
{ {
cacheTxt(dType, fromTEXT(data)); cacheTxt(dType, QString(data));
hook = 0; hook = 0;
} }
else if (cmdId == ASYNC_SYS_MSG)
{
if (dType == HOST_CERT)
{
QSslCertificate cert(data, QSsl::Pem);
QSslError selfSigned(QSslError::SelfSignedCertificate, cert);
cacheTxt(TEXT, "SSL cert received.\n\n");
if (cert.isSelfSigned())
{
cacheTxt(TEXT, "WARNING: the host cert is self signed.\n\n");
}
ignoreSslErrors(QList<QSslError>() << selfSigned);
addCaCertificate(cert);
startClientEncryption();
}
}
else if (cmdId == ASYNC_ADD_CMD) else if (cmdId == ASYNC_ADD_CMD)
{ {
if (dType == NEW_CMD) if (dType == NEW_CMD)
@ -375,7 +379,7 @@ void Session::dataFromHost(const QByteArray &data)
{ {
if ((dType == TEXT) || (dType == PRIV_TEXT) || (dType == BIG_TEXT) || (dType == ERR) || (dType == PROMPT_TEXT)) if ((dType == TEXT) || (dType == PRIV_TEXT) || (dType == BIG_TEXT) || (dType == ERR) || (dType == PROMPT_TEXT))
{ {
cacheTxt(dType, fromTEXT(data)); cacheTxt(dType, QString(data));
} }
else if (dType == PROG) else if (dType == PROG)
{ {
@ -435,32 +439,29 @@ void Session::dataIn()
{ {
// host header format: [1byte(reply)][2bytes(major)][2bytes(minor)][2bytes(tcp_rev)][2bytes(mod_rev)][28bytes(sesId)] // host header format: [1byte(reply)][2bytes(major)][2bytes(minor)][2bytes(tcp_rev)][2bytes(mod_rev)][28bytes(sesId)]
// reply is an 8bit little endian int the indicates the result of the client header
// sent to the host from the isConnected() function.
// sesId = 224bit sha3 hash // sesId = 224bit sha3 hash
// major = 16bit little endian uint (host ver major) // major = 16bit little endian uint (host ver major)
// minor = 16bit little endian uint (host ver minor) // minor = 16bit little endian uint (host ver minor)
// patch = 16bit little endian uint (host ver patch) // patch = 16bit little endian uint (host ver patch)
// reply 1: client header acceptable, SSL handshake not needed. // reply [1]: SSL handshake not needed.
// reply 2: client header acceptable, SSL handshake needed (STARTTLS). // reply [2]: SSL handshake needed (STARTTLS).
// reply 4: the common name sent by the client header was not found.
auto reply = static_cast<quint8>(rdInt(read(1))); auto servHeader = read(SERVER_HEADER_LEN);
auto reply = static_cast<quint8>(servHeader[0]);
if ((reply == 1) || (reply == 2)) if ((reply == 1) || (reply == 2))
{ {
*Shared::servMajor = static_cast<quint16>(rdInt(read(2))); *Shared::servMajor = static_cast<quint16>(rdInt(servHeader.mid(1, 2)));
*Shared::servMinor = static_cast<quint16>(rdInt(read(2))); *Shared::servMinor = static_cast<quint16>(rdInt(servHeader.mid(3, 2)));
*Shared::tcpRev = static_cast<quint16>(rdInt(read(2))); *Shared::tcpRev = static_cast<quint16>(rdInt(servHeader.mid(5, 2)));
*Shared::modRev = static_cast<quint16>(rdInt(read(2))); *Shared::modRev = static_cast<quint16>(rdInt(servHeader.mid(7, 2)));
cacheTxt(TEXT, "Detected host version: " + verText() + "\n"); cacheTxt(TEXT, "Detected host version: " + verText() + "\n");
if (*Shared::tcpRev == 0) if (*Shared::tcpRev == 2)
{ {
*Shared::sessionId = read(28); *Shared::sessionId = servHeader.mid(9, 28);
*Shared::connectedToHost = true; *Shared::connectedToHost = true;
flags |= VER_OK; flags |= VER_OK;
@ -469,7 +470,9 @@ void Session::dataIn()
if (reply == 2) if (reply == 2)
{ {
cacheTxt(TEXT, "Awaiting SSL cert from the host.\n"); cacheTxt(TEXT, "Starting SSL handshake.\n");
startClientEncryption();
} }
else else
{ {
@ -482,10 +485,6 @@ void Session::dataIn()
disconnectFromHost(); disconnectFromHost();
} }
} }
else if (reply == 4)
{
cacheTxt(ERR, "err: The host was unable to find a SSL cert for common name: " + *Shared::hostAddress + ".\n");
}
else else
{ {
cacheTxt(ERR, "err: Invalid reply id from the host: " + QString::number(reply) + ".\n"); cacheTxt(ERR, "err: Invalid reply id from the host: " + QString::number(reply) + ".\n");

View File

@ -26,7 +26,6 @@
#include <QFile> #include <QFile>
#include <QAbstractSocket> #include <QAbstractSocket>
#include <QCoreApplication> #include <QCoreApplication>
#include <QTextCodec>
#include <QTextStream> #include <QTextStream>
#include <QXmlStreamWriter> #include <QXmlStreamWriter>
#include <QSsl> #include <QSsl>
@ -34,6 +33,7 @@
#include <QSslCipher> #include <QSslCipher>
#include <QSslCertificate> #include <QSslCertificate>
#include <QSocketNotifier> #include <QSocketNotifier>
#include <QSslConfiguration>
#include <QTimer> #include <QTimer>
#include "cmd_objs/command.h" #include "cmd_objs/command.h"
@ -41,8 +41,7 @@
#include "cmd_objs/host_doc.h" #include "cmd_objs/host_doc.h"
#define SERVER_HEADER_TAG "MRCI" #define SERVER_HEADER_TAG "MRCI"
#define CLIENT_HEADER_LEN 410 #define SERVER_HEADER_LEN 37
#define SERVER_HEADER_LEN 35
#define FRAME_HEADER_LEN 8 #define FRAME_HEADER_LEN 8
class Session : public QSslSocket class Session : public QSslSocket
@ -76,6 +75,7 @@ private slots:
void resetProg(); void resetProg();
void startProg(); void startProg();
void sockerr(QAbstractSocket::SocketError err); void sockerr(QAbstractSocket::SocketError err);
void sslErrs(const QList<QSslError> &errors);
public: public:

View File

@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Exec=/usr/bin/$app_target
Name=$app_name
GenericName=Terminal emulator for MRCI host.
Icon=$app_target
StartupWMClass=$app_target
Terminal=false
Categories=Network;Terminal;MRCI;

View File

@ -0,0 +1,5 @@
#!/bin/sh
export QTDIR=$install_dir
export QT_PLUGIN_PATH=$install_dir
export LD_LIBRARY_PATH="$install_dir/lib:\$LD_LIBRARY_PATH"
$install_dir/$app_target $1 $2 $3

View File

@ -0,0 +1,21 @@
#!/bin/sh
rm -v /usr/bin/$app_target
rm -rv $install_dir
rm -v /usr/share/icons/hicolor/8x8/apps/$app_target.png
rm -v /usr/share/icons/hicolor/16x16/apps/$app_target.png
rm -v /usr/share/icons/hicolor/22x22/apps/$app_target.png
rm -v /usr/share/icons/hicolor/24x24/apps/$app_target.png
rm -v /usr/share/icons/hicolor/32x32/apps/$app_target.png
rm -v /usr/share/icons/hicolor/36x36/apps/$app_target.png
rm -v /usr/share/icons/hicolor/42x42/apps/$app_target.png
rm -v /usr/share/icons/hicolor/48x48/apps/$app_target.png
rm -v /usr/share/icons/hicolor/64x64/apps/$app_target.png
rm -v /usr/share/icons/hicolor/72x72/apps/$app_target.png
rm -v /usr/share/icons/hicolor/96x96/apps/$app_target.png
rm -v /usr/share/icons/hicolor/128x128/apps/$app_target.png
rm -v /usr/share/icons/hicolor/192x192/apps/$app_target.png
rm -v /usr/share/icons/hicolor/256x256/apps/$app_target.png
rm -v /usr/share/icons/hicolor/512x512/apps/$app_target.png
rm -v /usr/share/icons/hicolor/scalable/apps/$app_target.svg
rm -v /usr/share/applications/$app_target.desktop
echo "Uninstallation Complete"

6
templates/win_icon.vbs Normal file
View File

@ -0,0 +1,6 @@
set WshShell = CreateObject("Wscript.shell")
set oMyShortcut = WshShell.CreateShortcut("%SystemDrive%\ProgramData\Microsoft\Windows\Start Menu\Programs\$app_name.lnk")
oMyShortcut.IconLocation = "$install_dir\icon.ico"
OMyShortcut.TargetPath = "$install_dir\$app_target.exe"
oMyShortCut.Save

View File

@ -0,0 +1,6 @@
$app_target -stop
schtasks /delete /f /tn $app_name
del /f "%windir%\$app_target.exe"
del /f "%SystemDrive%\ProgramData\Microsoft\Windows\Start Menu\Programs\$app_name.lnk"
rd /q /s "$install_dir"
echo "Uninstallation Complete"