Browse Source

Merge branch 'develop' into 'master'

Final merge into master

See merge request tobias.wach/ccats!112
Serdyukov, Denys 4 years ago
parent
commit
aa7babdcd0
81 changed files with 2971 additions and 1338 deletions
  1. 18 0
      CMakeLists.txt
  2. 1 1
      Client-Server Protocol.md
  3. 23 6
      GUI-CLI Protocol.md
  4. 113 2
      README.md
  5. 98 0
      cli/README.md
  6. 1 1
      cli/include/batchioman.h
  7. 47 12
      cli/include/cmdman.h
  8. 2 3
      cli/include/fileman.h
  9. 17 1
      cli/include/ioman.h
  10. 1 1
      cli/include/machineioman.h
  11. 1 1
      cli/include/userioman.h
  12. 2 0
      cli/src/CMakeLists.txt
  13. 17 15
      cli/src/batchioman.cpp
  14. 106 61
      cli/src/cmdman.cpp
  15. 102 50
      cli/src/fileman.cpp
  16. 75 10
      cli/src/ioman.cpp
  17. 1 1
      cli/src/machineioman.cpp
  18. 10 6
      cli/src/main.cpp
  19. 56 36
      cli/src/userioman.cpp
  20. 7 2
      cli/test/CMakeLists.txt
  21. 136 28
      cli/test/cmdman_test.cpp
  22. 2 2
      cli/test/cryptoget.sh
  23. 2 2
      cli/test/cryptoput.sh
  24. 2 2
      cli/test/logintest_neg.sh
  25. 2 2
      cli/test/logintest_pos.sh
  26. 39 0
      daemon/Adding-a-Covert-Channel-Mode.md
  27. 2 1
      daemon/CMakeLists.txt
  28. 16 59
      daemon/CovertProtocol.md
  29. 0 52
      daemon/Daemon-Config-Reference.md
  30. 69 3
      daemon/README.md
  31. 2 2
      daemon/ccats.service
  32. 4 12
      daemon/include/CovertChannel/BidirectionalChannels.hpp
  33. 12 9
      daemon/include/CovertChannel/Channels/TCPAppendChannel.hpp
  34. 2 4
      daemon/include/CovertChannel/Channels/TCPOptionCustomChannel.hpp
  35. 2 4
      daemon/include/CovertChannel/Channels/TCPOptionTimestampChannel.hpp
  36. 4 4
      daemon/include/CovertChannel/Channels/TCPUrgencyChannel.hpp
  37. 2 2
      daemon/src/CMakeLists.txt
  38. 2 0
      daemon/src/FileManager.cpp
  39. 5 3
      daemon/src/JsonCommander.cpp
  40. 4 4
      daemon/src/UserManager.cpp
  41. 50 60
      daemon/src/main.cpp
  42. 115 5
      daemon/test/JsonCommanderTest.cpp
  43. 2 6
      daemon/test/QueueMock.cpp
  44. 4 3
      gui/CMakeLists.txt
  45. 72 0
      gui/Readme.md
  46. 36 2
      gui/include/climanager.h
  47. 184 2
      gui/include/cmdmanager.h
  48. 27 0
      gui/include/config.h
  49. 12 0
      gui/include/jsonhandler.h
  50. 380 43
      gui/include/qmlhandler.h
  51. 21 2
      gui/src/Forms/Connect/IpPopup.ui.qml
  52. 0 120
      gui/src/Forms/Connect/LoginForm.ui.qml
  53. 312 2
      gui/src/Forms/Connect/LoginSignupPopup.ui.qml
  54. 0 148
      gui/src/Forms/Connect/SignupForm.ui.qml
  55. 0 20
      gui/src/Forms/Help/HelpForm.ui.qml
  56. 40 0
      gui/src/Forms/Log/LogForm.ui.qml
  57. 29 47
      gui/src/Forms/Main/FooterForm.ui.qml
  58. 6 2
      gui/src/Forms/Main/InvalidCliPathPopup.ui.qml
  59. 5 1
      gui/src/Forms/Main/InvalidConfigPopup.ui.qml
  60. 6 2
      gui/src/Forms/Main/NoConfigFoundPopup.ui.qml
  61. 66 32
      gui/src/Forms/Main/main.qml
  62. 0 84
      gui/src/Forms/Messages/MessagesForm.ui.qml
  63. 0 2
      gui/src/Forms/Notifications/NotificationTemplate.ui.qml
  64. 2 3
      gui/src/Forms/Notifications/NotificationsForm.ui.qml
  65. 0 76
      gui/src/Forms/Receiving/ReceivingForm.ui.qml
  66. 0 95
      gui/src/Forms/Sending/SendingForm.ui.qml
  67. 9 10
      gui/src/Forms/ServerFiles/ServerFilesFileTemplate.ui.qml
  68. 6 2
      gui/src/Forms/ServerFiles/ServerFilesFileTemplateDeletePopup.ui.qml
  69. 225 0
      gui/src/Forms/ServerFiles/ServerFilesForm.ui.qml
  70. 9 1
      gui/src/Forms/Settings/DeleteMePopup.ui.qml
  71. 100 38
      gui/src/Forms/Settings/SettingsForm.ui.qml
  72. 42 23
      gui/src/climanager.cpp
  73. 63 15
      gui/src/cmdmanager.cpp
  74. 21 15
      gui/src/config.cpp
  75. BIN
      gui/src/images/tray-icon.png
  76. 22 3
      gui/src/jsonhandler.cpp
  77. 8 8
      gui/src/main.cpp
  78. 5 13
      gui/src/qml.qrc
  79. 68 47
      gui/src/qmlhandler.cpp
  80. 5 2
      gui/src/qtquickcontrols2.conf
  81. 12 0
      libs/libbcrypt/CMakeLists.txt

+ 18 - 0
CMakeLists.txt

@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 2.8)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/.cmake_modules/")
+
+project(ccats)
+
+if(ENABLE_TESTS)
+  enable_testing()
+  set(DAEMONFILEPATH ${CMAKE_BINARY_DIR}/files)
+endif()
+
+set(CMAKE_SOURCE_DIR ${CMAKE_SOURCE_DIR}/daemon)
+add_subdirectory(daemon)
+set(CMAKE_SOURCE_DIR ${CMAKE_SOURCE_DIR}/cli)
+add_subdirectory(cli)
+set(CMAKE_SOURCE_DIR ${CMAKE_SOURCE_DIR}/gui)
+add_subdirectory(gui)

+ 1 - 1
Client-Server Protocol.md

@@ -2,7 +2,7 @@
 
 Protocol version: <b>"0.3"</b>
 
-Every json message must be minimized and be followed by a newline. This rule makes receiving and parsing the messages easier because you can read until a newline comes and you will parse every json message seperately because you won't read multiple json messages from the buffer at a time.
+Every json message must be minimized (i.e. must not contain newlines) and be followed by a newline. This rule makes receiving and parsing the messages easier because you can read until a newline comes and you will parse every json message seperately because you won't read multiple json messages from the buffer at a time.
 
 ## 1. Start a connection
 

+ 23 - 6
GUI-CLI Protocol.md

@@ -2,7 +2,7 @@
 
 Protocol version: <b>"0.3"</b>
 
-Every json message must be minimized and be followed by a newline. This rule makes receiving and parsing the messages easier because you can read until a newline comes and you will parse every json message seperately because you won't read multiple json messages from the buffer at a time.
+Every json message must be minimized (i.e. must not contain newlines) and be followed by a newline. This rule makes receiving and parsing the messages easier because you can read until a newline comes and you will parse every json message seperately because you won't read multiple json messages from the buffer at a time.
 
 ## 0. Connect to the server
 
@@ -64,7 +64,7 @@ CLI:
 }
 ```
 
-If `accept` is `true` the connection is valid and the user is logged in. Else `error` has an error string and the connection will be terminated after the server answered.
+If `accept` is `true` the connection is valid and the user is logged in. Else `error` has an error string and the connection to the server will be terminated after the server answered.
 
 ### 1.2.2 Signup
 GUI:
@@ -72,7 +72,7 @@ GUI:
 write: "signup" username new_password
 ```
 
-Server:
+CLI:
 ```
 {
 	"command": "signup",
@@ -81,7 +81,7 @@ Server:
 }
 ```
 
-If `accept` is `true` the connection is valid and the user is logged in and signed up. Else `error` has an error string and the connection will be terminated after the server answered.
+If `accept` is `true` the connection is valid and the user is logged in and signed up. Else `error` has an error string and the connection to the server will be terminated after the server answered.
 
 
 ## 2. Sending commands
