Browse Source

Merge branch 'develop' of git.rwth-aachen.de:tobias.wach/ccats into cli-more-status-info

Denys 4 years ago
parent
commit
28e124616d

+ 2 - 2
cli/README.md

@@ -17,8 +17,8 @@ Batch mode. A batch file for the CLI must contain commands (as used in regular u
 
 ### Additional arguments
 
-`--usessl`: <br/>
-Used to enable SSL communication with the server. Needs to be passed if server is configured to use SSL encryption.
+`--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.
 
 `--verbose`: <br/>
 Prints additional output for debugging purposes.

+ 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();

+ 30 - 7
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
-	 */
-	enum rettype { none = 0, print = (1 << 1), send = (1 << 2), error = (1 << 3), close = (1 << 4), connect = (1 << 5), exit = (1 << 6) };
+	 * 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),
+		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
 	 */

+ 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 - 1
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;

+ 58 - 36
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;
@@ -150,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";
 		}
@@ -167,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];
@@ -224,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];
@@ -358,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;
 	}
@@ -377,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;
 	}
@@ -397,11 +405,15 @@ 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";
+		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];
@@ -412,6 +424,7 @@ CmdMan::CmdRet CmdMan::cmdConnect(vector<string> args) {
 		}
 		// construct json
 		retval.type = connect;
+		root["command"] = "connect";
 		root["address"] = ip;
 		root["port"] = port;
 	}
@@ -426,6 +439,7 @@ CmdMan::CmdRet CmdMan::execute(string cmd, vector<string> args) {
 	for (string s : args)
 		DEBUGPRINT(s + " ");
 	DEBUGPRINT("]");
+	cmdmutex.lock();
 	map<string, CmdRet (CmdMan::*)(vector<string>)>::iterator execit = execmap.find(cmd);
 	vector<string>::const_iterator alwaysit;
 	vector<string>::const_iterator connectit;
@@ -443,6 +457,7 @@ CmdMan::CmdRet CmdMan::execute(string cmd, vector<string> args) {
 		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 (alwaysit != cmdAllowAlways.cend()) {
 		// Command should be usable in all cases
@@ -461,6 +476,7 @@ CmdMan::CmdRet CmdMan::execute(string cmd, vector<string> args) {
 			root["command"] = "error";
 			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) {
@@ -471,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) {
@@ -481,10 +498,14 @@ CmdMan::CmdRet CmdMan::execute(string cmd, vector<string> args) {
 			root["command"] = "error";
 			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) {
@@ -492,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;
@@ -511,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;
@@ -580,9 +600,8 @@ 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;
@@ -611,9 +630,8 @@ 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;
@@ -639,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;
 	}
@@ -658,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;
 	}
@@ -708,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)
@@ -723,9 +742,12 @@ 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) {

+ 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)) {

+ 53 - 6
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,6 +176,7 @@ bool IoMan::connect() {
 		if (errcode) {
 			root["error"] = errcode.message();
 			connected = false;
+			disconnect();
 		} else {
 			connected = true;
 			root["error"] = "";
@@ -186,6 +190,7 @@ bool IoMan::connect() {
 		if (errcode) {
 			root["error"] = errcode.message();
 			connected = false;
+			disconnect();
 		} else {
 			connected = true;
 			root["error"] = "";
@@ -205,6 +210,7 @@ void IoMan::disconnect() {
 	if (errcode)
 		printMessage(string(__PRETTY_FUNCTION__) + string("tcp shutdown says ") + errcode.message(), debug);
 	connected = false;
+	cmdman.stateSetDisconnected();
 }
 
 bool IoMan::init() {
@@ -232,6 +238,8 @@ void IoMan::networkMain() {
 	Json::Value root;
 	unsigned int jsonsize, readsize;
 
+	bool firstWasGood = false;
+
 	printMessage("IoMan::networkMain() begin", debug);
 	networkmutex.lock();
 	while (runnetwork) {
@@ -255,6 +263,11 @@ 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);
+
+		timestampmutex.lock();
+		sendtimestampValid = false;
+		timestampmutex.unlock();
+
 		if (readsize < 1) {
 			break;
 		}
@@ -263,7 +276,7 @@ void IoMan::networkMain() {
 			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 +285,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 +303,7 @@ void IoMan::networkMain() {
 			// store locally
 			toput.push_back(root);
 		}
+		firstWasGood = false;
 
 		if (toput.size()) {
 			// put into global vector
@@ -298,7 +317,7 @@ void IoMan::networkMain() {
 
 		// clean up local stuff
 		toput = vector<Json::Value>();
-		recvbuf.consume(readsize);
+		recvbuf.consume(recvbuf.size() + 1);
 		networkmutex.lock();
 	}
 }
@@ -366,10 +385,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 +587,21 @@ void IoMan::run() {
 	while (runmain) {
 		mainmutex.unlock();
 
+		timestampmutex.lock();
+		if (sendtimestampValid && std::difftime(time(NULL), sendtimestamp) > 15) {
+			// answer took more than 15 seconds
+			disconnect();
+			// 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) {

+ 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;
 }

+ 39 - 19
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,19 +56,28 @@ 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() {
@@ -93,7 +97,23 @@ void UserIoMan::printJson(Json::Value root) {
 		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; }

+ 65 - 26
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());
@@ -2772,8 +2814,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 +3025,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 +3119,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 +3206,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", "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 - 1
daemon/README.md

@@ -29,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/

+ 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

+ 1 - 1
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;

+ 1 - 3
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,7 +64,6 @@ 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);
@@ -74,6 +71,7 @@ bool UserManager::deleteUser(const std::string &name, const std::string &pw) {
 		return false;
 	}
 
+	// checking password before deleting
 	std::string hash = it->second;
 	if (bcrypt_checkpw(pw.c_str(), hash.c_str()) != 0) {
 		return false;

+ 108 - 0
daemon/test/JsonCommanderTest.cpp

@@ -1726,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() {}
 

+ 1 - 1
gui/include/climanager.h

@@ -9,7 +9,7 @@ namespace CliManager {
 void setQmlHandler(QMLHandler *q);
 extern bool loggedin;
 
-void init();
+void init(bool useSSL);
 void writeToCli(QString s);
 void readPipeLoop();
 void notificationsLoop();

+ 1 - 0
gui/include/cmdmanager.h

@@ -52,6 +52,7 @@ void handleExtendedStatus(Json::Value root);
 void handleKeyfile(Json::Value root);
 void handleClosekey(Json::Value root);
 void handleDisconnect(Json::Value root);
+void handleConnectionError(Json::Value root);
 } // namespace CmdManager
 
 #endif // CMDMANAGER_H

+ 1 - 3
gui/include/qmlhandler.h

@@ -9,9 +9,6 @@ extern bool _RESTART;
 class QMLHandler : public QObject {
 	Q_OBJECT
 
-private:
-	void fileExists(std::string name);
-
 public:
 	explicit QMLHandler(QObject *parent = 0);
 	void onExit();
@@ -19,6 +16,7 @@ public:
 	void loadSettingsToGUI();
 	QString getIP();
 	void setRestart(bool restart);
+	void setConfigExists(bool exists);
 
 	// C++ -> QML
 signals:

+ 2 - 2
gui/src/Forms/Main/FooterForm.ui.qml

@@ -11,7 +11,7 @@ Page {
     Connections {
         target: _qmlHandler
         onFooterSetStatus: {
-            footerStatusText.text = "Status: " + status
+            footerStatusText.text = status
         }
 
         onFooterSetError: {
@@ -52,7 +52,7 @@ Page {
 
     Timer {
         id: timer
-        interval: 3000
+        interval: 5000
         onTriggered: {
             footerErrorText.text = ""
         }

+ 1 - 1
gui/src/Forms/ServerFiles/ServerFilesFileTemplate.ui.qml

@@ -74,7 +74,7 @@ Item {
             horizontalAlignment: Text.AlignHCenter
             text: fileDecryptableText
             color: (fileDecryptableText
-                    == "decryptable") ? "#3fdf3f" : (fileDecryptableText
+                    == "decryptable") ? "#00ad11" : (fileDecryptableText
                                                      == "undecryptable") ? "#df3f3f" : "#000000"
         }
 

+ 1 - 1
gui/src/Forms/Settings/SettingsForm.ui.qml

@@ -25,7 +25,7 @@ Page {
         onKeyfileStatus: {
             if (success) {
                 settingsKeyStatus.text = "Keyfile: OK"
-                settingsKeyStatus.color = "#3fdf3f"
+                settingsKeyStatus.color = "#00ad11"
             } else {
                 settingsKeyStatus.text = "Keyfile: Error"
                 settingsKeyStatus.color = "#df3f3f"

+ 6 - 4
gui/src/climanager.cpp

@@ -57,8 +57,7 @@ void CliManager::onExit() {
 
 void CliManager::setQmlHandler(QMLHandler *q) { qmlHandler = q; }
 
-void CliManager::init() {
-	// TODO: Remove
+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()) {
@@ -78,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);
 	}
@@ -144,7 +147,6 @@ void CliManager::readPipeLoop() {
 			pollCount = 0;
 			readOffset = 0;
 			if (waitpid(childpid, NULL, WNOHANG)) {
-				// TODO: Remove
 				qInfo() << "CLI has exited/did not launch";
 				emit qmlHandler->log("CLI has exited/did not launch");
 

+ 5 - 0
gui/src/cmdmanager.cpp

@@ -39,6 +39,7 @@ void CmdManager::init() {
 	cmdmap["keyfile"] = &CmdManager::handleKeyfile;
 	cmdmap["closekey"] = &CmdManager::handleClosekey;
 	cmdmap["disconnect"] = &CmdManager::handleDisconnect;
+	cmdmap["connectionerror"] = &CmdManager::handleConnectionError;
 
 	filemap.clear();
 }
@@ -340,3 +341,7 @@ void CmdManager::handleDisconnect(Json::Value root) {
 		CliManager::loggedin = false;
 	}
 }
+
+void CmdManager::handleConnectionError(Json::Value root) {
+	emit qmlHandler->footerSetError("Connection to the server lost. Use << File->Change Server... >> to connect again.");
+}

+ 6 - 2
gui/src/config.cpp

@@ -1,12 +1,11 @@
 #include "config.h"
-#include <QDebug>
 
 namespace Config {
 std::map<std::string, std::string> configuration;
 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"};
+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() {
@@ -17,6 +16,7 @@ void Config::setupDefaultConfig() {
 	setValue(configKeys[1], "0.0.0.0");
 	setValue(configKeys[2], "0");
 	setValue(configKeys[3], "user");
+	setValue(configKeys[6], "0");
 	configInitialized = true;
 	configValid = true;
 }
@@ -33,6 +33,10 @@ bool Config::checkConfig() {
 	if (autofill_user != "0" && autofill_user != "1")
 		return false;
 
+	std::string use_ssl = getValue("Use-SSL");
+	if (use_ssl != "0" && use_ssl != "1")
+		return false;
+
 	return true;
 }
 

+ 1 - 2
gui/src/jsonhandler.cpp

@@ -36,11 +36,10 @@ void JsonHandler::parseJSON(string buffer) {
 	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));
-		// TODO: Remove
 		qInfo() << QString::fromStdString(buffer);
 	}
 
-	if (err.compare("")) {
+	if (err.compare("") && cmd.compare("connectionerror")) {
 		emit qmlHandler->footerSetError(QString::fromStdString(err));
 	}
 

+ 2 - 0
gui/src/main.cpp

@@ -31,6 +31,8 @@ int main(int argc, char *argv[]) {
 	QQmlApplicationEngine engine;
 
 	QMLHandler qmlHandler;
+
+	qmlHandler.setConfigExists(Config::loadFile());
 	CmdManager::setQmlHandler(&qmlHandler);
 	CmdManager::init();
 	CliManager::setQmlHandler(&qmlHandler);

+ 17 - 4
gui/src/qmlhandler.cpp

@@ -24,6 +24,7 @@ using namespace std;
 
 QUrl sendFileUrl = QUrl("");
 bool _RESTART = false;
+bool configExists = false;
 
 QMLHandler::QMLHandler(QObject *parent) : QObject(parent) {}
 
@@ -44,8 +45,6 @@ void QMLHandler::loadSettingsToGUI() {
 
 // Main
 void QMLHandler::onStart(bool startWithCli) {
-	bool configExists = Config::loadFile();
-
 	if (configExists) {
 		// Config exists
 		if (Config::checkConfig()) {
@@ -62,8 +61,20 @@ void QMLHandler::onStart(bool startWithCli) {
 				// Invalid CLI Path
 				emit invalidCliPathPopupOpen();
 			} else if (startWithCli) {
-				// Everything OK, init CLI
-				CliManager::init();
+				// 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
@@ -237,3 +248,5 @@ void QMLHandler::onReceivingDequeueFileButton(QString fileName) {
 }
 
 void QMLHandler::setRestart(bool restart) { _RESTART = restart; }
+
+void QMLHandler::setConfigExists(bool exists) { configExists = exists; }