Browse Source

Merge branch 'develop' into 'master'

Merge develop into master

See merge request tobias.wach/ccats!95
Sander, Paul 4 years ago
parent
commit
67f3da16e5
100 changed files with 8649 additions and 793 deletions
  1. 49 0
      .cmake_modules/FindReadline.cmake
  2. 48 8
      .gitlab-ci.yml
  3. 154 1
      Client-Server Protocol.md
  4. 6 5
      Dockerfile
  5. 85 0
      Dockerfile.testing
  6. 175 16
      GUI-CLI Protocol.md
  7. 1 1
      autoformat.sh
  8. 7 11
      cli/CMakeLists.txt
  9. 6 1
      cli/include/batchioman.h
  10. 35 10
      cli/include/cmdman.h
  11. 46 36
      cli/include/fileman.h
  12. 24 34
      cli/include/ioman.h
  13. 1 1
      cli/include/machineioman.h
  14. 7 2
      cli/include/userioman.h
  15. 9 0
      cli/src/CMakeLists.txt
  16. 181 31
      cli/src/batchioman.cpp
  17. 445 72
      cli/src/cmdman.cpp
  18. 88 12
      cli/src/fileman.cpp
  19. 72 74
      cli/src/ioman.cpp
  20. 1 1
      cli/src/machineioman.cpp
  21. 6 13
      cli/src/main.cpp
  22. 137 3
      cli/src/userioman.cpp
  23. 44 0
      cli/test/CMakeLists.txt
  24. 31 0
      cli/test/CmdManForTest.cpp
  25. 3198 0
      cli/test/cmdman_test.cpp
  26. 49 0
      cli/test/cryptoget.sh
  27. 4 0
      cli/test/cryptoget.txt
  28. 57 0
      cli/test/cryptoput.sh
  29. 4 0
      cli/test/cryptoput.txt
  30. 20 0
      cli/test/cryptotest_gcm.c
  31. 59 0
      cli/test/fileman_mock.h
  32. 22 0
      cli/test/logintest_neg.sh
  33. 2 0
      cli/test/logintest_neg.txt
  34. 18 0
      cli/test/logintest_pos.sh
  35. 2 0
      cli/test/logintest_pos.txt
  36. 1 0
      cli/test/samplefile.txt
  37. 2 2
      daemon/CMakeLists.txt
  38. 107 0
      daemon/CovertProtocol.md
  39. 6 6
      daemon/Daemon-Config-Reference.md
  40. 99 0
      daemon/include/CovertChannel/BidirectionalChannels.hpp
  41. 63 0
      daemon/include/CovertChannel/ChannelControls.h
  42. 104 0
      daemon/include/CovertChannel/Channels/TCPAppendChannel.hpp
  43. 102 0
      daemon/include/CovertChannel/Channels/TCPOptionCustomChannel.hpp
  44. 82 0
      daemon/include/CovertChannel/Channels/TCPOptionTimestampChannel.hpp
  45. 77 0
      daemon/include/CovertChannel/Channels/TCPUrgencyChannel.hpp
  46. 27 26
      daemon/include/CovertChannel/CovertChannel.h
  47. 47 11
      daemon/include/CovertChannel/ForwardChannel.h
  48. 427 0
      daemon/include/CovertChannel/Protocols/CovertProtocol.hpp
  49. 224 0
      daemon/include/CovertChannel/Protocols/CovertProtocolBidirectional.hpp
  50. 0 82
      daemon/include/CovertChannel/ProxyChannel.h
  51. 42 0
      daemon/include/FileManager.h
  52. 40 0
      daemon/include/JsonCommander.h
  53. 39 0
      daemon/include/Notifications.h
  54. 54 0
      daemon/include/Queue.h
  55. 1 1
      daemon/src/CMakeLists.txt
  56. 11 22
      daemon/src/CovertChannel/CovertChannel.cpp
  57. 15 1
      daemon/src/CovertChannel/ForwardChannel.cpp
  58. 0 82
      daemon/src/CovertChannel/ProxyChannel.cpp
  59. 67 1
      daemon/src/FileManager.cpp
  60. 225 0
      daemon/src/JsonCommander.cpp
  61. 48 0
      daemon/src/Notifications.cpp
  62. 75 0
      daemon/src/Queue.cpp
  63. 1 3
      daemon/src/Server.cpp
  64. 30 9
      daemon/src/UserManager.cpp
  65. 68 11
      daemon/src/main.cpp
  66. 1 1
      daemon/test/CMakeLists.txt
  67. 22 0
      daemon/test/ChannelControlsMock.h
  68. 6 0
      daemon/test/FileManagerMock.h
  69. 528 0
      daemon/test/JsonCommanderTest.cpp
  70. 19 0
      daemon/test/QueueMock.cpp
  71. 10 0
      daemon/test/QueueMock.h
  72. 4 5
      gui/CMakeLists.txt
  73. 20 0
      gui/include/climanager.h
  74. 18 0
      gui/include/cmdmanager.h
  75. 15 5
      gui/include/qmlhandler.h
  76. 6 2
      gui/src/Forms/Connect/IpPopup.ui.qml
  77. 3 0
      gui/src/Forms/Connect/LoginForm.ui.qml
  78. 2 0
      gui/src/Forms/Connect/SignupForm.ui.qml
  79. 2 1
      gui/src/Forms/Main/FooterForm.ui.qml
  80. 1 1
      gui/src/Forms/Main/InvalidCliPathPopup.ui.qml
  81. 1 1
      gui/src/Forms/Main/NoConfigFoundPopup.ui.qml
  82. 24 0
      gui/src/Forms/Main/main.qml
  83. 52 0
      gui/src/Forms/Notifications/NotificationTemplate.ui.qml
  84. 93 0
      gui/src/Forms/Notifications/NotificationsForm.ui.qml
  85. 49 4
      gui/src/Forms/Receiving/ReceivingFileTemplate.ui.qml
  86. 12 5
      gui/src/Forms/Receiving/ReceivingForm.ui.qml
  87. 8 6
      gui/src/Forms/Settings/SettingsForm.ui.qml
  88. 181 0
      gui/src/climanager.cpp
  89. 167 14
      gui/src/cmdmanager.cpp
  90. 3 8
      gui/src/config.cpp
  91. BIN
      gui/src/images/tray-icon.png
  92. 2 0
      gui/src/jsonhandler.cpp
  93. 7 4
      gui/src/main.cpp
  94. 5 0
      gui/src/qml.qrc
  95. 49 146
      gui/src/qmlhandler.cpp
  96. 1 1
      gui/src/qtquickcontrols2.conf
  97. 4 0
      libs/libbcrypt/.gitignore
  98. 121 0
      libs/libbcrypt/COPYING
  99. 22 0
      libs/libbcrypt/Makefile
  100. 14 0
      libs/libbcrypt/README

+ 49 - 0
.cmake_modules/FindReadline.cmake

@@ -0,0 +1,49 @@
+# Code copied from sethhall@github
+#
+# - Try to find readline include dirs and libraries 
+#
+# Usage of this module as follows:
+#
+#     find_package(Readline)
+#
+# Variables used by this module, they can change the default behaviour and need
+# to be set before calling find_package:
+#
+#  Readline_ROOT_DIR         Set this variable to the root installation of
+#                            readline if the module has problems finding the
+#                            proper installation path.
+#
+# Variables defined by this module:
+#
+#  READLINE_FOUND            System has readline, include and lib dirs found
+#  Readline_INCLUDE_DIR      The readline include directories. 
+#  Readline_LIBRARY          The readline library.
+
+find_path(Readline_ROOT_DIR
+    NAMES include/readline/readline.h
+)
+
+find_path(Readline_INCLUDE_DIR
+    NAMES readline/readline.h
+    HINTS ${Readline_ROOT_DIR}/include
+)
+
+find_library(Readline_LIBRARY
+    NAMES readline
+    HINTS ${Readline_ROOT_DIR}/lib
+)
+
+if(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY)
+  set(READLINE_FOUND TRUE)
+else(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY)
+  FIND_LIBRARY(Readline_LIBRARY NAMES readline)
+  include(FindPackageHandleStandardArgs)
+  FIND_PACKAGE_HANDLE_STANDARD_ARGS(Readline DEFAULT_MSG Readline_INCLUDE_DIR Readline_LIBRARY )
+  MARK_AS_ADVANCED(Readline_INCLUDE_DIR Readline_LIBRARY)
+endif(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY)
+
+mark_as_advanced(
+    Readline_ROOT_DIR
+    Readline_INCLUDE_DIR
+    Readline_LIBRARY
+)

+ 48 - 8
.gitlab-ci.yml

@@ -12,10 +12,34 @@ build-image:
   before_script:
   - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
   script:
-    - docker build --pull -t "$CI_REGISTRY_IMAGE" .
-    - docker push "$CI_REGISTRY_IMAGE"
-     
-jsonCommaderTest:
+    - docker build --pull -t "$CI_REGISTRY_IMAGE:testing" -f Dockerfile.testing .
+    - docker push "$CI_REGISTRY_IMAGE:testing"
+
+daemon-jsonCommaderTest:
+  stage: test
+  before_script:
+  - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
+  after_script:
+  - docker stop cont
+  - docker rm cont
+  script:
+  - docker pull "$CI_REGISTRY_IMAGE:testing"
+  - docker run -d --name cont "$CI_REGISTRY_IMAGE:testing"
+  - docker exec cont daemon/build/test/jsonCommanderTest
+
+cli-cmdman_test:
+  stage: test
+  before_script:
+  - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
+  after_script:
+  - docker stop cont
+  - docker rm cont
+  script:
+  - docker pull "$CI_REGISTRY_IMAGE:testing"
+  - docker run -d --name cont "$CI_REGISTRY_IMAGE:testing"
+  - docker exec cont cli/build/test/cmdman_test
+
+cli-logintest:
   stage: test
   before_script:
   - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
@@ -23,7 +47,23 @@ jsonCommaderTest:
   - docker stop cont
   - docker rm cont
   script:
-  - docker pull "$CI_REGISTRY_IMAGE"
-  - docker run -d --name cont "$CI_REGISTRY_IMAGE" 
-  - docker exec cont test/jsonCommanderTest
-    
+  - docker pull "$CI_REGISTRY_IMAGE:testing"
+  - docker run -d --name cont "$CI_REGISTRY_IMAGE:testing"
+  - sleep 10
+  - docker exec cont cli/build/test/logintest_pos.sh
+  - docker exec cont cli/build/test/logintest_neg.sh
+
+cli-cryptotest:
+  stage: test
+  before_script:
+  - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
+  after_script:
+  - docker stop cont
+  - docker rm cont
+  script:
+  - docker pull "$CI_REGISTRY_IMAGE:testing"
+  - docker run -d --name cont "$CI_REGISTRY_IMAGE:testing"
+  - sleep 10
+  - docker exec cont cli/build/test/cryptoput.sh /root/build/files
+  - docker exec cont cli/build/test/cryptoget.sh /root/build/files
+

+ 154 - 1
Client-Server Protocol.md

@@ -274,7 +274,7 @@ If `cancel` is `true` then the download of the file is canceled and an error mes
 
 ### 2.5 Head command
 
-The `head` command requests the first 4 bytes of `file` from the server for further inspection by the client.
+The `head` command requests the first 32 bytes of `file` from the server for further inspection by the client. If the file is smaller than 32 bytes, the first 4 bytes are sent instead. If the file is even too small for this, `data` stays empty, and an error is sent.
 
 Client:
 ```
@@ -340,6 +340,159 @@ Server:
 }
 ```
 
+### 2.8 Notifications
+
+The `notifications` command allows the logged in user to get a list of notifications since the last time.
+
+Client:
+```
+{
+	"command": "notifications"
+}
+```
+
+Server:
+```
+{
+	"command": "notifications",
+	"accept": bool,
+	"messages": string[], 
+	"error": string
+}
+```
+
+### 2.9 ExtendedStatus
+
+The `extendedstatus` command allows the client to request detailed information about ongoing transfers at the server.
+
+Client:
+```
+{
+	"command": "extendedstatus"
+}
+```
+
+Server:
+```
+{
+    "command": "extendedstatus",
+    "accept": bool,
+    "error": string,
+    "transfersclientserver": {
+        "upload": bool,
+        "file": string,
+        "progress": int
+    }[],
+    "transfersserverserver": {
+        "type": string,
+        "file": string,
+        "progress": int,
+        "speed": float,
+        "method": string
+    }[]
+}
+```
+The answer consists of an array of objects, each representing a transfer. <br />
+In case of client-server transfers, `upload` indicates wether the transfer is an up- or download. In case of server-server transfers, `type` indicates wether the file  is `upload`ing, `download`ing or `queued` for upload. <br />
+`file` contains the name of the file being transferred. <br />
+`progress` contains the percentage completed. <br />
+`speed` contains the speed in bytes per second. <br />
+`method` contains the covert channel method being used.
+
+### 2.10 Queue command
+
+To add a file that is already on the server to the queue for sending with the covert channel, the client uses the `queue` command.
+
+Client:
+```
+{
+	"command": "queue",
+	"file": string
+}
+```
+
+Server:
+```
+{
+	"command": "queue",
+	"file": string,
+	"accept": bool,
+	"error": string
+}
+```
+
+### 2.11 Dequeue command
+
+To remove a file from the queue for sending with the covert channel, the client uses the `dequeue` command.
+
+Client:
+```
+{
+	"command": "dequeue",
+	"file": string
+}
+```
+
+Server:
+```
+{
+	"command": "dequeue",
+	"file": string,
+	"accept": bool,
+	"error": string
+}
+```
+### 2.12 Extendedlist command
+Extendedlist is split in two commands. `extendedlist` to request a list download and `extendedlistdata` to download the list.
+
+#### 2.12.1 extendedlist - request file list download
+Client:
+```
+{
+	"command": "extendedlist"
+}
+```
+Server:
+```
+{
+	"command": "extendedlist",
+	"accept": bool,
+	"items": int,
+	"chunks": int,
+	"error": string
+}
+```
+`chunks` is the number of chunks that will be sent.
+
+#### 2.12.2 extendedlistdata - download file list
+Client:
+```
+{
+	"command": "extendedlistdata",
+	"chunk": int,
+	"cancel": bool
+}
+```
+Here `chunk` is the number of remaining chunks. (Thus, when requesting the first chunk the number of chunks - 1 is expected.)
+<br /><br />
+Server:
+```
+{
+	"command": "extendedlistdata",
+	"remaining": int,
+	"cancel": bool,
+	"files": {
+        "name": string,
+        "head": string,
+        "size": float
+    }[],
+	"error": string
+}
+```
+The client has to request every chunk until all names have been sent and `remaining` hits `0`. <br />
+The list contains the name of a file and the `head` as specified by the `data` field of the head command (section 2.5), and its `size` in kByte.
+
+
 ## 3. Close connection
 
 ### 3.1 Close command

+ 6 - 5
Dockerfile

@@ -10,7 +10,7 @@ RUN wget https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.
     && rm boost_1_72_0.tar.gz \
     && cd boost_1_72_0 \
     && ./bootstrap.sh --prefix=/usr/local \
-    && ./b2 install \
+    && ./b2 -j$(nproc) install \
     && cd .. \
     && rm -rf boost_1_72_0
 
@@ -18,14 +18,14 @@ RUN wget https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.
 RUN git clone https://github.com/mfontanini/libtins.git \
     && cd libtins && git checkout v4.2 \
     && mkdir build && cd build \
-    && cmake ../ -DLIBTINS_ENABLE_CXX11=1 && make && make install \
+    && cmake ../ -DLIBTINS_ENABLE_CXX11=1 && make -j$(nproc) && make install \
     && cd ../.. && rm -rf libtins
 
 # Build and install googletest
 RUN git clone https://github.com/google/googletest.git \
     && cd googletest \
     && mkdir build && cd build \
-    && cmake .. && make && make install \
+    && cmake .. && make -j$(nproc) && make install \
     && cd ../.. && rm -rf googletest
 
 RUN mkdir /root/build/files \
@@ -33,17 +33,18 @@ RUN mkdir /root/build/files \
     && echo "port=1234" >> /root/build/config.txt \
     && echo "interface=lo" >> /root/build/config.txt \
     && echo "userdatabase=userStorage.txt" >> /root/build/config.txt \
-    && echo "filedirectory=./files" >> /root/build/config.txt
+    && echo "filedirectory=./files/" >> /root/build/config.txt
 
 # Copy all required data into image
 COPY .cmake_modules/ /root/.cmake_modules
+COPY libs/ /root/libs
 COPY /daemon/include /root/build/include
 COPY /daemon/src /root/build/src
 COPY /daemon/test /root/build/test
 COPY /daemon/CMakeLists.txt /root/build
 
 # Compile ccats with tests
-RUN cmake -DENABLE_TESTS=true . && make \
+RUN cmake . && make -j$(nproc) \
     && rm -rf src include CMakeFiles \
     && rm Makefile CMakeCache.txt cmake_install.cmake CMakeLists.txt \
     CTestTestfile.cmake

+ 85 - 0
Dockerfile.testing

@@ -0,0 +1,85 @@
+FROM debian
+
+# Install all packages available from the repos
+# second line is for GUI requirements
+RUN apt-get update -y && apt-get install -y \
+    build-essential git cmake libpcap-dev libjsoncpp-dev wget libssl-dev libreadline-dev pkg-config \
+    qtdeclarative5-dev
+
+WORKDIR /root/build
+
+RUN wget https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.gz \
+    && tar xfz boost_1_72_0.tar.gz \
+    && rm boost_1_72_0.tar.gz \
+    && cd boost_1_72_0 \
+    && ./bootstrap.sh --prefix=/usr/local \
+    && ./b2 -j$(nproc) install \
+    && cd .. \
+    && rm -rf boost_1_72_0
+
+# Build and install libtins
+RUN git clone https://github.com/mfontanini/libtins.git \
+    && cd libtins && git checkout v4.2 \
+    && mkdir build && cd build \
+    && cmake ../ -DLIBTINS_ENABLE_CXX11=1 && make -j$(nproc) && make install \
+    && cd ../.. && rm -rf libtins
+
+# Build and install googletest
+RUN git clone https://github.com/google/googletest.git \
+    && cd googletest \
+    && mkdir build && cd build \
+    && cmake .. && make -j$(nproc) && make install \
+    && cd ../.. && rm -rf googletest
+
+# Copy all required data into image
+COPY .cmake_modules/ /root/build/.cmake_modules
+COPY libs/ /root/build/libs
+RUN mkdir -p /root/build/daemon/build /root/build/cli/build
+RUN mkdir -p /root/build/gui/build
+
+# Daemon
+COPY /daemon/include	/root/build/daemon/include
+COPY /daemon/src	/root/build/daemon/src
+COPY /daemon/test	/root/build/daemon/test
+COPY /daemon/CMakeLists.txt /root/build/daemon/
+
+# CLI
+COPY /cli/include	/root/build/cli/include
+COPY /cli/src		/root/build/cli/src
+COPY /cli/test		/root/build/cli/test
+COPY /cli/CMakeLists.txt /root/build/cli/
+
+# GUI
+COPY /gui/include	/root/build/gui/include
+COPY /gui/src		/root/build/gui/src
+COPY /gui/CMakeLists.txt /root/build/gui/
+
+# Create Daemon config
+# must be located in WORKDIR because running the container will set this as default dir, so all other scripts are also run from there
+RUN mkdir /root/build/files \
+    && touch /root/build/config.txt \
+    && echo "port=1234" >> /root/build/config.txt \
+    && echo "interface=lo" >> /root/build/config.txt \
+    && echo "userdatabase=userStorage.txt" >> /root/build/config.txt \
+    && echo "filedirectory=./files/" >> /root/build/config.txt \
+    && echo "activateCovertChannel=false" >> /root/build/config.txt \
+    && touch /root/build/userStorage.txt \
+    && rm /root/build/userStorage.txt
+
+# Compile daemon
+RUN cd daemon/build && cmake -DENABLE_TESTS=true .. && make -j$(nproc) \
+    && rm -rf ../src ../include ../test ../CMakeLists.txt \
+    && rm -rf CMakeFiles Makefile CMakeCache.txt cmake_install.cmake \
+    CTestTestfile.cmake
+
+# Compile ccats with tests
+RUN cd cli/build && cmake -DENABLE_TESTS=true .. && make -j$(nproc) \
+    && rm -rf ../src ../include ../test ../CMakeLists.txt \
+    && rm -rf CMakeFiles Makefile CMakeCache.txt cmake_install.cmake \
+    CTestTestfile.cmake
+
+# Compile ccats-gui
+RUN cd gui/build && cmake .. && make -j$(nproc) \
+    && cd ../.. && rm -rf gui
+
+ENTRYPOINT /root/build/daemon/build/bin/ccats

+ 175 - 16
GUI-CLI Protocol.md

@@ -1,12 +1,25 @@
 # GUI-CLI Protocol
 
-Protocol version: <b>"0.2"</b>
+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.
 
 ## 0. Connect to the server
 
-The GUI creates a new instance of the CLI with the entered IP Address as a paramater. The CLI then tries to establish a connection to the given server and tells the GUI if the connection was successful.
+The GUI creates a new instance of the CLI with parameter `--machine`. Then the GUI sends a connect command to connect to a server. 
+
+## 1. Start a connection
+
+### 1.1 Connecting and version check
+
+The CLI then tries to establish a connection to the given server and tells the GUI if the connection was successful.
+
+GUI:
+```
+write: "connect" ip_address port
+```
+Alternatively, it is possible to only pass an ip address, then by default port 1234 is used.
+
 
 CLI:
 ```
@@ -17,11 +30,8 @@ CLI:
 }
 ```
 
-## 1. Start a connection
-
 To connect to a server, the CLI has to do a version check. Once that is successful, the GUI will pass the username and password to establish a connection.
 
-### 1.1 Version check
 CLI:
 ```
 {
@@ -32,18 +42,17 @@ CLI:
 }
 ```
 
-If `accept` is `true` the connection is valid. Else the connection will be terminated after the server answered.
+If `accept` is `true` the connection is valid. Else the connection will be terminated after the server answered. In this case it it possible to connect again (possibly to another server).
 
 
 ## 1.2 Login / Signup
 
-The GUI will send the username and password right after another into the pipe. If the response contains a "true" for accept, the login was successful.
+The GUI will send a login or signup request into the pipe. If the response contains a "true" for accept, the login was successful.
 
 ### 1.2.1 Login
 GUI:
 ```
-	write: username
-	write: password
+write: "login" username password
 ```
 
 CLI:
@@ -60,14 +69,15 @@ If `accept` is `true` the connection is valid and the user is logged in. Else `e
 ### 1.2.2 Signup
 GUI:
 ```
-	write: #ToBeAdded
+write: "signup" username new_password
 ```
 
 Server:
 ```
 {
 	"command": "signup",
-	#ToBeAdded
+	"accept": bool,
+	"error": string
 }
 ```
 
@@ -105,7 +115,7 @@ CLI:
 ```
 {
 	"command": "list",
-  "accept": bool,
+	"accept": bool,
 	"names": string[],
 	"error": string
 }
@@ -191,7 +201,6 @@ If `cancel` is `true` then the download of the file is canceled and an error mes
 
 Requests the deletion of a file from the server.
 
-#### 2.5.1  - request file list download
 GUI:
 ```
 write: "deletefile" file_name
@@ -207,19 +216,169 @@ CLI:
 ```
 If `accept` is `true` the command is valid and the server has deleted the file. Else the request was rejected no changes have been made. `error` should contain an error string in that case.
 
+### 2.6 Deleteme command
+
+The `deleteme` command allows a logged in user to delete their account on the server. This action needs to be confirmed with the users password.
+
+GUI:
+```
+write: "deleteme" password
+```
+CLI:
+```
+{
+	"command": "deleteme",
+	"accept": bool,
+	"error": string
+}
+```
+If `accept` is true the user has been deleted and the connection will be closed by the server.
+
+### 2.7 Notifications
+
+The `notifications` command allows the logged in user to get a list of notifications since the last time.
+
+GUI:
+```
+write: "notifications"
+```
+
+CLI:
+```
+{
+	"command": "notifications",
+	"accept": bool,
+	"messages": string[],
+	"error": string
+}
+```
+
+### 2.8 ExtendedStatus
+
+The `extendedstatus` command allows the client to request detailed information about ongoing transfers at the server.
+
+GUI:
+```
+write: "extendedstatus"
+```
+
+CLI:
+```
+{
+    "command": "extendedstatus",
+    "accept": bool,
+    "error": string,
+    "transfersclientserver": {
+        "upload": bool,
+        "file": string,
+        "progress": float
+    }[],
+    "transfersserverserver": {
+        "type": string,
+        "file": string,
+        "progress": float,
+        "speed": float,
+        "method": string
+    }[]
+}
+```
+The answer consists of an array of objects, each representing a transfer. <br />
+In case of client-server transfers, `upload` indicates wether the transfer is an up- or download. In case of server-server transfers, `type` indicates wether the file  is `upload`ing, `download`ing or `queued` for upload. <br />
+`file` contains the name of the file being transferred. <br />
+`progress` contains the percentage completed. <br />
+`speed` contains the speed in bytes per second. <br />
+`method` contains the covert channel method being used.
+
+### 2.9 Queue command
 
-### TODO
+To add a file that is already on the server to the queue for sending with the covert channel, the use the `queue` command.
 
+Client:
+```
+write: "queue" file_name
+```
+
+Server:
+```
+{
+	"command": "queue",
+	"file": string,
+	"accept": bool,
+	"error": string
+}
+```
 
-## 3. Disconnect
+### 2.10 Dequeue command
+
+To remove a file from the queue for sending with the covert channel, the use the `dequeue` command.
+
+Client:
+```
+write: "dequeue" file_name
+```
+
+Server:
+```
+{
+	"command": "dequeue",
+	"file": string,
+	"accept": bool,
+	"error": string
+}
+```
+
+### 2.11 Extendedlist command
 
 GUI:
 ```
+write: "extendedlist"
+```
+CLI:
+```
+{
+	"command": "extendedlist",
+	"accept": bool,
+	"files": {
+        "name": string,
+        "encrypted": string,
+        "size": float
+    }[],
+	"error": string
+}
+```
+
+The list contains the `name` of a file and its `size` in kByte. <br />
+The `encrypted` field contains information wether the file is `unknown` in case of bad signature or file size, `unencrypted`, `decryptable` or `undecryptable`.
+
+## 3. Disconnecting and exiting
+
+### 3.1. Disconnect
+
+GUI:
+```
+write: "disconnect"
+```
+
+CLI:
+```
 {
-	write: "disconnect"
+	"command": "disconnect",
+	"accept": bool
 }
 ```
 
+If `accept` is true, the connection is closed and it is possible to connect to another server.
+
+
+### 3.2. Exit
+
+GUI:
+```
+write: "exit"
+```
+
+If the CLI is not connected, the program can exit directly. Otherwise, the CLI sends a disconnect reply first: 
+
 CLI:
 ```
 {

+ 1 - 1
autoformat.sh

@@ -22,7 +22,7 @@ if [ -z "$FMT" ]; then
 fi
 
 function format() {
-    for f in $(find $@ -type d -path "$@/build" -prune -o -type f -name '*.h' -or -name '*.m' -or -name '*.mm' -or -name '*.c' -or -name '*.cpp'); do
+    for f in $(find $@ -type d -path "$@/build" -prune -o -type f -name '*.h' -or -name '*.hpp' -or -name '*.c' -or -name '*.cpp'); do
         if [ ! -d "${f}" ]; then
             echo "format ${f}";
             ${FMT} -i ${f};

+ 7 - 11
cli/CMakeLists.txt

@@ -1,21 +1,17 @@
 cmake_minimum_required(VERSION 2.8)
 
-# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/../.cmake_modules/")
 
 project(ccats-cli)
 
-add_executable(ccats-cli src/main.cpp src/ioman.cpp src/machineioman.cpp src/userioman.cpp src/batchioman.cpp src/base64.cpp src/cmdman.cpp src/fileman.cpp)
-
-# use pkg-config to find readline as it doesnt provide cmake files
-find_package(PkgConfig REQUIRED)
-pkg_check_modules(READLINE REQUIRED readline)
-pkg_check_modules(JSONCPP REQUIRED jsoncpp)
-
 find_package(Threads)
 find_package(OpenSSL REQUIRED)
 find_package(Boost 1.70 REQUIRED COMPONENTS system program_options)
+find_package(Jsoncpp REQUIRED)
 
+include(src/CMakeLists.txt)
 
-include_directories(${Boost_INCLUDE_DIR} ${JSONCPP_INCLUDEDIR} include)
-
-target_link_libraries(ccats-cli PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} ${Boost_LIBRARIES} ${READLINE_LIBRARIES} ${JSONCPP_LIBRARIES})
+if(ENABLE_TESTS)
+  include(test/CMakeLists.txt)
+endif()

+ 6 - 1
cli/include/batchioman.h

@@ -56,10 +56,12 @@ private:
 	std::string printConnect(Json::Value root);
 	std::string printHelp(Json::Value root);
 	std::string printStatus(Json::Value root);
+	std::string printExtendedstatus(Json::Value root);
 	std::string printDisconnect(Json::Value root);
 	std::string printPut(Json::Value root);
 	std::string printGet(Json::Value root);
 	std::string printList(Json::Value root);
+	std::string printExtendedlist(Json::Value root);
 	std::string printVersion(Json::Value root);
 	std::string printLogin(Json::Value root);
 	std::string printSignup(Json::Value root);
@@ -68,15 +70,18 @@ private:
 	std::string printListdata(Json::Value root);
 	std::string printHead(Json::Value root);
 	std::string printDeletefile(Json::Value root);
+	std::string printQueue(Json::Value root);
+	std::string printDequeue(Json::Value root);
 	std::string printDeleteme(Json::Value root);
 	std::string printKeyfile(Json::Value root);
 	std::string printClosekey(Json::Value root);
+	std::string printNotifications(Json::Value root);
 
 public:
 	/**
 	 * Constructor and destructor
 	 */
-	BatchIoMan(char *ipcstring, bool usessl, bool beverbose, string batchpath);
+	BatchIoMan(bool usessl, bool beverbose, string batchpath);
 	~BatchIoMan();
 
 	bool init();

+ 35 - 10
cli/include/cmdman.h

@@ -29,7 +29,7 @@ public:
 	 * close	- terminate the connection
 	 * seton	- contextually change state, used for version check and login
 	 */
-	enum rettype { print = (1 << 0), send = (1 << 1), error = (1 << 2), close = (1 << 3), seton = (1 << 4) };
+	enum rettype { none = 0, print = (1 << 1), send = (1 << 2), error = (1 << 3), close = (1 << 4), connect = (1 << 5), exit = (1 << 6) };
 	/**
 	 * Response to user or command input
 	 *
@@ -61,6 +61,17 @@ public:
 	 */
 	Json::CharReader *reader;
 
+	void stateSetConnectionOk();
+
+protected:
+	/**
+	 * State used to internally format received json to allow easy handling using
+	 * handlemap and to disallow the use of certain commands when logged in/not
+	 * logged in.
+	 */
+	enum state { connectionpossible, versionpossible, doversion, loginpossible, dologin, dosignup, normal, disconnecttoexit, disconnecttoexitearly };
+	state currentState;
+
 private:
 	/**
 	 * internal json writer and error string member
@@ -86,29 +97,26 @@ private:
 	 */
 	map<string, string> helpmap;
 
-	/**
-	 * State used to internally format received json to allow easy handling using
-	 * handlemap and to disallow the use of certain commands when logged in/not
-	 * logged in.
-	 */
-	bool doversion, loginpossible, dologin, dosignup;
-
 	/**
 	 * Help strings and method prototypes for commands to be used by a user
 	 */
 	/* execute command descriptions and methods go here */
 	const string descHelp = "print available commands";
 	CmdRet cmdHelp(vector<string> args);
-	const string descStatus = "request status from server";
+	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";
+	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";
 	CmdRet cmdPut(vector<string> args);
 	const string descGet = "retrieve file from server";
 	CmdRet cmdGet(vector<string> args);
-	const string descList = "list files available on server";
+	const string descList = "list names of files available on server";
 	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";
 	CmdRet cmdHead(vector<string> args);
 	const string descDeletefile = "delete a file from the server";
@@ -117,6 +125,12 @@ private:
 	CmdRet cmdKeyfile(vector<string> args);
 	const string descClosekey = "stop using the previously selected keyfile";
 	CmdRet cmdClosekey(vector<string> args);
+	const string descNotifications = "request notifications from the server";
+	CmdRet cmdNotifications(vector<string> args);
+	const string descConnect = "connect to server";
+	CmdRet cmdConnect(vector<string> args);
+	const string descExit = "exit the application";
+	CmdRet cmdExit(vector<string> args);
 
 	const string descLogin = "login to the server";
 	CmdRet cmdLogin(vector<string> args);
@@ -125,6 +139,10 @@ private:
 	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);
+	const string descDequeue = "remove a file from the queue for sending with the covert channel";
+	CmdRet cmdDequeue(vector<string> args);
 
 	/**
 	 * Method prototypes for commands used internally
@@ -134,25 +152,32 @@ private:
 	CmdRet cmdPutdata(vector<string> args);
 	CmdRet cmdGetdata(vector<string> args);
 	CmdRet cmdListdata(vector<string> args);
+	CmdRet cmdExtendedlistdata(vector<string> args);
 
 	/**
 	 * Method prototypes for handling json responses
 	 */
 	/* handle commands go here */
 	CmdRet handleStatus(Json::Value);
+	CmdRet handleExtendedstatus(Json::Value);
 	CmdRet handleClose(Json::Value);
 	CmdRet handlePut(Json::Value);
 	CmdRet handleGet(Json::Value);
 	CmdRet handleList(Json::Value);
+	CmdRet handleExtendedlist(Json::Value);
 	CmdRet handlePutdata(Json::Value);
 	CmdRet handleGetdata(Json::Value);
 	CmdRet handleListdata(Json::Value);
+	CmdRet handleExtendedlistdata(Json::Value);
 	CmdRet handleVersion(Json::Value);
 	CmdRet handleLogin(Json::Value);
 	CmdRet handleSignup(Json::Value);
 	CmdRet handleHead(Json::Value);
 	CmdRet handleDeletefile(Json::Value);
 	CmdRet handleDeleteme(Json::Value);
+	CmdRet handleQueue(Json::Value);
+	CmdRet handleDequeue(Json::Value);
+	CmdRet handleNotifications(Json::Value);
 };
 
 #endif

+ 46 - 36
cli/include/fileman.h

@@ -2,6 +2,7 @@
 #define FILEMAN_H
 
 #include <fstream>
+#include <json/json.h>
 #include <openssl/conf.h>
 #include <openssl/err.h>
 #include <openssl/evp.h>
@@ -28,7 +29,7 @@ private:
 	 */
 	std::vector<std::vector<char>> putdata;
 	std::fstream getfile;
-	std::vector<std::string> listdata;
+	std::vector<Json::Value> listdata;
 	std::string getpath, getname, putpath, putname;
 	const unsigned int max_read_len = 4096;
 	int putsize;
@@ -46,6 +47,8 @@ private:
 	bool cryptoreadyd;
 	bool pendingerr;
 
+	bool extendedListing;
+
 	unsigned char iv[12];  // 96bits
 	unsigned char tag[16]; // 128bits
 	unsigned char key[32]; // 256bits
@@ -65,6 +68,9 @@ private:
 	void writeEnc(const std::vector<char> data);
 	const char signature[4] = {'C', 'C', 'A', 'T'};
 
+	enum decryptability { unknown, plaintext, decryptable, undecryptable };
+	decryptability isDecryptable(const std::vector<char> data);
+
 public:
 	/**
 	 * Constructor and Destructor
@@ -75,21 +81,25 @@ public:
 	/**
 	 * Query internal state
 	 *
-	 * Return true if the corresponding action is being performed, false otherwise
+	 * Return true if the corresponding action is being performed, false otherwise.
+	 * isListing returns true if either a list from the "list" or "extendedlist" is built.
+	 * isListingExtended corresponds to the "extendedlist" command, isListingSimple to "list".
 	 */
-	bool isGetting();
-	bool isPutting();
-	bool isListing();
-	bool isEncrypted();
+	virtual bool isGetting();
+	virtual bool isPutting();
+	virtual bool isListing();
+	virtual bool isListingSimple();
+	virtual bool isListingExtended();
+	virtual bool isEncrypted();
 
 	/**
 	 * Check for and prepare state and streams for reading/writing
 	 *
 	 * Return true if successful, false otherwise
 	 */
-	bool openPut(const std::string &path);
-	bool openGet(const std::string &path);
-	bool openList();
+	virtual bool openPut(const std::string &path);
+	virtual bool openGet(const std::string &path);
+	virtual bool openList(bool extended);
 
 	/**
 	 * Open file and read a hex string from it as key and initialize the IV
@@ -97,14 +107,14 @@ public:
 	 *
 	 * Return true if successful, false otherwise. After this put and get will en/decrypt data
 	 */
-	bool openKey(const std::string &path);
+	virtual bool openKey(const std::string &path);
 
 	/**
 	 * Close the respective filestream
 	 */
-	void closePut();
-	void closeGet();
-	void closeList();
+	virtual void closePut();
+	virtual void closeGet();
+	virtual void closeList();
 
 	/**
 	 * Reset internal key state and disable en/decryption of data
@@ -112,68 +122,68 @@ public:
 	 *
 	 * Return true if key was reset, false otherwise. After this put and get will use unencrypted data
 	 */
-	bool closeKey();
+	virtual bool closeKey();
 
 	/**
 	 * Query the names of the file currently being put or get
 	 */
-	std::string getPutName();
-	std::string getGetName();
+	virtual std::string getPutName();
+	virtual std::string getGetName();
 
 	/**
 	 * Cancel a put, get or list, depreparing internal state (closing streams if
 	 * required)
 	 */
-	void cancelPut();
-	void cancelGet();
-	void cancelList();
+	virtual void cancelPut();
+	virtual void cancelGet();
+	virtual void cancelList();
 
 	/**
 	 * Read max_rea_len bytes from the current file opened for put
 	 */
-	std::vector<char> readPut();
+	virtual std::vector<char> readPut();
 	/**
 	 * Write the provided vector to the current file opened for get
 	 */
-	void writeGet(std::vector<char> data);
+	virtual void writeGet(std::vector<char> data);
 
 	/**
 	 * Wrapper methods for reading and writing base64 encoded data instead of raw
 	 * bytes
 	 */
-	std::string readBase64();
-	void writeBase64(std::string data);
+	virtual std::string readBase64();
+	virtual void writeBase64(std::string data);
 
 	/**
 	 * read and write emulating methods for list
 	 */
-	void putListData(std::vector<std::string> names);
-	std::vector<std::string> getListData();
+	virtual void putListData(std::vector<Json::Value> names);
+	virtual std::vector<Json::Value> getListData();
 
 	/**
 	 * Query internal state, requesting the corresponding size
 	 */
-	int getPutChunks();
-	int getGetChunks();
-	int getListChunks();
-	int getPutRemainingChunks();
-	int getGetRemainingChunks();
-	int getListRemainingChunks();
-	int getPutSize();
+	virtual int getPutChunks();
+	virtual int getGetChunks();
+	virtual int getListChunks();
+	virtual int getPutRemainingChunks();
+	virtual int getGetRemainingChunks();
+	virtual int getListRemainingChunks();
+	virtual int getPutSize();
 
 	/**
 	 * Set internal state, adjusting the chunks as well as chunks remaining for
 	 * get and list
 	 */
-	void setGetChunks(int chunks);
-	void setListChunks(int chunks);
+	virtual void setGetChunks(int chunks);
+	virtual void setListChunks(int chunks);
 
 	/**
 	 * Returns the filename of the passed (relative) path of a file
 	 */
-	std::string pathToFilename(std::string path);
+	virtual std::string pathToFilename(std::string path);
 
-	std::string getOpensslError();
+	virtual std::string getOpensslError();
 };
 
 #endif

+ 24 - 34
cli/include/ioman.h

@@ -60,17 +60,31 @@ protected:
 	virtual std::string getCmdPrompt() = 0;
 
 	/**
-	 * Enum for internal login and version state
-	 * Variables for keeping said state
-	 * Matching mutex and condition variable
-	 */
-	enum Status { off, on, err };
-	Status loginstatus;
-	Status versionstatus;
-	std::mutex initmutex;
-	std::condition_variable initcv;
+	 * The IP and port to connect to
+	 * Flag telling wether one is connected
+	 */
+	std::string ipstring;
+	unsigned short port;
 	bool connected;
 
+	/**
+	 * Thread handles for processing local and network input as well as generating
+	 * responses to both Matching mutexes for the flags wether the threads should
+	 * keep running Function prototypes for the main thread functions
+	 */
+	std::thread tinput, tnetwork, tresponse;
+	std::mutex inputmutex, networkmutex, responsemutex;
+	bool runinput, runnetwork, runresponse;
+	void networkMain();
+	void inputMain();
+	void responseMain();
+
+	/**
+	 * Instances of CmdMan and FileMan to process user input and handle File I/O
+	 */
+	CmdMan cmdman;
+	FileMan fileman;
+
 	virtual void handleInCmdResponse(CmdMan::CmdRet cmdret);
 	virtual void handleOutCmdResponse(CmdMan::CmdRet cmdret, vector<string> &toput);
 
@@ -85,17 +99,6 @@ private:
 	boost::asio::io_service ios;
 	boost::asio::streambuf recvbuf;
 	boost::asio::ssl::context *sslctx;
-	/**
-	 * The IP and port to connect to
-	 * Flag telling wether one is connected
-	 */
-	std::string ipstring;
-	unsigned short port;
-	/**
-	 * Instances of CmdMan and FileMan to process user input and handle File I/O
-	 */
-	CmdMan cmdman;
-	FileMan fileman;
 
 	/**
 	 * Class-wide json functionality
@@ -104,18 +107,6 @@ private:
 	Json::StreamWriterBuilder wbuilder;
 	string jsonerror;
 
-	/**
-	 * Thread handles for processing local and network input as well as generating
-	 * responses to both Matching mutexes for the flags wether the threads should
-	 * keep running Function prototypes for the main thread functions
-	 */
-	std::thread tinput, tnetwork, tresponse;
-	std::mutex inputmutex, networkmutex, responsemutex;
-	bool runinput, runnetwork, runresponse;
-	void networkMain();
-	void inputMain();
-	void responseMain();
-
 	/**
 	 * Vector to hold preprocessed input from the network
 	 * Matching condition variable to wake up waiting threads
@@ -136,8 +127,7 @@ public:
 	/**
 	 * Constructor and destructor
 	 */
-	IoMan(char *ipcstring);
-	IoMan(char *ipcstring, bool enablessl);
+	IoMan(bool enablessl);
 	virtual ~IoMan();
 
 	/**

+ 1 - 1
cli/include/machineioman.h

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

+ 7 - 2
cli/include/userioman.h

@@ -39,10 +39,12 @@ private:
 	void printConnect(Json::Value root);
 	void printHelp(Json::Value root);
 	void printStatus(Json::Value root);
+	void printExtendedstatus(Json::Value root);
 	void printDisconnect(Json::Value root);
 	void printPut(Json::Value root);
 	void printGet(Json::Value root);
 	void printList(Json::Value root);
+	void printExtendedlist(Json::Value root);
 	void printVersion(Json::Value root);
 	void printLogin(Json::Value root);
 	void printSignup(Json::Value root);
@@ -51,20 +53,23 @@ private:
 	void printListdata(Json::Value root);
 	void printHead(Json::Value root);
 	void printDeletefile(Json::Value root);
+	void printQueue(Json::Value root);
+	void printDequeue(Json::Value root);
 	void printDeleteme(Json::Value root);
 	void printKeyfile(Json::Value root);
 	void printClosekey(Json::Value root);
+	void printNotifications(Json::Value root);
 
 	/**
 	 * Mutex for synchronized message output
 	 */
-	std::recursive_mutex msgmutex;
+	std::mutex msgmutex;
 
 public:
 	/**
 	 * Constructor and destructor
 	 */
-	UserIoMan(char *ipcstring, bool usessl, bool verbose);
+	UserIoMan(bool usessl, bool verbose);
 	~UserIoMan();
 
 protected:

+ 9 - 0
cli/src/CMakeLists.txt

@@ -0,0 +1,9 @@
+cmake_minimum_required(VERSION 2.8)
+
+find_package(Readline REQUIRED)
+
+include_directories(${Boost_INCLUDE_DIR} ${JSONCPP_INCLUDE_DIRS} ${Readline_INCLUDE_DIR} include)
+
+add_executable(ccats-cli src/main.cpp src/ioman.cpp src/machineioman.cpp src/userioman.cpp src/batchioman.cpp src/base64.cpp src/cmdman.cpp src/fileman.cpp)
+
+target_link_libraries(ccats-cli PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} ${Boost_LIBRARIES} ${Readline_LIBRARY} ${JSONCPP_LIBRARIES})

+ 181 - 31
cli/src/batchioman.cpp

@@ -4,7 +4,7 @@
 #include <string>
 #include <vector>
 
-BatchIoMan::BatchIoMan(char *ipcstring, bool usessl, bool beverbose, std::string batchpath) : IoMan(ipcstring, usessl) {
+BatchIoMan::BatchIoMan(bool usessl, bool beverbose, std::string batchpath) : IoMan(usessl) {
 	/* setup json stuff */
 	Json::CharReaderBuilder rbuilder;
 	wbuilder.settings_["indentation"] = "";
@@ -15,10 +15,12 @@ BatchIoMan::BatchIoMan(char *ipcstring, bool usessl, bool beverbose, std::string
 	printmap["connect"] = &BatchIoMan::printConnect;
 	printmap["help"] = &BatchIoMan::printHelp;
 	printmap["status"] = &BatchIoMan::printStatus;
+	printmap["extendedstatus"] = &BatchIoMan::printExtendedstatus;
 	printmap["disconnect"] = &BatchIoMan::printDisconnect;
 	printmap["put"] = &BatchIoMan::printPut;
 	printmap["get"] = &BatchIoMan::printGet;
 	printmap["list"] = &BatchIoMan::printList;
+	printmap["extendedlist"] = &BatchIoMan::printExtendedlist;
 	printmap["version"] = &BatchIoMan::printVersion;
 	printmap["login"] = &BatchIoMan::printLogin;
 	printmap["signup"] = &BatchIoMan::printSignup;
@@ -27,10 +29,12 @@ BatchIoMan::BatchIoMan(char *ipcstring, bool usessl, bool beverbose, std::string
 	printmap["head"] = &BatchIoMan::printHead;
 	printmap["deletefile"] = &BatchIoMan::printDeletefile;
 	printmap["deleteme"] = &BatchIoMan::printDeleteme;
+	printmap["queue"] = &BatchIoMan::printQueue;
+	printmap["dequeue"] = &BatchIoMan::printDequeue;
 	printmap["keyfile"] = &BatchIoMan::printKeyfile;
 	printmap["closekey"] = &BatchIoMan::printClosekey;
 
-	getnextline = false;
+	getnextline = true;
 	verbose = beverbose;
 	filepath = batchpath;
 }
@@ -150,12 +154,24 @@ void BatchIoMan::handleInCmdResponse(CmdMan::CmdRet cmdret) {
 		printMessage(string(__PRETTY_FUNCTION__) + string(" release linemutex"), debug);
 		linecv.notify_all();
 	}
-}
-
-/* modified handleOutCmdResponse to fetch next command and abort on error */
-void BatchIoMan::handleOutCmdResponse(CmdMan::CmdRet cmdret, std::vector<std::string> &toput) {
-	if (cmdret.type & CmdMan::rettype::close) {
-		/* TODO i dunno */
+	if (cmdret.type & CmdMan::rettype::connect) {
+		ipstring = cmdret.msg["address"].asString();
+		port = cmdret.msg["port"].asUInt();
+		if (connect()) {
+			runnetwork = true;
+			tnetwork = std::thread(&BatchIoMan::networkMain, this);
+
+			// put new commands into global vector
+			localmutex.lock();
+			printMessage(string(__PRETTY_FUNCTION__) + string(" get localmutex"), debug);
+			localinput.push_back("version");
+			cmdman.stateSetConnectionOk();
+			localmutex.unlock();
+			printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"), debug);
+			localcv.notify_all();
+		}
+	}
+	if (cmdret.type & CmdMan::rettype::exit) {
 		mainmutex.lock();
 		runmain = false;
 		mainmutex.unlock();
@@ -167,14 +183,30 @@ void BatchIoMan::handleOutCmdResponse(CmdMan::CmdRet cmdret, std::vector<std::st
 		linecv.notify_all();
 		return;
 	}
+	if (cmdret.nextcommand.size()) {
+		localmutex.lock();
+		printMessage(string(__PRETTY_FUNCTION__) + string(" get localmutex"), debug);
+		localinput.push_back(cmdret.nextcommand);
+		localmutex.unlock();
+		printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"), debug);
+		localcv.notify_all();
+	}
+}
+
+/* modified handleOutCmdResponse to fetch next command and abort on error */
+void BatchIoMan::handleOutCmdResponse(CmdMan::CmdRet cmdret, std::vector<std::string> &toput) {
+	if (cmdret.type & CmdMan::rettype::close) {
+		// connection closed, stop network thread and shutdown any operations remaining
+		networkmutex.lock();
+		runnetwork = false;
+		networkmutex.unlock();
+		disconnect();
+		tnetwork.join();
+		if (cmdret.nextcommand.size()) {
+			toput.push_back(cmdret.nextcommand);
+		}
+	}
 	if (cmdret.type & CmdMan::rettype::error) {
-		initmutex.lock();
-		if (versionstatus == off)
-			versionstatus = err;
-		else if (loginstatus == off)
-			loginstatus = err;
-		initmutex.unlock();
-		initcv.notify_all();
 		printMessage(Json::writeString(wbuilder, cmdret.msg), error);
 		mainmutex.lock();
 		runmain = false;
@@ -187,15 +219,6 @@ void BatchIoMan::handleOutCmdResponse(CmdMan::CmdRet cmdret, std::vector<std::st
 		linecv.notify_all();
 		return;
 	}
-	if (cmdret.type & CmdMan::rettype::seton) {
-		initmutex.lock();
-		if (versionstatus == off)
-			versionstatus = on;
-		else
-			loginstatus = on;
-		initmutex.unlock();
-		initcv.notify_all();
-	}
 	if (cmdret.type & CmdMan::rettype::print) {
 		printMessage(Json::writeString(wbuilder, cmdret.msg), normal);
 	}
@@ -205,7 +228,7 @@ void BatchIoMan::handleOutCmdResponse(CmdMan::CmdRet cmdret, std::vector<std::st
 			toput.push_back(cmdret.nextcommand);
 		}
 	}
-	if (versionstatus == on && !(cmdret.type & CmdMan::rettype::send)) {
+	if (!(cmdret.type & CmdMan::rettype::send)) {
 		// only fetch next line if we did not send a new command on our own
 		// if we managed to get here, get next command from file
 		linemutex.lock();
@@ -241,7 +264,7 @@ void BatchIoMan::run() {
 		while (!line.size()) {
 			// skip empty lines until either eof or non-empty line
 			if (batchin.eof()) {
-				line = "disconnect";
+				line = "exit";
 			} else
 				std::getline(batchin, line);
 		}
@@ -256,11 +279,6 @@ void BatchIoMan::run() {
 		printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"), debug);
 		localcv.notify_all();
 
-		if (!connected)
-			break;
-		if (loginstatus == err)
-			break;
-
 		mainmutex.lock();
 	}
 	mainmutex.unlock();
@@ -295,6 +313,69 @@ std::string BatchIoMan::printHelp(Json::Value root) {
 
 std::string BatchIoMan::printStatus(Json::Value root) { return std::string("Server reports status: ") + root["response"].asString(); }
 
+std::string BatchIoMan::printExtendedstatus(Json::Value root) {
+	if (!root["accept"].asBool()) {
+		return "Listing transfers failed. Server reports: " + root["error"].asString();
+	} else {
+		std::string retval = "";
+
+		if (!root["transfersclientserver"].empty()) {
+			retval += "\nTransfers between clients and server:\n";
+			/*
+			 * EXAMPLE:
+			 * type      progress  file
+			 * download       99%  foobar.txt
+			 * upload          1%  baz.html
+			 */
+			retval += "type      progress  file\n";
+			for (Json::Value val : root["transfersclientserver"]) {
+				std::string type = val["upload"].asBool() ? "upload" : "download";
+				std::string progress = std::to_string(val["progress"].asInt());
+				std::string file = val["file"].asString();
+
+				char output[21];
+				std::snprintf(output, 21, "%-8s  %7.7s%%  ", type.c_str(), progress.c_str());
+
+				retval += output + file + "\n";
+			}
+		}
+		if (!root["transfersserverserver"].empty()) {
+			retval += "\nTransfers between different servers:\n";
+
+			/*
+			 * EXAMPLE:
+			 * type      progress  method               bytes/sec  file
+			 * download       99%  method0000001          9000.01  foobar.txt
+			 * queued          1%  urgent field             42.00  baz.html
+			 *
+			 * Too long method strings get truncated, unexpectedly high speeds are shown (with less pretty format), e.g.:
+			 *
+			 * download       95%  too long stri  340282346638528859811704183484516925440.00  filename.zip
+			 */
+			retval += "type      progress  method               bytes/sec  file\n";
+			for (Json::Value val : root["transfersclientserver"]) {
+				std::string type = val["type"].asString();
+				std::string progress = std::to_string(val["progress"].asInt());
+				std::string method = val["method"].asString();
+				float speed = val["speed"].asFloat();
+				std::string file = val["file"].asString();
+
+				// size of 80 is just enough for maximum possible float value to fit as string
+				char output[80];
+				std::snprintf(output, 80, "%-8s  %7.7s%%  %-13.13s  %15.2f  ", type.c_str(), progress.c_str(), method.c_str(), speed);
+
+				std::cout << output << file << std::endl;
+
+				retval += output + file + "\n";
+			}
+		}
+		if (root["transfersclientserver"].empty() && root["transfersserverserver"].empty()) {
+			retval += "No transfers running.";
+		}
+		return retval;
+	}
+}
+
 std::string BatchIoMan::printDisconnect(Json::Value root) {
 	if (!root["accept"].asBool()) {
 		return "Disconnect failed.";
@@ -338,6 +419,49 @@ std::string BatchIoMan::printList(Json::Value root) {
 	return ret;
 }
 
+std::string BatchIoMan::printExtendedlist(Json::Value root) {
+	std::string ret;
+	if (!root["accept"].asBool()) {
+		ret = "Listing files failed: " + root["error"].asString();
+	} else {
+		if (!root["files"].empty()) {
+			ret = "Files stored on server: \n";
+			/*
+			 * EXAMPLE:
+			 * size (kBytes)  decryptable  file
+			 *       9000.01  yes          foo.txt
+			 *         42.00  no           baz.html
+			 *          0.02  plaintext    bar.zip
+			 */
+			ret += "size (kBytes)  decryptable  file\n";
+			for (Json::Value val : root["files"]) {
+				float size = val["size"].asFloat();
+				std::string encrypted = val["encrypted"].asString();
+				std::string decryptable;
+				if (encrypted == "decryptable") {
+					decryptable = "yes          ";
+				} else if (encrypted == "undecryptable") {
+					decryptable = "no           ";
+				} else if (encrypted == "unknown") {
+					decryptable = "unknown      ";
+				} else {
+					decryptable = "plaintext    ";
+				}
+				std::string progress = std::to_string(val["progress"].asInt());
+				std::string file = val["name"].asString();
+
+				char sizeString[44];
+				std::snprintf(sizeString, 44, "%13.2f  ", size);
+
+				ret += std::string(sizeString) + decryptable + file + "\n";
+			}
+		} else {
+			std::cout << "No files stored on server." << std::endl;
+		}
+	}
+	return ret;
+}
+
 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();
@@ -399,3 +523,29 @@ std::string BatchIoMan::printClosekey(Json::Value root) {
 	else
 		return "Key closed.";
 }
+
+std::string BatchIoMan::printQueue(Json::Value root) {
+	if (!root["accept"].asBool())
+		return std::string("Queueing of file ") + root["file"].asString() + " failed. " + root["error"].asString();
+	else
+		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();
+	else
+		return std::string("File ") + root["file"].asString() + " dequeued succesfully";
+}
+
+std::string BatchIoMan::printNotifications(Json::Value root) {
+	std::string ret;
+	if (!root["accept"].asBool()) {
+		return std::string("Failed to get notifications: ") + root["error"].asString();
+	} else {
+		ret = "New notifications:\n";
+		for (Json::Value i : root["messages"])
+			ret += i.asString() + "\n";
+	}
+	return ret;
+}

+ 445 - 72
cli/src/cmdman.cpp

@@ -14,37 +14,44 @@ CmdMan::CmdMan(FileMan &fm, void (*dpf)(string)) : fileman(fm) {
 	wbuilder.settings_["indentation"] = "";
 	reader = rbuilder.newCharReader();
 
-	doversion = false;
-	loginpossible = false;
-	dologin = false;
-	dosignup = false;
+	currentState = connectionpossible;
 
 	/* initialize execute command map */
 	execmap["help"] = &CmdMan::cmdHelp;
 	execmap["status"] = &CmdMan::cmdStatus;
+	execmap["extendedstatus"] = &CmdMan::cmdExtendedstatus;
 	execmap["disconnect"] = &CmdMan::cmdDisconnect;
 	execmap["put"] = &CmdMan::cmdPut;
 	execmap["get"] = &CmdMan::cmdGet;
 	execmap["list"] = &CmdMan::cmdList;
+	execmap["extendedlist"] = &CmdMan::cmdExtendedlist;
 	execmap["version"] = &CmdMan::cmdVersion;
 	execmap["login"] = &CmdMan::cmdLogin;
 	execmap["signup"] = &CmdMan::cmdSignup;
 	execmap["putdata"] = &CmdMan::cmdPutdata;
 	execmap["getdata"] = &CmdMan::cmdGetdata;
 	execmap["listdata"] = &CmdMan::cmdListdata;
+	execmap["extendedlistdata"] = &CmdMan::cmdExtendedlistdata;
 	execmap["head"] = &CmdMan::cmdHead;
 	execmap["deletefile"] = &CmdMan::cmdDeletefile;
 	execmap["deleteme"] = &CmdMan::cmdDeleteme;
 	execmap["keyfile"] = &CmdMan::cmdKeyfile;
 	execmap["closekey"] = &CmdMan::cmdClosekey;
+	execmap["queue"] = &CmdMan::cmdQueue;
+	execmap["dequeue"] = &CmdMan::cmdDequeue;
+	execmap["notifications"] = &CmdMan::cmdNotifications;
+	execmap["connect"] = &CmdMan::cmdConnect;
+	execmap["exit"] = &CmdMan::cmdExit;
 
 	/* initialize description map */
 	helpmap["help"] = descHelp;
 	helpmap["status"] = descStatus;
+	helpmap["extendedstatus"] = descExtendedstatus;
 	helpmap["disconnect"] = descDisconnect;
 	helpmap["put"] = descPut;
 	helpmap["get"] = descGet;
 	helpmap["list"] = descList;
+	helpmap["extendedlist"] = descExtendedlist;
 	helpmap["head"] = descHead;
 	helpmap["login"] = descLogin;
 	helpmap["signup"] = descSignup;
@@ -52,28 +59,41 @@ CmdMan::CmdMan(FileMan &fm, void (*dpf)(string)) : fileman(fm) {
 	helpmap["deleteme"] = descDeleteme;
 	helpmap["keyfile"] = descKeyfile;
 	helpmap["closekey"] = descClosekey;
+	helpmap["queue"] = descQueue;
+	helpmap["dequeue"] = descDequeue;
+	helpmap["notifications"] = descNotifications;
+	helpmap["connect"] = descConnect;
+	helpmap["exit"] = descExit;
 
 	/* initialize handle command map */
 	handlemap["status"] = &CmdMan::handleStatus;
+	handlemap["extendedstatus"] = &CmdMan::handleExtendedstatus;
 	handlemap["close"] = &CmdMan::handleClose;
 	handlemap["put"] = &CmdMan::handlePut;
 	handlemap["get"] = &CmdMan::handleGet;
 	handlemap["putdata"] = &CmdMan::handlePutdata;
 	handlemap["getdata"] = &CmdMan::handleGetdata;
 	handlemap["list"] = &CmdMan::handleList;
+	handlemap["extendedlist"] = &CmdMan::handleExtendedlist;
 	handlemap["version"] = &CmdMan::handleVersion;
 	handlemap["login"] = &CmdMan::handleLogin;
 	handlemap["signup"] = &CmdMan::handleSignup;
 	handlemap["listdata"] = &CmdMan::handleListdata;
+	handlemap["extendedlistdata"] = &CmdMan::handleExtendedlistdata;
 	handlemap["head"] = &CmdMan::handleHead;
 	handlemap["deletefile"] = &CmdMan::handleDeletefile;
 	handlemap["deleteme"] = &CmdMan::handleDeleteme;
+	handlemap["queue"] = &CmdMan::handleQueue;
+	handlemap["dequeue"] = &CmdMan::handleDequeue;
+	handlemap["notifications"] = &CmdMan::handleNotifications;
 
 	debugprintfunc = dpf;
 }
 
 CmdMan::~CmdMan() { delete reader; }
 
+void CmdMan::stateSetConnectionOk() { currentState = versionpossible; }
+
 CmdMan::CmdRet CmdMan::cmdHelp(vector<string> args) {
 	CmdRet retval;
 	Json::Value root, arr;
@@ -100,18 +120,33 @@ CmdMan::CmdRet CmdMan::cmdStatus(vector<string> args) {
 	return retval;
 }
 
+CmdMan::CmdRet CmdMan::cmdExtendedstatus(vector<string> args) {
+	CmdRet retval;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+	Json::Value root;
+	root["command"] = "extendedstatus";
+	retval.type = send;
+	retval.msg = root;
+
+	return retval;
+}
+
 CmdMan::CmdRet CmdMan::cmdDisconnect(vector<string> args) {
 	CmdRet retval;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
 	retval.type = send;
-	if (loginpossible) {
+	if (currentState == loginpossible || currentState == disconnecttoexitearly) {
 		// not logged in, send appropriate login message instead of normal close
 		root["login"] = false;
 		root["user"] = "";
 		root["pass"] = "";
 		root["cancel"] = true;
 		retval.type |= close;
+		if (currentState == disconnecttoexitearly) {
+			retval.nextcommand = "exit";
+		}
+		currentState = connectionpossible;
 	} else {
 		root["command"] = "close";
 	}
@@ -138,13 +173,14 @@ CmdMan::CmdRet CmdMan::cmdPut(vector<string> args) {
 			root["error"] = "already putting file \"" + fileman.getPutName() + "\"";
 		} else {
 			bool opened = fileman.openPut(args[0]);
-			root["file"] = fileman.getPutName();
 			if (opened) {
+				root["file"] = fileman.getPutName();
 				root["size"] = fileman.getPutSize();
 				root["chunks"] = fileman.getPutChunks();
 				retval.type = send;
 			} else {
 				retval.type = error;
+				root["file"] = fileman.pathToFilename(args[0]);
 				root["accept"] = false;
 				root["error"] = "couldnt open local file \"" + args[0] + "\"";
 			}
@@ -161,11 +197,16 @@ CmdMan::CmdRet CmdMan::cmdPutdata(vector<string> args) {
 	Json::Value root;
 
 	root["command"] = "putdata";
-	root["file"] = fileman.getPutName();
-	root["cancel"] = false;
-	root["data"] = fileman.readBase64();
-	root["remaining"] = fileman.getPutRemainingChunks(); // number already decremented by readBase64
-	retval.type = send;
+	if (!fileman.isPutting()) {
+		root["error"] = "Client cannot handle input (received command \"putdata\").";
+		retval.type = error;
+	} else {
+		root["file"] = fileman.getPutName();
+		root["cancel"] = false;
+		root["data"] = fileman.readBase64();
+		root["remaining"] = fileman.getPutRemainingChunks(); // number already decremented by readBase64
+		retval.type = send;
+	}
 	retval.msg = root;
 
 	return retval;
@@ -182,6 +223,7 @@ CmdMan::CmdRet CmdMan::cmdGet(vector<string> args) {
 		root["accept"] = false;
 		root["error"] = "not enough arguments, at least 1 argument required";
 	} else {
+
 		if (fileman.isGetting()) {
 			retval.type = error;
 			root["file"] = args[0];
@@ -191,11 +233,13 @@ CmdMan::CmdRet CmdMan::cmdGet(vector<string> args) {
 			bool opened = fileman.openGet(args[0]);
 			root["file"] = fileman.getGetName();
 			if (opened) {
+				root["file"] = fileman.getGetName();
 				retval.type = send;
 			} else {
-				retval.type = error;
+				root["file"] = fileman.pathToFilename(args[0]);
 				root["accept"] = false;
 				root["error"] = "local file \"" + args[0] + "\" already exists";
+				retval.type = error;
 			}
 		}
 	}
@@ -210,10 +254,15 @@ CmdMan::CmdRet CmdMan::cmdGetdata(vector<string> args) {
 	Json::Value root;
 
 	root["command"] = "getdata";
-	root["file"] = fileman.getGetName();
-	root["chunk"] = fileman.getGetRemainingChunks();
-	root["cancel"] = false;
-	retval.type = send;
+	if (!fileman.isGetting()) {
+		root["error"] = "Client cannot handle input (received command \"getdata\").";
+		retval.type = error;
+	} else {
+		root["file"] = fileman.getGetName();
+		root["chunk"] = fileman.getGetRemainingChunks();
+		root["cancel"] = false;
+		retval.type = send;
+	}
 	retval.msg = root;
 
 	return retval;
@@ -224,7 +273,7 @@ CmdMan::CmdRet CmdMan::cmdList(vector<string> args) {
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
 
-	bool opened = fileman.openList();
+	bool opened = fileman.openList(false);
 	root["command"] = "list";
 	if (opened) {
 		retval.type = send;
@@ -243,12 +292,57 @@ CmdMan::CmdRet CmdMan::cmdListdata(vector<string> args) {
 	CmdRet retval;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
-	root["command"] = "listdata";
-	root["chunk"] = fileman.getListRemainingChunks();
-	root["cancel"] = false;
-	retval.type = send;
+	if (!fileman.isListingSimple()) {
+		root["command"] = "list";
+		root["error"] = "Client cannot handle input (received command \"listdata\").";
+		retval.type = error;
+	} else {
+		root["command"] = "listdata";
+		root["chunk"] = fileman.getListRemainingChunks();
+		root["cancel"] = false;
+		retval.type = send;
+	}
+
 	retval.msg = root;
+	return retval;
+}
 
+CmdMan::CmdRet CmdMan::cmdExtendedlist(vector<string> args) {
+	CmdRet retval;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+	Json::Value root;
+
+	bool opened = fileman.openList(true);
+	root["command"] = "extendedlist";
+	if (opened) {
+		retval.type = send;
+	} else {
+		retval.type = error;
+		root["accept"] = false;
+		root["names"] = "";
+		root["error"] = "cannot list, already listing";
+	}
+
+	retval.msg = root;
+	return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdExtendedlistdata(vector<string> args) {
+	CmdRet retval;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+	Json::Value root;
+	if (!fileman.isListingExtended()) {
+		root["command"] = "extendedlist";
+		root["error"] = "Client cannot handle input (received command \"listdata\").";
+		retval.type = error;
+	} else {
+		root["command"] = "extendedlistdata";
+		root["chunk"] = fileman.getListRemainingChunks();
+		root["cancel"] = false;
+		retval.type = send;
+	}
+
+	retval.msg = root;
 	return retval;
 }
 
@@ -290,6 +384,30 @@ CmdMan::CmdRet CmdMan::cmdDeletefile(vector<string> args) {
 	return retval;
 }
 
+CmdMan::CmdRet CmdMan::cmdConnect(vector<string> args) {
+	CmdRet retval;
+	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;
+	} else {
+		retval.type = connect;
+		root["address"] = args[0];
+		root["port"] = (unsigned int)stoul(args[1]);
+	}
+	retval.msg = root;
+
+	return retval;
+}
+
 CmdMan::CmdRet CmdMan::execute(string cmd, vector<string> args) {
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " using command \"" + cmd + "\" with arguments [ ");
@@ -306,7 +424,9 @@ CmdMan::CmdRet CmdMan::execute(string cmd, vector<string> args) {
 		root["error"] = string(__PRETTY_FUNCTION__) + " unknown command \"" + cmd + "\".\ntype help to list available commands.";
 		retval.msg = root;
 		return retval;
-	} else if (loginpossible || dologin || dosignup) {
+	} else if (!cmd.compare("help") || !cmd.compare("exit")) {
+		// allow help and exit 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")));
@@ -318,6 +438,26 @@ CmdMan::CmdRet CmdMan::execute(string cmd, vector<string> args) {
 			retval.msg = root;
 			return retval;
 		}
+	} else if (currentState == versionpossible || currentState == doversion) {
+		DEBUGPRINT("execute does version");
+		DEBUGPRINT(string("comparison is ") + std::to_string(cmd.compare("version")));
+		if (cmd.compare("version")) {
+			retval.type = error;
+			root["command"] = "error";
+			root["error"] = string("Version not checked yet. No commands avalable.");
+			retval.msg = root;
+			return retval;
+		}
+	} else if (currentState == connectionpossible) {
+		DEBUGPRINT("execute does connect");
+		DEBUGPRINT(string("comparison is ") + std::to_string(cmd.compare("connect")));
+		if (cmd.compare("version") && cmd.compare("connect")) {
+			retval.type = error;
+			root["command"] = "error";
+			root["error"] = string("Not connected. Connect using \"connect ip [port]\".");
+			retval.msg = root;
+			return retval;
+		}
 	}
 	return (this->*(execmap[cmd]))(args);
 }
@@ -387,6 +527,26 @@ CmdMan::CmdRet CmdMan::cmdClosekey(vector<string> args) {
 	return retval;
 }
 
+CmdMan::CmdRet CmdMan::cmdExit(vector<string> args) {
+	CmdRet retval;
+	Json::Value root;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+	root["command"] = "exit";
+	if (currentState != connectionpossible) {
+		// we are connected, disconnect first
+		retval.nextcommand = "disconnect";
+		if (currentState == loginpossible)
+			currentState = disconnecttoexitearly;
+		else
+			currentState = disconnecttoexit;
+		retval.type = none;
+	} else {
+		retval.type = exit;
+	}
+	retval.msg = root;
+	return retval;
+}
+
 /* login and signup commands */
 CmdMan::CmdRet CmdMan::cmdLogin(vector<string> args) {
 	CmdRet retval;
@@ -399,9 +559,8 @@ CmdMan::CmdRet CmdMan::cmdLogin(vector<string> args) {
 		root["accept"] = false;
 		root["error"] = "not enough arguments, at least 2 argument required";
 	} else {
-		if (loginpossible) {
-			dologin = true;
-			loginpossible = false;
+		if (currentState == loginpossible) {
+			currentState = dologin;
 			root["user"] = args[0];
 			root["pass"] = args[1];
 			root["login"] = true;
@@ -431,9 +590,8 @@ CmdMan::CmdRet CmdMan::cmdSignup(vector<string> args) {
 		root["accept"] = false;
 		root["error"] = "not enough arguments, at least 2 argument required";
 	} else {
-		if (loginpossible) {
-			dosignup = true;
-			loginpossible = false;
+		if (currentState == loginpossible) {
+			currentState = dosignup;
 			root["user"] = args[0];
 			root["pass"] = args[1];
 			root["login"] = false;
@@ -452,17 +610,71 @@ CmdMan::CmdRet CmdMan::cmdSignup(vector<string> args) {
 	return retval;
 }
 
+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";
+	} else {
+		root["file"] = args[0];
+		retval.type = send;
+	}
+	retval.msg = root;
+
+	return retval;
+}
+
+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";
+	} else {
+		root["file"] = args[0];
+		retval.type = send;
+	}
+	retval.msg = root;
+
+	return retval;
+}
+
 /* internal commands */
 CmdMan::CmdRet CmdMan::cmdVersion(vector<string> args) {
 	CmdRet retval;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
-	root["major"] = protocolMajorVersion;
-	root["minor"] = protocolMinorVersion;
-	retval.type = send;
+	if (currentState == versionpossible) {
+		root["major"] = protocolMajorVersion;
+		root["minor"] = protocolMinorVersion;
+		retval.type = send;
+		currentState = doversion;
+	} else {
+		retval.type = error;
+		root["command"] = "error";
+		root["error"] = "Executing version command not possible. Type help to list available commands.";
+	}
 	retval.msg = root;
 
-	doversion = true;
+	return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdNotifications(vector<string> args) {
+	CmdRet retval;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+	Json::Value root;
+	root["command"] = "notifications";
+	retval.type = send;
+	retval.msg = root;
 
 	return retval;
 }
@@ -471,11 +683,11 @@ CmdMan::CmdRet CmdMan::handle(Json::Value root) {
 	CmdRet retval;
 	Json::Value output;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
-	if (doversion)
+	if (currentState == doversion)
 		root["command"] = "version";
-	else if (dosignup)
+	else if (currentState == dosignup)
 		root["command"] = "signup";
-	else if (dologin)
+	else if (currentState == dologin)
 		root["command"] = "login";
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " using json\n" + Json::writeString(wbuilder, root) + "\n");
 	string retmsg;
@@ -500,6 +712,19 @@ CmdMan::CmdRet CmdMan::handleStatus(Json::Value root) {
 	return retval;
 }
 
+CmdMan::CmdRet CmdMan::handleExtendedstatus(Json::Value root) {
+	CmdRet retval;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+
+	if (!root["accept"].asBool()) {
+		retval.type = error;
+	} else {
+		retval.type = print;
+	}
+	retval.msg = root;
+	return retval;
+}
+
 CmdMan::CmdRet CmdMan::handleClose(Json::Value root) {
 	CmdRet retval;
 	Json::Value output;
@@ -509,6 +734,11 @@ CmdMan::CmdRet CmdMan::handleClose(Json::Value root) {
 	retval.type = close | print;
 	retval.msg = output;
 
+	if (currentState == disconnecttoexit) {
+		retval.nextcommand = "exit";
+	}
+	currentState = connectionpossible;
+
 	return retval;
 }
 
@@ -524,6 +754,9 @@ CmdMan::CmdRet CmdMan::handlePut(Json::Value root) {
 		retval.type = error;
 		output["error"] = "Server reports: " + root["error"].asString();
 		fileman.cancelPut();
+	} else if (!fileman.isPutting()) {
+		retval.type = error;
+		output["error"] = "Server responds to put message which was never sent.";
 	} else if (root["file"].asString() != fileman.getPutName()) {
 		retval.type = error;
 		output["error"] = "Server reports filename " + root["file"].asString() + " but actual filename is " + fileman.getPutName();
@@ -548,7 +781,14 @@ CmdMan::CmdRet CmdMan::handlePutdata(Json::Value root) {
 	output["speed"] = 0.0f; // TODO
 	output["cancel"] = true;
 
-	if (root["received"].asInt() != fileman.getPutRemainingChunks()) {
+	if (root["cancel"].asBool()) {
+		retval.type = error;
+		output["error"] = "Server reports: " + root["error"].asString();
+		fileman.cancelPut();
+	} else if (!fileman.isPutting()) {
+		retval.type = error;
+		output["error"] = "Server responds to put message which was never sent.";
+	} else if (root["received"].asInt() != fileman.getPutRemainingChunks()) {
 		// the number of remaining chunks received from the daemon does not equal
 		// the number stored at the client side
 		retval.type = error;
@@ -556,10 +796,6 @@ CmdMan::CmdRet CmdMan::handlePutdata(Json::Value root) {
 		                              "remaining chunks as ") +
 		                  std::to_string(root["received"].asInt()) + " but actual number is " + std::to_string(fileman.getPutRemainingChunks());
 		fileman.cancelPut();
-	} else if (root["cancel"].asBool()) {
-		retval.type = error;
-		output["error"] = "Server reports: " + root["error"].asString();
-		fileman.cancelPut();
 	} else if (root["file"].asString() != fileman.getPutName()) {
 		retval.type = error;
 		output["error"] = "Server reports filename " + root["file"].asString() + " but actual filename is " + fileman.getPutName();
@@ -594,9 +830,14 @@ CmdMan::CmdRet CmdMan::handleGet(Json::Value root) {
 	if (!root["accept"].asBool()) {
 		retval.type = error;
 		output["error"] = "Server reports: " + root["error"].asString();
+		fileman.cancelGet();
+	} else if (!fileman.isGetting()) {
+		retval.type = error;
+		output["error"] = "Server responds to get message which was never sent.";
 	} else if (root["file"].asString() != fileman.getGetName()) {
 		retval.type = error;
 		output["error"] = "Server reports filename " + root["file"].asString() + " but actual filename is " + fileman.getGetName();
+		fileman.cancelGet();
 	} else {
 		fileman.setGetChunks(root["chunks"].asInt());
 		output["accept"] = true;
@@ -618,15 +859,17 @@ CmdMan::CmdRet CmdMan::handleGetdata(Json::Value root) {
 	output["speed"] = 0.0f; // TODO
 	output["cancel"] = true;
 
-	// the passed number of recieved chunks should equal the number of sent chunks
-	if (root["remaining"].asInt() != fileman.getGetRemainingChunks()) {
+	if (root["cancel"].asBool()) {
 		retval.type = error;
-		output["error"] = std::string("Server reports number of remaining chunks as ") + std::to_string(root["remaining"].asInt()) + " but actual number is " +
-		                  std::to_string(fileman.getGetRemainingChunks());
+		output["error"] = "Server reports: " + root["error"].asString();
 		fileman.cancelGet();
-	} else if (root["cancel"].asBool()) {
+	} else if (!fileman.isGetting()) {
 		retval.type = error;
-		output["error"] = "Server reports: " + root["error"].asString();
+		output["error"] = "Server responds to get message which was never sent.";
+	} else if (root["remaining"].asInt() != fileman.getGetRemainingChunks()) {
+		retval.type = error;
+		output["error"] = std::string("Server reports number of remaining chunks as ") + std::to_string(root["remaining"].asInt()) + " but actual number is " +
+		                  std::to_string(fileman.getGetRemainingChunks());
 		fileman.cancelGet();
 	} else if (root["file"].asString() != fileman.getGetName()) {
 		retval.type = error;
@@ -637,8 +880,8 @@ CmdMan::CmdRet CmdMan::handleGetdata(Json::Value root) {
 		output["error"] = "";
 		fileman.writeBase64(root["data"].asString());
 		// loaded successfully
-		if (fileman.getGetRemainingChunks() < 0) {
-			// everything sent
+		if (!root["remaining"].asInt()) {
+			// everything received
 			retval.type = print;
 			//~ retval.msg = "succesfully downloaded file " + fileman.getGetName();
 			fileman.closeGet();
@@ -664,6 +907,15 @@ CmdMan::CmdRet CmdMan::handleList(Json::Value root) {
 		output["accept"] = false;
 		output["error"] = "Server reports: " + root["error"].asString();
 		fileman.cancelList();
+	} else if (root["items"].asInt() == 0) {
+		retval.type = print;
+		output["accept"] = false;
+		output["error"] = "There are no files stored on the server.";
+		fileman.closeList();
+	} else if (!fileman.isListingSimple()) {
+		retval.type = error;
+		output["accept"] = false;
+		output["error"] = "Server responds to list message which was never sent.";
 	} else {
 		fileman.setListChunks(root["chunks"].asInt());
 		retval.type = send;
@@ -679,38 +931,121 @@ CmdMan::CmdRet CmdMan::handleListdata(Json::Value root) {
 	CmdRet retval;
 	Json::Value output, arr;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
-	vector<string> toadd;
+	vector<Json::Value> toadd;
 
 	output["command"] = "list";
 	output["names"] = "";
 	output["accept"] = false;
-	// the passed number of recieved chunks should equal the number of sent chunks
-	if (root["remaining"].asInt() != fileman.getListRemainingChunks()) {
+
+	if (root["cancel"].asBool()) {
+		retval.type = error;
+		output["error"] = "Server reports: " + root["error"].asString();
+		fileman.cancelList();
+	} else if (!fileman.isListingSimple()) {
+		retval.type = error;
+		output["error"] = "Server responds to list message which was never sent.";
+	} else if (root["remaining"].asInt() != fileman.getListRemainingChunks()) {
+		// the passed number of recieved chunks should equal the number of sent chunks
 		retval.type = error;
 		output["error"] = std::string("Server reports number of "
 		                              "remaining chunks as ") +
 		                  std::to_string(root["remaining"].asInt()) + " but actual number is " + std::to_string(fileman.getListRemainingChunks());
 		fileman.cancelList();
-	} else if (root["cancel"].asBool()) {
+	} else {
+		output["accept"] = true;
+		for (Json::Value i : root["names"])
+			toadd.push_back(i);
+		fileman.putListData(toadd);
+		// loaded successfully
+		if (root["remaining"] <= 0) {
+			// everything sent
+			retval.type = print;
+			for (Json::Value s : fileman.getListData())
+				arr.append(s.asString());
+			output["names"] = arr;
+			fileman.closeList();
+		} else {
+			retval.type = send;
+			retval.nextcommand = "listdata";
+		}
+	}
+	retval.msg = output;
+	return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleExtendedlist(Json::Value root) {
+	CmdRet retval;
+	Json::Value output, files; // LOCALOUTPUT
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+
+	output["command"] = "extendedlist";
+	output["files"] = files;
+
+	if (!root["accept"].asBool()) {
 		retval.type = error;
+		output["accept"] = false;
 		output["error"] = "Server reports: " + root["error"].asString();
 		fileman.cancelList();
+	} else if (root["items"].asInt() == 0) {
+		retval.type = print;
+		output["accept"] = false;
+		output["error"] = "There are no files stored on the server.";
+		fileman.closeList();
+	} else if (!fileman.isListingExtended()) {
+		retval.type = error;
+		output["accept"] = false;
+		output["error"] = "Server responds to list message which was never sent.";
 	} else {
+		fileman.setListChunks(root["chunks"].asInt());
+		retval.type = send;
 		output["accept"] = true;
-		for (Json::Value i : root["names"])
-			toadd.push_back(i.asString());
+		retval.nextcommand = "extendedlistdata";
+	}
+
+	retval.msg = output;
+	return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleExtendedlistdata(Json::Value root) {
+	CmdRet retval;
+	Json::Value output, arr, files;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+	vector<Json::Value> toadd;
+
+	output["command"] = "extendedlist";
+	output["files"] = files;
+	output["accept"] = false;
+
+	if (root["cancel"].asBool()) {
+		retval.type = error;
+		output["error"] = "Server reports: " + root["error"].asString();
+		fileman.cancelList();
+	} else if (!fileman.isListingExtended()) {
+		retval.type = error;
+		output["error"] = "Server responds to list message which was never sent.";
+	} else if (root["remaining"].asInt() != fileman.getListRemainingChunks()) {
+		// the passed number of recieved chunks should equal the number of sent chunks
+		retval.type = error;
+		output["error"] = std::string("Server reports number of "
+		                              "remaining chunks as ") +
+		                  std::to_string(root["remaining"].asInt()) + " but actual number is " + std::to_string(fileman.getListRemainingChunks());
+		fileman.cancelList();
+	} else {
+		output["accept"] = true;
+		for (Json::Value i : root["files"])
+			toadd.push_back(i);
 		fileman.putListData(toadd);
 		// loaded successfully
-		if (fileman.getListRemainingChunks() < 0) {
+		if (root["remaining"] <= 0) {
 			// everything sent
 			retval.type = print;
-			for (string s : fileman.getListData())
+			for (Json::Value s : fileman.getListData())
 				arr.append(s);
-			output["names"] = arr;
+			output["files"] = arr;
 			fileman.closeList();
 		} else {
 			retval.type = send;
-			retval.nextcommand = "listdata";
+			retval.nextcommand = "extendedlistdata";
 		}
 	}
 	retval.msg = output;
@@ -723,17 +1058,17 @@ CmdMan::CmdRet CmdMan::handleVersion(Json::Value root) {
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 
 	output["command"] = "version";
-	output["serverversion"] = root["major"].asString() + "." + root["minor"].asString();
+	output["serverversion"] = std::to_string(root["major"].asInt()) + "." + std::to_string(root["minor"].asInt());
 	output["clientversion"] = std::to_string(protocolMajorVersion) + "." + std::to_string(protocolMinorVersion);
 
 	if (!root["accept"].asBool()) {
-		retval.type = error;
+		retval.type = error | close;
 		output["accept"] = false;
+		currentState = connectionpossible;
 	} else {
-		retval.type = print | seton;
+		retval.type = print;
 		output["accept"] = true;
-		doversion = false;
-		loginpossible = true;
+		currentState = loginpossible;
 	}
 	retval.msg = output;
 
@@ -748,16 +1083,15 @@ CmdMan::CmdRet CmdMan::handleLogin(Json::Value root) {
 	output["command"] = "login";
 
 	if (!root["accept"].asBool()) {
-		retval.type = error;
+		retval.type = error | close;
 		output["error"] = root["error"].asString();
 		output["accept"] = false;
-		loginpossible = true;
-		dologin = false;
+		currentState = connectionpossible;
 	} else {
-		retval.type = print | seton;
+		retval.type = print;
 		output["error"] = "";
 		output["accept"] = true;
-		dologin = false;
+		currentState = normal;
 	}
 	retval.msg = output;
 
@@ -775,13 +1109,12 @@ CmdMan::CmdRet CmdMan::handleSignup(Json::Value root) {
 		retval.type = error;
 		output["error"] = root["error"].asString();
 		output["accept"] = false;
-		loginpossible = true;
-		dosignup = false;
+		currentState = loginpossible;
 	} else {
-		retval.type = print | seton;
+		retval.type = print;
 		output["error"] = "";
 		output["accept"] = true;
-		dosignup = false;
+		currentState = normal;
 	}
 	retval.msg = output;
 
@@ -834,6 +1167,46 @@ CmdMan::CmdRet CmdMan::handleDeleteme(Json::Value root) {
 		retval.type = error;
 	} else {
 		retval.type = close | print;
+		currentState = connectionpossible;
+	}
+	retval.msg = root;
+	return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleQueue(Json::Value root) {
+	CmdRet retval;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+
+	if (root["accept"].asBool()) {
+		retval.type = print;
+	} else {
+		retval.type = error;
+	}
+	retval.msg = root;
+	return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleDequeue(Json::Value root) {
+	CmdRet retval;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+
+	if (root["accept"].asBool()) {
+		retval.type = print;
+	} else {
+		retval.type = error;
+	}
+	retval.msg = root;
+	return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleNotifications(Json::Value root) {
+	CmdRet retval;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+
+	if (root["accept"].asBool()) {
+		retval.type = print;
+	} else {
+		retval.type = error;
 	}
 	retval.msg = root;
 	return retval;

+ 88 - 12
cli/src/fileman.cpp

@@ -31,6 +31,8 @@ bool FileMan::isGetting() { return getfile.is_open(); }
 bool FileMan::isPutting() { return isputting; }
 
 bool FileMan::isListing() { return islisting; }
+bool FileMan::isListingSimple() { return islisting && !extendedListing; }
+bool FileMan::isListingExtended() { return islisting && extendedListing; }
 
 bool FileMan::isEncrypted() { return keyenabled; }
 
@@ -145,29 +147,30 @@ bool FileMan::openGet(const string &path) {
 	return true;
 }
 
-bool FileMan::openList() {
+bool FileMan::openList(bool extended) {
 	if (isListing()) {
 		return false;
 	}
-	listdata = vector<string>();
+	listdata = vector<Json::Value>();
+	extendedListing = extended;
 	islisting = true;
 	return true;
 }
 
 bool FileMan::openKey(const string &path) {
 	std::ifstream keyfile;
-	std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+	//~ std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
 	if (isPutting() || isGetting())
 		return false; // do not enable key mid-operation
 	if (keyenabled)
 		closeKey();
 	keyfile.open(path, std::ios::ate | std::ios::binary);
-	std::cerr << __PRETTY_FUNCTION__ << " open keyfile" << std::endl;
+	//~ std::cerr << __PRETTY_FUNCTION__ << " open keyfile" << std::endl;
 	if (keyfile.is_open()) {
-		std::cerr << __PRETTY_FUNCTION__ << " keyfile open " << keyfile.tellg() << " " << sizeof(key) << std::endl;
+		//~ std::cerr << __PRETTY_FUNCTION__ << " keyfile open " << keyfile.tellg() << " " << sizeof(key) << std::endl;
 		if (keyfile.tellg() == sizeof(key)) {
 			keyfile.seekg(0);
-			std::cerr << "keyfile is at " << keyfile.tellg() << std::endl;
+			//~ std::cerr << "keyfile is at " << keyfile.tellg() << std::endl;
 			keyfile.read((char *)key, sizeof(key));
 			keyfile.close();
 			keyenabled = true;
@@ -179,6 +182,10 @@ bool FileMan::openKey(const string &path) {
 
 void FileMan::closePut() {
 	putdata = vector<vector<char>>();
+	putname = "";
+	putpath = "";
+	putchunks = 0;
+	putchunksRemaining = 0;
 	isputting = false;
 	memset(iv, 0, sizeof(iv));
 	memset(tag, 0, sizeof(tag));
@@ -186,11 +193,20 @@ void FileMan::closePut() {
 
 void FileMan::closeGet() {
 	getfile.close();
+	getname = "";
+	getpath = "";
+	getchunks = 0;
+	getchunksRemaining = 0;
 	memset(iv, 0, sizeof(iv));
 	memset(tag, 0, sizeof(tag));
 }
 
-void FileMan::closeList() { islisting = false; }
+void FileMan::closeList() {
+	listdata = vector<Json::Value>();
+	islisting = false;
+	listchunks = 0;
+	listchunksRemaining = 0;
+}
 
 bool FileMan::closeKey() {
 	if (isPutting() || isGetting())
@@ -242,12 +258,35 @@ void FileMan::writeBase64(string data) {
 	keyenabled ? writeEnc(decode) : writeGet(decode);
 }
 
-void FileMan::putListData(vector<string> names) {
-	listdata.insert(listdata.end(), names.begin(), names.end());
+void FileMan::putListData(vector<Json::Value> files) {
+	if (!extendedListing) {
+		listdata.insert(listdata.end(), files.begin(), files.end());
+	} else {
+		for (Json::Value file : files) {
+
+			Json::Value fileInfo;
+			fileInfo["name"] = file["name"];
+			fileInfo["size"] = file["size"];
+
+			string headString = file["head"].asString();
+			decryptability decr = isDecryptable(base64::decodeVector(headString));
+			if (decr == FileMan::unknown) {
+				fileInfo["encrypted"] = "unknown";
+			} else if (decr == FileMan::undecryptable) {
+				fileInfo["encrypted"] = "undecryptable";
+			} else if (decr == FileMan::decryptable) {
+				fileInfo["encrypted"] = "decryptable";
+			} else {
+				fileInfo["encrypted"] = "unencrypted";
+			}
+
+			listdata.push_back(fileInfo);
+		}
+	}
 	listchunksRemaining--;
 }
 
-vector<string> FileMan::getListData() { return listdata; }
+vector<Json::Value> FileMan::getListData() { return listdata; }
 
 int FileMan::getGetChunks() { return getchunks; }
 
@@ -371,7 +410,7 @@ void FileMan::deinitCryptoD() {
 }
 
 void FileMan::writeEnc(const vector<char> data) {
-	std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+	//~ std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
 	writeGet(data);
 	if (getchunksRemaining < 0) {
 		// loaded everything, try to decrypt
@@ -385,7 +424,6 @@ void FileMan::writeEnc(const vector<char> data) {
 			return;
 		}
 		size -= (sizeof(iv) + sizeof(tag));
-		std::cerr << __PRETTY_FUNCTION__ << " size is " << size << std::endl;
 		cipher = new unsigned char[size];
 		plain = new unsigned char[size];
 		getfile.seekg(0);
@@ -428,3 +466,41 @@ void FileMan::writeEnc(const vector<char> data) {
 		delete[] plain;
 	}
 }
+
+FileMan::decryptability FileMan::isDecryptable(const vector<char> data) {
+	unsigned char plain[sizeof(signature)] = {0}, *cipher;
+	int plainlen;
+	// check if signature matches in plaintext
+	if (data.size() < sizeof(signature) || memcmp(data.data(), signature, sizeof(signature))) {
+		// either size or signature dont match
+		if (data.size() >= (sizeof(signature) + sizeof(iv) + sizeof(tag)) && keyenabled) {
+			// enough data, key is enabled
+			memcpy(iv, data.data(), sizeof(iv));
+			memcpy(tag, data.data() + sizeof(iv), sizeof(tag));
+			cipher = (unsigned char *)(data.data() + sizeof(iv) + sizeof(tag));
+
+			if (initCryptoD()) {
+				// crypto is ready
+				if (!EVP_DecryptUpdate(cryptctxd, plain, &plainlen, cipher, sizeof(signature))) {
+					setOpensslError();
+					std::cerr << __PRETTY_FUNCTION__ << " failed to update " << getOpensslError() << std::endl;
+				}
+
+				deinitCryptoD();
+
+				// check again if signature matches
+				if (memcmp(plain, signature, sizeof(signature))) {
+					// still no dice
+					return undecryptable;
+				}
+
+				// yup, got good data
+				return decryptable;
+			}
+		}
+		// size mismatch or no key enabled, consider unknown
+		return unknown;
+	}
+	// it does, assume plaintext
+	return plaintext;
+}

+ 72 - 74
cli/src/ioman.cpp

@@ -25,12 +25,11 @@ extern IoMan *gIOMAN;
 
 void ioman_externalDebugPrint(string msg) { gIOMAN->printMessage(msg, gIOMAN->OutMsgType::debug); }
 
-IoMan::IoMan(char *ipcstring, bool enablessl) : cmdman(fileman, &ioman_externalDebugPrint), recvbuf(16384) {
-	ipstring = std::string(ipcstring);
-	port = 1234;
+IoMan::IoMan(bool enablessl) : cmdman(fileman, &ioman_externalDebugPrint), recvbuf(16384) {
+	ipstring = "";
+	port = 0;
 	tcpsock = new tcp::socket(ios);
 	connected = false;
-	usessl = false;
 
 	/* to be put elsewhere */
 
@@ -41,8 +40,6 @@ IoMan::IoMan(char *ipcstring, bool enablessl) : cmdman(fileman, &ioman_externalD
 	runnetwork = false;
 	runinput = false;
 	runresponse = false;
-	versionstatus = off;
-	loginstatus = off;
 	usessl = enablessl;
 	if (usessl) {
 		sslctx = new boost::asio::ssl::context(boost::asio::ssl::context::sslv23);
@@ -54,8 +51,6 @@ IoMan::IoMan(char *ipcstring, bool enablessl) : cmdman(fileman, &ioman_externalD
 }
 
 IoMan::~IoMan() {
-	initcv.notify_all();
-
 	if (connected) {
 		disconnect();
 	}
@@ -161,6 +156,9 @@ bool IoMan::connect() {
 		root["error"] = errcode.message();
 		connected = false;
 	} else {
+		if (!ios.stopped())
+			ios.stop();
+		ios.restart();
 		// establish connection
 		printMessage(string(__PRETTY_FUNCTION__) + string(" connecting to ") + ipstring, debug);
 		ep = new tcp::endpoint(addr, port);
@@ -202,7 +200,10 @@ void IoMan::disconnect() {
 	printMessage("IoMan::disconnect()", debug);
 	tcpsock->shutdown(tcp::socket::shutdown_both, errcode);
 	if (errcode)
-		printMessage(string(__PRETTY_FUNCTION__) + string("tcp shutdown says ") + errcode.message(), error);
+		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;
 }
 
@@ -212,43 +213,13 @@ bool IoMan::init() {
 	Json::Value root;
 	std::unique_lock<std::mutex> ulock;
 
-	printMessage("IoMan::Init() begin", debug);
-
-	if (!connect())
-		return false;
-
-	printMessage("IoMan::Init() versioncheck", debug);
-
-	localmutex.lock();
-	printMessage(string(__PRETTY_FUNCTION__) + string(" get localmutex"), debug);
-	localinput.push_back("version");
-	localmutex.unlock();
-	printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"), debug);
-	localcv.notify_all();
-
-	printMessage("IoMan::Init() begin", debug);
-
-	runnetwork = true;
+	printMessage(string(__PRETTY_FUNCTION__) + string(" begin"), debug);
 	runinput = true;
 	runresponse = true;
 
-	tnetwork = std::thread(&IoMan::networkMain, this);
 	tinput = std::thread(&IoMan::inputMain, this);
 	tresponse = std::thread(&IoMan::responseMain, this);
 
-	ulock = std::unique_lock<std::mutex>(initmutex);
-	while (versionstatus == off) {
-		initcv.wait(ulock);
-	}
-	if (versionstatus == err) {
-		runnetwork = false;
-		runinput = false;
-		runresponse = false;
-		return false;
-	}
-	initmutex.unlock();
-	initcv.notify_all();
-
 	printWelcomeMessage();
 	return true;
 }
@@ -284,13 +255,13 @@ 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 (errcode && errcode != boost::asio::error::eof) {
-			printMessage("IoMan::networkMain() couldnt read json data\n" + errcode.message(), error);
-			continue;
-		}
 		if (readsize < 1) {
 			break;
 		}
+		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;
 		while (strchr(recvjson, '\n')) {
@@ -300,7 +271,7 @@ void IoMan::networkMain() {
 			printMessage(string(__PRETTY_FUNCTION__) + string(" found jsondata ") + string(recvjson), debug);
 
 			if (!reader->parse(recvjson, recvjson + jsonsize, &root, &jsonerror)) {
-				printMessage("IoMan::networkMain() couldnt parse json data: " + jsonerror, error);
+				printMessage("IoMan::networkMain() couldnt parse json data: " + jsonerror, debug);
 				recvbuf.consume(jsonsize);
 				recvjson += jsonsize;
 				continue;
@@ -341,7 +312,7 @@ void IoMan::inputMain() {
 	CmdMan::CmdRet cmdret;
 	std::unique_lock<std::mutex> ulock;
 
-	printMessage("IoMan::inputMain() begin", debug);
+	printMessage(string(__PRETTY_FUNCTION__) + string(" begin"), debug);
 	inputmutex.lock();
 	while (runinput) {
 		inputmutex.unlock();
@@ -400,7 +371,7 @@ void IoMan::handleInCmdResponse(CmdMan::CmdRet cmdret) {
 		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", error);
+			printMessage("IoMan::inputMain() couldnt send json data\n" + errcode.message() + "\n", debug);
 			return;
 		}
 	}
@@ -408,11 +379,43 @@ void IoMan::handleInCmdResponse(CmdMan::CmdRet cmdret) {
 		printMessage(Json::writeString(wbuilder, cmdret.msg), error);
 	}
 	if (cmdret.type & CmdMan::rettype::close) {
-		/* TODO i dunno */
+		// connection closed, stop network thread and shutdown any operations remaining
+		networkmutex.lock();
+		runnetwork = false;
+		networkmutex.unlock();
+		disconnect();
+		tnetwork.join();
+	}
+	if (cmdret.type & CmdMan::rettype::connect) {
+		ipstring = cmdret.msg["address"].asString();
+		port = cmdret.msg["port"].asUInt();
+		if (connect()) {
+			runnetwork = true;
+			tnetwork = std::thread(&IoMan::networkMain, this);
+
+			// put new commands into global vector
+			localmutex.lock();
+			printMessage(string(__PRETTY_FUNCTION__) + string(" get localmutex"), debug);
+			localinput.push_back("version");
+			cmdman.stateSetConnectionOk();
+			localmutex.unlock();
+			printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"), debug);
+			localcv.notify_all();
+		}
+	}
+	if (cmdret.type & CmdMan::rettype::exit) {
 		mainmutex.lock();
 		runmain = false;
 		mainmutex.unlock();
 	}
+	if (cmdret.nextcommand.size()) {
+		localmutex.lock();
+		printMessage(string(__PRETTY_FUNCTION__) + string(" get localmutex"), debug);
+		localinput.push_back(cmdret.nextcommand);
+		localmutex.unlock();
+		printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"), debug);
+		localcv.notify_all();
+	}
 }
 
 /* loop to handle responses that have been fetched by netMain and possibly add
@@ -423,7 +426,7 @@ void IoMan::responseMain() {
 	CmdMan::CmdRet cmdret;
 	std::unique_lock<std::mutex> ulock;
 
-	printMessage("IoMan::responseMain() begin", debug);
+	printMessage(string(__PRETTY_FUNCTION__) + string(" begin"), debug);
 	responsemutex.lock();
 	while (runresponse) {
 		responsemutex.unlock();
@@ -482,30 +485,19 @@ void IoMan::responseMain() {
 
 void IoMan::handleOutCmdResponse(CmdMan::CmdRet cmdret, vector<string> &toput) {
 	if (cmdret.type & CmdMan::rettype::close) {
-		/* TODO i dunno */
-		mainmutex.lock();
-		runmain = false;
-		mainmutex.unlock();
+		// connection closed, stop network thread and shutdown any operations remaining
+		networkmutex.lock();
+		runnetwork = false;
+		networkmutex.unlock();
+		disconnect();
+		tnetwork.join();
+		if (cmdret.nextcommand.size()) {
+			toput.push_back(cmdret.nextcommand);
+		}
 	}
 	if (cmdret.type & CmdMan::rettype::error) {
-		initmutex.lock();
-		if (versionstatus == off)
-			versionstatus = err;
-		else if (loginstatus == off)
-			loginstatus = err;
-		initmutex.unlock();
-		initcv.notify_all();
 		printMessage(Json::writeString(wbuilder, cmdret.msg), error);
 	}
-	if (cmdret.type & CmdMan::rettype::seton) {
-		initmutex.lock();
-		if (versionstatus == off)
-			versionstatus = on;
-		else
-			loginstatus = on;
-		initmutex.unlock();
-		initcv.notify_all();
-	}
 	if (cmdret.type & CmdMan::rettype::print) {
 		printMessage(Json::writeString(wbuilder, cmdret.msg), normal);
 	}
@@ -514,6 +506,11 @@ void IoMan::handleOutCmdResponse(CmdMan::CmdRet cmdret, vector<string> &toput) {
 			toput.push_back(cmdret.nextcommand);
 		}
 	}
+	if (cmdret.type & CmdMan::rettype::exit) {
+		mainmutex.lock();
+		runmain = false;
+		mainmutex.unlock();
+	}
 }
 
 /* this is the handler that readlines alternative interface will use to process
@@ -544,7 +541,7 @@ void ioman_readlineHandler(char *line) {
 
 /* main user input loop */
 void IoMan::run() {
-	printMessage("IoMan::run() begin", debug);
+	printMessage(string(__PRETTY_FUNCTION__) + string(" begin"), debug);
 	struct pollfd inpipestatus;
 	inpipestatus.fd = STDIN_FILENO;
 	inpipestatus.events = POLLIN;
@@ -563,14 +560,15 @@ void IoMan::run() {
 		if (inpipestatus.revents & POLLIN) {
 			rl_callback_read_char();
 		}
-		if (!connected)
-			break;
-		if (loginstatus == err)
-			break;
 		mainmutex.lock();
 	}
 	mainmutex.unlock();
 
+	// Clean up the terminal
+	rl_set_prompt("");
+	rl_replace_line("", 0);
+	rl_redisplay();
+
 	// Remove the handler
 	rl_callback_handler_remove();
 }

+ 1 - 1
cli/src/machineioman.cpp

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

+ 6 - 13
cli/src/main.cpp

@@ -13,8 +13,7 @@ IoMan *gIOMAN = NULL;
 
 void show_help(char *exec) {
 	std::printf("ccats command line interface\n");
-	std::printf("usage: %s IP [Options]\n", exec);
-	std::printf("IP should be in the format \"xxx.xxx.xxx.xxx\"\n");
+	std::printf("usage: %s [Options]\n", exec);
 }
 
 int main(int argc, char **argv) {
@@ -28,14 +27,8 @@ int main(int argc, char **argv) {
 	const char *file = NULL;
 	IoMan *ioman;
 
-	if (argc < 2 || !std::strncmp(argv[1], "--help", 6)) {
-		show_help(argv[0]);
-		std::cout << desc;
-		return 1;
-	}
-
 	try {
-		store(parse_command_line(argc - 1, argv + 1, desc), vm);
+		store(parse_command_line(argc, argv, desc), vm);
 		notify(vm);
 
 		if (vm.count("help")) {
@@ -61,13 +54,13 @@ int main(int argc, char **argv) {
 	} catch (const bpo::error &ex) {
 		std::fprintf(stderr, "%s\n", ex.what());
 	}
-	std::printf("ip %s machine mode is %d file is %s enablessl is %d verbose is %d\n", argv[1], machine, file ? file : "", usessl, verbose);
+	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(argv[1], usessl, verbose, file);
+		ioman = new BatchIoMan(usessl, verbose, file);
 	} else if (machine) {
-		ioman = new MachineIoMan(argv[1], usessl, verbose);
+		ioman = new MachineIoMan(usessl, verbose);
 	} else {
-		ioman = new UserIoMan(argv[1], usessl, verbose);
+		ioman = new UserIoMan(usessl, verbose);
 	}
 	gIOMAN = ioman;
 	if (ioman->init()) {

+ 137 - 3
cli/src/userioman.cpp

@@ -4,7 +4,7 @@
 #include <readline/readline.h>
 #include <vector>
 
-UserIoMan::UserIoMan(char *ipcstring, bool usessl, bool beverbose) : IoMan(ipcstring, usessl) {
+UserIoMan::UserIoMan(bool usessl, bool beverbose) : IoMan(usessl) {
 	/* setup json stuff */
 	Json::CharReaderBuilder rbuilder;
 	wbuilder.settings_["indentation"] = "";
@@ -15,11 +15,13 @@ UserIoMan::UserIoMan(char *ipcstring, bool usessl, bool beverbose) : IoMan(ipcst
 	printmap["error"] = &UserIoMan::printError;
 	printmap["connect"] = &UserIoMan::printConnect;
 	printmap["help"] = &UserIoMan::printHelp;
+	printmap["extendedstatus"] = &UserIoMan::printExtendedstatus;
 	printmap["status"] = &UserIoMan::printStatus;
 	printmap["disconnect"] = &UserIoMan::printDisconnect;
 	printmap["put"] = &UserIoMan::printPut;
 	printmap["get"] = &UserIoMan::printGet;
 	printmap["list"] = &UserIoMan::printList;
+	printmap["extendedlist"] = &UserIoMan::printExtendedlist;
 	printmap["version"] = &UserIoMan::printVersion;
 	printmap["login"] = &UserIoMan::printLogin;
 	printmap["signup"] = &UserIoMan::printSignup;
@@ -28,8 +30,11 @@ UserIoMan::UserIoMan(char *ipcstring, bool usessl, bool beverbose) : IoMan(ipcst
 	printmap["head"] = &UserIoMan::printHead;
 	printmap["deletefile"] = &UserIoMan::printDeletefile;
 	printmap["deleteme"] = &UserIoMan::printDeleteme;
+	printmap["queue"] = &UserIoMan::printQueue;
+	printmap["dequeue"] = &UserIoMan::printDequeue;
 	printmap["keyfile"] = &UserIoMan::printKeyfile;
 	printmap["closekey"] = &UserIoMan::printClosekey;
+	printmap["notifications"] = &UserIoMan::printNotifications;
 }
 
 UserIoMan::~UserIoMan() { delete reader; }
@@ -72,8 +77,10 @@ void UserIoMan::printMessage(std::string msg, OutMsgType type) {
 }
 
 void UserIoMan::printWelcomeMessage() {
-	std::cout << "please login by entering \"login <username> <password>\" \nor "
-	             "sign up and log in with \"signup <username> <password>\""
+	std::cout << std::endl
+	          << "Please connect to a server via \"connect <ip> <port>\"," << std::endl
+	          << "login by entering \"login <username> <password>\"" << std::endl
+	          << "or sign up and log in with \"signup <username> <password>\"." << std::endl
 	          << std::endl;
 }
 
@@ -106,6 +113,67 @@ void UserIoMan::printHelp(Json::Value root) {
 
 void UserIoMan::printStatus(Json::Value root) { std::cout << "Server reports status: " << root["response"].asString() << std::endl; }
 
+void UserIoMan::printExtendedstatus(Json::Value root) {
+
+	if (!root["accept"].asBool()) {
+		std::cout << "Listing transfers failed. Server reports: " << root["error"].asString() << std::endl;
+	} else {
+		if (!root["transfersclientserver"].empty()) {
+			std::cout << std::endl << "Transfers between clients and server:" << std::endl;
+			/*
+			 * EXAMPLE:
+			 * type      progress  file
+			 * download       99%  foobar.txt
+			 * upload          1%  baz.html
+			 */
+			std::cout << "type      progress  file" << std::endl;
+			for (Json::Value val : root["transfersclientserver"]) {
+				std::string type = val["upload"].asBool() ? "upload" : "download";
+				std::string progress = std::to_string(val["progress"].asInt());
+				std::string file = val["file"].asString();
+
+				char output[21];
+				std::snprintf(output, 21, "%-8s  %7.7s%%  ", type.c_str(), progress.c_str());
+
+				std::cout << output << file << std::endl;
+			}
+		}
+		if (!root["transfersserverserver"].empty()) {
+			std::cout << std::endl << "Transfers between different servers:" << std::endl;
+
+			/*
+			 * EXAMPLE:
+			 * type      progress  method               bytes/sec  file
+			 * download       99%  method0000001          9000.01  foobar.txt
+			 * queued          1%  urgent field             42.00  baz.html
+			 *
+			 * Too long method strings get truncated, unexpectedly high speeds are shown (with less pretty format), e.g.:
+			 *
+			 * download       95%  too long stri  340282346638528859811704183484516925440.00  filename.zip
+			 */
+			std::cout << "type      progress  method               bytes/sec  file" << std::endl;
+			for (Json::Value val : root["transfersserverserver"]) {
+				std::string type = val["type"].asString();
+				std::string progress = std::to_string(val["progress"].asInt());
+				std::string method = val["method"].asString();
+				float speed = val["speed"].asFloat();
+				std::string file = val["file"].asString();
+
+				// size of 80 is just enough for maximum possible float value to fit as string
+				char output[80];
+				std::snprintf(output, 80, "%-8s  %7.7s%%  %-13.13s  %15.2f  ", type.c_str(), progress.c_str(), method.c_str(), speed);
+
+				std::cout << output << file << std::endl;
+			}
+		}
+		if (root["transfersclientserver"].empty() && root["transfersserverserver"].empty()) {
+			std::cout << "No transfers running." << std::endl;
+		} else {
+			std::cout << std::endl;
+		}
+	}
+}
+
 void UserIoMan::printDisconnect(Json::Value root) {
 	if (!root["accept"].asBool()) {
 		std::cout << "Disconnect failed." << std::endl;
@@ -147,6 +215,47 @@ void UserIoMan::printList(Json::Value root) {
 	}
 }
 
+void UserIoMan::printExtendedlist(Json::Value root) {
+	if (!root["accept"].asBool()) {
+		std::cout << "Listing files failed: " << root["error"].asString() << std::endl;
+	} else {
+		if (!root["files"].empty()) {
+			std::cout << "Files stored on server: " << std::endl;
+			/*
+			 * EXAMPLE:
+			 * size (kBytes)  decryptable  file
+			 *       9000.01  yes          foo.txt
+			 *         42.00  no           baz.html
+			 *          0.02  plaintext    bar.zip
+			 */
+			std::cout << "size (kBytes)  decryptable  file" << std::endl;
+			for (Json::Value val : root["files"]) {
+				float size = val["size"].asFloat();
+				std::string encrypted = val["encrypted"].asString();
+				std::string decryptable;
+				if (encrypted == "decryptable") {
+					decryptable = "\033[32myes\033[0m          "; // "yes" in green
+				} else if (encrypted == "undecryptable") {
+					decryptable = "\033[31mno\033[0m           "; // "no" in red
+				} else if (encrypted == "unknown") {
+					decryptable = "unknown      "; // "no" in red
+				} else {
+					decryptable = "plaintext    ";
+				}
+				std::string progress = std::to_string(val["progress"].asInt());
+				std::string file = val["name"].asString();
+
+				char sizeString[44];
+				std::snprintf(sizeString, 44, "%13.2f  ", size);
+
+				std::cout << sizeString << decryptable << file << std::endl;
+			}
+		} else {
+			std::cout << "No files stored on server." << std::endl;
+		}
+	}
+}
+
 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()
@@ -209,3 +318,28 @@ void UserIoMan::printClosekey(Json::Value root) {
 	else
 		std::cout << "Key closed." << std::endl;
 }
+
+void UserIoMan::printQueue(Json::Value root) {
+	if (!root["accept"].asBool())
+		std::cout << "Queueing of file " << root["file"] << " failed. " << root["error"].asString() << std::endl;
+	else
+		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;
+	else
+		std::cout << "File " << root["file"] << " dequeued succesfully" << std::endl;
+}
+
+void UserIoMan::printNotifications(Json::Value root) {
+	if (!root["accept"].asBool()) {
+		std::cout << "Failed to get notifications: " << root["error"].asString() << std::endl;
+	} else {
+		std::cout << "New notifications:" << std::endl;
+		for (Json::Value i : root["messages"])
+			std::cout << "\033[94m" << i.asString() << std::endl;
+		std::cout << "\033[0m " << std::endl;
+	}
+}

+ 44 - 0
cli/test/CMakeLists.txt

@@ -0,0 +1,44 @@
+cmake_minimum_required(VERSION 2.8)
+
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/test)
+
+# Setup testing
+enable_testing()
+
+find_package(GTest REQUIRED)
+find_package(GMock REQUIRED)
+
+find_program(SHELL bash)
+
+include_directories(${Boost_INCLUDE_DIR} ${JSONCPP_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIR} ${GTEST_INCLUDE_DIR})
+
+# Add test cpp file
+add_executable(cmdman_test test/cmdman_test.cpp src/cmdman.cpp src/fileman.cpp src/base64.cpp)
+target_link_libraries(cmdman_test ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} ${Boost_LIBRARIES} ${JSONCPP_LIBRARIES} ${GMOCK_LIBRARIES} ${GTEST_LIBRARY} ${GTEST_MAIN_LIBRARY})
+
+add_executable(cryptotest_gcm test/cryptotest_gcm.c)
+target_link_libraries(cryptotest_gcm ${OPENSSL_LIBRARIES})
+
+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)
+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)
+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)
+
+add_test(
+  NAME cmdman_test
+  COMMAND cmdman_test
+)

+ 31 - 0
cli/test/CmdManForTest.cpp

@@ -0,0 +1,31 @@
+#include "../include/cmdman.h"
+
+class CmdManForTest : public CmdMan {
+public:
+	CmdManForTest(FileMan &fm, void (*dpf)(string)) : CmdMan(fm, dpf) {}
+
+	/*
+	 * initialize state
+	 */
+	void initNotConnected() { currentState = connectionpossible; }
+
+	void initConnected() { currentState = versionpossible; }
+
+	void initVersionChecked() { currentState = loginpossible; }
+
+	void initLoggedIn() { currentState = normal; }
+
+	/*
+	 * check state
+	 */
+
+	bool isNotConnected() { return currentState == connectionpossible; }
+
+	bool isConnected() { return currentState == versionpossible; }
+
+	bool isVersionChecked() { return currentState == loginpossible; }
+
+	bool isLoggedIn() { return currentState == normal; }
+
+	// connectionpossible, versionpossible, doversion, loginpossible, dologin, dosignup, normal, disconnecttoexit, disconnecttoexitearly
+};

+ 3198 - 0
cli/test/cmdman_test.cpp

@@ -0,0 +1,3198 @@
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "../include/cmdman.h"
+#include "../include/global.h"
+#include "fileman_mock.h"
+
+#include "CmdManForTest.cpp"
+
+/* template
+
+TEST(testVersion, Positive) {
+        FileManMock fm;
+        CmdManForTest cm(fm, dummyDebugPrint);
+
+        std::string cmd;
+        std::vector<std::string> args;
+        Json::Value root;
+        CmdMan::CmdRet retval;
+
+        // prepare root/cmd/args
+        cmd = "";
+        args = {"", ""};
+        root[""] = "";
+
+        // stick into cmdman
+        cm.execute(cmd, args);
+        cm.handle(root);
+
+        // check things
+        EXPECT_EQ(retval.type, sometypes);
+        EXPECT_EQ(retval.nextcommand, somestring);
+        EXPECT_TRUE(retval.msg[""].asBool());
+        EXPECT_FALSE(retval.msg[""].asBool());
+        ON_CALL(fm, methodname()).WillByDefault(testing::Return(true));
+}
+
+*/
+
+/* holds all tests */
+namespace {
+
+void dummyDebugPrint(string x) {
+	// do nothing
+}
+
+/*
+ * =====================================
+ * tests for connect
+ */
+TEST(testConnect, TooFewArgs) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "connect";
+	args = {};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "connect");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isNotConnected());
+}
+
+TEST(testConnect, Positive_NoPortSpecified) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "connect";
+	args = {"1.222.33.4"};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	cm.stateSetConnectionOk();
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::connect);
+	EXPECT_EQ(retvalCmd.msg["address"].asString(), "1.222.33.4");
+	EXPECT_EQ(retvalCmd.msg["port"].asString(), "1234");
+
+	EXPECT_TRUE(cm.isConnected());
+}
+
+TEST(testConnect, Positive_PortSpecified) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+
+	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();
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::connect);
+	EXPECT_EQ(retvalCmd.msg["address"].asString(), "1.222.33.4");
+	EXPECT_EQ(retvalCmd.msg["port"].asString(), "1337");
+
+	EXPECT_TRUE(cm.isConnected());
+}
+
+TEST(testConnect, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+
+	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::connect);
+	EXPECT_EQ(retvalCmd.msg["address"].asString(), "1.222.33.4");
+	EXPECT_EQ(retvalCmd.msg["port"].asString(), "1337");
+
+	EXPECT_TRUE(cm.isNotConnected());
+}
+
+/* =====================================
+ * tests for
+ * version check, signup, login
+ */
+
+TEST(testVersion, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initConnected();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "version";
+	args = {};
+	root["major"] = protocolMajorVersion;
+	root["minor"] = protocolMinorVersion;
+	root["accept"] = true;
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["major"], protocolMajorVersion);
+	EXPECT_EQ(retvalCmd.msg["minor"], protocolMinorVersion);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "version");
+	EXPECT_EQ(retvalHdl.msg["serverversion"].asString(), std::to_string(protocolMajorVersion) + "." + std::to_string(protocolMinorVersion));
+	EXPECT_EQ(retvalHdl.msg["clientversion"].asString(), std::to_string(protocolMajorVersion) + "." + std::to_string(protocolMinorVersion));
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+
+	EXPECT_TRUE(cm.isVersionChecked());
+}
+
+TEST(testVersion, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initConnected();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+	int servermajor, serverminor;
+
+	// prepare root/cmd/args
+	cmd = "version";
+	args = {};
+	servermajor = (protocolMajorVersion == 0) ? 42 : 0;
+	serverminor = (protocolMinorVersion == 3) ? 42 : 3;
+	root["major"] = servermajor;
+	root["minor"] = serverminor;
+	root["accept"] = false;
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["major"], protocolMajorVersion);
+	EXPECT_EQ(retvalCmd.msg["minor"], protocolMinorVersion);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error | CmdMan::close);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "version");
+	EXPECT_EQ(retvalHdl.msg["serverversion"].asString(), std::to_string(servermajor) + "." + std::to_string(serverminor));
+	EXPECT_EQ(retvalHdl.msg["clientversion"].asString(), std::to_string(protocolMajorVersion) + "." + std::to_string(protocolMinorVersion));
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+
+	EXPECT_TRUE(cm.isNotConnected());
+}
+
+TEST(testVersion, AlreadyLoggedIn) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args
+	cmd = "version";
+	args = {};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	// TODO: check some more things maybe
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testLogin, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initVersionChecked();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "login";
+	args = {"usernem", "paswod"};
+	root["accept"] = true;
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_TRUE(retvalCmd.msg["login"].asBool());
+	EXPECT_EQ(retvalCmd.msg["user"].asString(), "usernem");
+	EXPECT_EQ(retvalCmd.msg["pass"].asString(), "paswod");
+	EXPECT_FALSE(retvalCmd.msg["cancel"].asBool());
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "login");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testLogin, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initVersionChecked();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "login";
+	args = {"usernem", "paswod"};
+	root["accept"] = false;
+	root["error"] = "fancy error string";
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_TRUE(retvalCmd.msg["login"].asBool());
+	EXPECT_EQ(retvalCmd.msg["user"].asString(), "usernem");
+	EXPECT_EQ(retvalCmd.msg["pass"].asString(), "paswod");
+	EXPECT_FALSE(retvalCmd.msg["cancel"].asBool());
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error | CmdMan::close);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "login");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("fancy error string"));
+
+	EXPECT_TRUE(cm.isNotConnected());
+}
+
+TEST(testLogin, TooFewArgs) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initVersionChecked();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "login";
+	args = {"usernem"};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "login");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isVersionChecked());
+}
+
+TEST(testLogin, AlreadyLoggedIn) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare root/cmd/args
+	cmd = "login";
+	args = {"usernem", "paswod"};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "login");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testLogin, TwoRequestsInRow) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initVersionChecked();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd1;
+	CmdMan::CmdRet retvalCmd2;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "login";
+	args = {"usernem", "paswod"};
+	root["accept"] = true;
+
+	// stick into cmdman
+	retvalCmd1 = cm.execute(cmd, args);
+	retvalCmd2 = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd1.type, CmdMan::send);
+	EXPECT_TRUE(retvalCmd1.msg["login"].asBool());
+	EXPECT_EQ(retvalCmd1.msg["user"].asString(), "usernem");
+	EXPECT_EQ(retvalCmd1.msg["pass"].asString(), "paswod");
+	EXPECT_FALSE(retvalCmd1.msg["cancel"].asBool());
+
+	EXPECT_EQ(retvalCmd2.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd2.msg["command"].asString(), "login");
+	EXPECT_FALSE(retvalCmd2.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd2.msg["error"].asString(), "");
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "login");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testSignup, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initVersionChecked();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "signup";
+	args = {"usernem", "paswod"};
+	root["accept"] = true;
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_FALSE(retvalCmd.msg["login"].asBool());
+	EXPECT_EQ(retvalCmd.msg["user"].asString(), "usernem");
+	EXPECT_EQ(retvalCmd.msg["pass"].asString(), "paswod");
+	EXPECT_FALSE(retvalCmd.msg["cancel"].asBool());
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "signup");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testSignup, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initVersionChecked();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "signup";
+	args = {"usernem", "paswod"};
+	root["accept"] = false;
+	root["error"] = "fancy error string";
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_FALSE(retvalCmd.msg["login"].asBool());
+	EXPECT_EQ(retvalCmd.msg["user"].asString(), "usernem");
+	EXPECT_EQ(retvalCmd.msg["pass"].asString(), "paswod");
+	EXPECT_FALSE(retvalCmd.msg["cancel"].asBool());
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "signup");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("fancy error string"));
+
+	EXPECT_TRUE(cm.isVersionChecked());
+}
+
+TEST(testSignup, TooFewArgs) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initVersionChecked();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "signup";
+	args = {"usernem"};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "signup");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isVersionChecked());
+}
+
+/*
+ * =====================================
+ * tests for disconnect and exit
+ */
+TEST(testDisconnect, LoggedIn) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "disconnect";
+	args = {};
+	root["command"] = "close";
+	root["response"] = "bye";
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "close");
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::close | CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "disconnect");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+
+	EXPECT_TRUE(cm.isNotConnected());
+}
+
+TEST(testDisconnect, NotLoggedIn) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initVersionChecked();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "disconnect";
+	args = {};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send | CmdMan::close);
+	EXPECT_FALSE(retvalCmd.msg["login"].asBool());
+	EXPECT_EQ(retvalCmd.msg["user"].asString(), "");
+	EXPECT_EQ(retvalCmd.msg["pass"].asString(), "");
+	EXPECT_TRUE(retvalCmd.msg["cancel"].asBool());
+
+	EXPECT_TRUE(cm.isNotConnected());
+}
+
+TEST(testExit, LoggedIn) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmdExit;
+	CmdMan::CmdRet retvalCmdDisconnect;
+	CmdMan::CmdRet retvalHdl;
+	CmdMan::CmdRet retvalLastExit;
+
+	// prepare root/cmd/args
+	cmd = "exit";
+	args = {};
+	root["command"] = "close";
+	root["response"] = "bye";
+
+	// stick into cmdman and check things
+	retvalCmdExit = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmdExit.type, CmdMan::none);
+	EXPECT_EQ(retvalCmdExit.nextcommand, "disconnect");
+
+	retvalCmdDisconnect = cm.execute("disconnect", args);
+
+	EXPECT_EQ(retvalCmdDisconnect.type, CmdMan::send);
+	EXPECT_EQ(retvalCmdDisconnect.msg["command"].asString(), "close");
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::close | CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "disconnect");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.nextcommand, "exit");
+
+	EXPECT_TRUE(cm.isNotConnected());
+
+	retvalLastExit = cm.execute("exit", args);
+
+	EXPECT_EQ(retvalLastExit.type, CmdMan::exit);
+	EXPECT_EQ(retvalLastExit.msg["command"].asString(), "exit");
+}
+
+TEST(testExit, NotLoggedIn) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initVersionChecked();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmdExit;
+	CmdMan::CmdRet retvalCmdDisconnect;
+	CmdMan::CmdRet retvalHdl;
+	CmdMan::CmdRet retvalLastExit;
+
+	// prepare root/cmd/args
+	cmd = "exit";
+	args = {};
+	root["command"] = "close";
+	root["response"] = "bye";
+
+	// stick into cmdman and check things
+	retvalCmdExit = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmdExit.type, CmdMan::none);
+	EXPECT_EQ(retvalCmdExit.nextcommand, "disconnect");
+
+	retvalCmdDisconnect = cm.execute("disconnect", args);
+
+	EXPECT_EQ(retvalCmdDisconnect.type, CmdMan::send | CmdMan::close);
+	EXPECT_FALSE(retvalCmdDisconnect.msg["login"].asBool());
+	EXPECT_EQ(retvalCmdDisconnect.msg["user"].asString(), "");
+	EXPECT_EQ(retvalCmdDisconnect.msg["pass"].asString(), "");
+	EXPECT_TRUE(retvalCmdDisconnect.msg["cancel"].asBool());
+	EXPECT_EQ(retvalCmdDisconnect.nextcommand, "exit");
+
+	EXPECT_TRUE(cm.isNotConnected());
+
+	retvalLastExit = cm.execute("exit", args);
+
+	EXPECT_EQ(retvalLastExit.type, CmdMan::exit);
+	EXPECT_EQ(retvalLastExit.msg["command"].asString(), "exit");
+}
+
+TEST(testExit, NotConnected) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare root/cmd/args
+	cmd = "exit";
+	args = {};
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::exit);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "exit");
+}
+
+/* =====================================
+ * tests for put[data]
+ */
+
+TEST(testPut, TooFewArgs) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "put";
+	args = {};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "put");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testPut, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "put";
+	args = {"../some/nice/filepath/filename.txt"};
+	root["command"] = "put";
+	root["file"] = "filename.txt";
+	root["accept"] = true;
+
+	EXPECT_CALL(fm, openPut(testing::_)).WillOnce(testing::Return(true));
+	ON_CALL(fm, getPutSize()).WillByDefault(testing::Return(5));
+	ON_CALL(fm, getPutChunks()).WillByDefault(testing::Return(1));
+	ON_CALL(fm, getPutName()).WillByDefault(testing::Return("filename.txt"));
+
+	EXPECT_CALL(fm, cancelGet).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "put");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "filename.txt");
+	EXPECT_EQ(retvalCmd.msg["size"].asInt(), 5);
+	EXPECT_EQ(retvalCmd.msg["chunks"].asInt(), 1);
+
+	EXPECT_CALL(fm, isPutting()).WillOnce(testing::Return(true));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print | CmdMan::send);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "put");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_EQ(retvalHdl.nextcommand, "putdata");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testPutdata, Positive_LastChunk) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	/**logically continuation of TEST(testPut, Positive)**/
+
+	// prepare root/cmd/args
+	cmd = "putdata";
+	args = {};
+	root["command"] = "putdata";
+	root["file"] = "filename.txt";
+	root["received"] = 0;
+	root["cancel"] = false;
+
+	ON_CALL(fm, getPutName()).WillByDefault(testing::Return("filename.txt"));
+	ON_CALL(fm, readBase64()).WillByDefault(testing::Return("cool Base64 string"));
+	ON_CALL(fm, getPutRemainingChunks()).WillByDefault(testing::Return(0));
+	EXPECT_CALL(fm, isPutting()).WillOnce(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelPut).Times(0); // not called
+	EXPECT_CALL(fm, closePut);
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "putdata");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "filename.txt");
+	EXPECT_EQ(retvalCmd.msg["data"].asString(), "cool Base64 string");
+	EXPECT_EQ(retvalCmd.msg["remaining"].asInt(), 0);
+	EXPECT_FALSE(retvalCmd.msg["cancel"].asBool());
+
+	EXPECT_CALL(fm, isPutting()).WillOnce(testing::Return(true));
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "putdata");
+	EXPECT_FALSE(retvalHdl.msg["cancel"].asBool());
+	EXPECT_EQ(retvalHdl.msg["speed"].asInt(), 0);
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_EQ(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testPutdata, Positive_ChunksRemaining) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "putdata";
+	args = {};
+	root["command"] = "putdata";
+	root["file"] = "filename.txt";
+	root["received"] = 42;
+	root["cancel"] = false;
+
+	ON_CALL(fm, getPutName()).WillByDefault(testing::Return("filename.txt"));
+	ON_CALL(fm, readBase64()).WillByDefault(testing::Return("cool Base64 string"));
+	ON_CALL(fm, getPutRemainingChunks()).WillByDefault(testing::Return(42));
+	EXPECT_CALL(fm, isPutting()).WillOnce(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelGet).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "putdata");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "filename.txt");
+	EXPECT_EQ(retvalCmd.msg["data"].asString(), "cool Base64 string");
+	EXPECT_EQ(retvalCmd.msg["remaining"].asInt(), 42);
+	EXPECT_FALSE(retvalCmd.msg["cancel"].asBool());
+
+	EXPECT_CALL(fm, isPutting()).WillOnce(testing::Return(true));
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print | CmdMan::send);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "putdata");
+	EXPECT_FALSE(retvalHdl.msg["cancel"].asBool());
+	EXPECT_EQ(retvalHdl.msg["speed"].asInt(), 0);
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_EQ(retvalHdl.msg["error"].asString(), "");
+	EXPECT_EQ(retvalHdl.nextcommand, "putdata");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testPut, Negative_FileNotExisting) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "put";
+	args = {"../some/nice/filepath/filename.txt"};
+
+	EXPECT_CALL(fm, openPut(testing::_)).WillOnce(testing::Return(false));
+	ON_CALL(fm, pathToFilename("../some/nice/filepath/filename.txt")).WillByDefault(testing::Return("filename.txt"));
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "put");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "filename.txt");
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testPut, Negative_ServerError) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	/* tests the handling of server reply only,
+	 * initial request assumed as correct (tested in other tests) */
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	root["command"] = "put";
+	root["file"] = "filename.txt";
+	root["accept"] = false;
+	root["error"] = "foobar";
+
+	ON_CALL(fm, getPutSize()).WillByDefault(testing::Return(5));
+	ON_CALL(fm, getPutChunks()).WillByDefault(testing::Return(1));
+	ON_CALL(fm, getPutName()).WillByDefault(testing::Return("filename.txt"));
+
+	EXPECT_CALL(fm, cancelPut);
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "put");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("foobar"));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testPut, Negative_ServerWrongFile) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	/* tests the handling of server reply only,
+	 * initial request assumed as correct (tested in other tests) */
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	root["command"] = "put";
+	root["file"] = "completely_different_filename.txt";
+	root["accept"] = true;
+
+	ON_CALL(fm, getPutSize()).WillByDefault(testing::Return(5));
+	ON_CALL(fm, getPutChunks()).WillByDefault(testing::Return(1));
+	ON_CALL(fm, getPutName()).WillByDefault(testing::Return("filename.txt"));
+
+	EXPECT_CALL(fm, isPutting()).WillOnce(testing::Return(true));
+	EXPECT_CALL(fm, cancelPut);
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "put");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testPutdata, Negative_ServerError) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	/* tests the handling of server reply only,
+	 * initial request assumed as correct (tested in other tests) */
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	root["command"] = "putdata";
+	root["file"] = "filename.txt";
+	root["received"] = 42;
+	root["cancel"] = true;
+	root["error"] = "cool foobar";
+
+	ON_CALL(fm, getPutName()).WillByDefault(testing::Return("filename.txt"));
+	ON_CALL(fm, getPutRemainingChunks()).WillByDefault(testing::Return(42));
+	ON_CALL(fm, isPutting()).WillByDefault(testing::Return(true));
+	retvalHdl = cm.handle(root);
+
+	EXPECT_CALL(fm, cancelPut);
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "putdata");
+	EXPECT_TRUE(retvalHdl.msg["cancel"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("cool foobar"));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testPutdata, Negative_ServerWrongFile) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	/* tests the handling of server reply only,
+	 * initial request assumed as correct (tested in other tests) */
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	root["command"] = "putdata";
+	root["file"] = "completely_different_filename.txt";
+	root["received"] = 42;
+	root["cancel"] = false;
+
+	ON_CALL(fm, getPutName()).WillByDefault(testing::Return("filename.txt"));
+	ON_CALL(fm, getPutRemainingChunks()).WillByDefault(testing::Return(42));
+	EXPECT_CALL(fm, isPutting()).WillOnce(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelPut);
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "putdata");
+	EXPECT_TRUE(retvalHdl.msg["cancel"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testPutdata, Negative_ServerWrongChunkNumber) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	/* tests the handling of server reply only,
+	 * initial request assumed as correct (tested in other tests) */
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	root["command"] = "putdata";
+	root["file"] = "filename.txt";
+	root["received"] = 23;
+	root["cancel"] = false;
+
+	ON_CALL(fm, getPutName()).WillByDefault(testing::Return("filename.txt"));
+	ON_CALL(fm, getPutRemainingChunks()).WillByDefault(testing::Return(42));
+	EXPECT_CALL(fm, isPutting()).WillOnce(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelPut);
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "putdata");
+	EXPECT_TRUE(retvalHdl.msg["cancel"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testPut, Negative_NoFileOpen_ServerMessage) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	root["command"] = "put";
+	root["file"] = "filename.txt";
+	root["accept"] = true;
+
+	// handle reply
+	EXPECT_CALL(fm, isPutting()).WillOnce(testing::Return(false));
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "put");
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testPutdata, Negative_NoFileOpen_ServerMessage) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	root["command"] = "putdata";
+	root["file"] = "filename.txt";
+	root["received"] = 23;
+	root["cancel"] = false;
+
+	// handle reply
+	EXPECT_CALL(fm, isPutting()).WillOnce(testing::Return(false));
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "putdata");
+	//	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt"); // no guarantee should be given...
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testPutdata, Negative_NoFileOpen_UserRequest) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	cmd = "putdata";
+	args = {};
+
+	EXPECT_CALL(fm, isPutting()).WillOnce(testing::Return(false));
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "putdata");
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+/* =====================================
+ * tests for head
+ */
+TEST(testHead, TooFewArgs) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args
+	cmd = "head";
+	args = {};
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "head");
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testHead, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "head";
+	args = {"filename.txt"};
+	root["command"] = "head";
+	root["file"] = "filename.txt";
+	root["accept"] = true;
+	root["data"] = "this string is 4 bytes (obviously)";
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "head");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "filename.txt");
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "head");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_EQ(retvalHdl.msg["data"].asString(), "this string is 4 bytes (obviously)");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testHead, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "head";
+	args = {"filename.txt"};
+	root["command"] = "head";
+	root["file"] = "filename.txt";
+	root["accept"] = false;
+	root["error"] = "this is a fancy error message";
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "head");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "filename.txt");
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "head");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("this is a fancy error message"));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+/* =====================================
+ * tests for get[data]
+ */
+
+TEST(testGet, TooFewArgs) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "get";
+	args = {};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "get");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testGet, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "get";
+	args = {"../some/nice/filepath/filename.txt"};
+	root["command"] = "get";
+	root["file"] = "filename.txt";
+	root["accept"] = true;
+	root["chunks"] = 42;
+
+	EXPECT_CALL(fm, openGet("../some/nice/filepath/filename.txt")).WillOnce(testing::Return(true));
+	ON_CALL(fm, getGetName()).WillByDefault(testing::Return("filename.txt"));
+
+	EXPECT_CALL(fm, cancelGet).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "get");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "filename.txt");
+
+	EXPECT_CALL(fm, isGetting()).WillOnce(testing::Return(true));
+	EXPECT_CALL(fm, setGetChunks(42));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print | CmdMan::send);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "get");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_EQ(retvalHdl.nextcommand, "getdata");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testGetdata, Positive_LastChunk) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	/**logically continuation of TEST(testGet, Positive)**/
+
+	// prepare root/cmd/args
+	cmd = "getdata";
+	args = {};
+	root["command"] = "getdata";
+	root["file"] = "filename.txt";
+	root["remaining"] = 0;
+	root["cancel"] = false;
+	root["data"] = "cool Base64 string";
+
+	ON_CALL(fm, getGetName()).WillByDefault(testing::Return("filename.txt"));
+	EXPECT_CALL(fm, writeBase64("cool Base64 string"));
+	ON_CALL(fm, getGetRemainingChunks()).WillByDefault(testing::Return(0));
+	EXPECT_CALL(fm, isGetting()).WillOnce(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelGet).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "getdata");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "filename.txt");
+	EXPECT_EQ(retvalCmd.msg["chunk"].asInt(), 0);
+	EXPECT_FALSE(retvalCmd.msg["cancel"].asBool());
+
+	EXPECT_CALL(fm, isGetting()).WillOnce(testing::Return(true));
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "getdata");
+	EXPECT_FALSE(retvalHdl.msg["cancel"].asBool());
+	EXPECT_EQ(retvalHdl.msg["speed"].asInt(), 0);
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_EQ(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testGetdata, Positive_ChunksRemaining) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "getdata";
+	args = {};
+	root["command"] = "getdata";
+	root["file"] = "filename.txt";
+	root["remaining"] = 42;
+	root["cancel"] = false;
+	root["data"] = "cool Base64 string";
+
+	ON_CALL(fm, getGetName()).WillByDefault(testing::Return("filename.txt"));
+	EXPECT_CALL(fm, writeBase64("cool Base64 string"));
+	ON_CALL(fm, getGetRemainingChunks()).WillByDefault(testing::Return(42));
+	EXPECT_CALL(fm, isGetting()).WillOnce(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelGet).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "getdata");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "filename.txt");
+	EXPECT_EQ(retvalCmd.msg["chunk"].asInt(), 42);
+	EXPECT_FALSE(retvalCmd.msg["cancel"].asBool());
+
+	EXPECT_CALL(fm, isGetting()).WillOnce(testing::Return(true));
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print | CmdMan::send);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "getdata");
+	EXPECT_FALSE(retvalHdl.msg["cancel"].asBool());
+	EXPECT_EQ(retvalHdl.msg["speed"].asInt(), 0);
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_EQ(retvalHdl.msg["error"].asString(), "");
+	EXPECT_EQ(retvalHdl.nextcommand, "getdata");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testGet, Negative_FileAlreadyExisting) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "get";
+	args = {"../some/nice/filepath/filename.txt"};
+
+	EXPECT_CALL(fm, openGet(testing::_)).WillOnce(testing::Return(false));
+	ON_CALL(fm, pathToFilename("../some/nice/filepath/filename.txt")).WillByDefault(testing::Return("filename.txt"));
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "get");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "filename.txt");
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testGet, Negative_ServerError) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	/* tests the handling of server reply only,
+	 * initial request assumed as correct (tested in other tests) */
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	root["command"] = "get";
+	root["file"] = "filename.txt";
+	root["accept"] = false;
+	root["error"] = "foobar";
+
+	ON_CALL(fm, getGetName()).WillByDefault(testing::Return("filename.txt"));
+	EXPECT_CALL(fm, cancelGet());
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "get");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testGet, Negative_ServerWrongFile) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	/* tests the handling of server reply only,
+	 * initial request assumed as correct (tested in other tests) */
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	root["command"] = "get";
+	root["file"] = "completely_different_filename.txt";
+	root["accept"] = true;
+
+	ON_CALL(fm, getGetName()).WillByDefault(testing::Return("filename.txt"));
+
+	EXPECT_CALL(fm, isGetting()).WillOnce(testing::Return(true));
+	EXPECT_CALL(fm, cancelGet);
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "get");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testGetdata, Negative_ServerError) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	/* tests the handling of server reply only,
+	 * initial request assumed as correct (tested in other tests) */
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	root["command"] = "getdata";
+	root["file"] = "filename.txt";
+	root["remaining"] = 42;
+	root["cancel"] = true;
+	root["error"] = "fancy foobar";
+
+	ON_CALL(fm, getGetName()).WillByDefault(testing::Return("filename.txt"));
+	ON_CALL(fm, getGetRemainingChunks()).WillByDefault(testing::Return(42));
+	ON_CALL(fm, isGetting()).WillByDefault(testing::Return(true));
+	retvalHdl = cm.handle(root);
+
+	EXPECT_CALL(fm, cancelGet);
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "getdata");
+	EXPECT_TRUE(retvalHdl.msg["cancel"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("fancy foobar"));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testGetdata, Negative_ServerWrongFile) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	/* tests the handling of server reply only,
+	 * initial request assumed as correct (tested in other tests) */
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	root["command"] = "getdata";
+	root["file"] = "completely_different_filename.txt";
+	root["remaining"] = 42;
+	root["cancel"] = false;
+
+	ON_CALL(fm, getGetName()).WillByDefault(testing::Return("filename.txt"));
+	ON_CALL(fm, getGetRemainingChunks()).WillByDefault(testing::Return(42));
+	ON_CALL(fm, isGetting()).WillByDefault(testing::Return(true));
+	retvalHdl = cm.handle(root);
+
+	EXPECT_CALL(fm, cancelGet);
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "getdata");
+	EXPECT_TRUE(retvalHdl.msg["cancel"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testGetdata, Negative_ServerWrongChunkNumber) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	/* tests the handling of server reply only,
+	 * initial request assumed as correct (tested in other tests) */
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	root["command"] = "getdata";
+	root["file"] = "filename.txt";
+	root["remaining"] = 23;
+	root["cancel"] = false;
+
+	ON_CALL(fm, getGetName()).WillByDefault(testing::Return("filename.txt"));
+	ON_CALL(fm, getGetRemainingChunks()).WillByDefault(testing::Return(42));
+	ON_CALL(fm, isGetting()).WillByDefault(testing::Return(true));
+	retvalHdl = cm.handle(root);
+
+	EXPECT_CALL(fm, cancelGet);
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "getdata");
+	EXPECT_TRUE(retvalHdl.msg["cancel"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "filename.txt");
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+/* =====================================
+ * tests for list[data]
+ */
+
+TEST(testList, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "list";
+	args = {};
+	root["command"] = "list";
+	root["accept"] = true;
+	root["items"] = 92;
+	root["chunks"] = 2;
+
+	EXPECT_CALL(fm, openList(false)).WillOnce(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelList).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "list");
+
+	EXPECT_CALL(fm, isListingSimple()).WillOnce(testing::Return(true));
+	EXPECT_CALL(fm, setListChunks(2));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::send);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "list");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.nextcommand, "listdata");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testListdata, Positive_ChunksRemaining) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, namesArr;
+	vector<string> namevec = {"blue.txt", "red.pdf", "green.sh", "yellow.tgz"};
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "listdata";
+	args = {};
+	root["command"] = "listdata";
+	root["cancel"] = false;
+	root["remaining"] = 3;
+	for (string s : namevec)
+		namesArr.append(s);
+	root["names"] = namesArr;
+
+	EXPECT_CALL(fm, getListRemainingChunks).Times(2).WillRepeatedly(testing::Return(3));
+	EXPECT_CALL(fm, isListingSimple).Times(2).WillRepeatedly(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelList).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "listdata");
+	EXPECT_EQ(retvalCmd.msg["chunk"].asInt(), 3);
+	EXPECT_EQ(retvalCmd.msg["cancel"].asBool(), false);
+
+	vector<Json::Value> vec{Json::Value("blue.txt"), Json::Value("red.pdf"), Json::Value("green.sh"), Json::Value("yellow.tgz")};
+	EXPECT_CALL(fm, putListData(vec));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::send);
+	EXPECT_EQ(retvalHdl.nextcommand, "listdata");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testListdata, Positive_LastChunk) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, namesArr;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "listdata";
+	args = {};
+	root["command"] = "listdata";
+	root["cancel"] = false;
+	root["remaining"] = 0;
+	namesArr.append("magenta.vcd");
+	root["names"] = namesArr;
+
+	EXPECT_CALL(fm, getListRemainingChunks).Times(2).WillRepeatedly(testing::Return(0));
+	EXPECT_CALL(fm, isListingSimple).Times(2).WillRepeatedly(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelList).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "listdata");
+	EXPECT_EQ(retvalCmd.msg["chunk"].asInt(), 0);
+	EXPECT_EQ(retvalCmd.msg["cancel"].asBool(), false);
+
+	vector<Json::Value> vec{Json::Value("magenta.vcd")};
+	EXPECT_CALL(fm, putListData(vec));
+	EXPECT_CALL(fm, closeList);
+	vector<Json::Value> allFiles{"blue.txt", "red.pdf", "green.sh", "yellow.tgz", "cyan.exe", "white", "black", "magenta.vcd"};
+	EXPECT_CALL(fm, getListData).WillOnce(testing::Return(allFiles));
+
+	retvalHdl = cm.handle(root);
+	// convert returned json value into vector
+	vector<string> retval_msg_names;
+	for (Json::Value i : retvalHdl.msg["names"])
+		retval_msg_names.push_back(i.asString());
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "list");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+	vector<string> allFilesStringVec{"blue.txt", "red.pdf", "green.sh", "yellow.tgz", "cyan.exe", "white", "black", "magenta.vcd"};
+	EXPECT_EQ(retval_msg_names, allFilesStringVec);
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testList, Negative_ListEmpty) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "list";
+	args = {};
+	root["command"] = "list";
+	root["accept"] = true;
+	root["items"] = 0;
+
+	// stick into cmdman and check things
+	EXPECT_CALL(fm, openList(false)).WillOnce(testing::Return(true));
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "list");
+
+	EXPECT_CALL(fm, closeList);
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "list");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("There are no files stored on the server."));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testListdata, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	/* tests the handling of server reply only,
+	 * initial request assumed as correct (tested in other tests) */
+
+	Json::Value root;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	root["command"] = "listdata";
+	root["cancel"] = true;
+	root["error"] = "some foobar";
+
+	EXPECT_CALL(fm, cancelList);
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "list");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("some foobar"));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testList, Negative_AlreadyListing) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "list";
+	args = {};
+
+	EXPECT_CALL(fm, openList(false)).WillOnce(testing::Return(false));
+
+	EXPECT_CALL(fm, cancelList).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "list");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testList, Negative_NotListing_ServerRequest) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	Json::Value root, namesArr;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	root["command"] = "listdata";
+	root["cancel"] = false;
+	root["remaining"] = 0;
+	namesArr.append("magenta.vcd");
+	root["names"] = namesArr;
+
+	EXPECT_CALL(fm, cancelList).Times(0);
+	EXPECT_CALL(fm, putListData).Times(0);
+	EXPECT_CALL(fm, closeList).Times(0);
+	EXPECT_CALL(fm, getListData).Times(0);
+	EXPECT_CALL(fm, isListingSimple).WillOnce(testing::Return(false));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "list");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testListdata, Negative_NotListing_UserRequest) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare root/cmd/args
+	cmd = "listdata";
+	args = {};
+
+	EXPECT_CALL(fm, isListingSimple).WillOnce(testing::Return(false));
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "list");
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+	EXPECT_EQ(retvalCmd.msg["accept"].asBool(), false);
+}
+
+TEST(testListdata, Negative_NotListing_ServerRequest) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, namesArr;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	root["command"] = "listdata";
+	root["cancel"] = false;
+	root["remaining"] = 0;
+	namesArr.append("magenta.vcd");
+	root["names"] = namesArr;
+
+	EXPECT_CALL(fm, cancelList).Times(0);
+	EXPECT_CALL(fm, isListingSimple).WillOnce(testing::Return(false));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "list");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+/* =====================================
+ * tests for extendedlist[data]
+ */
+
+TEST(testExtendedlist, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "extendedlist";
+	args = {};
+	root["command"] = "extendedlist";
+	root["accept"] = true;
+	root["items"] = 92;
+	root["chunks"] = 2;
+
+	EXPECT_CALL(fm, openList(true)).WillOnce(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelList).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedlist");
+
+	EXPECT_CALL(fm, isListingExtended()).WillOnce(testing::Return(true));
+	EXPECT_CALL(fm, setListChunks(2));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::send);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedlist");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.nextcommand, "extendedlistdata");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedlistdata, Positive_ChunksRemaining) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, files, f1, f2, namesArr;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	f1["name"] = "blue.txt";
+	f1["size"] = 42.0f;
+	f1["head"] = "some head 1";
+	files.append(f1);
+	f2["name"] = "green.pdf";
+	f2["size"] = 9.0f;
+	f2["head"] = "some head 2";
+	files.append(f2);
+
+	cmd = "extendedlistdata";
+	args = {};
+	root["command"] = "extendedlistdata";
+	root["cancel"] = false;
+	root["remaining"] = 3;
+	root["files"] = files;
+
+	EXPECT_CALL(fm, getListRemainingChunks).Times(2).WillRepeatedly(testing::Return(3));
+	EXPECT_CALL(fm, isListingExtended).Times(2).WillRepeatedly(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelList).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedlistdata");
+	EXPECT_EQ(retvalCmd.msg["chunk"].asInt(), 3);
+	EXPECT_EQ(retvalCmd.msg["cancel"].asBool(), false);
+
+	vector<Json::Value> vec{f1, f2};
+	EXPECT_CALL(fm, putListData(vec));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::send);
+	EXPECT_EQ(retvalHdl.nextcommand, "extendedlistdata");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedlistdata, Positive_LastChunk) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, files, f1, f2, f3, inputfile;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare file json/root/cmd/args
+
+	inputfile["name"] = "magenta.vcd";
+	inputfile["size"] = 9000.001f;
+	inputfile["head"] = "some head 3";
+	files.append(inputfile);
+
+	cmd = "extendedlistdata";
+	args = {};
+	root["command"] = "extendedlistdata";
+	root["cancel"] = false;
+	root["remaining"] = 0;
+	root["files"] = files;
+
+	EXPECT_CALL(fm, getListRemainingChunks).Times(2).WillRepeatedly(testing::Return(0));
+	EXPECT_CALL(fm, isListingExtended).Times(2).WillRepeatedly(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelList).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedlistdata");
+	EXPECT_EQ(retvalCmd.msg["chunk"].asInt(), 0);
+	EXPECT_EQ(retvalCmd.msg["cancel"].asBool(), false);
+
+	vector<Json::Value> vec{inputfile};
+	EXPECT_CALL(fm, putListData(vec));
+	EXPECT_CALL(fm, closeList);
+	// output of fileman
+	f1["name"] = "blue.txt";
+	f1["size"] = 42.0f;
+	f1["encrypted"] = "unencrypted";
+	f2["name"] = "green.pdf";
+	f2["size"] = 9.0f;
+	f2["encrypted"] = "decryptable";
+	f3["name"] = "magenta.vcd";
+	f3["size"] = 9000.001f;
+	f3["encrypted"] = "undecryptable";
+	vector<Json::Value> allFiles{f1, f2, f3};
+	EXPECT_CALL(fm, getListData).WillOnce(testing::Return(allFiles));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedlist");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["files"][0]["name"].asString(), "blue.txt");
+	EXPECT_EQ(retvalHdl.msg["files"][0]["encrypted"].asString(), "unencrypted");
+	EXPECT_EQ(retvalHdl.msg["files"][0]["size"].asFloat(), 42.0f);
+	EXPECT_EQ(retvalHdl.msg["files"][1]["name"].asString(), "green.pdf");
+	EXPECT_EQ(retvalHdl.msg["files"][1]["encrypted"].asString(), "decryptable");
+	EXPECT_EQ(retvalHdl.msg["files"][1]["size"].asFloat(), 9.0f);
+	EXPECT_EQ(retvalHdl.msg["files"][2]["name"].asString(), "magenta.vcd");
+	EXPECT_EQ(retvalHdl.msg["files"][2]["encrypted"].asString(), "undecryptable");
+	EXPECT_EQ(retvalHdl.msg["files"][2]["size"].asFloat(), 9000.001f);
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedlist, Negative_ListEmpty) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "extendedlist";
+	args = {};
+	root["command"] = "extendedlist";
+	root["accept"] = true;
+	root["items"] = 0;
+
+	// stick into cmdman and check things
+	EXPECT_CALL(fm, openList(true)).WillOnce(testing::Return(true));
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedlist");
+
+	EXPECT_CALL(fm, closeList);
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedlist");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("There are no files stored on the server."));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedlistdata, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	/* tests the handling of server reply only,
+	 * initial request assumed as correct (tested in other tests) */
+
+	Json::Value root;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	root["command"] = "extendedlistdata";
+	root["cancel"] = true;
+	root["error"] = "some foobar";
+
+	EXPECT_CALL(fm, cancelList);
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedlist");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("some foobar"));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedlist, Negative_AlreadyListing) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "extendedlist";
+	args = {};
+
+	EXPECT_CALL(fm, openList(true)).WillOnce(testing::Return(false));
+
+	EXPECT_CALL(fm, cancelList).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedlist");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedlist, Negative_NotListing_ServerRequest) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, files, inputfile;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	inputfile["name"] = "magenta.vcd";
+	inputfile["size"] = 9000.001f;
+	inputfile["head"] = "some head 3";
+	files.append(inputfile);
+
+	root["command"] = "extendedlistdata";
+	root["cancel"] = false;
+	root["remaining"] = 0;
+	root["files"] = files;
+
+	EXPECT_CALL(fm, cancelList).Times(0);
+	EXPECT_CALL(fm, putListData).Times(0);
+	EXPECT_CALL(fm, closeList).Times(0);
+	EXPECT_CALL(fm, getListData).Times(0);
+	EXPECT_CALL(fm, isListingExtended).WillOnce(testing::Return(false));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedlist");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedistdata, Negative_NotListing_UserRequest) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare root/cmd/args
+	cmd = "extendedlistdata";
+	args = {};
+
+	EXPECT_CALL(fm, isListingExtended).WillOnce(testing::Return(false));
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedlist");
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+	EXPECT_EQ(retvalCmd.msg["accept"].asBool(), false);
+}
+
+TEST(testExtendedlistdata, Negative_NotListing_ServerRequest) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, files, inputfile;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	inputfile["name"] = "magenta.vcd";
+	inputfile["size"] = 9000.001f;
+	inputfile["head"] = "some head 3";
+	files.append(inputfile);
+
+	root["command"] = "extendedlistdata";
+	root["cancel"] = false;
+	root["remaining"] = 0;
+	root["files"] = files;
+
+	EXPECT_CALL(fm, cancelList).Times(0);
+	EXPECT_CALL(fm, isListingExtended).WillOnce(testing::Return(false));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedlist");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+/* =====================================
+ * tests for deleteme
+ */
+
+TEST(testDeleteme, TooFewArgs) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "deleteme";
+	args = {};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "deleteme");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testDeleteme, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args/root
+	cmd = "deleteme";
+	args = {"myFancyPasswd"};
+	root["command"] = "deleteme";
+	root["accept"] = true;
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "deleteme");
+	EXPECT_EQ(retvalCmd.msg["pass"].asString(), "myFancyPasswd");
+	EXPECT_EQ(retvalHdl.type, CmdMan::print | CmdMan::close);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "deleteme");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+
+	EXPECT_TRUE(cm.isNotConnected());
+}
+
+TEST(testDeleteme, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args/root
+	cmd = "deleteme";
+	args = {"myFancyPasswd"};
+	root["command"] = "deleteme";
+	root["accept"] = false;
+	root["error"] = "password incorrect";
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "deleteme");
+	EXPECT_EQ(retvalCmd.msg["pass"].asString(), "myFancyPasswd");
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "deleteme");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("password incorrect"));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+/* =====================================
+ * tests for deletefile
+ */
+TEST(testDeletefile, TooFewArgs) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "deletefile";
+	args = {};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "deletefile");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testDeletefile, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args/root
+	cmd = "deletefile";
+	args = {"fancy_file.txt"};
+	root["command"] = "deletefile";
+	root["file"] = "fancy_file.txt";
+	root["accept"] = true;
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "deletefile");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "fancy_file.txt");
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "deletefile");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "fancy_file.txt");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testDeletefile, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args/root
+	cmd = "deletefile";
+	args = {"fancy_file.txt"};
+	root["command"] = "deletefile";
+	root["file"] = "fancy_file.txt";
+	root["accept"] = false;
+	root["error"] = "file does not exist";
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "deletefile");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "fancy_file.txt");
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "deletefile");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "fancy_file.txt");
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("file does not exist"));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+/* =====================================
+ * test for status
+ */
+TEST(testStatus, Test) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args/root
+	cmd = "status";
+	args = {};
+	root["command"] = "status";
+	root["response"] = "fancy response";
+	root["accept"] = true;
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "status");
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "status");
+	EXPECT_THAT(retvalHdl.msg["response"].asString(), testing::HasSubstr("fancy response"));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+/* =====================================
+ * test for extendedstatus
+ */
+
+TEST(testExtendedstatus, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args/root
+	cmd = "extendedstatus";
+	args = {};
+	root["command"] = "extendedstatus";
+	root["accept"] = false;
+	root["error"] = "some foobar went wrong";
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedstatus");
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedstatus");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("some foobar went wrong"));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedstatus, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, transfersclientserver, tcs1, tcs2, transfersserverserver, tss1, tss2, tss3;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args/root
+	cmd = "extendedstatus";
+	args = {};
+	root["command"] = "extendedstatus";
+	root["accept"] = true;
+
+	tcs1["upload"] = true;
+	tcs1["file"] = "green.txt";
+	tcs1["progress"] = 99;
+	transfersclientserver.append(tcs1);
+	tcs2["upload"] = false;
+	tcs2["file"] = "red.txt";
+	tcs2["progress"] = 2;
+	transfersclientserver.append(tcs2);
+	root["transfersclientserver"] = transfersclientserver;
+
+	tss1["type"] = "upload";
+	tss1["file"] = "cyan.txt";
+	tss1["progress"] = 100;
+	tss1["method"] = "urg field";
+	tss1["speed"] = 42.0f;
+	transfersserverserver.append(tss1);
+	tss2["type"] = "download";
+	tss2["file"] = "magenta.txt";
+	tss2["progress"] = 42;
+	tss2["method"] = "fancy thing";
+	tss2["speed"] = 0.1f;
+	transfersserverserver.append(tss2);
+	tss3["type"] = "queued";
+	tss3["file"] = "yellow.txt";
+	tss3["progress"] = 42;
+	tss3["method"] = "cool timing stuff";
+	tss3["speed"] = 9000.001f;
+	transfersserverserver.append(tss3);
+	root["transfersserverserver"] = transfersserverserver;
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedstatus");
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedstatus");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+
+	EXPECT_TRUE(retvalHdl.msg["transfersclientserver"][0]["upload"].asBool());
+	EXPECT_EQ(retvalHdl.msg["transfersclientserver"][0]["file"].asString(), "green.txt");
+	EXPECT_EQ(retvalHdl.msg["transfersclientserver"][0]["progress"].asInt(), 99);
+	EXPECT_FALSE(retvalHdl.msg["transfersclientserver"][1]["upload"].asBool());
+	EXPECT_EQ(retvalHdl.msg["transfersclientserver"][1]["file"].asString(), "red.txt");
+	EXPECT_EQ(retvalHdl.msg["transfersclientserver"][1]["progress"].asInt(), 2);
+
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][0]["type"].asString(), "upload");
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][0]["file"].asString(), "cyan.txt");
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][0]["progress"].asInt(), 100);
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][0]["method"].asString(), "urg field");
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][0]["speed"].asFloat(), 42.0f);
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][1]["type"].asString(), "download");
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][1]["file"].asString(), "magenta.txt");
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][1]["progress"].asInt(), 42);
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][1]["method"].asString(), "fancy thing");
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][1]["speed"].asFloat(), 0.1f);
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][2]["type"].asString(), "queued");
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][2]["file"].asString(), "yellow.txt");
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][2]["progress"].asInt(), 42);
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][2]["method"].asString(), "cool timing stuff");
+	EXPECT_EQ(retvalHdl.msg["transfersserverserver"][2]["speed"].asFloat(), 9000.001f);
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedstatus, Positive_NoTransfersRunning) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, transfersclientserver, transfersserverserver;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args/root
+	cmd = "extendedstatus";
+	args = {};
+	root["command"] = "extendedstatus";
+	root["accept"] = true;
+	root["transfersclientserver"] = transfersclientserver;
+	root["transfersserverserver"] = transfersserverserver;
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedstatus");
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedstatus");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+	EXPECT_TRUE(retvalHdl.msg["transfersclientserver"].empty());
+	EXPECT_TRUE(retvalHdl.msg["transfersserverserver"].empty());
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+/* =====================================
+ * tests for help
+ */
+TEST(testHelp, LoggedIn) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args/root
+	cmd = "help";
+	args = {};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::print);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "help");
+	EXPECT_NE(retvalCmd.msg["names"], "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testHelp, NotConnected) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args/root
+	cmd = "help";
+	args = {};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::print);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "help");
+	EXPECT_NE(retvalCmd.msg["names"], "");
+
+	EXPECT_TRUE(cm.isNotConnected());
+}
+
+/* =====================================
+ * tests for keyfile and closekey
+ */
+TEST(testKeyfile, TooFewArgs) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "keyfile";
+	args = {};
+
+	// stick into cmdman
+	EXPECT_CALL(fm, openKey).Times(0);
+	EXPECT_CALL(fm, getOpensslError).Times(0);
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "keyfile");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testKeyfile, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "keyfile";
+	args = {"./some/path"};
+
+	// stick into cmdman
+	EXPECT_CALL(fm, openKey).WillOnce(testing::Return(false));
+	EXPECT_CALL(fm, getOpensslError).WillOnce(testing::Return("some openssl error"));
+	retvalCmd = cm.execute(cmd, args);
+
+	// 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["file"].asString(), "./some/path");
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testKeyfile, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "keyfile";
+	args = {"./some/path"};
+
+	// stick into cmdman
+	EXPECT_CALL(fm, openKey).WillOnce(testing::Return(true));
+	EXPECT_CALL(fm, getOpensslError).Times(0);
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::print);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "keyfile");
+	EXPECT_TRUE(retvalCmd.msg["accept"].asBool());
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "./some/path");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testClosekey, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "closekey";
+	args = {};
+
+	// stick into cmdman
+	EXPECT_CALL(fm, closeKey).WillOnce(testing::Return(true));
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::print);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "closekey");
+	EXPECT_TRUE(retvalCmd.msg["accept"].asBool());
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testClosekey, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "closekey";
+	args = {};
+
+	// stick into cmdman
+	EXPECT_CALL(fm, closeKey).WillOnce(testing::Return(false));
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "closekey");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+/* =====================================
+ * test for notifications
+ */
+TEST(testNotifications, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, messagesArr;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+	vector<string> msgvec = {"msg1", "msg2", "msg3 foo", "msg4 hello world!"};
+
+	// prepare cmd/args/root
+	cmd = "notifications";
+	args = {};
+	root["command"] = "notifications";
+	for (string s : msgvec)
+		messagesArr.append(s);
+	root["messages"] = messagesArr;
+	root["accept"] = true;
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "notifications");
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "notifications");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+
+	vector<string> retval_msgs;
+	for (Json::Value i : retvalHdl.msg["messages"])
+		retval_msgs.push_back(i.asString());
+	EXPECT_EQ(retval_msgs, msgvec);
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testNotifications, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, messagesArr;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args/root
+	cmd = "notifications";
+	args = {};
+	root["command"] = "notifications";
+	root["accept"] = false;
+	root["error"] = "some very descriptive string";
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "notifications");
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "notifications");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("some very descriptive string"));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+/* =====================================
+ * tests for queue and dequeue
+ */
+TEST(testQueue, TooFewArgs) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "queue";
+	args = {};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "queue");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testQueue, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args/root
+	cmd = "queue";
+	args = {"fancy_file.txt"};
+	root["command"] = "queue";
+	root["file"] = "fancy_file.txt";
+	root["accept"] = true;
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "queue");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "fancy_file.txt");
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "queue");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "fancy_file.txt");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testQueue, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args/root
+	cmd = "queue";
+	args = {"fancy_file.txt"};
+	root["command"] = "queue";
+	root["file"] = "fancy_file.txt";
+	root["accept"] = false;
+	root["error"] = "file does not exist";
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "queue");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "fancy_file.txt");
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "queue");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "fancy_file.txt");
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("file does not exist"));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testDequeue, TooFewArgs) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "dequeue";
+	args = {};
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "dequeue");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testDequeue, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args/root
+	cmd = "dequeue";
+	args = {"fancy_file.txt"};
+	root["command"] = "dequeue";
+	root["file"] = "fancy_file.txt";
+	root["accept"] = true;
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "dequeue");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "fancy_file.txt");
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "dequeue");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "fancy_file.txt");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testDequeue, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare cmd/args/root
+	cmd = "dequeue";
+	args = {"fancy_file.txt"};
+	root["command"] = "dequeue";
+	root["file"] = "fancy_file.txt";
+	root["accept"] = false;
+	root["error"] = "file not in queue";
+
+	// stick into cmdman
+	retvalCmd = cm.execute(cmd, args);
+	retvalHdl = cm.handle(root);
+
+	// check things
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "dequeue");
+	EXPECT_EQ(retvalCmd.msg["file"].asString(), "fancy_file.txt");
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "dequeue");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["file"].asString(), "fancy_file.txt");
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("file not in queue"));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+/* =====================================
+ * test for all commands that require a login
+ */
+TEST(testCommandsWithRequiredLogin, NotLoggedInOrNotConnected) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	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"};
+	// 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"};
+
+	for (std::string cmd : cmds) {
+		cm.initNotConnected();
+		retval1 = cm.execute(cmd, args);
+		cm.initConnected();
+		retval2 = cm.execute(cmd, args);
+		cm.initVersionChecked();
+		retval3 = cm.execute(cmd, args);
+
+		EXPECT_EQ(retval1.type, CmdMan::error);
+		EXPECT_EQ(retval1.msg["command"].asString(), "error");
+		EXPECT_NE(retval1.msg["error"].asString(), "");
+
+		EXPECT_EQ(retval2.type, CmdMan::error);
+		EXPECT_EQ(retval2.msg["command"].asString(), "error");
+		EXPECT_NE(retval2.msg["error"].asString(), "");
+
+		EXPECT_EQ(retval3.type, CmdMan::error);
+		EXPECT_EQ(retval3.msg["command"].asString(), "error");
+		EXPECT_NE(retval3.msg["error"].asString(), "");
+	}
+}
+
+} // namespace
+/* end of namespace */

+ 49 - 0
cli/test/cryptoget.sh

@@ -0,0 +1,49 @@
+#!/bin/bash
+
+SCRIPT_PATH=${0%/*}
+if [ "$0" != "$SCRIPT_PATH" ] && [ "$SCRIPT_PATH" != "" ]; then 
+    cd "$SCRIPT_PATH/.."
+fi
+
+DAEMONFILEPATH="../../daemon/build/files"
+
+if [ $# -eq 1 ];
+	then DAEMONFILEPATH="$1"
+fi
+
+echo "am in directory $PWD"
+echo "using file path $DAEMONFILEPATH"
+
+test/cryptotest_gcm 1 test/samplekey1.bin test/samplefile.txt $DAEMONFILEPATH/samplefile.txt
+
+./ccats-cli --batch test/cryptoget.txt
+
+if [ ! -f test/cryptoget.txt.out ];
+then
+	echo "running of batch file failed"
+	exit 1;
+fi
+
+if [ ! -f samplefile.txt ];
+then
+	echo "file didnt download"
+	echo "STDOUT is :"
+	cat test/cryptoget.txt.out
+	echo "STDERR is :"
+	cat test/cryptoget.txt.err
+	exit 1;
+fi
+
+diff samplefile.txt test/samplefile.txt
+DIFFRES=$?
+
+rm $DAEMONFILEPATH/samplefile.txt
+rm samplefile.txt
+
+if [ $DIFFRES -ne 0 ];
+then
+	echo "files are not equal after decryption: $DIFFRES"
+	exit 1;
+fi
+
+exit 0;

+ 4 - 0
cli/test/cryptoget.txt

@@ -0,0 +1,4 @@
+connect 0.0.0.0
+login user pass
+keyfile test/samplekey1.bin
+get samplefile.txt

+ 57 - 0
cli/test/cryptoput.sh

@@ -0,0 +1,57 @@
+#!/bin/bash
+
+SCRIPT_PATH=${0%/*}
+if [ "$0" != "$SCRIPT_PATH" ] && [ "$SCRIPT_PATH" != "" ]; then 
+    cd "$SCRIPT_PATH/.."
+fi
+
+DAEMONFILEPATH="../../daemon/build/files"
+
+if [ $# -eq 1 ];
+then
+	DAEMONFILEPATH="$1"
+fi
+
+echo "am in directory $PWD"
+echo "using file path $DAEMONFILEPATH"
+
+./ccats-cli --batch test/cryptoput.txt
+
+if [ ! -f test/cryptoput.txt.out ];
+then
+	echo "running of batch file failed"
+	exit 1;
+fi
+
+if [ ! -f $DAEMONFILEPATH/samplefile.txt ];
+then
+	echo "couldnt find file on server"
+	echo "STDOUT is :"
+	cat test/cryptoput.txt.out
+	echo "STDERR is :"
+	cat test/cryptoput.txt.err
+	exit 1;
+fi
+
+test/cryptotest_gcm 0 test/samplekey1.bin $DAEMONFILEPATH/samplefile.txt samplefile.txt
+CRYPTORES=$?
+
+diff samplefile.txt test/samplefile.txt
+DIFFRES=$?
+
+rm $DAEMONFILEPATH/samplefile.txt
+rm samplefile.txt
+
+if [ $CRYPTORES -ne 0 ];
+then
+	echo "decryption failed: $CRYPTORES"
+	exit 1;
+fi
+
+if [ $DIFFRES -ne 0 ];
+then
+	echo "files are not equal after decryption: $DIFFRES"
+	exit 1;
+fi
+
+exit 0;

+ 4 - 0
cli/test/cryptoput.txt

@@ -0,0 +1,4 @@
+connect 0.0.0.0
+login user pass
+keyfile test/samplekey1.bin
+put test/samplefile.txt

+ 20 - 0
cli/test/cryptotest-gcm.c → cli/test/cryptotest_gcm.c

@@ -57,6 +57,16 @@ int main(int argc, char **argv) {
 	}
 
 	if (mode) {
+		printf("using iv ");
+		for (unsigned i = 0; i < sizeof(iv); i++)
+			printf("%02x ", iv[i]);
+		printf("\n");
+
+		printf("using tag ");
+		for (unsigned i = 0; i < sizeof(tag); i++)
+			printf("%02x ", tag[i]);
+		printf("\n");
+
 		fseek(f, 0, SEEK_END);
 		insize = ftell(f) + 4;
 		fseek(f, 0, SEEK_SET);
@@ -83,6 +93,16 @@ int main(int argc, char **argv) {
 		fread(cipher, insize, 1, f);
 		fclose(f);
 
+		printf("using iv ");
+		for (unsigned i = 0; i < sizeof(iv); i++)
+			printf("%02x ", iv[i]);
+		printf("\n");
+
+		printf("using tag ");
+		for (unsigned i = 0; i < sizeof(tag); i++)
+			printf("%02x ", tag[i]);
+		printf("\n");
+
 		decryptedtext_len = gcm_decrypt(cipher, insize, tag, key, iv, sizeof(iv), plain);
 		if (decryptedtext_len < 0) {
 			printf("decrypt failed\n");

+ 59 - 0
cli/test/fileman_mock.h

@@ -0,0 +1,59 @@
+#ifndef FILEMAN_MOCK_H
+#define FILEMAN_MOCK_H
+
+#include "../include/fileman.h"
+#include <gmock/gmock.h>
+
+class FileManMock : public FileMan {
+public:
+	MOCK_METHOD(bool, isGetting, (), (override));
+	MOCK_METHOD(bool, isPutting, (), (override));
+	MOCK_METHOD(bool, isListing, (), (override));
+	MOCK_METHOD(bool, isListingSimple, (), (override));
+	MOCK_METHOD(bool, isListingExtended, (), (override));
+
+	MOCK_METHOD(bool, openPut, (const std::string &path), (override));
+	MOCK_METHOD(bool, openGet, (const std::string &path), (override));
+	MOCK_METHOD(bool, openList, (bool extended), (override));
+
+	MOCK_METHOD(void, closePut, (), (override));
+	MOCK_METHOD(void, closeGet, (), (override));
+	MOCK_METHOD(void, closeList, (), (override));
+
+	MOCK_METHOD(std::string, getPutName, (), (override));
+	MOCK_METHOD(std::string, getGetName, (), (override));
+
+	MOCK_METHOD(void, cancelPut, (), (override));
+	MOCK_METHOD(void, cancelGet, (), (override));
+	MOCK_METHOD(void, cancelList, (), (override));
+
+	MOCK_METHOD(std::vector<char>, readPut, (), (override));
+
+	MOCK_METHOD(void, writeGet, (std::vector<char> data), (override));
+
+	MOCK_METHOD(std::string, readBase64, (), (override));
+	MOCK_METHOD(void, writeBase64, (std::string data), (override));
+
+	MOCK_METHOD(void, putListData, (std::vector<Json::Value> names), (override));
+	MOCK_METHOD(std::vector<Json::Value>, getListData, (), (override));
+
+	MOCK_METHOD(int, getPutChunks, (), (override));
+	MOCK_METHOD(int, getGetChunks, (), (override));
+	MOCK_METHOD(int, getListChunks, (), (override));
+	MOCK_METHOD(int, getPutRemainingChunks, (), (override));
+	MOCK_METHOD(int, getGetRemainingChunks, (), (override));
+	MOCK_METHOD(int, getListRemainingChunks, (), (override));
+	MOCK_METHOD(int, getPutSize, (), (override));
+
+	MOCK_METHOD(void, setGetChunks, (int chunks), (override));
+	MOCK_METHOD(void, setListChunks, (int chunks), (override));
+
+	MOCK_METHOD(std::string, pathToFilename, (std::string path), (override));
+
+	MOCK_METHOD(std::string, getOpensslError, (), (override));
+	MOCK_METHOD(bool, openKey, (const std::string &path), (override));
+
+	MOCK_METHOD(bool, closeKey, (), (override));
+};
+
+#endif

+ 22 - 0
cli/test/logintest_neg.sh

@@ -0,0 +1,22 @@
+#!/bin/bash
+
+SCRIPT_PATH=${0%/*}
+if [ "$0" != "$SCRIPT_PATH" ] && [ "$SCRIPT_PATH" != "" ]; then 
+    cd "$SCRIPT_PATH/.."
+fi
+
+if [ $# -eq 1 ];
+	then DAEMONFILEPATH="$1"
+fi
+
+./ccats-cli --batch test/logintest_neg.txt
+
+if [ ! -f test/logintest_neg.txt.err ];
+then
+	exit 1;
+fi
+if [ -z "$(grep "Login failed" test/logintest_neg.txt.err)" ];
+then
+	exit 1;
+fi
+exit 0;

+ 2 - 0
cli/test/logintest_neg.txt

@@ -0,0 +1,2 @@
+connect 0.0.0.0
+login user badpass

+ 18 - 0
cli/test/logintest_pos.sh

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

+ 2 - 0
cli/test/logintest_pos.txt

@@ -0,0 +1,2 @@
+connect 0.0.0.0
+login user pass

+ 1 - 0
cli/test/samplefile.txt

@@ -0,0 +1 @@
+This is a ccats sample test file.

+ 2 - 2
daemon/CMakeLists.txt

@@ -1,6 +1,6 @@
 cmake_minimum_required(VERSION 2.8)
 
-set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/../.cmake_modules/")
 message("${CMAKE_MODULE_PATH}")
 
@@ -8,7 +8,7 @@ project(ccats)
 
 # dependencies used by server and tests
 find_package(Threads REQUIRED)
-find_package(Boost 1.67 REQUIRED COMPONENTS system filesystem)
+find_package(Boost 1.70 REQUIRED COMPONENTS system filesystem)
 find_package(Jsoncpp REQUIRED)
 
 include(src/CMakeLists.txt)

+ 107 - 0
daemon/CovertProtocol.md

@@ -0,0 +1,107 @@
+# Covert Protocol
+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 packet is at least 2 bytes big. The first byte is the header followed by one or multiple data bytes.
+
+```
+|   1 byte   |       n bytes       |
+|   header   |        data         |
+```
+
+
+
+## Header
+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          |
+
+| 2 bit |  3 bit  |  3 bit  |
+|  seg  |    -    |   com   |
+```
+
+### 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.
+
+An example how the packet exchange might look like.
+```
+A                       B
+seg                   seg
+=========================
+1 -------------------->
+  <-------------------- 2
+2 -------------------->
+  <-------------------- 3
+3 --------- X                Packet lost
+
+3 -------------------->      Retransmission from A
+  <-------------------- 4
+4 -------------------->
+            X --------- 5    Packet lost
+
+            X --------- 5    Retransmission from B
+4 --------------------> 4    Retransmission from A
+  <-------------------- 5
+```
+
+
+
+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 -------------------->
+```

+ 6 - 6
daemon/Daemon-Config-Reference.md

@@ -29,14 +29,14 @@ If you cannot connect and the server prints a error related to TLSv1, ensure you
 `outerInterface`: The interface of your outer network<br/>
 
 ##### Covert Channel Mode `forward`
-There no further config needed. Forward should work out of the pox
+There no further config needed. Forward should work out of the box
 
-##### Covert Channel Mode `proxy`
+##### Covert Channel Mode `tcpurgency`<br/>
 `ownIP`: IP of this server<br/>
-`partnerIP`: IP of the partner server. If this is a relay the other cannot be a relay.<br/>
-`targetIP`: IP of the target server of teh covert channel<br/>
-`targetPort`: Port of the target server of the covert channel<br/>
-`relay`: Bool which sets the mode: true - just relays the covert channel | false - uses it's own covert channel<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
 ```

+ 99 - 0
daemon/include/CovertChannel/BidirectionalChannels.hpp

@@ -0,0 +1,99 @@
+#ifndef BIDIRECTIONALCHANNELS_H
+#define BIDIRECTIONALCHANNELS_H
+
+#include "CovertChannel.h"
+#include "Protocols/CovertProtocolBidirectional.hpp"
+
+/**
+ * @class BidirectionalChannels
+ *
+ * Abstract class which implements the methods and constructors which are equal in all bidirectional channels.
+ *
+ * @param N number of bytes which can be used to transmit data
+ * @param PASSIVE true - server only reacts to incoming channel | false - server initiates channel
+ */
+template <int N, bool PASSIVE> class BidirectionalChannels : public CovertChannel {
+public:
+	/**
+	 * Sets up a CovertChannel.
+	 *
+	 * Creates a CovertChannel, sets the network interfaces for sniffing and sending and sets the filter.
+	 *
+	 * @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) {}
+
+	/**
+	 * Destroys the CovertChannel.
+	 */
+	virtual ~BidirectionalChannels() {}
+
+	/* ChannelControls */
+
+	/**
+	 * Starts sending a file.
+	 *
+	 * Starts sending a file if no transmission is running and the file exists.
+	 *
+	 * @param fileName name of the file in the file directory
+	 * @return true - file will be sent | false - file was not accepted
+	 */
+	virtual bool sendFile(const std::string &fileName) { return protocol.sendFile(fileName); }
+
+	/**
+	 * Get the progress
+	 *
+	 * @return progress counters
+	 */
+	virtual std::pair<uint32_t, uint32_t> getProgress() { return protocol.getProgress(); }
+
+	/**
+	 * Test if a transfer is running
+	 *
+	 * @return true - a transfer runs | false - no transfer runs
+	 */
+	virtual bool isTransferRunning() { return protocol.isTransferRunning(); }
+
+	/**
+	 * Resets state and sets reset flag so a reset signal is sent in the next packet
+	 */
+	virtual void reset() { protocol.reset(); }
+
+	/**
+	 * Test if a transfer is running
+	 *
+	 * @return true - a transfer runs | false - no transfer runs
+	 */
+	virtual std::time_t getTransferStart() { return protocol.getTransferStart(); }
+
+	/**
+	 * Get file name of the file which is currently be sent or received.
+	 *
+	 * @return file name
+	 */
+	virtual std::string getFileName() { return protocol.getFileName(); };
+
+	/* =============== */
+
+protected:
+	/**
+	 * protocol used to transmit data
+	 */
+	CovertProtocolBidirectional<N, PASSIVE> protocol;
+};
+
+#endif

+ 63 - 0
daemon/include/CovertChannel/ChannelControls.h

@@ -0,0 +1,63 @@
+#ifndef CHANNELCONTROLS_H
+#define CHANNELCONTROLS_H
+
+#include <ctime>
+#include <string>
+#include <utility>
+
+class ChannelControls {
+public:
+	/**
+	 * Send a file over the covert channel.
+	 *
+	 * @param fileName name of the file in the file directory
+	 * @return true - file will be sent | false - file was not accepted
+	 */
+	virtual bool sendFile(const std::string &fileName) = 0;
+
+	/**
+	 * Get the progress
+	 *
+	 * @return progress counters
+	 */
+	virtual std::pair<uint32_t, uint32_t> getProgress() = 0;
+
+	/**
+	 * Get the transfer start time
+	 *
+	 * @return start time of the transfer
+	 */
+	virtual std::time_t getTransferStart() = 0;
+
+	/**
+	 * Test if a transfer is running
+	 *
+	 * @return true - a transfer runs | false - no transfer runs
+	 */
+	virtual bool isTransferRunning() = 0;
+
+	/**
+	 * Resets the state of the channel
+	 */
+	virtual void reset() = 0;
+
+	/**
+	 * Get file name of the file which is currently be sent or received.
+	 *
+	 * @return file name
+	 */
+	virtual std::string getFileName() = 0;
+
+protected:
+	/**
+	 * Time when the transfer was started with sendFile
+	 */
+	std::time_t transferStart;
+
+	/**
+	 * file name of the file wich is being sent or received
+	 */
+	std::string fileName;
+};
+
+#endif

+ 104 - 0
daemon/include/CovertChannel/Channels/TCPAppendChannel.hpp

@@ -0,0 +1,104 @@
+#ifndef TCPAPPENDCHANNEL_H
+#define TCPAPPENDCHANNEL_H
+
+#include "../BidirectionalChannels.hpp"
+
+/**
+ * @class TCPAppendChannel
+ *
+ * A CovertChannel which appends data to the TCP payload
+ *
+ * @param N number of bytes which can be used to transmit data
+ * @param PASSIVE true - server only reacts to incoming channel | false - server initiates channel
+ */
+template <int N, bool PASSIVE> class TCPAppendChannel : public BidirectionalChannels<N, PASSIVE> {
+public:
+	/**
+	 * Sets up a CovertChannel.
+	 *
+	 * Creates a CovertChannel, sets the network interfaces for sniffing and sending and sets the filter.
+	 *
+	 * @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) {}
+
+	/**
+	 * Destroys the CovertChannel.
+	 */
+	virtual ~TCPAppendChannel() {}
+
+protected:
+	/**
+	 * Handler for sniffed packets filterd to forward from the outer network.
+	 *
+	 * Handles incoming packets and forwards them.
+	 *
+	 * @param pdu sniffed packet
+	 *
+	 * @return false = stop loop | true = continue loop
+	 */
+	virtual bool handleChannelFromOuter(Tins::PDU &pdu) {
+		Tins::TCP &tcp = pdu.rfind_pdu<Tins::TCP>();
+
+		// get payload
+		Tins::RawPDU *raw = tcp.find_pdu<Tins::RawPDU>();
+		if (raw != nullptr) {
+			Tins::RawPDU::payload_type &payload = raw->payload();
+
+			// read data from payload
+			std::size_t size = payload.size();
+			uint8_t *data = &payload.front();
+			data += size - N;
+
+			BidirectionalChannels<N, PASSIVE>::protocol.receive(data);
+
+			// resize payload
+			payload.resize(size - N);
+		}
+
+		BidirectionalChannels<N, PASSIVE>::innerSender.send(pdu);
+
+		return true;
+	}
+
+	/**
+	 * Handler for sniffed packets filterd to forward from the inner network.
+	 *
+	 * Handles incoming packets and forwards them.
+	 *
+	 * @param pdu sniffed packet
+	 *
+	 * @return false = stop loop | true = continue loop
+	 */
+	virtual bool handleChannelFromInner(Tins::PDU &pdu) {
+		Tins::TCP &tcp = pdu.rfind_pdu<Tins::TCP>();
+
+		// get payload
+		Tins::RawPDU *raw = tcp.find_pdu<Tins::RawPDU>();
+		if (raw != nullptr) {
+			Tins::RawPDU::payload_type &payload = raw->payload();
+
+			// resize payload
+			std::size_t size = payload.size();
+			payload.resize(size + N);
+
+			// write data in payload
+			uint8_t *data = &payload.front();
+			data += size;
+
+			BidirectionalChannels<N, PASSIVE>::protocol.send(data);
+		}
+
+		BidirectionalChannels<N, PASSIVE>::outerSender.send(pdu);
+
+		return true;
+	}
+};
+
+#endif

+ 102 - 0
daemon/include/CovertChannel/Channels/TCPOptionCustomChannel.hpp

@@ -0,0 +1,102 @@
+#ifndef TCPOPTIONCUSTOMCHANNEL_H
+#define TCPOPTIONCUSTOMCHANNEL_H
+
+#include "../BidirectionalChannels.hpp"
+
+/**
+ * @class TCPOptionCustom
+ *
+ * A CovertChannel which hides data in a custom field in the TCP options data
+ *
+ * In theory, any options field can be used to store data. This implementation specifically uses field 11 (CC).
+ * For (un)usable fields, refer to the IANA listing at
+ * https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml
+ *
+ * @param N number of bytes which can be used to transmit data
+ * @param PASSIVE true - server only reacts to incoming channel | false - server initiates channel
+ */
+template <int N, bool PASSIVE> class TCPOptionCustomChannel : public BidirectionalChannels<N, PASSIVE> {
+	static_assert(N <= 255 - 2);
+
+public:
+	/**
+	 * Sets up a CovertChannel.
+	 *
+	 * Creates a CovertChannel, sets the network interfaces for sniffing and sending and sets the filter.
+	 *
+	 * @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) {}
+
+	/**
+	 * Destroys the CovertChannel.
+	 */
+	virtual ~TCPOptionCustomChannel() {}
+
+protected:
+	// this is the id of the option as found in the article found at the top
+	const unsigned int target_options_id = 11;
+
+	/**
+	 * Handler for sniffed packets filterd to forward from the outer network.
+	 *
+	 * Handles incoming packets and forwards them.
+	 *
+	 * @param pdu sniffed packet
+	 *
+	 * @return false = stop loop | true = continue loop
+	 */
+	virtual bool handleChannelFromOuter(Tins::PDU &pdu) {
+		Tins::TCP &tcp = pdu.rfind_pdu<Tins::TCP>();
+
+		const Tins::TCP::options_type &options = tcp.options();
+		Tins::TCP::option op;
+		size_t i;
+		// find option field
+		for (i = 0; i < options.size(); i++) {
+			if (options[i].option() == target_options_id) {
+				op = options[i];
+				break;
+			}
+		}
+		if (i != options.size() && options[i].data_size()) {
+			// found the option
+			BidirectionalChannels<N, PASSIVE>::protocol.receive((uint8_t *)(options[i].data_ptr()));
+			tcp.remove_option((Tins::TCP::OptionTypes)target_options_id);
+		}
+
+		BidirectionalChannels<N, PASSIVE>::innerSender.send(pdu);
+
+		return true;
+	}
+
+	/**
+	 * Handler for sniffed packets filterd to forward from the inner network.
+	 *
+	 * Handles incoming packets and forwards them.
+	 *
+	 * @param pdu sniffed packet
+	 *
+	 * @return false = stop loop | true = continue loop
+	 */
+	virtual bool handleChannelFromInner(Tins::PDU &pdu) {
+		Tins::TCP &tcp = pdu.rfind_pdu<Tins::TCP>();
+		uint8_t data[N];
+		BidirectionalChannels<N, PASSIVE>::protocol.send(data);
+
+		Tins::TCP::option op(target_options_id, N, data);
+		tcp.add_option(op);
+
+		BidirectionalChannels<N, PASSIVE>::outerSender.send(pdu);
+
+		return true;
+	}
+};
+
+#endif

+ 82 - 0
daemon/include/CovertChannel/Channels/TCPOptionTimestampChannel.hpp

@@ -0,0 +1,82 @@
+#ifndef TCPOPTIONTIMESTAMPCHANNEL_H
+#define TCPOPTIONTIMESTAMPCHANNEL_H
+
+#include "../BidirectionalChannels.hpp"
+
+#include <utility>
+
+/**
+ * @class TCPOptionTimestampChannel
+ *
+ * A CovertChannel which hides data in the TCP timestamp option field.
+ *
+ * @warning Only use on connections which will never use the timestamp option on their own!!!
+ *
+ * @param PASSIVE true - server only reacts to incoming channel | false - server initiates channel
+ */
+template <bool PASSIVE> class TCPOptionTimestampChannel : public BidirectionalChannels<8, PASSIVE> {
+public:
+	/**
+	 * Sets up a CovertChannel.
+	 *
+	 * Creates a CovertChannel, sets the network interfaces for sniffing and sending and sets the filter.
+	 *
+	 * @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) {}
+
+	/**
+	 * Destroys the CovertChannel.
+	 */
+	virtual ~TCPOptionTimestampChannel() {}
+
+protected:
+	/**
+	 * Handler for sniffed packets filterd to forward from the outer network.
+	 *
+	 * Handles incoming packets and forwards them.
+	 *
+	 * @param pdu sniffed packet
+	 *
+	 * @return false = stop loop | true = continue loop
+	 */
+	virtual bool handleChannelFromOuter(Tins::PDU &pdu) {
+		Tins::TCP &tcp = pdu.rfind_pdu<Tins::TCP>();
+
+		std::pair<uint32_t, uint32_t> timestamp = tcp.timestamp();
+		uint64_t data = ((uint64_t)timestamp.first) << 32 | timestamp.second;
+		BidirectionalChannels<8, PASSIVE>::protocol.receive((uint8_t *)(&data));
+		tcp.remove_option(Tins::TCP::OptionTypes::TSOPT);
+		BidirectionalChannels<8, PASSIVE>::innerSender.send(pdu);
+
+		return true;
+	}
+
+	/**
+	 * Handler for sniffed packets filterd to forward from the inner network.
+	 *
+	 * Handles incoming packets and forwards them.
+	 *
+	 * @param pdu sniffed packet
+	 *
+	 * @return false = stop loop | true = continue loop
+	 */
+	virtual bool handleChannelFromInner(Tins::PDU &pdu) {
+		Tins::TCP &tcp = pdu.rfind_pdu<Tins::TCP>();
+
+		uint64_t data = 0;
+		BidirectionalChannels<8, PASSIVE>::protocol.send((uint8_t *)(&data));
+		tcp.timestamp(data >> 32, data);
+		BidirectionalChannels<8, PASSIVE>::outerSender.send(pdu);
+
+		return true;
+	}
+};
+
+#endif

+ 77 - 0
daemon/include/CovertChannel/Channels/TCPUrgencyChannel.hpp

@@ -0,0 +1,77 @@
+#ifndef TCPURGENCYCHANNEL_H
+#define TCPURGENCYCHANNEL_H
+
+#include "../BidirectionalChannels.hpp"
+
+/**
+ * @class TCPUrgencyChannel
+ *
+ * A CovertChannel which hides data in the TCP urgency pointer
+ *
+ * @param PASSIVE true - server only reacts to incoming channel | false - server initiates channel
+ */
+template <bool PASSIVE> class TCPUrgencyChannel : public BidirectionalChannels<2, PASSIVE> {
+public:
+	/**
+	 * Sets up a CovertChannel.
+	 *
+	 * Creates a CovertChannel, sets the network interfaces for sniffing and sending and sets the filter.
+	 *
+	 * @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) {}
+
+	/**
+	 * Destroys the CovertChannel.
+	 */
+	virtual ~TCPUrgencyChannel() {}
+
+protected:
+	/**
+	 * Handler for sniffed packets filterd to forward from the outer network.
+	 *
+	 * Handles incoming packets and forwards them.
+	 *
+	 * @param pdu sniffed packet
+	 *
+	 * @return false = stop loop | true = continue loop
+	 */
+	virtual bool handleChannelFromOuter(Tins::PDU &pdu) {
+		Tins::TCP &tcp = pdu.rfind_pdu<Tins::TCP>();
+
+		uint16_t data = tcp.urg_ptr();
+		BidirectionalChannels<2, PASSIVE>::protocol.receive((uint8_t *)(&data));
+		tcp.urg_ptr(0);
+		BidirectionalChannels<2, PASSIVE>::innerSender.send(pdu);
+
+		return true;
+	}
+
+	/**
+	 * Handler for sniffed packets filterd to forward from the inner network.
+	 *
+	 * Handles incoming packets and forwards them.
+	 *
+	 * @param pdu sniffed packet
+	 *
+	 * @return false = stop loop | true = continue loop
+	 */
+	virtual bool handleChannelFromInner(Tins::PDU &pdu) {
+		Tins::TCP &tcp = pdu.rfind_pdu<Tins::TCP>();
+
+		uint16_t data = 0;
+		BidirectionalChannels<2, PASSIVE>::protocol.send((uint8_t *)(&data));
+		tcp.urg_ptr(data);
+		BidirectionalChannels<2, PASSIVE>::outerSender.send(pdu);
+
+		return true;
+	}
+};
+
+#endif

+ 27 - 26
daemon/include/CovertChannel/CovertChannel.h

@@ -1,8 +1,11 @@
 #ifndef COVERTCHANNEL_H
 #define COVERTCHANNEL_H
 
+#include <thread>
 #include <tins/tins.h>
 
+#include "ChannelControls.h"
+
 /**
  * @class CovertChannel
  *
@@ -11,7 +14,7 @@
  * CovertChannel class which will sniff on two network interfacees. It handles filtered traffic with a virtual handler
  * function.
  */
-class CovertChannel {
+class CovertChannel : public ChannelControls {
 public:
 	/**
 	 * Sets up a CovertChannel.
@@ -24,11 +27,9 @@ public:
 	 * @param outerForwardFilter pcap filter string which will be set for the channel sniffers and negated for the forward sniffers
 	 * @param innerChannelFilter pcap filter string which will be set for the channel sniffers and negated for the forward sniffers
 	 * @param outerChannelFilter pcap filter string which will be set for the channel sniffers and negated for the forward sniffers
-	 * @param outerPartnerFilter pcap filter string which will be set for the channel sniffers and negated for the forward sniffers
 	 */
 	CovertChannel(const std::string &innerInterface, const std::string &outerInterface, const std::string &innerForwardFilter = "",
-	              const std::string &outerForwardFilter = "", const std::string &innerChannelFilter = "", const std::string &outerChannelFilter = "",
-	              const std::string &outerPartnerFilter = "");
+	              const std::string &outerForwardFilter = "", const std::string &innerChannelFilter = "", const std::string &outerChannelFilter = "");
 
 	/**
 	 * Destroys the CovertChannel.
@@ -52,7 +53,7 @@ public:
 	 * @param filter pcap filter string which will be set for the channel sniffers and negated for the forward sniffers
 	 */
 	void setFilter(const std::string &innerForwardFilter = "", const std::string &outerForwardFilter = "", const std::string &innerChannelFilter = "",
-	               const std::string &outerChannelFilter = "", const std::string &outerPartnerFilter = "");
+	               const std::string &outerChannelFilter = "");
 
 protected:
 	/**
@@ -99,17 +100,6 @@ protected:
 	 */
 	virtual bool handleChannelFromInner(Tins::PDU &pdu) = 0;
 
-	/**
-	 * Handler for sniffed packets filterd to use as channel from the outer network.
-	 *
-	 * Handles incoming packets and redirets them.
-	 *
-	 * @param pdu sniffed packet
-	 *
-	 * @return false = stop loop | true = continue loop
-	 */
-	virtual bool handlePartnerFromOuter(Tins::PDU &pdu) = 0;
-
 	/**
 	 * Starts the sniffing loop of the inner forward sniffer.
 	 */
@@ -130,11 +120,6 @@ protected:
 	 */
 	void startOuterChannelSniffing();
 
-	/**
-	 * Starts the sniffing loop of the outer partner sniffer.
-	 */
-	void startOuterPartnerSniffing();
-
 	/**
 	 * Tins Sniffer to filter packets to which should be forwarded
 	 */
@@ -155,11 +140,6 @@ protected:
 	 */
 	Tins::Sniffer *outerChannelSniffer;
 
-	/**
-	 * Tins Sniffer to filter packets to which should be used for the covert channel
-	 */
-	Tins::Sniffer *outerPartnerSniffer;
-
 	/**
 	 * Tins PacketSender which sends packets to the inner network
 	 */
@@ -169,6 +149,27 @@ protected:
 	 * Tins PacketSender which sends packets to the outer network
 	 */
 	Tins::PacketSender outerSender;
+
+private:
+	/**
+	 * Thread for the inner forward sniffer
+	 */
+	std::thread innerForwardSnifferThread;
+
+	/**
+	 * Thread for the outer forward sniffer
+	 */
+	std::thread outerForwardSnifferThread;
+
+	/**
+	 * Thread for the inner channel sniffer
+	 */
+	std::thread innerChannelSnifferThread;
+
+	/**
+	 * Thread for the outer channel sniffer
+	 */
+	std::thread outerChannelSnifferThread;
 };
 
 #endif

+ 47 - 11
daemon/include/CovertChannel/ForwardChannel.h

@@ -26,20 +26,56 @@ public:
 	 */
 	virtual ~ForwardChannel();
 
-protected:
+	/* ChannelControls */
+
 	/**
-	 * Handler for sniffed packets filterd to forward from the outer network.
+	 * Starts sending a file.
 	 *
-	 * Handles incoming packets and forwards them.
+	 * Starts sending a file if no transmission is running and the file exists.
 	 *
-	 * @param pdu sniffed packet
+	 * @param fileName name of the file in the file directory
+	 * @return true - file will be sent | false - file was not accepted
+	 */
+	virtual bool sendFile(const std::string &fileName);
+
+	/**
+	 * Get the progress
 	 *
-	 * @return false = stop loop | true = continue loop
+	 * @return progress counters
 	 */
-	virtual bool handleChannelFromOuter(Tins::PDU &pdu);
+	virtual std::pair<uint32_t, uint32_t> getProgress();
 
 	/**
-	 * Handler for sniffed packets filterd to forward from the inner network.
+	 * Test if a transfer is running
+	 *
+	 * @return true - a transfer runs | false - no transfer runs
+	 */
+	virtual bool isTransferRunning();
+
+	/**
+	 * Resets state and sets reset flag so a reset signal is sent in the next packet
+	 */
+	virtual void reset();
+
+	/**
+	 * Test if a transfer is running
+	 *
+	 * @return true - a transfer runs | false - no transfer runs
+	 */
+	virtual std::time_t getTransferStart();
+
+	/**
+	 * Get file name of the file which is currently be sent or received.
+	 *
+	 * @return file name
+	 */
+	virtual std::string getFileName();
+
+	/* =============== */
+
+protected:
+	/**
+	 * Handler for sniffed packets filterd to forward from the outer network.
 	 *
 	 * Handles incoming packets and forwards them.
 	 *
@@ -47,18 +83,18 @@ protected:
 	 *
 	 * @return false = stop loop | true = continue loop
 	 */
-	virtual bool handleChannelFromInner(Tins::PDU &pdu);
+	virtual bool handleChannelFromOuter(Tins::PDU &pdu);
 
 	/**
-	 * Handler for sniffed packets filterd to use as channel from the outer network.
+	 * Handler for sniffed packets filterd to forward from the inner network.
 	 *
-	 * Handles incoming packets and redirets them.
+	 * Handles incoming packets and forwards them.
 	 *
 	 * @param pdu sniffed packet
 	 *
 	 * @return false = stop loop | true = continue loop
 	 */
-	virtual bool handlePartnerFromOuter(Tins::PDU &pdu);
+	virtual bool handleChannelFromInner(Tins::PDU &pdu);
 };
 
 #endif

+ 427 - 0
daemon/include/CovertChannel/Protocols/CovertProtocol.hpp

@@ -0,0 +1,427 @@
+#ifndef COVERTPROTOCOL_H
+#define COVERTPROTOCOL_H
+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <type_traits>
+
+#include <boost/filesystem.hpp>
+
+#include "../../Config.h"
+#include "../../Notifications.h"
+#include "../../Queue.h"
+#include "../ChannelControls.h"
+
+/**
+ * @class CovertProtocol
+ *
+ * An unidirectional Covert Channel protocol with a variable size template.
+ *
+ * The protocol works unidirectional so the active side uses send to encode data to be sent and the passive side uses receive to extract data.
+ *
+ * @param N number of bytes which can be used to transmit data
+ * @param PASSIVE passive mode
+ */
+template <int N, bool PASSIVE> class CovertProtocol : public ChannelControls {
+	static_assert(N >= 1);
+
+public:
+	/**
+	 * CovertProtocol constructor
+	 */
+	CovertProtocol() : fileDirectory(Config::getValue("filedirectory")) {}
+
+	/**
+	 * CovertProtocol destructor
+	 *
+	 * Closes the file
+	 */
+	~CovertProtocol() { file.close(); }
+
+	/**
+	 * send encodes the data into the data array
+	 *
+	 * 1. Send file name size (length)
+	 * 2. Send file name
+	 * 3. Send data size (length)
+	 * 4. Send data
+	 *
+	 * @param data must be an array of size N
+	 */
+	void send(uint8_t *const data) {
+		static_assert(!PASSIVE);
+
+		switch (state) {
+		case ProtocolState::idle:
+			data[0] = 0;
+			return;
+
+		case ProtocolState::fileNameSize:
+			fileNameSize = fileName.size();
+			data[0] = fileNameSize;
+			fileNamePosition = 0;
+			state = ProtocolState::fileName;
+			break;
+
+		case ProtocolState::fileName:
+			if (fileNameSize - fileNamePosition >= N) {
+				for (int i = 0; i < N; i++) {
+					data[i] = fileName.at(fileNamePosition++);
+				}
+			} else {
+				int diff = fileNameSize - fileNamePosition;
+				for (int i = 0; i < diff; i++) {
+					data[i] = fileName.at(fileNamePosition++);
+				}
+			}
+
+			if (fileNamePosition == fileNameSize) {
+				file.open(fileDirectory + fileName, std::ios::in | std::ios::binary | std::ios::ate);
+				if (!file.is_open()) {
+					file.close();
+					std::cerr << "File \"" << fileName << "\" does exists. Resetting!!!" << std::endl;
+					Notifications::newNotification("File \"" + fileName + "\" does not exists. Skipping.");
+					// state = ProtocolState::error;
+					Queue::channel->reset();
+					return;
+				}
+
+				dataSize = file.tellg();
+				dataPosition = 0;
+				file.seekg(std::ios::beg);
+
+				state = ProtocolState::dataSize;
+			}
+
+			break;
+
+		case ProtocolState::dataSize:
+			if constexpr (N >= 4) {
+				for (; dataPosition < 4; dataPosition++) {
+					data[dataPosition] = dataSize >> ((3 - dataPosition) * 8);
+				}
+			} else {
+				uint32_t oldDataPosition = dataPosition;
+				uint32_t limit = dataPosition + N;
+				if (limit > 4) {
+					limit = 4;
+				}
+
+				for (; dataPosition < limit; dataPosition++) {
+					data[dataPosition - oldDataPosition] = dataSize >> ((3 - dataPosition) * 8);
+				}
+			}
+
+			if (dataPosition == 4) {
+				dataPosition = 0;
+				state = ProtocolState::data;
+			}
+
+			break;
+
+		case ProtocolState::data:
+			if (dataSize - dataPosition >= N) {
+				file.read((char *)data, N);
+				dataPosition += N;
+			} else {
+				file.read((char *)data, dataSize - dataPosition);
+				dataPosition = dataSize;
+			}
+
+			std::cout << "sent " << dataPosition << "/" << dataSize << std::endl;
+
+			if (dataPosition == dataSize) {
+				file.close();
+				state = ProtocolState::idle;
+				std::cout << "finished sending file \"" << fileName << "\"" << std::endl;
+				Notifications::newNotification("Finished sending file \"" + fileName + "\".");
+
+				// schedule next file transfer
+				Queue::schedule();
+			}
+
+			break;
+
+		case ProtocolState::error:
+			Queue::channel->reset();
+			break;
+		}
+
+		return;
+	}
+
+	/**
+	 * receive extracts data from the data array
+	 *
+	 * 1. Receive file name size (length)
+	 * 2. Receive file name
+	 * 3. Receive data size (length)
+	 * 4. Receive data
+	 *
+	 * @param data must be an array of size N
+	 */
+	void receive(const uint8_t *const data) {
+		static_assert(PASSIVE);
+
+		switch (state) {
+		case ProtocolState::idle:
+			if (data[0] == 0) {
+				return;
+			}
+
+			// no break because the first data received is the filename size
+		case ProtocolState::fileNameSize:
+			Notifications::newNotification("Incoming transmission.");
+			std::cout << "incoming file transmission" << std::endl;
+
+			fileNameSize = data[0];
+			fileNamePosition = 0;
+			fileName = "";
+			transferStart = std::time(nullptr);
+			state = ProtocolState::fileName;
+			break;
+
+		case ProtocolState::fileName:
+			if (fileNameSize - fileNamePosition >= N) {
+				uint8_t oldFileNamePosition = fileNamePosition;
+				for (; fileNamePosition < oldFileNamePosition + N; fileNamePosition++) {
+					fileName += data[fileNamePosition - oldFileNamePosition];
+				}
+			} else {
+				uint8_t oldFileNamePosition = fileNamePosition;
+				for (; fileNamePosition < fileNameSize; fileNamePosition++) {
+					fileName += data[fileNamePosition - oldFileNamePosition];
+				}
+			}
+
+			if (fileNamePosition == fileNameSize) {
+				file.open(fileDirectory + fileName, std::ios::in);
+				if (file.is_open()) {
+					file.close();
+					std::cerr << "File \"" << fileName << "\" already exists. Resetting!!!" << std::endl;
+					Notifications::newNotification("File \"" + fileName + "\" already exists. Skipping.");
+					// state = ProtocolState::error;
+					Queue::channel->reset();
+					return;
+				}
+
+				file.close();
+
+				file.open(fileDirectory + fileName, std::ios::out | std::ios::binary | std::ios::app);
+				if (!file.is_open()) {
+					std::cerr << "File \"" << fileName << "\" could not be opened! Resetting!!!" << std::endl;
+					Notifications::newNotification("File \"" + fileName + "\" could not be written. Skipping.");
+					// state = ProtocolState::error;
+					Queue::channel->reset();
+					return;
+				}
+
+				dataSize = 0;
+				dataPosition = 0;
+				state = ProtocolState::dataSize;
+
+				std::cout << "starting receiving file \"" << fileName << "\"" << std::endl;
+				Notifications::newNotification("Receiving file \"" + fileName + "\".");
+			}
+
+			break;
+
+		case ProtocolState::dataSize:
+			if constexpr (N >= 4) {
+				for (; dataPosition < 4; dataPosition++) {
+					dataSize = dataSize | data[dataPosition] << ((3 - dataPosition) * 8);
+				}
+			} else {
+				uint32_t oldDataPosition = dataPosition;
+				uint32_t limit = dataPosition + N;
+				if (limit > 4) {
+					limit = 4;
+				}
+
+				for (; dataPosition < limit; dataPosition++) {
+					dataSize = dataSize | data[dataPosition - oldDataPosition] << ((3 - dataPosition) * 8);
+				}
+			}
+
+			if (dataPosition == 4) {
+				dataPosition = 0;
+				state = ProtocolState::data;
+			}
+
+			break;
+		case ProtocolState::data:
+
+			if (dataSize - dataPosition >= N) {
+				file.write((char *)data, N);
+				dataPosition += N;
+			} else {
+				file.write((char *)data, dataSize - dataPosition);
+				dataPosition = dataSize;
+			}
+
+			std::cout << "received " << dataPosition << "/" << dataSize << std::endl;
+
+			if (dataPosition == dataSize) {
+				file.close();
+				state = ProtocolState::idle;
+
+				std::cout << "finished receiving file \"" << fileName << "\"" << std::endl;
+				Notifications::newNotification("Finished receiving file \"" + fileName + "\".");
+			}
+
+			break;
+		case ProtocolState::error:
+			//
+			break;
+		}
+	}
+
+	/* ChannelControls */
+
+	/**
+	 * Starts sending a file.
+	 *
+	 * Starts sending a file if no transmission is running and the file exists.
+	 *
+	 * @param fileName name of the file in the file directory
+	 * @return true - file will be sent | false - file was not accepted
+	 */
+	virtual bool sendFile(const std::string &fileName) {
+		if constexpr (PASSIVE) {
+			return false;
+		}
+
+		if (state != ProtocolState::idle || file.is_open()) {
+			return false;
+		}
+
+		file.open(fileDirectory + fileName, std::ios::in);
+		if (file.is_open()) {
+			file.close();
+			this->fileName = fileName;
+			transferStart = std::time(nullptr);
+			state = ProtocolState::fileNameSize;
+			Notifications::newNotification("Start sending file \"" + fileName + "\".");
+			std::cout << "starting sending file \"" << fileName << "\"" << std::endl;
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * Get the progress
+	 *
+	 * @return progress counters
+	 */
+	virtual std::pair<uint32_t, uint32_t> getProgress() {
+		if (state == ProtocolState::data) {
+			return std::pair<uint32_t, uint32_t>(dataPosition, dataSize);
+		} else {
+			return std::pair<uint32_t, uint32_t>(0, 0);
+		}
+	}
+
+	/**
+	 * Test if a transfer is running
+	 *
+	 * @return true - a transfer runs | false - no transfer runs
+	 */
+	virtual bool isTransferRunning() { return state != ProtocolState::idle; }
+
+	/**
+	 * Resets protocol state
+	 */
+	virtual void reset() {
+		file.close();
+
+		if (PASSIVE && state == ProtocolState::data) {
+			// delete file if in passive mode and data has been received
+			deleteFile(fileName);
+			Notifications::newNotification("Transfer of file \"" + fileName + "\" was aborted.");
+		}
+
+		state = ProtocolState::idle;
+		dataSize = 0;
+		dataPosition = 0;
+		fileNameSize = 0;
+		fileNamePosition = 0;
+		fileName = "";
+
+		Queue::schedule();
+	}
+
+	/**
+	 * Test if a transfer is running
+	 *
+	 * @return true - a transfer runs | false - no transfer runs
+	 */
+	virtual std::time_t getTransferStart() { return transferStart; }
+
+	/**
+	 * Get file name of the file which is currently be sent or received.
+	 *
+	 * @return file name
+	 */
+	virtual std::string getFileName() { return fileName; };
+
+	/* =============== */
+
+private:
+	/**
+	 * folder of the files
+	 */
+	const std::string fileDirectory;
+
+	/**
+	 * States of the data transmission
+	 *
+	 * @warning error state cannot be recovered!!!
+	 */
+	enum struct ProtocolState { idle, fileNameSize, fileName, dataSize, data, error };
+
+	/**
+	 * State of the data transmission
+	 */
+	ProtocolState state = ProtocolState::idle;
+
+	/**
+	 * size of the file to be sent/received
+	 */
+	uint32_t dataSize;
+
+	/**
+	 * position in the file to be sent/received
+	 */
+	uint32_t dataPosition;
+
+	/**
+	 * file to be sent/received
+	 */
+	std::fstream file;
+
+	/**
+	 * size of the file name
+	 */
+	uint8_t fileNameSize;
+
+	/**
+	 * position of the file name so far sent/received
+	 */
+	uint8_t fileNamePosition;
+
+	/**
+	 * Deletes a file
+	 */
+	void deleteFile(const std::string &fileName) {
+		std::string fname = this->fileDirectory;
+		fname.append(fileName);
+
+		if (boost::filesystem::exists(fname)) {
+			boost::filesystem::remove(fname);
+		}
+	}
+};
+
+#endif

+ 224 - 0
daemon/include/CovertChannel/Protocols/CovertProtocolBidirectional.hpp

@@ -0,0 +1,224 @@
+#ifndef COVERTPROTOCOLBIDIRECTIONAL_H
+#define COVERTPROTOCOLBIDIRECTIONAL_H
+
+#include <fstream>
+#include <string>
+#include <type_traits>
+
+#include "../ChannelControls.h"
+#include "CovertProtocol.hpp"
+
+/**
+ * @class CovertProtocolBidirectional
+ *
+ * A bidirectional Covert Channel protocol with a variable size template.
+ *
+ * The protocol works bidirectional so the active side uses send to encode data to be sent and the passive side uses receive to extract data. And the passive
+ * side uses send to send ACKs and and the active side uses receive to receive ACKs.
+ *
+ * @param N number of bytes which can be used to transmit data
+ * @param PASSIVE passive mode
+ */
+template <int N, bool PASSIVE> class CovertProtocolBidirectional : public ChannelControls {
+	static_assert(N >= 2);
+
+public:
+	/**
+	 * CovertProtocolBidirectional constructor
+	 */
+	CovertProtocolBidirectional() : segment(PASSIVE ? 1 : 0) {
+		lastData = new uint8_t[N]();
+		protocol.reset();
+	}
+
+	/**
+	 * CovertProtocol destructor
+	 */
+	~CovertProtocolBidirectional() { delete[](lastData); }
+
+	/**
+	 * send encodes the data into the data array
+	 *
+	 * @param data must be an array of size N
+	 */
+	void send(uint8_t *const data) {
+		if (sendResetFlag) {
+			sendResetFlag = false;
+			data[0] = (segment << 6) | 1;
+			std::memset(data + 1, 0, N - 1);
+			return;
+		}
+
+		if (sendSegment()) {
+			std::memcpy(data, lastData, N);
+			return;
+		}
+
+		if constexpr (!PASSIVE) {
+			protocol.send(data + 1);
+		}
+
+		data[0] = (segment << 6);
+
+		// save data to lastData
+		std::memcpy(lastData, data, N);
+	}
+
+	/**
+	 * receive extracts data from the data array
+	 *
+	 * @param data must be an array of size N
+	 */
+	void receive(const uint8_t *const data) {
+		uint8_t seg = data[0] >> 6;
+		bool reset = data[0] & 1;
+
+		if (reset) {
+			resetState();
+			return;
+		}
+
+		if (receiveSegment(seg)) {
+			return;
+		}
+
+		if constexpr (PASSIVE) {
+			protocol.receive(data + 1);
+		}
+	}
+
+	/* ChannelControls */
+
+	/**
+	 * Starts sending a file.
+	 *
+	 * Starts sending a file if no transmission is running and the file exists.
+	 *
+	 * @param fileName name of the file in the file directory
+	 * @return true - file will be sent | false - file was not accepted
+	 */
+	virtual bool sendFile(const std::string &fileName) { return protocol.sendFile(fileName); }
+
+	/**
+	 * Get the progress
+	 *
+	 * @return progress counters
+	 */
+	virtual std::pair<uint32_t, uint32_t> getProgress() { return protocol.getProgress(); }
+
+	/**
+	 * Test if a transfer is running
+	 *
+	 * @return true - a transfer runs | false - no transfer runs
+	 */
+	virtual bool isTransferRunning() { return protocol.isTransferRunning(); }
+
+	/**
+	 * Resets state and sets reset flag so a reset signal is sent in the next packet
+	 */
+	virtual void reset() {
+		sendResetFlag = true;
+		resetState();
+	}
+
+	/**
+	 * Test if a transfer is running
+	 *
+	 * @return true - a transfer runs | false - no transfer runs
+	 */
+	virtual std::time_t getTransferStart() { return protocol.getTransferStart(); }
+
+	/**
+	 * Get file name of the file which is currently be sent or received.
+	 *
+	 * @return file name
+	 */
+	virtual std::string getFileName() { return protocol.getFileName(); };
+
+	/* =============== */
+
+private:
+	/**
+	 * current segment counter
+	 */
+	uint8_t segment;
+
+	/**
+	 * last acknowledged segment counter
+	 */
+	uint8_t lastACKedSegment = 0;
+
+	/**
+	 * lastData sent
+	 */
+	uint8_t *lastData;
+
+	/**
+	 * Simple protocol
+	 */
+	CovertProtocol<N - 1, PASSIVE> protocol;
+
+	/**
+	 * Sends a reset signal on next send call
+	 */
+	bool sendResetFlag = true;
+
+	/**
+	 * Evaluates received segment number and increases own segment number if necessary
+	 *
+	 * @param seg received segment number
+	 * @return true if segment should discarded
+	 */
+	inline bool receiveSegment(uint8_t seg) {
+		if constexpr (PASSIVE) {
+
+			if (seg == segment && seg == increaseSegment(lastACKedSegment, 1)) {
+				lastACKedSegment = seg;
+				return false;
+			} else {
+				return true;
+			}
+		} else {
+
+			if (seg == increaseSegment(segment, 1) && seg == increaseSegment(lastACKedSegment, 2)) {
+				lastACKedSegment = increaseSegment(lastACKedSegment, 1);
+				return false;
+			} else {
+				return true;
+			}
+		}
+	}
+
+	/**
+	 * Returns true if the segment should be repeated.
+	 *
+	 * @return true if segment should be repeated
+	 */
+	inline bool sendSegment() {
+		if (segment == lastACKedSegment) {
+			segment = increaseSegment(segment, 1);
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 * Calculates seg + inc
+	 * @return seg + inc
+	 */
+	inline uint8_t increaseSegment(uint8_t seg, uint8_t inc) { return seg + inc & 0x3; }
+
+	/**
+	 * Resets the state of the protocol.
+	 * This method can / should only be called from this class.
+	 */
+	void resetState() {
+		protocol.reset();
+		segment = (PASSIVE ? 1 : 0);
+		lastACKedSegment = 0;
+		std::memset(lastData, 0, N);
+	}
+};
+
+#endif

+ 0 - 82
daemon/include/CovertChannel/ProxyChannel.h

@@ -1,82 +0,0 @@
-#ifndef PROXYCHANNEL_H
-#define PROXYCHANNEL_H
-
-#include "CovertChannel.h"
-
-/**
- * @class ForwardChannel
- *
- * A CovertChannel which forwards the traffic it captures.
- */
-class ProxyChannel : public CovertChannel {
-public:
-	/**
-	 * Sets up a CovertChannel.
-	 *
-	 * Creates a CovertChannel, sets the network interfaces for sniffing and sending and sets the filter.
-	 *
-	 * @param innerInterface name of the interface of the inner network
-	 * @param outerInterface name of the interface of the outer network
-	 * @param filter pcap filter string which will be set for the channel sniffers and negated for the forward sniffers
-	 * @param relayOnly true - server only relays traffic | false - server redirects traffic over another relay
-	 */
-	ProxyChannel(const std::string &innerInterface, const std::string &outerInterface, const std::string &ownIP, const std::string &partnerIP,
-	             const std::string &originIP, const std::string &targetIP, const std::string &targetPort, const std::string &ownMAC,
-	             const std::string &originMAC, const std::string &channelGatewayMAC, const std::string &gatewayMAC, const bool relayOnly);
-
-	/**
-	 * Destroys the CovertChannel.
-	 */
-	virtual ~ProxyChannel();
-
-protected:
-	/**
-	 * Handler for sniffed packets filterd to forward from the outer network.
-	 *
-	 * Handles incoming packets and forwards them.
-	 *
-	 * @param pdu sniffed packet
-	 *
-	 * @return false = stop loop | true = continue loop
-	 */
-	virtual bool handleChannelFromOuter(Tins::PDU &pdu);
-
-	/**
-	 * Handler for sniffed packets filterd to forward from the inner network.
-	 *
-	 * Handles incoming packets and forwards them.
-	 *
-	 * @param pdu sniffed packet
-	 *
-	 * @return false = stop loop | true = continue loop
-	 */
-	virtual bool handleChannelFromInner(Tins::PDU &pdu);
-
-	/**
-	 * Handler for sniffed packets filterd to use as channel from the outer network.
-	 *
-	 * Handles incoming packets and redirets them.
-	 *
-	 * @param pdu sniffed packet
-	 *
-	 * @return false = stop loop | true = continue loop
-	 */
-	virtual bool handlePartnerFromOuter(Tins::PDU &pdu);
-
-	/**
-	 * Relay option which activates relay only mode
-	 */
-	const bool relayOnly;
-
-	const Tins::IPv4Address ownAddress;
-	const Tins::IPv4Address partnerAddress;
-	const Tins::IPv4Address originAddress;
-	const Tins::IPv4Address targetAddress;
-
-	const Tins::HWAddress<6> ownMAC;
-	const Tins::HWAddress<6> channelGatewayMAC;
-	const Tins::HWAddress<6> gatewayMAC;
-	const Tins::HWAddress<6> originMAC;
-};
-
-#endif

+ 42 - 0
daemon/include/FileManager.h

@@ -2,6 +2,7 @@
 #define FILEMANAGER_H
 
 #include <fstream>
+#include <tuple>
 #include <vector>
 
 /**
@@ -59,6 +60,12 @@ private:
 	 */
 	std::vector<std::vector<std::string>> list;
 
+	/**
+	 * extendedlist vector for extendend list command
+	 * contains name head and size
+	 */
+	std::vector<std::vector<std::tuple<std::string, std::string, double>>> extendedlist;
+
 public:
 	enum { max_data_length = 4096 };
 
@@ -195,6 +202,41 @@ public:
 	 * @return return code 0: ok 1: disabled in config 2: file not found
 	 */
 	virtual Error deleteFile(const std::string &filename);
+
+	/**
+	 * Open extendedlist command. Set extendedlist vector and claculate chunks
+	 *
+	 * @return chunks of the resulting list | if a filename is too long it returns
+	 * -1
+	 */
+	virtual int openExtendedList();
+
+	/**
+	 * Open extendedlist command. Set extendedlist vector and claculate chunks
+	 *
+	 * @return chunks of the resulting extendedlist | if a filename is too long it returns
+	 * -1
+	 */
+	virtual int getRemainingExtendedListChunks();
+
+	/**
+	 * Return next chunk for extendedlistdata command.
+	 * You need to run openList before.
+	 *
+	 * @return next chnuk vector
+	 */
+	virtual std::vector<std::tuple<std::string, std::string, double>> getNextChunkFromExtendedList();
+
+	/**
+	 * @return size of the extendendlist vector
+	 */
+	virtual int getExtendedListSize();
+
+	/**
+	 * Cancel current extendendlist command.
+	 * Clear extendendlist vector and set remainingListChunks zero.
+	 */
+	virtual void cancelExtendedList();
 };
 
 #endif

+ 40 - 0
daemon/include/JsonCommander.h

@@ -92,6 +92,16 @@ private:
 	 */
 	int putFileReceived;
 
+	/**
+	 * Total chunk number from current put transfer
+	 */
+	int putSize;
+
+	/**
+	 * Total chunk number from current get transfer
+	 */
+	int getSize;
+
 	/**
 	 * Executes the status command
 	 */
@@ -146,6 +156,36 @@ private:
 	 * Executes the deletefile command
 	 */
 	Response executeDeleteFile(const Json::Value &message);
+
+	/**
+	 * Executes the extendedstatus command
+	 */
+	Response executeExtendedStatus(const Json::Value &message);
+
+	/**
+	 * Executes the notifications command
+	 */
+	Response executeNotifications(const Json::Value &message);
+
+	/**
+	 * Executes the extendedlist command
+	 */
+	Response executeExtendedList(const Json::Value &message);
+
+	/**
+	 * Executes the extendedlistdata command
+	 */
+	Response executeExtendedListData(const Json::Value &message);
+
+	/**
+	 * Executes the queue command
+	 */
+	Response executeQueue(const Json::Value &message);
+
+	/**
+	 * Executes the dequeue command
+	 */
+	Response executeDequeue(const Json::Value &message);
 };
 
 #endif

+ 39 - 0
daemon/include/Notifications.h

@@ -0,0 +1,39 @@
+#ifndef NOTIFICATIONS_H
+#define NOTIFICATIONS_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+/**
+ * Namespace which holds notifications
+ */
+namespace Notifications {
+/**
+ * Add a new notification. Will be stored in messages and delivered by getMessages
+ * @param message the new notification
+ */
+void newNotification(const std::string &message);
+
+/**
+ * Get all messages for a given users
+ * Deletes all messages wihch are older than a week
+ * @param user
+ * @return vector with all messages
+ */
+std::vector<std::string> getMessages(const std::string &user);
+
+/**
+ * Stores the notifications
+ * First is the timestamp
+ * Second is the message
+ */
+extern std::vector<std::pair<long int, std::string>> messages;
+
+/**
+ * Stores information when a user get messages for the last time
+ */
+extern std::map<std::string, long int> userTimeStamps;
+}; // namespace Notifications
+
+#endif

+ 54 - 0
daemon/include/Queue.h

@@ -0,0 +1,54 @@
+#ifndef QUEUE_H
+#define QUEUE_H
+
+#include <deque>
+#include <mutex>
+#include <string>
+
+#include "CovertChannel/ChannelControls.h"
+
+/**
+ * Namespace wich manages the queue for the covert channel
+ */
+namespace Queue {
+/**
+ * Adds file to queue
+ * @param file add file to queue
+ */
+bool push(const std::string &file);
+
+/**
+ * Remove file from queue. Checks if remove is allowed
+ * @param file file to remove from queue
+ */
+bool remove(const std::string &file);
+
+/**
+ * Schedules one file to be transfered over the covert channel.
+ * schedule() must be called again to send the next file.
+ */
+void schedule();
+
+/**
+ * Returns the name of the file of the current transfer. Empty otherwise.
+ * @return file name
+ */
+std::string curTransfer();
+
+/**
+ * The queue. Stores filenames. Acts as FIFO.
+ */
+extern std::deque<std::string> queue;
+
+/**
+ * Mutex to lock queue while editing it.
+ */
+extern std::mutex mtx;
+
+/**
+ * Covert channel which will be used to send files.
+ */
+extern ChannelControls *channel;
+}; // namespace Queue
+
+#endif // QUEUE_H

+ 1 - 1
daemon/src/CMakeLists.txt

@@ -2,7 +2,7 @@ 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/CovertChannel/ProxyChannel.cpp)
+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)
 
 # dependencies used by server only
 find_package(libtins 4.2 REQUIRED)

+ 11 - 22
daemon/src/CovertChannel/CovertChannel.cpp

@@ -1,11 +1,9 @@
 #include "../../include/CovertChannel/CovertChannel.h"
 #include <cstdlib>
 #include <iostream>
-#include <thread>
 
 CovertChannel::CovertChannel(const std::string &innerInterface, const std::string &outerInterface, const std::string &innerForwardFilter,
-                             const std::string &outerForwardFilter, const std::string &innerChannelFilter, const std::string &outerChannelFilter,
-                             const std::string &outerPartnerFilter)
+                             const std::string &outerForwardFilter, const std::string &innerChannelFilter, const std::string &outerChannelFilter)
     : innerSender(innerInterface), outerSender(outerInterface) {
 	Tins::SnifferConfiguration config;
 	config.set_promisc_mode(true);
@@ -18,9 +16,8 @@ CovertChannel::CovertChannel(const std::string &innerInterface, const std::strin
 		outerForwardSniffer = new Tins::Sniffer(outerInterface, config);
 		innerChannelSniffer = new Tins::Sniffer(innerInterface, config);
 		outerChannelSniffer = new Tins::Sniffer(outerInterface, config);
-		outerPartnerSniffer = new Tins::Sniffer(outerInterface, config);
 
-		setFilter(innerForwardFilter, outerForwardFilter, innerChannelFilter, outerChannelFilter, outerPartnerFilter);
+		setFilter(innerForwardFilter, outerForwardFilter, innerChannelFilter, outerChannelFilter);
 	} catch (const Tins::pcap_error &e) {
 		std::cerr << "An error accured setting up the sniffer: " << e.what() << std::endl;
 		std::exit(EXIT_FAILURE);
@@ -32,35 +29,29 @@ CovertChannel::~CovertChannel() {
 	outerForwardSniffer->stop_sniff();
 	innerChannelSniffer->stop_sniff();
 	outerChannelSniffer->stop_sniff();
-	outerPartnerSniffer->stop_sniff();
+	innerForwardSnifferThread.join();
+	outerForwardSnifferThread.join();
+	innerChannelSnifferThread.join();
+	outerChannelSnifferThread.join();
 	delete (innerForwardSniffer);
 	delete (outerForwardSniffer);
 	delete (innerChannelSniffer);
 	delete (outerChannelSniffer);
-	delete (outerPartnerSniffer);
 }
 
 void CovertChannel::setFilter(const std::string &innerForwardFilter, const std::string &outerForwardFilter, const std::string &innerChannelFilter,
-                              const std::string &outerChannelFilter, const std::string &outerPartnerFilter) {
+                              const std::string &outerChannelFilter) {
 	innerForwardSniffer->set_filter(innerForwardFilter);
 	outerForwardSniffer->set_filter(outerForwardFilter);
 	innerChannelSniffer->set_filter(innerChannelFilter);
 	outerChannelSniffer->set_filter(outerChannelFilter);
-	outerPartnerSniffer->set_filter(outerPartnerFilter);
 }
 
 void CovertChannel::startSniffing() {
-	std::thread innerForwardSnifferThread(&CovertChannel::startInnerForwardSniffing, this);
-	std::thread outerForwardSnifferThread(&CovertChannel::startOuterForwardSniffing, this);
-	std::thread innerChannelSnifferThread(&CovertChannel::startInnerChannelSniffing, this);
-	std::thread outerChannelSnifferThread(&CovertChannel::startOuterChannelSniffing, this);
-	std::thread outerPartnerSnifferThread(&CovertChannel::startOuterPartnerSniffing, this);
-
-	innerForwardSnifferThread.detach();
-	outerForwardSnifferThread.detach();
-	innerChannelSnifferThread.detach();
-	outerChannelSnifferThread.detach();
-	outerPartnerSnifferThread.detach();
+	innerForwardSnifferThread = std::thread(&CovertChannel::startInnerForwardSniffing, this);
+	outerForwardSnifferThread = std::thread(&CovertChannel::startOuterForwardSniffing, this);
+	innerChannelSnifferThread = std::thread(&CovertChannel::startInnerChannelSniffing, this);
+	outerChannelSnifferThread = std::thread(&CovertChannel::startOuterChannelSniffing, this);
 }
 
 void CovertChannel::startInnerForwardSniffing() { innerForwardSniffer->sniff_loop(make_sniffer_handler(this, &CovertChannel::handleForwardFromInner)); }
@@ -71,8 +62,6 @@ void CovertChannel::startInnerChannelSniffing() { innerChannelSniffer->sniff_loo
 
 void CovertChannel::startOuterChannelSniffing() { outerChannelSniffer->sniff_loop(make_sniffer_handler(this, &CovertChannel::handleChannelFromOuter)); }
 
-void CovertChannel::startOuterPartnerSniffing() { outerPartnerSniffer->sniff_loop(make_sniffer_handler(this, &CovertChannel::handlePartnerFromOuter)); }
-
 bool CovertChannel::handleForwardFromOuter(Tins::PDU &pdu) {
 	innerSender.send(pdu);
 

+ 15 - 1
daemon/src/CovertChannel/ForwardChannel.cpp

@@ -8,4 +8,18 @@ bool ForwardChannel::handleChannelFromOuter(Tins::PDU &pdu) { return false; }
 
 bool ForwardChannel::handleChannelFromInner(Tins::PDU &pdu) { return false; }
 
-bool ForwardChannel::handlePartnerFromOuter(Tins::PDU &pdu) { return false; }
+/* ChannelControls */
+
+bool ForwardChannel::sendFile(const std::string &fileName) { return false; }
+
+std::pair<uint32_t, uint32_t> ForwardChannel::getProgress() { return std::pair<uint32_t, uint32_t>(0, 0); }
+
+bool ForwardChannel::isTransferRunning() { return false; }
+
+void ForwardChannel::reset() {}
+
+std::time_t ForwardChannel::getTransferStart() { return 0; }
+
+std::string ForwardChannel::getFileName() { return ""; }
+
+/* =============== */

+ 0 - 82
daemon/src/CovertChannel/ProxyChannel.cpp

@@ -1,82 +0,0 @@
-#include "../../include/CovertChannel/ProxyChannel.h"
-#include <iostream>
-
-ProxyChannel::ProxyChannel(const std::string &innerInterface, const std::string &outerInterface, const std::string &ownIP, const std::string &partnerIP,
-                           const std::string &originIP, const std::string &targetIP, const std::string &targetPort, const std::string &ownMAC,
-                           const std::string &originMAC, const std::string &channelGatewayMAC, const std::string &gatewayMAC, const bool relayOnly)
-    : CovertChannel(innerInterface, outerInterface,
-                    "(not (tcp and src host " + originIP + " and dst host " + targetIP + " and dst port " + targetPort + ")) and (not (dst host " + ownIP +
-                        "))",
-                    "(not (tcp and src host " + targetIP + " and dst host " + ownIP + " and src port " + targetPort + ")) and (not (dst host " + ownIP + "))",
-                    "tcp and src host " + originIP + " and dst host " + targetIP + " and dst port " + targetPort,
-                    "tcp and src host " + targetIP + " and dst host " + ownIP + " and src port " + targetPort,
-                    "tcp and src host " + partnerIP + " and dst host " + ownIP + " and port " + targetPort),
-
-      relayOnly(relayOnly), ownAddress(ownIP), partnerAddress(partnerIP), originAddress(originIP), targetAddress(targetIP), ownMAC(ownMAC),
-      channelGatewayMAC(channelGatewayMAC), gatewayMAC(gatewayMAC), originMAC(originMAC) {}
-
-ProxyChannel::~ProxyChannel() {}
-
-bool ProxyChannel::handleChannelFromOuter(Tins::PDU &pdu) {
-	// TODO: check in a list how to route it and who send the request for this answer
-
-	Tins::EthernetII &eth = pdu.rfind_pdu<Tins::EthernetII>();
-	Tins::IP &ip = pdu.rfind_pdu<Tins::IP>();
-	if (relayOnly) {
-		// redirect to partner
-		eth.src_addr(ownMAC);
-		eth.dst_addr(channelGatewayMAC);
-		ip.src_addr(ownAddress);
-		ip.dst_addr(partnerAddress);
-		outerSender.send(pdu);
-	} else {
-		// Just forward it
-		// eth.src_addr(gatewayMAC);
-		// eth.dst_addr(originMAC);
-		// ip.src_addr(targetAddress);
-		// ip.dst_addr(originAddress);
-		innerSender.send(pdu);
-		std::cout << "channel from outer" << std::endl;
-	}
-	return true;
-}
-
-bool ProxyChannel::handleChannelFromInner(Tins::PDU &pdu) {
-	Tins::EthernetII &eth = pdu.rfind_pdu<Tins::EthernetII>();
-	Tins::IP &ip = pdu.rfind_pdu<Tins::IP>();
-	if (relayOnly) {
-		std::cerr << "Fixme: packet cannot be routed back so it's dropped here!!!" << std::endl;
-		// outerSender.send(pdu);
-		// TODO: add pdu to a list to check later how to route it
-	} else {
-		eth.src_addr(ownMAC);
-		eth.dst_addr(channelGatewayMAC);
-		ip.src_addr(ownAddress);
-		ip.dst_addr(partnerAddress);
-		outerSender.send(pdu);
-		std::cout << "channel from inner" << std::endl;
-	}
-	return true;
-}
-
-bool ProxyChannel::handlePartnerFromOuter(Tins::PDU &pdu) {
-	Tins::EthernetII &eth = pdu.rfind_pdu<Tins::EthernetII>();
-	Tins::IP &ip = pdu.rfind_pdu<Tins::IP>();
-	if (relayOnly) {
-		// redirect to target
-		eth.src_addr(ownMAC);
-		eth.dst_addr(gatewayMAC);
-		ip.src_addr(ownAddress);
-		ip.dst_addr(targetAddress);
-		outerSender.send(pdu);
-		std::cout << "relay" << std::endl;
-	} else {
-		eth.src_addr(gatewayMAC);
-		eth.dst_addr(originMAC);
-		ip.src_addr(targetAddress);
-		ip.dst_addr(originAddress);
-		innerSender.send(pdu);
-		std::cout << "partner" << std::endl;
-	}
-	return true;
-}

+ 67 - 1
daemon/src/FileManager.cpp

@@ -2,10 +2,13 @@
 
 #include <boost/filesystem.hpp>
 #include <boost/range/iterator_range.hpp>
+#include <string>
 
 #include "../include/Config.h"
 
-FileManager::FileManager() : fileDirectory(Config::getValue("filedirectory")), deleteAllowed(Config::getValue("deleteAllowed") == "true") {}
+FileManager::FileManager()
+    : deleteAllowed(Config::getValue("deleteAllowed") == "true"),
+      fileDirectory(Config::getValue("filedirectory") + ((Config::getValue("filedirectory").back() != '/') ? "/" : "")) {}
 
 FileManager::~FileManager() {
 	cancelPut();
@@ -189,3 +192,66 @@ FileManager::Error FileManager::deleteFile(const std::string &filename) {
 
 	return no_error;
 }
+
+int FileManager::openExtendedList() {
+	this->extendedlist.push_back(std::vector<std::tuple<std::string, std::string, double>>());
+
+	int cursize = 0;
+
+	for (const auto &entry : boost::filesystem::directory_iterator(this->fileDirectory)) {
+		// getting filename from
+		if (boost::filesystem::is_directory(entry.path()))
+			continue;
+		const std::string name = entry.path().filename().string();
+
+		// calc head
+		std::string head;
+		std::pair<std::vector<char>, FileManager::Error> h = getBytesFromFile(name, 32);
+		if (h.second == FileManager::no_error) {
+			std::string s(h.first.begin(), h.first.end());
+			head = s;
+		} else
+			head = "";
+
+		// calc size in kbyte
+		std::string fname = this->fileDirectory;
+		fname.append(name);
+		std::ifstream file(fname, std::ios::ate | std::ios::binary);
+		double size = (double)((double)file.tellg() / (double)1000.0);
+
+		cursize += name.length() + head.length() + std::to_string(size).length() + 7;
+		if (cursize > max_data_length) {
+			this->extendedlist.push_back(std::vector<std::tuple<std::string, std::string, double>>());
+			cursize = 0;
+		}
+		std::tuple<std::string, std::string, double> p = std::tuple(name, head, size);
+		this->extendedlist.back().push_back(p);
+	}
+
+	if (this->extendedlist.size() == 1 && this->extendedlist.back().size() == 0) {
+		this->extendedlist.clear();
+	}
+
+	return this->extendedlist.size();
+}
+
+int FileManager::getRemainingExtendedListChunks() { return this->extendedlist.size(); }
+
+std::vector<std::tuple<std::string, std::string, double>> FileManager::getNextChunkFromExtendedList() {
+	if (getRemainingExtendedListChunks() == 0)
+		return std::vector<std::tuple<std::string, std::string, double>>();
+
+	std::vector<std::tuple<std::string, std::string, double>> ret = this->extendedlist.back();
+	this->extendedlist.pop_back();
+
+	return ret;
+}
+
+int FileManager::getExtendedListSize() {
+	int size = 0;
+	for (const std::vector<std::tuple<std::string, std::string, double>> v : this->extendedlist)
+		size += v.size();
+	return size;
+}
+
+void FileManager::cancelExtendedList() { this->extendedlist.clear(); }

+ 225 - 0
daemon/src/JsonCommander.cpp

@@ -1,4 +1,7 @@
 #include "../include/JsonCommander.h"
+#include "../include/Config.h"
+#include "../include/Notifications.h"
+#include "../include/Queue.h"
 #include "../include/UserManager.h"
 #include "../include/base64.h"
 
@@ -14,6 +17,12 @@ JsonCommander::JsonCommander(FileManager &fileManager) : fileManager(fileManager
 	commandsMap["head"] = &JsonCommander::executeHead;
 	commandsMap["deleteme"] = &JsonCommander::executeDeleteMe;
 	commandsMap["deletefile"] = &JsonCommander::executeDeleteFile;
+	commandsMap["extendedstatus"] = &JsonCommander::executeExtendedStatus;
+	commandsMap["notifications"] = &JsonCommander::executeNotifications;
+	commandsMap["extendedlist"] = &JsonCommander::executeExtendedList;
+	commandsMap["extendedlistdata"] = &JsonCommander::executeExtendedListData;
+	commandsMap["queue"] = &JsonCommander::executeQueue;
+	commandsMap["dequeue"] = &JsonCommander::executeDequeue;
 }
 
 JsonCommander::~JsonCommander() {}
@@ -161,6 +170,7 @@ JsonCommander::Response JsonCommander::executePut(const Json::Value &message) {
 			response.json["accept"] = true;
 			response.json["error"] = "";
 			this->putFileReceived = message["chunks"].asInt();
+			this->putSize = message["chunks"].asInt();
 		} else {
 			response.json["accept"] = false;
 			response.json["error"] = "file already exists";
@@ -266,6 +276,7 @@ JsonCommander::Response JsonCommander::executeGet(const Json::Value &message) {
 
 		if (opened.first) {
 			this->getFileRemaining = opened.second;
+			this->getSize = opened.second;
 			response.json["accept"] = true;
 			response.json["chunks"] = this->getFileRemaining;
 			response.json["error"] = "";
@@ -516,3 +527,217 @@ JsonCommander::Response JsonCommander::executeDeleteFile(const Json::Value &mess
 
 	return response;
 }
+
+JsonCommander::Response JsonCommander::executeExtendedStatus(const Json::Value &message) {
+	JsonCommander::Response response;
+	response.json["command"] = "extendedstatus";
+
+	// get status from client server transfers
+	int index = 0;
+	if (fileManager.isUploading()) {
+		response.json["transfersclientserver"][index]["upload"] = true;
+		response.json["transfersclientserver"][index]["file"] = fileManager.getPutBaseFileName();
+		int progress = 0;
+		if (this->putSize != 0) {
+			double d = (double)this->putFileReceived / (double)this->putSize;
+			progress = (int)(d * 100);
+		}
+		response.json["transfersclientserver"][index]["progress"] = progress;
+		index++;
+	}
+	if (fileManager.isDownloading()) {
+		response.json["transfersclientserver"][index]["upload"] = false;
+		response.json["transfersclientserver"][index]["file"] = fileManager.getGetBaseFileName();
+		int progress = 0;
+		if (this->getSize != 0) {
+			double d = 1 - ((double)this->getFileRemaining / (double)this->getSize);
+			progress = (int)(d * 100);
+		}
+		response.json["transfersclientserver"][index]["progress"] = progress;
+	}
+
+	// get status from covert channel
+	index = 0;
+	if (Queue::channel != nullptr && Queue::channel->isTransferRunning()) {
+		response.json["transfersserverserver"][index]["type"] = Config::getValue("passiveMode").compare("true") == 0 ? "download" : "upload";
+		response.json["transfersserverserver"][index]["file"] = Queue::curTransfer();
+
+		auto rawprogress = Queue::channel->getProgress();
+		double d;
+		if (rawprogress.second == 0)
+			d = 0.0;
+		else
+			d = ((double)rawprogress.first / (double)rawprogress.second);
+
+		int progress = (int)(d * 100);
+		response.json["transfersserverserver"][index]["progress"] = progress;
+
+		std::time_t startTime = Queue::channel->getTransferStart();
+		std::time_t curTime = std::time(nullptr);
+		std::time_t diffTime = curTime - startTime;
+		double speed = (double)rawprogress.first / (double)diffTime;
+
+		response.json["transfersserverserver"][index]["speed"] = speed;
+		response.json["transfersserverserver"][index]["method"] = Config::getValue("covertChannelMode");
+		index++;
+	}
+
+	for (int i = 0; i < Queue::queue.size(); i++) {
+		response.json["transfersserverserver"][index]["type"] = "queued";
+		response.json["transfersserverserver"][index]["file"] = Queue::queue[i];
+		response.json["transfersserverserver"][index]["progress"] = 0;
+		response.json["transfersserverserver"][index]["speed"] = 0;
+		response.json["transfersserverserver"][index]["method"] = Config::getValue("covertChannelMode");
+		index++;
+	}
+
+	response.json["accept"] = true;
+	response.json["error"] = "";
+	response.action = send;
+	return response;
+}
+
+JsonCommander::Response JsonCommander::executeNotifications(const Json::Value &message) {
+	JsonCommander::Response response;
+	response.json["command"] = "notifications";
+
+	Json::Value array;
+	std::vector<std::string> v = Notifications::getMessages(currentUser);
+	for (int i = 0; i < v.size(); i++)
+		array.append(v.at(i));
+
+	response.action = send;
+	response.json["messages"] = array;
+	response.json["accept"] = true;
+	response.json["error"] = "";
+
+	return response;
+}
+
+JsonCommander::Response JsonCommander::executeExtendedList(const Json::Value &message) {
+	JsonCommander::Response response;
+	response.action = send;
+	response.json["command"] = "extendedlist";
+
+	int chunks;
+	if (fileManager.getRemainingExtendedListChunks() > 0) {
+		response.json["accept"] = false;
+		response.json["chunks"] = -1;
+		response.json["items"] = -1;
+		response.json["error"] = "there is already an open extendedlist command";
+	} else if ((chunks = fileManager.openExtendedList()) == -1) {
+		response.json["accept"] = false;
+		response.json["chunks"] = -1;
+		response.json["items"] = -1;
+		response.json["error"] = "there is a filename which is too long";
+	} else {
+		response.json["accept"] = true;
+		response.json["chunks"] = chunks;
+		response.json["items"] = fileManager.getExtendedListSize();
+		response.json["error"] = "";
+	}
+
+	return response;
+}
+
+JsonCommander::Response JsonCommander::executeQueue(const Json::Value &message) {
+	JsonCommander::Response response;
+	response.json["command"] = "queue";
+
+	if (!message["file"].isString()) {
+		response.action = closeAndSend;
+		response.json["file"] = "";
+		response.json["accept"] = false;
+		response.json["error"] = "invalid request";
+	} else {
+		bool res = Queue::push(message["file"].asString());
+		if (res) {
+			response.action = send;
+			response.json["file"] = message["file"].asString();
+			response.json["accept"] = true;
+			response.json["error"] = "";
+		} else {
+			response.action = send;
+			response.json["file"] = message["file"].asString();
+			response.json["accept"] = false;
+			response.json["error"] = "file could not be queued";
+		}
+	}
+
+	return response;
+}
+
+JsonCommander::Response JsonCommander::executeExtendedListData(const Json::Value &message) {
+	JsonCommander::Response response;
+	response.action = send;
+	response.json["command"] = "extendedlistdata";
+	Json::Value array;
+
+	const int remainingListchunks = fileManager.getRemainingExtendedListChunks();
+	if (!message["chunk"].isInt() || !message["cancel"].isBool()) {
+		response.action = closeAndSend;
+		response.json["cancel"] = true;
+		response.json["remaining"] = -1;
+		response.json["files"] = Json::arrayValue;
+		response.json["error"] = "incorrect listdata command request";
+	} else if (remainingListchunks == 0) {
+		response.json["cancel"] = true;
+		response.json["remaining"] = -1;
+		response.json["files"] = Json::arrayValue;
+		response.json["error"] = "there are no chunks to send";
+	} else if (message["cancel"].asBool()) {
+		response.json["cancel"] = true;
+		response.json["remaining"] = message["chunk"].asInt();
+		response.json["files"] = Json::arrayValue;
+		response.json["error"] = "";
+		fileManager.cancelExtendedList();
+	} else if (remainingListchunks - 1 != message["chunk"].asInt()) {
+		response.action = closeAndSend;
+		response.json["cancel"] = true;
+		response.json["remaining"] = -1;
+		response.json["files"] = Json::arrayValue;
+		response.json["error"] = "wrong chunk number";
+	} else {
+		auto v = fileManager.getNextChunkFromExtendedList();
+		for (int i = 0; i < v.size(); i++) {
+			Json::Value obj;
+			obj["name"] = std::get<0>(v.at(i));
+			obj["head"] = std::get<1>(v.at(i)).compare("") == 0 ? "" : base64::encode(std::get<1>(v.at(i)));
+			obj["size"] = std::get<2>(v.at(i));
+			array.append(obj);
+		}
+		response.json["remaining"] = message["chunk"].asInt();
+		response.json["cancel"] = false;
+		response.json["files"] = array;
+		response.json["error"] = "";
+	}
+
+	return response;
+}
+
+JsonCommander::Response JsonCommander::executeDequeue(const Json::Value &message) {
+	JsonCommander::Response response;
+	response.json["command"] = "dequeue";
+
+	if (!message["file"].isString()) {
+		response.action = closeAndSend;
+		response.json["file"] = "";
+		response.json["accept"] = false;
+		response.json["error"] = "invalid request";
+	} else {
+		bool res = Queue::remove(message["file"].asString());
+		if (res) {
+			response.action = send;
+			response.json["file"] = message["file"].asString();
+			response.json["accept"] = true;
+			response.json["error"] = "";
+		} else {
+			response.action = send;
+			response.json["file"] = message["file"].asString();
+			response.json["accept"] = false;
+			response.json["error"] = "cannot remove this file from queue";
+		}
+	}
+
+	return response;
+}

+ 48 - 0
daemon/src/Notifications.cpp

@@ -0,0 +1,48 @@
+#include "../include/Notifications.h"
+
+#include <ctime>
+
+namespace Notifications {
+std::vector<std::pair<long int, std::string>> messages;
+std::map<std::string, long int> userTimeStamps;
+}; // namespace Notifications
+
+void Notifications::newNotification(const std::string &message) {
+	long int timestamp = static_cast<long int>(time(0));
+	messages.push_back(make_pair(timestamp, message));
+}
+
+std::vector<std::string> Notifications::getMessages(const std::string &user) {
+	std::vector<std::string> ret;
+
+	// first clean up
+	long int latest = static_cast<long int>(time(0)) - 604800;
+	for (int i = 0; i < messages.size(); i++) {
+		if (messages.at(i).first < latest) {
+			messages.erase(messages.begin() + i);
+		}
+	}
+
+	auto it = userTimeStamps.find(user);
+	if (it == userTimeStamps.end()) {
+		// case user has no timestamp yet
+
+		for (int i = 0; i < messages.size(); i++) {
+			ret.push_back(messages.at(i).second);
+		}
+		userTimeStamps.insert(std::pair<std::string, long int>(user, static_cast<long int>(time(0))));
+	} else {
+		long int userTimeStamp = it->second;
+
+		for (int i = 0; i < messages.size(); i++) {
+			if (userTimeStamp <= messages.at(i).first) {
+				ret.push_back(messages.at(i).second);
+			}
+		}
+
+		// update user timestamp
+		it->second = static_cast<long int>(time(0));
+	}
+
+	return ret;
+}

+ 75 - 0
daemon/src/Queue.cpp

@@ -0,0 +1,75 @@
+#include "../include/Queue.h"
+#include "../include/Notifications.h"
+#include <algorithm>
+
+namespace Queue {
+std::deque<std::string> queue;
+std::mutex mtx;
+ChannelControls *channel;
+} // namespace Queue
+
+bool Queue::push(const std::string &file) {
+	// Don't queue if no channel is available
+	if (channel == nullptr) {
+		return false;
+	}
+
+	mtx.lock();
+	queue.push_back(file);
+	mtx.unlock();
+
+	Notifications::newNotification("File \"" + file + "\" queued.");
+
+	if (curTransfer().compare("") == 0) {
+		schedule();
+	}
+
+	return true;
+}
+
+bool Queue::remove(const std::string &file) {
+	if (curTransfer().compare(file) == 0) {
+		// Reset channel and remove from queue
+		channel->reset();
+		Notifications::newNotification("Transfer of file \"" + file + "\" was aborted.");
+		return true;
+	} else {
+		mtx.lock();
+		auto it = std::find(queue.begin(), queue.end(), file);
+		if (it == queue.end()) {
+			return false;
+		}
+		queue.erase(it);
+		mtx.unlock();
+		return true;
+	}
+}
+
+void Queue::schedule() {
+	if (!queue.empty()) {
+		mtx.lock();
+		std::string file = queue.front();
+		queue.pop_front();
+
+		// success is false if file could not be sent or found
+		bool success = channel->sendFile(file);
+		mtx.unlock();
+
+		if (!success) {
+			Notifications::newNotification("File \"" + file + "\" could not be sent.");
+			schedule();
+		}
+		// don't wait until transer finished because schedule must be called again
+	} else {
+		mtx.lock();
+		mtx.unlock();
+	}
+}
+
+std::string Queue::curTransfer() {
+	if (channel == nullptr || !channel->isTransferRunning()) {
+		return "";
+	}
+
+	return channel->getFileName();
+}

+ 1 - 3
daemon/src/Server.cpp

@@ -123,9 +123,7 @@ void con_handler::handle_read_command(const boost::system::error_code &err, size
 }
 
 void con_handler::handle_write(const boost::system::error_code &err, size_t bytes_transferred) {
-	if (!err) {
-		std::cout << "Hello World!" << std::endl;
-	} else {
+	if (err) {
 		std::cerr << __PRETTY_FUNCTION__ << " error: " << err.message() << std::endl;
 		close_sock();
 	}

+ 30 - 9
daemon/src/UserManager.cpp

@@ -1,6 +1,6 @@
 #include "../include/UserManager.h"
+#include "../../libs/libbcrypt/bcrypt.h"
 
-// TODO passwords are stored and checked in plain text
 // TODO read userStorage file location from config
 
 // initialize static filename to empty string
@@ -10,11 +10,8 @@ void UserManager::init(const std::string &file) {
 	filename = file;
 	std::ifstream ifile(filename);
 	if (!ifile.is_open()) {
-		// create new file if userStorage does not exist
-		std::ofstream ofile;
-		ofile.open(filename);
-		ofile << "user;pass\n";
-		ofile.close();
+		// create new file by adding a user if userStorage does not exist
+		addUser("user", "pass");
 		std::cout << "Created \"" << filename << "\" and added the default user" << std::endl;
 	}
 	ifile.close();
@@ -24,8 +21,15 @@ bool UserManager::isAllowed(const std::string &name, const std::string &pw) {
 	std::map<std::string, std::string> user_map;
 	readFromFile(user_map);
 	auto it = user_map.find(name);
-	// check if user exists and pw is equal
-	if (it != user_map.end() && (it->second.compare(pw) == 0)) {
+	// check if user exists and pw is valid
+	if (it != user_map.end()) {
+
+		// check bcrypt hash
+		std::string hash = it->second;
+		int ret = bcrypt_checkpw(pw.c_str(), hash.c_str());
+		if (ret != 0)
+			return false;
+
 		return true;
 	}
 	return false;
@@ -39,7 +43,24 @@ bool UserManager::addUser(const std::string &name, const std::string &pw) {
 	if (it != user_map.end()) {
 		return false;
 	}
-	user_map.insert(std::pair<std::string, std::string>(name, pw));
+
+	// calculate bcrypt hash
+	std::string salt;
+	std::string hash;
+	salt.resize(BCRYPT_HASHSIZE);
+	hash.resize(BCRYPT_HASHSIZE);
+
+	int ret = bcrypt_gensalt(10, &salt.front());
+	if (ret != 0)
+		return false;
+	ret = bcrypt_hashpw(pw.c_str(), salt.c_str(), &hash.front());
+	if (ret != 0)
+		return false;
+
+	// remove trailing terminator chars
+	hash.resize(hash.find('\0'));
+
+	user_map.insert(std::pair<std::string, std::string>(name, hash));
 	writeToFile(user_map);
 	return true;
 }

+ 68 - 11
daemon/src/main.cpp

@@ -1,9 +1,13 @@
 #include <iostream>
 
 #include "../include/Config.h"
+#include "../include/CovertChannel/Channels/TCPAppendChannel.hpp"
+#include "../include/CovertChannel/Channels/TCPOptionCustomChannel.hpp"
+#include "../include/CovertChannel/Channels/TCPOptionTimestampChannel.hpp"
+#include "../include/CovertChannel/Channels/TCPUrgencyChannel.hpp"
 #include "../include/CovertChannel/CovertChannel.h"
 #include "../include/CovertChannel/ForwardChannel.h"
-#include "../include/CovertChannel/ProxyChannel.h"
+#include "../include/Queue.h"
 #include "../include/Server.h"
 #include "../include/UserManager.h"
 
@@ -18,22 +22,72 @@ int main(int argc, char *argv[]) {
 
 	CovertChannel *covertchannel = nullptr;
 	const std::string covertChannelMode = Config::getValue("covertChannelMode");
-	if (covertChannelMode == "proxy") {
+	if (covertChannelMode == "tcpurgency") {
 		const string innerInterface = Config::getValue("innerInterface");
 		const string outerInterface = Config::getValue("outerInterface");
 
 		const string ownIP = Config::getValue("ownIP");
-		const string partnerIP = Config::getValue("partnerIP");
-		const string originIP = Config::getValue("originIP");
 		const string targetIP = Config::getValue("targetIP");
 		const string targetPort = Config::getValue("targetPort");
-		const string relayMode = Config::getValue("relayMode");
-		const string ownMAC = Config::getValue("ownMAC");
-		const string originMAC = Config::getValue("originMAC");
-		const string gatewayMAC = Config::getValue("gatewayMAC");
-		const string channelGatewayMAC = Config::getValue("channelGatewayMAC");
-		covertchannel = new ProxyChannel(innerInterface, outerInterface, ownIP, partnerIP, originIP, targetIP, targetPort, ownMAC, originMAC, channelGatewayMAC,
-		                                 gatewayMAC, relayMode == "true");
+		const string passiveMode = Config::getValue("passiveMode");
+
+		if (passiveMode == "true") {
+			covertchannel = new TCPUrgencyChannel<true>(innerInterface, outerInterface, ownIP, targetIP, targetPort);
+		} else {
+			covertchannel = new TCPUrgencyChannel<false>(innerInterface, outerInterface, ownIP, 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");
+
+		if (passiveMode == "true") {
+			covertchannel = new TCPAppendChannel<8, true>(innerInterface, outerInterface, ownIP, targetIP, targetPort);
+		} else {
+			covertchannel = new TCPAppendChannel<8, false>(innerInterface, outerInterface, ownIP, 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");
+
+		if (passiveMode == "true") {
+			covertchannel = new TCPOptionTimestampChannel<true>(innerInterface, outerInterface, ownIP, targetIP, targetPort);
+		} else {
+			covertchannel = new TCPOptionTimestampChannel<false>(innerInterface, outerInterface, ownIP, 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");
+
+		if (passiveMode == "true") {
+			covertchannel = new TCPOptionCustomChannel<8, true>(innerInterface, outerInterface, ownIP, targetIP, targetPort);
+		} else {
+			covertchannel = new TCPOptionCustomChannel<8, false>(innerInterface, outerInterface, ownIP, targetIP, targetPort);
+		}
 
 		// covertchannel = new ForwardChannel(innerInterface, outerInterface);
 		covertchannel->startSniffing();
@@ -49,6 +103,9 @@ int main(int argc, char *argv[]) {
 	// if not create one
 	UserManager::init(Config::getValue("userdatabase"));
 
+	// Init queue so the file transfers can be executed
+	Queue::channel = covertchannel;
+
 	try {
 		io_service io_service;
 		Server server(io_service);

+ 1 - 1
daemon/test/CMakeLists.txt

@@ -12,7 +12,7 @@ find_package(GMock REQUIRED)
 include_directories(${Boost_INCLUDE_DIR} ${JSONCPP_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS})
 
 # Add test cpp file
-add_executable(jsonCommanderTest test/JsonCommanderTest.cpp src/JsonCommander.cpp src/FileManager.cpp src/base64.cpp test/ConfigMock.cpp test/UserManagerMock.cpp)
+add_executable(jsonCommanderTest test/JsonCommanderTest.cpp src/JsonCommander.cpp src/FileManager.cpp src/base64.cpp test/ConfigMock.cpp test/UserManagerMock.cpp src/Notifications.cpp test/QueueMock.cpp)
 target_link_libraries(jsonCommanderTest ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES} ${JSONCPP_LIBRARIES} ${GTEST_BOTH_LIBRARIES} ${GMOCK_BOTH_LIBRARIES})
 
 add_test(

+ 22 - 0
daemon/test/ChannelControlsMock.h

@@ -0,0 +1,22 @@
+#ifndef CHANNELCONTROLSMOCK_H
+#define CHANNELCONTROLSMOCK_H
+
+#include "../include/CovertChannel/ChannelControls.h"
+#include <gmock/gmock.h>
+
+/**
+ * @class ChannelControlsMock
+ *
+ * Gmock stub class for ChannelControls so you can test without starting an actual covert channel.
+ */
+class ChannelControlsMock : public ChannelControls {
+public:
+	MOCK_METHOD(bool, sendFile, (const std::string &fileName), (override));
+	MOCK_METHOD((std::pair<uint32_t, uint32_t>), getProgress, (), (override));
+	MOCK_METHOD(std::time_t, getTransferStart, (), (override));
+	MOCK_METHOD(bool, isTransferRunning, (), (override));
+	MOCK_METHOD(void, reset, (), (override));
+	MOCK_METHOD(std::string, getFileName, (), (override));
+};
+
+#endif

+ 6 - 0
daemon/test/FileManagerMock.h

@@ -32,6 +32,12 @@ public:
 
 	MOCK_METHOD(FileManager::Error, deleteFile, (const std::string &filename), (override));
 	MOCK_METHOD((std::pair<std::vector<char>, FileManager::Error>), getBytesFromFile, (const std::string &filename, int numOfBytes), (override));
+
+	MOCK_METHOD(int, openExtendedList, (), (override));
+	MOCK_METHOD(int, getRemainingExtendedListChunks, (), (override));
+	MOCK_METHOD(int, getExtendedListSize, (), (override));
+	MOCK_METHOD((std::vector<std::tuple<std::string, std::string, double>>), getNextChunkFromExtendedList, (), (override));
+	MOCK_METHOD(void, cancelExtendedList, (), (override));
 };
 
 #endif

+ 528 - 0
daemon/test/JsonCommanderTest.cpp

@@ -1,8 +1,12 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include "../include/Config.h"
 #include "../include/JsonCommander.h"
+#include "../include/Notifications.h"
+#include "ChannelControlsMock.h"
 #include "FileManagerMock.h"
+#include "QueueMock.h"
 
 namespace {
 /* Version tests */
@@ -1196,4 +1200,528 @@ TEST(DeleteFile, DisabledInConfig) {
 	EXPECT_TRUE(response.json["error"].asString().compare("") != 0);
 }
 
+TEST(ExtendedStatus, NoUploadOrDownload) {
+	FileManagerMock fileManager;
+	JsonCommander jsonCommander(fileManager);
+	ChannelControlsMock channelControls;
+	Queue::channel = &channelControls;
+	Queue::queue.clear();
+
+	const std::string command = "extendedstatus";
+
+	Json::Value message;
+	message["command"] = command;
+
+	EXPECT_CALL(fileManager, isUploading()).WillOnce(testing::Return(false));
+	EXPECT_CALL(fileManager, isDownloading()).WillOnce(testing::Return(false));
+	EXPECT_CALL(channelControls, isTransferRunning()).WillOnce(testing::Return(false));
+
+	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["error"].asString(), "");
+	EXPECT_TRUE(response.json["transfersclientserver"].isNull());
+	EXPECT_TRUE(response.json["transfersserverserver"].isNull());
+}
+
+TEST(ExtendedStatus, ClientServerDownload) {
+	FileManagerMock fileManager;
+	JsonCommander jsonCommander(fileManager);
+	ChannelControlsMock channelControls;
+	Queue::channel = &channelControls;
+	Queue::queue.clear();
+
+	const std::string command = "extendedstatus";
+
+	Json::Value message;
+	message["command"] = command;
+
+	EXPECT_CALL(fileManager, isUploading()).WillOnce(testing::Return(false));
+	EXPECT_CALL(fileManager, isDownloading()).WillOnce(testing::Return(true));
+	EXPECT_CALL(fileManager, getGetBaseFileName()).WillOnce(testing::Return("asdf"));
+	EXPECT_CALL(channelControls, isTransferRunning()).WillOnce(testing::Return(false));
+
+	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["error"].asString(), "");
+	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);
+}
+
+TEST(ExtendedStatus, ClientServerUpload) {
+	FileManagerMock fileManager;
+	JsonCommander jsonCommander(fileManager);
+	ChannelControlsMock channelControls;
+	Queue::channel = &channelControls;
+	Queue::queue.clear();
+
+	const std::string command = "extendedstatus";
+
+	Json::Value message;
+	message["command"] = command;
+
+	EXPECT_CALL(fileManager, isUploading()).WillOnce(testing::Return(true));
+	EXPECT_CALL(fileManager, isDownloading()).WillOnce(testing::Return(false));
+	EXPECT_CALL(fileManager, getPutBaseFileName()).WillOnce(testing::Return("asdf"));
+	EXPECT_CALL(channelControls, isTransferRunning()).WillOnce(testing::Return(false));
+
+	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["error"].asString(), "");
+	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);
+}
+
+TEST(ExtendedStatus, ClientServerDonwloadAndUpload) {
+	FileManagerMock fileManager;
+	JsonCommander jsonCommander(fileManager);
+	ChannelControlsMock channelControls;
+	Queue::channel = &channelControls;
+	Queue::queue.clear();
+
+	const std::string command = "extendedstatus";
+
+	Json::Value message;
+	message["command"] = command;
+
+	EXPECT_CALL(fileManager, isUploading()).WillOnce(testing::Return(true));
+	EXPECT_CALL(fileManager, isDownloading()).WillOnce(testing::Return(true));
+	EXPECT_CALL(fileManager, getGetBaseFileName()).WillOnce(testing::Return("asdfGet"));
+	EXPECT_CALL(fileManager, getPutBaseFileName()).WillOnce(testing::Return("asdfPut"));
+	EXPECT_CALL(channelControls, isTransferRunning()).WillOnce(testing::Return(false));
+
+	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["error"].asString(), "");
+	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_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);
+}
+
+TEST(ExtendedStatus, ServerServerDownload) {
+	FileManagerMock fileManager;
+	JsonCommander jsonCommander(fileManager);
+	ChannelControlsMock channelControls;
+	Queue::channel = &channelControls;
+	Queue::queue.clear();
+	Queue::fileName = "a.txt";
+	Config::storage.clear();
+	Config::storage.insert(std::pair<std::string, std::string>("passiveMode", "true"));
+	Config::storage.insert(std::pair<std::string, std::string>("covertChannelMode", "m"));
+
+	const std::string command = "extendedstatus";
+
+	Json::Value message;
+	message["command"] = command;
+
+	EXPECT_CALL(fileManager, isUploading()).WillOnce(testing::Return(false));
+	EXPECT_CALL(fileManager, isDownloading()).WillOnce(testing::Return(false));
+	EXPECT_CALL(channelControls, isTransferRunning()).WillOnce(testing::Return(true));
+	EXPECT_CALL(channelControls, getProgress()).WillOnce(testing::Return(std::pair<int, int>(1, 2)));
+	EXPECT_CALL(channelControls, getTransferStart()).WillOnce(testing::Return(0));
+
+	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["error"].asString(), "");
+	EXPECT_EQ(response.json["transfersclientserver"].size(), 0);
+	EXPECT_EQ(response.json["transfersserverserver"].size(), 1);
+	EXPECT_EQ(response.json["transfersserverserver"][0]["type"].asString(), "download");
+	EXPECT_EQ(response.json["transfersserverserver"][0]["file"].asString(), "a.txt");
+	EXPECT_EQ(response.json["transfersserverserver"][0]["progress"].asInt(), 50);
+	EXPECT_TRUE(response.json["transfersserverserver"][0]["speed"].isDouble());
+	EXPECT_EQ(response.json["transfersserverserver"][0]["method"].asString(), "m");
+}
+
+TEST(ExtendedStatus, ServerServerUpload) {
+	FileManagerMock fileManager;
+	JsonCommander jsonCommander(fileManager);
+	ChannelControlsMock channelControls;
+	Queue::channel = &channelControls;
+	Queue::queue.clear();
+	Queue::fileName = "a.txt";
+	Config::storage.clear();
+	Config::storage.insert(std::pair<std::string, std::string>("passiveMode", "false"));
+	Config::storage.insert(std::pair<std::string, std::string>("covertChannelMode", "m"));
+
+	const std::string command = "extendedstatus";
+
+	Json::Value message;
+	message["command"] = command;
+
+	EXPECT_CALL(fileManager, isUploading()).WillOnce(testing::Return(false));
+	EXPECT_CALL(fileManager, isDownloading()).WillOnce(testing::Return(false));
+	EXPECT_CALL(channelControls, isTransferRunning()).WillOnce(testing::Return(true));
+	EXPECT_CALL(channelControls, getProgress()).WillOnce(testing::Return(std::pair<int, int>(1, 2)));
+	EXPECT_CALL(channelControls, getTransferStart()).WillOnce(testing::Return(0));
+
+	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["error"].asString(), "");
+	EXPECT_EQ(response.json["transfersclientserver"].size(), 0);
+	EXPECT_EQ(response.json["transfersserverserver"].size(), 1);
+	EXPECT_EQ(response.json["transfersserverserver"][0]["type"].asString(), "upload");
+	EXPECT_EQ(response.json["transfersserverserver"][0]["file"].asString(), "a.txt");
+	EXPECT_EQ(response.json["transfersserverserver"][0]["progress"].asInt(), 50);
+	EXPECT_TRUE(response.json["transfersserverserver"][0]["speed"].isDouble());
+	EXPECT_EQ(response.json["transfersserverserver"][0]["method"].asString(), "m");
+}
+
+TEST(ExtendedStatus, QueueNotEmpty) {
+	FileManagerMock fileManager;
+	JsonCommander jsonCommander(fileManager);
+	ChannelControlsMock channelControls;
+	Queue::channel = &channelControls;
+	Queue::queue.clear();
+	Queue::queue.push_back("a");
+	Queue::queue.push_back("b");
+	Config::storage.clear();
+	Config::storage.insert(std::pair<std::string, std::string>("covertChannelMode", "m"));
+
+	const std::string command = "extendedstatus";
+
+	Json::Value message;
+	message["command"] = command;
+
+	EXPECT_CALL(fileManager, isUploading()).WillOnce(testing::Return(false));
+	EXPECT_CALL(fileManager, isDownloading()).WillOnce(testing::Return(false));
+	EXPECT_CALL(channelControls, isTransferRunning()).WillOnce(testing::Return(false));
+
+	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["error"].asString(), "");
+	EXPECT_EQ(response.json["transfersclientserver"].size(), 0);
+	EXPECT_EQ(response.json["transfersserverserver"].size(), 2);
+	EXPECT_EQ(response.json["transfersserverserver"][0]["type"], "queued");
+	EXPECT_EQ(response.json["transfersserverserver"][0]["file"], "a");
+	EXPECT_EQ(response.json["transfersserverserver"][0]["progress"], 0);
+	EXPECT_EQ(response.json["transfersserverserver"][0]["speed"], 0);
+	EXPECT_EQ(response.json["transfersserverserver"][0]["method"], "m");
+	EXPECT_EQ(response.json["transfersserverserver"][1]["type"], "queued");
+	EXPECT_EQ(response.json["transfersserverserver"][1]["file"], "b");
+	EXPECT_EQ(response.json["transfersserverserver"][1]["progress"], 0);
+	EXPECT_EQ(response.json["transfersserverserver"][1]["speed"], 0);
+	EXPECT_EQ(response.json["transfersserverserver"][1]["method"], "m");
+}
+
+TEST(Notifications, NoMessage) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "notifications";
+
+	Notifications::messages.clear();
+	Notifications::userTimeStamps.clear();
+
+	Json::Value message;
+	message["command"] = command;
+
+	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["error"].asString(), "");
+	EXPECT_TRUE(response.json["messages"].isNull());
+}
+
+TEST(Notifications, OneMessage) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "notifications";
+
+	Notifications::messages.clear();
+	Notifications::userTimeStamps.clear();
+	Notifications::newNotification("asdf");
+
+	Json::Value message;
+	message["command"] = command;
+
+	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["error"].asString(), "");
+	EXPECT_TRUE(response.json["messages"].isArray());
+	EXPECT_EQ(response.json["messages"][0], "asdf");
+
+	// cleaning up
+	Notifications::messages.clear();
+	Notifications::userTimeStamps.clear();
+}
+
+TEST(Notifications, OneMessageMultipleTimesCalled) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "notifications";
+
+	Notifications::messages.clear();
+	Notifications::userTimeStamps.clear();
+	Notifications::newNotification("asdf");
+
+	Json::Value message;
+	message["command"] = command;
+
+	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["error"].asString(), "");
+	EXPECT_TRUE(response.json["messages"].isArray());
+	EXPECT_EQ(response.json["messages"][0], "asdf");
+
+	// make suhre that timestamp from message is older
+	Notifications::messages.at(0).first--;
+
+	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["error"].asString(), "");
+	EXPECT_TRUE(response.json["messages"].isNull());
+
+	// cleaning up
+	Notifications::messages.clear();
+	Notifications::userTimeStamps.clear();
+}
+
+TEST(Notifications, TwoMessages) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "notifications";
+
+	Notifications::messages.clear();
+	Notifications::userTimeStamps.clear();
+	Notifications::newNotification("asdf");
+	Notifications::newNotification("qwer");
+
+	Json::Value message;
+	message["command"] = command;
+
+	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["error"].asString(), "");
+	EXPECT_TRUE(response.json["messages"].isArray());
+	EXPECT_EQ(response.json["messages"][0], "asdf");
+	EXPECT_EQ(response.json["messages"][1], "qwer");
+
+	// cleaning up
+	Notifications::messages.clear();
+	Notifications::userTimeStamps.clear();
+}
+
+TEST(ExtendedList, positive) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "extendedlist";
+	Json::Value message;
+	message["command"] = command;
+
+	EXPECT_CALL(fileManager, getRemainingExtendedListChunks()).WillOnce(testing::Return(0));
+	EXPECT_CALL(fileManager, openExtendedList()).WillOnce(testing::Return(1));
+	EXPECT_CALL(fileManager, getExtendedListSize()).WillOnce(testing::Return(5));
+
+	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["chunks"].asInt(), 1);
+	EXPECT_EQ(response.json["items"].asInt(), 5);
+	EXPECT_EQ(response.json["error"].asString(), "");
+}
+
+TEST(ExtendedList, negative) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "extendedlist";
+	Json::Value message;
+	message["command"] = command;
+
+	EXPECT_CALL(fileManager, getRemainingExtendedListChunks()).WillOnce(testing::Return(0));
+	EXPECT_CALL(fileManager, openExtendedList()).WillOnce(testing::Return(-1));
+
+	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["chunks"].asInt(), -1);
+	EXPECT_EQ(response.json["items"].asInt(), -1);
+	EXPECT_TRUE(response.json["error"].asString().compare("") != 0);
+}
+
+TEST(ExtendedList, EmptyList) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "extendedlist";
+	Json::Value message;
+	message["command"] = command;
+
+	EXPECT_CALL(fileManager, getRemainingExtendedListChunks()).WillOnce(testing::Return(0));
+	EXPECT_CALL(fileManager, openExtendedList()).WillOnce(testing::Return(0));
+	EXPECT_CALL(fileManager, getExtendedListSize()).WillOnce(testing::Return(0));
+
+	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["chunks"].asInt(), 0);
+	EXPECT_EQ(response.json["items"].asInt(), 0);
+	EXPECT_EQ(response.json["error"].asString(), "");
+}
+
+TEST(ExtendedListData, Positive) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "extendedlistdata";
+	Json::Value message;
+	message["command"] = command;
+	message["chunk"] = 0;
+	message["cancel"] = false;
+
+	std::vector<std::tuple<std::string, std::string, double>> chunks;
+	std::tuple<std::string, std::string, double> chunk = std::tuple("asdf", "asdfghjkasdfghjkasdfghjkasdfghjk", 41.2);
+	chunks.push_back(chunk);
+	EXPECT_CALL(fileManager, getRemainingExtendedListChunks()).WillOnce(testing::Return(1));
+	EXPECT_CALL(fileManager, getNextChunkFromExtendedList()).WillOnce(testing::Return(chunks));
+
+	JsonCommander::Response response = jsonCommander.execute(message);
+
+	EXPECT_EQ(response.json["command"].asString(), command);
+	EXPECT_FALSE(response.json["cancel"].asBool());
+	EXPECT_EQ(response.json["remaining"].asInt(), 0);
+	EXPECT_TRUE(response.json["files"].isArray());
+	EXPECT_EQ(response.json["files"].size(), 1);
+	EXPECT_EQ(response.json["files"][0]["name"].asString(), "asdf");
+	EXPECT_NE(response.json["files"][0]["head"].asString(), "");
+	EXPECT_EQ(response.json["files"][0]["size"].asDouble(), 41.2);
+	EXPECT_EQ(response.json["error"].asString(), "");
+}
+
+TEST(ExtendedListData, Cancel) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "extendedlistdata";
+	Json::Value message;
+	message["command"] = command;
+	message["chunk"] = 0;
+	message["cancel"] = true;
+
+	EXPECT_CALL(fileManager, getRemainingExtendedListChunks()).WillOnce(testing::Return(1));
+	EXPECT_CALL(fileManager, cancelExtendedList());
+
+	JsonCommander::Response response = jsonCommander.execute(message);
+
+	EXPECT_EQ(response.json["command"].asString(), command);
+	EXPECT_TRUE(response.json["cancel"].asBool());
+	EXPECT_EQ(response.json["remaining"].asInt(), 0);
+	EXPECT_TRUE(response.json["files"].isArray());
+	EXPECT_EQ(response.json["error"].asString(), "");
+}
+
+TEST(ExtendedListData, WrongChunkNumber) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "extendedlistdata";
+	Json::Value message;
+	message["command"] = command;
+	message["chunk"] = 1;
+	message["cancel"] = false;
+
+	EXPECT_CALL(fileManager, getRemainingExtendedListChunks()).WillOnce(testing::Return(1));
+
+	JsonCommander::Response response = jsonCommander.execute(message);
+	EXPECT_TRUE(response.action == JsonCommander::Action::closeAndSend);
+	EXPECT_EQ(response.json["command"].asString(), command);
+	EXPECT_TRUE(response.json["cancel"].asBool());
+	EXPECT_EQ(response.json["remaining"].asInt(), -1);
+	EXPECT_TRUE(response.json["files"].isArray());
+	EXPECT_TRUE(response.json["error"].asString().compare("") != 0);
+}
+
+TEST(ExtendedListData, NoChunksToBeSend) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "extendedlistdata";
+	Json::Value message;
+	message["command"] = command;
+	message["chunk"] = 1;
+	message["cancel"] = false;
+
+	EXPECT_CALL(fileManager, getRemainingExtendedListChunks()).WillOnce(testing::Return(0));
+
+	JsonCommander::Response response = jsonCommander.execute(message);
+	EXPECT_TRUE(response.action == JsonCommander::Action::send);
+	EXPECT_EQ(response.json["command"].asString(), command);
+	EXPECT_TRUE(response.json["cancel"].asBool());
+	EXPECT_EQ(response.json["remaining"].asInt(), -1);
+	EXPECT_TRUE(response.json["files"].isArray());
+	EXPECT_TRUE(response.json["error"].asString().compare("") != 0);
+}
+
+TEST(ExtendedListData, InvalidRequest) {
+	FileManagerMock fileManager;
+
+	JsonCommander jsonCommander(fileManager);
+
+	const std::string command = "extendedlistdata";
+	Json::Value message;
+	message["command"] = command;
+	message["chunk"] = 5;
+
+	EXPECT_CALL(fileManager, getRemainingExtendedListChunks()).WillOnce(testing::Return(0));
+
+	JsonCommander::Response response = jsonCommander.execute(message);
+	EXPECT_TRUE(response.action == JsonCommander::Action::closeAndSend);
+	EXPECT_EQ(response.json["command"].asString(), command);
+	EXPECT_TRUE(response.json["cancel"].asBool());
+	EXPECT_EQ(response.json["remaining"].asInt(), -1);
+	EXPECT_TRUE(response.json["files"].isArray());
+	EXPECT_TRUE(response.json["error"].asString().compare("") != 0);
+}
+
 } // namespace

+ 19 - 0
daemon/test/QueueMock.cpp

@@ -0,0 +1,19 @@
+#include "../include/Queue.h"
+
+namespace Queue {
+std::deque<std::string> queue;
+std::string fileName = "";
+ChannelControls *channel;
+} // namespace Queue
+
+bool Queue::push(const std::string &file) {
+	queue.push_back(file);
+
+	return true;
+}
+
+bool Queue::remove(const std::string &file) { return true; }
+
+void Queue::schedule() {}
+
+std::string Queue::curTransfer() { return fileName; }

+ 10 - 0
daemon/test/QueueMock.h

@@ -0,0 +1,10 @@
+#ifndef QUEUE_MOCK_H
+#define QUEUE_MOCK_H
+
+#include "../include/Queue.h"
+
+namespace Queue {
+extern std::string fileName;
+}
+
+#endif

+ 4 - 5
gui/CMakeLists.txt

@@ -9,14 +9,13 @@ set(CMAKE_CXX_STANDARD 11)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
 find_package(Threads)
-find_package(Boost 1.67 REQUIRED COMPONENTS system filesystem)
-find_package(Qt5 COMPONENTS Core Quick REQUIRED)
+find_package(Qt5 COMPONENTS Core Quick Widgets REQUIRED)
 find_package(PkgConfig REQUIRED)
 pkg_check_modules(JSONCPP REQUIRED jsoncpp)
 
 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 include/qmlhandler.h)
+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(${Boost_INCLUDE_DIR} ${JSONCPP_INCLUDEDIR} include)
-target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${JSONCPP_LIBRARIES} ${Boost_LIBRARIES} Qt5::Core Qt5::Quick)
+include_directories(${JSONCPP_INCLUDEDIR} include)
+target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${JSONCPP_LIBRARIES} Qt5::Core Qt5::Quick Qt5::Widgets)

+ 20 - 0
gui/include/climanager.h

@@ -0,0 +1,20 @@
+#ifndef CLIMANAGER_H
+#define CLIMANAGER_H
+
+#include "config.h"
+#include "qmlhandler.h"
+#include <QGuiApplication>
+
+namespace CliManager {
+void setQmlHandler(QMLHandler *q);
+
+void init();
+void writeToCli(QString s);
+void readPipeLoop();
+void notificationsLoop();
+void statusLoop();
+void onExit();
+void setProgramActive(bool active);
+} // namespace CliManager
+
+#endif // CLIMANAGER_H

+ 18 - 0
gui/include/cmdmanager.h

@@ -9,15 +9,29 @@
 #include <string>
 #include <vector>
 
+struct fileEntry {
+	bool dirty;
+	std::string type;
+	std::string method;
+	int progress;
+	float speed;
+};
+
 namespace CmdManager {
 void init();
 void setQmlHandler(QMLHandler *q);
 
+void updateInternalFile(std::string name, std::string type, std::string method, int progress, float speed);
+void emitFileList();
+void cleanInternalList();
+
 void executeCmd(std::string cmd, Json::Value root);
 
+void handleError(Json::Value root);
 void handleStatus(Json::Value root);
 void handleClose(Json::Value root);
 void handleList(Json::Value root);
+void handleExtendedList(Json::Value root);
 void handleConnect(Json::Value root);
 void handleVersion(Json::Value root);
 void handleLogin(Json::Value root);
@@ -28,6 +42,10 @@ void handleGet(Json::Value root);
 void handleGetData(Json::Value root);
 void handleDeleteMe(Json::Value root);
 void handleDeleteFile(Json::Value root);
+void handleNotifications(Json::Value root);
+void handleQueue(Json::Value root);
+void handleDequeue(Json::Value root);
+void handleExtendedStatus(Json::Value root);
 } // namespace CmdManager
 
 #endif // CMDMANAGER_H

+ 15 - 5
gui/include/qmlhandler.h

@@ -10,16 +10,12 @@ class QMLHandler : public QObject {
 	Q_OBJECT
 
 private:
-	void readPipeLoop();
 	void fileExists(std::string name);
 
 public:
 	explicit QMLHandler(QObject *parent = 0);
 	void onExit();
-	void setProgramActive(bool active);
-	void reopenCLI(QString ip);
 	void closeCLI();
-	void writeToCLI(QString command);
 	void loadSettingsToGUI();
 	QString getIP();
 	void setRestart(bool restart);
@@ -45,7 +41,8 @@ signals:
 
 	// Receiving
 	void receivingClearFileList();
-	void receivingListFile(QString fileName, bool existsLocally);
+	void receivingListFile(QString fileName, QString fileSize, QString fileDecryptable, bool existsLocally);
+	void receivingUpdateFile(QString fileName, QString fileProgress, bool isQueued);
 	void receivingDisableDownloadButton(QString fileName);
 	void receivingCloseConfirmDeletePopup();
 
@@ -88,6 +85,11 @@ signals:
 	void log(QString logText);
 	void footerSetStatus(QString status);
 
+	// Notifications
+	void notification(QString message);
+	void dismissNotification(int index);
+	void showDesktopNotification(QString title, QString message);
+
 	// QML -> C++
 public slots:
 	void onStart();
@@ -133,6 +135,14 @@ public slots:
 
 	// Footer
 	void onFooterGetStatusButton();
+
+	// Notifications
+	void onDismissNotificationButton(int id);
+
+	// Queueing
+	void onReceivingQueueFileButton(QString fileName);
+
+	void onReceivingDequeueFileButton(QString fileName);
 };
 
 #endif // QMLHANDLER_H

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

@@ -23,7 +23,7 @@ Popup {
             popup.close()
         }
         onIpPopupOpen: {
-            popup.open();
+            popup.open()
         }
         onIpPopupSetStatus: {
             ipPopupStatusText.text = status
@@ -86,6 +86,8 @@ Popup {
             verticalAlignment: Text.AlignVCenter
             Layout.alignment: Qt.AlignCenter
             font.pixelSize: 20
+            wrapMode: Text.WordWrap
+            Layout.preferredWidth: parent.width
         }
 
         Button {
@@ -107,7 +109,9 @@ Popup {
                 // @disable-check M223
                 if (ipPopupConnectButton.enabled) {
                     // @disable-check M222
-                    _qmlHandler.onIpPopupConnectButton(ipPopupIpInput.text, ipPopupSetDefaultCheckbox.checked)
+                    _qmlHandler.onIpPopupConnectButton(
+                                ipPopupIpInput.text,
+                                ipPopupSetDefaultCheckbox.checked)
                 }
             }
         }

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

@@ -5,6 +5,7 @@ import QtQuick.Layouts 1.3
 Page {
     width: 400
     height: 400
+    title: ""
 
     Connections {
         target: _qmlHandler
@@ -83,6 +84,8 @@ Page {
             id: loginStatusText
             color: "#df3f3f"
             text: qsTr("")
+            wrapMode: Text.WordWrap
+            Layout.preferredWidth: parent.width
             horizontalAlignment: Text.AlignHCenter
             verticalAlignment: Text.AlignVCenter
             Layout.alignment: Qt.AlignCenter

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

@@ -114,6 +114,8 @@ Page {
             horizontalAlignment: Text.AlignHCenter
             verticalAlignment: Text.AlignVCenter
             Layout.alignment: Qt.AlignCenter
+            wrapMode: Text.WordWrap
+            Layout.preferredWidth: parent.width
             font.pixelSize: 20
         }
 

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

@@ -11,7 +11,8 @@ Page {
     Connections {
         target: _qmlHandler
         onLog: {
-            footerLog.append(new Date().toLocaleTimeString(Qt.locale("de_DE"), "[hh:mm:ss]\n") + logText)
+            footerLog.append(new Date().toLocaleTimeString(
+                                 Qt.locale("C"), "[hh:mm:ss]\n") + logText)
             footerFlickable.contentY = footerLog.height - footerFlickable.height
         }
 

+ 1 - 1
gui/src/Forms/Main/InvalidCliPathPopup.ui.qml

@@ -121,7 +121,7 @@ Popup {
         onAccepted: {
             // @disable-check M222
             var path = invalidCliPathPopupDialog.fileUrl.toString()
-            path = path.replace(/^(file:\/{2})/,"");
+            path = path.replace(/^(file:\/{2})/, "")
             invalidCliPathPopupCliPath.text = path
         }
     }

+ 1 - 1
gui/src/Forms/Main/NoConfigFoundPopup.ui.qml

@@ -101,7 +101,7 @@ Popup {
         onAccepted: {
             // @disable-check M222
             var path = noConfigFoundPopupCliDialog.fileUrl.toString()
-            path = path.replace(/^(file:\/{2})/,"");
+            path = path.replace(/^(file:\/{2})/, "")
             noConfigFoundPopupCliPath.text = path
         }
     }

+ 24 - 0
gui/src/Forms/Main/main.qml

@@ -6,6 +6,7 @@ import "../Messages"
 import "../Settings"
 import "../Help"
 import "../Connect"
+import "../Notifications"
 
 ApplicationWindow {
     id: window
@@ -20,6 +21,17 @@ ApplicationWindow {
     minimumWidth: width
     title: qsTr("Covert Channel - Control Panel")
 
+    property string notificationTabTitle: "Notifications"
+
+    Connections {
+        target: _qmlHandler
+
+        onNotification: {
+            if (swipeView.currentIndex != 5)
+              notificationTabTitle = "* Notifications"
+        }
+    }
+
     SwipeView {
         id: swipeView
         anchors.fill: parent
@@ -44,6 +56,10 @@ ApplicationWindow {
         HelpForm {
 
         }
+
+        NotificationsForm {
+
+        }
     }
 
     header: TabBar {
@@ -70,6 +86,14 @@ ApplicationWindow {
         TabButton {
             text: qsTr("Help")
         }
+
+        TabButton {
+            text: notificationTabTitle
+
+            onClicked: {
+                text = "Notifications"
+            }
+        }
     }
 
     footer: FooterForm {

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

@@ -0,0 +1,52 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.3
+
+Item {
+    width: 1250
+    height: 50
+    property string notificationDateText
+    property string notificationMessageText
+    property int myIndex
+
+    RowLayout {
+        id: rowLayout
+        anchors.fill: parent
+
+        Text {
+            id: notificationTemplateDateText
+            Layout.alignment: Qt.AlignCenter
+            Layout.preferredHeight: parent.height
+            Layout.preferredWidth: 200
+            verticalAlignment: Text.AlignVCenter
+            horizontalAlignment: Text.AlignHLeft
+            text: notificationDateText
+            color: "#ffffff"
+        }
+
+        Text {
+            id: notificationTemplateMessageText
+            Layout.alignment: Qt.AlignCenter
+            Layout.preferredHeight: parent.height
+            Layout.preferredWidth: 800
+            verticalAlignment: Text.AlignVCenter
+            horizontalAlignment: Text.AlignHLeft
+            text: notificationMessageText
+            color: "#ffffff"
+        }
+
+        Button {
+            id: notificationTemplateDismissButton
+            Layout.alignment: Qt.AlignCenter
+            Layout.preferredHeight: parent.height
+            Layout.preferredWidth: 50
+            text: qsTr("X")
+
+            // @disable-check M223
+            onClicked: {
+                // @disable-check M222
+                _qmlHandler.onDismissNotificationButton(myIndex)
+            }
+        }
+    }
+}

+ 93 - 0
gui/src/Forms/Notifications/NotificationsForm.ui.qml

@@ -0,0 +1,93 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.3
+import Qt.labs.platform 1.1
+
+Page {
+    width: 1280
+    height: 470
+    id: notificationsForm
+
+    font.capitalization: Font.MixedCase
+
+    Connections {
+        target: _qmlHandler
+        onNotification: {
+            notificationList.append({
+                                        "notificationDate": new Date().toLocaleString(
+                                                                Qt.locale(
+                                                                    "de_DE"),
+                                                                "[dd.MM.yyyy hh:mm:ss]"),
+                                        "notificationMessage": message
+                                    })
+        }
+
+        onDismissNotification: {
+            notificationList.remove(index)
+        }
+
+        onShowDesktopNotification: {
+            trayIcon.showMessage(title, message)
+        }
+    }
+
+    ColumnLayout {
+        anchors.fill: parent
+
+        ScrollView {
+            Layout.preferredWidth: parent.width
+            Layout.preferredHeight: 370
+
+            ListView {
+                anchors.fill: parent
+                model: notificationList
+                clip: true
+
+                delegate: NotificationTemplate {
+                    notificationDateText: notificationDate
+                    notificationMessageText: notificationMessage
+                    myIndex: index
+                }
+            }
+        }
+
+        ListModel {
+            id: notificationList
+        }
+
+        Text {
+            Layout.alignment: Qt.AlignCenter
+            Layout.preferredWidth: parent.width
+            Layout.preferredHeight: 30
+            id: loginTitle
+            color: "#ffffff"
+            text: qsTr("No new notifications!")
+            horizontalAlignment: Text.AlignHCenter
+            verticalAlignment: Text.AlignVCenter
+            visible: notificationList.count == 0 ? true : false
+            font.pixelSize: 20
+        }
+
+        Button {
+            id: notificationsDismissAllButton
+            Layout.preferredWidth: 180
+            Layout.preferredHeight: 70
+            Layout.alignment: Qt.AlignCenter
+            text: qsTr("Dismiss all")
+            enabled: notificationList.count != 0 ? true : false
+            visible: enabled
+
+            // @disable-check M223
+            onClicked: {
+                // @disable-check M222
+                notificationList.clear()
+            }
+        }
+    }
+
+    SystemTrayIcon {
+        id: trayIcon
+        visible: true
+        icon.source: "qrc:/images/tray-icon.png"
+    }
+}

+ 49 - 4
gui/src/Forms/Receiving/ReceivingFileTemplate.ui.qml

@@ -7,8 +7,10 @@ Item {
     height: 50
     property string fileNameText: "Name Placeholder"
     property string fileSizeText: "Size Placeholder"
+    property string fileProgressText: "Progress Placeholder"
     property string fileDecryptableText: "Decryptable Placeholder"
     property bool fileExists: false
+    property bool fileQueued: false
 
     Connections {
         target: _qmlHandler
@@ -18,6 +20,13 @@ Item {
             }
         }
 
+        onReceivingUpdateFile: {
+            if (fileNameText == fileName) {
+                fileProgressText = fileProgress
+                fileQueued = isQueued
+            }
+        }
+
         onReceivingCloseConfirmDeletePopup: {
             confirmDeletePopup.close()
         }
@@ -41,24 +50,52 @@ Item {
             id: fileTemplateFileSize
             Layout.alignment: Qt.AlignCenter
             Layout.preferredHeight: parent.height
-            Layout.preferredWidth: 200
+            Layout.preferredWidth: 100
             verticalAlignment: Text.AlignVCenter
             horizontalAlignment: Text.AlignHCenter
             text: fileSizeText
             color: "#ffffff"
         }
 
+        Text {
+            id: fileTemplateFileProgress
+            Layout.alignment: Qt.AlignCenter
+            Layout.preferredHeight: parent.height
+            Layout.preferredWidth: 100
+            verticalAlignment: Text.AlignVCenter
+            horizontalAlignment: Text.AlignHCenter
+            text: fileProgressText
+            color: "#ffffff"
+        }
+
         Text {
             id: fileTemplateFileDecryptable
             Layout.alignment: Qt.AlignCenter
             Layout.preferredHeight: parent.height
-            Layout.preferredWidth: 200
+            Layout.preferredWidth: 150
             verticalAlignment: Text.AlignVCenter
             horizontalAlignment: Text.AlignHCenter
             text: fileDecryptableText
             color: "#ffffff"
         }
 
+        Button {
+            id: fileTemplateQueueButton
+            Layout.alignment: Qt.AlignCenter
+            Layout.preferredHeight: parent.height
+            Layout.preferredWidth: 100
+            text: fileQueued ? qsTr("Dequeue") : qsTr("Enqueue")
+
+            // @disable-check M223
+            onClicked: {
+                // @disable-check M222
+                fileQueued ? _qmlHandler.onReceivingDequeueFileButton(
+                                 // @disable-check M222
+                                 fileNameText) : _qmlHandler.onReceivingQueueFileButton(
+                                 fileNameText)
+            }
+        }
+
         Button {
             id: fileTemplateDownloadButton
             Layout.alignment: Qt.AlignCenter
@@ -67,7 +104,11 @@ Item {
             enabled: !fileExists
             text: fileExists ? qsTr("Already Downloaded") : qsTr("Download")
 
-            onClicked: _qmlHandler.onReceivingDownloadFileButton(fileNameText)
+            // @disable-check M223
+            onClicked: {
+                // @disable-check M222
+                _qmlHandler.onReceivingDownloadFileButton(fileNameText)
+            }
         }
 
         Button {
@@ -77,7 +118,11 @@ Item {
             Layout.preferredWidth: 200
             text: qsTr("Delete from server")
 
-            onClicked: confirmDeletePopup.open()
+            // @disable-check M223
+            onClicked: {
+                // @disable-check M222
+                confirmDeletePopup.open()
+            }
         }
     }
 

+ 12 - 5
gui/src/Forms/Receiving/ReceivingForm.ui.qml

@@ -12,10 +12,13 @@ Page {
     Connections {
         target: _qmlHandler
         onReceivingListFile: {
-            fileList.append({"fileName" : fileName,
-                           "fileSize" : "42 kb",
-                           "fileDecryptable" : "Decryptable?: Yes",
-                           "fileExistsLocally" : existsLocally})
+            fileList.append({
+                                "fileName": fileName,
+                                "fileSize": fileSize + " kB",
+                                "fileProgress": "",
+                                "fileDecryptable": fileDecryptable,
+                                "fileExistsLocally": existsLocally
+                            })
         }
 
         onReceivingClearFileList: {
@@ -38,6 +41,7 @@ Page {
                 delegate: ReceivingFileTemplate {
                     fileNameText: fileName
                     fileSizeText: fileSize
+                    fileProgressText: fileProgress
                     fileDecryptableText: fileDecryptable
                     fileExists: fileExistsLocally
                 }
@@ -60,10 +64,13 @@ Page {
 
                 // @disable-check M223
                 onClicked: {
-                    // @disable-check M223
+                    // @disable-check M222
                     _qmlHandler.onReceivingListFilesButton()
                 }
             }
         }
     }
 }
+
+
+

+ 8 - 6
gui/src/Forms/Settings/SettingsForm.ui.qml

@@ -203,10 +203,12 @@ Page {
                 text: "Save Changes"
                 font.pixelSize: 20
                 onClicked: {
-                    _qmlHandler.onSettingsSaveButton(settingsCovertMethodPicker.currentIndex,
-                                                     settingsSaveIpSwitch.checked,
-                                                     settingsSaveUsernameSwitch.checked,
-                                                     settingsCliPath.text.replace("CLI-Path:     ", ""))
+                    _qmlHandler.onSettingsSaveButton(
+                                settingsCovertMethodPicker.currentIndex,
+                                settingsSaveIpSwitch.checked,
+                                settingsSaveUsernameSwitch.checked,
+                                settingsCliPath.text.replace("CLI-Path:     ",
+                                                             ""))
                 }
             }
 
@@ -233,11 +235,11 @@ Page {
         onAccepted: {
             // @disable-check M222
             var path = settingsCliDialog.fileUrl.toString()
-            path = path.replace(/^(file:\/{2})/,"");
+            path = path.replace(/^(file:\/{2})/, "")
             settingsCliPath.text = "CLI-Path:   " + path
         }
     }
-    
+
     DeleteMePopup {
         id: deleteMePopup
     }

+ 181 - 0
gui/src/climanager.cpp

@@ -0,0 +1,181 @@
+#include <QDebug>
+#include <QGuiApplication>
+
+#include <chrono>
+#include <csignal>
+#include <cstdio>
+#include <cstdlib>
+#include <iostream>
+#include <poll.h>
+#include <string>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <thread>
+#include <unistd.h>
+
+#include <iostream>
+#include <json/json.h>
+
+#include "../include/climanager.h"
+#include "../include/jsonhandler.h"
+
+using namespace std;
+
+thread readPipeLoopThread;
+thread notificationsLoopThread;
+thread statusLoopThread;
+
+namespace CliManager {
+
+QMLHandler *qmlHandler;
+bool programActive = true;
+int inpipefd[2];
+int outpipefd[2];
+
+char buf[1025];
+pid_t childpid;
+
+} // namespace CliManager
+
+void CliManager::writeToCli(QString command) {
+	command += "\n";
+	write(outpipefd[1], command.toUtf8().constData(), strlen(command.toUtf8().constData()));
+}
+
+void CliManager::onExit() {
+	// stop threads
+	CliManager::setProgramActive(false);
+	readPipeLoopThread.join();
+	notificationsLoopThread.join();
+	statusLoopThread.join();
+
+	writeToCli("exit");
+}
+
+void CliManager::setQmlHandler(QMLHandler *q) { qmlHandler = q; }
+
+void CliManager::init() {
+	pipe(inpipefd);
+	pipe(outpipefd);
+
+	childpid = fork();
+	if (childpid == 0) {
+		// Child
+		dup2(outpipefd[0], STDIN_FILENO);
+		dup2(inpipefd[1], STDOUT_FILENO);
+		// dup2(inpipefd[1], STDERR_FILENO);
+
+		// 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);
+
+		exit(1);
+	}
+
+	close(outpipefd[0]);
+	close(inpipefd[1]);
+
+	// start threads
+	readPipeLoopThread = thread(&CliManager::readPipeLoop);
+	notificationsLoopThread = thread(&CliManager::notificationsLoop);
+	statusLoopThread = thread(&CliManager::statusLoop);
+}
+
+std::vector<std::string> tokenizeByNewlines(std::string in) {
+	vector<string> res;
+	size_t index;
+	for (index = in.find("\n"); index != std::string::npos; index = in.find("\n")) {
+		if (index != 0)
+			res.push_back(in.substr(0, index));
+		in = in.substr(index + 1);
+	}
+	if (in.length() > 0)
+		res.push_back(in);
+	return res;
+}
+
+void CliManager::readPipeLoop() {
+	unsigned int readOffset = 0;
+	unsigned int pollCount = 0;
+	struct pollfd inPipeStatus;
+	inPipeStatus.fd = inpipefd[0];
+	inPipeStatus.events = POLLIN;
+	vector<string> inputs;
+	string pipeInput;
+
+	while (programActive) {
+		inputs = vector<string>();
+		poll(&inPipeStatus, 1, 100);
+
+		if (inPipeStatus.revents & POLLIN) {
+			readOffset += read(inpipefd[0], buf + readOffset, 1);
+			pollCount = 0;
+
+		} else {
+			pollCount++;
+		}
+
+		if (pollCount > 4 && (readOffset || pipeInput.size())) {
+			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();
+			memset(buf, 0, 1025);
+			pollCount = 0;
+			readOffset = 0;
+			if (waitpid(childpid, NULL, WNOHANG)) {
+				// nonzero means error or childid has changed state
+				// for us that means child has exited -> CLI is dead
+				break;
+			}
+		}
+
+		if (readOffset >= 1024) {
+			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 {
+				pipeInput = inputs[inputs.size() - 1]; // set last, potentially incomplete line, as current buffer
+			}
+			readOffset = 0;
+			pollCount = 0;
+			memset(buf, 0, 1025);
+		}
+	}
+}
+
+void CliManager::notificationsLoop() {
+	while (programActive) {
+		std::this_thread::sleep_for(std::chrono::milliseconds(3000));
+		writeToCli("notifications");
+		writeToCli("extendedlist");
+	}
+}
+
+void CliManager::statusLoop() {
+	while (programActive) {
+		std::this_thread::sleep_for(std::chrono::seconds(1));
+		writeToCli("status");
+		writeToCli("extendedstatus");
+	}
+}
+
+void CliManager::setProgramActive(bool active) { programActive = active; }

+ 167 - 14
gui/src/cmdmanager.cpp

@@ -1,26 +1,25 @@
 #include <QDebug>
 #include <QGuiApplication>
 
+#include "../include/climanager.h"
 #include "../include/cmdmanager.h"
 #include "../include/config.h"
-#include <boost/asio.hpp>
-#include <boost/filesystem.hpp>
-#include <boost/lexical_cast.hpp>
 #include <json/json.h>
 
-using boost::lexical_cast;
-using boost::asio::buffer;
 using namespace std;
 
 namespace CmdManager {
 QMLHandler *qmlHandler;
-map<string, void (*)(Json::Value root)> cmdmap;
+map<string, void (*)(Json::Value)> cmdmap;
+map<string, struct fileEntry> filemap;
 } // namespace CmdManager
 
 void CmdManager::init() {
+	cmdmap["error"] = &CmdManager::handleError;
 	cmdmap["status"] = &CmdManager::handleStatus;
 	cmdmap["close"] = &CmdManager::handleClose;
 	cmdmap["list"] = &CmdManager::handleList;
+	cmdmap["extendedlist"] = &CmdManager::handleExtendedList;
 	cmdmap["connect"] = &CmdManager::handleConnect;
 	cmdmap["version"] = &CmdManager::handleVersion;
 	cmdmap["login"] = &CmdManager::handleLogin;
@@ -31,24 +30,118 @@ void CmdManager::init() {
 	cmdmap["getdata"] = &CmdManager::handleGetData;
 	cmdmap["deleteme"] = &CmdManager::handleDeleteMe;
 	cmdmap["deletefile"] = &CmdManager::handleDeleteFile;
+	cmdmap["notifications"] = &CmdManager::handleNotifications;
+	cmdmap["queue"] = &CmdManager::handleQueue;
+	cmdmap["dequeue"] = &CmdManager::handleDequeue;
+	cmdmap["extendedstatus"] = &CmdManager::handleExtendedStatus;
+
+	filemap.clear();
 }
 
 void CmdManager::setQmlHandler(QMLHandler *q) { qmlHandler = q; }
 
-void CmdManager::executeCmd(string cmd, Json::Value root) { cmdmap[cmd](root); }
+void CmdManager::updateInternalFile(string name, string type, string method, int progress, float speed) {
+	map<string, struct fileEntry>::iterator it = filemap.find(name);
+	if (it != filemap.end()) {
+		it->second.type = type;
+		it->second.method = method;
+		it->second.progress = progress;
+		it->second.speed = speed;
+		it->second.dirty = true;
+	}
+}
+
+void CmdManager::emitFileList() {
+	map<string, struct fileEntry>::iterator it;
+	char speedbuf[256];
+	string progstr;
+
+	for (it = filemap.begin(); it != filemap.end(); it++) {
+		// This is a transfer
+		if (it->second.type.size()) {
+			// using a covert channel
+			if (it->second.method.size()) {
+				snprintf(speedbuf, 256, "%.2f B/s", it->second.speed);
+				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");
+		}
+		// else emit plain entry
+		else
+			emit qmlHandler->receivingUpdateFile(it->first.c_str(), "", false);
+	}
+}
+
+void CmdManager::cleanInternalList() {
+	map<string, struct fileEntry>::iterator it;
+
+	for (it = filemap.begin(); it != filemap.end(); it++) {
+		if (!it->second.dirty) {
+			it->second.type.clear();
+			it->second.method.clear();
+			it->second.progress = 0;
+			it->second.speed = 0;
+		} else
+			it->second.dirty = false;
+	}
+}
+
+void printInternalList() {
+	map<string, struct fileEntry>::iterator it;
+
+	for (it = CmdManager::filemap.begin(); it != CmdManager::filemap.end(); it++)
+		emit CmdManager::qmlHandler->log((string("IFL ENTRY ") + it->first + " " + it->second.type + " " + it->second.method + " " +
+		                                  std::to_string(it->second.progress) + " " + std::to_string(it->second.speed) + " " + std::to_string(it->second.dirty))
+		                                     .c_str());
+}
+
+void CmdManager::executeCmd(string cmd, Json::Value root) {
+	map<string, void (*)(Json::Value)>::iterator it = cmdmap.find(cmd);
+	if (it == cmdmap.end()) {
+		return;
+	}
+	cmdmap[cmd](root);
+}
+
+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()); }
 
-void CmdManager::handleClose(Json::Value root) { qmlHandler->setProgramActive(false); }
+void CmdManager::handleClose(Json::Value root) { CliManager::setProgramActive(false); }
 
 void CmdManager::handleList(Json::Value root) {
+	char sizebuf[256];
+	snprintf(sizebuf, 256, "%.2f", 4.2f);
 	if (root["accept"] == true) {
 		emit qmlHandler->receivingClearFileList();
+		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().c_str()), boost::filesystem::exists(fileNames[i].asString()));
+			emit qmlHandler->receivingListFile(QString::fromStdString(fileNames[i].asString()), QString::fromStdString(sizebuf), QString("Decryptable"),
+			                                   !!ifstream(fileNames[i].asString()));
+			filemap[fileNames[i].asString()] = {false, "", "", 0, 0};
+		}
+	} else {
+		emit qmlHandler->log(root["error"].asString().c_str());
+	}
+}
+
+void CmdManager::handleExtendedList(Json::Value root) {
+	char sizebuf[256];
+	if (root["accept"] == true) {
+		emit qmlHandler->receivingClearFileList();
+		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()));
+			filemap[f["name"].asString()] = {false, "", "", 0, 0};
 		}
 	} else {
 		emit qmlHandler->log(root["error"].asString().c_str());
@@ -58,7 +151,6 @@ void CmdManager::handleList(Json::Value root) {
 void CmdManager::handleConnect(Json::Value root) {
 	if (!root["accept"].asBool()) {
 		emit qmlHandler->ipPopupSetStatus(root["error"].asString().c_str());
-		qmlHandler->closeCLI();
 		emit qmlHandler->ipPopupEnableConnectButton();
 	}
 }
@@ -71,7 +163,6 @@ void CmdManager::handleVersion(Json::Value root) {
 		QString errorMessage =
 		    QString::fromStdString(string("Version mismatch: \nClient: " + root["clientversion"].asString() + "\nServer: " + root["serverversion"].asString()));
 		emit qmlHandler->ipPopupSetStatus(errorMessage);
-		qmlHandler->closeCLI();
 		emit qmlHandler->ipPopupEnableConnectButton();
 	}
 }
@@ -79,11 +170,10 @@ void CmdManager::handleVersion(Json::Value root) {
 void CmdManager::handleLogin(Json::Value root) {
 	if (root["accept"] == true) {
 		emit qmlHandler->loginSignupPopupClose();
-		qmlHandler->writeToCLI("list\n");
+		CliManager::writeToCli("extendedlist");
 		qmlHandler->loadSettingsToGUI();
 	} else {
 		emit qmlHandler->loginSetStatus(root["error"].asString().c_str());
-		qmlHandler->reopenCLI(qmlHandler->getIP());
 		emit qmlHandler->loginEnableLoginButton();
 	}
 }
@@ -93,7 +183,6 @@ void CmdManager::handleSignup(Json::Value root) {
 		emit qmlHandler->loginSignupPopupClose();
 	} else {
 		emit qmlHandler->signupSetStatus(root["error"].asString().c_str());
-		qmlHandler->reopenCLI(qmlHandler->getIP());
 		emit qmlHandler->signupEnableRegisterButton();
 	}
 }
@@ -144,3 +233,67 @@ void CmdManager::handleDeleteFile(Json::Value root) {
 		emit qmlHandler->log(message);
 	}
 }
+
+void CmdManager::handleNotifications(Json::Value root) {
+	if (root["accept"] == true) {
+		if (!root["messages"].isNull()) {
+			// Get the array of notifications
+			auto notifications = root["messages"];
+			for (int i = 0; i < notifications.size(); i++) {
+				emit qmlHandler->notification(QString::fromStdString(notifications[i].asString()));
+			}
+
+			if (notifications.size() > 1)
+				emit qmlHandler->showDesktopNotification("Covert Channel - New Notifications",
+				                                         "You have multiple new notifications. Open the program to see them.");
+			else if (notifications.size() == 1)
+				emit qmlHandler->showDesktopNotification("Covert Channel - New Notification", QString::fromStdString(notifications[0].asString()));
+		}
+	} else {
+		emit qmlHandler->log(root["error"].asString().c_str());
+	}
+}
+
+void CmdManager::handleQueue(Json::Value root) {
+	if (root["accept"] == false) {
+		QString errorMessage = QString::fromStdString(string("Error when queueing file " + root["file"].asString() + ":\n" + root["error"].asString()));
+		emit qmlHandler->log(errorMessage);
+	}
+}
+
+void CmdManager::handleDequeue(Json::Value root) {
+	if (root["accept"] == false) {
+		QString errorMessage = QString::fromStdString(string("Error when dequeueing file " + root["file"].asString() + ":\n" + root["error"].asString()));
+		emit qmlHandler->log(errorMessage);
+	}
+}
+
+void CmdManager::handleExtendedStatus(Json::Value root) {
+	Json::Value cs, ss;
+	string typestring;
+	if (!root["accept"].asBool()) {
+		QString errorMessage = QString::fromStdString(string("Error when loading status " + root["error"].asString()));
+		emit qmlHandler->log(errorMessage);
+	} else {
+		cs = root["transfersclientserver"];
+		ss = root["transfersserverserver"];
+		if (!cs.isNull()) {
+			for (Json::Value t : cs) {
+				updateInternalFile(t["file"].asString(), t["upload"].asBool() ? "Upload" : "Download", "", t["progress"].asInt(), 0);
+			}
+		}
+		if (!ss.isNull()) {
+			for (Json::Value t : ss) {
+				if (t["type"] == "download")
+					typestring = "Receiving";
+				else if (t["type"] == "upload")
+					typestring = "Sending";
+				else if (t["type"] == "queued")
+					typestring = "Queued";
+				updateInternalFile(t["file"].asString(), typestring, t["method"].asString(), t["progress"].asInt(), t["speed"].asFloat());
+			}
+		}
+		cleanInternalList();
+		emitFileList();
+	}
+}

+ 3 - 8
gui/src/config.cpp

@@ -1,9 +1,5 @@
 #include "config.h"
 #include <QDebug>
-#include <boost/lexical_cast.hpp>
-
-using boost::bad_lexical_cast;
-using boost::lexical_cast;
 
 namespace Config {
 std::map<std::string, std::string> configuration;
@@ -37,11 +33,10 @@ bool Config::checkConfig() {
 	if (autofill_user != "0" && autofill_user != "1")
 		return false;
 
-	try {
-		int covert_method = lexical_cast<int>(getValue("Covert-Channel-Method"));
-	} catch (bad_lexical_cast &e) {
+	std::istringstream iss(getValue("Covert-Channel-Method"));
+	int covert_method;
+	if (!(iss >> covert_method))
 		return false;
-	}
 
 	return true;
 }

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


+ 2 - 0
gui/src/jsonhandler.cpp

@@ -15,6 +15,8 @@ void JsonHandler::parseJSON(string buffer) {
 	// boolean
 	bool parsingSuccessful = reader->parse(buffer.c_str(), buffer.c_str() + buffer.size(), &root, &jsonError);
 
+	delete (reader);
+
 	// If the string is not correct Json, return
 	if (!parsingSuccessful) {
 		return;

+ 7 - 4
gui/src/main.cpp

@@ -1,4 +1,4 @@
-#include <QGuiApplication>
+#include <QApplication>
 #include <QObject>
 #include <QQmlApplicationEngine>
 #include <QQmlComponent>
@@ -13,6 +13,7 @@
 #include <thread>
 #include <unistd.h>
 
+#include "../include/climanager.h"
 #include "../include/cmdmanager.h"
 #include "../include/jsonhandler.h"
 #include "../include/qmlhandler.h"
@@ -25,13 +26,14 @@ int main(int argc, char *argv[]) {
 	// This has to be here to fix warnings
 	QCoreApplication::setOrganizationName("CCats");
 
-	QGuiApplication app(argc, argv);
+	QApplication app(argc, argv);
 
 	QQmlApplicationEngine engine;
 
 	QMLHandler qmlHandler;
 	CmdManager::setQmlHandler(&qmlHandler);
 	CmdManager::init();
+	CliManager::setQmlHandler(&qmlHandler);
 
 	// Set the context for the window, so that the qml files can be connected to
 	// the qmlHandler
@@ -49,10 +51,9 @@ int main(int argc, char *argv[]) {
 
 	// If we land here, the window has been closed. Properly disconnect from the
 	// server now
-	qmlHandler.onExit();
+	CliManager::onExit();
 
 	if (_RESTART) {
-		// DO SHIT
 		pid_t pid = 0;
 		pid = fork();
 		if (pid == 0) {
@@ -61,5 +62,7 @@ int main(int argc, char *argv[]) {
 		}
 	}
 
+	delete object;
+
 	return ret;
 }

+ 5 - 0
gui/src/qml.qrc

@@ -24,6 +24,11 @@
         <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>

+ 49 - 146
gui/src/qmlhandler.cpp

@@ -12,147 +12,37 @@
 #include <thread>
 #include <unistd.h>
 
+#include "../include/climanager.h"
 #include "../include/config.h"
 #include "../include/jsonhandler.h"
 #include "../include/qmlhandler.h"
-#include <boost/asio.hpp>
-#include <boost/filesystem.hpp>
-#include <boost/lexical_cast.hpp>
 #include <iostream>
 #include <json/json.h>
 
-using boost::lexical_cast;
-using boost::asio::buffer;
-
 using namespace std;
 
-int inpipefd[2];
-int outpipefd[2];
-
-char buf[1025];
 QUrl sendFileUrl = QUrl("");
-QString _IP = "";
-bool _CLI_RUNNING = false;
 bool _RESTART = false;
-pid_t childpid;
-
-bool programActive = true;
 
 QMLHandler::QMLHandler(QObject *parent) : QObject(parent) {}
 
-void QMLHandler::closeCLI() { _CLI_RUNNING = false; }
-
 void QMLHandler::loadSettingsToGUI() {
-	int covertMethod = lexical_cast<int>(Config::getValue("Covert-Channel-Method"));
-	bool saveIP = lexical_cast<bool>(Config::getValue("Autofill-IP"));
-	bool saveUsername = lexical_cast<bool>(Config::getValue("Autofill-Username"));
-	QString cliPath = QString::fromStdString(Config::getValue("CLI-Path"));
-	emit loadSettings(covertMethod, saveIP, saveUsername, cliPath);
-}
-
-void QMLHandler::reopenCLI(QString ip) {
-	if (_CLI_RUNNING) {
-		waitpid(childpid, NULL, 0);
-		closeCLI();
-	}
+	stringstream ss;
 
-	_IP = ip;
+	int covertMethod;
+	ss << Config::getValue("Covert-Channel-Method");
+	ss >> covertMethod;
 
-	pipe(inpipefd);
-	pipe(outpipefd);
+	bool saveIP;
+	ss << Config::getValue("Autofill-IP");
+	ss >> saveIP;
 
-	childpid = fork();
-	if (childpid == 0) {
-		// Child
-		dup2(outpipefd[0], STDIN_FILENO);
-		dup2(inpipefd[1], STDOUT_FILENO);
-		// dup2(inpipefd[1], STDERR_FILENO);
+	bool saveUsername;
+	ss << Config::getValue("Autofill-Username");
+	ss >> saveUsername;
 
-		// ask kernel to deliver SIGTERM in case the parent dies
-		prctl(PR_SET_PDEATHSIG, SIGTERM);
-
-		// Set the path to the CLI - pass argument h (help) for now
-		// TODO: Change hardcoded path
-		// execl("../../cli/build/ccats-cli", "ccats-cli", ip.toUtf8().constData(), "--machine", (char *)NULL);
-		execl(Config::getValue("CLI-Path").c_str(), "ccats-cli", ip.toUtf8().constData(), "--machine", (char *)NULL);
-
-		exit(1);
-	}
-
-	_CLI_RUNNING = true;
-
-	close(outpipefd[0]);
-	close(inpipefd[1]);
-	std::thread(&QMLHandler::readPipeLoop, this).detach();
-}
-
-std::vector<std::string> tokenizeByNewlines(std::string in) {
-	vector<string> res;
-	size_t index;
-	for (index = in.find("\n"); index != std::string::npos; index = in.find("\n")) {
-		if (index != 0)
-			res.push_back(in.substr(0, index));
-		in = in.substr(index + 1);
-	}
-	if (in.length() > 0)
-		res.push_back(in);
-	return res;
-}
-
-void QMLHandler::onExit() { write(outpipefd[1], "disconnect\n", strlen("disconnect\n")); }
-
-// This method is a loop which runs in a seperate thread.
-// If will read the Input Pipe, which containts the string that get printed
-// on stdout by the CLI/Server, and calls handleJSON with the read content
-// one it reads a newline.
-void QMLHandler::readPipeLoop() {
-	unsigned int readOffset = 0;
-	unsigned int pollCount = 0;
-	struct pollfd inPipeStatus;
-	inPipeStatus.fd = inpipefd[0];
-	inPipeStatus.events = POLLIN;
-	vector<string> inputs;
-	string pipeInput;
-
-	while (programActive && _CLI_RUNNING) {
-		inputs = vector<string>();
-		poll(&inPipeStatus, 1, 100);
-
-		if (inPipeStatus.revents & POLLIN) {
-			readOffset += read(inpipefd[0], buf + readOffset, 1);
-			pollCount = 0;
-
-		} else {
-			pollCount++;
-		}
-
-		if (pollCount > 4 && (readOffset || pipeInput.size())) {
-			pipeInput.append(buf);
-			inputs = tokenizeByNewlines(pipeInput);
-			for (string s : inputs) {
-				emit log(QString::fromStdString(s));
-				qInfo() << QString::fromStdString(s);
-				// handleJSON(s);
-				JsonHandler::parseJSON(s);
-			}
-			pipeInput = string();
-			memset(buf, 0, 1025);
-			pollCount = 0;
-			readOffset = 0;
-			if (waitpid(childpid, NULL, WNOHANG)) {
-				// nonzero means error or childid has changed state
-				// for us that means child has exited -> CLI is dead
-				break;
-			}
-		}
-
-		if (readOffset >= 1024) {
-			pipeInput.append(buf);
-			readOffset = 0;
-			pollCount = 0;
-			memset(buf, 0, 1025);
-		}
-	}
+	QString cliPath = QString::fromStdString(Config::getValue("CLI-Path"));
+	emit loadSettings(covertMethod, saveIP, saveUsername, cliPath);
 }
 
 // ### QML Handlers ###
@@ -164,8 +54,7 @@ void QMLHandler::onStart() {
 		// Config exists
 		if (Config::checkConfig() == true) {
 			// Config is valid
-			if (!boost::filesystem::is_regular_file(Config::getValue("CLI-Path")) ||
-			    boost::filesystem::path(Config::getValue("CLI-Path")).filename().compare("ccats-cli")) {
+			if (!ifstream(Config::getValue("CLI-Path"))) {
 				// Invalid CLI Path
 				emit invalidCliPathPopupOpen();
 			}
@@ -186,6 +75,8 @@ void QMLHandler::onStart() {
 		Config::setupDefaultConfig();
 		emit noConfigFoundPopupOpen();
 	}
+
+	CliManager::init();
 }
 
 // No Config Found Popup
@@ -219,8 +110,9 @@ void QMLHandler::onSendingSelectFileButton(QUrl url) {
 }
 
 void QMLHandler::onSendingSendFileButton() {
-	QString command = "put " + sendFileUrl.toString() + "\n";
-	write(outpipefd[1], command.toUtf8().constData(), strlen(command.toUtf8().constData()));
+	QString command = "put \"" + sendFileUrl.toString() + "\"";
+	CliManager::writeToCli(command);
+	CliManager::writeToCli("extendedlist");
 }
 
 void QMLHandler::onSendingClearSelectionButton() {
@@ -231,16 +123,17 @@ void QMLHandler::onSendingClearSelectionButton() {
 }
 
 // Receiving
-void QMLHandler::onReceivingListFilesButton() { write(outpipefd[1], "list\n", strlen("list\n")); }
+void QMLHandler::onReceivingListFilesButton() { CliManager::writeToCli("extendedlist"); }
 
 void QMLHandler::onReceivingDownloadFileButton(QString fileName) {
-	QString command = "get " + fileName + "\n";
-	write(outpipefd[1], command.toUtf8().constData(), strlen(command.toUtf8().constData()));
+	QString command = "get \"" + fileName + "\"";
+	CliManager::writeToCli(command);
 }
 
 void QMLHandler::onReceivingConfirmDeleteFileButton(QString fileName) {
-	QString command = "deletefile " + fileName + "\n";
-	write(outpipefd[1], command.toUtf8().constData(), strlen(command.toUtf8().constData()));
+	QString command = "deletefile \"" + fileName + "\"";
+	CliManager::writeToCli(command);
+	CliManager::writeToCli("extendedlist");
 }
 
 // Messages
@@ -248,8 +141,8 @@ void QMLHandler::onMessagesSendButton(QString msg) { emit message(msg); }
 
 // Settings
 void QMLHandler::onSettingsDeleteMeButton(QString password) {
-	QString command = "deleteme " + password + "\n";
-	write(outpipefd[1], command.toUtf8().constData(), strlen(command.toUtf8().constData()));
+	QString command = "deleteme " + password;
+	CliManager::writeToCli(command);
 }
 
 void QMLHandler::onSettingsRevertChangesButton() {
@@ -266,9 +159,9 @@ void QMLHandler::onSettingsResetButton() {
 }
 
 void QMLHandler::onSettingsSaveButton(int covertMethod, bool saveIP, bool saveUsername, QString cliPath) {
-	Config::setValue("Covert-Channel-Method", lexical_cast<string>(covertMethod));
-	Config::setValue("Autofill-IP", lexical_cast<string>(saveIP));
-	Config::setValue("Autofill-Username", lexical_cast<string>(saveUsername));
+	Config::setValue("Covert-Channel-Method", to_string(covertMethod));
+	Config::setValue("Autofill-IP", to_string(saveIP));
+	Config::setValue("Autofill-Username", to_string(saveUsername));
 	Config::setValue("CLI-Path", cliPath.toUtf8().constData());
 	Config::saveFile();
 	emit log("Settings saved.");
@@ -276,7 +169,9 @@ void QMLHandler::onSettingsSaveButton(int covertMethod, bool saveIP, bool saveUs
 
 // Ip Popup
 void QMLHandler::onIpPopupConnectButton(QString ip, bool saveAsDefault) {
-	reopenCLI(ip);
+	QString command = "connect " + ip;
+	CliManager::writeToCli(command);
+
 	emit ipPopupDisableConnectButton();
 	if (saveAsDefault) {
 		Config::setValue("Default-IP", ip.toUtf8().constData());
@@ -287,8 +182,8 @@ void QMLHandler::onIpPopupConnectButton(QString ip, bool saveAsDefault) {
 
 // Login
 void QMLHandler::onLoginLoginButton(QString username, QString password, bool saveAsDefault) {
-	QString command = "login " + username + " " + password + "\n";
-	write(outpipefd[1], command.toUtf8().constData(), strlen(command.toUtf8().constData()));
+	QString command = "login " + username + " " + password;
+	CliManager::writeToCli(command);
 	emit loginDisableLoginButton();
 	if (saveAsDefault) {
 		Config::setValue("Default-Username", username.toUtf8().constData());
@@ -303,8 +198,8 @@ void QMLHandler::onSignupRegisterButton(QString username, QString passwordOne, Q
 		emit signupSetStatus("Passwords don't match");
 		return;
 	}
-	QString command = "signup " + username + " " + passwordOne + "\n";
-	write(outpipefd[1], command.toUtf8().constData(), strlen(command.toUtf8().constData()));
+	QString command = "signup " + username + " " + passwordOne;
+	CliManager::writeToCli(command);
 	emit signupDisableRegisterButton();
 	if (saveAsDefault) {
 		Config::setValue("Default-Username", username.toUtf8().constData());
@@ -314,12 +209,20 @@ void QMLHandler::onSignupRegisterButton(QString username, QString passwordOne, Q
 }
 
 // Footer
-void QMLHandler::onFooterGetStatusButton() { write(outpipefd[1], "status\n", strlen("status\n")); }
+void QMLHandler::onFooterGetStatusButton() { CliManager::writeToCli("extendedstatus"); }
 
-void QMLHandler::setProgramActive(bool active) { programActive = active; }
+// Notifications
+void QMLHandler::onDismissNotificationButton(int index) { emit dismissNotification(index); }
 
-void QMLHandler::writeToCLI(QString command) { write(outpipefd[1], command.toUtf8().constData(), strlen(command.toUtf8().constData())); }
+// Queueing
+void QMLHandler::onReceivingQueueFileButton(QString fileName) {
+	QString command = "queue " + fileName;
+	CliManager::writeToCli(command);
+}
 
-QString QMLHandler::getIP() { return _IP; }
+void QMLHandler::onReceivingDequeueFileButton(QString fileName) {
+	QString command = "dequeue " + fileName;
+	CliManager::writeToCli(command);
+}
 
 void QMLHandler::setRestart(bool restart) { _RESTART = restart; }

+ 1 - 1
gui/src/qtquickcontrols2.conf

@@ -7,4 +7,4 @@ Style=Material
 
 [Material]
 Theme=Dark
-Accent=Blue
+Accent=Cyan

+ 4 - 0
libs/libbcrypt/.gitignore

@@ -0,0 +1,4 @@
+*.o
+*.a
+*.3
+!crypt_blowfish/crypt.3

+ 121 - 0
libs/libbcrypt/COPYING

@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+    HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+  i. the right to reproduce, adapt, distribute, perform, display,
+     communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+     likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+     subject to the limitations in paragraph 4(a), below;
+  v. rights protecting the extraction, dissemination, use and reuse of data
+     in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+     European Parliament and of the Council of 11 March 1996 on the legal
+     protection of databases, and under any national implementation
+     thereof, including any amended or successor version of such
+     directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+     world based on applicable law or treaty, and any national
+     implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+    surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+    warranties of any kind concerning the Work, express, implied,
+    statutory or otherwise, including without limitation warranties of
+    title, merchantability, fitness for a particular purpose, non
+    infringement, or the absence of latent or other defects, accuracy, or
+    the present or absence of errors, whether or not discoverable, all to
+    the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+    that may apply to the Work or any use thereof, including without
+    limitation any person's Copyright and Related Rights in the Work.
+    Further, Affirmer disclaims responsibility for obtaining any necessary
+    consents, permissions or other rights required for any use of the
+    Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+    party to this document and has no duty or obligation with respect to
+    this CC0 or use of the Work.

+ 22 - 0
libs/libbcrypt/Makefile

@@ -0,0 +1,22 @@
+CC = gcc
+CFLAGS = $(shell grep '^CFLAGS = ' crypt_blowfish/Makefile | cut -d= -f2-)
+.PHONY: crypt_blowfish
+
+all: bcrypt.a
+
+test: bcrypt.c crypt_blowfish
+	$(CC) $(CFLAGS) -DTEST_BCRYPT -c bcrypt.c
+	$(CC) -o bcrypt_test bcrypt.o crypt_blowfish/*.o
+
+bcrypt.a: bcrypt.o crypt_blowfish
+	ar r bcrypt.a bcrypt.o crypt_blowfish/*.o
+
+bcrypt.o: bcrypt.c
+	$(CC) $(CFLAGS) -c bcrypt.c
+
+crypt_blowfish:
+	$(MAKE) -C crypt_blowfish
+
+clean:
+	rm -f *.o bcrypt_test bcrypt.a *~ core
+	$(MAKE) -C crypt_blowfish clean

+ 14 - 0
libs/libbcrypt/README

@@ -0,0 +1,14 @@
+This library is a simple wrapper providing a convenient reentrant interface for
+the bcrypt password hashing algorithm implementation as provided by Solar
+Designer at http://www.openwall.com/crypt/. An exact copy of that source code
+is included in the crypt_blowfish subdirectory.
+
+The provided C interface is inspired in the bcrypt Python module that can be
+found at http://code.google.com/p/py-bcrypt/, and consists of a function to
+generate salts with the characteristic work factor parameter, one to generate
+password hashes which can also used to verify passwords and one designed
+specifically to verify passwords and avoid timing attacks. Please check the
+header bcrypt.h. It contains the prototypes and lots of comments with examples.
+
+All this code is released to the public domain under the terms of CC0. See the
+COPYING file for the legal text.

Some files were not shown because too many files changed in this diff