@@ -298,7 +298,7 @@ Client:
 write: "queue" file_name
 ```
 
-Server:
+CLI:
 ```
 {
 	"command": "queue",
@@ -317,7 +317,7 @@ Client:
 write: "dequeue" file_name
 ```
 
-Server:
+CLI:
 ```
 {
 	"command": "dequeue",
@@ -388,3 +388,20 @@ CLI:
 ```
 
 If `accept` is true, the connection is closed and the program can exit.
+
+## 4. Errors
+
+If an unexpected error occurs, the GUI might receive an `error` message:
+```
+{
+	"command": "error",
+	"error": string
+}
+```
+If the server does not respond to requests for some time, the GUI receives a `connectionerror` message and the the connection to the server is terminated:
+```
+{
+	"command": "connectionerror",
+	"error": string
+}
+```

+ 113 - 2
README.md

@@ -1,4 +1,115 @@
-# CCats
+# CCats - Covert Channels are Tricky Stuff
 
-Covert Channels are Tricky Stuff
+CCats is a covert channel tool which works as man in the middle.
+Our motivation is to create a tool which allows you to send files over a covert channel between two controlled servers.
+<br/><br/>
+This repository consists of three programs: a server (daemon), a CLI-client and a GUI-client.
+<br/><br/>
+The server manages the uploaded files and transmits the files over the covert channel.
+The clients let you connect and login to the server and upload files and start file transmission over the covert channel.
 
+## The basic setup
+```
+   A           -----------------                        ------------------           B
+   o  traffic  |               |                        |                |  traffic  o
+  /|\ ---------| active Server | <== covert channel ==> | passive Server |--------- /|\
+   \\          |               |                        |                |          //
+               -----------------                        ------------------
+                      ||                                       ||
+               client connection                        client connection
+                      ||                                       ||
+               E  ----------                               ----------  F
+               o  |        |                               |        |  o
+              /|\ | Client |                               | Client | /|\
+               \\ |        |                               |        | //
+                  ----------                               ----------
+```
+E wants to send a file to F. A and E live together and B and F live together. E and F know that A will surf on B's website a lot so they decided to setup a covert channel which utilises the traffic of A and B as man in the middle. <br/>
+A surfs on the website of B on Port p and produces traffic between them. E and F set up the two servers. The active server is the server which will be used to send a file to the passive server. <br/>
+E sets the target IP on the active server side to the IP of A and F the target IP on the passive side to the IP of B. The target Port on both sides is set to p.
+Now the servers are forwarding A's and B's traffic and can add secret data to the traffic. <br/>
+E connects to the active server with his/her client and queues a file for sending over the covert channel. The file transmission will start once A surfs on B's website again. Now E and F have to wait until A produced enough traffic to transmit the file completely.
+After the file transmission finished F can downlod the file with his/her client.
+
+## Encryption
+E and F can decide to use a common key for symmetrical encryption. Files will be encrypted on the client side so the servers cannot read the files as plain data. The covert channel does not add another encryption-layer so it's encouraged to use this feature.
+
+## Dependencies
+- libtins >=4.2
+- Jsoncpp
+- Boost >=1.70
+- GNU Readline
+- OpenSSL
+- Qt5 (Core, Quick, Widgets)
+
+### Dependencies for testing
+- GTest
+- GMock
+
+### Installing dependencies on Ubuntu
+Packages to install after minimal Ubuntu 19.10 installation:
+```
+sudo apt install qtdeclarative5-dev libboost-all-dev libjsoncpp-dev libreadline-dev libpcap-dev build-essential qml-module-qt-labs-platform libssl-dev
+```
+
+(NOTE: Due to a bug in jsoncpp, in the file size field of the GUI, all numbers after the decimal point are displayed as 0. This bug occurs in version 1.7.4 and is fixed in 1.9.1 (or possibly earlier).)
+
+#### Install libtins
+```
+mkdir libtins
+cd libtins
+git clone https://github.com/mfontanini/libtins.git
+sudo apt-get install libpcap-dev libssl-dev cmake
+cd libtins
+mkdir build
+cd build
+cmake ../ -DLIBTINS_ENABLE_CXX11=1
+make
+sudo make install
+sudo ldconfig
+```
+
+#### Install boost
+```
+mkdir boost
+cd boost
+wget https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.gz
+tar -xf boost_1_72_0.tar.gz
+cd boost_1_72_0
+./bootstrap.sh
+./b2
+sudo ./b2 install
+```
+
+#### Build and install googletest
+```
+git clone https://github.com/google/googletest.git
+cd googletest
+mkdir build
+cd build
+cmake ..
+make
+make install
+cd ../..
+rm -rf googletest
+```
+
+## Build
+CMake is used as build system. It should check which dependency is missing and tell you.
+
+All three compontents can be built together using the top-level CMake file.
+```
+mkdir build
+cd build
+cmake ..
+make
+```
+The components can also be built seperately following the same instructions from the `./daemon/`, `./cli/` and `./gui/` directory.
+
+### Build with tests
+```
+mkdir build
+cd build
+cmake .. -DENABLE_TESTS=true
+make
+```

+ 98 - 0
cli/README.md

@@ -0,0 +1,98 @@
+# Using the Command Line Interface (CLI)
+
+## Starting the CLI
+
+The command line interface can be started with the following arguments:
+
+### Choosing the CLI mode
+
+<b><i>no argument</i></b>:  <br/>
+Regular user mode, designed for user interaction.
+
+`--machine`: <br/>
+Machine mode, not designed for user interaction. Other programs (like the GUI) can use this for communication with the CLI. For usage, please refer to [GUI-CLI Protocol.md](../../GUI-CLI Protocol.md).
+
+`--batch <file>`: <br/>
+Batch mode. A batch file for the CLI must contain commands (as used in regular user mode) separated by line breaks. The operations can be run by passing the path to the batch file as argument <i>&lt;file&gt;</i>.
+
+### Additional arguments
+
+`--usessl <certfile>`: <br/>
+Used to enable SSL communication with the server using the certificate file specified by the file path <i>&lt;certfile&gt;</i>. Needs to be passed if server is configured to use SSL encryption. Configurations where only one side has SSL encryption enabled are not supported.
+
+`--verbose`: <br/>
+Prints additional output for debugging purposes.
+
+
+## Connecting to a server and logging in
+
+The user connects to a server using `connect <ip> <port>` where <i>&lt;ip&gt;</i> is the IP of the server and <i>&lt;port&gt;</i> is the port used by the server. The `<port>` parameter is optional. If no port is specified, 1234 is used.
+
+Then a user has to connect by typing `login <username> <password>`. Alternatively the user can create an account and log in with `signup <username> <password>`. If wrong user data was passed, the user is disconnected from the server and needs to connect again.
+
+## Specifying a key file for encryption
+
+The following commands are used to specify key files used for encryption of all sent files and for decryption. They can be used at any time.
+
+`keyfile <filepath>`: <br/>
+Set a key file. The path of the file has to be specified by <i>&lt;filepath&gt;</i>.
+
+`closekey`: <br/>
+Stop using the previously selected keyfile.
+
+
+## Interacting with the server
+
+The following commands can be sent when the user is connected to a server.
+
+
+`status`: <br/>
+Shows username, ip and port and wether a file transfer between server and client is running.
+
+`extendedstatus`: <br/>
+Shows detailed information about ongoing transfers at the server.
+
+`list`: <br/>
+Shows a simple list of the files stored on the server.
+
+`extendedlist`: <br/>
+Shows a detailed list of the files stored on the server, containing file size and decryptability.
+
+`notifications`: <br/>
+Shows a list of notifications since the last time.
+<br/><br/>
+
+`put <filepath>`: <br/>
+Request a file upload to the server. The path of the file has to be specified by <i>&lt;filepath&gt;</i>.
+
+`get <filepath>`: <br/>
+Request a download of the file with name <i>&lt;filepath&gt;</i> from the server. <br/>
+Alternatively a path can be specified where the file with corresponding name is to save, e.g. `get ./subfolder/foo.txt` downloads the file <i>foo.txt</i> from the server to the directory <i>./subfolder/</i>.
+
+`deletefile <filename>`: <br/>
+Request a deletion of the file with name <i>&lt;filename&gt;</i> from the server. Has to be activated in the server configuration.
+
+`queue <filename>`: <br/>
+To add a file that is already on the server to the queue for sending with the covert channel.
+
+`dequeue <filename>`: <br/>
+To remove a file from the queue for sending with the covert channel. If the transfer is already running and the covert cahnnel method allows it, this cancels the transfer.
+
+`head <filename>`: <br/>
+Request the first 32 bytes of a file on the server in base64 encoding. If the file is smaller than 32 bytes, the first 4 bytes are shown if possible.
+<br/><br/>
+
+`deleteme <password>`: <br/>
+Allows the logged in user to delete their account on the server. This action needs to be confirmed with the password.
+
+
+## Disconnecting and exiting
+
+Disconnecting from a server is possible by typing `disconnect`. After this, it is possible to connect to a server again.
+
+The program can be exited by typing `exit` at any time.
+
+
+## Displaying a list of commands
+
+`help` can be used at any time and shows a list of commands.

+ 1 - 1
cli/include/batchioman.h

@@ -81,7 +81,7 @@ public:
 	/**
 	 * Constructor and destructor
 	 */
-	BatchIoMan(bool usessl, bool beverbose, string batchpath);
+	BatchIoMan(bool usessl, const char *certfile, bool beverbose, string batchpath);
 	~BatchIoMan();
 
 	bool init();

+ 47 - 12
cli/include/cmdman.h

@@ -6,6 +6,7 @@
 #include <json/json.h>
 
 #include <map>
+#include <mutex>
 #include <string>
 #include <vector>
 
@@ -23,13 +24,24 @@ class CmdMan {
 public:
 	/**
 	 * Flags for type of message returned in CmdRet.
-	 * print	- print something to the user
-	 * send	- send something to the server
-	 * error	- an error occured, do not send to the server
-	 * close	- terminate the connection
-	 * seton	- contextually change state, used for version check and login
+	 * print			- print something to the user
+	 * send				- send something to the server
+	 * error			- an error occured, do not send to the server
+	 * close			- terminate the connection
+	 * connect			- connect to the server
+	 * exit				- exit the program
+	 * noanswerexpected	- do not expect an answer from the server
 	 */
-	enum rettype { none = 0, print = (1 << 1), send = (1 << 2), error = (1 << 3), close = (1 << 4), connect = (1 << 5), exit = (1 << 6) };
+	enum rettype {
+		none = 0,
+		print = (1 << 1),
+		send = (1 << 2),
+		error = (1 << 3),
+		close = (1 << 4),
+		connect = (1 << 5),
+		exit = (1 << 6),
+		noanswerexpected = (1 << 7)
+	};
 	/**
 	 * Response to user or command input
 	 *
@@ -61,7 +73,12 @@ public:
 	 */
 	Json::CharReader *reader;
 
+	/**
+	 * Used to inform the CmdMan that the CLI is now (dis-)connected to the server.
+	 * Sets the internal state of the CmdMan accordingly.
+	 */
 	void stateSetConnectionOk();
+	void stateSetDisconnected();
 
 protected:
 	/**
@@ -73,6 +90,12 @@ protected:
 	state currentState;
 
 private:
+	/**
+	 * Prevents multiple calls of execute and handle at the same time.
+	 * Thereby prevents an incorrect state of used cmdman and fileman instances.
+	 */
+	std::mutex cmdmutex;
+
 	/**
 	 * internal json writer and error string member
 	 */
@@ -97,6 +120,19 @@ private:
 	 */
 	map<string, string> helpmap;
 
+	/**
+	 * Vectors containing command strings that should either be usable in any
+	 * state or after connecting but not logging in
+	 */
+	vector<string> cmdAllowAlways = {"help", "keyfile", "closekey", "exit", "status"};
+	vector<string> cmdAllowAfterConnect = {"login", "signup", "disconnect"};
+
+	/**
+	 * Fields used for status output.
+	 */
+	string username, ip;
+	unsigned int port;
+
 	/**
 	 * Help strings and method prototypes for commands to be used by a user
 	 */
@@ -105,11 +141,11 @@ private:
 	CmdRet cmdHelp(vector<string> args);
 	const string descStatus = "request basic status information from server";
 	CmdRet cmdStatus(vector<string> args);
-	const string descExtendedstatus = "request detailed status information from server with running transfers";
+	const string descExtendedstatus = "request detailed status information from server about running transfers";
 	CmdRet cmdExtendedstatus(vector<string> args);
 	const string descDisconnect = "disconnect from server";
 	CmdRet cmdDisconnect(vector<string> args);
-	const string descPut = "upload file to server and add to queue";
+	const string descPut = "upload file to server";
 	CmdRet cmdPut(vector<string> args);
 	const string descGet = "retrieve file from server";
 	CmdRet cmdGet(vector<string> args);
@@ -117,11 +153,11 @@ private:
 	CmdRet cmdList(vector<string> args);
 	const string descExtendedlist = "list files available on server with further information";
 	CmdRet cmdExtendedlist(vector<string> args);
-	const string descHead = "request the first four bytes of a file from the server";
+	const string descHead = "request the first few bytes of a file from the server";
 	CmdRet cmdHead(vector<string> args);
 	const string descDeletefile = "delete a file from the server";
 	CmdRet cmdDeletefile(vector<string> args);
-	const string descKeyfile = "set keyfile to use";
+	const string descKeyfile = "set keyfile to use for encryption";
 	CmdRet cmdKeyfile(vector<string> args);
 	const string descClosekey = "stop using the previously selected keyfile";
 	CmdRet cmdClosekey(vector<string> args);
@@ -136,8 +172,7 @@ private:
 	CmdRet cmdLogin(vector<string> args);
 	const string descSignup = "sign up and login to the server";
 	CmdRet cmdSignup(vector<string> args);
-	const string descDeleteme = "delete the user you are currently logged in as "
-	                            "(needs to be confirmed with the password)";
+	const string descDeleteme = "delete the user you are currently logged in as (needs to be confirmed with the password)";
 	CmdRet cmdDeleteme(vector<string> args);
 	const string descQueue = "add a file that is already on the server to the queue for sending with the covert channel";
 	CmdRet cmdQueue(vector<string> args);

+ 2 - 3
cli/include/fileman.h

@@ -27,10 +27,10 @@ private:
 	 * Boolean replacement for filestreams being open for list
 	 *
 	 */
-	std::vector<std::vector<char>> putdata;
+	std::ifstream putfile;
 	std::fstream getfile;
 	std::vector<Json::Value> listdata;
-	std::string getpath, getname, putpath, putname;
+	std::string getpath, getname, putpath, putname, cipherpath;
 	const unsigned int max_read_len = 4096;
 	int putsize;
 	int putchunks;
@@ -64,7 +64,6 @@ private:
 	void deinitCryptoE();
 	void deinitCryptoD();
 
-	std::vector<std::vector<char>> chunkify(char *data, unsigned int size);
 	void writeEnc(const std::vector<char> data);
 	const char signature[4] = {'C', 'C', 'A', 'T'};
 

+ 17 - 1
cli/include/ioman.h

@@ -85,9 +85,15 @@ protected:
 	CmdMan cmdman;
 	FileMan fileman;
 
+	/**
+	 * Functions used to handle results produced by CmdMan for user input or network input
+	 */
 	virtual void handleInCmdResponse(CmdMan::CmdRet cmdret);
 	virtual void handleOutCmdResponse(CmdMan::CmdRet cmdret, vector<string> &toput);
 
+	/**
+	 * Boost asio sockets used for tcp and ssl communication and flag wether to use ssl or not
+	 */
 	tcp::socket *tcpsock;
 	boost::asio::ssl::stream<tcp::socket &> *sslsock;
 	bool usessl;
@@ -123,11 +129,21 @@ private:
 	 */
 	std::vector<std::string> tokenizeInput(std::string in);
 
+	/**
+	 * Timestamp saves the sending time of the oldest request to the server that was not followed by an answer.
+	 * If the timestamp is not valid, since the last request to the server, there was an answer
+	 * (or the cli is disconnected, or there was no request yet).
+	 * Mutex used for access to the timestamps.
+	 */
+	time_t sendtimestamp;
+	bool sendtimestampValid;
+	std::mutex timestampmutex;
+
 public:
 	/**
 	 * Constructor and destructor
 	 */
-	IoMan(bool enablessl);
+	IoMan(bool enablessl, const char *certfile);
 	virtual ~IoMan();
 
 	/**

+ 1 - 1
cli/include/machineioman.h

@@ -14,7 +14,7 @@ private:
 	bool verbose;
 
 public:
-	MachineIoMan(bool usessl, bool beverbose);
+	MachineIoMan(bool usessl, const char *certfile, bool beverbose);
 
 protected:
 	/**

+ 1 - 1
cli/include/userioman.h

@@ -69,7 +69,7 @@ public:
 	/**
 	 * Constructor and destructor
 	 */
-	UserIoMan(bool usessl, bool verbose);
+	UserIoMan(bool usessl, const char *certfile, bool verbose);
 	~UserIoMan();
 
 protected:

+ 2 - 0
cli/src/CMakeLists.txt

@@ -1,5 +1,7 @@
 cmake_minimum_required(VERSION 2.8)
 
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
+
 find_package(Readline REQUIRED)
 
 include_directories(${Boost_INCLUDE_DIR} ${JSONCPP_INCLUDE_DIRS} ${Readline_INCLUDE_DIR} include)

+ 17 - 15
cli/src/batchioman.cpp

@@ -4,7 +4,7 @@
 #include <string>
 #include <vector>
 
-BatchIoMan::BatchIoMan(bool usessl, bool beverbose, std::string batchpath) : IoMan(usessl) {
+BatchIoMan::BatchIoMan(bool usessl, const char *certfile, bool beverbose, std::string batchpath) : IoMan(usessl, certfile) {
 	/* setup json stuff */
 	Json::CharReaderBuilder rbuilder;
 	wbuilder.settings_["indentation"] = "";
@@ -12,6 +12,7 @@ BatchIoMan::BatchIoMan(bool usessl, bool beverbose, std::string batchpath) : IoM
 
 	/* initialize print command map */
 	printmap["error"] = &BatchIoMan::printError;
+	printmap["connectionerror"] = &BatchIoMan::printError;
 	printmap["connect"] = &BatchIoMan::printConnect;
 	printmap["help"] = &BatchIoMan::printHelp;
 	printmap["status"] = &BatchIoMan::printStatus;
@@ -288,7 +289,7 @@ std::string BatchIoMan::printJson(Json::Value root) {
 	map<string, std::string (BatchIoMan::*)(Json::Value)>::iterator it = printmap.find(root["command"].asString());
 	if (it == printmap.end()) {
 		// this should never happen outside of development
-		printMessage(string(__PRETTY_FUNCTION__) + " unknown command \"" + root["command"].asString() + "\".\nensure code is implemented.", debug);
+		printMessage(string(__PRETTY_FUNCTION__) + " unknown command \"" + root["command"].asString() + "\".\nEnsure code is implemented.", debug);
 		return "";
 	}
 	return (this->*(printmap[root["command"].asString()]))(root);
@@ -298,7 +299,7 @@ std::string BatchIoMan::printError(Json::Value root) { return std::string("Error
 
 std::string BatchIoMan::printConnect(Json::Value root) {
 	if (!root["accept"].asBool()) {
-		return std::string("Couldnt connect to ") + root["address"].asString() + ":" + std::to_string(root["port"].asUInt()) + "\n" +
+		return std::string("Couldnt connect to ") + root["address"].asString() + ":" + std::to_string(root["port"].asUInt()) + ".\n" +
 		       "Reason: " + root["error"].asString();
 	}
 	return "";
@@ -311,7 +312,7 @@ std::string BatchIoMan::printHelp(Json::Value root) {
 	return ret;
 }
 
-std::string BatchIoMan::printStatus(Json::Value root) { return std::string("Server reports status: ") + root["response"].asString(); }
+std::string BatchIoMan::printStatus(Json::Value root) { return root["response"].asString(); }
 
 std::string BatchIoMan::printExtendedstatus(Json::Value root) {
 	if (!root["accept"].asBool()) {
@@ -392,7 +393,7 @@ std::string BatchIoMan::printPut(Json::Value root) {
 			return std::string("Upload request failed: ") + root["error"].asString();
 		}
 	} else
-		return std::string("Begin uploading file ") + root["file"].asString();
+		return std::string("Begin uploading file ") + root["file"].asString() + ".";
 }
 
 std::string BatchIoMan::printGet(Json::Value root) {
@@ -464,7 +465,8 @@ std::string BatchIoMan::printExtendedlist(Json::Value root) {
 
 std::string BatchIoMan::printVersion(Json::Value root) {
 	if (!root["accept"].asBool()) {
-		return std::string("Version check failed. Server reports ") + root["serverversion"].asString() + " but client is " + root["clientversion"].asString();
+		return std::string("Version check failed. Server reports version ") + root["serverversion"].asString() + " but client is " +
+		       root["clientversion"].asString() + ".";
 	} else
 		return "Version check ok.";
 }
@@ -498,23 +500,23 @@ std::string BatchIoMan::printListdata(Json::Value root) { return ""; }
 
 std::string BatchIoMan::printHead(Json::Value root) {
 	if (!root["accept"].asBool())
-		return std::string("Request of the first four bytes failed. ") + root["error"].asString();
+		return std::string("Request of the first few bytes failed. ") + root["error"].asString();
 	else
-		return std::string("First four bytes of file ") + root["file"].asString() + " are: " + root["data"].asString();
+		return std::string("First few bytes of file ") + root["file"].asString() + " are: " + root["data"].asString();
 }
 
 std::string BatchIoMan::printDeletefile(Json::Value root) {
 	if (!root["accept"].asBool())
-		return std::string("Deletion of file ") + root["file"].asString() + " failed. " + root["error"].asString();
+		return std::string("Deletion of file ") + root["file"].asString() + " failed: " + root["error"].asString();
 	else
-		return std::string("File ") + root["file"].asString() + " deleted succesfully";
+		return std::string("File ") + root["file"].asString() + " deleted succesfully.";
 }
 
 std::string BatchIoMan::printKeyfile(Json::Value root) {
 	if (!root["accept"].asBool())
 		return std::string("Couldnt select keyfile ") + root["file"].asString() + ": " + root["error"].asString();
 	else
-		return std::string("Using keyfile ") + root["file"].asString();
+		return std::string("Using keyfile ") + root["file"].asString() + ".";
 }
 
 std::string BatchIoMan::printClosekey(Json::Value root) {
@@ -526,16 +528,16 @@ std::string BatchIoMan::printClosekey(Json::Value root) {
 
 std::string BatchIoMan::printQueue(Json::Value root) {
 	if (!root["accept"].asBool())
-		return std::string("Queueing of file ") + root["file"].asString() + " failed. " + root["error"].asString();
+		return std::string("Queueing of file ") + root["file"].asString() + " failed: " + root["error"].asString();
 	else
-		return std::string("File ") + root["file"].asString() + " queued succesfully";
+		return std::string("File ") + root["file"].asString() + " queued succesfully.";
 }
 
 std::string BatchIoMan::printDequeue(Json::Value root) {
 	if (!root["accept"].asBool())
-		return std::string("Dequeueing of file ") + root["file"].asString() + " failed. " + root["error"].asString();
+		return std::string("Dequeueing of file ") + root["file"].asString() + " failed: " + root["error"].asString();
 	else
-		return std::string("File ") + root["file"].asString() + " dequeued succesfully";
+		return std::string("File ") + root["file"].asString() + " dequeued succesfully.";
 }
 
 std::string BatchIoMan::printNotifications(Json::Value root) {

+ 106 - 61
cli/src/cmdman.cpp

@@ -94,6 +94,13 @@ CmdMan::~CmdMan() { delete reader; }
 
 void CmdMan::stateSetConnectionOk() { currentState = versionpossible; }
 
+void CmdMan::stateSetDisconnected() {
+	currentState = connectionpossible;
+	fileman.cancelGet();
+	fileman.cancelPut();
+	fileman.cancelList();
+}
+
 CmdMan::CmdRet CmdMan::cmdHelp(vector<string> args) {
 	CmdRet retval;
 	Json::Value root, arr;
@@ -114,7 +121,15 @@ CmdMan::CmdRet CmdMan::cmdStatus(vector<string> args) {
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
 	root["command"] = "status";
-	retval.type = send;
+	if (currentState == normal) {
+		retval.type = send;
+	} else if (currentState == connectionpossible || currentState == disconnecttoexit || currentState == disconnecttoexitearly) {
+		retval.type = print;
+		root["response"] = "You are not connected.";
+	} else {
+		retval.type = print;
+		root["response"] = "Connected to " + ip + ":" + std::to_string(port) + ". Not logged in.";
+	}
 	retval.msg = root;
 
 	return retval;
@@ -142,7 +157,7 @@ CmdMan::CmdRet CmdMan::cmdDisconnect(vector<string> args) {
 		root["user"] = "";
 		root["pass"] = "";
 		root["cancel"] = true;
-		retval.type |= close;
+		retval.type |= close | noanswerexpected;
 		if (currentState == disconnecttoexitearly) {
 			retval.nextcommand = "exit";
 		}
@@ -159,13 +174,13 @@ CmdMan::CmdRet CmdMan::cmdPut(vector<string> args) {
 	CmdRet retval;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
-	root["command"] = "put";
 
 	if (args.size() < 1) {
 		retval.type = error;
-		root["accept"] = false;
-		root["error"] = "not enough arguments, at least 1 argument required";
+		root["command"] = "error";
+		root["error"] = "Not enough arguments, put requires at least 1 argument.";
 	} else {
+		root["command"] = "put";
 		if (fileman.isPutting()) {
 			retval.type = error;
 			root["file"] = args[0];
@@ -216,14 +231,13 @@ CmdMan::CmdRet CmdMan::cmdGet(vector<string> args) {
 	CmdRet retval;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
-	root["command"] = "get";
 
 	if (args.size() < 1) {
 		retval.type = error;
-		root["accept"] = false;
-		root["error"] = "not enough arguments, at least 1 argument required";
+		root["command"] = "error";
+		root["error"] = "Not enough arguments, get requires at least 1 argument.";
 	} else {
-
+		root["command"] = "get";
 		if (fileman.isGetting()) {
 			retval.type = error;
 			root["file"] = args[0];
@@ -350,13 +364,14 @@ CmdMan::CmdRet CmdMan::cmdHead(vector<string> args) {
 	CmdRet retval;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
-	root["command"] = "head";
 
 	if (args.size() < 1) {
 		retval.type = error;
+		root["command"] = "error";
 		root["accept"] = false;
-		root["error"] = "not enough arguments, at least 1 argument required";
+		root["error"] = "Not enough arguments, head requires at least 1 argument.";
 	} else {
+		root["command"] = "head";
 		root["file"] = args[0];
 		retval.type = send;
 	}
@@ -369,13 +384,14 @@ CmdMan::CmdRet CmdMan::cmdDeletefile(vector<string> args) {
 	CmdRet retval;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
-	root["command"] = "deletefile";
 
 	if (args.size() < 1) {
 		retval.type = error;
+		root["command"] = "error";
 		root["accept"] = false;
-		root["error"] = "not enough arguments, at least 1 argument required";
+		root["error"] = "Not enough arguments, deletefile requires at least 1 argument.";
 	} else {
+		root["command"] = "deletefile";
 		root["file"] = args[0];
 		retval.type = send;
 	}
@@ -389,19 +405,28 @@ CmdMan::CmdRet CmdMan::cmdConnect(vector<string> args) {
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
 
-	root["command"] = "connect";
-
 	if (args.size() < 1) {
 		retval.type = error;
-		root["error"] = "not enough arguments, at least 1 argument required";
-	} else if (args.size() < 2) {
-		retval.type = connect;
-		root["address"] = args[0];
-		root["port"] = 1234;
+		root["command"] = "error";
+		root["accept"] = false;
+		root["error"] = "Not enough arguments, connect requires at least 1 argument.";
+	} else if (currentState != connectionpossible) {
+		retval.type = error;
+		root["command"] = "error"; // analogous to execute() method when command unavailable
+		root["error"] = "Connecting not possible, you are already connected to a server.";
 	} else {
+		// set internal ip and port fields first
+		ip = args[0];
+		if (args.size() < 2) {
+			port = 1234;
+		} else {
+			port = (unsigned int)stoul(args[1]);
+		}
+		// construct json
 		retval.type = connect;
-		root["address"] = args[0];
-		root["port"] = (unsigned int)stoul(args[1]);
+		root["command"] = "connect";
+		root["address"] = ip;
+		root["port"] = port;
 	}
 	retval.msg = root;
 
@@ -414,28 +439,44 @@ CmdMan::CmdRet CmdMan::execute(string cmd, vector<string> args) {
 	for (string s : args)
 		DEBUGPRINT(s + " ");
 	DEBUGPRINT("]");
-	map<string, CmdRet (CmdMan::*)(vector<string>)>::iterator it = execmap.find(cmd);
+	cmdmutex.lock();
+	map<string, CmdRet (CmdMan::*)(vector<string>)>::iterator execit = execmap.find(cmd);
+	vector<string>::const_iterator alwaysit;
+	vector<string>::const_iterator connectit;
+	for (alwaysit = cmdAllowAlways.cbegin(); alwaysit != cmdAllowAlways.cend(); alwaysit++)
+		if (*alwaysit == cmd)
+			break;
+	for (connectit = cmdAllowAfterConnect.cbegin(); connectit != cmdAllowAfterConnect.cend(); connectit++)
+		if (*connectit == cmd)
+			break;
 	CmdRet retval;
 	Json::Value root;
 	root["command"] = cmd;
-	if (it == execmap.end()) {
+	if (execit == execmap.end()) {
 		retval.type = error;
 		root["command"] = "error";
 		root["error"] = string(__PRETTY_FUNCTION__) + " unknown command \"" + cmd + "\".\ntype help to list available commands.";
 		retval.msg = root;
+		cmdmutex.unlock();
 		return retval;
-	} else if (!cmd.compare("help") || !cmd.compare("exit")) {
-		// allow help and exit in all cases
+	} else if (alwaysit != cmdAllowAlways.cend()) {
+		// Command should be usable in all cases
 	} else if (currentState == loginpossible || currentState == dologin || currentState == dosignup) {
 		DEBUGPRINT("execute does login");
-		DEBUGPRINT(string("comparison is ") +
-		           std::to_string(cmd.compare("login") && cmd.compare("signup") && cmd.compare("disconnect") && cmd.compare("help")));
-		if (cmd.compare("login") && cmd.compare("signup") && cmd.compare("disconnect") && cmd.compare("help")) {
+		DEBUGPRINT(string("cmd is in usable commands ") + std::to_string(connectit != cmdAllowAfterConnect.end()));
+		if (connectit == cmdAllowAfterConnect.cend()) {
+			// Command was NOT in list of usable commands after login
+			string allowedCommands;
+			for (string s : cmdAllowAlways)
+				allowedCommands += s + " ";
+			for (string s : cmdAllowAfterConnect)
+				allowedCommands += s + " ";
+
 			retval.type = error;
 			root["command"] = "error";
-			root["error"] = string("Not logged in. Available commands are limited to ") + "login" + ", " + "signup" + " and " + "disconnect" + "\n" +
-			                "Use help for usage of these commands.";
+			root["error"] = string("Not logged in. Available commands are limited to ") + allowedCommands + "\n" + "Use help for usage of these commands.";
 			retval.msg = root;
+			cmdmutex.unlock();
 			return retval;
 		}
 	} else if (currentState == versionpossible || currentState == doversion) {
@@ -446,6 +487,7 @@ CmdMan::CmdRet CmdMan::execute(string cmd, vector<string> args) {
 			root["command"] = "error";
 			root["error"] = string("Version not checked yet. No commands avalable.");
 			retval.msg = root;
+			cmdmutex.unlock();
 			return retval;
 		}
 	} else if (currentState == connectionpossible) {
@@ -454,12 +496,16 @@ CmdMan::CmdRet CmdMan::execute(string cmd, vector<string> args) {
 		if (cmd.compare("version") && cmd.compare("connect")) {
 			retval.type = error;
 			root["command"] = "error";
-			root["error"] = string("Not connected. Connect using \"connect ip [port]\".");
+			root["error"] = "Not connected. Please connect.";
 			retval.msg = root;
+			cmdmutex.unlock();
 			return retval;
 		}
 	}
-	return (this->*(execmap[cmd]))(args);
+
+	retval = (this->*(execmap[cmd]))(args);
+	cmdmutex.unlock();
+	return retval;
 }
 
 CmdMan::CmdRet CmdMan::cmdDeleteme(vector<string> args) {
@@ -467,14 +513,13 @@ CmdMan::CmdRet CmdMan::cmdDeleteme(vector<string> args) {
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
 
-	root["command"] = "deleteme";
-
 	if (args.size() < 1) {
 		retval.type = error;
-		root["accept"] = false;
-		root["error"] = "not enough arguments, at least 1 argument required";
+		root["command"] = "error";
+		root["error"] = "Not enough arguments, deleteme requires at least 1 argument.";
 	} else {
 		retval.type = send;
+		root["command"] = "deleteme";
 		root["pass"] = args[0];
 	}
 	retval.msg = root;
@@ -486,13 +531,13 @@ CmdMan::CmdRet CmdMan::cmdKeyfile(vector<string> args) {
 	CmdRet retval;
 	Json::Value root;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
-	root["command"] = "keyfile";
 	if (args.size() < 1) {
 		retval.type = error;
-		root["accept"] = false;
-		root["error"] = "not enough arguments, at least 1 argument required";
+		root["command"] = "error";
+		root["error"] = "Not enough arguments, keyfile requires at least 1 argument.";
 	} else {
 		DEBUGPRINT(string(__PRETTY_FUNCTION__) + " haveargs");
+		root["command"] = "keyfile";
 		if (!fileman.openKey(args[0])) {
 			DEBUGPRINT(string(__PRETTY_FUNCTION__) + " openkey fail");
 			root["accept"] = false;
@@ -555,13 +600,12 @@ CmdMan::CmdRet CmdMan::cmdLogin(vector<string> args) {
 
 	if (args.size() < 2) {
 		retval.type = error;
-		root["command"] = "login";
-		root["accept"] = false;
-		root["error"] = "not enough arguments, at least 2 argument required";
+		root["command"] = "error";
+		root["error"] = "Not enough arguments, login requires at least 2 arguments.";
 	} else {
 		if (currentState == loginpossible) {
 			currentState = dologin;
-			root["user"] = args[0];
+			root["user"] = username = args[0];
 			root["pass"] = args[1];
 			root["login"] = true;
 			root["cancel"] = false;
@@ -586,13 +630,12 @@ CmdMan::CmdRet CmdMan::cmdSignup(vector<string> args) {
 
 	if (args.size() < 2) {
 		retval.type = error;
-		root["command"] = "signup";
-		root["accept"] = false;
-		root["error"] = "not enough arguments, at least 2 argument required";
+		root["command"] = "error";
+		root["error"] = "Not enough arguments, signup requires at least 2 arguments.";
 	} else {
 		if (currentState == loginpossible) {
 			currentState = dosignup;
-			root["user"] = args[0];
+			root["user"] = username = args[0];
 			root["pass"] = args[1];
 			root["login"] = false;
 			root["cancel"] = false;
@@ -614,13 +657,13 @@ CmdMan::CmdRet CmdMan::cmdQueue(vector<string> args) {
 	CmdRet retval;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
-	root["command"] = "queue";
 
 	if (args.size() < 1) {
 		retval.type = error;
-		root["accept"] = false;
-		root["error"] = "not enough arguments, at least 1 argument required";
+		root["command"] = "error";
+		root["error"] = "Not enough arguments, queue requires at least 1 argument.";
 	} else {
+		root["command"] = "queue";
 		root["file"] = args[0];
 		retval.type = send;
 	}
@@ -633,13 +676,13 @@ CmdMan::CmdRet CmdMan::cmdDequeue(vector<string> args) {
 	CmdRet retval;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
-	root["command"] = "dequeue";
 
 	if (args.size() < 1) {
 		retval.type = error;
-		root["accept"] = false;
-		root["error"] = "not enough arguments, at least 1 argument required";
+		root["command"] = "error";
+		root["error"] = "Not enough arguments, dequeue requires at least 1 argument.";
 	} else {
+		root["command"] = "dequeue";
 		root["file"] = args[0];
 		retval.type = send;
 	}
@@ -683,6 +726,7 @@ CmdMan::CmdRet CmdMan::handle(Json::Value root) {
 	CmdRet retval;
 	Json::Value output;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+	cmdmutex.lock();
 	if (currentState == doversion)
 		root["command"] = "version";
 	else if (currentState == dosignup)
@@ -698,16 +742,22 @@ CmdMan::CmdRet CmdMan::handle(Json::Value root) {
 		output["command"] = "error";
 		output["error"] = string(__PRETTY_FUNCTION__) + " unknown command \"" + root["command"].asString() + "\".\nEnsure code is implemented.";
 		retval.msg = output;
+		cmdmutex.unlock();
 		return retval;
 	}
-	return (this->*(handlemap[root["command"].asString()]))(root);
+	retval = (this->*(handlemap[root["command"].asString()]))(root);
+	cmdmutex.unlock();
+	return retval;
 }
 
 CmdMan::CmdRet CmdMan::handleStatus(Json::Value root) {
 	CmdRet retval;
+	Json::Value output;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	retval.type = print;
-	retval.msg = root;
+	output["command"] = "status";
+	output["response"] = username + "@" + ip + ":" + std::to_string(port) + " - Status: " + root["response"].asString();
+	retval.msg = output;
 
 	return retval;
 }
@@ -778,7 +828,6 @@ CmdMan::CmdRet CmdMan::handlePutdata(Json::Value root) {
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	output["command"] = "putdata";
 	output["file"] = fileman.getPutName();
-	output["speed"] = 0.0f; // TODO
 	output["cancel"] = true;
 
 	if (root["cancel"].asBool()) {
@@ -807,8 +856,6 @@ CmdMan::CmdRet CmdMan::handlePutdata(Json::Value root) {
 		if (!root["received"].asInt()) {
 			// everything sent
 			retval.type = print;
-			// TODO
-			//~ retval.msg = "succesfully uploaded file " + fileman.getPutName();
 			fileman.closePut();
 		} else {
 			retval.type = print | send;
@@ -856,7 +903,6 @@ CmdMan::CmdRet CmdMan::handleGetdata(Json::Value root) {
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	output["command"] = "getdata";
 	output["file"] = fileman.getGetName();
-	output["speed"] = 0.0f; // TODO
 	output["cancel"] = true;
 
 	if (root["cancel"].asBool()) {
@@ -883,7 +929,6 @@ CmdMan::CmdRet CmdMan::handleGetdata(Json::Value root) {
 		if (!root["remaining"].asInt()) {
 			// everything received
 			retval.type = print;
-			//~ retval.msg = "succesfully downloaded file " + fileman.getGetName();
 			fileman.closeGet();
 		} else {
 			retval.type = print | send;

+ 102 - 50
cli/src/fileman.cpp

@@ -36,21 +36,7 @@ bool FileMan::isListingExtended() { return islisting && extendedListing; }
 
 bool FileMan::isEncrypted() { return keyenabled; }
 
-vector<vector<char>> FileMan::chunkify(char *data, unsigned int size) {
-	vector<vector<char>> ret;
-	vector<char> chunk;
-	unsigned int i;
-	for (i = 0; (i + max_read_len) < size; i += max_read_len) {
-		chunk = vector<char>(data + i, data + i + max_read_len);
-		ret.push_back(chunk);
-	}
-	chunk = vector<char>(data + i, data + size);
-	ret.push_back(chunk);
-	return ret;
-}
-
 bool FileMan::openPut(const string &path) {
-	std::ifstream putfile;
 	std::fstream keyfile;
 	putpath = path;
 	putname = pathToFilename(path);
@@ -68,32 +54,71 @@ bool FileMan::openPut(const string &path) {
 		}
 		putfile.seekg(0);
 
-		// read into memory and chunkify
+		// if crypto enabled, encrypt now
+		if (keyenabled) {
+			cipherpath = path + ".tmp";
+
+			if (!!std::ifstream(cipherpath)) {
+				// close put if tmp file already exists
+				closePut();
+				return false;
+			}
 
-		char *temp = new char[size + sizeof(signature)];
-		memcpy(temp, signature, sizeof(signature));
-		putfile.read(temp + sizeof(signature), size);
+			std::ofstream cipherFile(cipherpath, std::ios::binary);
+			if (!cipherFile) {
+				closePut();
+				return false;
+			}
 
-		size += sizeof(signature);
+			// resize buffer to also fit IV and tag
+			size_t additionalsize = sizeof(iv) + sizeof(tag);
+
+			// skip first 32 bytes. Those are needed for the header
+			cipherFile.seekp(additionalsize);
 
-		// have all file in memory prepended with signature
-		// if crypto enabled, encrypt now
-		if (keyenabled) {
 			if (!initCryptoE()) {
 				// failed to init crypto, do not continue
-				delete[] temp;
 				return false;
 			}
-			// resize buffer to also fit IV and tag
-			size_t additionalsize = sizeof(iv) + sizeof(tag);
-			unsigned char *cipher = new unsigned char[size];
+
+			unsigned char *plain = new unsigned char[max_read_len];
+			unsigned char *cipher = new unsigned char[max_read_len];
 			int cipherlen;
 
-			if (!EVP_EncryptUpdate(cryptctxe, cipher, &cipherlen, (unsigned char *)temp, size)) {
+			// prepend signature
+			memcpy(plain, signature, sizeof(signature));
+
+			if (!EVP_EncryptUpdate(cryptctxe, cipher, &cipherlen, plain, sizeof(signature))) {
 				setOpensslError();
 				std::cerr << __PRETTY_FUNCTION__ << " failed to update " << getOpensslError() << std::endl;
 			}
-			if (!EVP_EncryptFinal_ex(cryptctxe, cipher + cipherlen, &cipherlen)) {
+
+			cipherFile.write((char *)cipher, cipherlen);
+
+			while ((std::size_t)(putfile.tellg()) + max_read_len < size) {
+				putfile.read((char *)plain, max_read_len);
+
+				if (!EVP_EncryptUpdate(cryptctxe, cipher, &cipherlen, plain, max_read_len)) {
+					setOpensslError();
+					std::cerr << __PRETTY_FUNCTION__ << " failed to update " << getOpensslError() << std::endl;
+				}
+
+				cipherFile.write((char *)cipher, cipherlen);
+			}
+
+			if (putfile.tellg() < size - 1) {
+				int rest = size - putfile.tellg();
+				putfile.read((char *)plain, rest);
+
+				if (!EVP_EncryptUpdate(cryptctxe, cipher, &cipherlen, plain, rest)) {
+					setOpensslError();
+					std::cerr << __PRETTY_FUNCTION__ << " failed to update " << getOpensslError() << std::endl;
+				}
+
+				cipherFile.write((char *)cipher, cipherlen);
+			}
+
+			if (!EVP_EncryptFinal_ex(cryptctxe, cipher, &cipherlen)) {
 				setOpensslError();
 				std::cerr << __PRETTY_FUNCTION__ << " failed to finalize " << getOpensslError() << std::endl;
 			}
@@ -103,29 +128,35 @@ bool FileMan::openPut(const string &path) {
 				std::cerr << __PRETTY_FUNCTION__ << " failed to get tag " << getOpensslError() << std::endl;
 			}
 
-			delete[] temp;
-			temp = new char[size + additionalsize];
-
 			// prepend IV and tag
-			memcpy(temp, iv, sizeof(iv));
-			memcpy(temp + sizeof(iv), tag, sizeof(tag));
-			memcpy(temp + additionalsize, cipher, size);
+			cipherFile.seekp(0);
+			cipherFile.write((char *)iv, sizeof(iv));
+			cipherFile.write((char *)tag, sizeof(tag));
+
+			cipherFile.close();
+			putfile.close();
 
 			delete[] cipher;
+			delete[] plain;
+
+			// now open cipher file as put file
+			putfile.open(cipherpath, std::ios::ate | std::ios::binary);
+			if (putfile.is_open()) {
+				size = putfile.tellg();
+				putfile.seekg(0);
+			} else {
+				closePut();
+				return false;
+			}
 
-			// increase size to also include IV and tag
-			size += sizeof(iv) + sizeof(tag);
 			deinitCryptoE();
+		} else {
+			size += sizeof(signature);
 		}
 
-		// chunkify
-		putdata = chunkify(temp, size);
+		// calculate chunks
 		putsize = size;
-		putchunksRemaining = putchunks = putdata.size();
-
-		delete[] temp;
-
-		// end read into memory and chunkify
+		putchunksRemaining = putchunks = size / max_read_len + (size % max_read_len > 0 ? 1 : 0);
 
 		isputting = true;
 
@@ -181,7 +212,7 @@ bool FileMan::openKey(const string &path) {
 }
 
 void FileMan::closePut() {
-	putdata = vector<vector<char>>();
+	putfile.close();
 	putname = "";
 	putpath = "";
 	putchunks = 0;
@@ -189,6 +220,11 @@ void FileMan::closePut() {
 	isputting = false;
 	memset(iv, 0, sizeof(iv));
 	memset(tag, 0, sizeof(tag));
+
+	if (keyenabled && !!std::ifstream(cipherpath)) {
+		// delete tmp cipher file if it exists
+		std::remove(cipherpath.c_str());
+	}
 }
 
 void FileMan::closeGet() {
@@ -303,7 +339,23 @@ void FileMan::setListChunks(int chunks) {
 }
 
 vector<char> FileMan::readPut() {
-	vector<char> data = putdata[putchunks - putchunksRemaining];
+	int chunk = putchunks - putchunksRemaining;
+	vector<char> data;
+
+	int toread = max_read_len;
+	if (putchunksRemaining == 1) {
+		toread = putsize - (putchunks - 1) * max_read_len;
+	}
+
+	data.resize(toread);
+
+	if (chunk == 0 && !keyenabled) {
+		memcpy(&data.front(), signature, sizeof(signature));
+		putfile.read(&data.front() + sizeof(signature), toread - sizeof(signature));
+	} else {
+		putfile.read(&data.front(), toread);
+	}
+
 	putchunksRemaining--;
 	return data;
 }
@@ -362,7 +414,7 @@ void FileMan::setOpensslError() {
 bool FileMan::initCryptoE() {
 	// try to initialize crypto context
 	if (!cryptoreadye) {
-		std::cerr << __PRETTY_FUNCTION__ << " init crypto" << std::endl;
+		//~ std::cerr << __PRETTY_FUNCTION__ << " init crypto" << std::endl;
 		if (!(cryptctxe = EVP_CIPHER_CTX_new())) {
 			setOpensslError();
 			return false;
@@ -379,7 +431,7 @@ bool FileMan::initCryptoE() {
 bool FileMan::initCryptoD() {
 	// try to initialize crypto context
 	if (!cryptoreadyd) {
-		std::cerr << __PRETTY_FUNCTION__ << " init crypto" << std::endl;
+		//~ std::cerr << __PRETTY_FUNCTION__ << " init crypto" << std::endl;
 		if (!(cryptctxd = EVP_CIPHER_CTX_new())) {
 			setOpensslError();
 			return false;
@@ -437,15 +489,15 @@ void FileMan::writeEnc(const vector<char> data) {
 				setOpensslError();
 				std::cerr << __PRETTY_FUNCTION__ << " failed to update " << getOpensslError() << std::endl;
 			}
-			std::cerr << __PRETTY_FUNCTION__ << " decrypted" << std::endl;
+			//~ std::cerr << __PRETTY_FUNCTION__ << " decrypted" << std::endl;
 			if (!EVP_CIPHER_CTX_ctrl(cryptctxd, EVP_CTRL_GCM_SET_TAG, 16, tag)) {
 				setOpensslError();
 				std::cerr << __PRETTY_FUNCTION__ << " failed to finalize " << getOpensslError() << std::endl;
 			}
-			std::cerr << __PRETTY_FUNCTION__ << " set tag" << std::endl;
+			//~ std::cerr << __PRETTY_FUNCTION__ << " set tag" << std::endl;
 			if (0 < EVP_DecryptFinal_ex(cryptctxd, plain + plainlen, &finallen)) {
 				plainlen += finallen;
-				std::cerr << __PRETTY_FUNCTION__ << " finalized with len " << plainlen << std::endl;
+				//~ std::cerr << __PRETTY_FUNCTION__ << " finalized with len " << plainlen << std::endl;
 				getfile.close();
 				// check signature
 				if (memcmp(plain, signature, 4)) {

+ 75 - 10
cli/src/ioman.cpp

@@ -25,13 +25,13 @@ extern IoMan *gIOMAN;
 
 void ioman_externalDebugPrint(string msg) { gIOMAN->printMessage(msg, gIOMAN->OutMsgType::debug); }
 
-IoMan::IoMan(bool enablessl) : cmdman(fileman, &ioman_externalDebugPrint), recvbuf(16384) {
+IoMan::IoMan(bool enablessl, const char *certfile) : cmdman(fileman, &ioman_externalDebugPrint), recvbuf(16384) {
 	ipstring = "";
 	port = 0;
 	tcpsock = new tcp::socket(ios);
 	connected = false;
 
-	/* to be put elsewhere */
+	sendtimestampValid = false;
 
 	/* setup json stuff */
 	Json::CharReaderBuilder rbuilder;
@@ -45,7 +45,10 @@ IoMan::IoMan(bool enablessl) : cmdman(fileman, &ioman_externalDebugPrint), recvb
 		sslctx = new boost::asio::ssl::context(boost::asio::ssl::context::sslv23);
 		sslctx->set_verify_mode(boost::asio::ssl::verify_peer);
 		sslctx->set_options(boost::asio::ssl::context::no_sslv2);
-		sslctx->load_verify_file("rootca.crt");
+		sslctx->load_verify_file(string(certfile), errcode);
+		if (errcode) {
+			throw std::runtime_error("Couldn't initialize SSL, Boost reports: " + errcode.message());
+		}
 		sslsock = new boost::asio::ssl::stream<tcp::socket &>(*tcpsock, *sslctx);
 	}
 }
@@ -173,19 +176,21 @@ bool IoMan::connect() {
 		if (errcode) {
 			root["error"] = errcode.message();
 			connected = false;
+			disconnect();
 		} else {
 			connected = true;
 			root["error"] = "";
 		}
 		delete ep;
 	}
-	if (usessl) {
+	if (connected && usessl) {
 		// try to do ssl handshake
 		printMessage(string(__PRETTY_FUNCTION__) + string(" doing ssl handshake with ") + ipstring, debug);
 		sslsock->handshake(boost::asio::ssl::stream_base::client, errcode);
 		if (errcode) {
-			root["error"] = errcode.message();
+			root["error"] = string("couldnt connect via ssl: ") + errcode.message();
 			connected = false;
+			disconnect();
 		} else {
 			connected = true;
 			root["error"] = "";
@@ -198,13 +203,23 @@ bool IoMan::connect() {
 
 void IoMan::disconnect() {
 	printMessage("IoMan::disconnect()", debug);
+
+	if (connected) {
+		connected = false;
+		runnetwork = false;
+	}
+
+	if (usessl)
+		sslsock->shutdown(errcode);
+	if (errcode)
+		printMessage(string(__PRETTY_FUNCTION__) + string("ssl shutdown says ") + errcode.message(), debug);
 	tcpsock->shutdown(tcp::socket::shutdown_both, errcode);
 	if (errcode)
 		printMessage(string(__PRETTY_FUNCTION__) + string("tcp shutdown says ") + errcode.message(), debug);
 	tcpsock->close(errcode);
 	if (errcode)
-		printMessage(string(__PRETTY_FUNCTION__) + string("tcp shutdown says ") + errcode.message(), debug);
-	connected = false;
+		printMessage(string(__PRETTY_FUNCTION__) + string("tcp close says ") + errcode.message(), debug);
+	cmdman.stateSetDisconnected();
 }
 
 bool IoMan::init() {
@@ -232,6 +247,8 @@ void IoMan::networkMain() {
 	Json::Value root;
 	unsigned int jsonsize, readsize;
 
+	bool firstWasGood = false;
+
 	printMessage("IoMan::networkMain() begin", debug);
 	networkmutex.lock();
 	while (runnetwork) {
@@ -255,15 +272,25 @@ void IoMan::networkMain() {
 		printMessage(string(__PRETTY_FUNCTION__) + string(" asio::read() ok ") + std::to_string(readsize), debug);
 		// printMessage(string("have ") + std::to_string(toprocess.size()) +
 		// string(" commands"), debug);
+
 		if (readsize < 1) {
+			printMessage(string(__PRETTY_FUNCTION__) + string(" no read size stopping network"), debug);
+			networkmutex.lock();
+			runnetwork = false;
+			networkmutex.unlock();
 			break;
 		}
+
+		timestampmutex.lock();
+		sendtimestampValid = false;
+		timestampmutex.unlock();
+
 		if (errcode && errcode != boost::asio::error::eof) {
 			printMessage("IoMan::networkMain() couldnt read json data\n" + errcode.message(), debug);
 			continue;
 		}
 		recvjson = (char *)(boost::asio::buffer_cast<const char *>(recvbuf.data()));
-		recvjson[readsize] = 0;
+		recvjson[recvbuf.size()] = 0;
 		while (strchr(recvjson, '\n')) {
 			// parse
 			jsonsize = strchr(recvjson, '\n') - recvjson + 1;
@@ -272,12 +299,17 @@ void IoMan::networkMain() {
 
 			if (!reader->parse(recvjson, recvjson + jsonsize, &root, &jsonerror)) {
 				printMessage("IoMan::networkMain() couldnt parse json data: " + jsonerror, debug);
+				if (firstWasGood) {
+					// we found garbage at the end
+					break;
+				}
+				// we found garbage at the beginning
 				recvbuf.consume(jsonsize);
 				recvjson += jsonsize;
 				continue;
 			}
+			firstWasGood = true;
 			recvbuf.consume(jsonsize);
-			readsize -= jsonsize;
 
 			printMessage(string(__PRETTY_FUNCTION__) + string(" remaining recvbuf ") + string(boost::asio::buffer_cast<const char *>(recvbuf.data())), debug);
 
@@ -285,6 +317,7 @@ void IoMan::networkMain() {
 			// store locally
 			toput.push_back(root);
 		}
+		firstWasGood = false;
 
 		if (toput.size()) {
 			// put into global vector
@@ -298,7 +331,7 @@ void IoMan::networkMain() {
 
 		// clean up local stuff
 		toput = vector<Json::Value>();
-		recvbuf.consume(readsize);
+		recvbuf.consume(recvbuf.size() + 1);
 		networkmutex.lock();
 	}
 }
@@ -366,10 +399,23 @@ void IoMan::handleInCmdResponse(CmdMan::CmdRet cmdret) {
 	}
 	if (cmdret.type & CmdMan::rettype::send) {
 		printMessage("IoMan::inputMain() sending json \"" + Json::writeString(wbuilder, cmdret.msg) + "\"", debug);
+
+		timestampmutex.lock();
+		if (!sendtimestampValid) {
+			if (cmdret.type & CmdMan::rettype::noanswerexpected) {
+				// there will be no answer from the server, do not set a timestamp
+			} else {
+				sendtimestampValid = true;
+				time(&sendtimestamp); // set timestamp
+			}
+		}
+		timestampmutex.unlock();
+
 		if (usessl)
 			boost::asio::write(*sslsock, buffer(Json::writeString(wbuilder, cmdret.msg) + "\n"), errcode);
 		else
 			boost::asio::write(*tcpsock, buffer(Json::writeString(wbuilder, cmdret.msg) + "\n"), errcode);
+
 		if (errcode) {
 			printMessage("IoMan::inputMain() couldnt send json data\n" + errcode.message() + "\n", debug);
 			return;
@@ -555,6 +601,23 @@ void IoMan::run() {
 	while (runmain) {
 		mainmutex.unlock();
 
+		timestampmutex.lock();
+		if (sendtimestampValid && (!runnetwork || std::difftime(time(NULL), sendtimestamp) > 15)) {
+			// answer took more than 15 seconds or network thread stopped
+			runnetwork = false;
+			disconnect();
+			tnetwork.join();
+			// cmdman gets informed inside disconnect method
+			// inform the user by giving output
+			Json::Value root;
+			root["command"] = "connectionerror";
+			root["error"] = "The server does not respond. You are now disconnected.";
+			printMessage(Json::writeString(wbuilder, root), normal);
+
+			sendtimestampValid = false;
+		}
+		timestampmutex.unlock();
+
 		poll(&inpipestatus, 1, 100);
 
 		if (inpipestatus.revents & POLLIN) {
@@ -569,6 +632,8 @@ void IoMan::run() {
 	rl_replace_line("", 0);
 	rl_redisplay();
 
+	rl_clear_history();
+
 	// Remove the handler
 	rl_callback_handler_remove();
 }

+ 1 - 1
cli/src/machineioman.cpp

@@ -3,7 +3,7 @@
 #include <iostream>
 #include <vector>
 
-MachineIoMan::MachineIoMan(bool usessl, bool beverbose) : IoMan(usessl) { verbose = beverbose; }
+MachineIoMan::MachineIoMan(bool usessl, const char *certfile, bool beverbose) : IoMan(usessl, certfile) { verbose = beverbose; }
 
 void MachineIoMan::printMessage(std::string msg, OutMsgType type) {
 	switch (type) {

+ 10 - 6
cli/src/main.cpp

@@ -19,12 +19,13 @@ void show_help(char *exec) {
 int main(int argc, char **argv) {
 	bpo::options_description desc("Options");
 	desc.add_options()("help", "show this help")("machine", "switch to machine mode for I/O (not designed for user interaction)")(
-	    "batch", bpo::value<std::string>(), "run operations from arg as batch file")("usessl", "enable ssl for connection to server")("verbose",
-	                                                                                                                                  "enable debug output");
+	    "batch", bpo::value<std::string>(), "run operations from arg as batch file")("usessl", bpo::value<std::string>(),
+	                                                                                 "enable ssl for connection to server")("verbose", "enable debug output");
 
 	bpo::variables_map vm;
 	bool machine = false, usessl = false, batch = false, verbose = false;
 	const char *file = NULL;
+	const char *certfile = NULL;
 	IoMan *ioman;
 
 	try {
@@ -46,21 +47,23 @@ int main(int argc, char **argv) {
 			batch = true;
 		}
 		if (vm.count("usessl")) {
+			certfile = vm["usessl"].as<std::string>().c_str();
 			usessl = true;
 		}
 		if (vm.count("verbose")) {
 			verbose = true;
 		}
 	} catch (const bpo::error &ex) {
-		std::fprintf(stderr, "%s\n", ex.what());
+		std::cerr << ex.what() << std::endl;
+		return 1;
 	}
 	std::printf("machine mode is %d file is %s enablessl is %d verbose is %d\n", machine, file ? file : "", usessl, verbose);
 	if (batch) {
-		ioman = new BatchIoMan(usessl, verbose, file);
+		ioman = new BatchIoMan(usessl, certfile, verbose, file);
 	} else if (machine) {
-		ioman = new MachineIoMan(usessl, verbose);
+		ioman = new MachineIoMan(usessl, certfile, verbose);
 	} else {
-		ioman = new UserIoMan(usessl, verbose);
+		ioman = new UserIoMan(usessl, certfile, verbose);
 	}
 	gIOMAN = ioman;
 	if (ioman->init()) {
@@ -69,4 +72,5 @@ int main(int argc, char **argv) {
 	delete ioman;
 	gIOMAN = NULL;
 	std::printf("done\n");
+	return 0;
 }

+ 56 - 36
cli/src/userioman.cpp

@@ -4,7 +4,7 @@
 #include <readline/readline.h>
 #include <vector>
 
-UserIoMan::UserIoMan(bool usessl, bool beverbose) : IoMan(usessl) {
+UserIoMan::UserIoMan(bool usessl, const char *certfile, bool beverbose) : IoMan(usessl, certfile) {
 	/* setup json stuff */
 	Json::CharReaderBuilder rbuilder;
 	wbuilder.settings_["indentation"] = "";
@@ -13,6 +13,7 @@ UserIoMan::UserIoMan(bool usessl, bool beverbose) : IoMan(usessl) {
 
 	/* initialize print command map */
 	printmap["error"] = &UserIoMan::printError;
+	printmap["connectionerror"] = &UserIoMan::printError;
 	printmap["connect"] = &UserIoMan::printConnect;
 	printmap["help"] = &UserIoMan::printHelp;
 	printmap["extendedstatus"] = &UserIoMan::printExtendedstatus;
@@ -25,8 +26,8 @@ UserIoMan::UserIoMan(bool usessl, bool beverbose) : IoMan(usessl) {
 	printmap["version"] = &UserIoMan::printVersion;
 	printmap["login"] = &UserIoMan::printLogin;
 	printmap["signup"] = &UserIoMan::printSignup;
-	printmap["putdata"] = &UserIoMan::printPutdata;
-	printmap["getdata"] = &UserIoMan::printGetdata;
+	//~ printmap["putdata"] = &UserIoMan::printPutdata;
+	//~ printmap["getdata"] = &UserIoMan::printGetdata;
 	printmap["head"] = &UserIoMan::printHead;
 	printmap["deletefile"] = &UserIoMan::printDeletefile;
 	printmap["deleteme"] = &UserIoMan::printDeleteme;
@@ -41,13 +42,7 @@ UserIoMan::~UserIoMan() { delete reader; }
 
 void UserIoMan::printMessage(std::string msg, OutMsgType type) {
 	Json::Value root;
-	msgmutex.lock();
-
-	char *savedline = rl_copy_text(0, rl_end);
-	int savedpoint = rl_point;
-	rl_set_prompt("");
-	rl_replace_line("", 0);
-	rl_redisplay();
+	vector<string>::const_iterator connectit;
 
 	switch (type) {
 	case normal:
@@ -61,24 +56,33 @@ void UserIoMan::printMessage(std::string msg, OutMsgType type) {
 		break;
 	}
 	case debug: {
-		if (verbose)
+		if (verbose) {
+			msgmutex.lock();
+
+			char *savedline = rl_copy_text(0, rl_end);
+			int savedpoint = rl_point;
+			rl_set_prompt("");
+			rl_replace_line("", 0);
+			rl_redisplay();
+
 			std::cerr << msg << std::endl;
+
+			rl_set_prompt(getCmdPrompt().c_str());
+			rl_replace_line(savedline, 0);
+			rl_point = savedpoint;
+			rl_redisplay();
+			free(savedline);
+
+			msgmutex.unlock();
+		}
 		break;
 	}
 	}
-
-	rl_set_prompt(getCmdPrompt().c_str());
-	rl_replace_line(savedline, 0);
-	rl_point = savedpoint;
-	rl_redisplay();
-	free(savedline);
-
-	msgmutex.unlock();
 }
 
 void UserIoMan::printWelcomeMessage() {
 	std::cout << std::endl
-	          << "Please connect to a server via \"connect <ip> <port>\"," << std::endl
+	          << "Please connect to a server via \"connect <ip> <port>\", then" << std::endl
 	          << "login by entering \"login <username> <password>\"" << std::endl
 	          << "or sign up and log in with \"signup <username> <password>\"." << std::endl
 	          << std::endl;
@@ -90,17 +94,33 @@ void UserIoMan::printJson(Json::Value root) {
 	map<string, void (UserIoMan::*)(Json::Value)>::iterator it = printmap.find(root["command"].asString());
 	if (it == printmap.end()) {
 		// this should never happen outside of development
-		printMessage(string(__PRETTY_FUNCTION__) + " unknown command \"" + root["command"].asString() + "\".\nensure code is implemented.", debug);
+		printMessage(string(__PRETTY_FUNCTION__) + " unknown command \"" + root["command"].asString() + "\".\nEnsure code is implemented.", debug);
 		return;
 	}
+	msgmutex.lock();
+
+	char *savedline = rl_copy_text(0, rl_end);
+	int savedpoint = rl_point;
+	rl_set_prompt("");
+	rl_replace_line("", 0);
+	rl_redisplay();
+
 	(this->*(printmap[root["command"].asString()]))(root);
+
+	rl_set_prompt(getCmdPrompt().c_str());
+	rl_replace_line(savedline, 0);
+	rl_point = savedpoint;
+	rl_redisplay();
+	free(savedline);
+
+	msgmutex.unlock();
 }
 
 void UserIoMan::printError(Json::Value root) { std::cout << "Error: " << root["error"].asString() << std::endl; }
 
 void UserIoMan::printConnect(Json::Value root) {
 	if (!root["accept"].asBool()) {
-		std::cout << "Couldnt connect to " << root["address"].asString() << ":" << root["port"].asUInt() << std::endl
+		std::cout << "Couldnt connect to " << root["address"].asString() << ":" << root["port"].asUInt() << "." << std::endl
 		          << "Reason: " << root["error"].asString() << std::endl;
 	}
 }
@@ -111,7 +131,7 @@ void UserIoMan::printHelp(Json::Value root) {
 		std::cout << i.asString() << std::endl;
 }
 
-void UserIoMan::printStatus(Json::Value root) { std::cout << "Server reports status: " << root["response"].asString() << std::endl; }
+void UserIoMan::printStatus(Json::Value root) { std::cout << root["response"].asString() << std::endl; }
 
 void UserIoMan::printExtendedstatus(Json::Value root) {
 
@@ -201,7 +221,7 @@ void UserIoMan::printGet(Json::Value root) {
 			std::cout << "Download request failed: " << root["error"].asString() << std::endl;
 		}
 	} else
-		std::cout << "Begin downloading file " << root["file"].asString() << std::endl;
+		std::cout << "Begin downloading file " << root["file"].asString() << "." << std::endl;
 }
 
 void UserIoMan::printList(Json::Value root) {
@@ -238,7 +258,7 @@ void UserIoMan::printExtendedlist(Json::Value root) {
 				} else if (encrypted == "undecryptable") {
 					decryptable = "\033[31mno\033[0m           "; // "no" in red
 				} else if (encrypted == "unknown") {
-					decryptable = "unknown      "; // "no" in red
+					decryptable = "unknown      ";
 				} else {
 					decryptable = "plaintext    ";
 				}
@@ -258,8 +278,8 @@ void UserIoMan::printExtendedlist(Json::Value root) {
 
 void UserIoMan::printVersion(Json::Value root) {
 	if (!root["accept"].asBool()) {
-		std::cout << "Version check failed. Server reports " << root["serverversion"].asString() << " but client is " << root["clientversion"].asString()
-		          << std::endl;
+		std::cout << "Version check failed. Server reports version " << root["serverversion"].asString() << " but client is "
+		          << root["clientversion"].asString() << "." << std::endl;
 	} else
 		std::cout << "Version check ok." << std::endl;
 }
@@ -293,23 +313,23 @@ void UserIoMan::printListdata(Json::Value root) {}
 
 void UserIoMan::printHead(Json::Value root) {
 	if (!root["accept"].asBool())
-		std::cout << "Request of the first four bytes failed. " << root["error"].asString() << std::endl;
+		std::cout << "Request of the first few bytes failed. " << root["error"].asString() << std::endl;
 	else
-		std::cout << "First four bytes of file " << root["file"].asString() << " are: " << root["data"].asString() << std::endl;
+		std::cout << "First few bytes of file " << root["file"].asString() << " are: " << root["data"].asString() << std::endl;
 }
 
 void UserIoMan::printDeletefile(Json::Value root) {
 	if (!root["accept"].asBool())
-		std::cout << "Deletion of file " << root["file"] << " failed. " << root["error"].asString() << std::endl;
+		std::cout << "Deletion of file " << root["file"] << " failed: " << root["error"].asString() << std::endl;
 	else
-		std::cout << "File " << root["file"] << " deleted succesfully" << std::endl;
+		std::cout << "File " << root["file"] << " deleted succesfully." << std::endl;
 }
 
 void UserIoMan::printKeyfile(Json::Value root) {
 	if (!root["accept"].asBool())
 		std::cout << "Couldnt select keyfile " << root["file"].asString() << ": " << root["error"].asString() << std::endl;
 	else
-		std::cout << "Using keyfile " << root["file"].asString() << std::endl;
+		std::cout << "Using keyfile " << root["file"].asString() << "." << std::endl;
 }
 
 void UserIoMan::printClosekey(Json::Value root) {
@@ -321,16 +341,16 @@ void UserIoMan::printClosekey(Json::Value root) {
 
 void UserIoMan::printQueue(Json::Value root) {
 	if (!root["accept"].asBool())
-		std::cout << "Queueing of file " << root["file"] << " failed. " << root["error"].asString() << std::endl;
+		std::cout << "Queueing of file " << root["file"] << " failed: " << root["error"].asString() << std::endl;
 	else
-		std::cout << "File " << root["file"] << " queued succesfully" << std::endl;
+		std::cout << "File " << root["file"] << " queued succesfully." << std::endl;
 }
 
 void UserIoMan::printDequeue(Json::Value root) {
 	if (!root["accept"].asBool())
-		std::cout << "Dequeueing of file " << root["file"] << " failed. " << root["error"].asString() << std::endl;
+		std::cout << "Dequeueing of file " << root["file"] << " failed: " << root["error"].asString() << std::endl;
 	else
-		std::cout << "File " << root["file"] << " dequeued succesfully" << std::endl;
+		std::cout << "File " << root["file"] << " dequeued succesfully." << std::endl;
 }
 
 void UserIoMan::printNotifications(Json::Value root) {

+ 7 - 2
cli/test/CMakeLists.txt

@@ -19,22 +19,27 @@ target_link_libraries(cmdman_test ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES}
 add_executable(cryptotest_gcm test/cryptotest_gcm.c)
 target_link_libraries(cryptotest_gcm ${OPENSSL_LIBRARIES})
 
+if(NOT DEFINED DAEMONFILEPATH)
+    set(DAEMONFILEPATH "../../daemon/build/files")
+endif()
+
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/samplekey1.bin ${CMAKE_BINARY_DIR}/test/samplekey1.bin COPYONLY)
 
 add_test(logintest_pos ${SHELL} ${CMAKE_BINARY_DIR}/test/logintest_pos.sh)
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/logintest_pos.sh ${CMAKE_BINARY_DIR}/test/logintest_pos.sh COPYONLY)
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/logintest_pos.txt ${CMAKE_BINARY_DIR}/test/logintest_pos.txt COPYONLY)
+
 add_test(logintest_neg ${SHELL} ${CMAKE_BINARY_DIR}/test/logintest_neg.sh)
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/logintest_neg.sh ${CMAKE_BINARY_DIR}/test/logintest_neg.sh COPYONLY)
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/logintest_neg.txt ${CMAKE_BINARY_DIR}/test/logintest_neg.txt COPYONLY)
 
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/samplefile.txt ${CMAKE_BINARY_DIR}/test/samplefile.txt COPYONLY)
 
-add_test(cryptoput ${SHELL} ${CMAKE_BINARY_DIR}/test/cryptoput.sh)
+add_test(cryptoput ${SHELL} ${CMAKE_BINARY_DIR}/test/cryptoput.sh ${DAEMONFILEPATH})
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/cryptoput.sh ${CMAKE_BINARY_DIR}/test/cryptoput.sh COPYONLY)
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/cryptoput.txt ${CMAKE_BINARY_DIR}/test/cryptoput.txt COPYONLY)
 
-add_test(cryptoget ${SHELL} ${CMAKE_BINARY_DIR}/test/cryptoget.sh)
+add_test(cryptoget ${SHELL} ${CMAKE_BINARY_DIR}/test/cryptoget.sh ${DAEMONFILEPATH})
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/cryptoget.sh ${CMAKE_BINARY_DIR}/test/cryptoget.sh COPYONLY)
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/cryptoget.txt ${CMAKE_BINARY_DIR}/test/cryptoget.txt COPYONLY)
 

+ 136 - 28
cli/test/cmdman_test.cpp

@@ -65,8 +65,7 @@ TEST(testConnect, TooFewArgs) {
 
 	// check things
 	EXPECT_EQ(retvalCmd.type, CmdMan::error);
-	EXPECT_EQ(retvalCmd.msg["command"].asString(), "connect");
-	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "error");
 	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
 
 	EXPECT_TRUE(cm.isNotConnected());
@@ -144,6 +143,56 @@ TEST(testConnect, Negative) {
 	EXPECT_TRUE(cm.isNotConnected());
 }
 
+TEST(testConnect, AlreadyConnected) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initConnected();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "connect";
+	args = {"1.222.33.4", "1337"};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	// cm.stateSetConnectionOk() not called
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "error");
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isConnected());
+}
+
+TEST(testConnect, AlreadyLoggedIn) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "connect";
+	args = {"1.222.33.4", "1337"};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	// cm.stateSetConnectionOk() not called
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "error");
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
 /* =====================================
  * tests for
  * version check, signup, login
@@ -244,7 +293,6 @@ TEST(testVersion, AlreadyLoggedIn) {
 
 	// check things
 	EXPECT_EQ(retvalCmd.type, CmdMan::error);
-	// TODO: check some more things maybe
 
 	EXPECT_TRUE(cm.isLoggedIn());
 }
@@ -337,8 +385,7 @@ TEST(testLogin, TooFewArgs) {
 
 	// check things
 	EXPECT_EQ(retvalCmd.type, CmdMan::error);
-	EXPECT_EQ(retvalCmd.msg["command"].asString(), "login");
-	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "error");
 	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
 
 	EXPECT_TRUE(cm.isVersionChecked());
@@ -498,8 +545,7 @@ TEST(testSignup, TooFewArgs) {
 
 	// check things
 	EXPECT_EQ(retvalCmd.type, CmdMan::error);
-	EXPECT_EQ(retvalCmd.msg["command"].asString(), "signup");
-	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "error");
 	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
 
 	EXPECT_TRUE(cm.isVersionChecked());
@@ -560,7 +606,7 @@ TEST(testDisconnect, NotLoggedIn) {
 	retvalCmd = cm.execute(cmd, args);
 
 	// check things
-	EXPECT_EQ(retvalCmd.type, CmdMan::send | CmdMan::close);
+	EXPECT_EQ(retvalCmd.type, CmdMan::send | CmdMan::close | CmdMan::noanswerexpected);
 	EXPECT_FALSE(retvalCmd.msg["login"].asBool());
 	EXPECT_EQ(retvalCmd.msg["user"].asString(), "");
 	EXPECT_EQ(retvalCmd.msg["pass"].asString(), "");
@@ -641,7 +687,7 @@ TEST(testExit, NotLoggedIn) {
 
 	retvalCmdDisconnect = cm.execute("disconnect", args);
 
-	EXPECT_EQ(retvalCmdDisconnect.type, CmdMan::send | CmdMan::close);
+	EXPECT_EQ(retvalCmdDisconnect.type, CmdMan::send | CmdMan::close | CmdMan::noanswerexpected);
 	EXPECT_FALSE(retvalCmdDisconnect.msg["login"].asBool());
 	EXPECT_EQ(retvalCmdDisconnect.msg["user"].asString(), "");
 	EXPECT_EQ(retvalCmdDisconnect.msg["pass"].asString(), "");
@@ -697,8 +743,7 @@ TEST(testPut, TooFewArgs) {
 
 	// check things
 	EXPECT_EQ(retvalCmd.type, CmdMan::error);
-	EXPECT_EQ(retvalCmd.msg["command"].asString(), "put");
-	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "error");
 	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
 
 	EXPECT_TRUE(cm.isLoggedIn());
@@ -1169,7 +1214,7 @@ TEST(testHead, TooFewArgs) {
 	retvalCmd = cm.execute(cmd, args);
 
 	EXPECT_EQ(retvalCmd.type, CmdMan::error);
-	EXPECT_EQ(retvalCmd.msg["command"].asString(), "head");
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "error");
 	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
 
 	retvalHdl = cm.handle(root);
@@ -1273,8 +1318,7 @@ TEST(testGet, TooFewArgs) {
 
 	// check things
 	EXPECT_EQ(retvalCmd.type, CmdMan::error);
-	EXPECT_EQ(retvalCmd.msg["command"].asString(), "get");
-	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "error");
 	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
 
 	EXPECT_TRUE(cm.isLoggedIn());
@@ -2337,8 +2381,7 @@ TEST(testDeleteme, TooFewArgs) {
 
 	// check things
 	EXPECT_EQ(retvalCmd.type, CmdMan::error);
-	EXPECT_EQ(retvalCmd.msg["command"].asString(), "deleteme");
-	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "error");
 	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
 
 	EXPECT_TRUE(cm.isLoggedIn());
@@ -2431,8 +2474,7 @@ TEST(testDeletefile, TooFewArgs) {
 
 	// check things
 	EXPECT_EQ(retvalCmd.type, CmdMan::error);
-	EXPECT_EQ(retvalCmd.msg["command"].asString(), "deletefile");
-	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "error");
 	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
 
 	EXPECT_TRUE(cm.isLoggedIn());
@@ -2511,10 +2553,9 @@ TEST(testDeletefile, Negative) {
 /* =====================================
  * test for status
  */
-TEST(testStatus, Test) {
+TEST(testStatus, LoggedIn) {
 	FileManMock fm;
 	CmdManForTest cm(fm, dummyDebugPrint);
-	cm.initLoggedIn();
 
 	std::string cmd;
 	std::vector<std::string> args;
@@ -2522,6 +2563,12 @@ TEST(testStatus, Test) {
 	CmdMan::CmdRet retvalCmd;
 	CmdMan::CmdRet retvalHdl;
 
+	// set ip, port and user name fields of the cmdman
+	cm.execute("connect", {"1.2.3.4", "1337"});
+	cm.initVersionChecked();
+	cm.execute("login", {"usernem", "paswod"});
+	cm.initLoggedIn();
+
 	// prepare cmd/args/root
 	cmd = "status";
 	args = {};
@@ -2539,10 +2586,74 @@ TEST(testStatus, Test) {
 	EXPECT_EQ(retvalHdl.type, CmdMan::print);
 	EXPECT_EQ(retvalHdl.msg["command"].asString(), "status");
 	EXPECT_THAT(retvalHdl.msg["response"].asString(), testing::HasSubstr("fancy response"));
+	EXPECT_THAT(retvalHdl.msg["response"].asString(), testing::HasSubstr("1.2.3.4"));
+	EXPECT_THAT(retvalHdl.msg["response"].asString(), testing::HasSubstr("1337"));
+	EXPECT_THAT(retvalHdl.msg["response"].asString(), testing::HasSubstr("usernem"));
 
 	EXPECT_TRUE(cm.isLoggedIn());
 }
 
+TEST(testStatus, NotConnected) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// set ip and port fields of the cmdman
+	cm.execute("connect", {"1.2.3.4"});
+	cm.initNotConnected();
+
+	// prepare cmd/args
+	cmd = "status";
+	args = {};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::print);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "status");
+	EXPECT_NE(retvalCmd.msg["response"].asString(), "");
+	EXPECT_THAT(retvalCmd.msg["response"].asString(), testing::Not(testing::HasSubstr("1.2.3.4")));
+
+	EXPECT_TRUE(cm.isNotConnected());
+}
+
+TEST(testStatus, NotLoggedIn) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// set ip, port and user name fields of the cmdman
+	cm.initConnected();
+	cm.execute("login", {"usernem", "paswod"});
+	cm.initNotConnected();
+	cm.execute("connect", {"1.2.3.4", "1337"});
+	cm.initVersionChecked(); // not logged in
+
+	// prepare cmd/args
+	cmd = "status";
+	args = {};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::print);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "status");
+	EXPECT_THAT(retvalCmd.msg["response"].asString(), testing::HasSubstr("1.2.3.4"));
+	EXPECT_THAT(retvalCmd.msg["response"].asString(), testing::HasSubstr("1337"));
+	EXPECT_THAT(retvalCmd.msg["response"].asString(), testing::Not(testing::HasSubstr("usernem")));
+
+	EXPECT_TRUE(cm.isVersionChecked());
+}
+
 /* =====================================
  * test for extendedstatus
  */
@@ -2772,8 +2883,7 @@ TEST(testKeyfile, TooFewArgs) {
 
 	// check things
 	EXPECT_EQ(retvalCmd.type, CmdMan::error);
-	EXPECT_EQ(retvalCmd.msg["command"].asString(), "keyfile");
-	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "error");
 	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
 
 	EXPECT_TRUE(cm.isLoggedIn());
@@ -2984,8 +3094,7 @@ TEST(testQueue, TooFewArgs) {
 
 	// check things
 	EXPECT_EQ(retvalCmd.type, CmdMan::error);
-	EXPECT_EQ(retvalCmd.msg["command"].asString(), "queue");
-	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "error");
 	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
 
 	EXPECT_TRUE(cm.isLoggedIn());
@@ -3079,8 +3188,7 @@ TEST(testDequeue, TooFewArgs) {
 
 	// check things
 	EXPECT_EQ(retvalCmd.type, CmdMan::error);
-	EXPECT_EQ(retvalCmd.msg["command"].asString(), "dequeue");
-	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "error");
 	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
 
 	EXPECT_TRUE(cm.isLoggedIn());
@@ -3167,8 +3275,8 @@ TEST(testCommandsWithRequiredLogin, NotLoggedInOrNotConnected) {
 	CmdMan::CmdRet retval1, retval2, retval3;
 
 	// prepare cmd/args
-	std::vector<std::string> cmds = {"put",  "get",        "list",     "extendedlist", "putdata",  "getdata",       "listdata", "extendedlistdata",
-	                                 "head", "deletefile", "deleteme", "keyfile",      "closekey", "extendedstatus"};
+	std::vector<std::string> cmds = {"put",  "get",        "list",     "extendedlist",   "putdata",       "getdata", "listdata", "extendedlistdata",
+	                                 "head", "deletefile", "deleteme", "extendedstatus", "notifications", "queue",   "dequeue"};
 	// as every command works fine with too many args, we will simply pass three to all of them
 	std::vector<std::string> args = {"arg1", "arg2", "arg3"};
 

+ 2 - 2
cli/test/cryptoget.sh

@@ -1,7 +1,7 @@
 #!/bin/bash
 
 SCRIPT_PATH=${0%/*}
-if [ "$0" != "$SCRIPT_PATH" ] && [ "$SCRIPT_PATH" != "" ]; then 
+if [ "$0" != "$SCRIPT_PATH" ] && [ "$SCRIPT_PATH" != "" ]; then
     cd "$SCRIPT_PATH/.."
 fi
 
@@ -16,7 +16,7 @@ echo "using file path $DAEMONFILEPATH"
 
 test/cryptotest_gcm 1 test/samplekey1.bin test/samplefile.txt $DAEMONFILEPATH/samplefile.txt
 
-./ccats-cli --batch test/cryptoget.txt
+bin/ccats-cli --batch test/cryptoget.txt
 
 if [ ! -f test/cryptoget.txt.out ];
 then

+ 2 - 2
cli/test/cryptoput.sh

@@ -1,7 +1,7 @@
 #!/bin/bash
 
 SCRIPT_PATH=${0%/*}
-if [ "$0" != "$SCRIPT_PATH" ] && [ "$SCRIPT_PATH" != "" ]; then 
+if [ "$0" != "$SCRIPT_PATH" ] && [ "$SCRIPT_PATH" != "" ]; then
     cd "$SCRIPT_PATH/.."
 fi
 
@@ -15,7 +15,7 @@ fi
 echo "am in directory $PWD"
 echo "using file path $DAEMONFILEPATH"
 
-./ccats-cli --batch test/cryptoput.txt
+bin/ccats-cli --batch test/cryptoput.txt
 
 if [ ! -f test/cryptoput.txt.out ];
 then

+ 2 - 2
cli/test/logintest_neg.sh

@@ -1,7 +1,7 @@
 #!/bin/bash
 
 SCRIPT_PATH=${0%/*}
-if [ "$0" != "$SCRIPT_PATH" ] && [ "$SCRIPT_PATH" != "" ]; then 
+if [ "$0" != "$SCRIPT_PATH" ] && [ "$SCRIPT_PATH" != "" ]; then
     cd "$SCRIPT_PATH/.."
 fi
 
@@ -9,7 +9,7 @@ if [ $# -eq 1 ];
 	then DAEMONFILEPATH="$1"
 fi
 
-./ccats-cli --batch test/logintest_neg.txt
+bin/ccats-cli --batch test/logintest_neg.txt
 
 if [ ! -f test/logintest_neg.txt.err ];
 then

+ 2 - 2
cli/test/logintest_pos.sh

@@ -1,11 +1,11 @@
 #!/bin/bash
 
 SCRIPT_PATH=${0%/*}
-if [ "$0" != "$SCRIPT_PATH" ] && [ "$SCRIPT_PATH" != "" ]; then 
+if [ "$0" != "$SCRIPT_PATH" ] && [ "$SCRIPT_PATH" != "" ]; then
     cd "$SCRIPT_PATH/.."
 fi
 
-./ccats-cli --batch test/logintest_pos.txt
+bin/ccats-cli --batch test/logintest_pos.txt
 
 if [ ! -f test/logintest_pos.txt.out ];
 then

+ 39 - 0
daemon/Adding-a-Covert-Channel-Mode.md

@@ -0,0 +1,39 @@
+# Adding a Covert Channel Mode
+
+There are multiple abstraction levels on which you can add a covert channel:
+- CovertChannel
+- BidirectionalChannels
+
+## CovertChannel
+The CovertChannel connects the inner network with the outer network. It forwards packets which are not important for the defined filter and can edit the filtered packets.
+To do that a CovertChannel consists of 4 sniffers that run in their own threads.
+- innerForward - forwards packets from inner network to outer network
+- outerForward - forwards packets from outer network to inner network
+- innerChannel - modifies filtered packets from inner network to outer network
+- outerChannel - modifies filtered packets form outer network to inner network
+
+To implement your own channel you should derive from CovertChannel.
+
+### Deriving CovertChannel
+The CovertChannel constructor needs the interface name of the inner and outer network and pcap filter strings to set the filters for the sniffer. The sniffers will call the handle functions when receiving a packet.
+
+When you derive CovertChannel you must implement:
+- handleChannelFromOuter
+- handleChannelFromInner
+
+The pdu parameter is the sniffed packet. This should be modified and send to the other (inner/outer) network.
+
+#### ChannelControls
+So you can control your channel ChannelControls is an interface to provide basic control elements. CovertChannel derives from ChannelControls so you have to implement the virtual functions and methods of ChannelControls as well.
+- sendFile
+- getProgress
+- getTransferStart
+- isTransferRunning
+- reset
+- getFileName
+
+## BidirectionalChannels
+Bidirectional channels are explicit TCP channels. It implements the basic controls from ChannelControls and sets the filter string for TCP connecitons.
+So you can deriving from BidirectionalChannels and you must only implement:
+- handleChannelFromOuter
+- handleChannelFromInner

+ 2 - 1
daemon/CMakeLists.txt

@@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 2.8)
 
 set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/../.cmake_modules/")
-message("${CMAKE_MODULE_PATH}")
 
 project(ccats)
 
@@ -11,6 +10,8 @@ find_package(Threads REQUIRED)
 find_package(Boost 1.70 REQUIRED COMPONENTS system filesystem)
 find_package(Jsoncpp REQUIRED)
 
+add_subdirectory(../libs/libbcrypt/ ${CMAKE_BINARY_DIR}/libbcrypt)
+
 include(src/CMakeLists.txt)
 
 if(ENABLE_TESTS)

+ 16 - 59
daemon/CovertProtocol.md

@@ -2,7 +2,7 @@
 The Covert Protocol is the protocol used to communicate over the covert channel. The communication over the covert channels aims mainly on file transfers.
 
 # Bidirectional Covert Protocol
-A bidirectional communication which uses commands to manage a file transfer.
+A bidirectional communication which uses segment numbers to prevent data loss.
 
 A packet is at least 2 bytes big. The first byte is the header followed by one or multiple data bytes.
 
@@ -17,58 +17,13 @@ A packet is at least 2 bytes big. The first byte is the header followed by one o
 A header contains a command and a segment number. Even if the `no operation` command is used the segment number must be counted.
 
 ```
-|           8 bit           |
-|           header          |
+|          8 bit          |
+|          header         |
 
-| 2 bit |  3 bit  |  3 bit  |
-|  seg  |    -    |   com   |
+| 2 bit |  5 bit  | 1 bit |
+|  seg  |    -    | reset |
 ```
 
-### Command
-Commands which indicate
-
-```
-0x0 no operation
-0x1 send file name size
-0x2 send file name
-0x3 send data size
-0x4 send data
-0x5 -
-0x6 cancel / reset
-0x7 error
-```
-
-The sending commands must be sent in the right order: lower number to higher number
-You can of course send a `no operation` or a `cancel / reset` command. If the order is not followed an `error` answer should be invoked.
-
-
-#### `0x0 no operation`
-No operation is the command which indicates to do nothing. It simply works as ACK to signal the packet was received.
-
-#### `0x1 send file name size`
-A file name size is a one byte number.
-```
-0 < file name size < 256
-```
-
-#### `0x2 send file name`
-A file name is a string which can be up to 255 bytes long.
-This command must be looped until the file name is fully transmitted!
-
-#### `0x3 send data size`
-A data size is a 4 byte unsigned int.
-This command must be looped until the data size is fully transmitted!
-
-#### `0x4 send data`
-Data are as much bytes as communicated in `send data size`.
-This command must be looped until the data is fully transmitted!
-
-#### `0x6 cancel / reset`
-This command resets the transmission. The following segment number is the segment number of the reset packet plus 1.
-
-#### `0x7 error`
-This command signals there was an error and the transmission is to be resetted. The following segment number is the segnemtn number of the error packet plus 1.
-
 
 ### Segment
 The segment number is a counter for the packets in the covert channels. The segment number is increased on the passive server side.
@@ -94,14 +49,16 @@ seg                   seg
   <-------------------- 5
 ```
 
+### Reset
+A reset will will be triggered by a set reset bit.
+Running file transfers will be canceled and the segment counter will be reset to its initial value.
 
+## Data
+The data will be transmitted over a sequential protocol. You could in fact use the data protocol and ditch the bidirectional protocol.
+There are several states of a transfer:
 
-It might happen that a server is not possible to answer or all the answers won't be transmitted. This case should be handled with the `cancel / reset` or `error` command upon reconnection. Maybe the session is recoverable without a reset.
-```
-A                       B
-seg                   seg
-=========================
-1 -------------------->
-1 -------------------->
-1 -------------------->
-```
+0. idle - does nothing
+1. file name size - sends size of file name as 1 byte unsigned int
+2. file name - sends file name chars and loops until the full file name is transmitted
+3. data size - sends 4 bytes of data size and loops until the full data size is transmitted
+4. data - sends n bytes of data and loops until the full file is transmitted

+ 0 - 52
daemon/Daemon-Config-Reference.md

@@ -1,52 +0,0 @@
-# Daemon configuration
-
-The daemon is configurable by config.txt.
-The config file must be in the same directory from where you run the binary.
-
-### Configuration Values
-`port` : The port where the server listens. Must be a valid port.
-`interface` : The sniffer interface you want to use.
-`userdatabase` : The file where userdata is stored in format: user;password
-`deleteAllowed` : Says if a client is allowed to delete files from its file directory
-`filedirectory` : The directory where files from the clients will be stored and read from
-`SSLenabled` : When set to true, the server will only use and accept SSL connections from clients. Set to false to disable this
-`SSLcertificate` : The certificate file to use for SSL connections
-`SSLprivatekey` : The private key file to use for SSL connections
-`SSLdhparams` : The diffie-hellman file to use for SSL connections
-
-### Notes about SSL
-To use SSL, certificates, keys and diffie-hellman parameters are required. To generate these, a convenience script `createsslfiles.sh` is provided.
-The names of the output files are controlled with variables at the top of the script, modify these if desired.
-Assuming default names, place the `user.crt`, `user.key` and `dh2048.pem` files somewhere convenient and configure the server accordingly.
-Place the `rootca.crt` certificate in the directory you intend to run the client from.
-
-If you get an error about SSL related files not being found despite them existing, shorten the names of the files.
-If you cannot connect and the server prints a error related to TLSv1, ensure your version of boost and OpenSSL are up to date.
-
-#### Covert Channel options
-`covertChannelMode`: Sets the covert channel mode. To deactiveate don't set it or set it to none or false.<br/>
-`innerInterface`: The interface of your inner network<br/>
-`outerInterface`: The interface of your outer network<br/>
-
-##### Covert Channel Mode `forward`
-There no further config needed. Forward should work out of the box
-
-##### Covert Channel Mode `tcpurgency`<br/>
-`ownIP`: IP of this server<br/>
-`targetIP`: IP of the target server<br/>
-`targetPort`: Port of the target server<br/>
-`passiveMode`: true - server only reacts to incoming channel | false - server initiates channel<br/>
-`sendFile`: file name in file directory of the file which will be sent after starting the server<br/>
-
-### Example for config.txt
-```
-port=1234
-userdatabase=userStorage.txt
-filedirectory=./files/
-deleteAllowed=true
-SSLenabled=true
-SSLcertificate=user.crt
-SSLprivatekey=user.key
-SSLdhparams=dh2048.pem
-activateCovertChannel=false
-```

+ 69 - 3
daemon/README.md

@@ -11,14 +11,16 @@ make
 ### Tests
 To build tests just set the `ENABLE_TESTS` variable to true and rebuild the program.
 ```
+mkdir build
+cd build
 cmake .. -DENABLE_TESTS=true
 make
 ```
 
 ## Run
-Currently the first argument is the network interface for the sniffer.
+You must [create a config.txt](#daemon-configuration) before running the server.
 ```
-bin/ccats lo
+bin/ccats
 ```
 
 ### Tests
@@ -27,13 +29,14 @@ make test
 ```
 
 ## Installation
+You can adjust the installation directory as you like but don't forget to adjust the `ExecStart` option in the service file. The location of the configuration file is the first parameter for the daemon so you can change it as well.
+Don't forget to create the files folder!!!
 
 After building the program copy executable:
 ```
 sudo cp bin/ccats /usr/bin
 ```
 
-
 Copy service file:
 ```
 sudo cp ccats.service /etc/systemd/system/
@@ -47,3 +50,66 @@ and start it
 ```
 sudo systemctl start ccats.service
 ```
+
+## Daemon configuration
+
+The daemon is configurable by config.txt. You can use another config name using the config name as first parameter when launching the server.
+The config file must be in the same directory from where you run the binary. If the file does not exist, the server will exit immediately.
+
+### General Configuration Values
+`port`: The port where the server listens for clients. Must be a valid port<br/>
+`userdatabase`: The file where userdata is stored in format: user;password<br/>
+`deleteAllowed`: Says if a client is allowed to delete files from its file directory<br/>
+`filedirectory`: The directory where files from the clients will be stored and read from<br/>
+`SSLenabled`: When set to true, the server will only use and accept SSL connections from clients. Set to false to disable this<br/>
+`SSLcertificate`: The certificate file to use for SSL connections<br/>
+`SSLprivatekey`: The private key file to use for SSL connections<br/>
+`SSLdhparams`: The diffie-hellman file to use for SSL connections<br/>
+
+#### Notes about SSL
+To use SSL, certificates, keys and diffie-hellman parameters are required. To generate these, a convenience script `createsslfiles.sh` is provided.
+The names of the output files are controlled with variables at the top of the script, modify these if desired.
+Assuming default names, place the `user.crt`, `user.key` and `dh2048.pem` files somewhere convenient and configure the server accordingly.
+Place the `rootca.crt` certificate in the directory you intend to run the client from.
+
+If you get an error about SSL related files not being found despite them existing, shorten the names of the files.
+If you cannot connect and the server prints a error related to TLSv1, ensure your version of boost and OpenSSL are up to date.
+
+#### Covert Channel Modes
+There are several covert channel modes which will transmit data in other ways. If you do not set this to any of the values below, the server will not have covert channel sending and recieving capabilities, but still answer to requests from clients. In this case, no superuser permissions will be required, as no network interfaces are touched directly.
+`forward`: no data transmission<br/>
+`tcpurgency`: uses the TCP urgency pointer<br/>
+`tcpoptiontimestamp`: uses the TCP option Timestamp to transmit data. WARNING: most OSs use the timestamp so you should not use this option unless you are sure that the communication does not depend on it.<br/>
+`tcpappend`: appends the data to the payload of a TCP packet<br/>
+`tcpoptioncustom`: writes data in a custom option field<br/>
+
+#### General Covert Channel options
+`covertChannelMode`: Sets the covert channel mode. To deactivate don't set it or set it to none or false.<br/>
+`innerInterface`: The interface of your inner network<br/>
+`outerInterface`: The interface of your outer network<br/>
+
+###### Covert Channel Mode `forward`
+No further config is needed. Forward should work out of the box.<br/>
+
+###### Covert Channel Modes `tcpurgency`, `tcpoptiontimestamp`, `tcpappend`, `tcpoptioncustom`
+`targetIP`: IP of the target server<br/>
+`targetPort`: Port of the target server<br/>
+`passiveMode`: true - server only reacts to incoming channel | false - server initiates channel<br/>
+
+### Example for config.txt
+```
+covertChannelMode=tcpurgency
+deleteAllowed=false
+filedirectory=./files/
+innerInterface=eth0
+outerInterface=eth1
+passiveMode=false
+port=1234
+SSLcertificate=user.crt
+SSLdhparams=dh2048.pem
+SSLenabled=true
+SSLprivatekey=user.key
+targetIP=1.2.3.4
+targetPort=443
+userdatabase=userStorage.txt
+```

+ 2 - 2
daemon/ccats.service

@@ -5,7 +5,7 @@ Description=CCats
 Type=simple
 Restart=always
 RestartSec=1
-ExecStart=/usr/bin/ccats
+ExecStart=/usr/bin/ccats /etc/ccats.conf
 
 [Install]
-WantedBy=multi-user.target
+WantedBy=multi-user.target

+ 4 - 12
daemon/include/CovertChannel/BidirectionalChannels.hpp

@@ -21,21 +21,13 @@ public:
 	 *
 	 * @param innerInterface name of the interface of the inner network
 	 * @param outerInterface name of the interface of the outer network
-	 * @param ownIP IP of this server
 	 * @param targetIP IP of the target server
 	 * @param targetPort Port of the target server
 	 */
-	BidirectionalChannels(const std::string &innerInterface, const std::string &outerInterface, const std::string &ownIP, const std::string &targetIP,
-	                      const std::string &targetPort)
-	    : CovertChannel(innerInterface, outerInterface,
-	                    "(not (tcp and " + std::string(PASSIVE ? "src" : "dst") + " host " + targetIP + " and " + std::string(PASSIVE ? "src" : "dst") +
-	                        " port " + targetPort + ")) and (not (dst host " + ownIP + "))",
-	                    "(not (tcp and " + std::string(PASSIVE ? "dst" : "src") + " host " + targetIP + " and " + std::string(PASSIVE ? "dst" : "src") +
-	                        " port " + targetPort + ")) and (not (dst host " + ownIP + "))",
-	                    "tcp and " + std::string(PASSIVE ? "src" : "dst") + " host " + targetIP + " and " + std::string(PASSIVE ? "src" : "dst") + " port " +
-	                        targetPort,
-	                    "tcp and " + std::string(PASSIVE ? "dst" : "src") + " host " + targetIP + " and " + std::string(PASSIVE ? "dst" : "src") + " port " +
-	                        targetPort) {}
+	BidirectionalChannels(const std::string &innerInterface, const std::string &outerInterface, const std::string &targetIP, const std::string &targetPort)
+	    : CovertChannel(innerInterface, outerInterface, "not (tcp and host " + targetIP + " and port " + targetPort + ")",
+	                    "not (tcp and host " + targetIP + " and port " + targetPort + ")", "tcp and host " + targetIP + " and port " + targetPort,
+	                    "tcp and host " + targetIP + " and port " + targetPort) {}
 
 	/**
 	 * Destroys the CovertChannel.

+ 12 - 9
daemon/include/CovertChannel/Channels/TCPAppendChannel.hpp

@@ -20,13 +20,11 @@ public:
 	 *
 	 * @param innerInterface name of the interface of the inner network
 	 * @param outerInterface name of the interface of the outer network
-	 * @param ownIP IP of this server
 	 * @param targetIP IP of the target server
 	 * @param targetPort Port of the target server
 	 */
-	TCPAppendChannel(const std::string &innerInterface, const std::string &outerInterface, const std::string &ownIP, const std::string &targetIP,
-	                 const std::string &targetPort)
-	    : BidirectionalChannels<N, PASSIVE>(innerInterface, outerInterface, ownIP, targetIP, targetPort) {}
+	TCPAppendChannel(const std::string &innerInterface, const std::string &outerInterface, const std::string &targetIP, const std::string &targetPort)
+	    : BidirectionalChannels<N, PASSIVE>(innerInterface, outerInterface, targetIP, targetPort) {}
 
 	/**
 	 * Destroys the CovertChannel.
@@ -53,13 +51,18 @@ protected:
 
 			// read data from payload
 			std::size_t size = payload.size();
-			uint8_t *data = &payload.front();
-			data += size - N;
 
-			BidirectionalChannels<N, PASSIVE>::protocol.receive(data);
+			if (size < N) {
+				std::cerr << __PRETTY_FUNCTION__ << " payload size is too small" << std::endl;
+			} else {
+				uint8_t *data = &payload.front();
+				data += size - N;
 
-			// resize payload
-			payload.resize(size - N);
+				BidirectionalChannels<N, PASSIVE>::protocol.receive(data);
+
+				// resize payload
+				payload.resize(size - N);
+			}
 		}
 
 		BidirectionalChannels<N, PASSIVE>::innerSender.send(pdu);

+ 2 - 4
daemon/include/CovertChannel/Channels/TCPOptionCustomChannel.hpp

@@ -26,13 +26,11 @@ public:
 	 *
 	 * @param innerInterface name of the interface of the inner network
 	 * @param outerInterface name of the interface of the outer network
-	 * @param ownIP IP of this server
 	 * @param targetIP IP of the target server
 	 * @param targetPort Port of the target server
 	 */
-	TCPOptionCustomChannel(const std::string &innerInterface, const std::string &outerInterface, const std::string &ownIP, const std::string &targetIP,
-	                       const std::string &targetPort)
-	    : BidirectionalChannels<N, PASSIVE>(innerInterface, outerInterface, ownIP, targetIP, targetPort) {}
+	TCPOptionCustomChannel(const std::string &innerInterface, const std::string &outerInterface, const std::string &targetIP, const std::string &targetPort)
+	    : BidirectionalChannels<N, PASSIVE>(innerInterface, outerInterface, targetIP, targetPort) {}
 
 	/**
 	 * Destroys the CovertChannel.

+ 2 - 4
daemon/include/CovertChannel/Channels/TCPOptionTimestampChannel.hpp

@@ -23,13 +23,11 @@ public:
 	 *
 	 * @param innerInterface name of the interface of the inner network
 	 * @param outerInterface name of the interface of the outer network
-	 * @param ownIP IP of this server
 	 * @param targetIP IP of the target server
 	 * @param targetPort Port of the target server
 	 */
-	TCPOptionTimestampChannel(const std::string &innerInterface, const std::string &outerInterface, const std::string &ownIP, const std::string &targetIP,
-	                          const std::string &targetPort)
-	    : BidirectionalChannels<8, PASSIVE>(innerInterface, outerInterface, ownIP, targetIP, targetPort) {}
+	TCPOptionTimestampChannel(const std::string &innerInterface, const std::string &outerInterface, const std::string &targetIP, const std::string &targetPort)
+	    : BidirectionalChannels<8, PASSIVE>(innerInterface, outerInterface, targetIP, targetPort) {}
 
 	/**
 	 * Destroys the CovertChannel.

+ 4 - 4
daemon/include/CovertChannel/Channels/TCPUrgencyChannel.hpp

@@ -19,13 +19,11 @@ public:
 	 *
 	 * @param innerInterface name of the interface of the inner network
 	 * @param outerInterface name of the interface of the outer network
-	 * @param ownIP IP of this server
 	 * @param targetIP IP of the target server
 	 * @param targetPort Port of the target server
 	 */
-	TCPUrgencyChannel(const std::string &innerInterface, const std::string &outerInterface, const std::string &ownIP, const std::string &targetIP,
-	                  const std::string &targetPort)
-	    : BidirectionalChannels<2, PASSIVE>(innerInterface, outerInterface, ownIP, targetIP, targetPort) {}
+	TCPUrgencyChannel(const std::string &innerInterface, const std::string &outerInterface, const std::string &targetIP, const std::string &targetPort)
+	    : BidirectionalChannels<2, PASSIVE>(innerInterface, outerInterface, targetIP, targetPort) {}
 
 	/**
 	 * Destroys the CovertChannel.
@@ -48,6 +46,7 @@ protected:
 		uint16_t data = tcp.urg_ptr();
 		BidirectionalChannels<2, PASSIVE>::protocol.receive((uint8_t *)(&data));
 		tcp.urg_ptr(0);
+		tcp.set_flag(Tins::TCP::Flags::URG, false);
 		BidirectionalChannels<2, PASSIVE>::innerSender.send(pdu);
 
 		return true;
@@ -68,6 +67,7 @@ protected:
 		uint16_t data = 0;
 		BidirectionalChannels<2, PASSIVE>::protocol.send((uint8_t *)(&data));
 		tcp.urg_ptr(data);
+		tcp.set_flag(Tins::TCP::Flags::URG, true);
 		BidirectionalChannels<2, PASSIVE>::outerSender.send(pdu);
 
 		return true;

+ 2 - 2
daemon/src/CMakeLists.txt

@@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 2.8)
 
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
 
-add_executable(ccats src/main.cpp src/Server.cpp src/base64.cpp src/JsonCommander.cpp src/FileManager.cpp src/UserManager.cpp src/Config.cpp src/CovertChannel/CovertChannel.cpp src/CovertChannel/ForwardChannel.cpp src/Notifications.cpp src/Queue.cpp ../libs/libbcrypt/bcrypt.c ../libs/libbcrypt/crypt_blowfish/crypt_blowfish.c ../libs/libbcrypt/crypt_blowfish/crypt_gensalt.c ../libs/libbcrypt/crypt_blowfish/wrapper.c)
+add_executable(ccats src/main.cpp src/Server.cpp src/base64.cpp src/JsonCommander.cpp src/FileManager.cpp src/UserManager.cpp src/Config.cpp src/CovertChannel/CovertChannel.cpp src/CovertChannel/ForwardChannel.cpp src/Notifications.cpp src/Queue.cpp)
 
 # dependencies used by server only
 find_package(libtins 4.2 REQUIRED)
 find_package(OpenSSL REQUIRED)
 
 include_directories(${Boost_INCLUDE_DIR} ${JSONCPP_INCLUDE_DIRS})
-target_link_libraries(ccats PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} ${Boost_LIBRARIES} ${LIBTINS_LIBRARIES} ${JSONCPP_LIBRARIES})
+target_link_libraries(ccats PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} ${Boost_LIBRARIES} ${LIBTINS_LIBRARIES} ${JSONCPP_LIBRARIES} bcrypt)

+ 2 - 0
daemon/src/FileManager.cpp

@@ -207,6 +207,8 @@ int FileManager::openExtendedList() {
 		// calc head
 		std::string head;
 		std::pair<std::vector<char>, FileManager::Error> h = getBytesFromFile(name, 32);
+		if (h.second == FileManager::file_too_small)
+			h = getBytesFromFile(name, 4);
 		if (h.second == FileManager::no_error) {
 			std::string s(h.first.begin(), h.first.end());
 			head = s;

+ 5 - 3
daemon/src/JsonCommander.cpp

@@ -84,7 +84,7 @@ JsonCommander::Response JsonCommander::executeList(const Json::Value &message) {
 		response.json["chunks"] = -1;
 		response.json["items"] = -1;
 		response.json["error"] = "there is already an open list command";
-	} else if ((chunks = fileManager.openList()) == -1) { // TODO do we need to check? maybe. Think about it
+	} else if ((chunks = fileManager.openList()) == -1) { // this should not happen on a current system
 		response.json["accept"] = false;
 		response.json["chunks"] = -1;
 		response.json["items"] = -1;
@@ -377,7 +377,9 @@ JsonCommander::Response JsonCommander::executeHead(const Json::Value &message) {
 		response.json["data"] = "";
 		response.json["error"] = "incorrect head command request";
 	} else {
-		std::pair<std::vector<char>, FileManager::Error> res = fileManager.getBytesFromFile(message["file"].asString(), 4);
+		std::pair<std::vector<char>, FileManager::Error> res = fileManager.getBytesFromFile(message["file"].asString(), 32);
+		if (res.second == FileManager::Error::file_too_small)
+			res = fileManager.getBytesFromFile(message["file"].asString(), 4);
 		switch (res.second) {
 		case FileManager::Error::no_error:
 			response.json["accept"] = true;
@@ -539,7 +541,7 @@ JsonCommander::Response JsonCommander::executeExtendedStatus(const Json::Value &
 		response.json["transfersclientserver"][index]["file"] = fileManager.getPutBaseFileName();
 		int progress = 0;
 		if (this->putSize != 0) {
-			double d = (double)this->putFileReceived / (double)this->putSize;
+			double d = (double)(this->putSize - this->putFileReceived) / (double)this->putSize;
 			progress = (int)(d * 100);
 		}
 		response.json["transfersclientserver"][index]["progress"] = progress;

+ 4 - 4
daemon/src/UserManager.cpp

@@ -1,8 +1,6 @@
 #include "../include/UserManager.h"
 #include "../../libs/libbcrypt/bcrypt.h"
 
-// TODO read userStorage file location from config
-
 // initialize static filename to empty string
 std::string UserManager::filename = "";
 
@@ -66,14 +64,16 @@ bool UserManager::addUser(const std::string &name, const std::string &pw) {
 }
 
 bool UserManager::deleteUser(const std::string &name, const std::string &pw) {
-	// TODO check pw before delete
 	std::map<std::string, std::string> user_map;
 	readFromFile(user_map);
 	auto it = user_map.find(name);
 	if (it == user_map.end()) {
 		return false;
 	}
-	if (it->second.compare(pw) != 0) {
+
+	// checking password before deleting
+	std::string hash = it->second;
+	if (bcrypt_checkpw(pw.c_str(), hash.c_str()) != 0) {
 		return false;
 	}
 	user_map.erase(it);

+ 50 - 60
daemon/src/main.cpp

@@ -13,92 +13,82 @@
 
 using namespace std;
 
-int main(int argc, char *argv[]) {
-	// load config int namespace
-	if (!Config::init("config.txt")) {
-		cerr << "configuration could not be loaded properly" << endl;
-		exit(EXIT_FAILURE);
-	}
-
+/**
+ * Creates the covert channel
+ *
+ * @return covert channel pointer | nullptr if no channel
+ */
+CovertChannel *createChannel() {
 	CovertChannel *covertchannel = nullptr;
-	const std::string covertChannelMode = Config::getValue("covertChannelMode");
-	if (covertChannelMode == "tcpurgency") {
-		const string innerInterface = Config::getValue("innerInterface");
-		const string outerInterface = Config::getValue("outerInterface");
 
-		const string ownIP = Config::getValue("ownIP");
-		const string targetIP = Config::getValue("targetIP");
-		const string targetPort = Config::getValue("targetPort");
-		const string passiveMode = Config::getValue("passiveMode");
+	const std::string covertChannelMode = Config::getValue("covertChannelMode");
+	const string innerInterface = Config::getValue("innerInterface");
+	const string outerInterface = Config::getValue("outerInterface");
+	const string targetIP = Config::getValue("targetIP");
+	const string targetPort = Config::getValue("targetPort");
+	const string passiveMode = Config::getValue("passiveMode");
 
+	if (covertChannelMode == "tcpurgency") {
 		if (passiveMode == "true") {
-			covertchannel = new TCPUrgencyChannel<true>(innerInterface, outerInterface, ownIP, targetIP, targetPort);
+			covertchannel = new TCPUrgencyChannel<true>(innerInterface, outerInterface, targetIP, targetPort);
 		} else {
-			covertchannel = new TCPUrgencyChannel<false>(innerInterface, outerInterface, ownIP, targetIP, targetPort);
+			covertchannel = new TCPUrgencyChannel<false>(innerInterface, outerInterface, targetIP, targetPort);
 		}
-
-		// covertchannel = new ForwardChannel(innerInterface, outerInterface);
 		covertchannel->startSniffing();
-	} else if (covertChannelMode == "tcpappend") {
-		const string innerInterface = Config::getValue("innerInterface");
-		const string outerInterface = Config::getValue("outerInterface");
-
-		const string ownIP = Config::getValue("ownIP");
-		const string targetIP = Config::getValue("targetIP");
-		const string targetPort = Config::getValue("targetPort");
-		const string passiveMode = Config::getValue("passiveMode");
 
+	} else if (covertChannelMode == "tcpappend") {
 		if (passiveMode == "true") {
-			covertchannel = new TCPAppendChannel<8, true>(innerInterface, outerInterface, ownIP, targetIP, targetPort);
+			covertchannel = new TCPAppendChannel<8, true>(innerInterface, outerInterface, targetIP, targetPort);
 		} else {
-			covertchannel = new TCPAppendChannel<8, false>(innerInterface, outerInterface, ownIP, targetIP, targetPort);
+			covertchannel = new TCPAppendChannel<8, false>(innerInterface, outerInterface, targetIP, targetPort);
 		}
-
-		// covertchannel = new ForwardChannel(innerInterface, outerInterface);
 		covertchannel->startSniffing();
-	} else if (covertChannelMode == "tcpoptiontimestamp") {
-		const string innerInterface = Config::getValue("innerInterface");
-		const string outerInterface = Config::getValue("outerInterface");
-
-		const string ownIP = Config::getValue("ownIP");
-		const string targetIP = Config::getValue("targetIP");
-		const string targetPort = Config::getValue("targetPort");
-		const string passiveMode = Config::getValue("passiveMode");
 
+	} else if (covertChannelMode == "tcpoptiontimestamp") {
 		if (passiveMode == "true") {
-			covertchannel = new TCPOptionTimestampChannel<true>(innerInterface, outerInterface, ownIP, targetIP, targetPort);
+			covertchannel = new TCPOptionTimestampChannel<true>(innerInterface, outerInterface, targetIP, targetPort);
 		} else {
-			covertchannel = new TCPOptionTimestampChannel<false>(innerInterface, outerInterface, ownIP, targetIP, targetPort);
+			covertchannel = new TCPOptionTimestampChannel<false>(innerInterface, outerInterface, targetIP, targetPort);
 		}
-
-		// covertchannel = new ForwardChannel(innerInterface, outerInterface);
 		covertchannel->startSniffing();
-	} else if (covertChannelMode == "tcpoptioncustom") {
-		const string innerInterface = Config::getValue("innerInterface");
-		const string outerInterface = Config::getValue("outerInterface");
-
-		const string ownIP = Config::getValue("ownIP");
-		const string targetIP = Config::getValue("targetIP");
-		const string targetPort = Config::getValue("targetPort");
-		const string passiveMode = Config::getValue("passiveMode");
-		const string sendFile = Config::getValue("sendFile");
 
+	} else if (covertChannelMode == "tcpoptioncustom") {
 		if (passiveMode == "true") {
-			covertchannel = new TCPOptionCustomChannel<8, true>(innerInterface, outerInterface, ownIP, targetIP, targetPort);
+			covertchannel = new TCPOptionCustomChannel<8, true>(innerInterface, outerInterface, targetIP, targetPort);
 		} else {
-			covertchannel = new TCPOptionCustomChannel<8, false>(innerInterface, outerInterface, ownIP, targetIP, targetPort);
+			covertchannel = new TCPOptionCustomChannel<8, false>(innerInterface, outerInterface, targetIP, targetPort);
 		}
-
-		// covertchannel = new ForwardChannel(innerInterface, outerInterface);
 		covertchannel->startSniffing();
-	} else if (covertChannelMode == "forward") {
-		const string innerInterface = Config::getValue("innerInterface");
-		const string outerInterface = Config::getValue("outerInterface");
 
+	} else if (covertChannelMode == "forward") {
 		covertchannel = new ForwardChannel(innerInterface, outerInterface);
 		covertchannel->startSniffing();
 	}
 
+	return covertchannel;
+}
+
+int main(int argc, char *argv[]) {
+
+	std::string configFile = "config.txt";
+	if (argc >= 2) {
+		if (std::string(argv[1]) == "help") {
+			std::cout << "Usage " << argv[0] << " [config-file]" << std::endl;
+			return 0;
+		}
+
+		configFile = argv[1];
+	}
+
+	// load config in namespace
+	std::cout << "Loading config file \"" << configFile << "\"." << std::endl;
+	if (!Config::init(configFile)) {
+		cerr << "configuration could not be loaded properly" << endl;
+		exit(EXIT_FAILURE);
+	}
+
+	CovertChannel *covertchannel = createChannel();
+
 	// check if userStorage is add specified location
 	// if not create one
 	UserManager::init(Config::getValue("userdatabase"));
@@ -114,7 +104,7 @@ int main(int argc, char *argv[]) {
 		cerr << e.what() << endl;
 	}
 
-	if (covertchannel == nullptr) {
+	if (covertchannel != nullptr) {
 		delete (covertchannel);
 	}
 

+ 115 - 5
daemon/test/JsonCommanderTest.cpp

@@ -1011,7 +1011,9 @@ TEST(Head, FileTooSmall) {
 	message["file"] = file;
 
 	std::vector<char> bytes;
-	EXPECT_CALL(fileManager, getBytesFromFile(testing::_, testing::_)).WillOnce(testing::Return(std::make_pair(bytes, FileManager::Error::file_too_small)));
+	EXPECT_CALL(fileManager, getBytesFromFile(testing::_, testing::_))
+	    .Times(2)
+	    .WillRepeatedly(testing::Return(std::make_pair(bytes, FileManager::Error::file_too_small)));
 
 	JsonCommander::Response response = jsonCommander.execute(message);
 
@@ -1250,7 +1252,7 @@ TEST(ExtendedStatus, ClientServerDownload) {
 	EXPECT_EQ(response.json["transfersclientserver"].size(), 1);
 	EXPECT_FALSE(response.json["transfersclientserver"][0]["upload"].asBool());
 	EXPECT_EQ(response.json["transfersclientserver"][0]["file"].asString(), "asdf");
-	EXPECT_EQ(response.json["transfersclientserver"][0]["progress"].asInt(), 0);
+	EXPECT_TRUE(response.json["transfersclientserver"][0]["progress"].isInt());
 }
 
 TEST(ExtendedStatus, ClientServerUpload) {
@@ -1278,7 +1280,7 @@ TEST(ExtendedStatus, ClientServerUpload) {
 	EXPECT_EQ(response.json["transfersclientserver"].size(), 1);
 	EXPECT_TRUE(response.json["transfersclientserver"][0]["upload"].asBool());
 	EXPECT_EQ(response.json["transfersclientserver"][0]["file"].asString(), "asdf");
-	EXPECT_EQ(response.json["transfersclientserver"][0]["progress"].asInt(), 0);
+	EXPECT_TRUE(response.json["transfersclientserver"][0]["progress"].isInt());
 }
 
 TEST(ExtendedStatus, ClientServerDonwloadAndUpload) {
@@ -1307,10 +1309,10 @@ TEST(ExtendedStatus, ClientServerDonwloadAndUpload) {
 	EXPECT_EQ(response.json["transfersclientserver"].size(), 2);
 	EXPECT_TRUE(response.json["transfersclientserver"][0]["upload"].asBool());
 	EXPECT_EQ(response.json["transfersclientserver"][0]["file"].asString(), "asdfPut");
-	EXPECT_EQ(response.json["transfersclientserver"][0]["progress"].asInt(), 0);
+	EXPECT_TRUE(response.json["transfersclientserver"][0]["progress"].isInt());
 	EXPECT_FALSE(response.json["transfersclientserver"][1]["upload"].asBool());
 	EXPECT_EQ(response.json["transfersclientserver"][1]["file"].asString(), "asdfGet");
-	EXPECT_EQ(response.json["transfersclientserver"][1]["progress"].asInt(), 0);
+	EXPECT_TRUE(response.json["transfersclientserver"][1]["progress"].isInt());
 }
 
 TEST(ExtendedStatus, ServerServerDownload) {
@@ -1724,4 +1726,112 @@ TEST(ExtendedListData, InvalidRequest) {
 	EXPECT_TRUE(response.json["error"].asString().compare("") != 0);
 }
 
+TEST(Queue, InvalidRequest) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "queue";
+	Json::Value message;
+	message["command"] = command;
+	message["file"] = 5;
+
+	JsonCommander::Response response = jsonCommander.execute(message);
+	EXPECT_TRUE(response.action == JsonCommander::Action::closeAndSend);
+	EXPECT_EQ(response.json["command"].asString(), command);
+	EXPECT_FALSE(response.json["accept"].asBool());
+	EXPECT_EQ(response.json["file"].asString(), "");
+	EXPECT_TRUE(response.json["error"].asString().compare("") != 0);
+}
+
+TEST(Queue, PushToQueueFailed) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "queue";
+	Json::Value message;
+	message["command"] = command;
+	message["file"] = "negative";
+
+	JsonCommander::Response response = jsonCommander.execute(message);
+	EXPECT_TRUE(response.action == JsonCommander::Action::send);
+	EXPECT_EQ(response.json["command"].asString(), command);
+	EXPECT_FALSE(response.json["accept"].asBool());
+	EXPECT_EQ(response.json["file"].asString(), "negative");
+	EXPECT_TRUE(response.json["error"].asString().compare("") != 0);
+}
+
+TEST(Queue, Positive) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "queue";
+	Json::Value message;
+	message["command"] = command;
+	message["file"] = "positive";
+
+	JsonCommander::Response response = jsonCommander.execute(message);
+	EXPECT_TRUE(response.action == JsonCommander::Action::send);
+	EXPECT_EQ(response.json["command"].asString(), command);
+	EXPECT_TRUE(response.json["accept"].asBool());
+	EXPECT_EQ(response.json["file"].asString(), "positive");
+	EXPECT_EQ(response.json["error"].asString(), "");
+}
+
+TEST(Dequeue, InvalidRequest) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "dequeue";
+	Json::Value message;
+	message["command"] = command;
+	message["file"] = 5;
+
+	JsonCommander::Response response = jsonCommander.execute(message);
+	EXPECT_TRUE(response.action == JsonCommander::Action::closeAndSend);
+	EXPECT_EQ(response.json["command"].asString(), command);
+	EXPECT_FALSE(response.json["accept"].asBool());
+	EXPECT_EQ(response.json["file"].asString(), "");
+	EXPECT_TRUE(response.json["error"].asString().compare("") != 0);
+}
+
+TEST(Dequeue, QueueRemoveFailed) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "dequeue";
+	Json::Value message;
+	message["command"] = command;
+	message["file"] = "negative";
+
+	JsonCommander::Response response = jsonCommander.execute(message);
+	EXPECT_TRUE(response.action == JsonCommander::Action::send);
+	EXPECT_EQ(response.json["command"].asString(), command);
+	EXPECT_FALSE(response.json["accept"].asBool());
+	EXPECT_EQ(response.json["file"].asString(), "negative");
+	EXPECT_TRUE(response.json["error"].asString().compare("") != 0);
+}
+
+TEST(Dequeue, Positive) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "dequeue";
+	Json::Value message;
+	message["command"] = command;
+	message["file"] = "positive";
+
+	JsonCommander::Response response = jsonCommander.execute(message);
+	EXPECT_TRUE(response.action == JsonCommander::Action::send);
+	EXPECT_EQ(response.json["command"].asString(), command);
+	EXPECT_TRUE(response.json["accept"].asBool());
+	EXPECT_EQ(response.json["file"].asString(), "positive");
+	EXPECT_EQ(response.json["error"].asString(), "");
+}
+
 } // namespace

+ 2 - 6
daemon/test/QueueMock.cpp

@@ -6,13 +6,9 @@ std::string fileName = "";
 ChannelControls *channel;
 } // namespace Queue
 
-bool Queue::push(const std::string &file) {
-	queue.push_back(file);
+bool Queue::push(const std::string &file) { return file == "positive"; }
 
-	return true;
-}
-
-bool Queue::remove(const std::string &file) { return true; }
+bool Queue::remove(const std::string &file) { return file == "positive"; }
 
 void Queue::schedule() {}
 

+ 4 - 3
gui/CMakeLists.txt

@@ -7,15 +7,16 @@ set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTORCC ON)
 set(CMAKE_CXX_STANDARD 11)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/../.cmake_modules/")
 
 find_package(Threads)
 find_package(Qt5 COMPONENTS Core Quick Widgets REQUIRED)
-find_package(PkgConfig REQUIRED)
-pkg_check_modules(JSONCPP REQUIRED jsoncpp)
+find_package(Jsoncpp REQUIRED)
 
 add_definitions(-DQT_NO_DEBUG_OUTPUT)
 
 add_executable(${PROJECT_NAME} src/main.cpp src/cmdmanager.cpp src/qmlhandler.cpp src/jsonhandler.cpp src/qml.qrc src/config.cpp src/climanager.cpp include/qmlhandler.h)
 
-include_directories(${JSONCPP_INCLUDEDIR} include)
+include_directories(${JSONCPP_INCLUDE_DIRS} include)
 target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${JSONCPP_LIBRARIES} Qt5::Core Qt5::Quick Qt5::Widgets)

+ 72 - 0
gui/Readme.md

@@ -0,0 +1,72 @@
+# Using the Graphical User Interface (GUI)
+
+The GUI of the Covert Channel Application provides an easy way to interact with a covert channel server. It is using the CLI for this interaction, so a valid CLI executable is required for it to work.
+
+
+## Starting the GUI
+
+The graphical user interface should be started without any additional parameters.
+
+
+## Configuration
+
+The GUI uses a configuration file. This file should be called <i>configGUI.txt</i> and should be located in the same folder where the GUI is executed from. On start, the program will check if a valid configuration file exists and if the specified CLI path is valid.
+
+If no configuration file exists, the program will generate a default file. The user will be notified about this and will be asked to set a path to the CLI.
+
+If the configuration file is not valid, the user will get notified and has the option to either quit the program or to generate the default configuration file. The latter would overwrite the existing one.
+
+Any changes made to the configuration using the GUI have to be explicitly saved using the "Save Changes" button. This includes the selection of a keyfile.
+
+### Configuration Values
+
+`Autofill-IP`: Should the default IP automatically be filled in on startup.<br/>
+`Autofill-Username`: Should the default Username automatically be filled in on startup.<br/>
+`CLI-Path`: The absolute path to the command line interface (CLI) file.<br/>
+`Default-IP`: The default IP to be used for the autofill.<br/>
+`Default-Username`: The default Username to be used for the autofill.<br/>
+`Keyfile-Path`: The absolute path to the keyfile to be used for encryption.<br/>
+`Use-SSL`: Should the SSL file be used for a secure connection.<br/>
+`SSL-Path`: The absolute path to the SSL file to be used.<br/>
+
+
+## Connecting to a server
+
+On startup, the GUI asks the user for an IP-address to connect to. This address can contain a port (for example `127.0.0.1:1234`). If no port is specified, the default port <i>1234</i> is used. Additionally, the user has the option to set the given IP-address as the default one in the configuration file.
+
+If the connection was successful, the user will be asked to either log in or sign up using the given forms. Additionally, the user has the option to set the given username as the default one in the configuration file.
+
+
+## Layout Overview
+
+The GUI is splitted into 3 parts: The menubar, the main window and the footer.
+
+The menubar on the top gives the user the option to change the server, to quit the application and to show the <i>about</i> page.
+
+The footer shows the user some information about his active connection such as the server status, the IP-address and their username. Additionally, any error that occurs will be shown here for 3 seconds.
+
+The main window contains of the following 4 tabs:
+
+### Server Files
+
+This tab shows the user all the files that are currently on the server, including their size, encryption and status. The user has the option to enqueue/dequeue, download or delete the files.
+
+Downloading a file will download it to the same folder where the GUI is executed from.
+
+At the bottom, the user can select a file to be uploaded to the server. The selection is done with a file dialog.
+
+### Notifications
+
+This tab shows the user all notifications that get sent from the server (for example when a transfer is complete). Every notification can be dismissed. Additionally, notifications will trigger a system notification.
+
+The title of this tab will contain a star (<b>* Notifications</b>) when there is a new notification.
+
+### Settings
+
+This tab shows the user the current settings that are specified in the configuration file. These can be changed and saved to the file. Additionally the user has the option to reset the settings to the default values.
+
+In this tab, the user can also delete his account from the server. This requires the users password.
+
+### Log
+
+This tab shows the user the log.

+ 36 - 2
gui/include/climanager.h

@@ -5,15 +5,49 @@
 #include "qmlhandler.h"
 #include <QGuiApplication>
 
+/**
+ * Namespace which handles the CLI interaction
+ */
 namespace CliManager {
+/**
+ * Set the qml handler pointer
+ * @param q The reference to the qml handler
+ */
 void setQmlHandler(QMLHandler *q);
-
-void init();
+/**
+ * Boolean if the user is logged in to enable or disable the background status and list requests
+ */
+extern bool loggedin;
+/**
+ * Tries to start the CLI in a new thread
+ * @param useSSL Should the CLI be started with the SSL parameter?
+ */
+void init(bool useSSL);
+/**
+ * Writes a command to the CLI
+ * @param s The command
+ */
 void writeToCli(QString s);
+/**
+ * The background loop that reads the output of the CLI and handles it using the jsonhandler
+ */
 void readPipeLoop();
+/**
+ * The background loop that requests the notifications and extendedlist using the CLI every 3 seconds
+ */
 void notificationsLoop();
+/**
+ * The background loop that requests the status and extendedstatus using the CLI every second
+ */
 void statusLoop();
+/**
+ * Exit from the CLI and stop the background threads
+ */
 void onExit();
+/**
+ * Set the programActive variable
+ * @param active The new value
+ */
 void setProgramActive(bool active);
 } // namespace CliManager
 

+ 184 - 2
gui/include/cmdmanager.h

@@ -9,6 +9,9 @@
 #include <string>
 #include <vector>
 
+/**
+ * The struct for a file entry in the GUI file list
+ */
 struct fileEntry {
 	bool dirty;
 	std::string type;
@@ -17,35 +20,214 @@ struct fileEntry {
 	float speed;
 };
 
+/**
+ * Namespace which handles the commands received from the CLI
+ */
 namespace CmdManager {
+/**
+ * Initialize the cmdmap with the references to the according methods
+ */
 void init();
+/**
+ * Set the qml handler pointer
+ * @param q The reference to the qml handler
+ */
 void setQmlHandler(QMLHandler *q);
-
+/**
+ * Update a file in the file map
+ * @param name The name of the file
+ * @param type The new type of the file
+ * @param method The new method of the file
+ * @param progress The new progress of the file
+ * @param speed The new speed of the file
+ */
 void updateInternalFile(std::string name, std::string type, std::string method, int progress, float speed);
+/**
+ * Update the GUI file list to match the file map
+ */
 void emitFileList();
+/**
+ * Clear the file map
+ */
 void cleanInternalList();
-
+/**
+ * Execute a command received from the CLI
+ * @param cmd The command to be executed using the cmdmap
+ * @param root The entire JSON value
+ */
 void executeCmd(std::string cmd, Json::Value root);
+/**
+ * Sets the cached ip address
+ * @param ip The ip address
+ */
+void setCachedIP(QString ip);
+/**
+ * Sets the cached port
+ * @param port The port
+ */
+void setCachedPort(QString port);
 
+/**
+ * Handle the error command
+ *
+ * Print the error in the log
+ * @param root The entire JSON value
+ */
 void handleError(Json::Value root);
+/**
+ * Handle the status command
+ *
+ * Update the status text in the GUI
+ * @param root The entire JSON value
+ */
 void handleStatus(Json::Value root);
+/**
+ * Handle the close command
+ *
+ * Set the program as inactive to stop the CLI background threads
+ * @param root The entire JSON value
+ */
 void handleClose(Json::Value root);
+/**
+ * Handle the list command
+ *
+ * List the server files in the GUI
+ * @param root The entire JSON value
+ */
 void handleList(Json::Value root);
+/**
+ * Handle the extendedlist command
+ *
+ * List the server files in the GUI with detailed information
+ * @param root The entire JSON value
+ */
 void handleExtendedList(Json::Value root);
+/**
+ * Handle the connect command
+ *
+ * If there was an error, reactivate the connect button and show the error
+ * @param root The entire JSON value
+ */
 void handleConnect(Json::Value root);
+/**
+ * Handle the version command
+ *
+ * Close the ip popup and open the login/signup popup if it was accepted, else reactivate the connect button and show the error
+ * @param root The entire JSON value
+ */
 void handleVersion(Json::Value root);
+/**
+ * Handle the login command
+ *
+ * Set the loggedin value to true and close the popup if it was accepted, else reactivate the login button and show the error
+ * @param root The entire JSON value
+ */
 void handleLogin(Json::Value root);
+/**
+ * Handle the signup command
+ *
+ * Set the loggedin value to true and close the popup if it was accepted, else reactivate the signup button and show the error
+ * @param root The entire JSON value
+ */
 void handleSignup(Json::Value root);
+/**
+ * Handle the put command
+ *
+ * Show an error if it was not accepted
+ * @param root The entire JSON value
+ */
 void handlePut(Json::Value root);
+/**
+ * Handle the putdata command
+ *
+ * Show an error if it was not accepted
+ * @param root The entire JSON value
+ */
 void handlePutData(Json::Value root);
+/**
+ * Handle the get command
+ *
+ * Show an error if it was not accepted, else disable the file's download button
+ * @param root The entire JSON value
+ */
 void handleGet(Json::Value root);
+/**
+ * Handle the getdata command
+ *
+ * Show an error if it was not accepted
+ * @param root The entire JSON value
+ */
 void handleGetData(Json::Value root);
+/**
+ * Handle the deleteme command
+ *
+ * Show an error if it was not accepted, else disconnect from the server and restart the application
+ * @param root The entire JSON value
+ */
 void handleDeleteMe(Json::Value root);
+/**
+ * Handle the deletefile command
+ *
+ * Show an error if it was not accepted, else show the confirmation in the log
+ * @param root The entire JSON value
+ */
 void handleDeleteFile(Json::Value root);
+/**
+ * Handle the notifications command
+ *
+ * Show an error if it was not accepted, else handle the new notifications if there are some
+ * @param root The entire JSON value
+ */
 void handleNotifications(Json::Value root);
+/**
+ * Handle the queue command
+ *
+ * Show an error if it was not accepted
+ * @param root The entire JSON value
+ */
 void handleQueue(Json::Value root);
+/**
+ * Handle the dequeue command
+ *
+ * Show an error if it was not accepted
+ * @param root The entire JSON value
+ */
 void handleDequeue(Json::Value root);
+/**
+ * Handle the extendedstatus command
+ *
+ * Show an error if it was not accepted, else update the file list with the new data
+ * @param root The entire JSON value
+ */
 void handleExtendedStatus(Json::Value root);
+/**
+ * Handle the keyfile command
+ *
+ * Show an error if it was not accepted, else update the keyfile status in the settings tab
+ * @param root The entire JSON value
+ */
+void handleKeyfile(Json::Value root);
+/**
+ * Handle the closekey command
+ *
+ * Show an error if it was not accepted, else update the keyfile status in the settings tab
+ * @param root The entire JSON value
+ */
+void handleClosekey(Json::Value root);
+/**
+ * Handle the disconnect command
+ *
+ * Set the loggedin variable to false to stop the background CLI threads if it was accepted
+ * @param root The entire JSON value
+ */
+void handleDisconnect(Json::Value root);
+/**
+ * Handle the connectionerror command
+ *
+ * Sets the footer error to encourage the user to restart the application on a lost connection
+ * @param root The entire JSON value
+ */
+void handleConnectionError(Json::Value root);
 } // namespace CmdManager
 
 #endif // CMDMANAGER_H

+ 27 - 0
gui/include/config.h

@@ -6,12 +6,39 @@
 #include <sstream>
 #include <vector>
 
+/**
+ * Namespace which handles the GUI configuration and it's config file
+ */
 namespace Config {
+/**
+ * Set up the configuration map with the default values and declare the configuration valid
+ */
 void setupDefaultConfig();
+/**
+ * Check the loaded config if the values are valid and if it's size is valid
+ * @return Is the config valid?
+ */
 bool checkConfig();
+/**
+ * Load the configuration file - if values are missing, they are added with their default values
+ * @return Has the config file been successfully loaded?
+ */
 bool loadFile();
+/**
+ * Save the configuration map to the config file
+ */
 void saveFile();
+/**
+ * Get a value from the configuration map
+ * @param key The configuration key to get
+ * @return The value
+ */
 std::string getValue(const std::string &key);
+/**
+ * Set a value in the configuration map to the given one
+ * @param key The configuration key
+ * @param value The new value
+ */
 void setValue(const std::string &key, const std::string &value);
 } // namespace Config
 

+ 12 - 0
gui/include/jsonhandler.h

@@ -4,7 +4,19 @@
 #include "qmlhandler.h"
 #include <json/json.h>
 
+/**
+ * Namespace which handles JSON strings
+ */
 namespace JsonHandler {
+/**
+ * Set the qml handler pointer
+ * @param q The reference to the qml handler
+ */
+void setQmlHandler(QMLHandler *q);
+/**
+ * Parse a JSON string, put them in the terminal and in the log and handle the command using the CmdManager class
+ * @param buffer The JSON string
+ */
 void parseJSON(std::string buffer);
 } // namespace JsonHandler
 

+ 380 - 43
gui/include/qmlhandler.h

@@ -6,143 +6,480 @@
 
 extern bool _RESTART;
 
+/**
+ * @class QMLHandler
+ *
+ * This class is the connection between Qml and C++, where signals can be emitted using code to interact with the forms
+ * and slots can be used to run code from a Qml form.
+ */
 class QMLHandler : public QObject {
 	Q_OBJECT
 
-private:
-	void fileExists(std::string name);
-
 public:
+	/**
+	 * This class
+	 */
 	explicit QMLHandler(QObject *parent = 0);
+
+	/**
+	 * Handle the closing of the main window
+	 */
 	void onExit();
-	void closeCLI();
+
+	/**
+	 * Get the settings from the config class and adjust the gui settings accordingly
+	 */
 	void loadSettingsToGUI();
-	QString getIP();
+
+	/**
+	 * Set the _RESTART variable
+	 * @param restart The new value
+	 */
 	void setRestart(bool restart);
 
+	/**
+	 * Set the configExists variable
+	 * @param exists The new value
+	 */
+	void setConfigExists(bool exists);
+
 	// C++ -> QML
 signals:
 	// No Config Found Popup
+
+	/**
+	 * Open the popup for when no config has been found
+	 */
 	void noConfigFoundPopupOpen();
+	/**
+	 * Close the popup for when no config has been found
+	 */
 	void noConfigFoundPopupClose();
 
 	// Invalid Cli Path Popup
+
+	/**
+	 * Open the popup for when the cli path is not valid
+	 */
 	void invalidCliPathPopupOpen();
+	/**
+	 * Close the popup for when the cli path is not valid
+	 */
 	void invalidCliPathPopupClose();
 
 	// Invalid Config Popup
+
+	/**
+	 * Open the popup for when the config is invalid
+	 */
 	void invalidConfigPopupOpen();
+	/**
+	 * Close the popup for when the config is invalid
+	 */
 	void invalidConfigPopupClose();
 
-	// Sending
-	void sendingSetFileUrlText(QString signalText);
-	void sendingEnableSendButton();
-	void sendingDisableSendButton();
-
-	// Receiving
-	void receivingClearFileList();
-	void receivingListFile(QString fileName, QString fileSize, QString fileDecryptable, bool existsLocally);
-	void receivingUpdateFile(QString fileName, QString fileProgress, bool isQueued);
-	void receivingDisableDownloadButton(QString fileName);
-	void receivingCloseConfirmDeletePopup();
-
-	// Messages
-	void message(QString msg);
+	// Server Files
+
+	/**
+	 * Set the text that shows the selected file to be uploaded
+	 * @param signalText The new file url
+	 */
+	void serverFilesSetFileUrlText(QString signalText);
+	/**
+	 * Enable the Upload File button
+	 */
+	void serverFilesEnableSendButton();
+	/**
+	 * Disable the Upload File button
+	 */
+	void serverFilesDisableSendButton();
+	/**
+	 * Clear the list of files that are on the server
+	 */
+	void serverFilesClearFileList();
+	/**
+	 * List a new file in the file list
+	 * @param fileName The name of the file
+	 * @param fileSize The size of the file
+	 * @param fileDecryptable Is the file decryptable for the current user?
+	 * @param existsLocally Is the file already downloaded?
+	 */
+	void serverFilesListFile(QString fileName, QString fileSize, QString fileDecryptable, bool existsLocally);
+	/**
+	 * Update an existing file in the file list
+	 * @param fileName The name of the file
+	 * @param fileProgress The download or upload progress of the file
+	 * @param isQueued Is the file queued on the server?
+	 */
+	void serverFilesUpdateFile(QString fileName, QString fileProgress, bool isQueued);
+	/**
+	 * Disable the download button for a specific file
+	 * @param The name of the file
+	 */
+	void serverFilesDisableDownloadButton(QString fileName);
+	/**
+	 * Close the delete popup for a file
+	 */
+	void serverFilesCloseConfirmDeletePopup();
 
 	// Settings
+
+	/**
+	 * Close the main window
+	 */
 	void closeWindow();
-	void loadSettings(int covertMethod, bool saveIP, bool saveUsername, QString cliPath);
+	/**
+	 * Load the settings to the GUI
+	 * @param saveIP Should the autofill ip toggle be switched on or off?
+	 * @param saveUsername Should the autofill username toggle be switched on or off?
+	 * @param cliPath The path to the cli
+	 * @param keyPath The path to the keyfile
+	 */
+	void loadSettings(bool saveIP, bool saveUsername, QString cliPath, QString keyPath);
+	/**
+	 * Update the status of the keyfile
+	 * @param success Has the keyfile successfully been loaded?
+	 * @param msg The message to be shown
+	 */
+	void keyfileStatus(bool success, QString msg);
+	/**
+	 * Signal for when the keyfile has been successfully closed and should be shown in the settings
+	 */
+	void keyfileClosedOK();
 
 	// Delete Me Popup
+
+	/**
+	 * Set the status on the delete me popup (when a user want's to delete their acccount)
+	 * @param status The status message
+	 */
 	void deleteMePopupSetStatus(QString status);
 
 	// Ip Popup
+
+	/**
+	 * Set the status on the ip popup
+	 * @param status The status messsage
+	 */
 	void ipPopupSetStatus(QString status);
+	/**
+	 * Set the ip address in the ip input field (used for autofill ip)
+	 * @param default_ip The ip address
+	 */
 	void ipPopupSetIP(QString default_ip);
+	/**
+	 * Close the ip popup
+	 */
 	void ipPopupClose();
+	/**
+	 * Open the ip popup
+	 */
 	void ipPopupOpen();
+	/**
+	 * Enable the connect button in the ip popup
+	 */
 	void ipPopupEnableConnectButton();
+	/**
+	 * Disbale the connect button in the ip popup
+	 */
 	void ipPopupDisableConnectButton();
+	/**
+	 * Tick the "Save IP" checkbox in the ip popup
+	 */
 	void ipPopupCheckSaveCheckbox();
 
 	// Login Signup Popup
+
+	/**
+	 * Close the login/signup popup
+	 */
 	void loginSignupPopupClose();
+	/**
+	 * Open the login/signup popup
+	 */
 	void loginSignupPopupOpen();
+	/**
+	 * Tick the "Save Username" checkbox in the login/signup popup
+	 */
 	void loginSignupCheckSaveCheckbox();
 
 	// Login
+
+	/**
+	 * Set the status for the login tab in the login/signup popup
+	 * @param status The status message
+	 */
 	void loginSetStatus(QString status);
+	/**
+	 * Enable the login button for the login tab in the login/signup popup
+	 */
 	void loginEnableLoginButton();
+	/**
+	 * Disable the login button for the login tab in the login/signup popup
+	 */
 	void loginDisableLoginButton();
+	/**
+	 * Set the username input field for the login tab in the login/signup popup (used for autofill username)
+	 * @param username The username
+	 */
 	void loginSetUsername(QString username);
 
 	// Signup
+
+	/**
+	 * Set the status for the signup tab in the login/signup popup
+	 * @param status The status message
+	 */
 	void signupSetStatus(QString status);
+	/**
+	 * Disable the register button for the signup tab in the login/signup popup
+	 */
 	void signupEnableRegisterButton();
+	/**
+	 * Disable the register button for the signup tab in the login/signup popup
+	 */
 	void signupDisableRegisterButton();
 
 	// Footer
-	void log(QString logText);
+
+	/**
+	 * Set the status in the footer
+	 * @param status The new status
+	 */
 	void footerSetStatus(QString status);
+	/**
+	 * Set the error in the footer
+	 * @param error The error message
+	 */
+	void footerSetError(QString error);
 
 	// Notifications
+
+	/**
+	 * Add a new notification to the notifications tab
+	 * @param message The new notification message
+	 */
 	void notification(QString message);
+	/**
+	 * Dismiss an existing notification in the notifications tab
+	 * @param index The index of the notification
+	 */
 	void dismissNotification(int index);
+	/**
+	 * Show a desktop notification
+	 * @param title The title of the notification
+	 * @param message The message of the notification
+	 */
 	void showDesktopNotification(QString title, QString message);
 
+	// Log
+
+	/**
+	 * Add a message to the log
+	 * @param logText The message to be added
+	 */
+	void log(QString logText);
+
 	// QML -> C++
 public slots:
-	void onStart();
+	// Main
+
+	/**
+	 * Handle the start of the main window
+	 *
+	 * Check the configuration, load the values to the GUI and start the CLI is needed
+	 * Opens the according popup in the window
+	 * @param startWithCli Should the CLI be started?
+	 */
+	void onStart(bool startWithCli);
+	/**
+	 * Handle the "Switch Server" button
+	 *
+	 * Disconnect from the server
+	 */
+	void onSwitchServer();
 
 	// No Config Found Popup
+
+	/**
+	 * Handle the continue button after a CLI has been selected
+	 *
+	 * Creates a default config with the path to the selected CLI and runs the onStart method
+	 * @param cli_path The path to the CLI
+	 */
 	void onNoConfigFoundPopupContinueButton(QString cli_path);
 
 	// Invalid Cli Path Popup
+
+	/**
+	 * Handle the continue button after a CLI has been selected
+	 *
+	 * Sets the new CLI path in the config and runs the onStart method
+	 * @param cli_path The path to the CLI
+	 */
 	void onInvalidCliPathPopupContinueButton(QString cli_path);
+	/**
+	 * Handle the quit button
+	 *
+	 * Emits a signal to close the main window
+	 */
 	void onInvalidCliPathPopupQuitButton();
 
 	// Invalid Config Popup
+
+	/**
+	 * Handle the "create default" button
+	 *
+	 * Sets up a default config and opens the "invalid cli path" popup so that the user can select a CLI
+	 */
 	void onInvalidConfigPopupCreateDefaultButton();
+	/**
+	 * Handle the quit button
+	 *
+	 * Emits a signal to close the main window
+	 */
 	void onInvalidConfigPopupQuitButton();
 
-	// Sending
-	void onSendingSelectFileButton(QUrl url);
-	void onSendingSendFileButton();
-	void onSendingClearSelectionButton();
-
-	// Receiving
-	void onReceivingListFilesButton();
-	void onReceivingDownloadFileButton(QString fileName);
-	void onReceivingConfirmDeleteFileButton(QString fileName);
-
-	// Messages
-	void onMessagesSendButton(QString msg);
+	// Server Files
+
+	/**
+	 * Handle the selection of a file with the file dialog
+	 *
+	 * Sets the text for the selected file and enables the "send file" button
+	 * @param url The new file url
+	 */
+	void onServerFilesSelectFileButton(QUrl url);
+	/**
+	 * Handle the "send file" button
+	 *
+	 * Upload the selected file to the server
+	 */
+	void onServerFilesSendFileButton();
+	/**
+	 * Handle the "clear selection" button
+	 *
+	 * Resets the selected file
+	 */
+	void onServerFilesClearSelectionButton();
+	/**
+	 * Handle the download button for a file
+	 *
+	 * Downloads the file from the server
+	 * @param fileName The name of the file
+	 */
+	void onServerFilesDownloadFileButton(QString fileName);
+	/**
+	 * Handle the delete button in the delete confirmation popup
+	 *
+	 * Deletes the file from the server
+	 * @param filename The name of the file
+	 */
+	void onServerFilesConfirmDeleteFileButton(QString fileName);
+	/**
+	 * Handle the queue button for a file
+	 *
+	 * Puts the file into the queue on the server
+	 * @param fileName The name of the file
+	 */
+	void onReceivingQueueFileButton(QString fileName);
+	/**
+	 * Handle the queue button for a file
+	 *
+	 * Dequeues the file on the server
+	 * @param fileName The name of the file
+	 */
+	void onReceivingDequeueFileButton(QString fileName);
 
 	// Settings
+
+	/**
+	 * Handle the selection of a keyfile with the file dialog
+	 *
+	 * Selects the new keyfile on the CLI for further use
+	 * @param path The path to the keyfile
+	 */
+	void onKeyfileSelected(QString path);
+	/**
+	 * Handle the "close keyfile" button
+	 *
+	 * Closes the keyfile on the CLi
+	 */
+	void onKeyfileClosed();
+	/**
+	 * Handle the delete button in the delete me confirmation popup
+	 *
+	 * Tries to delete the current user from the server when the password is correct
+	 * @param password The entered password
+	 */
 	void onSettingsDeleteMeButton(QString password);
+	/**
+	 * Handle the "revert changes" button
+	 *
+	 * Sets the settings values to those in the config file
+	 */
 	void onSettingsRevertChangesButton();
+	/**
+	 * Handle the reset button
+	 *
+	 * Sets the settings to the default values
+	 */
 	void onSettingsResetButton();
-	void onSettingsSaveButton(int covertMethod, bool saveIP, bool saveUsername, QString cliPath);
+	/**
+	 * Handle the save button
+	 *
+	 * Saves the current values of the settings in the GUI to the config file
+	 * @param saveIP Should the autofill ip toggle be switched on or off?
+	 * @param saveUsername Should the autofill username toggle be switched on or off?
+	 * @param cliPath The path to the cli
+	 * @param keyPath The path to the keyfile
+	 */
+	void onSettingsSaveButton(bool saveIP, bool saveUsername, QString cliPath, QString keyPath);
 
 	// Ip Popup
+
+	/**
+	 * Handle the connect button
+	 *
+	 * Connects to the server and saves the ip in the config if desired
+	 * @param ip The entered ip address
+	 * @param saveAsDefault Was the "Save IP" checkbox ticked?
+	 */
 	void onIpPopupConnectButton(QString ip, bool saveAsDefault);
 
 	// Login
+
+	/**
+	 * Handle the login button
+	 *
+	 * Logs in on the server and saves the username in the config if desired
+	 * @param username The entered username
+	 * @param password The entered password
+	 * @param saveAsDefault Was the "Save Username" checkbox ticked?
+	 */
 	void onLoginLoginButton(QString username, QString password, bool saveAsDefault);
 
 	// Signup
-	void onSignupRegisterButton(QString username, QString passwordOne, QString passwordTwo, bool saveAsDefault);
 
-	// Footer
-	void onFooterGetStatusButton();
+	/**
+	 * Handle the register button
+	 *
+	 * Registers on the server and saves the username in the config if desired
+	 * @param username The entered username
+	 * @param passwordOne The entered first password
+	 * @param passwordTwo The entered confirmation password
+	 * @param saveAsDefault Was the "Save Username" checkbox ticked?
+	 */
+	void onSignupRegisterButton(QString username, QString passwordOne, QString passwordTwo, bool saveAsDefault);
 
 	// Notifications
-	void onDismissNotificationButton(int id);
-
-	// Queueing
-	void onReceivingQueueFileButton(QString fileName);
 
-	void onReceivingDequeueFileButton(QString fileName);
+	/**
+	 * Handle the "x" button on a noticiation
+	 *
+	 * Dismiss the notification in the notifications tab
+	 * @param id The id of the notification
+	 */
+	void onDismissNotificationButton(int id);
 };
 
 #endif // QMLHANDLER_H

+ 21 - 2
gui/src/Forms/Connect/IpPopup.ui.qml

@@ -13,6 +13,16 @@ Popup {
     closePolicy: Popup.NoAutoClose
     anchors.centerIn: Overlay.overlay
 
+    signal resetOnClose
+
+    onClosed: {
+        resetOnClose()
+    }
+
+    Overlay.modal: Rectangle {
+        color: "#b5c5c5c5"
+    }
+
     Connections {
         target: _qmlHandler
         onIpPopupSetIP: {
@@ -45,7 +55,6 @@ Popup {
         Text {
             Layout.alignment: Qt.AlignCenter
             id: ipPopupText
-            color: "#ffffff"
             text: qsTr("Enter the IP to connect:")
             horizontalAlignment: Text.AlignHCenter
             verticalAlignment: Text.AlignVCenter
@@ -61,7 +70,7 @@ Popup {
             placeholderText: "IP-Address"
             horizontalAlignment: Text.AlignHCenter
             validator: RegExpValidator {
-                regExp: /^(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))$/
+                regExp: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(:[0-9]+)?$/
             }
             // @disable-check M222
             Keys.onReturnPressed: ipPopupConnectButton.activate()
@@ -69,6 +78,11 @@ Popup {
             Keys.onEnterPressed: ipPopupConnectButton.activate()
 
             onTextEdited: ipPopupConnectButton.enabled = ipPopupIpInput.acceptableInput
+
+            Connections {
+                target: popup
+                onResetOnClose: ipPopupIpInput.text = ""
+            }
         }
 
         CheckBox {
@@ -88,6 +102,11 @@ Popup {
             font.pixelSize: 20
             wrapMode: Text.WordWrap
             Layout.preferredWidth: parent.width
+
+            Connections {
+                target: popup
+                onResetOnClose: ipPopupStatusText.text = ""
+            }
         }
 
         Button {

+ 0 - 120
gui/src/Forms/Connect/LoginForm.ui.qml

@@ -1,120 +0,0 @@
-import QtQuick 2.12
-import QtQuick.Controls 2.5
-import QtQuick.Layouts 1.3
-
-Page {
-    width: 400
-    height: 400
-    title: ""
-
-    Connections {
-        target: _qmlHandler
-        onLoginSetUsername: {
-            loginUsernameInput.text = username
-        }
-        onLoginSetStatus: {
-            loginStatusText.text = status
-        }
-        onLoginEnableLoginButton: {
-            loginLoginButton.enabled = true
-        }
-        onLoginDisableLoginButton: {
-            loginLoginButton.enabled = false
-        }
-        onLoginSignupCheckSaveCheckbox: {
-            loginSetDefaultCheckbox.checked = true
-        }
-    }
-
-    ColumnLayout {
-        anchors.fill: parent
-
-        Text {
-            Layout.alignment: Qt.AlignCenter
-            id: loginTitle
-            color: "#ffffff"
-            text: qsTr("Login")
-            horizontalAlignment: Text.AlignHCenter
-            verticalAlignment: Text.AlignVCenter
-            font.pixelSize: 20
-        }
-
-        TextField {
-            Layout.alignment: Qt.AlignCenter
-            id: loginUsernameInput
-            selectByMouse: true
-            focus: true
-            text: qsTr("")
-            placeholderText: "Username"
-            horizontalAlignment: Text.AlignHCenter
-            // @disable-check M222
-            Keys.onReturnPressed: loginLoginButton.activate()
-            // @disable-check M222
-            Keys.onEnterPressed: loginLoginButton.activate()
-
-            onTextEdited: loginLoginButton.enabled = (loginUsernameInput.text != ""
-                                                      && loginPasswordInput.text != "")
-        }
-
-        TextField {
-            Layout.alignment: Qt.AlignCenter
-            id: loginPasswordInput
-            selectByMouse: true
-            text: qsTr("")
-            placeholderText: "Password"
-            horizontalAlignment: Text.AlignHCenter
-            // @disable-check M222
-            Keys.onReturnPressed: loginLoginButton.activate()
-            // @disable-check M222
-            Keys.onEnterPressed: loginLoginButton.activate()
-            echoMode: TextInput.Password
-
-            onTextEdited: loginLoginButton.enabled = (loginUsernameInput.text != ""
-                                                      && loginPasswordInput.text != "")
-        }
-
-        CheckBox {
-            id: loginSetDefaultCheckbox
-            Layout.alignment: Qt.AlignCenter
-            checked: false
-            text: "Save as default user"
-        }
-
-        Text {
-            id: loginStatusText
-            color: "#df3f3f"
-            text: qsTr("")
-            wrapMode: Text.WordWrap
-            Layout.preferredWidth: parent.width
-            horizontalAlignment: Text.AlignHCenter
-            verticalAlignment: Text.AlignVCenter
-            Layout.alignment: Qt.AlignCenter
-            font.pixelSize: 20
-        }
-
-        Button {
-            Layout.alignment: Qt.AlignCenter
-            id: loginLoginButton
-            text: qsTr("Login")
-            enabled: false
-            font.pointSize: 16
-            // @disable-check M223
-            onClicked: {
-                // @disable-check M222
-                loginLoginButton.activate()
-            }
-
-            // @disable-check M222
-            function activate() {
-                // @disable-check M223
-                if (loginLoginButton.enabled) {
-                    // @disable-check M222
-                    _qmlHandler.onLoginLoginButton(
-                                loginUsernameInput.text,
-                                loginPasswordInput.text,
-                                loginSetDefaultCheckbox.checked)
-                }
-            }
-        }
-    }
-}

+ 312 - 2
gui/src/Forms/Connect/LoginSignupPopup.ui.qml

@@ -13,6 +13,16 @@ Popup {
     closePolicy: Popup.NoAutoClose
     anchors.centerIn: Overlay.overlay
 
+    signal resetOnClose
+
+    onClosed: {
+        resetOnClose()
+    }
+
+    Overlay.modal: Rectangle {
+        color: "#b5c5c5c5"
+    }
+
     Connections {
         target: _qmlHandler
         onLoginSignupPopupClose: {
@@ -21,6 +31,33 @@ Popup {
         onLoginSignupPopupOpen: {
             popup.open()
         }
+
+        onLoginSetUsername: {
+            loginUsernameInput.text = username
+        }
+        onLoginSetStatus: {
+            loginStatusText.text = status
+        }
+        onLoginEnableLoginButton: {
+            loginLoginButton.enabled = true
+        }
+        onLoginDisableLoginButton: {
+            loginLoginButton.enabled = false
+        }
+        onLoginSignupCheckSaveCheckbox: {
+            loginSetDefaultCheckbox.checked = true
+            signupSetDefaultCheckbox.checked = true
+        }
+
+        onSignupSetStatus: {
+            signupStatusText.text = status
+        }
+        onSignupEnableRegisterButton: {
+            signupRegisterButton.enabled = true
+        }
+        onSignupDisableRegisterButton: {
+            signupRegisterButton.enabled = false
+        }
     }
 
     Page {
@@ -46,10 +83,283 @@ Popup {
             currentIndex: header.currentIndex
             clip: true
 
-            LoginForm {
+            // Login
+            Page {
+                width: 400
+                height: 400
+                title: ""
+
+                ColumnLayout {
+                    anchors.fill: parent
+
+                    Text {
+                        Layout.alignment: Qt.AlignCenter
+                        id: loginTitle
+
+                        text: qsTr("Login")
+                        horizontalAlignment: Text.AlignHCenter
+                        verticalAlignment: Text.AlignVCenter
+                        font.pixelSize: 20
+                    }
+
+                    TextField {
+                        Layout.alignment: Qt.AlignCenter
+                        id: loginUsernameInput
+                        selectByMouse: true
+                        focus: true
+                        text: qsTr("")
+                        placeholderText: "Username"
+                        horizontalAlignment: Text.AlignHCenter
+                        // @disable-check M222
+                        Keys.onReturnPressed: loginLoginButton.activate()
+                        // @disable-check M222
+                        Keys.onEnterPressed: loginLoginButton.activate()
+
+                        onTextEdited: loginLoginButton.enabled
+                                      = (loginUsernameInput.text != ""
+                                         && loginPasswordInput.text != "")
+
+                        Connections {
+                            target: popup
+                            onResetOnClose: loginUsernameInput.text = ""
+                        }
+                    }
+
+                    TextField {
+                        Layout.alignment: Qt.AlignCenter
+                        id: loginPasswordInput
+                        selectByMouse: true
+                        text: qsTr("")
+                        placeholderText: "Password"
+                        horizontalAlignment: Text.AlignHCenter
+                        // @disable-check M222
+                        Keys.onReturnPressed: loginLoginButton.activate()
+                        // @disable-check M222
+                        Keys.onEnterPressed: loginLoginButton.activate()
+                        echoMode: TextInput.Password
+
+                        onTextEdited: loginLoginButton.enabled
+                                      = (loginUsernameInput.text != ""
+                                         && loginPasswordInput.text != "")
+
+                        Connections {
+                            target: popup
+                            onResetOnClose: loginPasswordInput.text = ""
+                        }
+                    }
+
+                    CheckBox {
+                        id: loginSetDefaultCheckbox
+                        Layout.alignment: Qt.AlignCenter
+                        checked: false
+                        text: "Save as default user"
+
+                        Connections {
+                            target: popup
+                            onResetOnClose: loginSetDefaultCheckbox.checked = false
+                        }
+                    }
+
+                    Text {
+                        id: loginStatusText
+                        color: "#df3f3f"
+                        text: qsTr("")
+                        wrapMode: Text.WordWrap
+                        Layout.preferredWidth: parent.width
+                        horizontalAlignment: Text.AlignHCenter
+                        verticalAlignment: Text.AlignVCenter
+                        Layout.alignment: Qt.AlignCenter
+                        font.pixelSize: 20
+
+                        Connections {
+                            target: popup
+                            onResetOnClose: loginStatusText.text = ""
+                        }
+                    }
+
+                    Button {
+                        Layout.alignment: Qt.AlignCenter
+                        id: loginLoginButton
+                        text: qsTr("Login")
+                        enabled: false
+                        font.pointSize: 16
+                        // @disable-check M223
+                        onClicked: {
+                            // @disable-check M222
+                            loginLoginButton.activate()
+                        }
+
+                        // @disable-check M222
+                        function activate() {
+                            // @disable-check M223
+                            if (loginLoginButton.enabled) {
+                                // @disable-check M222
+                                _qmlHandler.onLoginLoginButton(
+                                            loginUsernameInput.text,
+                                            loginPasswordInput.text,
+                                            loginSetDefaultCheckbox.checked)
+                            }
+                        }
+                    }
+                }
             }
 
-            SignupForm {
+            // Signup
+            Page {
+                width: 400
+                height: 400
+
+                ColumnLayout {
+                    anchors.fill: parent
+
+                    Text {
+                        Layout.alignment: Qt.AlignCenter
+                        id: signupTitle
+
+                        text: qsTr("Signup")
+                        horizontalAlignment: Text.AlignHCenter
+                        verticalAlignment: Text.AlignVCenter
+                        font.pixelSize: 20
+                    }
+
+                    TextField {
+                        Layout.alignment: Qt.AlignCenter
+                        id: signupUsernameInput
+                        selectByMouse: true
+                        focus: true
+                        text: qsTr("")
+                        placeholderText: "Username"
+                        horizontalAlignment: Text.AlignHCenter
+                        // @disable-check M222
+                        Keys.onReturnPressed: signupRegisterButton.activate()
+                        // @disable-check M222
+                        Keys.onEnterPressed: signupRegisterButton.activate()
+
+                        onTextEdited: {
+                            signupStatusText.text = ""
+                            signupRegisterButton.enabled
+                                    = (signupUsernameInput.text != ""
+                                       && signupPasswordOneInput.text != ""
+                                       && signupPasswordTwoInput.text != "")
+                        }
+
+                        Connections {
+                            target: popup
+                            onResetOnClose: signupUsernameInput.text = ""
+                        }
+                    }
+
+                    TextField {
+                        Layout.alignment: Qt.AlignCenter
+                        id: signupPasswordOneInput
+                        selectByMouse: true
+                        focus: true
+                        text: qsTr("")
+                        placeholderText: "Password"
+                        horizontalAlignment: Text.AlignHCenter
+                        // @disable-check M222
+                        Keys.onReturnPressed: signupRegisterButton.activate()
+                        // @disable-check M222
+                        Keys.onEnterPressed: signupRegisterButton.activate()
+                        echoMode: TextInput.Password
+
+                        onTextEdited: {
+                            signupStatusText.text = ""
+                            signupRegisterButton.enabled
+                                    = (signupUsernameInput.text != ""
+                                       && signupPasswordOneInput.text != ""
+                                       && signupPasswordTwoInput.text != "")
+                        }
+
+                        Connections {
+                            target: popup
+                            onResetOnClose: signupPasswordOneInput.text = ""
+                        }
+                    }
+
+                    TextField {
+                        Layout.alignment: Qt.AlignCenter
+                        id: signupPasswordTwoInput
+                        selectByMouse: true
+                        focus: true
+                        text: qsTr("")
+                        placeholderText: "Repeat Passw."
+                        horizontalAlignment: Text.AlignHCenter
+                        // @disable-check M222
+                        Keys.onReturnPressed: signupRegisterButton.activate()
+                        // @disable-check M222
+                        Keys.onEnterPressed: signupRegisterButton.activate()
+                        echoMode: TextInput.Password
+
+                        onTextEdited: {
+                            signupStatusText.text = ""
+                            signupRegisterButton.enabled
+                                    = (signupUsernameInput.text != ""
+                                       && signupPasswordOneInput.text != ""
+                                       && signupPasswordTwoInput.text != "")
+                        }
+
+                        Connections {
+                            target: popup
+                            onResetOnClose: signupPasswordTwoInput.text = ""
+                        }
+                    }
+
+                    CheckBox {
+                        id: signupSetDefaultCheckbox
+                        Layout.alignment: Qt.AlignCenter
+                        checked: false
+                        text: "Save as default user"
+
+                        Connections {
+                            target: popup
+                            onResetOnClose: signupSetDefaultCheckbox.checked = false
+                        }
+                    }
+
+                    Text {
+                        id: signupStatusText
+                        color: "#df3f3f"
+                        text: qsTr("")
+                        horizontalAlignment: Text.AlignHCenter
+                        verticalAlignment: Text.AlignVCenter
+                        Layout.alignment: Qt.AlignCenter
+                        wrapMode: Text.WordWrap
+                        Layout.preferredWidth: parent.width
+                        font.pixelSize: 20
+
+                        Connections {
+                            target: popup
+                            onResetOnClose: signupStatusText.text = ""
+                        }
+                    }
+
+                    Button {
+                        Layout.alignment: Qt.AlignCenter
+                        id: signupRegisterButton
+                        text: qsTr("Register")
+                        enabled: false
+                        font.pointSize: 16
+                        // @disable-check M223
+                        onClicked: {
+                            // @disable-check M222
+                            signupRegisterButton.activate()
+                        }
+
+                        // @disable-check M222
+                        function activate() {
+                            // @disable-check M223
+                            if (signupRegisterButton.enabled) {
+                                // @disable-check M222
+                                _qmlHandler.onSignupRegisterButton(
+                                            signupUsernameInput.text,
+                                            signupPasswordOneInput.text,
+                                            signupPasswordTwoInput.text,
+                                            signupSetDefaultCheckbox.checked)
+                            }
+                        }
+                    }
+                }
             }
         }
     }

+ 0 - 148
gui/src/Forms/Connect/SignupForm.ui.qml

@@ -1,148 +0,0 @@
-import QtQuick 2.12
-import QtQuick.Controls 2.5
-import QtQuick.Layouts 1.3
-
-Page {
-    width: 400
-    height: 400
-
-    Connections {
-        target: _qmlHandler
-        onSignupSetStatus: {
-            signupStatusText.text = status
-        }
-        onSignupEnableRegisterButton: {
-            signupRegisterButton.enabled = true
-        }
-        onSignupDisableRegisterButton: {
-            signupRegisterButton.enabled = false
-        }
-        onLoginSignupCheckSaveCheckbox: {
-            signupSetDefaultCheckbox.checked = true
-        }
-    }
-
-    ColumnLayout {
-        anchors.fill: parent
-
-        Text {
-            Layout.alignment: Qt.AlignCenter
-            id: signupTitle
-            color: "#ffffff"
-            text: qsTr("Signup")
-            horizontalAlignment: Text.AlignHCenter
-            verticalAlignment: Text.AlignVCenter
-            font.pixelSize: 20
-        }
-
-        TextField {
-            Layout.alignment: Qt.AlignCenter
-            id: signupUsernameInput
-            selectByMouse: true
-            focus: true
-            text: qsTr("")
-            placeholderText: "Username"
-            horizontalAlignment: Text.AlignHCenter
-            // @disable-check M222
-            Keys.onReturnPressed: signupRegisterButton.activate()
-            // @disable-check M222
-            Keys.onEnterPressed: signupRegisterButton.activate()
-
-            onTextEdited: {
-                signupStatusText.text = ""
-                signupRegisterButton.enabled = (signupUsernameInput.text != ""
-                                                && signupPasswordOneInput.text != ""
-                                                && signupPasswordTwoInput.text != "")
-            }
-        }
-
-        TextField {
-            Layout.alignment: Qt.AlignCenter
-            id: signupPasswordOneInput
-            selectByMouse: true
-            focus: true
-            text: qsTr("")
-            placeholderText: "Password"
-            horizontalAlignment: Text.AlignHCenter
-            // @disable-check M222
-            Keys.onReturnPressed: signupRegisterButton.activate()
-            // @disable-check M222
-            Keys.onEnterPressed: signupRegisterButton.activate()
-            echoMode: TextInput.Password
-
-            onTextEdited: {
-                signupStatusText.text = ""
-                signupRegisterButton.enabled = (signupUsernameInput.text != ""
-                                                && signupPasswordOneInput.text != ""
-                                                && signupPasswordTwoInput.text != "")
-            }
-        }
-
-        TextField {
-            Layout.alignment: Qt.AlignCenter
-            id: signupPasswordTwoInput
-            selectByMouse: true
-            focus: true
-            text: qsTr("")
-            placeholderText: "Repeat Passw."
-            horizontalAlignment: Text.AlignHCenter
-            // @disable-check M222
-            Keys.onReturnPressed: signupRegisterButton.activate()
-            // @disable-check M222
-            Keys.onEnterPressed: signupRegisterButton.activate()
-            echoMode: TextInput.Password
-
-            onTextEdited: {
-                signupStatusText.text = ""
-                signupRegisterButton.enabled = (signupUsernameInput.text != ""
-                                                && signupPasswordOneInput.text != ""
-                                                && signupPasswordTwoInput.text != "")
-            }
-        }
-
-        CheckBox {
-            id: signupSetDefaultCheckbox
-            Layout.alignment: Qt.AlignCenter
-            checked: false
-            text: "Save as default user"
-        }
-
-        Text {
-            id: signupStatusText
-            color: "#df3f3f"
-            text: qsTr("")
-            horizontalAlignment: Text.AlignHCenter
-            verticalAlignment: Text.AlignVCenter
-            Layout.alignment: Qt.AlignCenter
-            wrapMode: Text.WordWrap
-            Layout.preferredWidth: parent.width
-            font.pixelSize: 20
-        }
-
-        Button {
-            Layout.alignment: Qt.AlignCenter
-            id: signupRegisterButton
-            text: qsTr("Register")
-            enabled: false
-            font.pointSize: 16
-            // @disable-check M223
-            onClicked: {
-                // @disable-check M222
-                signupRegisterButton.activate()
-            }
-
-            // @disable-check M222
-            function activate() {
-                // @disable-check M223
-                if (signupRegisterButton.enabled) {
-                    // @disable-check M222
-                    _qmlHandler.onSignupRegisterButton(
-                                signupUsernameInput.text,
-                                signupPasswordOneInput.text,
-                                signupPasswordTwoInput.text,
-                                signupSetDefaultCheckbox.checked)
-                }
-            }
-        }
-    }
-}

+ 0 - 20
gui/src/Forms/Help/HelpForm.ui.qml

@@ -1,20 +0,0 @@
-import QtQuick 2.12
-import QtQuick.Controls 2.5
-
-Page {
-    width: 1280
-    height: 470
-
-    Text {
-        id: helpPlaceholder
-        x: 220
-        y: 135
-        width: 840
-        height: 200
-        color: "#ffffff"
-        text: qsTr("Tons of useful information goes here")
-        horizontalAlignment: Text.AlignHCenter
-        verticalAlignment: Text.AlignVCenter
-        font.pixelSize: 40
-    }
-}

+ 40 - 0
gui/src/Forms/Log/LogForm.ui.qml

@@ -0,0 +1,40 @@
+import QtQuick 2.4
+import QtQuick.Controls 2.4
+import QtQuick.Layouts 1.3
+
+Page {
+    width: 1280
+    height: 570
+    id: logForm
+
+    font.capitalization: Font.MixedCase
+
+    Connections {
+        target: _qmlHandler
+        onLog: {
+            log.append(new Date().toLocaleTimeString(Qt.locale("C"),
+                                                     "[hh:mm:ss]\n") + logText)
+            logFlickable.contentY = log.height - logFlickable.height
+        }
+    }
+
+    Flickable {
+        anchors.fill: parent
+        id: logFlickable
+        flickableDirection: Flickable.VerticalFlick
+        Layout.preferredHeight: 170
+        Layout.preferredWidth: parent.width
+
+        TextArea.flickable: TextArea {
+            selectByMouse: true
+            id: log
+            readOnly: true
+            wrapMode: TextArea.Wrap
+            text: qsTr("")
+            font.pointSize: 15
+        }
+
+        ScrollBar.vertical: ScrollBar {
+        }
+    }
+}

+ 29 - 47
gui/src/Forms/Main/FooterForm.ui.qml

@@ -4,75 +4,57 @@ import QtQuick.Layouts 1.3
 
 Page {
     width: 1280
-    height: 200
+    height: 70
 
     font.capitalization: Font.MixedCase
 
     Connections {
         target: _qmlHandler
-        onLog: {
-            footerLog.append(new Date().toLocaleTimeString(
-                                 Qt.locale("C"), "[hh:mm:ss]\n") + logText)
-            footerFlickable.contentY = footerLog.height - footerFlickable.height
+        onFooterSetStatus: {
+            footerStatusText.text = status
         }
 
-        onFooterSetStatus: {
-            footerStatusText.text = "Status: " + status
+        onFooterSetError: {
+            footerErrorText.text = "Error: " + error
+            timer.start()
         }
     }
 
     Rectangle {
         anchors.fill: parent
-        color: "#404040"
+        color: "#c4c4c4"
 
         ColumnLayout {
+            spacing: 3
             anchors.fill: parent
 
-            RowLayout {
-                Layout.preferredHeight: 30
+            Text {
+                id: footerStatusText
                 Layout.preferredWidth: parent.width
 
-                Text {
-                    id: footerStatusText
-                    Layout.preferredHeight: parent.height
-                    Layout.preferredWidth: 1000
-                    color: "#ffffff"
-                    text: qsTr("")
-                    font.pixelSize: 23
-                }
-
-                Button {
-                    id: footerGetStatusButton
-                    Layout.preferredHeight: 30
-                    Layout.preferredWidth: 250
-                    text: qsTr("Get Status")
-                    // @disable-check M223
-                    onClicked: {
-                        footerStatusText.text = ""
-                        // @disable-check M222
-                        _qmlHandler.onFooterGetStatusButton()
-                    }
-                }
+                text: qsTr("")
+                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+                verticalAlignment: Text.AlignVCenter
+                font.pixelSize: 23
             }
 
-            Flickable {
-                id: footerFlickable
-                flickableDirection: Flickable.VerticalFlick
-                Layout.preferredHeight: 170
+            Text {
+                id: footerErrorText
                 Layout.preferredWidth: parent.width
-
-                TextArea.flickable: TextArea {
-                    selectByMouse: true
-                    id: footerLog
-                    readOnly: true
-                    wrapMode: TextArea.Wrap
-                    text: qsTr("")
-                    font.pointSize: 15
-                }
-
-                ScrollBar.vertical: ScrollBar {
-                }
+                color: "#fa3737"
+                text: qsTr("")
+                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+                verticalAlignment: Text.AlignVCenter
+                font.pixelSize: 23
             }
         }
     }
+
+    Timer {
+        id: timer
+        interval: 5000
+        onTriggered: {
+            footerErrorText.text = ""
+        }
+    }
 }

+ 6 - 2
gui/src/Forms/Main/InvalidCliPathPopup.ui.qml

@@ -14,6 +14,10 @@ Popup {
     closePolicy: Popup.NoAutoClose
     anchors.centerIn: Overlay.overlay
 
+    Overlay.modal: Rectangle {
+        color: "#b5c5c5c5"
+    }
+
     Connections {
         target: _qmlHandler
         onInvalidCliPathPopupClose: {
@@ -32,7 +36,7 @@ Popup {
             Layout.preferredWidth: parent.width
             Layout.preferredHeight: 50
             id: invalidCliPathPopupText
-            color: "#ffffff"
+
             text: qsTr("Invalid CLI-Path: CLI could not be found on the location specified in the config file.\nPlease specify the new location of the CLI:")
             wrapMode: Text.WordWrap
             horizontalAlignment: Text.AlignHCenter
@@ -50,7 +54,7 @@ Popup {
                 Layout.preferredHeight: parent.height
                 Layout.preferredWidth: 600
                 id: invalidCliPathPopupCliPath
-                color: "#ffffff"
+
                 text: qsTr("Select CLI Path >>>")
                 horizontalAlignment: Text.AlignLeft
                 font.italic: true

+ 5 - 1
gui/src/Forms/Main/InvalidConfigPopup.ui.qml

@@ -14,6 +14,10 @@ Popup {
     closePolicy: Popup.NoAutoClose
     anchors.centerIn: Overlay.overlay
 
+    Overlay.modal: Rectangle {
+        color: "#b5c5c5c5"
+    }
+
     Connections {
         target: _qmlHandler
         onInvalidConfigPopupClose: {
@@ -32,7 +36,7 @@ Popup {
             Layout.preferredWidth: parent.width
             Layout.preferredHeight: 50
             id: invalidConfigPopupText
-            color: "#ffffff"
+
             text: qsTr("Configuration file invalid!\nWarning: Creating the default config will overwrite your config file!")
             wrapMode: Text.WordWrap
             horizontalAlignment: Text.AlignHCenter

+ 6 - 2
gui/src/Forms/Main/NoConfigFoundPopup.ui.qml

@@ -14,6 +14,10 @@ Popup {
     closePolicy: Popup.NoAutoClose
     anchors.centerIn: Overlay.overlay
 
+    Overlay.modal: Rectangle {
+        color: "#b5c5c5c5"
+    }
+
     Connections {
         target: _qmlHandler
         onNoConfigFoundPopupClose: {
@@ -32,7 +36,7 @@ Popup {
             Layout.preferredWidth: parent.width
             Layout.preferredHeight: 50
             id: noConfigFoundPopupText
-            color: "#ffffff"
+
             text: qsTr("No configuration file found. Default config will be created.\nPlease specify the location of the CLI:")
             wrapMode: Text.WordWrap
             horizontalAlignment: Text.AlignHCenter
@@ -50,7 +54,7 @@ Popup {
                 Layout.preferredHeight: parent.height
                 Layout.preferredWidth: 600
                 id: noConfigFoundPopupCliPath
-                color: "#ffffff"
+
                 text: qsTr("Select CLI Path >>>")
                 horizontalAlignment: Text.AlignLeft
                 font.italic: true

+ 66 - 32
gui/src/Forms/Main/main.qml

@@ -1,12 +1,10 @@
 import QtQuick 2.12
 import QtQuick.Controls 2.5
-import "../Sending"
-import "../Receiving"
-import "../Messages"
+import "../ServerFiles"
 import "../Settings"
-import "../Help"
 import "../Connect"
 import "../Notifications"
+import "../Log"
 
 ApplicationWindow {
     id: window
@@ -19,7 +17,7 @@ ApplicationWindow {
 
     minimumHeight: height
     minimumWidth: width
-    title: qsTr("Covert Channel - Control Panel")
+    title: qsTr("Covert Channel Application")
 
     property string notificationTabTitle: "Notifications"
 
@@ -27,25 +25,48 @@ ApplicationWindow {
         target: _qmlHandler
 
         onNotification: {
-            if (swipeView.currentIndex != 5)
+            if (swipeView.currentIndex != 2)
               notificationTabTitle = "* Notifications"
         }
     }
 
+    menuBar: MenuBar {
+        contentHeight: 30
+        Menu {
+            title: qsTr("&File")
+            Action {
+                text: qsTr("Change Server...")
+                onTriggered: {
+                    _qmlHandler.onSwitchServer()
+                    _qmlHandler.onStart(false)
+                    ipPopup.open()
+                }
+            }
+            MenuSeparator { }
+            Action {
+                text: qsTr("&Quit")
+                onTriggered: window.close()
+            }
+        }
+        Menu {
+            title: qsTr("&Help")
+            Action {
+                text: qsTr("About")
+                onTriggered: aboutPopup.open()
+            }
+        }
+    }
+
     SwipeView {
         id: swipeView
         anchors.fill: parent
         currentIndex: header.currentIndex
 
-        SendingForm {
-
-        }
-
-        ReceivingForm {
+        ServerFilesForm {
 
         }
 
-        MessagesForm {
+        NotificationsForm {
 
         }
 
@@ -53,11 +74,7 @@ ApplicationWindow {
 
         }
 
-        HelpForm {
-
-        }
-
-        NotificationsForm {
+        LogForm {
 
         }
     }
@@ -68,15 +85,15 @@ ApplicationWindow {
         contentHeight: 50
 
         TabButton {
-            text: qsTr("Sending")
+            text: qsTr("Server Files")
         }
 
         TabButton {
-            text: qsTr("Receiving")
-        }
+            text: notificationTabTitle
 
-        TabButton {
-            text: qsTr("Messages")
+            onClicked: {
+                text = "Notifications"
+            }
         }
 
         TabButton {
@@ -84,15 +101,7 @@ ApplicationWindow {
         }
 
         TabButton {
-            text: qsTr("Help")
-        }
-
-        TabButton {
-            text: notificationTabTitle
-
-            onClicked: {
-                text = "Notifications"
-            }
+            text: qsTr("Log")
         }
     }
 
@@ -120,9 +129,34 @@ ApplicationWindow {
         id: invalidCliPathPopup
     }
 
+    Popup {
+        id: aboutPopup
+        height: 300
+        dim: true
+        clip: false
+        width: 400
+        modal: true
+        focus: true
+        anchors.centerIn: Overlay.overlay
+
+        Overlay.modal: Rectangle {
+                color: "#b5b5b5e7"
+            }
+
+        TextArea {
+            anchors.fill: parent
+            text: "Covert Channel Application\n\nDeveloped by:\n\nTobias Alexander Wach\nPaul Leonard Sander\nMarius Rescheleit\nDenys Serdyukov\nJonas Pflanzer\n\nLicensed under the GNU General Public License v3.0"
+            wrapMode: Text.WordWrap
+            horizontalAlignment: Text.AlignHCenter
+            verticalAlignment: Text.AlignVCenter
+            font.pixelSize: 20
+        }
+    }
+
+
     Component.onCompleted: {
       swipeView.interactive = false
       ipPopup.open()
-      _qmlHandler.onStart()
+      _qmlHandler.onStart(true)
     }
 }

+ 0 - 84
gui/src/Forms/Messages/MessagesForm.ui.qml

@@ -1,84 +0,0 @@
-import QtQuick 2.12
-import QtQuick.Controls 2.5
-import QtQuick.Layouts 1.3
-
-Page {
-    width: 1280
-    height: 470
-
-    font.capitalization: Font.MixedCase
-
-    Connections {
-        target: _qmlHandler
-        onMessage: {
-            messagesLog.append(msg)
-            messagesFlickable.contentY = messagesLog.height - messagesFlickable.height
-        }
-    }
-
-    ColumnLayout {
-        anchors.fill: parent
-
-        Flickable {
-            id: messagesFlickable
-            flickableDirection: Flickable.VerticalFlick
-            Layout.preferredHeight: 400
-            Layout.preferredWidth: parent.width
-
-            TextArea.flickable: TextArea {
-                id: messagesLog
-                readOnly: true
-                selectByMouse: true
-                wrapMode: TextArea.Wrap
-                font.pointSize: 15
-            }
-
-            ScrollBar.vertical: ScrollBar {
-            }
-        }
-
-        RowLayout {
-            Layout.preferredHeight: 70
-            Layout.preferredWidth: parent.width
-
-            TextField {
-                id: messagesInputField
-                selectByMouse: true
-                Layout.preferredWidth: 1060
-                Layout.preferredHeight: parent.height
-                placeholderText: "Enter message..."
-                text: qsTr("")
-                font.pixelSize: 20
-                // @disable-check M222
-                Keys.onReturnPressed: messagesSendButton.activate()
-                // @disable-check M222
-                Keys.onEnterPressed: messagesSendButton.activate()
-            }
-
-            Button {
-                id: messagesSendButton
-                Layout.preferredWidth: 180
-                Layout.preferredHeight: parent.height
-                text: qsTr("Send")
-
-                // @disable-check M223
-                onClicked: {
-                    // @disable-check M222
-                    messagesSendButton.activate()
-                }
-
-                enabled: messagesInputField.text != ""
-                // @disable-check M222
-                function activate() {
-                    // @disable-check M223
-                    if (messagesInputField.text != "") {
-                        // @disable-check M222
-                        _qmlHandler.onMessagesSendButton(
-                                    messagesInputField.text)
-                        messagesInputField.text = ""
-                    }
-                }
-            }
-        }
-    }
-}

+ 0 - 2
gui/src/Forms/Notifications/NotificationTemplate.ui.qml

@@ -21,7 +21,6 @@ Item {
             verticalAlignment: Text.AlignVCenter
             horizontalAlignment: Text.AlignHLeft
             text: notificationDateText
-            color: "#ffffff"
         }
 
         Text {
@@ -32,7 +31,6 @@ Item {
             verticalAlignment: Text.AlignVCenter
             horizontalAlignment: Text.AlignHLeft
             text: notificationMessageText
-            color: "#ffffff"
         }
 
         Button {

+ 2 - 3
gui/src/Forms/Notifications/NotificationsForm.ui.qml

@@ -5,7 +5,7 @@ import Qt.labs.platform 1.1
 
 Page {
     width: 1280
-    height: 470
+    height: 570
     id: notificationsForm
 
     font.capitalization: Font.MixedCase
@@ -60,7 +60,7 @@ Page {
             Layout.preferredWidth: parent.width
             Layout.preferredHeight: 30
             id: loginTitle
-            color: "#ffffff"
+
             text: qsTr("No new notifications!")
             horizontalAlignment: Text.AlignHCenter
             verticalAlignment: Text.AlignVCenter
@@ -88,6 +88,5 @@ Page {
     SystemTrayIcon {
         id: trayIcon
         visible: true
-        icon.source: "qrc:/images/tray-icon.png"
     }
 }

+ 0 - 76
gui/src/Forms/Receiving/ReceivingForm.ui.qml

@@ -1,76 +0,0 @@
-import QtQuick 2.12
-import QtQuick.Controls 2.5
-import QtQuick.Layouts 1.3
-
-Page {
-    width: 1280
-    height: 470
-    id: receivingForm
-
-    font.capitalization: Font.MixedCase
-
-    Connections {
-        target: _qmlHandler
-        onReceivingListFile: {
-            fileList.append({
-                                "fileName": fileName,
-                                "fileSize": fileSize + " kB",
-                                "fileProgress": "",
-                                "fileDecryptable": fileDecryptable,
-                                "fileExistsLocally": existsLocally
-                            })
-        }
-
-        onReceivingClearFileList: {
-            fileList.clear()
-        }
-    }
-
-    ColumnLayout {
-        anchors.fill: parent
-
-        ScrollView {
-            Layout.preferredWidth: parent.width
-            Layout.preferredHeight: 400
-
-            ListView {
-                anchors.fill: parent
-                model: fileList
-                clip: true
-
-                delegate: ReceivingFileTemplate {
-                    fileNameText: fileName
-                    fileSizeText: fileSize
-                    fileProgressText: fileProgress
-                    fileDecryptableText: fileDecryptable
-                    fileExists: fileExistsLocally
-                }
-            }
-        }
-
-        ListModel {
-            id: fileList
-        }
-
-        RowLayout {
-            Layout.preferredWidth: parent.width
-            Layout.preferredHeight: 70
-
-            Button {
-                id: receivingListFilesButton
-                Layout.preferredWidth: 180
-                Layout.preferredHeight: parent.height
-                text: qsTr("Refresh File List")
-
-                // @disable-check M223
-                onClicked: {
-                    // @disable-check M222
-                    _qmlHandler.onReceivingListFilesButton()
-                }
-            }
-        }
-    }
-}
-
-
-

+ 0 - 95
gui/src/Forms/Sending/SendingForm.ui.qml

@@ -1,95 +0,0 @@
-import QtQuick 2.12
-import QtQuick.Controls 2.5
-import QtQuick.Dialogs 1.0
-
-Page {
-    width: 1280
-    height: 470
-    font.capitalization: Font.MixedCase
-
-    title: qsTr("Sending")
-
-    Connections {
-        target: _qmlHandler
-        onSendingSetFileUrlText: {
-            sendingSelectedFileText.text = signalText
-        }
-        onSendingEnableSendButton: {
-            sendingSendFileButton.enabled = true
-        }
-        onSendingDisableSendButton: {
-            sendingSendFileButton.enabled = false
-        }
-    }
-
-    Button {
-        id: sendingClearFileButton
-        x: 900
-        y: 40
-        width: 260
-        height: 90
-        text: qsTr("Clear Selection")
-        enabled: sendingSendFileButton.enabled
-        font.pointSize: 16
-        // @disable-check M223
-        onClicked: {
-            // @disable-check M222
-            _qmlHandler.onSendingClearSelectionButton()
-        }
-    }
-
-    Button {
-        id: sendingSelectFileButton
-        x: 120
-        y: 40
-        width: 260
-        height: 90
-        text: qsTr("Select File")
-        font.pointSize: 16
-        // @disable-check M223
-        onClicked: {
-            // @disable-check M222
-            sendingFileDialog.open()
-        }
-    }
-
-    Text {
-        id: sendingSelectedFileText
-        x: 54
-        y: 186
-        width: 1172
-        height: 52
-        color: "#ffffff"
-        text: qsTr("Selected File: None")
-        verticalAlignment: Text.AlignVCenter
-        horizontalAlignment: Text.AlignHCenter
-        font.pixelSize: 23
-    }
-
-    FileDialog {
-        id: sendingFileDialog
-        title: "Please choose a file"
-        folder: shortcuts.home
-        // @disable-check M223
-        onAccepted: {
-            // @disable-check M222
-            _qmlHandler.onSendingSelectFileButton(sendingFileDialog.fileUrl)
-        }
-    }
-
-    Button {
-        id: sendingSendFileButton
-        x: 510
-        y: 304
-        enabled: false
-        width: 260
-        height: 90
-        text: qsTr("Send File")
-        font.pointSize: 16
-        // @disable-check M223
-        onClicked: {
-            // @disable-check M222
-            _qmlHandler.onSendingSendFileButton()
-        }
-    }
-}

+ 9 - 10
gui/src/Forms/Receiving/ReceivingFileTemplate.ui.qml → gui/src/Forms/ServerFiles/ServerFilesFileTemplate.ui.qml

@@ -14,20 +14,20 @@ Item {
 
     Connections {
         target: _qmlHandler
-        onReceivingDisableDownloadButton: {
+        onServerFilesDisableDownloadButton: {
             if (fileNameText == fileName) {
                 fileExists = true
             }
         }
 
-        onReceivingUpdateFile: {
+        onServerFilesUpdateFile: {
             if (fileNameText == fileName) {
                 fileProgressText = fileProgress
                 fileQueued = isQueued
             }
         }
 
-        onReceivingCloseConfirmDeletePopup: {
+        onServerFilesCloseConfirmDeletePopup: {
             confirmDeletePopup.close()
         }
     }
@@ -43,7 +43,6 @@ Item {
             Layout.preferredWidth: 400
             verticalAlignment: Text.AlignVCenter
             text: fileNameText
-            color: "#ffffff"
         }
 
         Text {
@@ -52,9 +51,8 @@ Item {
             Layout.preferredHeight: parent.height
             Layout.preferredWidth: 100
             verticalAlignment: Text.AlignVCenter
-            horizontalAlignment: Text.AlignHCenter
+            horizontalAlignment: Text.AlignRight
             text: fileSizeText
-            color: "#ffffff"
         }
 
         Text {
@@ -65,7 +63,6 @@ Item {
             verticalAlignment: Text.AlignVCenter
             horizontalAlignment: Text.AlignHCenter
             text: fileProgressText
-            color: "#ffffff"
         }
 
         Text {
@@ -76,7 +73,9 @@ Item {
             verticalAlignment: Text.AlignVCenter
             horizontalAlignment: Text.AlignHCenter
             text: fileDecryptableText
-            color: "#ffffff"
+            color: (fileDecryptableText
+                    == "decryptable") ? "#00ad11" : (fileDecryptableText
+                                                     == "undecryptable") ? "#df3f3f" : "#000000"
         }
 
         Button {
@@ -107,7 +106,7 @@ Item {
             // @disable-check M223
             onClicked: {
                 // @disable-check M222
-                _qmlHandler.onReceivingDownloadFileButton(fileNameText)
+                _qmlHandler.onServerFilesDownloadFileButton(fileNameText)
             }
         }
 
@@ -126,7 +125,7 @@ Item {
         }
     }
 
-    ReceivingFileTemplateDeletePopup {
+    ServerFilesFileTemplateDeletePopup {
         id: confirmDeletePopup
     }
 }

+ 6 - 2
gui/src/Forms/Receiving/ReceivingFileTemplateDeletePopup.ui.qml → gui/src/Forms/ServerFiles/ServerFilesFileTemplateDeletePopup.ui.qml

@@ -13,6 +13,10 @@ Popup {
     closePolicy: Popup.NoAutoClose
     anchors.centerIn: Overlay.overlay
 
+    Overlay.modal: Rectangle {
+        color: "#b5c5c5c5"
+    }
+
     ColumnLayout {
         anchors.fill: parent
 
@@ -26,7 +30,6 @@ Popup {
             horizontalAlignment: Text.AlignHCenter
             wrapMode: Text.WordWrap
             font.pointSize: 15
-            color: "#ffffff"
         }
         RowLayout {
             Layout.preferredHeight: 80
@@ -39,7 +42,8 @@ Popup {
                 // @disable-check M223
                 onClicked: {
                     // @disable-check M222
-                    _qmlHandler.onReceivingConfirmDeleteFileButton(fileNameText)
+                    _qmlHandler.onServerFilesConfirmDeleteFileButton(
+                                fileNameText)
                 }
             }
 

+ 225 - 0
gui/src/Forms/ServerFiles/ServerFilesForm.ui.qml

@@ -0,0 +1,225 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.5
+import QtQuick.Controls.Material 2.3
+import QtQuick.Layouts 1.3
+import QtQuick.Dialogs 1.0
+
+Page {
+    width: 1280
+    height: 570
+    id: serverFilesForm
+
+    font.capitalization: Font.MixedCase
+
+    Connections {
+        target: _qmlHandler
+        onServerFilesListFile: {
+            fileList.append({
+                                "fileName": fileName,
+                                "fileSize": fileSize + " kB",
+                                "fileProgress": "",
+                                "fileDecryptable": fileDecryptable,
+                                "fileExistsLocally": existsLocally
+                            })
+        }
+
+        onServerFilesClearFileList: {
+            fileList.clear()
+        }
+
+        onServerFilesSetFileUrlText: {
+            sendingSelectedFileText.text = signalText
+        }
+
+        onServerFilesEnableSendButton: {
+            sendingSendFileButton.enabled = true
+        }
+
+        onServerFilesDisableSendButton: {
+            sendingSendFileButton.enabled = false
+        }
+    }
+
+    ColumnLayout {
+        anchors.fill: parent
+
+        RowLayout {
+            Layout.preferredWidth: 750
+            Layout.preferredHeight: 30
+
+            Text {
+
+                text: qsTr("File Name")
+                Layout.alignment: Qt.AlignCenter
+                Layout.preferredWidth: 400
+                Layout.preferredHeight: parent.height
+                verticalAlignment: Text.AlignVCenter
+                horizontalAlignment: Text.AlignLeft
+                font.pixelSize: 15
+            }
+
+            Text {
+
+                text: qsTr("Size")
+                Layout.alignment: Qt.AlignCenter
+                Layout.preferredWidth: 100
+                Layout.preferredHeight: parent.height
+                verticalAlignment: Text.AlignVCenter
+                horizontalAlignment: Text.AlignHCenter
+                font.pixelSize: 15
+            }
+
+            Text {
+
+                text: qsTr("Status")
+                Layout.alignment: Qt.AlignCenter
+                Layout.preferredWidth: 100
+                Layout.preferredHeight: parent.height
+                verticalAlignment: Text.AlignVCenter
+                horizontalAlignment: Text.AlignHCenter
+                font.pixelSize: 15
+            }
+
+            Text {
+
+                text: qsTr("Encryption")
+                Layout.alignment: Qt.AlignCenter
+                Layout.preferredWidth: 150
+                Layout.preferredHeight: parent.height
+                verticalAlignment: Text.AlignVCenter
+                horizontalAlignment: Text.AlignHCenter
+                font.pixelSize: 15
+            }
+        }
+
+        Rectangle {
+            Layout.alignment: Qt.AlignCenter
+            Layout.preferredWidth: parent.width
+            Layout.preferredHeight: 2
+            color: "#404040"
+        }
+
+        ScrollView {
+            Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+            Layout.preferredWidth: parent.width
+
+            Layout.preferredHeight: 440
+            ListView {
+                anchors.fill: parent
+                model: fileList
+                clip: true
+
+                delegate: ServerFilesFileTemplate {
+                    fileNameText: fileName
+                    fileSizeText: fileSize
+                    fileProgressText: fileProgress
+                    fileDecryptableText: fileDecryptable
+                    fileExists: fileExistsLocally
+                }
+            }
+        }
+
+        ListModel {
+            id: fileList
+        }
+
+        Rectangle {
+            Layout.alignment: Qt.AlignCenter
+            Layout.preferredWidth: parent.width
+            Layout.preferredHeight: 2
+            color: Material.accent
+        }
+
+        RowLayout {
+            Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+            Layout.preferredWidth: parent.width
+
+            Text {
+
+                text: qsTr("Selected File: ")
+                Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+                Layout.preferredWidth: 150
+                Layout.preferredHeight: 50
+                verticalAlignment: Text.AlignVCenter
+                horizontalAlignment: Text.AlignHCenter
+                font.pixelSize: 23
+            }
+
+            Text {
+                id: sendingSelectedFileText
+
+                text: qsTr("None")
+                Layout.preferredWidth: 450
+                Layout.preferredHeight: 50
+                verticalAlignment: Text.AlignVCenter
+                horizontalAlignment: Text.AlignLeft
+                font.pixelSize: 23
+                elide: Text.ElideLeft
+                ToolTip.visible: mouseArea.containsMouse
+                ToolTip.text: text
+                ToolTip.delay: 200
+
+                MouseArea {
+                    id: mouseArea
+                    anchors.fill: parent
+                    hoverEnabled: true
+                }
+            }
+
+            Button {
+                id: sendingClearFileButton
+                text: qsTr("Clear Selection")
+                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+                Layout.preferredWidth: 200
+                Layout.preferredHeight: 50
+                enabled: sendingSendFileButton.enabled
+                font.pointSize: 16
+                // @disable-check M223
+                onClicked: {
+                    // @disable-check M222
+                    _qmlHandler.onServerFilesClearSelectionButton()
+                }
+            }
+
+            Button {
+                id: sendingSelectFileButton
+                text: qsTr("Select File")
+                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+                Layout.preferredWidth: 200
+                Layout.preferredHeight: 50
+                font.pointSize: 16
+                // @disable-check M223
+                onClicked: {
+                    // @disable-check M222
+                    sendingFileDialog.open()
+                }
+            }
+
+            Button {
+                id: sendingSendFileButton
+                enabled: false
+                text: qsTr("Upload File")
+                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+                Layout.preferredWidth: 200
+                Layout.preferredHeight: 50
+                font.pointSize: 16
+                // @disable-check M223
+                onClicked: {
+                    // @disable-check M222
+                    _qmlHandler.onServerFilesSendFileButton()
+                }
+            }
+        }
+    }
+
+    FileDialog {
+        id: sendingFileDialog
+        title: "Please choose a file"
+        folder: shortcuts.home
+        // @disable-check M223
+        onAccepted: {
+            // @disable-check M222
+            _qmlHandler.onServerFilesSelectFileButton(sendingFileDialog.fileUrl)
+        }
+    }
+}

+ 9 - 1
gui/src/Forms/Settings/DeleteMePopup.ui.qml

@@ -15,6 +15,10 @@ Popup {
 
     signal resetStatus
 
+    Overlay.modal: Rectangle {
+        color: "#b5c5c5c5"
+    }
+
     onClosed: {
         resetStatus()
     }
@@ -33,7 +37,7 @@ Popup {
             Layout.alignment: Qt.AlignCenter
             Layout.preferredWidth: parent.width
             Layout.preferredHeight: 100
-            color: "#ffffff"
+
             text: qsTr("Are you sure you want to delete your account on the server?\nThe application will restart.")
             wrapMode: Text.WordWrap
             horizontalAlignment: Text.AlignHCenter
@@ -67,6 +71,10 @@ Popup {
             text: ""
             horizontalAlignment: Text.AlignHCenter
             placeholderText: "Enter password to confirm"
+            Connections {
+                target: popup
+                onResetStatus: deleteMePopupPasswordInput.text = ""
+            }
         }
 
         RowLayout {

+ 100 - 38
gui/src/Forms/Settings/SettingsForm.ui.qml

@@ -6,8 +6,8 @@ import QtQuick.Dialogs 1.0
 
 Page {
     width: 1280
-    height: 470
-    id: page
+    height: 570
+    id: settingsForm
 
     font.capitalization: Font.MixedCase
 
@@ -17,10 +17,25 @@ Page {
             window.close()
         }
         onLoadSettings: {
-            settingsCovertMethodPicker.currentIndex = covertMethod
             settingsSaveIpSwitch.checked = saveIP
             settingsSaveUsernameSwitch.checked = saveUsername
-            settingsCliPath.text = "CLI-Path:     " + cliPath
+            settingsCliPath.text = "CLI-Path:   " + cliPath
+            settingsKeyPath.text = "            " + keyPath
+        }
+        onKeyfileStatus: {
+            if (success) {
+                settingsKeyStatus.text = "Keyfile: OK"
+                settingsKeyStatus.color = "#00ad11"
+            } else {
+                settingsKeyStatus.text = "Keyfile: Error"
+                settingsKeyStatus.color = "#df3f3f"
+                settingsKeyPath.text = msg
+            }
+        }
+        onKeyfileClosedOK: {
+            settingsKeyStatus.text = "Keyfile:"
+            settingsKeyStatus.color = "#000000"
+            settingsKeyPath.text = "   "
         }
     }
 
@@ -42,8 +57,8 @@ Page {
                     Layout.alignment: Qt.AlignCenter
                     Layout.preferredWidth: 400
                     Layout.preferredHeight: 50
-                    color: "#ffffff"
-                    text: "Covert Channel Method:"
+
+                    text: "Autofill default IP on start:"
                     verticalAlignment: Text.AlignVCenter
                     horizontalAlignment: Text.AlignLeft
                     font.pixelSize: 20
@@ -53,31 +68,44 @@ Page {
                     Layout.alignment: Qt.AlignCenter
                     Layout.preferredWidth: 400
                     Layout.preferredHeight: 50
-                    color: "#ffffff"
-                    text: "Autofill default IP on start:"
+
+                    text: "Autofill default username on start:"
                     verticalAlignment: Text.AlignVCenter
                     horizontalAlignment: Text.AlignLeft
                     font.pixelSize: 20
                 }
 
                 Text {
+                    id: settingsCliPath
                     Layout.alignment: Qt.AlignCenter
                     Layout.preferredWidth: 400
                     Layout.preferredHeight: 50
-                    color: "#ffffff"
-                    text: "Autofill default username on start:"
+
+                    text: "CLI-Path:   "
                     verticalAlignment: Text.AlignVCenter
                     horizontalAlignment: Text.AlignLeft
                     font.pixelSize: 20
                 }
 
                 Text {
-                    id: settingsCliPath
+                    id: settingsKeyStatus
                     Layout.alignment: Qt.AlignCenter
                     Layout.preferredWidth: 400
                     Layout.preferredHeight: 50
-                    color: "#ffffff"
-                    text: "CLI-Path:   "
+
+                    text: "Keyfile:"
+                    verticalAlignment: Text.AlignVCenter
+                    horizontalAlignment: Text.AlignLeft
+                    font.pixelSize: 20
+                }
+
+                Text {
+                    id: settingsKeyPath
+                    Layout.alignment: Qt.AlignCenter
+                    Layout.preferredWidth: 400
+                    Layout.preferredHeight: 50
+
+                    text: "   "
                     verticalAlignment: Text.AlignVCenter
                     horizontalAlignment: Text.AlignLeft
                     font.pixelSize: 20
@@ -87,7 +115,7 @@ Page {
                     Layout.alignment: Qt.AlignCenter
                     Layout.preferredWidth: 400
                     Layout.preferredHeight: 50
-                    color: "#ffffff"
+
                     text: "Delete my account:"
                     verticalAlignment: Text.AlignVCenter
                     horizontalAlignment: Text.AlignLeft
@@ -100,25 +128,6 @@ Page {
                 Layout.preferredWidth: 500
                 Layout.preferredHeight: parent.height
 
-                ComboBox {
-                    id: settingsCovertMethodPicker
-                    Layout.alignment: Qt.AlignCenter
-                    Layout.preferredHeight: 50
-                    Layout.preferredWidth: 400
-
-                    model: ListModel {
-                        ListElement {
-                            text: "Method 1"
-                        }
-                        ListElement {
-                            text: "Method 2"
-                        }
-                        ListElement {
-                            text: "Method 3"
-                        }
-                    }
-                }
-
                 Switch {
                     id: settingsSaveIpSwitch
                     Layout.alignment: Qt.AlignCenter
@@ -153,6 +162,37 @@ Page {
                     }
                 }
 
+                ColumnLayout {
+                    Layout.alignment: Qt.AlignCenter
+                    Button {
+                        id: settingsChangeKeyfilePathButton
+                        Layout.alignment: Qt.AlignCenter
+                        Layout.preferredHeight: 50
+                        Layout.preferredWidth: 220
+                        text: "Select"
+                        font.pixelSize: 20
+                        // @disable-check M223
+                        onClicked: {
+                            // @disable-check M222
+                            settingsKeyfileDialog.open()
+                        }
+                    }
+
+                    Button {
+                        id: settingsDisableKeyfile
+                        Layout.alignment: Qt.AlignCenter
+                        Layout.preferredHeight: 50
+                        Layout.preferredWidth: 220
+                        text: "Close Keyfile"
+                        font.pixelSize: 20
+                        // @disable-check M223
+                        onClicked: {
+                            // @disable-check M222
+                            _qmlHandler.onKeyfileClosed()
+                        }
+                    }
+                }
+
                 Button {
                     id: settingsDeleteMeButton
                     Layout.alignment: Qt.AlignCenter
@@ -173,7 +213,6 @@ Page {
             Layout.alignment: Qt.AlignCenter
             Layout.preferredWidth: parent.width
             Layout.preferredHeight: 2
-            Layout.bottomMargin: 30
             color: Material.accent
         }
 
@@ -190,7 +229,9 @@ Page {
                 Layout.preferredHeight: 50
                 text: "Select defaults"
                 font.pixelSize: 20
+                // @disable-check M223
                 onClicked: {
+                    // @disable-check M222
                     _qmlHandler.onSettingsResetButton()
                 }
             }
@@ -202,13 +243,17 @@ Page {
                 Layout.preferredHeight: 50
                 text: "Save Changes"
                 font.pixelSize: 20
+                // @disable-check M223
                 onClicked: {
+                    // @disable-check M222
                     _qmlHandler.onSettingsSaveButton(
-                                settingsCovertMethodPicker.currentIndex,
                                 settingsSaveIpSwitch.checked,
                                 settingsSaveUsernameSwitch.checked,
-                                settingsCliPath.text.replace("CLI-Path:     ",
-                                                             ""))
+                                // @disable-check M222
+                                settingsCliPath.text.replace("CLI-Path:   ",
+                                                             ""),
+                                // @disable-check M222
+                                settingsKeyPath.text.replace("   ", ""))
                 }
             }
 
@@ -219,7 +264,9 @@ Page {
                 Layout.preferredHeight: 50
                 text: "Revert Changes"
                 font.pixelSize: 20
+                // @disable-check M223
                 onClicked: {
+                    // @disable-check M222
                     _qmlHandler.onSettingsRevertChangesButton()
                 }
             }
@@ -233,13 +280,28 @@ Page {
         folder: shortcuts.home
         // @disable-check M223
         onAccepted: {
-            // @disable-check M222
             var path = settingsCliDialog.fileUrl.toString()
+            // @disable-check M222
             path = path.replace(/^(file:\/{2})/, "")
             settingsCliPath.text = "CLI-Path:   " + path
         }
     }
 
+    FileDialog {
+        id: settingsKeyfileDialog
+        title: "Select keyfile to use"
+        folder: shortcuts.home
+        // @disable-check M223
+        onAccepted: {
+            var path = settingsKeyfileDialog.fileUrl.toString()
+            // @disable-check M222
+            path = path.replace(/^(file:\/{2})/, "")
+            settingsKeyPath.text = "   " + path
+            // @disable-check M222
+            _qmlHandler.onKeyfileSelected(path)
+        }
+    }
+
     DeleteMePopup {
         id: deleteMePopup
     }

+ 42 - 23
gui/src/climanager.cpp

@@ -29,7 +29,8 @@ thread statusLoopThread;
 namespace CliManager {
 
 QMLHandler *qmlHandler;
-bool programActive = true;
+bool programActive = false;
+bool loggedin = false;
 int inpipefd[2];
 int outpipefd[2];
 
@@ -45,17 +46,24 @@ void CliManager::writeToCli(QString command) {
 
 void CliManager::onExit() {
 	// stop threads
-	CliManager::setProgramActive(false);
-	readPipeLoopThread.join();
-	notificationsLoopThread.join();
-	statusLoopThread.join();
-
-	writeToCli("exit");
+	if (programActive) {
+		writeToCli("exit");
+		CliManager::setProgramActive(false);
+		readPipeLoopThread.join();
+		notificationsLoopThread.join();
+		statusLoopThread.join();
+	}
 }
 
 void CliManager::setQmlHandler(QMLHandler *q) { qmlHandler = q; }
 
-void CliManager::init() {
+void CliManager::init(bool useSSL) {
+	qInfo() << QString::fromStdString(string("InitCLI with path ") + Config::getValue("CLI-Path"));
+	emit qmlHandler->log(QString::fromStdString(string("InitCLI with path ") + Config::getValue("CLI-Path")));
+	if (!Config::getValue("CLI-Path").size()) {
+		qInfo() << "Empty CLI-Path!";
+		return;
+	}
 	pipe(inpipefd);
 	pipe(outpipefd);
 
@@ -69,7 +77,11 @@ void CliManager::init() {
 		// ask kernel to deliver SIGTERM in case the parent dies
 		prctl(PR_SET_PDEATHSIG, SIGTERM);
 
-		execl(Config::getValue("CLI-Path").c_str(), "ccats-cli", "--machine", (char *)NULL);
+		if (useSSL) {
+			execl(Config::getValue("CLI-Path").c_str(), "ccats-cli", "--machine", "--usessl", Config::getValue("SSL-Path").c_str(), (char *)NULL);
+		} else {
+			execl(Config::getValue("CLI-Path").c_str(), "ccats-cli", "--machine", (char *)NULL);
+		}
 
 		exit(1);
 	}
@@ -77,6 +89,13 @@ void CliManager::init() {
 	close(outpipefd[0]);
 	close(inpipefd[1]);
 
+	setProgramActive(true);
+
+	if (Config::getValue("Keyfile-Path").size()) {
+		QString cmd = QString("keyfile ") + Config::getValue("Keyfile-Path").c_str();
+		writeToCli(cmd);
+	}
+
 	// start threads
 	readPipeLoopThread = thread(&CliManager::readPipeLoop);
 	notificationsLoopThread = thread(&CliManager::notificationsLoop);
@@ -121,9 +140,6 @@ void CliManager::readPipeLoop() {
 			pipeInput.append(buf);
 			inputs = tokenizeByNewlines(pipeInput);
 			for (string s : inputs) {
-				emit qmlHandler->log(QString::fromStdString(s));
-				qInfo() << QString::fromStdString(s);
-				// handleJSON(s);
 				JsonHandler::parseJSON(s);
 			}
 			pipeInput = string();
@@ -131,6 +147,11 @@ void CliManager::readPipeLoop() {
 			pollCount = 0;
 			readOffset = 0;
 			if (waitpid(childpid, NULL, WNOHANG)) {
+				qInfo() << "CLI has exited/did not launch";
+				emit qmlHandler->log("CLI has exited/did not launch");
+
+				emit qmlHandler->footerSetError("CLI crashed. Please restart the application.");
+				setProgramActive(false);
 				// nonzero means error or childid has changed state
 				// for us that means child has exited -> CLI is dead
 				break;
@@ -141,15 +162,9 @@ void CliManager::readPipeLoop() {
 			pipeInput.append(buf);
 			inputs = tokenizeByNewlines(pipeInput);
 			for (unsigned i = 0; i < inputs.size() - 1; i++) { // process all lines except the last, potentially incomplete one
-				// do not log these lines, Qt/Quick has no simple way of limiting the number of lines in the logging window so we might go OoM real quick
-				//~ emit qmlHandler->log(QString::fromStdString(inputs[i]));
-				qInfo() << QString::fromStdString(inputs[i]);
-				// handleJSON(s);
 				JsonHandler::parseJSON(inputs[i]);
 			}
 			if (pipeInput.back() == '\n') { // process last line if it was complete
-				qInfo() << QString::fromStdString(inputs.back());
-				// handleJSON(s);
 				JsonHandler::parseJSON(inputs.back());
 				pipeInput = string();
 			} else {
@@ -164,17 +179,21 @@ void CliManager::readPipeLoop() {
 
 void CliManager::notificationsLoop() {
 	while (programActive) {
-		std::this_thread::sleep_for(std::chrono::milliseconds(3000));
-		writeToCli("notifications");
-		writeToCli("extendedlist");
+		std::this_thread::sleep_for(std::chrono::seconds(3));
+		if (loggedin) {
+			writeToCli("notifications");
+			writeToCli("extendedlist");
+		}
 	}
 }
 
 void CliManager::statusLoop() {
 	while (programActive) {
 		std::this_thread::sleep_for(std::chrono::seconds(1));
-		writeToCli("status");
-		writeToCli("extendedstatus");
+		if (loggedin) {
+			writeToCli("status");
+			writeToCli("extendedstatus");
+		}
 	}
 }
 

+ 63 - 15
gui/src/cmdmanager.cpp

@@ -9,6 +9,8 @@
 using namespace std;
 
 namespace CmdManager {
+QString cachedIP;
+QString cachedPort;
 QMLHandler *qmlHandler;
 map<string, void (*)(Json::Value)> cmdmap;
 map<string, struct fileEntry> filemap;
@@ -34,6 +36,10 @@ void CmdManager::init() {
 	cmdmap["queue"] = &CmdManager::handleQueue;
 	cmdmap["dequeue"] = &CmdManager::handleDequeue;
 	cmdmap["extendedstatus"] = &CmdManager::handleExtendedStatus;
+	cmdmap["keyfile"] = &CmdManager::handleKeyfile;
+	cmdmap["closekey"] = &CmdManager::handleClosekey;
+	cmdmap["disconnect"] = &CmdManager::handleDisconnect;
+	cmdmap["connectionerror"] = &CmdManager::handleConnectionError;
 
 	filemap.clear();
 }
@@ -65,11 +71,11 @@ void CmdManager::emitFileList() {
 				progstr = it->second.type + " via " + it->second.method + "\n" + std::to_string(it->second.progress) + "%" + " @ " + speedbuf;
 			} else
 				progstr = it->second.type + "\n" + std::to_string(it->second.progress) + "%";
-			emit qmlHandler->receivingUpdateFile(it->first.c_str(), progstr.c_str(), it->second.type == "Queued" || it->second.type == "Sending");
+			emit qmlHandler->serverFilesUpdateFile(it->first.c_str(), progstr.c_str(), it->second.type == "Queued" || it->second.type == "Sending");
 		}
 		// else emit plain entry
 		else
-			emit qmlHandler->receivingUpdateFile(it->first.c_str(), "", false);
+			emit qmlHandler->serverFilesUpdateFile(it->first.c_str(), "", false);
 	}
 }
 
@@ -104,6 +110,10 @@ void CmdManager::executeCmd(string cmd, Json::Value root) {
 	cmdmap[cmd](root);
 }
 
+void CmdManager::setCachedIP(QString ip) { cachedIP = ip; }
+
+void CmdManager::setCachedPort(QString port) { cachedPort = port; }
+
 void CmdManager::handleError(Json::Value root) { emit qmlHandler->log(root["error"].asString().c_str()); }
 
 void CmdManager::handleStatus(Json::Value root) { emit qmlHandler->footerSetStatus(root["response"].asString().c_str()); }
@@ -114,14 +124,14 @@ void CmdManager::handleList(Json::Value root) {
 	char sizebuf[256];
 	snprintf(sizebuf, 256, "%.2f", 4.2f);
 	if (root["accept"] == true) {
-		emit qmlHandler->receivingClearFileList();
+		emit qmlHandler->serverFilesClearFileList();
 		filemap.clear();
 
 		// Get the array of file Names
 		auto fileNames = root["names"];
 		for (int i = 0; i < fileNames.size(); i++) {
-			emit qmlHandler->receivingListFile(QString::fromStdString(fileNames[i].asString()), QString::fromStdString(sizebuf), QString("Decryptable"),
-			                                   !!ifstream(fileNames[i].asString()));
+			emit qmlHandler->serverFilesListFile(QString::fromStdString(fileNames[i].asString()), QString::fromStdString(sizebuf), QString("Decryptable"),
+			                                     !!ifstream(fileNames[i].asString()));
 			filemap[fileNames[i].asString()] = {false, "", "", 0, 0};
 		}
 	} else {
@@ -132,15 +142,15 @@ void CmdManager::handleList(Json::Value root) {
 void CmdManager::handleExtendedList(Json::Value root) {
 	char sizebuf[256];
 	if (root["accept"] == true) {
-		emit qmlHandler->receivingClearFileList();
+		emit qmlHandler->serverFilesClearFileList();
 		filemap.clear();
 
 		// Get the array of file Names
 		auto files = root["files"];
 		for (Json::Value f : files) {
 			snprintf(sizebuf, 256, "%.2f", f["size"].asFloat());
-			emit qmlHandler->receivingListFile(QString::fromStdString(f["name"].asString()), QString::fromStdString(sizebuf),
-			                                   QString::fromStdString(f["encrypted"].asString()), !!ifstream(f["name"].asString()));
+			emit qmlHandler->serverFilesListFile(QString::fromStdString(f["name"].asString()), QString::fromStdString(sizebuf),
+			                                     QString::fromStdString(f["encrypted"].asString()), !!ifstream(f["name"].asString()));
 			filemap[f["name"].asString()] = {false, "", "", 0, 0};
 		}
 	} else {
@@ -170,10 +180,11 @@ void CmdManager::handleVersion(Json::Value root) {
 void CmdManager::handleLogin(Json::Value root) {
 	if (root["accept"] == true) {
 		emit qmlHandler->loginSignupPopupClose();
-		CliManager::writeToCli("extendedlist");
-		qmlHandler->loadSettingsToGUI();
+		CliManager::loggedin = true;
 	} else {
+		QString cmd = "connect " + cachedIP + " " + cachedPort;
 		emit qmlHandler->loginSetStatus(root["error"].asString().c_str());
+		CliManager::writeToCli(cmd);
 		emit qmlHandler->loginEnableLoginButton();
 	}
 }
@@ -181,8 +192,11 @@ void CmdManager::handleLogin(Json::Value root) {
 void CmdManager::handleSignup(Json::Value root) {
 	if (root["accept"] == true) {
 		emit qmlHandler->loginSignupPopupClose();
+		CliManager::loggedin = true;
 	} else {
+		QString cmd = "connect " + cachedIP + " " + cachedPort;
 		emit qmlHandler->signupSetStatus(root["error"].asString().c_str());
+		CliManager::writeToCli(cmd);
 		emit qmlHandler->signupEnableRegisterButton();
 	}
 }
@@ -195,7 +209,10 @@ void CmdManager::handlePut(Json::Value root) {
 }
 
 void CmdManager::handlePutData(Json::Value root) {
-	// TODO: Show speed and handle Error
+	if (root["cancel"] == true) {
+		QString errorMessage = QString::fromStdString(string("Error when uploading file " + root["file"].asString() + ":\n" + root["error"].asString()));
+		emit qmlHandler->log(errorMessage);
+	}
 }
 
 void CmdManager::handleGet(Json::Value root) {
@@ -204,17 +221,20 @@ void CmdManager::handleGet(Json::Value root) {
 		emit qmlHandler->log(errorMessage);
 	} else {
 		string fileName = root["file"].asString();
-		// TODO: Only do this in getdata when remaining is 0 (when the file is fully downloaded) - maybe set text to "downloading.." in between
-		emit qmlHandler->receivingDisableDownloadButton(QString::fromStdString(fileName));
+		emit qmlHandler->serverFilesDisableDownloadButton(QString::fromStdString(fileName));
 	}
 }
 
 void CmdManager::handleGetData(Json::Value root) {
-	// TODO: Show speed and handle Error
+	if (root["cancel"] == true) {
+		QString errorMessage = QString::fromStdString(string("Error when downloading file " + root["file"].asString() + ":\n" + root["error"].asString()));
+		emit qmlHandler->log(errorMessage);
+	}
 }
 
 void CmdManager::handleDeleteMe(Json::Value root) {
 	if (root["accept"] == true) {
+		CliManager::loggedin = false;
 		qmlHandler->setRestart(true);
 		emit qmlHandler->closeWindow();
 	} else {
@@ -224,7 +244,7 @@ void CmdManager::handleDeleteMe(Json::Value root) {
 }
 
 void CmdManager::handleDeleteFile(Json::Value root) {
-	emit qmlHandler->receivingCloseConfirmDeletePopup();
+	emit qmlHandler->serverFilesCloseConfirmDeletePopup();
 	if (root["accept"] == false) {
 		QString errorMessage = QString::fromStdString(string("Error when deleting file " + root["file"].asString() + ":\n" + root["error"].asString()));
 		emit qmlHandler->log(errorMessage);
@@ -297,3 +317,31 @@ void CmdManager::handleExtendedStatus(Json::Value root) {
 		emitFileList();
 	}
 }
+
+void CmdManager::handleKeyfile(Json::Value root) {
+	QString errorMessage;
+	if (root["accept"] == false) {
+		errorMessage = QString::fromStdString(string("Error when setting keyfile file " + root["file"].asString() + ":\n" + root["error"].asString()));
+		emit qmlHandler->log(errorMessage);
+	}
+	emit qmlHandler->keyfileStatus(root["accept"].asBool(), errorMessage);
+}
+
+void CmdManager::handleClosekey(Json::Value root) {
+	if (root["accept"] == false) {
+		QString errorMessage = QString::fromStdString(string("Error when closing keyfile:\n" + root["error"].asString()));
+		emit qmlHandler->log(errorMessage);
+	} else {
+		emit qmlHandler->keyfileClosedOK();
+	}
+}
+
+void CmdManager::handleDisconnect(Json::Value root) {
+	if (root["accept"] == true) {
+		CliManager::loggedin = false;
+	}
+}
+
+void CmdManager::handleConnectionError(Json::Value root) {
+	emit qmlHandler->footerSetError("Connection to the server lost. Use << File->Change Server... >> to connect again.");
+}

+ 21 - 15
gui/src/config.cpp

@@ -1,29 +1,29 @@
 #include "config.h"
-#include <QDebug>
 
 namespace Config {
 std::map<std::string, std::string> configuration;
-bool configValid;
-std::string configPath = "config.txt";
-std::string configKeys[] = {"Autofill-IP", "Default-IP", "Autofill-Username", "Default-Username", "CLI-Path", "Covert-Channel-Method"};
+bool configValid = false;
+bool configInitialized = false;
+std::string configPath = "configGUI.txt";
+std::vector<std::string> configKeys = {"Autofill-IP", "Default-IP", "Autofill-Username", "Default-Username", "CLI-Path", "Keyfile-Path", "Use-SSL", "SSL-Path"};
 } // namespace Config
 
 void Config::setupDefaultConfig() {
 	configuration.clear();
+	for (std::string s : configKeys)
+		configuration.insert(std::pair<std::string, std::string>(s, ""));
 	setValue(configKeys[0], "0");
 	setValue(configKeys[1], "0.0.0.0");
 	setValue(configKeys[2], "0");
 	setValue(configKeys[3], "user");
-	setValue(configKeys[5], "0");
+	setValue(configKeys[6], "0");
+	configInitialized = true;
+	configValid = true;
 }
 
 bool Config::checkConfig() {
-	if (!configValid || configuration.size() != (sizeof(configKeys) / sizeof(*configKeys)))
+	if (!configValid || configuration.size() != configKeys.size())
 		return false;
-	for (int i = 0; i < (sizeof(configKeys) / sizeof(*configKeys)); i++) {
-		if (getValue(configKeys[i]) == "")
-			return false;
-	}
 
 	std::string autofill_ip = getValue("Autofill-IP");
 	if (autofill_ip != "0" && autofill_ip != "1")
@@ -33,18 +33,20 @@ bool Config::checkConfig() {
 	if (autofill_user != "0" && autofill_user != "1")
 		return false;
 
-	std::istringstream iss(getValue("Covert-Channel-Method"));
-	int covert_method;
-	if (!(iss >> covert_method))
+	std::string use_ssl = getValue("Use-SSL");
+	if (use_ssl != "0" && use_ssl != "1")
 		return false;
 
 	return true;
 }
 
 bool Config::loadFile() {
-	std::ifstream ifile(configPath);
 	std::string line;
 
+	if (!configInitialized)
+		setupDefaultConfig();
+	std::ifstream ifile(configPath);
+
 	if (ifile.is_open()) {
 		while (getline(ifile, line)) {
 			std::stringstream ss(line);
@@ -57,7 +59,7 @@ bool Config::loadFile() {
 				// One line doesn't have the format *=*
 				configValid = false;
 			} else {
-				configuration.insert(std::pair<std::string, std::string>(v.at(0), v.at(1)));
+				configuration[v.at(0)] = v.at(1);
 			}
 		}
 		configValid = true;
@@ -68,6 +70,10 @@ bool Config::loadFile() {
 
 void Config::saveFile() {
 	std::ofstream file;
+
+	if (!configInitialized)
+		setupDefaultConfig();
+
 	file.open(configPath);
 	for (auto const &x : configuration) {
 		file << x.first << "=" << x.second << std::endl;

BIN
gui/src/images/tray-icon.png


+ 22 - 3
gui/src/jsonhandler.cpp

@@ -1,8 +1,16 @@
-#include "../include/jsonhandler.h"
+#include <QDebug>
+
 #include "../include/cmdmanager.h"
+#include "../include/jsonhandler.h"
 
 using namespace std;
 
+namespace JsonHandler {
+QMLHandler *qmlHandler;
+}
+
+void JsonHandler::setQmlHandler(QMLHandler *q) { qmlHandler = q; }
+
 // This method gets a string and tries to read it as Json
 // If it fails to do so, return and do nothing, else handle the content
 void JsonHandler::parseJSON(string buffer) {
@@ -11,8 +19,7 @@ void JsonHandler::parseJSON(string buffer) {
 	Json::CharReader *reader = builder.newCharReader();
 	string jsonError;
 
-	// Try to parse the string as Json and store the result of the pasring in a
-	// boolean
+	// Try to parse the string as Json and store the result of the parsing in a boolean
 	bool parsingSuccessful = reader->parse(buffer.c_str(), buffer.c_str() + buffer.size(), &root, &jsonError);
 
 	delete (reader);
@@ -23,6 +30,18 @@ void JsonHandler::parseJSON(string buffer) {
 	}
 
 	string cmd = root["command"].asString();
+	string err = root["error"].asString();
+
+	// Filter the commands to not be printed
+	if (cmd.compare("notifications") && cmd.compare("extendedlist") && cmd.compare("extendedstatus") && cmd.compare("status") && cmd.compare("putdata") &&
+	    cmd.compare("getdata")) {
+		emit qmlHandler->log(QString::fromStdString(buffer));
+		qInfo() << QString::fromStdString(buffer);
+	}
+
+	if (err.compare("") && cmd.compare("connectionerror")) {
+		emit qmlHandler->footerSetError(QString::fromStdString(err));
+	}
 
 	CmdManager::executeCmd(cmd, root);
 }

+ 8 - 8
gui/src/main.cpp

@@ -23,7 +23,6 @@ using namespace std;
 int main(int argc, char *argv[]) {
 	QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
 
-	// This has to be here to fix warnings
 	QCoreApplication::setOrganizationName("CCats");
 
 	QApplication app(argc, argv);
@@ -31,12 +30,14 @@ int main(int argc, char *argv[]) {
 	QQmlApplicationEngine engine;
 
 	QMLHandler qmlHandler;
+
+	qmlHandler.setConfigExists(Config::loadFile());
 	CmdManager::setQmlHandler(&qmlHandler);
 	CmdManager::init();
 	CliManager::setQmlHandler(&qmlHandler);
+	JsonHandler::setQmlHandler(&qmlHandler);
 
-	// Set the context for the window, so that the qml files can be connected to
-	// the qmlHandler
+	// Set the context for the window, so that the qml files can be connected to the qmlHandler
 	engine.rootContext()->setContextProperty("_qmlHandler", &qmlHandler);
 
 	// Load the main window
@@ -44,15 +45,14 @@ int main(int argc, char *argv[]) {
 
 	QObject *object = component.create();
 
-	// Run the main window and save the return of the application in an integer
-	// ret This is blocking, which means it will block the main program until the
-	// window is closed
+	// Run the main window and save the return of the application in an integer ret
+	// This is blocking, which means it will block the main program until the window is closed
 	int ret = app.exec();
 
-	// If we land here, the window has been closed. Properly disconnect from the
-	// server now
+	// If we land here, the window has been closed. Properly disconnect from the server now
 	CliManager::onExit();
 
+	// Restart this application if the _RESTART variable is set (for example when the user deletes their account)
 	if (_RESTART) {
 		pid_t pid = 0;
 		pid = fork();

+ 5 - 13
gui/src/qml.qrc

@@ -8,27 +8,19 @@
         <file>Forms/Main/InvalidCliPathPopup.ui.qml</file>
         <file>Forms/Main/InvalidConfigPopup.ui.qml</file>
 
-        <file>Forms/Connect/LoginForm.ui.qml</file>
+        <file>Forms/ServerFiles/ServerFilesForm.ui.qml</file>
+        <file>Forms/ServerFiles/ServerFilesFileTemplate.ui.qml</file>
+        <file>Forms/ServerFiles/ServerFilesFileTemplateDeletePopup.ui.qml</file>
+
         <file>Forms/Connect/IpPopup.ui.qml</file>
         <file>Forms/Connect/LoginSignupPopup.ui.qml</file>
-        <file>Forms/Connect/SignupForm.ui.qml</file>
-
-        <file>Forms/Sending/SendingForm.ui.qml</file>
-
-        <file>Forms/Receiving/ReceivingForm.ui.qml</file>
-        <file>Forms/Receiving/ReceivingFileTemplate.ui.qml</file>
-        <file>Forms/Receiving/ReceivingFileTemplateDeletePopup.ui.qml</file>
 
-        <file>Forms/Messages/MessagesForm.ui.qml</file>
+        <file>Forms/Log/LogForm.ui.qml</file>
 
         <file>Forms/Settings/DeleteMePopup.ui.qml</file>
         <file>Forms/Settings/SettingsForm.ui.qml</file>
 
         <file>Forms/Notifications/NotificationsForm.ui.qml</file>
         <file>Forms/Notifications/NotificationTemplate.ui.qml</file>
-
-        <file>Forms/Help/HelpForm.ui.qml</file>
-
-        <file>images/tray-icon.png</file>
     </qresource>
 </RCC>

+ 68 - 47
gui/src/qmlhandler.cpp

@@ -13,6 +13,7 @@
 #include <unistd.h>
 
 #include "../include/climanager.h"
+#include "../include/cmdmanager.h"
 #include "../include/config.h"
 #include "../include/jsonhandler.h"
 #include "../include/qmlhandler.h"
@@ -23,41 +24,31 @@ using namespace std;
 
 QUrl sendFileUrl = QUrl("");
 bool _RESTART = false;
+bool configExists = false;
 
 QMLHandler::QMLHandler(QObject *parent) : QObject(parent) {}
 
 void QMLHandler::loadSettingsToGUI() {
 	stringstream ss;
 
-	int covertMethod;
-	ss << Config::getValue("Covert-Channel-Method");
-	ss >> covertMethod;
-
-	bool saveIP;
-	ss << Config::getValue("Autofill-IP");
-	ss >> saveIP;
-
-	bool saveUsername;
-	ss << Config::getValue("Autofill-Username");
-	ss >> saveUsername;
+	// The space is needed to get the stringstream to perform proper formatting of multiple values
+	bool saveIP, saveUsername;
+	ss << Config::getValue("Autofill-IP") << " " << Config::getValue("Autofill-Username");
+	ss >> saveIP >> saveUsername;
 
 	QString cliPath = QString::fromStdString(Config::getValue("CLI-Path"));
-	emit loadSettings(covertMethod, saveIP, saveUsername, cliPath);
+	QString keyPath = QString::fromStdString(Config::getValue("Keyfile-Path"));
+	emit loadSettings(saveIP, saveUsername, cliPath, keyPath);
 }
 
 // ### QML Handlers ###
 
-void QMLHandler::onStart() {
-	bool configExists = Config::loadFile();
-
-	if (configExists == true) {
+// Main
+void QMLHandler::onStart(bool startWithCli) {
+	if (configExists) {
 		// Config exists
-		if (Config::checkConfig() == true) {
+		if (Config::checkConfig()) {
 			// Config is valid
-			if (!ifstream(Config::getValue("CLI-Path"))) {
-				// Invalid CLI Path
-				emit invalidCliPathPopupOpen();
-			}
 			if (Config::getValue("Autofill-IP") == "1") {
 				emit ipPopupSetIP(QString::fromStdString(Config::getValue("Default-IP")));
 				emit ipPopupCheckSaveCheckbox();
@@ -66,6 +57,25 @@ void QMLHandler::onStart() {
 				emit loginSetUsername(QString::fromStdString(Config::getValue("Default-Username")));
 				emit loginSignupCheckSaveCheckbox();
 			}
+			if (!ifstream(Config::getValue("CLI-Path"))) {
+				// Invalid CLI Path
+				emit invalidCliPathPopupOpen();
+			} else if (startWithCli) {
+				// CLI exists, check for SSL
+				if (Config::getValue("Use-SSL") == "1") {
+					if (!ifstream(Config::getValue("SSL-Path"))) {
+						// SSL file does not exist
+						emit ipPopupSetStatus("Invalid SSL-Path. SSL has been disabled.");
+						CliManager::init(false);
+					} else {
+						// All good, start CLI with SSL
+						CliManager::init(true);
+					}
+				} else {
+					// All good, SSL is disabled, start CLI without SSL
+					CliManager::init(false);
+				}
+			}
 		} else {
 			// Config is invalid
 			emit invalidConfigPopupOpen();
@@ -75,20 +85,22 @@ void QMLHandler::onStart() {
 		Config::setupDefaultConfig();
 		emit noConfigFoundPopupOpen();
 	}
-
-	CliManager::init();
 }
 
+void QMLHandler::onSwitchServer() { CliManager::writeToCli("disconnect"); }
+
 // No Config Found Popup
 void QMLHandler::onNoConfigFoundPopupContinueButton(QString cli_path) {
 	Config::setValue("CLI-Path", cli_path.toUtf8().constData());
 	Config::saveFile();
+	onStart(true);
 }
 
 // Invalid Cli Path Popup
 void QMLHandler::onInvalidCliPathPopupContinueButton(QString cli_path) {
 	Config::setValue("CLI-Path", cli_path.toUtf8().constData());
 	Config::saveFile();
+	onStart(true);
 }
 
 void QMLHandler::onInvalidCliPathPopupQuitButton() { emit closeWindow(); }
@@ -101,51 +113,50 @@ void QMLHandler::onInvalidConfigPopupCreateDefaultButton() {
 	emit noConfigFoundPopupOpen();
 }
 
-// Sending
-void QMLHandler::onSendingSelectFileButton(QUrl url) {
+// Server Files
+void QMLHandler::onServerFilesSelectFileButton(QUrl url) {
 	sendFileUrl = url.toLocalFile();
 	emit log("File Selected: " + sendFileUrl.toString());
-	emit sendingSetFileUrlText("Selected File: " + sendFileUrl.toString());
-	emit sendingEnableSendButton();
+	emit serverFilesSetFileUrlText(sendFileUrl.toString());
+	emit serverFilesEnableSendButton();
 }
 
-void QMLHandler::onSendingSendFileButton() {
+void QMLHandler::onServerFilesSendFileButton() {
 	QString command = "put \"" + sendFileUrl.toString() + "\"";
 	CliManager::writeToCli(command);
-	CliManager::writeToCli("extendedlist");
 }
 
-void QMLHandler::onSendingClearSelectionButton() {
+void QMLHandler::onServerFilesClearSelectionButton() {
 	sendFileUrl = QUrl("");
 	emit log("Cleared Selection");
-	emit sendingSetFileUrlText("Selected File: None");
-	emit sendingDisableSendButton();
+	emit serverFilesSetFileUrlText("None");
+	emit serverFilesDisableSendButton();
 }
 
-// Receiving
-void QMLHandler::onReceivingListFilesButton() { CliManager::writeToCli("extendedlist"); }
-
-void QMLHandler::onReceivingDownloadFileButton(QString fileName) {
+void QMLHandler::onServerFilesDownloadFileButton(QString fileName) {
 	QString command = "get \"" + fileName + "\"";
 	CliManager::writeToCli(command);
 }
 
-void QMLHandler::onReceivingConfirmDeleteFileButton(QString fileName) {
+void QMLHandler::onServerFilesConfirmDeleteFileButton(QString fileName) {
 	QString command = "deletefile \"" + fileName + "\"";
 	CliManager::writeToCli(command);
-	CliManager::writeToCli("extendedlist");
 }
 
-// Messages
-void QMLHandler::onMessagesSendButton(QString msg) { emit message(msg); }
-
 // Settings
+void QMLHandler::onKeyfileSelected(QString path) {
+	QString command = "keyfile \"" + path + "\"";
+	CliManager::writeToCli(command);
+}
+void QMLHandler::onKeyfileClosed() { CliManager::writeToCli("closekey"); }
+
 void QMLHandler::onSettingsDeleteMeButton(QString password) {
 	QString command = "deleteme " + password;
 	CliManager::writeToCli(command);
 }
 
 void QMLHandler::onSettingsRevertChangesButton() {
+	Config::loadFile();
 	loadSettingsToGUI();
 	emit log("Settings changes reverted.");
 }
@@ -158,18 +169,26 @@ void QMLHandler::onSettingsResetButton() {
 	emit log("Settings resetted to default.");
 }
 
-void QMLHandler::onSettingsSaveButton(int covertMethod, bool saveIP, bool saveUsername, QString cliPath) {
-	Config::setValue("Covert-Channel-Method", to_string(covertMethod));
+void QMLHandler::onSettingsSaveButton(bool saveIP, bool saveUsername, QString cliPath, QString keyPath) {
 	Config::setValue("Autofill-IP", to_string(saveIP));
 	Config::setValue("Autofill-Username", to_string(saveUsername));
 	Config::setValue("CLI-Path", cliPath.toUtf8().constData());
+	Config::setValue("Keyfile-Path", keyPath.toUtf8().constData());
 	Config::saveFile();
 	emit log("Settings saved.");
 }
 
 // Ip Popup
 void QMLHandler::onIpPopupConnectButton(QString ip, bool saveAsDefault) {
-	QString command = "connect " + ip;
+	QStringList ipport = ip.split(":");
+	QString command = "connect " + ipport[0];
+
+	CmdManager::setCachedIP(ipport[0]);
+	if (ipport.size() > 1) {
+		command += " " + ipport[1];
+		CmdManager::setCachedPort(ipport[1]);
+	}
+
 	CliManager::writeToCli(command);
 
 	emit ipPopupDisableConnectButton();
@@ -177,6 +196,7 @@ void QMLHandler::onIpPopupConnectButton(QString ip, bool saveAsDefault) {
 		Config::setValue("Default-IP", ip.toUtf8().constData());
 		Config::setValue("Autofill-IP", "1");
 		Config::saveFile();
+		loadSettingsToGUI();
 	}
 }
 
@@ -189,6 +209,7 @@ void QMLHandler::onLoginLoginButton(QString username, QString password, bool sav
 		Config::setValue("Default-Username", username.toUtf8().constData());
 		Config::setValue("Autofill-Username", "1");
 		Config::saveFile();
+		loadSettingsToGUI();
 	}
 }
 
@@ -205,12 +226,10 @@ void QMLHandler::onSignupRegisterButton(QString username, QString passwordOne, Q
 		Config::setValue("Default-Username", username.toUtf8().constData());
 		Config::setValue("Autofill-Username", "1");
 		Config::saveFile();
+		loadSettingsToGUI();
 	}
 }
 
-// Footer
-void QMLHandler::onFooterGetStatusButton() { CliManager::writeToCli("extendedstatus"); }
-
 // Notifications
 void QMLHandler::onDismissNotificationButton(int index) { emit dismissNotification(index); }
 
@@ -226,3 +245,5 @@ void QMLHandler::onReceivingDequeueFileButton(QString fileName) {
 }
 
 void QMLHandler::setRestart(bool restart) { _RESTART = restart; }
+
+void QMLHandler::setConfigExists(bool exists) { configExists = exists; }

+ 5 - 2
gui/src/qtquickcontrols2.conf

@@ -3,8 +3,11 @@
 ; http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html
 
 [Controls]
-Style=Material
+Style=Fusion
 
 [Material]
-Theme=Dark
 Accent=Cyan
+
+[Fusion]
+Palette\Window=#dedede
+Palette\WindowText=#000000

+ 12 - 0
libs/libbcrypt/CMakeLists.txt

@@ -0,0 +1,12 @@
+cmake_minimum_required(VERSION 2.8)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+add_library(bcrypt
+            STATIC
+            bcrypt.c
+            crypt_blowfish/crypt_blowfish.c
+            crypt_blowfish/crypt_gensalt.c
+            crypt_blowfish/wrapper.c
+)